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.01for 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.