Skip to main content
Version: v1.0.0(int)

Purchase orders

Header + line items + receipts. The header carries target_type choosing whether the line items reference materials or SKUs. Status is an org.po_status ENUM stored directly on the row.

ER diagram

org.purchase_orders

ColumnTypeNotes
idUUID PK
org_idUUID FKplatform.organizations(id).
po_numberVARCHAR(100)NOT NULL. UNIQUE per (org_id, po_number).
vendor_idUUID FKNOT NULL. → org.vendors(id) ON DELETE RESTRICT.
target_typepo_target_typeNOT NULL. ENUM (materials, finished_goods). Default materials.
statuspo_statusNOT NULL. ENUM. See values below.
ship_to_location_idUUID FKorg.locations(id) ON DELETE SET NULL.
placed_atDATENOT NULL. Default CURRENT_DATE. Matches Planning (initial).
ship_dateDATEWhen the vendor ships from their end.
expected_delivery_dateDATE
received_atDATEMatches Received status.
paid_atDATEMatches Paid status.
cancelled_atDATEMatches Cancelled status.
shipping_costsNUMERIC(18,6)NOT NULL. CHECK ≥ 0. Default 0.
setup_costsNUMERIC(18,6)NOT NULL. CHECK ≥ 0. Default 0.
other_costsNUMERIC(18,6)NOT NULL. CHECK ≥ 0. Default 0. Catch-all for customs, duties, misc.
notesTEXT
created_atTIMESTAMPTZNOT NULL. Default NOW().
updated_atTIMESTAMPTZNOT NULL. Default NOW(). Auto-updated by trigger.

org.po_items

ColumnTypeNotes
idUUID PK
org_idUUID FK
purchase_order_idUUID FKNOT NULL. → org.purchase_orders(id) ON DELETE CASCADE.
material_idUUID FKorg.materials(id) ON DELETE RESTRICT. XOR with sku_id.
sku_idUUID FKorg.skus(id) ON DELETE RESTRICT. XOR with material_id.
quantityNUMERIC(18,6)NOT NULL. CHECK > 0. Decimals allowed (matches bom_items).
unit_costNUMERIC(18,6)NOT NULL. CHECK ≥ 0. Default 0.

Constraints:

  • XOR CHECK: ((material_id IS NOT NULL AND sku_id IS NULL) OR (material_id IS NULL AND sku_id IS NOT NULL)).
  • The chosen input kind must match the parent PO's target_type — application-enforced.

org.po_receipts

ColumnTypeNotes
idUUID PK
org_idUUID FK
purchase_order_idUUID FKNOT NULL. → org.purchase_orders(id) ON DELETE CASCADE.
receipt_dateDATENOT NULL. Default CURRENT_DATE.
created_atTIMESTAMPTZNOT NULL. Default NOW().
updated_atTIMESTAMPTZNOT NULL. Default NOW(). Auto-updated by trigger.

org.po_receipt_items

ColumnTypeNotes
idUUID PK
org_idUUID FK
receipt_idUUID FKNOT NULL. → org.po_receipts(id) ON DELETE CASCADE.
material_idUUID FKorg.materials(id) ON DELETE RESTRICT. XOR with sku_id.
sku_idUUID FKorg.skus(id) ON DELETE RESTRICT. XOR with material_id.
quantityNUMERIC(18,6)NOT NULL. CHECK > 0. Decimals allowed.
unit_costNUMERIC(18,6)NOT NULL. CHECK ≥ 0. Default 0. Cost charged at receipt — may differ from the ordered po_items.unit_cost (price changes, fuel surcharge, etc.). This is what flows to the inventory ledger.
created_atTIMESTAMPTZNOT NULL. Default NOW().

XOR CHECK: same as po_items. Application-enforced match with the parent PO's target_type.

Status values

org.po_status ENUM: PlanningPlacedIn TransitPartialReceivedPaid, plus terminal Cancelled.

Documents

Files attached to a purchase order live in the polymorphic org.documents table with entity_type = 'purchase_order'. Useful for vendor-issued PO PDFs, invoices, packing slips, shipping documents.

Cross-references

Notes

  • No is_active — cancellation is status = Cancelled.
  • Aggregate cost is computed on read: subtotal = SUM(po_items.quantity * po_items.unit_cost); total = subtotal + shipping_costs + setup_costs + other_costs.
  • Header overhead is NOT capitalized into inventory. shipping_costs, setup_costs, other_costs stay at the PO header. Ledger entries get the vendor unit cost from po_receipt_items.unit_cost as-is.
  • Lifecycle dates match statuses: placed_at (Planning, the initial state), received_at (Received), paid_at (Paid), cancelled_at (Cancelled). ship_date and expected_delivery_date are vendor-side dates, not lifecycle dates.