entity

Product Order

One submission of selected BOM lines for one project, billed as one invoice. Aggregate root with the invoice lifecycle draft → issued → paid, or cancelled.

Entity(Aggregate Root)Status: Implemented in api-v1Aggregate Root

Purpose

A steward ticks BOM lines and runs Create Order. The API drafts one Product Order for that project, with one Product Order Item per selected BOM line. The order copies what the BOM lines and the variants’ owners said at that moment (stock from product-marketplace, made-to-order from design-management, ADR-0083). It then watches how each BOM line drifts (Source Status) without ever changing the BOM.

In the MVP the Product Order is both the procurement record and the invoice record (ADR-0133). It is not a purchase order to a vendor. It is also not the Part 05 invoice aggregate (due, partially paid, void), which is deferred with the enterprise procurement workflow.

Invoice lifecycle

FromCommandTo
draftissue (UI Issue order)issued
issuedmarkPaid (UI Mark paid)paid
draft or issuedcancel (UI Cancel order)cancelled
draftdiscard, or removing the last itemdiscarded (the order is deleted)

A repeat into the same state is a no-change success with no event. paid and cancelled are terminal.

  • Draft only: Set shipping, Update and Delete.
  • Issue: locks the order (number, bill-to, tax rate, shipping and totals) and withdraws the DRAFT preview.
  • Print: never issues. A draft prints a DRAFT preview. An issued or paid order attaches its final invoice once and re-signs it on later prints. A cancelled order does not print.

Invariants

  • INV-1: one project per order.
  • INV-2: a persisted order has at least one item. Removing the last one discards the order.
  • INV-3: at most one item per BOM line across the project. A cancelled order keeps its items, so its lines stay taken. This is enforced by the orderability policy and a re-check after create.
  • INV-4: an item stores its BOM line’s domain_id as text, and the BOM line gains no order key.
  • INV-5: a final invoice document is attached at most once.
  • INV-6: issued and paid orders carry a frozen invoice number, bill-to, tax rate, shipping and totals.

Deviations:

  • OCC: omitted. Commands serialize per project with an in-process lock, which the glossary records.
  • Idempotency: create’s idempotency key is stored on the order (durable). This deviates from RULE-HTTP §4.4.

Domain Events

All internal. There are no integration events and no outbox.

Persistence

Airtable construction base: product_order (header) and product_order_items. Steward actions arrive on the throwaway AT-A1 table ui_product_order_actions (product_order_actions.js). Invoice PDFs are stored in the private Supabase bucket SUPABASE_STORAGE_INVOICES_BUCKET and copied into product_order.pdf from a one-hour signed URL.