Skip to Content

Business document lifecycle contract v1

The Exchange library validates structured lifecycle snapshots. It reuses the order contract and its exact account-independent envelope, canonical UUIDs, frozen parties, Core address snapshots, timestamps, revision rules, units, decimal precision, half-up currency-increment rounding, and 64 KiB total JSON limit. Decode wire JSON with Transport parse_json, then call validate_document_envelope to validate and obtain its SHA-256 digest. The validators do not authenticate senders or implement native delivery.

Common snapshot fields

Snapshots have exactly exchange_uuid, document_uuid, revision, previous_digest, kind, source_state, issued_at, document_date, seller, and buyer, plus the document-specific fields below. Invoice dates are seller invoice dates; picking dates identify completed shipment/return dates. Receiver accounting dates are determined independently by receiver settings and open periods. The original complete envelope digest is required for every subsequent revision.

A document reference is exactly {origin_instance_uuid, document_uuid, revision, digest}. A line/move reference adds line_uuid. Dependencies must belong to one of the two peers, never a third instance or the current document. Lists contain at most 200 unique references. Header and line dependency references must agree, including revision and digest. Unresolved declared references remain pending review; absence of an order reference must be explicit rather than inferred by dropping an unresolved link.

Invoices and credit notes

Capabilities are invoice.snapshot and credit_note.snapshot. The seller sends kind: invoice or kind: credit_note, with source_state: posted. Extra fields are:

  • currency: {code, rounding}, unchanged quotation currency.
  • lines: 1–200 unique commercial lines.
  • untaxed_amount, tax_amount, total_amount: signed decimal strings.
  • order_references, delivery_references: exact document dependencies, or [] for genuinely standalone invoices.
  • original_reference: null for invoices; required original seller invoice dependency for credit notes.
  • replaces_reference: null or the exact replaced seller document dependency; a replacement always has a distinct document identity.
  • attachments: frozen metadata including at least one invoice PDF.

Invoice lines have exactly line_uuid, product_uuid, description, quantity, unit, pricing, source_evidence, order_line_references, and delivery_move_references. Product is a stable variant UUID or null. Pricing is {unit_price, discount, untaxed_amount}; source evidence is {unit_price, discount, untaxed_amount, total_amount, taxes} with tax entries {label, amount, price_included}. Source evidence is required for accounting lines. Descriptions, units, and tax evidence have the order contract's bounds.

Accounting quantity is nonzero and can be negative. Prices, source monetary evidence, tax amounts, and untaxed amounts can be signed to retain correction lines. Discounts remain between 0 and 100; negative zero is rejected. Each commercial line must satisfy the declared rounded quantity/price/discount amount; header untaxed amount is the exact sum of those line amounts. Header total equals untaxed plus source tax amount. Tax labels and amounts are evidence, never local tax instructions.

Native document kind determines invoice/refund polarity: a positive seller credit note line becomes a positive vendor refund line, not a negative ordinary bill. Signed correction lines keep their signs within that kind. Local tax differences are informational, while unexplained price/quantity/currency/untaxed-total differences require acknowledgment before receiver posting. Receiver posting, payments, and reconciliation remain manual.

Deliveries and customer returns

Capability picking.snapshot uses kind: picking, source_state: done, and a seller-to-buyer direction. Capability return.snapshot uses kind: return, source_state: done, and a buyer-to-seller direction for a customer return of the original shipment. Extra fields are order_references, original_reference, backorder_reference, lines, and attachments.

Each delivery has at least one exact order dependency and 1–200 unique moves. Each move has exactly line_uuid, product_uuid, description, quantity, unit, order_line_references, and original_move_reference. Product UUID and at least one order-line link are required. Quantities are positive actual completed quantities, not ordered quantities; direction supplies shipment/return polarity.

