Skip to content

Refunds, returns, disputes, and inventory

Refunds, returns, credit notes, and chargebacks are different facts. They may occur independently and must not share one status or operation.

Source/customer evidence and reporting dimensions follow the data governance and warehouse marts contracts.

Definitions

Concept Source fact Odoo consequence
Refund Merchant/provider sends money back Usually a credit note and payment/refund clearing treatment
Return Goods physically move back or are disposed Native return picking or approved non-restock disposition
Credit note Legal/accounting correction Posted Odoo refund move linked to original invoice
Cancellation Commercial order/line is cancelled Sale-order adjustment; credit note only if invoiced
Chargeback Provider forcibly reverses funds during a dispute Clearing/receivable and fee treatment under dispute policy
Exchange Return plus a new commercial supply Separate return and replacement order; no netting shortcut

Refund workflow

observed -> validated -> accounting_review | ready
ready -> credit_note_creating -> credit_note_posted
credit_note_posted -> refund_payment_recorded -> reconciled
any mutation state -> verification_required -> confirmed | repair_required

Validation requires the original order/invoice mapping, exact refunded lines or explicit order-level adjustment, amount/currency, source refund transaction, tax treatment, refund date, and cumulative-refund limit. The platform rejects a refund that would exceed the source paid amount or Odoo invoice amount unless an accounting reviewer approves a separately classified adjustment.

Return workflow

requested -> authorized -> in_transit -> received
received -> inspected -> restock | scrap | supplier_return | exception
restock -> odoo_return_created -> odoo_return_validated

A source return does not prove warehouse receipt. The inventory operation waits for the authority defined by the tenant inventory policy. Quantities are bounded by fulfilled minus previously returned quantities for the same source line and Odoo move.

Dispute workflow

opened -> evidence_required -> submitted -> won | lost | expired
opened -> accepted_loss

The platform stores provider deadlines and evidence status but does not infer legal/accounting treatment. A lost or accepted dispute creates settlement lines for principal, fees, and adjustments. A won dispute creates the corresponding release/credit lines. Each line posts through approved mappings and settlement reconciliation.

Inventory authority matrix

Inventory has four separate authorities:

Authority Meaning Pilot owner
Commercial availability Quantity offered for sale Shopify unless tenant explicitly syncs from Odoo
Physical custody Where units are physically held Tenant warehouse/fulfillment provider
Operational movement Pick, ship, receive, return, scrap Odoo for merchant-fulfilled pilot
Accounting valuation Stock valuation and COGS entries Odoo

The pilot supports merchant-fulfilled Shopify orders where Odoo owns operational movement and valuation. Amazon FBA, third-party fulfillment, consignment, dropship, and source-owned stock are deferred profiles.

Odoo-owned inventory flow

  1. Map the Shopify location to one Odoo warehouse and allowed source/destination locations.
  2. Confirm the Odoo sale order and use the native delivery picking.
  3. Apply source fulfillment quantities only to mapped sale lines/moves.
  4. Validate the picking through the approved command after quantity and state checks.
  5. Read back move and picking state; Odoo computes stock and valuation effects.
  6. Compare source fulfilled quantities to Odoo delivered quantities.
  7. For returns, create a native return from the original picking, validate the approved disposition, and read back stock and valuation state.

The platform never creates arbitrary stock moves or inventory adjustments from an external balance. Balance discrepancies produce an inventory reconciliation case.

Inventory reconciliation

create table inventory_reconciliation_cases (
  tenant_id uuid not null,
  id uuid not null default gen_random_uuid(),
  source_connection_id uuid not null,
  product_mapping_id uuid not null,
  source_location_id text not null,
  odoo_warehouse_id bigint not null,
  comparison_at timestamptz not null,
  source_available numeric(20,6),
  source_on_hand numeric(20,6),
  odoo_available numeric(20,6),
  odoo_on_hand numeric(20,6),
  in_transit_adjustment numeric(20,6) not null default 0,
  difference numeric(20,6) not null,
  state text not null check (state in ('open','explained','adjustment_approved','resolved','ignored')),
  explanation_code text,
  approved_operation_id uuid,
  primary key (tenant_id, id)
);

available and on_hand are not interchangeable. The comparison records reservations, in-transit timing, unprocessed returns, and source update time before declaring a discrepancy.

Required safeguards

  • Refund, credit-note, return, and dispute idempotency keys use their own source object IDs and operation types.
  • A credit note cannot be posted merely because a return exists; financial evidence is required.
  • A return cannot increase stock merely because a refund exists; physical receipt/disposition evidence is required.
  • Cumulative refunded and returned quantities are checked across all prior confirmed operations.
  • Original tax, fiscal position, currency, and invoice links are read before reversal.
  • Closed-period handling follows the approved accounting policy and never silently changes dates.
  • Unsupported restocking fees, exchanges, gift cards, store credit, or cross-currency refunds enter POLICY_UNSUPPORTED.

Acceptance cases

Tests include full and partial refunds, shipping-only refund, goodwill refund, refund without return, return without refund, partial return, non-restock return, cancellation before invoice, cancellation after invoice, duplicate refund webhook, late refund correction, chargeback opened/won/lost, and timeout after credit-note or return mutation.