measured at e70ff195
Created: 2026-07-19 | Last Updated: 2026-07-19
Second filled instance of
module-scorecard-template.md(afterpo-scorecard.md). Read-only analysis; recommendations only. Evidence isfile:line/ endpoint againstD:\BCPOSGit\qiiubon branchmainat commite70ff195. Gathering source-of-record:artifact-receivings-gathering.md.Why Receivings was scored second. It is PO's downstream neighbor, so it is the sharpest test of the framework's single-owner rule: the capabilities PO listed as dependencies pointing here (partial-receipt execution, receive-without-PO, cost-variance capture) are scored here for the first time, while the PO-status-from-coverage write β even though its code physically lives in this module's
CompletePurchaseReceiptEndpointβ stays PO-owned and is a dependency here, not re-scored.
| Field | Value |
|---|---|
| Module name | Receivings (PurchaseReceipt) |
| File | receivings-scorecard.md (target: docs/modules/receivings-scorecard.md) |
| Author / session | Claude (Opus 4.8), coordinated multi-agent analysis |
| Created | 2026-07-19 |
| Last-Updated | 2026-07-19 |
| Repo commit (SHA) analyzed | e70ff195 (branch main) |
| Governing ADRs | 0021 (separate PO/Receipt/Distribution), 0023 (three-tier history), 0085 (CRUD update β cites UpdatePurchaseReceipt), 0038 (not-imported), 0102/0120 (tangential). No ADR covers landed cost, serial, or lot. |
| Governing design docs | docs/design/inventory-supply-chain.md Β§4.2/Β§6.2/Β§10 (Draft, frozen 2026-03-13); docs/superpowers/specs+plans/2026-07-17-off-supplier-receiving-and-cost* (PR #906) |
| Status of this analysis | Adversarially-verified β Fable pass (findings + resolutions in artifact-receivings-fable-review.md); live-confirmed against the running app 2026-07-19 |
LΓ©elo asΓ (en simple). Foto del 2026-07-19 (commit
e70ff195) de cuΓ‘nto del mΓ³dulo de recepciones (registrar la llegada fΓsica de mercancΓa) estΓ‘ construido, medido contra el estΓ‘ndar de POS de retail establecidos + mercado moderno + el roadmap propio de QIIUB. El nΓΊcleo (recibir contra un PO o sin PO, recepciΓ³n parcial, completar β sube el inventario) estΓ‘ hecho y probado; lo que falta es amplitud (costo histΓ³rico, lote/vencimiento, impresiΓ³n, landed cost, promedio/FIFO).
Purpose. The Receivings module records the physical arrival of goods at a location as a PurchaseReceipt document. Per ADR-0021 the receipt is deliberately a separate document from the Purchase Order: one PO can have many receipts (partial deliveries), and a receipt can exist with no PO at all (walk-in / emergency / off-supplier restock). Completing a receipt is the event that posts to inventory β it raises quantity-on-hand, snapshots a movement, and updates the item's cost.
Vertical relevance.
core. Booking supplier deliveries is the primary use.core, but with a hard gap: lot/expiry capture at receipt (RCV-F019) is not built, and pharmacy receiving is lot/expiry-driven by regulation. The module functions for Rx but is not compliant-complete.core. Receiving materials by location/job.Actors & roles. All receipt endpoints require SystemAdmin | Admin | Manager (no Cashier). Surfaces: API (FastEndpoints, complete), portal (qiiub-portal β list, standalone create, PO-driven "receive goods", detail with Complete/Cancel). POS and qiiub-admin: no receipt surface. Mobile/handheld receiving is a declared future dependency.
Not-in-this-module. The PO status that a receipt drives (PartiallyReceived/FullyReceived) is PO-owned (PO-F017) β see Β§2. Allocating received goods to stores is Distribution. Vendor-invoice / 3-way-match / GL posting is a future AP module. Reorder/replenishment that reads LastReceivedDate/cost is the Purchasing reorder engine (PO-F025). Correcting posted stock is Adjustments. See Β§2.
POST /purchase-receipts. Choose the supplier and destination location; optionally link a PO (PurchaseOrderId) and pre-fill lines from its order lines. A RCV-000xxx DocumentNumber is minted. A receipt with no PO is first-class (RCV-F014).QuantityReceived in the purchasing UoM + QuantityInStockingUoM posted to inventory + UnitCost). Draft lines are lax (zero-qty and empty lists are legal); editing is PUT /purchase-receipts/{id} (Draft-only, RowVersion-guarded).POST /purchase-receipts/{id}/complete. Draft β Completed, in one transaction: (a) increment the PO line's QuantityReceived and recompute the PO status (PO-owned outcome); (b) post inventory β QuantityOnHand +=, append an InventoryMovement, stamp LastReceivedDate; (c) update cost β LocationProduct.Cost = UnitCost (last-cost); (d) auto-link off-supplier products into ProductSupplier (best-effort, savepoint). A receipt with no positively-received line is rejected (409).POST /purchase-receipts/{id}/cancel, Draft only. Completed and Cancelled are terminal (no correction/reversal path β RCV-F021).State machine: Draft(0) β Completed(1); Draft β Cancelled(2). Each transition is an atomic ExecuteUpdateAsync guarded on Status == Draft (409 otherwise).
| Module | Direction | What crosses the boundary | Scoring treatment |
|---|---|---|---|
| Purchasing (PurchaseOrder) β status from coverage | upstream/peer | Complete increments PurchaseOrderLine.QuantityReceived and writes PO Status |
dependency β PO-owned (PO-F017), scored in PO, NOT re-scored here |
| Purchasing β backorder/remainder | peer | QuantityToReceive remainder stays open on the PO line |
dependency β PO-owned (PO-F026) |
| Purchasing β reorder engine | downstream | reads LastReceivedDate / updated cost the receipt writes |
dependency β PO-F025 |
| Distribution | downstream | allocates received goods to stores; consumes receipts | dependency β out of scope |
| AP / Vendor Invoice / GL | downstream/future | QuantityInvoiced/QuantityToInvoice reserved on receipt line for 3-way match |
dependency β out of scope |
| Adjustments | peer/future | correcting posted stock after a completed receipt | dependency β out of scope (the workaround for RCV-F021) |
| Catalog / Inventory β Supplier, ProductVariant, LocationProduct, UoM | upstream | receipt references them; last-cost writes LocationProduct.Cost, off-supplier writes ProductSupplier |
dependency β tables owned upstream; the receipt-triggered writes are scored here (single-owner) |
| Document Sequencing | shared-infra | mints the RCV-000xxx number |
shared-infra; the receipt's use is in-scope (RCV-F011), the service is a dependency |
| Mobile / handheld / scanner receiving | downstream/future | receive on a phone/scanner | dependency β POS/Mobile future |
| Reporting | downstream | HQ "monitoring receiving status" dashboard | dependency β out of scope |
Boundary statement. Receivings owns the PurchaseReceipt + PurchaseReceiptLine tables, every endpoint under Api/Features/PurchaseReceipts/, the receipt state machine, and the act of posting a receipt to inventory (the on-hand/movement/cost writes that Complete performs). It does not own the PO status those writes drive, distribution, vendor invoicing, stock adjustments, or the master data it references.
Single-owner rule (the headline test). CompletePurchaseReceiptEndpoint.cs:296-322 physically writes PurchaseOrder.Status. That code lives in this module, but the capability "a PO reflects what's been received" is PO's semantic outcome β it is scored once, in po-scorecard.md as PO-F017 (Implemented), and appears here only as RCV-D01 (Dependency), never re-scored. This is exactly the inflation the rule prevents: the same capability must not lift two modules' percentages.
Shared-infra split. Complete also writes LocationProduct.Cost (Inventory table) and ProductSupplier (Catalog table). Those tables are owned upstream, but the receipt-triggered behaviors β "receiving updates the item's cost" (RCV-F015) and "receiving an off-supplier item links it to the supplier" (RCV-F022) β are the receipt's user-visible outcomes and are scored here. The tables' own CRUD is scored in Catalog/Inventory.
Boundary-leak checklist (audited in the Fable pass):
LocationProduct, but "completing a receipt raises on-hand" is the receipt's outcome β scored here (RCV-F005).Cost column; "receiving updates cost" is scored here (RCV-F015); the avg/FIFO sophistication is RCV-F024 (Missing).ProductSupplier; the receipt-triggered auto-link is scored here (RCV-F022); Catalog's own supplier-link CRUD is not.Status legend: Implemented / Partial / Missing / Dependency (excluded). Source: RMS / RMH / market / QIIUB-original.
Comparison map, not a backlog. The universe is drawn from RMS/RMH + the modern market + QIIUB's own roadmap/ADRs. A
Missingmeans "that world has it and QIIUB doesn't (yet)," not a commitment. Legacy artifacts (Commit-vs-Save, HQ lot-propagation) are N/A candidates. The decided work is the prioritized Β§7 gaps.
| ID | Name | Description | Source | Status | Evidence | Notes |
|---|---|---|---|---|---|---|
| RCV-F001 | Create receipt (Draft) | Create a Draft receipt header + optional lines | RMS+RMH+market | Implemented | POST /purchase-receipts β CreatePurchaseReceiptEndpoint |
Mints RCV- number; PO optional; one-open-Draft-per-PO guard (app-level, line 178-191) |
| RCV-F002 | Receive against a PO | Link the receipt + each line to a PO / PO line | RMS+RMH+market | Implemented | PurchaseReceipt.PurchaseOrderId, PurchaseReceiptLine.PurchaseOrderLineId; POLine must belong to the body's PO (create validation) |
Composite FK MerchantId-first |
| RCV-F003 | Partial receipt | Receive some now, PO stays partially-received | RMS+RMH+market | Implemented | PurchaseReceiptPartialReceiveTests.cs (9); accumulator at CompletePurchaseReceiptEndpoint.cs:265-292 |
Was PO's dependency (PO-F046); scored here. The remainder tracking is PO-owned (RCV-D02) |
| RCV-F004 | Received-to-date accumulation | Running received total across multiple receipts | RMS+RMH | Implemented | poLine.QuantityReceived += line.QuantityReceived (:265-292) |
Multiple receipts per PO first-class (ADR-0021) |
| RCV-F005 | Complete β post to inventory | Raise on-hand, append movement, stamp last-received | RMS+RMH+market | Implemented | CompletePurchaseReceiptEndpoint.cs:191-261 (InventoryMovement Receipt + QuantityOnHand += + LastReceivedDate) |
The event that makes a receipt matter. Guarded: no positively-received line β 409 (:148-154) |
| RCV-F006 | Receipt lifecycle + guards | Draft/Completed/Cancelled with atomic transition guards | RMS+RMH+market | Implemented | PurchaseReceiptStatus (0/1/2); atomic ExecuteUpdateAsync filtered on Status==Draft (Complete :105-118, Cancel :89-105) |
Completed/Cancelled terminal |
| RCV-F007 | Edit Draft | Full-replace edit of a Draft receipt's lines | RMS+RMH+market | Implemented | PUT /purchase-receipts/{id} β UpdatePurchaseReceiptEndpoint; Draft-only 409 (:173); RowVersion 409 (:310) |
ADR-0085's cited concurrency example. No inventory side-effects |
| RCV-F008 | Cancel receipt | Draft β Cancelled | RMS+RMH+market | Implemented | POST /purchase-receipts/{id}/cancel (:89-105) |
No movements; atomic guard |
| RCV-F009 | View detail + audit | Full receipt + lines + audit user names | RMS+RMH+market | Implemented | GET /purchase-receipts/{id} β GetPurchaseReceiptEndpoint (resolves CentralDb audit names) |
|
| RCV-F010 | List / filter / search / counts | Paginated, filterable receipt list + status counts | RMS+RMH+market | Implemented | GET /purchase-receipts β ListPurchaseReceiptsEndpoint; Draft/Completed/WithPo counts (#633) |
Search by receipt# or PO# |
| RCV-F011 | Document numbering | Human-readable RCV-000xxx per merchant |
RMS+RMH+market | Implemented | DocumentSequence "RCV"; unique (MerchantId, DocumentNumber) |
Sequence service itself is shared-infra (RCV-D09) |
| RCV-F012 | Cross-merchant isolation | Every receipt op tenant-scoped | QIIUB-original (tenancy) | Implemented | Composite PK/FK MerchantId-first; 7 cross-merchant tests (one per test file) | Critical Rule #1 |
| RCV-F013 | Unit-cost capture (cost variance) | Actual receiving cost on the line, may differ from PO | RMS+RMH+market | Implemented | PurchaseReceiptLine.UnitCost; class doc "may differ from PO cost (cost variance)" |
Was PO's dependency (PO-F047); scored here. Capture only β the variance history is RCV-F016 |
| ID | Name | Description | Source | Status | Evidence | Notes |
|---|---|---|---|---|---|---|
| RCV-F014 | Receive without PO (standalone) | Book stock in with no originating PO | market+RMS | Implemented | PurchaseOrderId nullable; Create accepts null PO; Complete guards PO side-effects behind HasValue (:296) |
Was PO's dependency (PO-F048); scored here. Near-universal in market. Tier T2 (module is coherent PO-only) β arguable T1 |
| RCV-F015 | Cost update on receipt (last-cost) | Item cost reflects the received cost | RMS+RMH+market | Implemented | lp.Cost = line.UnitCost (:236 existing, :251 new); ReceivingLastCostTests.cs (2); PR #906 |
Partial-equivalent of the market's near-universal average-cost update β meets the "cost reflects receipt" need via last-cost. Avg/FIFO is RCV-F024. Selling price deliberately untouched (#635) |
| RCV-F016 | Cost-history on receiving variance | A queryable CostChange row when receipt cost β current cost |
RMH+market+QIIUB-original | Missing | Zero CostChange writes on any receipt path (sole writer in src/ is Products/UpdateLocationOverrides); live-confirmed 2026-07-19: cost changed NULLβ474.26, CostChange stayed 0 |
The C1 gap. Missing, not Partial (Fable): the InventoryMovement.UnitCost snapshot (:191-203) is already credited to RCV-F005 β crediting it again here double-counts (guard b); the designed mechanism (CostChange ReceivingVariance, ADR-0023:21 + design Β§6.2:642-644) has zero in-module fraction. Natural home: a CostChange insert in CompletePurchaseReceiptEndpoint |
| RCV-F017 | Supplier delivery-note reference | Capture supplier delivery/packing-slip no + date | RMH | Missing | No SupplierDelNo/DelDate/ExternalDocNo columns on PurchaseReceipt (verified) |
Evergreen home: nullable columns on PurchaseReceipt. RMH POD_Receipt.SupplierDelNo |
| RCV-F018 | Receipt output (print / ack / labels) | Printable goods-received acknowledgement + item labels | RMS+RMH | Missing | No print endpoint; ADR-0070 "Receipt Ack" is Phase-1-proposed but unbuilt (zero QuestPDF in src/) |
Pairs with PO-F023 (PO print). Received-item barcode labels folded here |
| RCV-F019 | Lot / batch + expiry capture | Lot number + expiry per received line | RMH+market+QIIUB-original | Missing | No lot/batch/expiry fields on either entity; design Β§10.4 "not in Phase 1" | Rx-CRITICAL β pharmacy receiving is lot/expiry-driven by regulation; the Rx vertical is not compliant-complete without it. Broadly a T3 differentiator, raised to T2 by QIIUB's Rx ambition |
| RCV-F020 | Landed cost allocation | Spread freight/duty/tax across received lines into unit cost | RMS+market | Missing | Design Β§10.3 ReceiptCharge table β not built; no ADR |
Market-common now (Cin7/NetSuite/Odoo). RMS had it fully (cost-distribution methods Qty/Value/Manual folded here) |
| RCV-F021 | Receipt correction / reversal | Adjust or reverse an already-posted receipt | RMH | Missing | Completed is terminal (no transition out); no un-post path | RMH POD_ReceiptEntry.Correction. QIIUB workaround = an inventory Adjustment (dependency RCV-D06) β but the receipt can't be corrected |
| RCV-F024 | Average / FIFO / FEFO costing | Configurable cost model + cost layers at receipt | market | Missing | Last-cost only (credited RCV-F015); no averaging/layer logic anywhere; needs its own ADR (spec :53) |
T2 because average-cost update is near-universal (QIIUB does last-cost, not average); FIFO/FEFO is the differentiator tail. No double-count with F015 β distinct code |
| ID | Name | Description | Source | Status | Evidence | Notes |
|---|---|---|---|---|---|---|
| RCV-F022 | Off-supplier auto-link on receipt | Receiving an unlisted product auto-links it to the supplier | QIIUB-original+RMS | Implemented | LinkOffSupplierProductsAsync (:372-471): per-product ProductSupplier, SupplierCost=UnitCost, savepoint, idempotent; OffSupplierReceivingTests.cs (6); PR #906 |
QIIUB-native enrichment; adjacent to RMS "auto-update supplier cost on receipt" (#14). Creates links only |
| RCV-F023 | Over-receipt handling / tolerance | Allow/flag receiving more than ordered | RMS+market | Partial | Over-receive allowed with a warning; QuantityToReceive clamped β₯0 (:265-292) |
Partial: over-receive works + warns, but no configurable tolerance limit (NetSuite/Odoo). What works = accept+warn; missing = tolerance policy |
| RCV-F025 | Auto-update existing supplier cost | Push received cost onto an already-linked ProductSupplier | RMS | Missing | Off-supplier link skips products already linked β never updates existing SupplierCost (:372-471) |
RMS POUpdateSupplierCost. RCV-F022 covers only the create-new case (no double-count) |
| RCV-F026 | Serialized item receiving | Capture serial numbers per unit at receipt | RMS+RMH+market | Missing | No serial fields | Differentiator (Cin7/NetSuite/Odoo; Square/Shopify absent) |
| RCV-F027 | Discrepancy status flag | Flag a receipt/PO when ordered β received | RMH | Missing | No discrepancy flag; over-receive only logs a warning | RMH DiscrepancyStatus/DelStatus |
| RCV-F028 | Partial-receipt disposition menu | On close-out, choose backorder / spawn child / mark partial | RMS | Missing | Completing a partial receipt just leaves the PO PartiallyReceived β no disposition choice |
RMS parent/origin-PO options. QIIUB keeps the remainder open on the PO (PO-F026) β a simpler model; the menu is absent |
| RCV-F029 | ASN / advance-ship-notice receiving | Pre-fill a receipt from a supplier ship notice | market | Missing | No ASN import; supplier del-note ref itself also absent (RCV-F017) | NetSuite native; others EDI/none. Pairs with PO-F031 (electronic PO) |
| RCV-F030 | Put-away / bin assignment | Direct received stock to a bin/sub-location | market | Missing | No bin/sub-location model in QIIUB inventory | WMS-tier (Cin7/NetSuite/Odoo). Scoping decision / N/A candidate β needs a bin model QIIUB doesn't have |
| RCV-F031 | In-receipt guidance / KPI panel | Show on-hand/reorder/history/KPI while receiving | RMS+RMH | Missing | No read-side panel on the receive screen | RMS power feature; sibling of PO-F038 |
| RCV-F032 | Receive-All / quick full receipt | One click sets received = ordered for all pending lines | RMS+RMH | Implemented | receiveAllPending() β PurchaseReceiptCreatePage.tsx:271-284, wired to a header button :703-713 (sets quantityReceived := quantityToReceive, stocking-UoM converted) |
Fable-caught + coordinator-verified β was scored Partial on unchecked evidence (a verification miss); it is fully built |
| RCV-F033 | Blind receiving | Receive without seeing ordered quantities | market | Missing | No blind-mode UI | Niche β rare/absent across all 6 modern platforms. N/A candidate; kept as a low-priority T3 for denominator honesty |
Dependencies (excluded from score) β listed for reproducibility:
| ID | Name | Owner module | Status | Note |
|---|---|---|---|---|
| RCV-D01 | PO status from receipt coverage | Purchasing (PO-F017) | Dependency | Code lives here (CompletePurchaseReceiptEndpoint.cs:296-322) but scored in PO β the single-owner case |
| RCV-D02 | Backorder / remainder tracking | Purchasing (PO-F026) | Dependency | QuantityToReceive remainder stays open on the PO |
| RCV-D03 | Reorder generation from received data | Purchasing (PO-F025) | Dependency | Reads LastReceivedDate / updated cost |
| RCV-D04 | Distribution to stores | Distribution | Dependency | Allocates received goods |
| RCV-D05 | Vendor invoice / 3-way match / GL | AP (future) | Dependency | QuantityInvoiced/QuantityToInvoice reserved on the receipt line |
| RCV-D06 | Inventory adjustment (stock correction) | Adjustments | Dependency | The workaround for the missing receipt correction (RCV-F021) |
| RCV-D07 | Scanner / mobile / handheld receiving | POS / Mobile (future) | Dependency | Near-universal in market, but a POS/mobile surface, not the portal receipt module |
| RCV-D08 | Monitoring-receiving-status dashboard | Reporting | Dependency | HQ cross-store receiving report |
| RCV-D09 | Document sequence service | Shared-infra | Dependency | Mints RCV-; the receipt's use is RCV-F011 |
| RCV-D10 | Master data (Supplier/Variant/LocationProduct/UoM) | Catalog / Inventory | Dependency | Referenced; tables owned upstream |
N/A β not applicable to QIIUB (excluded; listed so a reader sees they were considered):
| ID | Name | Why N/A (not Missing) |
|---|---|---|
| RCV-N01 | Commit-vs-Save inventory toggle | Superseded by architecture. RMS separates "save entries" from "commit to inventory"; QIIUB's Draft (no inventory effect) β Complete (posts) split is that distinction, met a cleaner way. Scoring it Missing would penalize a deliberate design. |
| RCV-N02 | Lot/serial cross-site propagation + AutoReceive | Dissolves in the cloud model. RMH POP_SerialDownload/Upload with an AutoReceive flag exists because HQ and stores run on disconnected DBs. QIIUB is cloud single-source β no download/upload/auto-receive lifecycle. (The lot/expiry capture itself is a real gap β RCV-F019 β this N/A is only the cross-site propagation machinery.) |
| RCV-N03 | Import legacy receipts | ADR-0038 (:26) lists "NOT imported: Purchase orders, transfers, inventory adjustments, open batches" β "receipt" is not named, but receipts of un-imported POs can't exist and merchants "start fresh." N/A by that inference (not a verbatim ADR statement). |
Two tables are owned: PurchaseReceipt, PurchaseReceiptLine. Both extend BaseEntity (soft-deletable β a receipt is an operational document, not a posted-immutable financial record; financial immutability attaches at the future AP/invoice layer). Composite PK (MerchantId, Id), PublicId varchar(32) unique per merchant, from BaseMerchantEntityConfiguration<T>.
PurchaseReceiptConfiguration.cs| Column | SQL type | Null | Business purpose |
|---|---|---|---|
| Id, MerchantId | bigint, int | no | Composite PK; tenancy |
| PublicId | varchar(32) | no | External identity (prefix rcv, ADR-0082) |
| DocumentNumber | varchar(20) | no | Human number RCV-000142; unique per merchant |
| PurchaseOrderId | bigint | yes | FK β PO β nullable = receive-without-PO |
| LocationId | bigint | no | FK β Location (where received) |
| SupplierId | bigint | no | FK β Supplier |
| EmployeeId | bigint | yes | Reserved / dead β never written by any endpoint |
| Status | tinyint | no | Draft/Completed/Cancelled (0β2) |
| ReceiptDate | datetime2(3) | no | When goods arrived |
| Notes | nvarchar(500) | yes | Free-text |
| ClosedDate | datetime2(3) | yes | Set at Complete |
| ClosedByEmployeeId | bigint | yes | Reserved / dead β never written |
Indexes: unique (MerchantId, DocumentNumber); (MerchantId, PurchaseOrderId), (MerchantId, LocationId), (MerchantId, SupplierId). FKs (composite, MerchantId-first): PO / Location / Supplier β all Restrict.
PurchaseReceiptLineConfiguration.cs| Column | SQL type | Null | Business purpose |
|---|---|---|---|
| Id, MerchantId | bigint, int | no | Composite PK; tenancy |
| PublicId | varchar(32) | no | External identity (prefix rcln) |
| PurchaseReceiptId | bigint | no | FK β parent receipt |
| PurchaseOrderLineId | bigint | yes | FK β PO line (nullable = standalone line) |
| LineNumber | int | no | Ordinal |
| ProductVariantId | bigint | no | What was received |
| QuantityReceived | decimal(10,4) | no | Qty in the purchasing UoM |
| QuantityInStockingUoM | decimal(10,4) | no | Qty posted to inventory (stocking UoM) |
| PurchasingUoMId | bigint | yes | FK β UnitOfMeasure |
| UnitCost | decimal(18,4) | no | Actual receiving cost (cost variance vs PO) |
| UnitPrice | decimal(18,2) | no | Captured; NOT written to LocationProduct.Price (#635) |
| Notes | nvarchar(200) | yes | Free-text |
Indexes: (MerchantId, PurchaseReceiptId), (MerchantId, PurchaseOrderLineId); unique (MerchantId, PurchaseReceiptId, PublicId) (ADR-0082 Cat-2 nested lookup). FKs: Receipt / POLine / Variant / UoM β all Restrict.
Reserved/unwired: EmployeeId, ClosedByEmployeeId (audit is via BaseEntity.CreatedByUserId/UpdatedByUserId). Absent entirely: any serial / lot / batch / expiry / bin / supplier-delivery-note columns.
Referenced (dependency-owned): PurchaseOrder/PurchaseOrderLine, Supplier, ProductVariant, LocationProduct (+ Cost/QuantityOnHand/LastReceivedDate), ProductSupplier, InventoryMovement, CostChange (designed to be written here, isn't β C1), UnitOfMeasure, DocumentSequence.
| Surface | Verb+Route | Impl | Realizes | Roles |
|---|---|---|---|---|
| API | POST /purchase-receipts |
CreatePurchaseReceiptEndpoint | RCV-F001, F002, F011, F013, F014 | Admin/Mgr/SysAdmin |
| API | GET /purchase-receipts/{PublicId} |
GetPurchaseReceiptEndpoint | RCV-F009 | " |
| API | GET /purchase-receipts |
ListPurchaseReceiptsEndpoint | RCV-F010 | " |
| API | PUT /purchase-receipts/{PublicId} |
UpdatePurchaseReceiptEndpoint | RCV-F007 | " |
| API | POST /purchase-receipts/{PublicId}/complete |
CompletePurchaseReceiptEndpoint | RCV-F005, F015, F022 (+ RCV-D01 PO-status, PO-owned) | " |
| API | POST /purchase-receipts/{PublicId}/cancel |
CancelPurchaseReceiptEndpoint | RCV-F008 | " |
| portal | Receipts list + standalone create + PO-driven "receive goods" + detail (Complete/Cancel modals) | apps/qiiub-portal/src/features/purchaseReceipts/ + /purchase-orders/{id}/receive |
RCV-F001βF010 (UI) | merchant Admin/Manager |
CompletePurchaseReceiptEndpoint but is PO-owned (scored PO-F017).Present β field-level (FluentValidation): PublicId prefix checks (rcv_/rcln_/po_/poln_/supp_/loc_/pvar_); decimal precision/scale/sign bounds; Notes max-lengths; list caps; sort-whitelist on list. Zero-qty coherence: a line's QuantityReceived and QuantityInStockingUoM must agree on zero-ness (Create :74-101, Complete :179-188 β 409).
Present β business-rule (handlers): Draft-only edit guard (409); atomic status-transition predicates (Complete/Cancel flip only from Draft β 409); no-positively-received-line rejection on Complete (400/409, :148-154); one-open-Draft-receipt-per-PO (app-level, :178-191, explicitly not race-proof); POLine-belongs-to-PO check; RowVersion optimistic concurrency β 409; over-receive warning (non-blocking).
Absent β business validations:
| Missing validation | Effect today | Tracked as |
|---|---|---|
| One-open-Draft-per-PO as a DB constraint | Concurrent creates could both pass the app check β two open drafts | Β§7 hardening (needs filtered-unique index) |
| Over-receipt tolerance limit | Any over-receive is accepted (only warned) | RCV-F023 (Partial) |
| Receipt cost β 0 / sanity | An all-zero-cost receipt posts (cost overwrite to 0) | Β§7 hardening |
| Active-supplier / active-location check | A receipt against an inactive supplier/location is allowed | Β§7 hardening |
Second axis, orthogonal to the score. Testedness never enters Β§6.
Inventory (at e70ff195, method counts): 40 integration (tests/QIIUB.Tests.Integration/Features/PurchaseReceipts/ β 7 files) Β· 0 dedicated unit Β· 0 e2e. Counts are context.
Critical-flow coverage (flow granularity):
| Flow | Tested |
|---|---|
| Create Draft (guards, one-open-per-PO, POLine-belongs-to-PO, cross-merchant) | β |
| Update Draft (RowVersion, zero-qty/no-lines 409, same-variant aggregation, cross-merchant 404) | β (12) |
| Complete β post to inventory (partial receive, PO accumulator, incoherent-line 409, never-stocked price-null) | β |
| Last-cost overwrite on complete (+ cross-merchant cost isolation) | β (2) |
| Off-supplier auto-link matrix (+ cross-merchant isolation) | β (6) |
| List filter/search + status counts (+ cross-merchant) | β |
| Cross-merchant isolation (create/update/list/complete/last-cost/off-supplier) | β (7, one per file) |
| Cancel DraftβCancelled (+ non-Draft 409) | β (no dedicated Cancel test file found) |
| Complete of a standalone / no-PO receipt (end-to-end) | β (inferred from PO-guard code, not asserted) |
| CostChange written on cost variance | β (feature absent β RCV-F016) |
| Dedicated unit tests (validators, cost math) | β (0 unit files) |
Named test gaps β Β§7 "test hardening": the Cancel path, a standalone-receipt E2E, and zero dedicated unit coverage. Plus the reserved-and-unwired EmployeeId/ClosedByEmployeeId (no write path, no test). All low-to-moderate risk; the honest edges of the "T1 100%" claim.
Live confirmation (2026-07-19). Drove the full receiveβpost chain against the running app (local dotnet run API + SQL): created a 1-line PO (qty 5) β submitted β created a receipt for 3 of 5 β completed. Observed:
Draft β Completed (200); PO β PartiallyReceived β confirms RCV-D01 (the PO-owned status write) end-to-end.LocationProduct: on-hand 8 β 11 (+3, RCV-F005), Cost NULL β 474.26 (RCV-F015 last-cost), LastReceivedDate stamped; InventoryMovement 0 β 1 (Receipt, qty 3, cost 474.26).CostChange 0 β 0 despite the cost changing β the C1 gap reproduced live: cost history is silently lost on the receiving path. This is direct evidence for RCV-F016 = Missing β the designed cost-history mechanism produced nothing.LocationProduct restored to pre-state afterward β zero DB residue.This is the confidence axis's strongest tier: the core (RCV-F005/F015) and the boundary (RCV-D01) are live-driven, and the sharpest gap (C1/RCV-F016) is live-proven, not just code-read.
Formula (fixed, template Β§6): weights T1Γ3/T2Γ2/T3Γ1; Implemented=1.0, Partial=0.5, Missing=0.0; Dependency + N/A excluded.
| Tier | Implemented | Partial | Missing | Dep+N/A excluded | Scored count | Ξ£ statusValue | Tier % |
|---|---|---|---|---|---|---|---|
| T1 (Γ3) | 13 | 0 | 0 | 0 | 13 | 13.0 | 100.0% |
| T2 (Γ2) | 2 | 0 | 7 | 0 | 9 | 2.0 | 22.2% |
| T3 (Γ1) | 2 | 1 | 8 | 0 | 11 | 2.5 | 22.7% |
T2 scored (9): Implemented F014/F015; Missing F016/F017/F018/F019/F020/F021/F024. β 2Γ1 + 7Γ0 = 2.0 / 9 = 22.2%. T3 scored (11): Implemented F022/F032; Partial F023; Missing F025/F026/F027/F028/F029/F030/F031/F033. β 2 + 1Γ0.5 + 8Γ0 = 2.5 / 11 = 22.7%. Excluded: 10 Dependency (RCV-D01βD10) + 3 N/A (RCV-N01βN03).
Overall weighted:
numerator = 3Γ13.0 + 2Γ2.0 + 1Γ2.5 = 39 + 4 + 2.5 = 45.5
denominator = 3Γ13 + 2Γ9 + 1Γ11 = 39 + 18 + 11 = 68
overall% = 45.5 / 68 = 66.9%
Interpretation. The receiving core is complete and tested (T1 100%): a merchant can create a receipt (against a PO or standalone), receive partially, and complete it to post inventory β on-hand, movement trail, and last-cost β with tenant isolation and lifecycle guards, all covered by 40 integration tests. What's missing is breadth: the designed cost-history (CostChange) is not written on the receiving path (the sharpest gap β Missing, and proven Missing live: the cost changed but no CostChange row appeared), lot/expiry capture is absent (an Rx-blocking gap), and average/FIFO, landed cost, print, supplier delivery-note, correction/reversal, serial, ASN are unbuilt. ~67% is a healthy early-phase signal β close to PO's 73β74%, and the lowest scores (cost-history, lot/expiry, average costing) are the ones that matter most for a real supply chain.
Sensitivity / anti-gaming notes:
artifact-receivings-fable-review.md.CostChange mechanism has zero in-module fraction. Live-confirmed Missing (cost changed, CostChange stayed 0).| Rank | Gap (ID) | Tier | Impact | Recommendation | ADR? |
|---|---|---|---|---|---|
| 1 | RCV-F019 Lot / batch + expiry capture | T2 | High β Rx-blocking. Pharmacy receiving is lot/expiry-driven by law | Add lot/expiry columns to PurchaseReceiptLine + capture UI + FEFO downstream. Design Β§10.4 flagged it |
requires new ADR (schema + traceability model) |
| 2 | RCV-F016 CostChange on receiving | T2 | High β correctness/audit. Cost history is silently lost (only the movement snapshot records it) | Write a CostChange (ReceivingVariance) row in Complete when UnitCost β LocationProduct.Cost β the table + enum already exist |
No β over existing schema (closes C1) |
| 3 | RCV-F018 Receipt print / acknowledgement | T2 | Medium β no formal goods-received document | Implement ADR-0070 Receipt Ack (QuestPDF), sibling of PO print | Promote ADR-0070 |
| 4 | RCV-F020 Landed cost allocation | T2/T3 | Medium β true cost understated (freight/duty not in unit cost) | Build the ReceiptCharge table (design Β§10.3) + a distribution method |
requires new ADR |
| 5 | RCV-F021 Receipt correction / reversal | T2 | Medium β a mistaken receipt can't be undone, only adjusted | Add a reverse/correct path (un-post movement + restore cost) or document the Adjustments workaround | Likely new ADR |
| β | RCV-F017 Supplier delivery-note ref | T2 | Low-Med | Nullable columns on PurchaseReceipt |
No |
| β | Validation + test hardening (Β§5b/Β§5c) | β | Low-Med | DB filtered-unique for one-open-Draft-per-PO; over-receipt tolerance; a Cancel test; a standalone-receipt E2E; first unit tests | No |
| β | RCV-F024 avg/FIFO, F026 serial, F029 ASN, F030 put-away | T3 | Low now, high at ERP-tier | Defer; each needs an ADR + (serial/lot) a traceability model | new ADR each |
Quick wins: #2 CostChange (table exists), RCV-F017 del-note columns. Net-new: #1 lot/expiry, #4 landed cost, correction/reversal, avg/FIFO, serial, ASN, put-away.
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, never hand-maintained.β untracked= a gap with no open issue.
| Gap (ID) | Tracked by (open GHES issue) |
|---|---|
| RCV-F019 lot / batch + expiry (Rx-BLOCKING) | β untracked β β a regulation-blocking gap with no ticket |
| RCV-F016 CostChange on receiving (C1) | β untracked (design + ADR-0023 mandate it; WIDS365 ships it; QIIUB has no issue) |
| One-open-Draft receipt DB guard (Β§5b) | #808 "Race-proof one-open-Draft-per-PO via filtered unique index" (same guard family) β partial |
| RCV-F020 landed cost, F021 correction/reversal, F024 avg/FIFO, F018 print, F017 delivery-note ref | β untracked |
| (seed / demo data) | #807 "DevSeed coverage β populate empty feature areas" β adjacent |
Read (the actionable alarm): the two sharpest Receivings gaps β Rx lot/expiry (F019) and cost-history (F016/C1) β are completely untracked in GHES. If Receivings is going to a pharmacy client, F019 needs a ticket now. This is exactly the "what's missing beyond the ~100 open issues" signal the scorecard is for.
Contradictions (from artifact-receivings-gathering.md):
LocationProduct.Cost but writes NO CostChange row. ADR-0023 (0023:21) + design Β§6.2 (inventory-supply-chain.md:642-644) mandate a CostChange (ReceivingVariance) on cost variance; the shipped code (CompletePurchaseReceiptEndpoint.cs:236) just overwrites. CostChange is written only by Products/UpdateLocationOverrides. The Tier-2 cost-history promise is unfulfilled on the receiving path. (RCV-F016 Missing; Β§7 gap #2.)inventory-supply-chain.md frozen at Draft/2026-03-13, never updated for last-cost overwrite, off-supplier auto-link, or the #635 "never write Price" rule β all shipped.Decisions needed from a human:
EmployeeId/ClosedByEmployeeId intended owner (or drop them).Every capability in the legacy+market inventory (artifact-receivings-gathering.md Β§C, 34 rows) mapped to a disposition. scored = own Β§3 row; folded = subsumed; N/A = out of scope; dep = neighbor module.
| Source capability | Disposition |
|---|---|
| 1 Receive against a PO | scored RCV-F002 |
| 2 Receive-All / quick full receipt | scored RCV-F032 (Implemented) |
| 3 Partial receipt | scored RCV-F003 |
| 4 Received-to-date accumulation | scored RCV-F004 |
| 5 Commit-vs-Save toggle | N/A RCV-N01 (superseded by DraftβComplete) |
| 6 Standalone / ad-hoc receive (no PO) | scored RCV-F014 |
| 7 Quick-scan at desk | dep RCV-D07 (POS/mobile surface) β folded with #8 |
| 8 Mobile / handheld WMS receiving | dep RCV-D07 |
| 9 Landed cost | scored RCV-F020 |
| 10 Cost-distribution methods (Qty/Value/Manual) | folded into RCV-F020 |
| 11 Actual shipping/fees at receipt | folded into RCV-F020 |
| 12 Cost-update method (None/Last/Wtd-Avg) | Last β scored RCV-F015; the configurable/avg half β RCV-F024 |
| 13 FIFO/FEFO/Std layers | folded into RCV-F024 |
| 14 Auto-update supplier cost on receipt | scored RCV-F025 (existing links); create-new is RCV-F022 |
| 15 Store Last-Cost / LastReceived | scored RCV-F015 (+ LastReceivedDate in RCV-F005) |
| 16 Serialized receiving | scored RCV-F026 |
| 17 Lot/batch capture | scored RCV-F019 |
| 18 Expiry capture / FEFO | folded into RCV-F019 (capture) + RCV-F024 (FEFO costing) |
| 19 Lot/serial cross-site propagation + AutoReceive | N/A RCV-N02 (cloud-dissolved) |
| 20 Partial-receipt disposition menu | scored RCV-F028 |
| 21 Backorder remainder | dep RCV-D02 (PO-F026 β remainder is PO-owned) |
| 22 Over-receipt tolerance | scored RCV-F023 (Partial) |
| 23 Discrepancy-status flag | scored RCV-F027 |
| 24 Receipt correction / reversal | scored RCV-F021 |
| 25 Blind receiving | scored RCV-F033 (Missing; N/A candidate) |
| 26 Discrete receipt doc (own #/status) | folded into RCV-F001/F006/F011 (the module premise; no separate row) |
| 27 Supplier delivery-note ref | scored RCV-F017 |
| 28 Print labels for received items | folded into RCV-F018 |
| 29 Print / acknowledge receipt | scored RCV-F018 |
| 30 Customizable receipt template | folded into RCV-F018 |
| 31 In-receipt ordering-guidance / KPI panel | scored RCV-F031 |
| 32 ASN receive-from-advance-ship-notice | scored RCV-F029 |
| 33 Put-away / bin on receipt | scored RCV-F030 (Missing; N/A candidate) |
| 34 Receipt β vendor-bill / 3-way match | dep RCV-D05 (AP) |
| (QIIUB-original) Off-supplier auto-link on receipt | scored RCV-F022 |
| (QIIUB-original) Cost-history via CostChange on receiving | scored RCV-F016 (Missing β the C1 gap) |
| (ADR-0038) Import legacy receipts | N/A RCV-N03 |
| (RMS/RMH) Monitoring receiving status dashboard | dep RCV-D08 (Reporting) |
No source line is left unreconciled.
WIDS365 sweep (2026-07-26 β retroactive 4th source, artifact-receivings-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 new denominator row and changes no status β β67% stands. Its receiving is stored-proc-centric (web_ReceivingPost.sql, OpenβClosed one-transaction: PO accrual + InventoryTransferLog movement + ItemDynamic on-hand + last-cost + supplier auto-link + DevExpress print) β every capability maps to an existing RCV-Fxxx. Value = corroboration, notably:
web_ReceivingPost.sql:145-150 writes a CostChangesLog row (type 1 = "Receiving") on every cost variance. A production BCPOS POS does exactly what QIIUB designed (ADR-0023 + design Β§6.2) but left unbuilt β so QIIUB's Missing here is a real gap vs a sibling system, not just vs "the market." Also corroborates RCV-F025 (WIDS365 updates the existing supplier's cost, :175-181 β Missing in QIIUB) and RCV-F017 (ReceivingHeader.ReferenceNumber = delivery-note ref β Missing in QIIUB).LK_ReceivingCharges is an unused stub β RCV-F020), no discrepancy flag (RCV-F027), no receipt reversal (RCV-F021). Guard (e) now spans four source families: RMS/RMH + market + QIIUB-original + WIDS365.| Term | In plain words |
|---|---|
| Receipt (PurchaseReceipt) | The document that records goods physically arriving. One PO can have several. |
| Receive without PO | Booking stock in when there's no purchase order (walk-in delivery, emergency, off-supplier). |
| Complete (a receipt) | The step that actually raises inventory: on-hand up, a movement logged, cost updated. Before that it's just a Draft. |
| QuantityInStockingUoM | How much is added to inventory (in the unit you stock in), vs QuantityReceived (in the unit you buy in). |
| Last-cost | Setting the item's cost to whatever it cost on this receipt (last one wins), vs averaging. |
| Weighted-average / FIFO / FEFO | Costing methods QIIUB doesn't do: blend costs, oldest-first, or earliest-expiry-first. |
| Landed cost | The item's true cost after adding freight, duty, and taxes on top of the price. |
| CostChange | A log row recording that an item's cost changed β designed to be written on receiving, currently isn't (the C1 gap). |
| InventoryMovement | The audit trail row for every stock change (a receipt writes one per line). |
| Lot / batch + expiry | Tracking which production batch and expiration date each received unit belongs to β essential for pharmacy, not built. |
| Serialized receiving | Capturing a serial number per received unit (electronics, high-value) β not built. |
| Off-supplier auto-link | When you receive a product the supplier isn't yet linked to, QIIUB auto-links it (QIIUB-native). |
| Backorder / remainder | The unreceived part of an order β tracked on the PO (PO-owned), not the receipt. |
| ASN | Advance Ship Notice β an electronic "here's what's coming" from the supplier, used to pre-fill a receipt. |
| Put-away / bin | Directing received stock to a specific shelf/bin β warehouse-tier, no bin model in QIIUB. |
| Single-owner rule | Each capability is scored in exactly one module. The PO-status write lives in this module's code but is scored in PO, not here. |
| Dependency (in this doc) | A capability that belongs to a neighbor module, so it's not counted in this %. |
| N/A (in this doc) | A legacy capability that doesn't apply to QIIUB (superseded by our design), so it's not a gap and not counted. |