Design Rationale — Orders
Non-obvious design choices for the orders module — the WHY behind each decision.
Orders (locked 2026-06-10)
Own order_payment table, not POS's sale_payment
Decision: The orders schema has its own order_payment table for deposits and installments. It does not reuse pos.sale_payment.
Why: Orders take payment over time — a quote may receive a deposit, the order may receive partial payments, and fulfillment closes the balance. pos.sale_payment is a point-of-sale tender record tied to a single register session. Different lifecycle, different FK targets, different semantics.
Rejected: Reusing sale_payment — the POS tender model (register, shift, split-tender) doesn't apply to order deposits; forcing orders into it would require nullable columns that defeat the intent of both tables.
Link-don't-convert: fulfilled_sale_id on order_header
Decision: When an order is fulfilled at the POS, the resulting pos.sale is linked back via order_header.fulfilled_sale_id (text forward-ref). The order is not transformed into or replaced by a sale.
Why: An order and its fulfilling sale are different documents with different histories. The order may have a multi-week lifecycle with deposits, changes, and approvals; the sale is the moment of tender and tax finalization. Preserving both records keeps the complete history.
Guard: Do not "convert" orders to sales or merge their records at fulfillment. Link them.
order_type discriminator: quote / order / special_order / preorder
Decision: One order_header table with an order_type discriminator, not a table per order kind.
Why: Discriminator-over-sprawl — all are orders at different stages or kinds, sharing the same FK targets (customer, site, line items, payments, fulfillment). Variant behavior is captured by order_type-aware service logic.
Reserve-on-confirm, not reserve-on-quote or reserve-on-fulfillment
Decision: InventoryService reserves stock when an order status moves to confirmed. Quotes do not reserve; confirmed orders do.
Why: A quote is exploratory — reserving on every quote would lock inventory against hypothetical sales. Waiting until fulfillment risks overselling in the window between confirmation and pick. Confirmation is the right commitment point.
estimated_tax_cents is display-only; legal tax finalizes at POS
Decision: order_header.estimated_tax_cents and order_line.estimated_line_tax_cents are display figures only. The legally charged tax is finalized by Stripe Tax at POS fulfillment.
Why: Tax is determined at the point and time of sale (jurisdiction, exemptions, and item codes are verified then). An earlier estimate on a quote or order may be wrong — the customer's address could differ, they may present a tax cert at checkout, or rates may change. Only the POS sale_line_tax rows are the authoritative tax record.
Guard: Do not treat estimated_tax_cents as the charged amount — it is a UI convenience figure. pos.sale_line_tax is authoritative.