original_reference and each move's original_move_reference are null on ordinary deliveries. Customer returns require them; every original move must identify the exact original delivery revision/digest declared in the header. backorder_reference is null or an exact earlier picking dependency. Partial shipments have distinct document and move identities against the same order. The receiver reuses eligible native order-generated receipts and never validates them automatically. Returns never automatically create credit notes or refunds.

Cancellation and replacement notices

Capability document.cancel uses kind: cancellation, source_state: cancel, and the sender's original document ownership direction. Extra fields are original_reference and replacement_reference. The former is required; the latter is null or an exact sender-owned replacement dependency. Notice, original, and replacement document identities must be distinct. Missing replacement dependencies stay pending review. A notice never cancels or overwrites native orders, completed stock moves, or posted accounting records.

Attachments

Attachment metadata has exactly {attachment_uuid, name, mimetype, size, digest, purpose}. UUID and SHA-256 bind frozen content; names are bounded to 256 UTF-8 bytes. There are at most 10 distinct attachment UUIDs, 5 MiB per file, and 10 MiB combined. Allowed MIME types are PDF, PNG, and JPEG. Purpose is supporting or, on invoices/refunds only, invoice_pdf; invoice PDFs must use application/pdf.

This contract carries metadata, not inline binary data, private URLs, or native attachment IDs. The transactional delivery adapter must provide authenticated, company-scoped transfer, verify actual type/size/digest, enforce attachment read access, automatically freeze invoice PDFs, and require explicit sender selection for other files. No bulk chatter attachment copying is implied.

Acknowledgments

Capability document.status has actor_identity_uuid: null for receiver service attribution. Its payload is exactly {exchange_uuid, subject, operation_uuid, state, code, issued_at}. subject is the original sender's exact document reference, including complete envelope digest. Payload operation_uuid identifies that original operation; the outer envelope has its own acknowledgment operation UUID. Status travels in the reverse peer direction. It includes no native record IDs, human-readable business errors, prices, document changes, or native confirmation/posting state.

State Permitted codes
received accepted
pending_review mapping_required, dependency_required, revision_required, verification_required, conflict
applied accepted
rejected denied, invalid_document, conflict

Applied means the operation was applied, not that a receiver confirmed or posted its counterpart. Adapters must verify subject correspondence before returning a status and retain monotonic evidence without echoing status as a new business exchange.

Native commercial calculation service

pmis.federation.exchange.pricing is an abstract model with private methods:

  • _extract_pricing(company, currency, taxes, quantity, unit_price, discount, ...) returns commercial pricing and original source_evidence from Odoo's native tax computation. Numeric inputs and outputs are contract decimal strings. Native source calculations use the explicit company and currency; currency-rounded source untaxed amount is preserved when deriving a discounted tax-exclusive price.
  • _receiver_unit_price(company, currency, taxes, quantity, pricing, ...) returns a native unit price under receiver-selected taxes. Odoo's total_excluded tax mode supplies the included-tax delta, followed by native recomputation to prove the quoted untaxed amount. Unrepresentable combinations require review.
  • _convert_units(company, currency, quantity, unit_price, source_unit, target_unit, approved=False) uses native same-category conversion without quantity rounding, changes price and quantity together, and verifies the line amount. Different units require explicit adapter approval derived from an approved scoped mapping. Packaging stays source evidence; it cannot itself approve a conversion.

These helpers require explicit accessible company scope, active currency, and same-company tax/product/partner records. They do not choose a fiscal position, map products, activate currencies, convert currency, or grant approval. Private methods are unavailable as generic Odoo RPC actions. Reviewed Exchange channels advertise immutable journal delivery through Transport. Native application remains receiver-owned and requires an installed native adapter.

Immutable incoming journal

The private pmis.federation.exchange.snapshot._receive_envelope method records validated incoming document envelopes against caller-authenticated peers and capability. It resolves reviewed routing and sender authority, then persists canonical envelope bytes, SHA-256, exact operation/document/exchange/revision references, and current verification evidence with append-only receipts. The caller owns the commit. Public snapshot and receipt creation, mutation, and deletion are blocked.

