Skip to content

Ecommerce accounting policy

Accounting behavior is selected by a versioned tenant policy. Source adapters may report facts, but they do not decide posting treatment.

Mapping-set lifecycle, deterministic resolution, approval, and drift are defined by the mappings and master data contract.

Pilot posting profile

The only pilot profile is order_invoice_v1:

  • One canonical order maps to one Odoo sale order.
  • Canonical order lines map to Odoo sale order lines after SKU/product and tax validation.
  • Odoo sale orders are confirmed through native Odoo behavior.
  • Customer invoices are created from delivered quantities under the tenant's approved Odoo invoice policy.
  • Payments are recorded against a payment-provider clearing journal and reconciled to posted invoices.
  • Source refunds create credit-note intents; physical returns create separate stock-return intents.
  • Provider fees, reserves, withholdings, disputes, and payout differences are posted from settlement evidence, not inferred from order totals.
  • Payouts clear the provider clearing account against the imported bank transaction.

Daily or settlement-summary posting is not a mode switch. It is a future, separately versioned posting profile with different source grouping, tax evidence, Odoo mappings, and idempotency semantics.

Policy record

create table tenant_posting_policies (
  tenant_id uuid not null,
  id uuid not null default gen_random_uuid(),
  version integer not null,
  profile text not null check (profile in ('order_invoice_v1')),
  effective_from date not null,
  effective_to date,
  status text not null check (status in ('draft','approved','retired')),
  company_id bigint not null,
  base_currency_code char(3) not null,
  timezone text not null,
  invoice_trigger text not null check (invoice_trigger in ('delivery')),
  customer_strategy text not null check (customer_strategy in ('named_partner','anonymous_b2c_partner')),
  tax_strategy text not null check (tax_strategy in ('source_tax_code_mapping')),
  fx_strategy text not null check (fx_strategy in ('odoo_rate_on_accounting_date')),
  rounding_strategy text not null check (rounding_strategy in ('explicit_rounding_line')),
  mapping_set_version integer not null,
  approved_by uuid,
  approved_at timestamptz,
  policy_json jsonb not null,
  created_at timestamptz not null default now(),
  primary key (tenant_id, id),
  unique (tenant_id, version)
);

Approved policy versions are immutable. Corrections create a new version with a non-overlapping effective range. An operation captures policy_id, policy_version, and mapping_set_version.

Required mappings

Mapping Key Target/evidence
Company source connection + market allowed Odoo company
Product source platform + variant/SKU Odoo product or approved fallback product
Customer source customer or B2C policy Odoo partner strategy
Tax source tax title/rate/jurisdiction/inclusive flag Odoo sale tax and fiscal position evidence
Shipping source shipping code Odoo service product and tax
Discount source discount type line allocation or discount product policy
Payment provider + transaction type + currency Odoo clearing journal and account
Fee provider + fee type + currency expense account, tax, analytic dimensions
Warehouse source location/fulfillment service Odoo warehouse, picking type, locations
Return source reason/location return picking and disposition policy
Dispute provider + dispute stage clearing/expense account and manual-review rule

Mappings have effective dates, version, status, source evidence, target company, approver, and last capability-check timestamp. Missing or ambiguous mappings produce MAPPING_REQUIRED; workers never choose the first search result.

Dates and periods

Record Accounting date rule
Sale order Canonical order date in tenant timezone
Customer invoice Approved tax point; pilot default is Odoo invoice creation date after delivery
Payment Provider capture/settlement transaction date under the approved policy
Credit note Refund/accounting correction date allowed by policy and open period
Fee/adjustment Settlement line posted date
Payout clearing Bank value date when matched

If the required Odoo period is closed, the operation enters blocked with ACCOUNTING_PERIOD_CLOSED. The platform does not silently move the date. An accounting reviewer may approve the explicitly configured next-open-period treatment where law and policy permit.

Tax

  • Preserve source taxable base, rate, amount, jurisdiction, title/code, and inclusive/exclusive flag.
  • Resolve a tax only within the target company and approved fiscal position.
  • Validate Odoo-computed invoice totals against canonical expected totals before posting.
  • Do not derive tax from a gross total when source tax lines are required but absent.
  • Marketplace deemed-supplier, OSS/IOSS, mixed-rate bundle, gift card, and reverse-charge cases are unsupported until separate policy rules and fixtures exist.
  • A rate match alone is insufficient when multiple jurisdictions or tax scopes share the same rate.

Currency and foreign exchange

  • The pilot accepts one order currency and one settlement currency per posting intent. Cross-currency payout allocation is unsupported.
  • Source, order, presentment, settlement, bank, company, and Odoo document currencies are stored independently.
  • Currency conversion uses the Odoo rate on the approved accounting date under odoo_rate_on_accounting_date.
  • Source-provided FX amounts and rates are retained as evidence and compared to Odoo results.
  • Realized exchange differences are created by Odoo during reconciliation, not calculated as hidden Accountify adjustments.

Rounding and tolerances

  • Canonical control totals use the currency's approved precision and deterministic decimal rounding.
  • Differences caused by line-to-document rounding are represented by an explicit rounding adjustment line.
  • Automatic source-to-document tolerance is zero unless the policy defines a named rounding rule.
  • Settlement balancing may allow a maximum currency-unit rounding tolerance, default 0.01 for EUR, only when the difference is classified as rounding and posted to the approved account.
  • Amount, currency, and sign must all match. Absolute-value matching is prohibited.

Posting intent

Before creating an operation, the policy engine produces an immutable posting intent containing:

intent_id and intent_version
tenant, source connection, Odoo connection, company
posting profile and policy version
canonical aggregate IDs and source payload hashes
target command type
accounting date and currency
expected Odoo records and totals
resolved mapping IDs and versions
approval requirement and approval evidence
precondition read versions
canonical request hash

Changing any accounting-relevant input produces a new intent version and request hash. An approved intent cannot be edited.

Approval policy

The pilot may auto-approve ordinary orders only after the tenant completes a monitored shadow period and the case matches an approved policy with no exception flags. The following always require review or fail closed:

  • new or changed tax, journal, fee, warehouse, product-fallback, or customer strategy;
  • source control-total mismatch;
  • manual source-object match;
  • closed-period date override;
  • settlement difference above the rounding rule;
  • chargeback, reserve, withholding, unknown adjustment, or cross-currency event;
  • operation verification or repair after a timeout-unknown result.