Wiki πŸ” Search
QIIUB Β· Module completeness

Receivings

33 scored features 13 dependencies + N/A area: Inventory & Supply Chain

measured at e70ff195

67%
Overall
T1100%
T222%
T323%

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

Second filled instance of module-scorecard-template.md (after po-scorecard.md). Read-only analysis; recommendations only. Evidence is file:line / endpoint against D:\BCPOSGit\qiiub on branch main at commit e70ff195. 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.


0. Document control

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).


1. Module identity

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.

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.


1b. Canonical flow β€” how goods are received

  1. Create (Draft) β€” 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).
  2. Build β€” add lines while Draft (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).
  3. Complete β€” 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).
  4. Cancel β€” 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).


2. Dependency map

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):


3. Functional taxonomy

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 Missing means "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.

T1 β€” Core (module unusable without) β€” weight Γ—3

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

T2 β€” Standard (market expects) β€” weight Γ—2

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

T3 β€” Advanced / Differentiator β€” weight Γ—1

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).

4. Storage specification

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>.

Table: PurchaseReceipt β€” 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.

Table: PurchaseReceiptLine β€” 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.


5. API surface

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

5b. Validations inventory

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

5c. Test surface & confidence

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:

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.


6. Scoring rubric & result

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%

Receivings module completeness: β‰ˆ 67% overall β€” T1 100%, T2 22%, T3 23%.

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:


7. Gap list & recommendations

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.


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, 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.

8. Open questions & contradictions

Contradictions (from artifact-receivings-gathering.md):

  1. C1 β€” receiving overwrites 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.)
  2. C2 β€” design doc 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.
  3. C3 β€” index-vs-PROGRESS drift: CLEAN for the receiving ADRs (0021/0023 README matches front-matter). PROGRESS Phase-5 counts predate #884/#906 (expected staleness, not a contradiction).
  4. C4 β€” plan-vs-shipped (harmless): the off-supplier savepoint is in the design doc but not the plan; the shipped code followed the stronger design.

Decisions needed from a human:


9. Source reconciliation (denominator integrity)

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:


Glossary

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.