Replay uses the original operation and digest. The instance pair owns both the operation UUID namespace and each document revision; sender claims cannot move an established document into another company or exchange. Instance-row locks serialize operation and revision reservations. A snapshot savepoint includes initial receipt recording, so a receipt failure cannot leave a partial operation.

Verification evidence retains the original route, company/counterparty/partner mapping bindings, sender correspondence and membership, and reviewed native address snapshots. It also freezes exact commercial approval IDs and native targets. Current approval changes invalidate receiver review without altering original journal evidence. Unresolved addresses are retained without approval. Later address edits, retirement, reviewed route replacement, or changed verification require receiver review and never overwrite the frozen envelope or follow a successor automatically.

Known immediate predecessors are checked against exact digest and party ownership; valid revisions still require explicit receiver review. Missing dependencies remain pending. Dependency lookup is batched and requires exact applied snapshot and line evidence in the same company, environment, and peer pair. It does not establish native counterpart/order/picking/invoice correspondence. Attachment metadata and file bytes are frozen. Authenticated binary transfer verifies declared ownership, MIME signature, length, and digest. Missing declared bytes block application.

pmis.federation.exchange.receipt._record appends received, pending-review, or terminal outcomes. Identical observations reuse their receipt; terminal outcomes are permanent. Application requires a native adapter's _authorize_application implementation in the same transaction as its native effects. The default implementation rejects application.

Receiver administrators can record explicit verification approval through Federation > Incoming Documents > Review Verification. Each approval is a separate immutable pmis.federation.exchange.review record attributed to the acting administrator, with bounded review evidence and the current verification JSON. Original snapshots, route bindings, and receipt history remain unchanged. The private _approve workflow requires an exact applied predecessor for revisions, resolved declared dependencies, and verified attachment content. It never follows a replacement route or applies a native document. Terminal outcomes block approval.

Native adapters call _check_application_review before their reviewed application effects. It checks that the newest approval still matches current verification and rechecks all dependencies. Verification changes invalidate approval and require a new review, without overwriting the original evidence or earlier approvals.

_acknowledgment constructs the exact bounded reverse-direction document.status envelope. The authenticated document connection returns these acknowledgments from delivery and read-only receipt lookup. Their construction changes no native state.

Durable outgoing journal and native adapter inputs

pmis.federation.exchange.outgoing._enqueue_envelope(connection, envelope, source, selected_attachments=None) records one explicit sender-owned native-source revision. The sender must have an approved Core identity mapping and local membership. Native read access, company, supported source model/state, outgoing route, and exact party claims are checked. Invoice/refund PDFs are rendered automatically; readable binary supporting attachments require explicit selection on that same source record. Envelopes and files are frozen atomically. No source IDs enter the wire contract.

Dispatch uses Transport's pinned HTTPS, scoped capability discovery and runtime bindings. Uncertain delivery retries the original operation, bytes, and digest at most eight times. File transfers use the original operation and attachment UUID. Reconciliation retrieves receipts without replaying business effects. Immutable pmis.federation.exchange.observation records validate exact reverse direction, operation, exchange, subject revision/digest, allowed states, and terminal history. Changed route, sender membership, or native address verification requires review; dispatch never changes frozen source ownership or follows successor routes.

pmis.federation.exchange.adapter._source_pricing accepts native sale, purchase, and accounting lines. _receiver_lines(snapshot, side) prepares order inputs using the exact active currency, approved product/unit/packaging mappings, native fiscal positions, and local taxes. Unknown or incompatible conversions preserve original priced productless inputs and review reasons. Buyer quotation claims require native seller-line correspondence and never become authoritative prices from a header dependency alone. These services do not create orders. Native adapters own stable document/line correspondence, revision acceptance, and the same-transaction application hook. Stock/accounting adapters add scoped native warehouse/picking-type/ location/journal/account mappings with their workflows.