Wiki 🔍 Search
QIIUB · Module completeness

Purchase Orders

40 scored features 18 dependencies + N/A area: Inventory & Supply Chain

measured at e70ff195

74%
Overall
T1100%
T246%
T30%

Created: 2026-07-16 | Last Updated: 2026-07-19

Pilot instance of module-scorecard-template.md. Read-only analysis; recommendations only. Evidence is file:line / endpoint against D:\BCPOSGit\qiiub on branch main at commit e70ff195.

Re-scored 2026-07-19 (69% at bdf367d3 → 72% → 74% at e70ff195). PR #884 landed the reorder proposal engine — moving PO-F025 Missing → Implemented (the prior #1 gap) and PO-F028 Missing → Partial; F025 + the lifecycle were live-confirmed against the running app. Then the Receivings Fable review found landed cost (PO-F030) and three-way match (PO-F032) were double-scored / mis-owned — reclassified as Dependencies (→ Receivings / AP), out of PO's denominator, lifting the headline 72 → 74.


0. Document control

Field Value
Module name Purchase Orders (PO)
File po-scorecard.md (target: docs/modules/po-scorecard.md)
Author / session Claude (Opus 4.8), coordinated multi-agent analysis
Created 2026-07-16
Last-Updated 2026-07-19
Repo commit (SHA) analyzed e70ff195 (branch main; re-scored 2026-07-19 — was bdf367d3)
Governing ADRs 0021 (separate PO/Receiving), 0023 (three-tier history), 0020, 0024, 0038, 0070 (proposed), 0081, 0082, 0085, 0102, 0108 (proposed), 0120 (proposed) + reorder engine design spec/plan (PR #884, #446+#449)
Governing design docs docs/design/inventory-supply-chain.md (Draft), docs/PROGRESS.md Phase 5
Status of this analysis Adversarially-verified — Fable pass (findings in artifact-phase6-fable-review.md)

Léelo así (en simple). Esto es una foto temporal, no un contrato — mide cuánto del módulo estaba construido el 2026-07-19 (commit e70ff195; re-scoreado desde el bdf367d3 original).

Cuál es la fuente. Este archivo (po-scorecard.md) es el documento que lista todo lo que el módulo debería tener y su estado; es el que se sintetizó en este análisis (no existía antes). El scorecard HTML no es más que su representación visual — lo que se ve ahí sale de aquí.

De dónde salió este archivo (fuentes exactas, no "todo el repo"). Se leyeron de forma dirigida (con citas file:line, no un barrido exhaustivo):

  1. Códigosrc/QIIUB.Domain/Merchants/PurchaseOrder*.cs, src/QIIUB.Api/Features/PurchaseOrders/*, sus EF configs + migraciones + tests, y apps/qiiub-portal/src/features/purchaseOrders/.
  2. Documentación QIIUB — el design doc docs/design/inventory-supply-chain.md, los ADRs (0021, 0023, 0070, 0081, 0082, 0102…) y docs/PROGRESS.md.
  3. Legado — los árboles Markdown de RMS y RMH en D:\Code\POSRMS y D:\Code\POSRMH (para saber qué features existían antes).
  4. Mercado — investigación web (Lightspeed, Square, Shopify/Stocky, Cin7, NetSuite, RMH actual, Odoo).

La app SÍ se ejecutó el 2026-07-19 para confirmar en vivo la generación por reorden (F025) y el ciclo de vida — se levantó la API + el portal contra SQL local y se manejó la app real (no se hizo en el snapshot original). Cuando el código cambie, los números quedan viejos: hay que regenerar el documento.


1. Module identity

Purpose. The Purchase Orders module lets a merchant create, issue, track, and close orders to suppliers for replenishing stock. A PO is the intent-to-buy document: it records who is being ordered from, to which location, what products and quantities at what cost, and moves through a lifecycle (Draft → Submitted → PartiallyReceived → FullyReceived → Closed) as goods arrive. Per ADR-0021, the PO is deliberately only the order — physical arrival is a separate PurchaseReceipt document, and stock distribution is a third Distribution document.

Vertical relevance.

Actors & roles. All PO endpoints require SystemAdmin | Admin | Manager (no Cashier). Surfaces: API (FastEndpoints, complete), portal (qiiub-portal merchant UI, CRUD present). POS and qiiub-admin: no PO surface. Mobile PO approval is a declared future dependency.

Not-in-this-module. Physical receiving, quantity-on-hand updates, and cost-averaging happen in Receivings (PurchaseReceipt). Multi-store allocation happens in Distribution. Supplier master data and per-product supplier links (Supplier, ProductSupplier) belong to Catalog. Reorder thresholds live on LocationProduct in Inventory. Vendor invoicing / GL posting is a future AP module. See §2.


1b. Canonical flow — how a PO is created and worked

The established route today (from the shipped endpoints), and where each step lives:

  1. Create (Draft)POST /purchase-orders. Pick the supplier (PO-F006), the destination location where goods will be received (PO-F007), and optionally the first lines. A DocumentNumber (PO-000142) is minted. Creation methods are now manual (PO-F001) and reorder-based generation (PO-F025, PR #884 — proposal → fill / generate, see §1c); only sales-history generation (PO-F039) is not built.
  2. Build — add / edit / remove lines while Draft (PO-F002/F003/F004), or bulk-remove selected lines (PO-F021). SubTotal recomputes. Editing is locked once past Draft (PO-F013).
  3. Submit / issuePOST /purchase-orders/{id}/submit (PO-F010). Draft → Submitted. An empty PO is rejected. (No print/email/API transmission yet — PO-F023/F024; see §1c.)
  4. Receive — goods arrive and are booked via the Receivings module (PurchaseReceipt, a dependency). Each receipt increments QuantityReceived; the PO auto-moves to PartiallyReceived / FullyReceived (PO-F017). Multiple partial receipts per PO are first-class (ADR-0021).
  5. ClosePOST /purchase-orders/{id}/close (PO-F012). FullyReceived → Closed, stamping who/when. Blocked if any draft receipt is pending.
  6. CancelPOST /purchase-orders/{id}/cancel (PO-F011), allowed only from Draft or Submitted.

State machine: Draft → Submitted → PartiallyReceived → FullyReceived → Closed; Draft|Submitted → Cancelled. Each transition is guarded independently (no central state-machine class — PO-F009).


1c. Creation methods — the "PO Requisition"

In both legacy systems a store user creates a PO by choosing an order-calculation method, then a supplier strategy and an item scope. RMS calls it "Create New Purchase Order"; RMH calls it the "Purchase Order Requisition" wizard (Store Manager → Inventory/Purchasing → Purchase Order Requisition; commit → PO created). QIIUB now offers Manual + reorder-based generation (PR #884, live-confirmed 2026-07-19); only the sales-history generated method (F039) is not built.

Store-level method What it does RMS/RMH QIIUB
Manual / blank Add items & quantities by hand Both Implemented (PO-F001)
By Reorder Information Add items below reorder point; suggest qty up to restock level Both Implemented (PO-F025, PR #884 — worksheet + fill + generate)
By Recently Sold (sales history) Add items/qty sold in a date range Both Missing (PO-F039) — sold-30d is a display hint only, not a generation input
Supplier strategy: primary / lowest-cost / specified Which supplier the generated lines go to Both Primary implemented — reorder groups by primary supplier (ProductSupplier.IsPrimary); no-primary items go to an Unassigned bucket. Lowest-cost/specified not offered
Item scope Which items an auto-generation considers. In QIIUB this maps to hierarchical categories (Category self-FK tree + ProductCategory N:M; ADR-0044 deliberately collapsed RMH's Dept/Category/Filter split into one tree — no Brand/tags entity yet) + supplier + search Both (adapted to QIIUB) Backend-completeCategoryPublicId + SupplierPublicId filters on the proposal; portal category picker is a documented fast-follow

Naming is QIIUB-native, not RMH-literal. RMH calls the store flow "PO Requisition" and modern POS call it "reorder suggestions / automated reordering" (Lightspeed, Stocky, Odoo). The QIIUB feature is named for what it does (reorder-/sales-based generation), and its inputs map to QIIUB's own model (hierarchical categories, LocationProduct reorder point), not to RMH's taxonomy.

Fixed Quantity was a central-only variant in RMS/RMH. In QIIUB it's just a minor variant of the same generation idea (fan a fixed qty across the scoped items) — folded with the two generated methods above, not scored on its own. So the real store-PO gap is exactly those two generated methods (PO-F025, PO-F039).


2. Dependency map

Module Direction What crosses the boundary Scoring treatment
Catalog — Supplier / SupplierContact upstream PO references SupplierId; supplier name/terms/contacts resolved for display dependency — out of scope
Catalog — ProductSupplier upstream Per-product supplier link, SupplierCost, MinimumOrder, SupplierSku, primary-supplier flag dependency — out of scope
Catalog — Product / ProductVariant upstream PO lines reference ProductVariantId dependency — out of scope
Catalog — UnitOfMeasure upstream PurchasingUoMId + conversion snapshot on line dependency — out of scope
Inventory — LocationProduct upstream ReorderPoint / RestockLevel (the inputs auto-reorder PO generation would read) dependency — out of scope
Document Sequencing shared-infra DocumentSequence + DocumentSequenceService mint the PO-000142 number shared-infra; PO's use is in-scope (PO-F014), the service itself is a dependency
Receivings — PurchaseReceipt downstream Receipts increment PurchaseOrderLine.QuantityReceived and drive PO status dependency — out of scope
Distribution downstream Allocates received goods to stores; consumes receipts, not POs directly dependency — out of scope
Cross-cutting UI (@qiiub/ui DataTable) shared-infra Configurable grid columns (show/hide/reorder/persist) + custom-field/field-visibility config dependency — out of scope (belongs to the UI library / a platform config module, not PO)
Mobile (Operations) downstream/future Planned mobile PO approval dependency — out of scope
AP / Vendor Invoice downstream/future QuantityInvoiced / QuantityToInvoice fields reserved for 3-way match dependency — out of scope

Boundary statement. The PO module owns the PurchaseOrder and PurchaseOrderLine tables, every endpoint under Api/Features/PurchaseOrders/, and the PO state machine — the set of statuses and the transition guards. The physical actions that some transitions represent (receiving goods, distributing them) are executed by neighbor modules: the Submitted → PartiallyReceived → FullyReceived transitions are triggered by Receivings completing a receipt, even though the resulting PO status is a PO-owned outcome (single-owner rule — Receivings lists it as a dependency, PO scores it; see PO-F017). The PO module does not own supplier data, product data, reorder thresholds, the numbering service, physical receiving, or distribution.

On "central ordering" (why there is NO separate PO Planner module in QIIUB). RMS/RMH have a separate HQ "PO Planner" (Worksheet 340) because their HQ and stores run on disconnected databases — the worksheet exists to be approved centrally and then downloaded and converted into a store PO. QIIUB is cloud with a single source of truth, so that whole lifecycle is unnecessary. Verified: one merchant has many Locations; the portal identity is merchant-wide + role with no per-location wall; and CreatePurchaseOrder already accepts any location of the merchant. So a head-office user already creates a PO for any store today (covered by PO-F001 + PO-F007), and cross-store visibility is just the existing list under RBAC (PO-F016). The only genuinely missing piece is multi-location "master" bulk create — one action that fans out POs across several locations — which is an in-scope PO feature (PO-F027), naturally modeled on the existing Distribution/DistributionAllocation per-location pattern. (Fixed-quantity generation, distribution-site disbursement, and worksheet approval are RMS/RMH-only artifacts — the first would just be another generation method, the second is the Distribution module, the third does not apply to cloud.)

Boundary-leak checklist (audited in the Fable adversarial pass — see artifact-phase6-fable-review.md):


3. Functional taxonomy

Status legend: Implemented / Partial / Missing / Dependency (excluded from score). Source: RMS / RMH / market / QIIUB-original.

This is a comparison map, not a backlog. The feature universe below is drawn from what RMS, RMH, and the modern market offer — it's the yardstick QIIUB is measured against, so the % means "how complete vs. a mature POS," not "how much of a committed plan is done." A Missing means that world had it and QIIUB doesn't (yet)not that QIIUB must build it. Several legacy items (especially T3, and anything tagged an RMS/RMH artifact like "HQ worksheets") QIIUB may deliberately never do; those are candidates for a human to mark N/A. The features QIIUB has actually decided to pursue are the prioritized §7 gaps — everything else is context for that decision.

For developers (use the Evidence column as a pointer). Before building any feature, a dev — or their Claude — can start from the row's Evidence: for Implemented it's the file:line/endpoint that already exists; for Missing it's the natural code home / nearest existing piece (e.g. PO-F056 → a column on Supplier; PO-F037 → the existing PurchaseOrder.EmployeeId; PO-F027 → the Distribution/DistributionAllocation pattern). That answers "does anything already exist for this?" in one hop, before writing code.

T1 — Core (module unusable without) — weight ×3

ID Name Description Source Status Evidence Notes
PO-F001 Create PO (Draft) Create a Draft PO header + optional lines RMS+RMH+market Implemented POST /purchase-ordersCreatePurchaseOrder/CreatePurchaseOrderEndpoint.cs:84 Mints DocumentNumber; validators enforce column bounds
PO-F002 Add line Append a line to a Draft PO RMS+RMH+market Implemented POST /purchase-orders/{pid}/linesAddPurchaseOrderLineEndpoint.cs:60 Auto LineNumber, recomputes SubTotal
PO-F003 Edit line Full-replace edit of a Draft line RMS+RMH+market Implemented PUT /purchase-orders/{poPid}/lines/{pid}UpdatePurchaseOrderLineEndpoint.cs:62 Route-forgery defense; rejects if QuantityReceived>0
PO-F004 Remove line Delete a Draft line RMS+RMH+market Implemented DELETE /purchase-orders/{poPid}/lines/{pid}RemovePurchaseOrderLineEndpoint.cs:30 204, recomputes SubTotal
PO-F005 Update header Edit supplier/location/expected-date/notes on a Draft PO RMS+RMH+market Implemented PUT /purchase-orders/{pid}UpdatePurchaseOrderEndpoint.cs:44 Draft-only
PO-F006 Supplier selection Assign the supplier a PO orders from RMS+RMH+market Implemented PurchaseOrder.SupplierId FK — PurchaseOrderConfiguration.cs:22-32; validated on create Composite FK MerchantId-first
PO-F007 Destination location The location where the ordered goods will be received (the "ship-to" store/warehouse) RMS+RMH+market Implemented PurchaseOrder.LocationId FK — PurchaseOrderConfiguration.cs One location per PO; a head-office user can already pick any location. Creating for many locations at once is the in-scope gap PO-F027 (T2)
PO-F008 Line cost & rollup Unit cost, line total, PO SubTotal recomputation RMS+RMH+market Implemented PurchaseOrderLine.UnitCost/LineTotal; SubTotal recompute in Add/Update/Remove endpoints decimal(18,4)/(18,2)
PO-F009 Status lifecycle Draft/Submitted/PartiallyReceived/FullyReceived/Closed/Cancelled enum + guards RMS+RMH+market Implemented PurchaseOrderStatus.cs:6-14; per-endpoint source-state predicates No central state-machine class — each endpoint guards its own transition
PO-F010 Submit / issue Draft→Submitted; issue PO to supplier RMS+RMH+market Implemented POST /purchase-orders/{pid}/submitSubmitPurchaseOrderEndpoint.cs:32 Atomic flip; rejects empty POs (400)
PO-F011 Cancel Draft|Submitted→Cancelled RMS+RMH+market Implemented POST /purchase-orders/{pid}/cancelCancelPurchaseOrderEndpoint.cs:32 Predicate excludes received states
PO-F012 Close FullyReceived→Closed with accountability stamp RMS+RMH+market Implemented POST /purchase-orders/{pid}/closeClosePurchaseOrderEndpoint.cs:33 Blocks if draft receipts pending; stamps ClosedDate/ClosedByEmployeeId
PO-F013 Draft-only mutability All header/line edits locked once past Draft RMS+RMH+market Implemented 409 guard in 5 endpoints (Update/Add/UpdateLine/Remove/Batch) Stricter than RMS (RMS allowed post-issue edits); satisfies RMH "cannot be altered" intent
PO-F014 Document numbering Human-readable PO-000142 per merchant RMS+RMH+market Implemented DocumentSequenceService.cs:24-30; S12_DocumentSequencesSeed.cs:21-32 (prefix "PO-") Dapper UPDATE...OUTPUT + session_context; sequence service itself is shared-infra dep
PO-F015 View PO detail Full PO + lines graph with audit attribution RMS+RMH+market Implemented GET /purchase-orders/{pid}GetPurchaseOrderEndpoint.cs:84 Joins Supplier/Location/Employee names + CentralDb audit user names
PO-F016 List / filter / sort Paginated PO list, filter by supplier/location/status/search, sort, StatCards RMS+RMH+market Implemented GET /purchase-ordersListPurchaseOrdersEndpoint.cs:91; aggregates tested Draft/Open/Closed counts span all pages
PO-F017 Status from receipt coverage PO auto-transitions to Partially/FullyReceived as receipts complete RMS+RMH+market Implemented CompletePurchaseReceiptEndpoint.cs:296-312 (FullyReceived :309, PartiallyReceived :311; write lives in Receipts module) Cross-module write into PO; the PO-side outcome is real and tested. PR #906 added last-cost (LocationProduct.Cost) + off-supplier auto-link (ProductSupplier) around it — both Inventory/Catalog-owned, NOT PO (relevant to the future Receivings scorecard)
PO-F018 Cross-merchant isolation Every PO op tenant-scoped; no cross-merchant read/write QIIUB-original (tenancy) Implemented Composite PK/FK MerchantId-first; tests CreatePO_CrossMerchantIsolation_NotVisible, CrossMerchant_CannotEditAnotherMerchantsPOLine Critical Rule #1

T2 — Standard (market expects) — weight ×2

ID Name Description Source Status Evidence Notes
PO-F019 Expected delivery date Capture, edit, and track an expected arrival date market+RMS+RMH Partial PurchaseOrder.ExpectedDate field; editable via PUT /purchase-orders/{pid} Capture+edit work; the tracking half is absent — no overdue/ETA surfacing, no per-line ETA, no alerting. That gap is why this is Partial, not Implemented
PO-F020 Purchasing UoM Order in a purchase unit (case/box) distinct from sales unit, snapshot conversion RMH+market Implemented PurchaseOrderLine.PurchasingUoMId + UnitsPerPurchaseUoM; GetPO_LineWithUoM_... test Conversion snapshotted at PO creation
PO-F021 Bulk line delete Multi-select delete of Draft lines QIIUB-original+market Implemented POST /purchase-orders/{poPid}/lines/batch-deleteRemovePurchaseOrderLinesEndpoint.cs:58 Atomic, all-or-nothing, ≤100/call
PO-F022 Portal PO UI Merchant-facing CRUD for POs market Implemented apps/qiiub-portal/src/features/purchaseOrders/ (List/Detail pages, Create/Edit drawers, Submit/Cancel/Close modals, LinesGrid); en/es locales A route-index comment claims Create/Edit/Detail deferred to "another chat," but component files exist — comment appears stale
PO-F023 PO printing / PDF confirmation Generate a printable/PDF PO Confirmation RMS+RMH+market Missing ADR-0070 (Phase 1, GET /purchase-orders/{id}/print, QuestPDF); actively being built per ADR-0070/PR #79 but not merged — zero QuestPDF in src/ at this commit Channel 1 of 3 (print / email / electronic). Closest to landing
PO-F024 Email PO to vendor Email the PO (PDF) to the supplier's contact inbox RMS+RMH+market Missing Designed in ADR-0070 as Phase 2 (POST /api/purchase-orders/{id}/email, behind the AP-module flag) — not built Channel 2 of 3. This is a PDF to a human inbox, not machine-to-machine integration (that's PO-F031)
PO-F025 Reorder-based PO generation Auto-build a PO from items below reorder point (qty up to restock level) RMS+RMH+market Implemented Engine ReorderProposalService.cs:29-167 (below-ROP → qty-up-to-restock ReorderQuantity.cs:18-30, primary-supplier grouping, Draft/Submitted/Partial counted as on-order); GET /inventory/reorder-proposal (GetReorderProposalEndpoint.cs:85); POST /purchase-orders/{id}/reorder-fill (FillFromReorderEndpoint.cs:71); POST /purchase-orders/generate (GeneratePurchaseOrdersEndpoint.cs:141); portal worksheet ReorderWorksheetPage.tsx (route + hooks + en/es i18n); 30 integration + 7 unit tests. Live-confirmed 2026-07-19 (proposal returned real data; generate → 201 PO-000012) Landed since bdf367d3 (PR #884, #446+#449) — was the prior #1 gap. Roles SysAdmin/Admin/Manager. Fans out one Draft PO per supplier for a single location (multi-location bulk is still PO-F027). Category filter backend-complete, portal picker a documented fast-follow
PO-F026 Backorder / remainder tracking Track the outstanding un-received quantity on a partially-received PO RMH+market Partial PurchaseOrderLine.QuantityToReceive remainder; PO stays PartiallyReceived until closed Open remainder on the PO line IS PO-owned and IS the backorder-equivalent; what's missing is explicit B/O cancel/disposition + a backorder report
PO-F028 Vendor price applied on PO Auto-fill line cost from the supplier's catalog price market+RMH Partial Reorder path auto-applies server-side: cost fallback ProductSupplier.SupplierCost ?? LocationProduct.Cost ?? ProductVariant.Cost (ReorderProposalService.cs:136-138), flows through fill (FillFromReorderEndpoint.cs:120), seeds the worksheet; tested + live-confirmed (proposal suggestedUnitCost populated) Partial, not Implemented: the manual AddPurchaseOrderLineEndpoint still writes req.UnitCost verbatim (no server lookup); the manual drawer pre-fills cost client-side only (lookupSupplierCost.ts). Auto-apply is enforced only in the reorder path
PO-F041 PO header shipping terms Ship-via / FOB / freight / estimated shipping on the PO header RMS+RMH Missing No ShipVia/FOB/Freight/EstShipping columns on PurchaseOrder (verified) Evergreen home: nullable columns directly on PurchaseOrder (not a RMH-style POA_ShipVia side table). Distinct from charge lines (PO-F035)
PO-F056 Supplier minimum-order enforcement Validate a PO meets the supplier's minimum order before submit RMH+market Missing No min-order check in any PO endpoint (verified); Supplier has no min-order-amount field; ProductSupplier.MinimumOrder (per-item qty floor) is never read by PO handlers Two parts: the field → one nullable MinimumOrderAmount column on Supplier (evergreen, not a side table); the enforcement → a PO-level validation. See §5b
PO-F027 Multi-location / master PO (bulk create) One action that creates POs across several locations (a "master" order fanned out per store) RMS+RMH+market Missing Single LocationId per PurchaseOrder; no bulk/master-create endpoint. Single-location free-choice PO already works (F001/F007) The one genuine "central ordering" gap (see §2). Model on the existing Distribution/DistributionAllocation per-location pattern — evergreen, no HQ worksheet tables

T2 dependencies (excluded from score) — listed as rows for reproducibility:

ID Name Owner module Status Note
PO-F044 Multiple suppliers per product + primary Catalog (ProductSupplier.IsPrimary) Dependency PO reads it; Catalog owns it
PO-F045 Supplier payment terms Catalog/Supplier (Supplier.Terms) Dependency Stored on Supplier; no PO-side logic
PO-F046 Partial-receipt execution Receivings Dependency The act of receiving; PO only reflects it (PO-F017/F026)
PO-F047 Cost-variance capture on receipt Receivings Dependency Actual vs ordered cost captured on the receipt line
PO-F048 Receive-without-PO Receivings (ADR-0021) Dependency A receipt may exist with no PO
PO-F049 Supplier min-order dollar VALUE (field) Catalog/Supplier Dependency RMH MinOrderValue field. Confirmed absent from QIIUB Supplier. The field is Catalog's; its enforcement on a PO is PO-F056
PO-F057 Configurable grid columns @qiiub/ui DataTable (cross-cutting) Dependency Show/hide/reorder/persist columns. Missing repo-wide; would live in the UI library + a user-preference store, not PO
PO-F058 Configurable / custom fields Platform config (cross-cutting) Dependency Field visibility + custom fields on drawers/detail. Missing; a disabled "Custom Fields" tile exists in Settings (catalog-scoped, no backend). PO-side consumption would be PO work once the infra exists

T3 — Advanced / Differentiator — weight ×1

ID Name Description Source Status Evidence Notes
PO-F029 Multi-level approval workflow Route a PO for approval by spend/role before issue market Missing No approval status/gate in Features/PurchaseOrders; would be a new pre-Submit state + routing rules RMS had HQ-worksheet "Approve"; QIIUB has none. Mobile approval is a separate future dep
PO-F030 Landed cost allocation Spread freight/duty/tax across received lines into unit cost RMS+market Dependency Receivings RCV-F020 — landed cost is allocated at the receipt (design §10.3 ReceiptCharge on the receipt) Fable C-1: was double-scored here + in Receivings. Owned by Receivings; excluded from PO's denominator
PO-F031 Electronic PO transmission (EDI / API) Send POs to suppliers machine-to-machine (EDI, supplier API, punchout) — channel 3, beyond print/email market+QIIUB-original Missing Contemplated in docs/design/arch-integration-architecture.md §2.1.1 (stakeholder mtg 2026-04-09; PR pharmacy suppliers: catalog-in / PO-out / ASN-in) — exploratory, [DECISION PENDING], no ADR/roadmap/code JJ: direct supplier-API integration is a planned QIIUB direction. Distinct from human email (PO-F024). Would pair with electronic catalog-in + ASN → auto-draft-receipt
PO-F032 Three-way match Match PO ↔ receipt ↔ vendor invoice market+RMH Dependency AP (future) — 3-way match is an AP invoice-matching control; QuantityInvoiced/QuantityToInvoice reserved on PO + line for it Fable m-3: consolidated to AP for cross-doc consistency (Receivings lists it as RCV-D05 dep); excluded from PO's denominator
PO-F033 Supplier qty-break discounts Discount tiers by quantity/date on supplier cost RMH Missing No discount-schedule model; would extend ProductSupplier/Supplier pricing (evergreen: a child pricing table) RMH POC_SupplierDiscount
PO-F034 Time-bound supplier pricing Supplier item cost valid for a date range / min qty RMH Missing No date-ranged cost; would extend ProductSupplier with effective-from/to columns RMH POC_ItemPrice
PO-F035 Non-inventory charges as lines Add freight/handling as PO charge lines tied to tax/GL RMH Missing No charge-line concept; would add a line "kind" on PurchaseOrderLine or a sibling charge line RMH POA_Charge
PO-F036 Matrix item ordering Order by size/color grid RMS+RMH Missing ProductVariant + Product.BundleType=Matrix exist; the gap is a matrix-grid ordering UI, not backend QIIUB has variants; no matrix-grid order screen
PO-F037 Purchaser / buyer tracking Attribute a PO to a named purchaser/buyer RMH Missing PurchaseOrder.EmployeeId column already exists (reserved); no FK/nav configured, no write path/test Not a schema add — just wire the FK + a write path + UI. Closest-to-free of the T3 gaps
PO-F038 Order worksheet w/ KPI panel Build a PO alongside sales/order history + GMROI/turns RMS Missing Needs sales/inventory read models (GMROI, turns); none built. A reporting/read-side feature RMS power-user feature
PO-F039 Sales-history PO generation Auto-build a PO from units sold in a date range RMS+RMH Missing No generation logic; would read sales history (Sales module) + write draft PO lines. Sibling of PO-F025 Legacy: RMS/RMH "recently sold" requisition; modern POS: "demand-based purchase suggestions" (Stocky). Second generated method (§1c). Only manual is built
PO-F040 Configurable numbering mask User-configurable per-doc-type prefix/format RMS+RMH Missing No endpoint/UI to configure masks; the seeded DocumentSequence.Prefix/PadLength are the config surface to expose Those seeded values are already credited to PO-F014 (Implemented). Configurability is 0% built — crediting the same infra twice would be double-counting, so Missing

T3 dependencies (excluded from score) — listed as rows for reproducibility:

ID Name Owner module Status Note
PO-F050 Mobile PO approval Mobile / Operations (future) Dependency Declared future QIIUB Operations feature
PO-F051 Mobile PO creation Mobile (future) Dependency Square/RMH offer it; QIIUB POS has no PO surface
PO-F052 Cost-update method on receipt Receivings / Inventory costing Dependency Avg/FIFO/last-cost applied when goods are received
PO-F053 Serial / lot capture at receipt Receivings Dependency Serial + lot/expiry captured on the receipt
PO-F054 Vendor invoice / AP posting / GL AP (future, design §10.1) Dependency QuantityInvoiced/QuantityToInvoice reserved for this
PO-F055 Distribution to multiple stores Distribution (downstream) Dependency Allocates received goods to stores

N/A — not applicable to QIIUB (excluded from score; listed so a reader sees they were considered, not forgotten):

ID Name Why N/A (not Missing)
PO-F042 Related / parent-child PO chain Superseded by architecture. RMS/RMH spawn a child PO for the un-received remainder because their PO is the receipt; QIIUB keeps the remainder open on the same PO and takes multiple receipts (ADR-0021, PO-F026). It's not a gap — the need is met a better way. Scoring it Missing would penalize a deliberate design choice.
PO-F043 HQ / central PO planning & monitoring Dissolves in the cloud model (see §2). Cross-store creation already works (any-location PO, PO-F001/F007); cross-store visibility is the existing list under merchant-wide RBAC (PO-F016); the worksheet approve→download→convert lifecycle is an RMS/RMH disconnected-DB artifact with no cloud analogue. The only residual real gap — multi-location bulk create — is scored as PO-F027 (T2).

4. Storage specification

Evergreen mapping principle. RMH's purchasing schema is fragmented into many custom side-tables (POA_*, POC_*, POE_*, Worksheet_*) because RMH was bolted onto an existing RMS database — that sprawl is a migration artifact, not a design to copy. QIIUB is greenfield/evergreen with one clean schema, so every "missing field" in this doc maps to a single natural home on an existing QIIUB table (e.g. supplier min-order amount → a column on Supplier; PO shipping terms → columns on PurchaseOrder; buyer → the existing PurchaseOrder.EmployeeId), not a new RMH-style lookup/side table. Recommendations name that home; they do not import RMH's table structure.

Two tables are owned by this module: PurchaseOrder, PurchaseOrderLine. Both extend BaseEntity (BaseImmutableEntity does not exist in the codebase) — so both carry Id, GlobalId, MerchantId, RowVersion, CreatedAtUtc, UpdatedAtUtc, CreatedByUserId, UpdatedByUserId, IsDeleted and are soft-deletable (not financial-immutable). Composite PK (MerchantId, Id) and PublicId varchar(32) unique (MerchantId, PublicId) come from BaseMerchantEntityConfiguration<T>.

Immutability note. POs are BaseEntity (soft-delete), which is correct: a PO is an editable operational intent, not a posted financial record. Financial immutability (per feedback_immutability) attaches downstream at the receipt/invoice layer, not here.

Table: PurchaseOrder — PurchaseOrderConfiguration.cs:7-45

Column SQL type Null Business purpose
Id, MerchantId bigint, int no Composite PK; tenancy
PublicId varchar(32) no External API identity (prefix po, ADR-0082)
DocumentNumber varchar(20) no Human PO number "PO-000142"; unique per merchant
SupplierId bigint no FK → Supplier — who we order from
LocationId bigint no FK → Location — destination
EmployeeId bigint yes Reserved — intended purchaser/buyer (PO-F037); no write path
Status tinyint no Lifecycle (PurchaseOrderStatus 0–5)
OrderDate datetime2(3) no When the PO was raised
ExpectedDate datetime2(3) yes Expected arrival (PO-F019)
SubTotal decimal(18,2) no Sum of line totals
Notes nvarchar(500) yes Free-text
ClosedDate datetime2(3) yes Accountability — when closed
ClosedByEmployeeId bigint yes Accountability — who closed
QuantityInvoiced decimal(18,4) no Reserved for AP 3-way match (PO-F032); unwired
QuantityToInvoice decimal(18,4) no Reserved for AP; unwired

Indexes: unique (MerchantId, DocumentNumber); (MerchantId, SupplierId, Status); (MerchantId, LocationId, Status); base unique (MerchantId, PublicId) and (MerchantId, GlobalId). FKs (composite, MerchantId-first): Supplier (Restrict), Location (Restrict).

Table: PurchaseOrderLine — PurchaseOrderLineConfiguration.cs:7-53

Column SQL type Null Business purpose
Id, MerchantId bigint, int no Composite PK; tenancy
PublicId varchar(32) no External identity (prefix poln) for nested-route edits
PurchaseOrderId bigint no FK → parent PO
LineNumber int no Ordinal within the PO
ProductVariantId bigint no FK → what is being ordered
QuantityOrdered decimal(10,4) no How many ordered
QuantityReceived decimal(10,4) no Accumulator, incremented by receipts (RMH POD_OrderEntry pattern)
QuantityToReceive decimal(10,4) no Outstanding remainder (backorder-equivalent, PO-F026)
QuantityInvoiced decimal(10,4) no Reserved for AP; unwired
QuantityToInvoice decimal(10,4) no Reserved for AP; unwired
PurchasingUoMId bigint yes FK → UnitOfMeasure (purchase unit)
UnitsPerPurchaseUoM decimal(10,4) no Conversion snapshot (default 1)
UnitCost decimal(18,4) no Cost per base unit
LineTotal decimal(18,2) no Extended cost
Notes nvarchar(200) yes Free-text

Indexes: (MerchantId, PurchaseOrderId, LineNumber); unique (MerchantId, PurchaseOrderId, PublicId) (ADR-0082 nested-route lookup). FKs: PurchaseOrder, ProductVariant, UnitOfMeasure — all Restrict.

Referenced (dependency-owned, not re-specified here): Supplier, SupplierContact, ProductSupplier (+ MinimumOrder added 2026-07-09), DocumentSequence, Location, ProductVariant, UnitOfMeasure, PurchaseReceipt/PurchaseReceiptLine. See the Catalog / Receivings module docs when they exist.


5. API surface

Surface Verb+Route or screen Impl (file) Realizes Roles
API POST /purchase-orders CreatePurchaseOrderEndpoint PO-F001, F006, F007, F008, F014 Admin/Mgr/SysAdmin
API GET /purchase-orders/{PublicId} GetPurchaseOrderEndpoint PO-F015 "
API GET /purchase-orders ListPurchaseOrdersEndpoint PO-F016 "
API PUT /purchase-orders/{PublicId} UpdatePurchaseOrderEndpoint PO-F005, F019 "
API POST /purchase-orders/{PublicId}/submit SubmitPurchaseOrderEndpoint PO-F010 "
API POST /purchase-orders/{PublicId}/cancel CancelPurchaseOrderEndpoint PO-F011 "
API POST /purchase-orders/{PublicId}/close ClosePurchaseOrderEndpoint PO-F012 "
API POST /purchase-orders/{PublicId}/lines AddPurchaseOrderLineEndpoint PO-F002, F008, F020 "
API PUT /purchase-orders/{poPid}/lines/{PublicId} UpdatePurchaseOrderLineEndpoint PO-F003, F013 "
API DELETE /purchase-orders/{poPid}/lines/{PublicId} RemovePurchaseOrderLineEndpoint PO-F004 "
API POST /purchase-orders/{poPid}/lines/batch-delete RemovePurchaseOrderLinesEndpoint PO-F021 "
API GET /inventory/reorder-proposal GetReorderProposalEndpoint PO-F025 (proposal), F028 (auto-cost), F039 (sold-30d hint) Admin/Mgr/SysAdmin
API POST /purchase-orders/{PublicId}/reorder-fill FillFromReorderEndpoint PO-F025 (fill a Draft PO) "
API POST /purchase-orders/generate GeneratePurchaseOrdersEndpoint PO-F025 (generate one PO per supplier) "
portal Purchase Orders list/detail + Create/Edit drawers, Submit/Cancel/Close modals apps/qiiub-portal/src/features/purchaseOrders/ PO-F022 (renders F001–F021) merchant Admin/Manager
portal Reorder worksheet (below-ROP by supplier, editable qty/cost, generate) apps/qiiub-portal/src/features/inventory/routes/ReorderWorksheetPage.tsx PO-F025 (UI) merchant Admin/Manager

5b. Validations inventory

What the PO endpoints actually enforce today, and what they don't. (Field-level validation is part of what makes T1 features Implemented; the business gaps below are the actionable part — see PO-F056 and the §7 hardening item.)

Present — field-level (FluentValidation, per endpoint): PublicId prefix checks (po_/poln_/supp_/loc_/pvar_); max-lengths matching DB columns (Notes 500/200); decimal precision/scale + sign bounds via DecimalBounds (QuantityOrdered/UnitsPerPurchaseUoM > 0 as decimal(10,4); UnitCost ≥ 0 as decimal(18,4)); list not-empty, distinct, and ≤100-cap on batch delete; list-query Page/PageSize/SortBy whitelist/SortDirection guards.

Present — business-rule (in handlers): Draft-only mutability guard (5 endpoints → 409); empty-PO rejection on submit (400); atomic status-transition predicates (Submit/Cancel/Close only flip from the allowed source state → 409 otherwise); Close blocks if draft receipts pending (400); route-forgery / line-ownership checks on line edit/delete (404); QuantityReceived > 0 edit/delete block (409); supplier/location/variant/UoM existence resolution (400); duplicate DocumentNumber unique-index violation → 409; RowVersion optimistic-concurrency conflict → 409.

Absent — business validations (verified not present in Features/PurchaseOrders/):

Missing validation Effect today Tracked as
Supplier minimum-order enforcement A PO under the supplier's minimum can be submitted PO-F056 (scored) + Supplier field gap (PO-F049 dep)
Duplicate product-line prevention Two lines for the same ProductVariantId on one PO are allowed §7 validation-hardening
Supplier-is-active check A PO can be raised against an inactive supplier (Supplier.IsActive never checked) §7 validation-hardening
Location-is-active check Same, for the destination location §7 validation-hardening
Expected-date sanity (not in past) ExpectedDate accepted with no date validation §7 validation-hardening
Non-zero total guard An all-zero-cost PO is legal (only per-field bounds exist) §7 validation-hardening
Credit / payment-terms check Supplier.Terms never read; no credit logic out of scope (AP dep)

Scoring note: only the supplier-minimum validation is scored as its own feature (PO-F056), because JJ raised it specifically and it maps to a concrete RMH capability. The remaining validation gaps are documented here and bundled as one §7 hardening recommendation rather than split into micro-features — splitting them would pad the denominator and distort the tiers (anti-gaming guard b).


5c. Test surface & confidence

A second axis, orthogonal to the score. The §6 % says "how complete vs a mature POS"; this says "how trustworthy is the built part." A feature is Implemented on behavior + evidence — testedness never enters the §6 statusValue (mixing them would silently move the yardstick). T1 reads 100%, but this is where you see how well-guarded that 100% is.

Inventory (at e70ff195, method counts): 120 integration (Inventory/PurchaseOrderTests.cs core lifecycle + PurchaseOrders/{GetReorderProposal,FillFromReorder,GeneratePurchaseOrders,ListPurchaseOrdersAggregates}Tests.cs) · 15 unit (PurchaseOrders/ReorderQuantityTests.cs, DevSeed/S14_PurchaseOrdersSeedTests.cs) · 0 e2e. Of these, reorder-engine-specific = 30 integration + 7 unit (added by PR #884). Counts are context; the signal is the flow matrix below.

Critical-flow coverage (flow granularity, not per-feature):

Flow Tested
Create Draft (valid, column-max, empty-lines, wrong-prefix)
Submit Draft→Submitted (+ empty-PO 400, already-submitted 409)
Cancel Draft/Submitted→Cancelled (+ already-cancelled 409, 404)
Close FullyReceived→Closed (+ not-fully-received 409, ClosedBy stamp)
Line add/edit/remove + batch-delete (Draft-only 409, QtyReceived>0 block, route-forgery)
List filter/sort/paginate + StatCard aggregates (cross-page, cross-merchant)
Cross-merchant isolation (create/edit/line/batch)
Receipt-complete → PO status recalculation (E2E with Receipts)
Validation bounds (decimal precision/scale, max-lengths, prefixes)
Reorder proposal (below-ROP grouped by supplier, cost fallback, sold-30d hint, cross-merchant) ✓ (15 tests + live 2026-07-19)
Fill-from-reorder into a Draft PO (Draft-only, skip-existing, cross-merchant) ✓ (6 tests)
Generate POs per supplier from the worksheet (+ merge open, cross-merchant 400/404) ✓ (9 tests + live: generate → PO-000012)
Reorder quantity formula (restock − onHand − onOrder, min-order floor) ✓ (7 unit)
Re-close an already-Closed PO
Cancel a Partially/FullyReceived PO (409 predicate) ✗ (inferred from code, not asserted)
PublicId / DocumentNumber collision race

Named test gaps (→ bundled into §7 as one "test hardening" row, mirroring §5b): the three ✗ above, plus the reserved-and-unwired fields (EmployeeId, QuantityInvoiced, QuantityToInvoice) which have neither write path nor test. All low-risk, but they are the honest edges of the "T1 100%" claim.

Live confirmation (2026-07-19). Beyond the suite, F025 + the lifecycle were driven against the running app (API dotnet run + portal, local SQL): the reorder proposal returned a real below-ROP worksheet (5 suppliers; suggestedUnitCost auto-filled — confirms F028; soldLast30Days display-only — confirms F039); POST /purchase-orders/generate created PO-000012 (Draft, $14,724.32, 2 lines); submit flipped it to Submitted; the portal rendered the PO list/detail and the populated Reorder worksheet ($176,397.54 across 5 suppliers, editable qty/cost, Unassigned bucket). Screenshots in scratch. This is the confidence axis's strongest tier — live-driven, not just asserted.


6. Scoring rubric & result

Formula (fixed, from template §6): tier weights T1×3, T2×2, T3×1; statusValue Implemented=1.0, Partial=0.5, Missing=0.0; Dependency excluded from numerator and denominator.

Tier Implemented Partial Missing Dep-excluded Scored count Σ statusValue Tier %
T1 (×3) 18 0 0 0 18 18.0 100.0%
T2 (×2) 4 3 5 8 12 5.5 45.8%
T3 (×1) 0 0 10 8 10 0.0 0.0%

T2 scored (12): Implemented F020/F021/F022/F025; Partial F019/F026/F028; Missing F023/F024/F027/F041/F056. → 4×1 + 3×0.5 + 5×0 = 5.5 / 12 = 45.8%. T3 scored (10): all Missing (F029, F031, F033–F040; F030 landed cost + F032 three-way match now Dependency per Fable). → 0.0 / 10 = 0.0%. Excluded: 16 Dependency rows (incl. F030 landed cost → Receivings, F032 three-way match → AP, per Fable) + 2 N/A (F042 superseded, F043 dissolved into RBAC).

Overall weighted:

numerator   = 3×18.0 + 2×5.5 + 1×0.0 = 54 + 11 + 0 = 65.0
denominator = 3×18   + 2×12  + 1×10  = 54 + 24 + 10 = 88
overall%    = 65.0 / 88 = 73.9%

PO module completeness: ≈ 74% overall — T1 100%, T2 46%, T3 0%.

Interpretation. The PO module is operationally complete at its core (T1 100%): a merchant can fully create, issue, track, and close purchase orders with tenant isolation, numbering, and lifecycle guards, all tested. The reorder proposal engine (F025, PR #884) now also auto-drafts POs from below-reorder-point stock — the prior #1 gap, live-confirmed 2026-07-19. What's still missing is breadth, not foundationstandard-market conveniences (printing, emailing, header shipping terms, min-order validation, multi-location bulk create — T2 46%) and the advanced/ERP tier (approval workflows, EDI, matrix ordering, configurable numbering — T3 0%; landed cost + three-way match are now Receivings/AP dependencies). ~74% is a healthy signal for an early-phase POS, up from 69% (reorder engine shipped + landed-cost/3-way-match reclassified to their owning modules).

Sensitivity / anti-gaming notes:


7. Gap list & recommendations

Rank Gap Tier Impact Recommendation ADR?
1 PO-F023 PO print/PDF confirmation T2 High — merchants cannot hand/send a formal PO document; blocks real supplier workflow Implement ADR-0070 Phase 1 (GET /purchase-orders/{id}/print, QuestPDF). ADR already proposed. Promote ADR-0070 to accepted (exists)
2 PO-F024 Email PO to vendor T2 High — pairs with #1; the actual "send to supplier" step Add email dispatch of the PO PDF (reuse existing SMTP infra) Likely no — extends within model
PO-F025 Auto-reorder PO generation T2 SHIPPED (PR #884) — reorder proposal engine + worksheet + fill + generate; live-confirmed 2026-07-19. No longer a gap; was the #1. — (done)
4 PO-F019 Expected-date tracking T2 Medium — field exists but no overdue visibility Add overdue/ETA surfacing in list + detail; optional per-line ETA No — over existing schema
5 PO-F040 Configurable numbering mask T3 Medium — infra exists, not user-editable Expose a DocumentSequence config endpoint/UI No — over existing schema
Validation hardening (§5b) T2/T3 Medium — data integrity Add supplier-minimum (PO-F056), duplicate-line, active-supplier/location, expected-date sanity, non-zero-total checks Mostly no — over existing schema (min-order needs a Supplier field)
Test hardening (§5c) Low — confidence Assert the 3 untested negative-transition/race flows; wire or remove the reserved EmployeeId/QuantityInvoiced/QuantityToInvoice fields No ADR
Multi-location "master" PO bulk create T2 Medium (chains/franchises) One action → POs for several locations; model on the existing Distribution/DistributionAllocation pattern. (Central ordering otherwise already works via RBAC — no separate module.) Likely new ADR (a new endpoint/shape)
PO-F029 approval workflow T3 Low now Defer (Operations mobile). (Landed cost PO-F030 and 3-way match PO-F032 are Dependencies → Receivings / AP, not PO gaps — see §2 / §9.) requires new ADR

Quick wins (over existing schema, likely no ADR): #2 email, #4 expected-date tracking, #5 numbering mask, wiring EmployeeId for purchaser tracking (PO-F037). Net-new (new tables/services/ADRs): multi-store PO, approval workflow (PO-F029). (Landed cost PO-F030 and three-way match PO-F032 are Dependencies → Receivings / AP — not PO net-new; see §2 / §9.) (Auto-reorder — the former marquee net-new — shipped in PR #884.)


7a. GHES issue coverage — is each gap already ticketed?

Point-in-time snapshot (GHES Qiiub/qiiub, queried 2026-07-27; 103 open issues). In the promoted framework this is generated by the render tool at build time (GHES query by part:* + feature-ID convention), never hand-maintained — closing an issue updates the badge on the next render, like the staleness stamp. Shown here as a point-in-time demo. — untracked = a gap with no open issue (a prompt for a human to file one; the scorecard flags, it does not auto-create).

Gap (ID) Tracked by (open GHES issue)
PO-F056 supplier minimum-order #902 "Add supplier minimum order amount ($) to Supplier"
One-open-Draft-per-PO DB guard (§5b) #808 "Race-proof one-open-Draft-per-PO via filtered unique index"
PO-F024 email rides the email outbox being built — #990 (migrate IEmailService onto the outbox) + #986–989 (Resend hardening)
Reorder → Reports integration (F025 shipped) #955 "Route Reports 'Low Stock / Reorder' order qty through the reorder engine" (adjacent)
Copy / Duplicate PO follow-ups (shipped feature) #980 "copy-PO (#972) review follow-ups", #1000 "action-column convention (Duplicate PO)"
PO-F023 print, F019 expected-date, F040 numbering-mask, F027 multi-location bulk, PO-level tax untracked

Read: the highest-impact PO gap (min-order F056) is ticketed (#902), and the email path is actively being built; the remaining breadth (print, expected-date, masks, bulk) is untracked.

8. Open questions & contradictions

Contradictions found (repo cross-check):

  1. Phase-number drift (index-vs-PROGRESS class): docs/PROGRESS.md:40-41 attributes the inventory backend to "Phase 5" (90 endpoints/128 tests/22 entities); docs/adr/0081-...:14 says the same numbers shipped in "Phase 6." One is wrong about the phase number. Recommend: reconcile in a docs PR (not this module's concern to fix).
  2. "Phase 5" overloaded in PROGRESS.md — the PO-pipeline Phase 5 (:40-41) vs unrelated portal-UI "Phase 5" (:272, :326, :335). Search-ambiguity hazard.
  3. Distribution has no portal surface and isn't tracked as a gap: ADR-0021 + design doc make PO→Receipt→Distribution first-class, but ADR-0081's four sidebar entries omit Distribution and don't list it as future. Features/Distributions/ has only 4 endpoints (no Update/Cancel); no portal folder. The Phase-5 "pipeline complete" claim holds only at the API layer. Downstream of PO — noted, not scored here.
  4. ADR-0070 (PO Confirmation) is proposed, not shipped — no QuestPDF in src/. Do not cite PO print as existing.
  5. Design doc inventory-supply-chain.md frozen at "Draft" since 2026-03-13 despite governing an accepted, shipped module and being extended by later ADRs (0070/0081/0102). Of its §10 recommendations, Auto-Reorder (§10.2) has since shipped (PR #884, with its own design spec + impl plan) — so the design doc's "no automated PO generation logic is designed" line is now stale. Vendor Invoice (§10.1) and Landed Cost (§10.3) remain un-designed with no forward pointer.

Decisions needed from a human before gaps close:


9. Source reconciliation (denominator integrity)

Every line item in the RMS/RMH inventory (artifact-phase3) and the market scan (artifact-phase4) mapped to a disposition, so the denominator cannot be inflated by silent omission (template §6 guard e). scored = own §3 row; folded = subsumed by another ID (not double-counted); N/A = out of scope with reason.

Source feature (RMS/RMH/market) Disposition
Manual PO creation scored PO-F001
PO gen from reorder-point / min-max scored PO-F025
PO gen from items sold / sales history scored PO-F039
Requisition method: Manual/blank scored PO-F001 (the only built method)
Requisition method: Fixed Quantity folded into the generated methods (PO-F025/F039); a minor variant, was central-only in RMS/RMH
Generation item scope (RMS/RMH dept/category/supplier/filter) folded into PO-F025/F039; in QIIUB maps to hierarchical categories + supplier + search, not RMH's split
Supplier selection strategy (primary vs lowest cost) folded into PO-F025
PO Planner / HQ-generated worksheets N/A — the approve→download→convert worksheet lifecycle is an RMS/RMH disconnected-DB artifact; no cloud analogue (§2). Central creation = existing any-location PO
Master PO / consolidated multi-store scored PO-F027 (multi-location bulk create — back in PO scope, not a separate module)
Distribution-site / warehouse consolidation N/A — Distribution dep (PO-F055)
Order worksheets / suggested qty folded into PO-F025 + PO-F038 (KPI worksheet)
Item ordering info (Reorder No/Min Order/MPQ/Cost per supplier) N/A — Catalog dep (PO-F044/F049)
Reorder Point / Restock Level per store N/A — Inventory dep (LocationProduct)
Multiple suppliers per item + primary scored PO-F044 (dependency)
Supplier min-order dollar VALUE field → dep PO-F049 (Catalog); PO-level enforcement → scored PO-F056
Supplier payment terms scored PO-F045 (dependency)
Supplier lead-time / ship-via / FOB / freight scored PO-F041 (PO-header side); supplier-side is dep
Supplier qty-break discount schedules scored PO-F033
Time-bound supplier item pricing scored PO-F034
"Do Not Order" item flag N/A — Catalog product flag dep
Purchaser / buyer tracking scored PO-F037
Requisitioner / "Confirming to" free-text folded into PO-F037 (managed vs free-text variant)
PO number auto-gen scored PO-F014
Custom numbering mask scored PO-F040
Purchase Unit of Measure scored PO-F020
Non-inventory Charges as PO lines scored PO-F035
Est. shipping/other fees (header) vs actual (receipt) header → PO-F041; actual → dep PO-F047
Landed cost calculation on receipt dep PO-F030 → Receivings RCV-F020 (landed cost owned at the receipt, Fable); receipt-side costing → dep PO-F052
Cost distribution methods (Qty/Value/Manual) folded into PO-F030 (dep → Receivings RCV-F020)
Cost update method on receipt (None/Last/Weighted Avg) scored PO-F052 (dependency)
Auto-update supplier cost/tax on receipt N/A — Catalog/Receivings dep
Last cost / Replacement cost on item N/A — Catalog/Inventory dep
PO/Invoice GL account mapping scored PO-F054 (AP dependency)
Formal Invoice / Credit Note posting scored PO-F054 (AP dependency)
PO lifecycle / statuses scored PO-F009
Placement status / issuing a PO scored PO-F010
Editing rules after issue / release lock scored PO-F013
Related PO / parent-child chain N/A — superseded (PO-F042, see N/A block)
Partial shipment / partial receiving dep PO-F046; PO-side remainder → PO-F026
Backorder handling on completion scored PO-F026
Receive All / quick full receipt N/A — Receivings dep
Commit vs Save on receiving N/A — Receivings dep
Serialized item receiving scored PO-F053 (dependency)
Lot number tracking on receipt folded into PO-F053 (dependency)
Matrix item ordering (size/color grid) scored PO-F036
Lot Matrix items (bulk pack sizes) folded into PO-F036 / N/A Catalog product-type
Item Assembly / Kit purchasing N/A — Catalog product-type; no PO-specific behavior
PO printing scored PO-F023
PO emailing scored PO-F024
PO export to file/Word/Excel folded into PO-F023
Customizable print templates folded into PO-F023 (ADR-0070)
PO template variables (customization SDK) N/A — RMS SDK concept; QIIUB has no template-var SDK
SavePurchaseOrder hook (customization SDK) N/A — RMS SDK concept; QIIUB uses sync/domain events
Approval workflow for HQ PO worksheets scored PO-F029 (approval itself); the HQ-worksheet framing is N/A in cloud (PO-F043)
Release / auto-release step folded into PO-F010 (submit/issue)
EDI / electronic ordering scored PO-F031
Import Orders from legacy N/A — ADR-0038: POs explicitly NOT imported
Order/qty discrepancy tracking (DiscrepancyStatus) folded into PO-F026 (remainder/variance)
Inventory Transfer as parallel order type N/A — separate Transfers module (own definition doc)
Central PO/receiving status monitoring covered by PO-F016 (list/filter) under merchant-wide RBAC — cross-store visibility already works
KPI / Order History / Sales History panel scored PO-F038 (a store-level PO-building worksheet)
Three-way match (market) dep PO-F032 → AP (future)
Expected-date / ETA tracking (market) scored PO-F019
Mobile PO approval / creation (market) scored PO-F050 / PO-F051 (dependencies)
Vendor catalogs / price lists (market) scored PO-F028 (+ dep PO-F044)
Configurable grid columns (market/UX) N/A — cross-cutting @qiiub/ui DataTable (dep PO-F057)
Configurable / custom fields (market/UX) N/A — cross-cutting platform config (dep PO-F058)

No source line is left unreconciled.

WIDS365 sweep (2026-07-26 — retroactive 4th source, artifact-po-wids365-sweep.md). WIDS365 (a BCPOS production POS) was added as a source after this scorecard's original RMS/RMH + market pass. Verdict: it adds no confident new denominator row and changes no status — ≈74% stands. WIDS365 has a native PO layer (Wids365-DB: POHeader/PODetail + web_POHeaderUpsert/POClose/PORecalcQtyRcvd/SeqDocNumber + PurchaseOrderController), but every capability maps to an existing PO-Fxxx: header/line CRUD (F001–F005/F021), supplier+store (F006/F007), totals (F008), lifecycle/close (F009/F012), numbering (F014), list/detail (F015/F016), receipt-driven status (F017), QtyRTD remainder (F026), RequiredDate (F019), Requisitioner/ConfirmingTo (F037), print report (F023).


Glossary

Plain-language definitions for terms that can't be fully simplified. Kept short on purpose.

Term In plain words
PO (Purchase Order) The order you send a supplier saying "I want to buy these items at these prices."
Line / PO line One row on the order: a product, a quantity, a cost.
Draft / Submitted / Received / Closed The stages a PO goes through: being written → sent to the supplier → goods arrived → finished.
Receipt (Receivings) A separate document that records goods physically arriving. One PO can have several receipts (partial deliveries).
Distribution A separate document that splits received goods out to several stores.
Reorder point / Restock level The low-stock trigger and the target level — "when I drop to X, order back up to Y."
Backorder / remainder The part of an order that hasn't arrived yet and is still owed.
UoM (Unit of Measure) The unit you buy in (case, box) vs. sell in (each).
SKU / variant A specific sellable version of a product (size/color); QIIUB calls it a ProductVariant.
Supplier / vendor Who you buy from. (QIIUB uses "Supplier".)
Min-order amount A dollar floor a supplier requires before they'll take an order.
Landed cost The true cost of an item after adding freight, duty, and taxes on top of the item price.
Three-way match An accounting check that the PO, the receipt, and the supplier's invoice all agree before paying.
AP (Accounts Payable) The "money we owe suppliers" side of accounting — a future QIIUB module.
GL (General Ledger) The master accounting record; "GL posting" = writing a transaction into it.
EDI / supplier API / punchout Computer-to-computer ways to send orders straight into a supplier's system (no email/PDF). "Punchout" is one such method.
ASN Advance Ship Notice — an electronic "here's what's on the way" message a supplier sends before delivery.
GMROI / turns Retail metrics: profit per dollar of inventory, and how fast stock sells through.
MPQ Master Pack Quantity — how many units come in one supplier pack/case.
FOB / ship-via Shipping terms on an order: who pays freight from what point, and the carrier.
Master PO / multi-location One action that creates orders for several stores at once.
Reorder / requisition generation Letting the system draft the PO for you (from low-stock or sales history) instead of typing every line.
Tier (T1/T2/T3) How essential a feature is: T1 = can't work without it, T2 = market expects it, T3 = advanced/ERP nice-to-have.
Dependency A feature that really belongs to a neighbor module (e.g. Receivings), so it's not counted in this module's %.
N/A (in this doc) A legacy feature that doesn't apply to QIIUB (superseded by our design), so it's not a gap and not counted.
Evergreen QIIUB is one clean, modern schema built fresh — not carrying old tables forward. So a missing field gets one natural column, not a legacy-style side table.
RBAC Role-based access control — what a user can do based on their role (Admin, Manager, Cashier…).
RLS Row-level security — the database itself blocks one merchant from seeing another's rows.
MerchantId / tenant Each business (merchant) is a separate tenant; every row is tagged with its MerchantId so data never crosses.
PublicId The safe public identifier used in the API/URLs (e.g. po_…), instead of the internal database number.
Commit (e70ff195) The exact saved snapshot of the code this analysis measured (re-scored here from the original bdf367d3). Check out that commit and you see precisely what we saw.
ADR Architecture Decision Record — a short doc in the repo that records a design decision and why.
PO Planner (RMS/RMH) Legacy central-office ordering tool. In QIIUB this is not a separate thing — it's just a PO created by a head-office user (see §2).
Master / origin PO chain Legacy links between a parent PO and spun-off child POs. QIIUB doesn't need it (we keep the remainder on one PO).