Files
ERP-core/docs/10-BACKEND-PHASE1.md
T

29 KiB
Raw Blame History

10 · BACKEND — Phase 1 Spec (Inventory & Supply Chain)

Authoritative for: backend architecture, business rules, and the data model (the 38-entity schema). Navigation: you arrived here from 00-CORE.md. API request/response contracts are in 11-BACKEND-PHASE1.md. Frontend rules are in 20-FRONTEND.md. Record work in Backend/PROGRESS.md. Scope basis: SRS v1.1. Costing = FIFO · Multi-warehouse · Single-tenant · RBAC deferred (user identity stamped) · approvals auto/config-gated · vendor invoice + 3-way match deferred to Accounting. Authentication: delegated to the external AuthHex identity provider (separate service). ERPCore is a resource server that only validates AuthHex's RS256 JWTs — it does not issue tokens or own a login endpoint. See A.4 (Authentication / Audit actor). RBAC (per-endpoint) still deferred.


Part A — Architecture & Layer Rules

The high-level layering is introduced in 00-CORE.md §4; this part is the authoritative detail the implementation must follow.

A.1 Layered flow (one direction only)

HTTP ─► Controller ─► Service ─► Repository ─► UnitOfWork / ErpDbContext ─► PostgreSQL
        (DTOs)        (logic,     (entities)     (transaction boundary)
                       returns DTOs)

Controller

  • HTTP concerns only: routing, model binding, status codes, [ProducesResponseType].
  • Accepts and returns DTOs only. Never references EF entities or ErpDbContext.
  • No business logic, no data access.

Service

  • Owns business logic and orchestration. Returns DTOs.
  • Maps entity ⇄ DTO (manual mapping is fine and dependency-free; keep mapping in the service or a dedicated mapper class).
  • Opens the UnitOfWork transaction for any operation that spans more than one write.

Repository

  • EF Core data access over entities. One repository per aggregate (Item, PurchaseOrder, Grn, Stock…).
  • Query + persistence only. No business rules. Returns entities or projections to services.

UnitOfWork

  • The transaction boundary. Wraps a single ErpDbContext; exposes SaveChangesAsync() and an explicit transaction scope (BeginTransactionAsync) for multi-step stock operations.
  • Every stock-affecting operation (GRN confirm, transfer dispatch/receive, adjustment, purchase return, count post) runs inside one UoW transaction so it commits or rolls back atomically (NFR-02, NFR-05).

A.2 FIFO placement (critical)

  • FIFO cost-layer consumption and valuation live in a domain service: Services/Stock/FifoCostingService.
  • It is invoked by stock services inside the UoW transaction — never from a controller or repository.
  • On any issue (transfer-out, adjustment-out, purchase return, future goods issue), it consumes open layers oldest-first, decrements qtyRemaining, and returns the costed movement for the ledger.
  • Layer rows being consumed must be locked for the duration of the transaction to stay concurrency-safe (NFR-02). Use SELECT … FOR UPDATE semantics (EF: query within the transaction with appropriate locking) so two concurrent issues can't consume the same remaining quantity.

A.3 DTO boundary

  • Every request body and response is a DTO in Dtos/. Group by module (Dtos/Items, Dtos/Procurement, Dtos/Grn, Dtos/Stock, Dtos/Common).
  • Dtos/Common holds shared shapes: PagedResult<T>, PaginationMeta, and the error/problem shapes.
  • Entities in Domain/Entities never leave the service layer.

A.4 Cross-cutting

  • Errors: RFC 7807 ProblemDetails (framework default). Domain exceptions in System/Errors carry a stable code; a middleware maps them to ProblemDetails. Catalog in 11-BACKEND-PHASE1.md §7.
  • Authentication: ERPCore is a resource server. It validates JWTs issued by the external AuthHex IdP — algorithm RS256 (asymmetric RSA), issuer AuthHex, audience AuthHexClient. AuthHex exposes no JWKS/OIDC discovery, so ERPCore is configured with AuthHex's RSA public key statically (rotation is a manual config update). Tokens live ~1000 min (prod) / 60 min (dev). A single door authorization policy requires an ERP UserTypeCode/RoleCode claim (AuthHex is a shared IdP, so a valid token alone is not enough); per-endpoint RBAC stays deferred.
  • Audit actor: the token carries no sub/nameid; identity is AuthHex's custom UserId (GUID) claim. An ICurrentUser abstraction (Infra/Auth) resolves the acting user from a local shadow user — the GUID is mapped (JIT-provisioned) to a local int users.user_id that all FKs reference (see C.7). Services stamp mutations with it. Never trust a createdBy from the request body.
  • Concurrency: mutable resources carry a RowVersion ([Timestamp] byte[]), surfaced as ETag; PUT/PATCH require If-Match412 on mismatch.
  • Numbering: document numbers come from NumberSequence (per doc type, per year), issued inside the same transaction as the document.

A.5 DI registration (lifetimes)

  • ErpDbContext: scoped (default).
  • IUnitOfWork, repositories, services, ICurrentUser, FifoCostingService: scoped.
  • Register in Program.cs (or an AddApplication() extension) after AddDbContext.
  • Auth wiring: JWT bearer validation is built from AuthHex's RSA public key (config XML → RsaSecurityKey) with ValidIssuer=AuthHex, ValidAudience=AuthHexClient. A scoped IClaimsTransformation provisions/looks up the local shadow user (by auth_user_id = token UserId GUID) and injects the resolved local int id as a ClaimTypes.NameIdentifier (nameid) claim, so ICurrentUser/AuditUserId resolve a real user unchanged (falling back to the seeded system user only when unauthenticated).

Part B — Software Requirements Specification (v1.1)

Basis: ISO/IEC/IEEE 29148. Requirement IDs FR-<AREA>-<n>; priority M/S/C (Must/Should/Could).

B.1 Introduction

B.1.1 Purpose

Specifies Phase 1 — the Inventory & Supply Chain subsystem: master data, procurement, goods receipt, stock and warehouse management, plus the integration seams reserved for later phases.

B.1.2 Scope

In scope: master data (Item, UOM & conversions, Category, Vendor, Warehouse/Bin); procurement (Requisition → RFQ → PO with approval + amendments → Purchase Return); GRN with inspection hold and partial receipt; stock management (count, transfer with in-transit, adjustment, ledger, FIFO valuation, reorder alerts); warehouse management (multi-warehouse, bin/location, batch/expiry and serial, basic putaway/pick); cross-cutting services (user identity + audit, document numbering, reason codes).

Out of scope (deferred, hooks retained): vendor invoice & three-way match → Accounting (GRN retains PO ref, received qty, received value per line); GL posting (Phase 1 emits GL-ready journal entries as data, no ledger); sales orders / reservation fulfilment / manufacturing consumption / formal QC dispositions (interfaces stubbed, §B.7); landed-cost apportionment (decision §B.1.2.1).

B.1.2.1 Landed cost decision (open)

Imports carry freight + duty + VAT, so true unit cost ≠ PO price.

  • (A) Defer to Accounting — layers valued at PO price + directly-attributable line charges only. Simpler; valuation understates true cost.
  • (B) Include apportionment at GRN — distribute additional charges across received lines into the FIFO layer cost. More accurate; one extra workflow.

Affects FR-GRN-06 and FR-STK-14. Recommendation: B if import duties are material; else A.

B.1.3 Definitions

GRN = Goods Receipt Note · PO = Purchase Order · RFQ = Request for Quotation · PR = Purchase Requisition · UOM = Unit of Measure · FIFO = First-In-First-Out costing · FEFO = First-Expiry-First-Out picking · Cost layer = quantity received at a specific unit cost, consumed FIFO · In-transit = stock left source, not yet confirmed at destination · ROP = reorder point · RBAC = role-based access control · GL = general ledger.

B.1.4 Overview

§B.2 overall description; §B.3 functional requirements; §B.4 data model; §B.5 external interfaces; §B.6 NFRs; §B.7 future seams; §B.8 appendices. The entity model is expanded in Part C.

B.2 Overall Description

B.2.1 Product perspective

Foundation of a modular ERP. Single-tenant, multi-warehouse. All later modules depend on the Item master, stock ledger, and vendor master defined here. Module boundaries are isolated behind services (Part A).

B.2.2 Product functions

Maintain master data; raise/approve procurement through PO and return; receive goods with inspection hold; track stock movements in a costed FIFO ledger across warehouses; perform counts/transfers/adjustments with audit; track batch/expiry/serial and locate by bin; raise reorder alerts.

B.2.3 User classes

Phase-1 note: role-based permissions are deferred (FR-X-01). Identity is supplied by the external AuthHex IdP (see A.4); a single door policy admits only ERP UserType/Role holders, but beyond that any admitted user may perform any action, with each action stamped with the authenticated user's identity for audit. The roles below are the functional blueprint for future per-endpoint RBAC, not enforced boundaries.

Storekeeper/Warehouse operator (receive, count, transfer, pick) · Procurement officer (requisitions, POs, vendors) · Approver/Manager (authorizes once approvals enabled) · Inventory controller (valuation, adjustments, reorder policy) · Auditor (read-only) · System administrator (users, numbering, config).

B.2.4 Operating environment

Web application; responsive UI incl. handheld/scanner. Relational DB with transactions + row-level locking (FIFO consumption needs it). Barcode/QR capable (designed-for).

B.2.5 Constraints

FIFO is a system-wide constraint; ledger must track cost layers. Multi-warehouse mandatory day 1; multi-tenancy out of scope. Single base currency (LKR) in Phase 1. Every stock transaction atomic + immutable ledger entry.

B.2.6 Assumptions & dependencies

One base currency; invoicing/3-way match in Accounting (GRN carries data); users/warehouses configured before transactions; landed-cost scope (§B.1.2.1) resolved before GRN valuation finalized.

B.3 Functional Requirements

B.3.1 Master Data (FR-MD)

ID Requirement Pri
FR-MD-01 Maintain Item master: SKU (unique), name, description, category, item type (Stocked/NonStocked/Service), tracking mode (None/Batch/Serial), status, tax class, default vendor. M
FR-MD-02 Maintain UOM master with base UOM per item and conversion factors (purchase→stock→base). M
FR-MD-03 Convert quantities between UOMs on every transaction; store base-UOM quantity in the ledger. M
FR-MD-04 Maintain hierarchical item categories. S
FR-MD-05 Hold reorder point and reorder quantity per item, optionally per warehouse. M
FR-MD-06 Maintain Vendor master: code, name, contact, terms, tax reg, status, currency. M
FR-MD-07 Maintain Warehouse master and, within each, a bin/location structure. M
FR-MD-08 Prevent deletion of any master referenced by a transaction; deactivate instead. M

B.3.2 Procurement (FR-PROC)

ID Requirement Pri
FR-PROC-01 Create Purchase Requisition with lines (item, qty, required-by, requester). M
FR-PROC-02 Optional RFQ: issue to vendors, record quotations for comparison. S
FR-PROC-03 Generate PO from PR/RFQ or directly (item, UOM, qty, price, tax, delivery date, warehouse). M
FR-PROC-04 [Phase 1: auto-approve] Auto-approve PO on creation (status Approved). Config flag approvalRequired (default off) gates a future approval workflow (authorization matrix); when on, PO cannot issue until approved. PendingApproval state + approval fields retained in schema (no migration to enable). M
FR-PROC-05 [Phase 1: Option B — edit-while-open] PO may be freely edited while open (not fully received/closed); changes take effect immediately with an audit entry. Versioned amendments deferred; schema must not preclude adding a version field later. S
FR-PROC-06 PO lifecycle: Draft → (PendingApproval →) Approved → PartiallyReceived → FullyReceived → Closed/Cancelled. Phase 1 bypasses PendingApproval via auto-approve. M
FR-PROC-07 Support partial receipt; PO stays open until fully received or manually closed. M
FR-PROC-08 Support Purchase Return referencing original GRN/PO line; generates outbound movement. M
FR-PROC-09 Retain data sufficient for future three-way match without schema change. M

B.3.3 Goods Receipt (FR-GRN)

ID Requirement Pri
FR-GRN-01 Create GRN against an approved PO, defaulting lines/quantities from open PO lines. M
FR-GRN-02 Support GRN without PO (direct/emergency) by permission, flagged for review. S
FR-GRN-03 Support over/under-receipt tolerances (per item or global); warn or block beyond tolerance. S
FR-GRN-04 Capture batch + expiry and/or serial numbers for tracked items on receipt. M
FR-GRN-05 Allow receipt into inspection/quarantine hold (not issuable) pending QC, before QC module exists. M
FR-GRN-06 On confirm, each line creates a FIFO cost layer at unit cost (PO price + attributable charges; landed cost per §B.1.2.1) and posts an inbound ledger entry. M
FR-GRN-07 Record received value per line and PO reference for downstream matching. M
FR-GRN-08 Assign received stock to a bin/location (putaway). S

B.3.4 Stock Management (FR-STK)

ID Requirement Pri
FR-STK-01 Maintain an immutable, append-only stock ledger: item, warehouse, bin, batch/serial, qty (base UOM), unit cost, value, running balance, source doc, user, timestamp. M
FR-STK-02 Maintain FIFO cost layers per item per warehouse (received qty, remaining qty, unit cost, receipt date). M
FR-STK-03 On any issue, consume oldest layers first, posting cost at each layer's unit cost. M
FR-STK-04 Compute valuation = Σ(remaining qty × unit cost) over open layers, per item/warehouse and total. M
FR-STK-05 Support transfer between warehouses/bins with in-transit: out consumes source layers; in confirms and creates destination layer. M
FR-STK-06 Transfers are cost-preserving: destination layer inherits consumed source cost. M
FR-STK-07 [Phase 1: auto-post] Adjustments (increase/decrease/write-off) with mandatory reason code post immediately; a config flag (default off) gates threshold approval later. Reason code + user stamp always mandatory. M
FR-STK-08 Support cycle count and full physical count workflows; post variance adjustments on confirmation. M
FR-STK-09 Define negative-stock policy (default: block issues that would drive on-hand negative; configurable per item). M
FR-STK-10 Provide reorder alerts when available ≤ reorder point; optionally suggest a requisition. M
FR-STK-11 Expose a reservation/allocation status on stock (stubbed Phase 1; consumed by Sales later) to distinguish on-hand vs available. S
FR-STK-12 Stock enquiry by item/warehouse/bin/batch/serial showing on-hand, in-transit, on-hold, available. M
FR-STK-13 Every stock transaction generates a GL-ready journal entry as data (no posting in Phase 1). S
FR-STK-14 With landed cost enabled (Option B), apportion additional charges into FIFO layer costs at GRN by configurable basis. C

B.3.5 Warehouse Management (FR-WH)

ID Requirement Pri
FR-WH-01 Support multiple warehouses, each with a bin/location hierarchy. M
FR-WH-02 Track stock to bin level; support bin-to-bin movement within a warehouse. M
FR-WH-03 Support batch/lot tracking with expiry; enforce FEFO picking for perishables (distinct from FIFO costing). M
FR-WH-04 Support serial-number tracking across lifecycle (receipt → issue). M
FR-WH-05 Provide basic putaway and pick steps. S
FR-WH-06 Be barcode/QR-scan ready for item, bin, batch, serial. C
FR-WH-07 Block issue/pick of on-hold/quarantine or expired stock. M

B.3.6 Cross-cutting (FR-X)

ID Requirement Pri
FR-X-01 [Phase 1: external IdP, user identity only] Authentication is delegated to the external AuthHex IdP (ERPCore validates its RS256 tokens; no local login). ERPCore provisions a local shadow user (auth_user_id GUID → local int) and stamps every transaction with the acting user's identity for audit. AuthHex now also supplies RoleCode/UserTypeCode claims, used only for the door gate; full per-endpoint RBAC (role→permission matrix) remains deferred — reserve role/permission structures for no-migration enablement. M
FR-X-02 Maintain an immutable audit trail for every create/update/delete and stock movement (who/when/old→new/reason). M
FR-X-03 Generate document numbers from configurable sequences (per type, per year), unique and gap-controlled. M
FR-X-04 Maintain configurable reason-code lists (adjustments, returns, count variances). M
FR-X-05 No editing/deleting confirmed/posted transactions; corrections via reversing entries. M

B.4 Data Model (summary)

Key entities and relationships are enumerated in Part C. The commitment: Item, UOM/UOMConversion, Category, Vendor, Warehouse/Bin, ItemReorder, Requisition(+Line), RFQ(+Line)/VendorQuotation, PurchaseOrder(+Line), GRN(+Line), PurchaseReturn(+Line), StockLayer (FIFO), StockLedger (immutable), Batch, Serial, StockTransfer(+Line), StockAdjustment(+Line), StockCount(+Line), User, ReasonCode, NumberSequence, AuditLog, JournalEntryStub, and reserved RBAC (Role, Permission, UserRole, RolePermission).

B.5 External Interfaces

UI: responsive; count/pick screens handheld-friendly; status badges; mandatory-field validation. Hardware: barcode/QR (designed-for). Software: relational DB with transactional integrity + row locking; internal service interfaces/events for Phase-2+ modules.

B.6 Non-Functional Requirements

ID Category Requirement
NFR-01 Performance Single item/warehouse enquiry + valuation < 2s under normal load; ledger posting transactional, < 1s per line.
NFR-02 Integrity FIFO layer consumption atomic and concurrency-safe; no double-consumption of remaining qty.
NFR-03 Security Users authenticated via the external AuthHex IdP; password hashing (BCrypt) is AuthHex's responsibility — ERPCore validates tokens only. Every action attributed to a user and logged. (Role-based enforcement deferred, FR-X-01.)
NFR-04 Auditability Audit trail immutable, retained per policy; ledger append-only.
NFR-05 Reliability No stock transaction partially commits; full rollback on failure.
NFR-06 Scalability Growth in items/warehouses/ledger without redesign; ledger indexed for time-series queries.
NFR-07 Usability Receive/count/transfer achievable with minimal training; scan-first where hardware present.
NFR-08 Maintainability Modular service boundaries; Phase-2+ integrates without altering Phase-1 schema.
NFR-09 Availability Not a Phase-1 engineering commitment. Uptime SLA to be set for production; HA infra (failover, redundancy) deferred to a later hardening phase.
NFR-10 Configurability Numbering, tolerances, reason codes, negative-stock and reorder policies configurable without code change.

B.7 Future Modules & Integration Seams

Module Seam in Phase 1
Accounting GL-ready journal entries per movement (FR-STK-13); GRN retains PO ref + received value for 3-way match (FR-PROC-09, FR-GRN-07).
Sales & CRM Reservation/allocation status distinguishing on-hand vs available (FR-STK-11).
Manufacturing Generic goods-issue/consumption movement type BOM will consume through (extends FR-STK-03).
QC / QA GRN inspection/quarantine hold (FR-GRN-05); hold blocks issue (FR-WH-07).
HRM User identity foundation (FR-X-01) reusable for employee-linked permissions.
RBAC & Approvals Config flags + retained PendingApproval/role structures enable PO & adjustment approvals with no schema change.

B.8 Appendices

B.8.1 Status lifecycles PO: Draft → (PendingApproval →) Approved → PartiallyReceived → FullyReceived → Closed/Cancelled · GRN: Draft → Confirmed → Closed · Transfer: Draft → InTransit → Received → Closed · Adjustment/Count: Draft → (PendingApproval →) Posted.

B.8.2 Document numbering (examples) PR-YYYY-#####, PO-YYYY-#####, GRN-YYYY-#####, TRF-YYYY-#####, ADJ-YYYY-#####, CNT-YYYY-#####, PRET-YYYY-##### (FR-X-03).

B.8.3 Reason codes (seed) Adjustment: Damage, Theft/Loss, Count Variance, Expiry Write-off, System Correction · Return: Defective, Wrong Item, Over-supply, Quality Reject.

B.8.4 Open decisions log

# Decision Status
1 Landed-cost scope (§B.1.2.1, A vs B) Open
2 Negative-stock: global block vs per-item (FR-STK-09) Proposed: block
3 GRN-without-PO permission scope (FR-GRN-02) Open
4 Availability/uptime SLA target (NFR-09) Deferred to production
5 PO & adjustment approvals Resolved: deferred — auto-approve, config-gated off
6 RBAC Resolved: deferred — user identity only
7 PO amendments Resolved: Option B, edit-while-open
8 Costing method Resolved: FIFO
9 Tenancy Resolved: single-tenant
10 Authentication Resolved: external AuthHex IdP (RS256; ERPCore validates only), shadow-user provisioning (auth_user_id GUID → local int), door-gated by ERP UserType/Role; per-endpoint RBAC deferred. Open sub-item: exact ERP UserTypeCode/RoleCode + RSA-key rotation process.

Part C — ER Model (38 entities)

Costing: FIFO · Multi-warehouse · Single-tenant. Legend: PK primary key · FK foreign key. Companion visual diagrams (Mermaid / draw.io ERD) accompany this repo; this part is the authoritative textual model.

C.1 Master Data

CATEGORY(category_id PK, parent_id FK→CATEGORY, name)
UOM(uom_id PK, name)
UOM_CONVERSION(conversion_id PK, item_id FK→ITEM, from_uom FK→UOM, to_uom FK→UOM, factor)
ITEM(item_id PK, sku, name, category_id FK→CATEGORY, base_uom_id FK→UOM,
     default_vendor_id FK→VENDOR, item_type, tracking_mode, tax_class, status)
ITEM_REORDER(reorder_id PK, item_id FK→ITEM, warehouse_id FK→WAREHOUSE, reorder_point, reorder_qty)
VENDOR(vendor_id PK, code, name, terms, tax_reg, currency, status)
WAREHOUSE(warehouse_id PK, code, name)
BIN(bin_id PK, warehouse_id FK→WAREHOUSE, code, bin_type)

C.2 Procurement

REQUISITION(requisition_id PK, doc_no, requested_by FK→USER, status, created_at)
REQUISITION_LINE(req_line_id PK, requisition_id FK→REQUISITION, item_id FK→ITEM, qty, required_by)
RFQ(rfq_id PK, doc_no, requisition_id FK→REQUISITION, status)
RFQ_LINE(rfq_line_id PK, rfq_id FK→RFQ, item_id FK→ITEM, qty)
VENDOR_QUOTATION(quotation_id PK, rfq_id FK→RFQ, vendor_id FK→VENDOR, unit_price, lead_days)
PURCHASE_ORDER(po_id PK, doc_no, vendor_id FK→VENDOR, requisition_id FK→REQUISITION,
     status, approval_required, created_by FK→USER, created_at)
PO_LINE(po_line_id PK, po_id FK→PURCHASE_ORDER, item_id FK→ITEM, uom_id FK→UOM,
     warehouse_id FK→WAREHOUSE, qty, unit_price, tax, qty_received)
PURCHASE_RETURN(return_id PK, doc_no, vendor_id FK→VENDOR, warehouse_id FK→WAREHOUSE,
     reason_code_id FK→REASON_CODE, created_by FK→USER)
PURCHASE_RETURN_LINE(return_line_id PK, return_id FK→PURCHASE_RETURN,
     grn_line_id FK→GRN_LINE, item_id FK→ITEM, qty)

C.3 Goods Receipt

GRN(grn_id PK, doc_no, po_id FK→PURCHASE_ORDER, vendor_id FK→VENDOR,
     warehouse_id FK→WAREHOUSE, status, created_by FK→USER, created_at)
GRN_LINE(grn_line_id PK, grn_id FK→GRN, po_line_id FK→PO_LINE, item_id FK→ITEM, uom_id FK→UOM,
     bin_id FK→BIN, batch_id FK→BATCH, qty, unit_cost, received_value, hold_status)

C.4 Batch / Serial

BATCH(batch_id PK, item_id FK→ITEM, batch_no, expiry_date)
SERIAL(serial_id PK, item_id FK→ITEM, serial_no, status)

C.5 Stock Core (FIFO + Ledger)

STOCK_LAYER(layer_id PK, item_id FK→ITEM, warehouse_id FK→WAREHOUSE, batch_id FK→BATCH,
     serial_id FK→SERIAL, grn_line_id FK→GRN_LINE,
     qty_received, qty_remaining, unit_cost, receipt_date)          -- FIFO layers
STOCK_LEDGER(ledger_id PK, item_id FK→ITEM, warehouse_id FK→WAREHOUSE, bin_id FK→BIN,
     batch_id FK→BATCH, serial_id FK→SERIAL, user_id FK→USER,
     direction, qty_base, unit_cost, value, running_balance,
     source_doc_type, source_doc_id, created_at)                    -- immutable journal

C.6 Stock Transactions

STOCK_TRANSFER(transfer_id PK, doc_no, src_warehouse_id FK→WAREHOUSE,
     dest_warehouse_id FK→WAREHOUSE, status, created_by FK→USER)
STOCK_TRANSFER_LINE(transfer_line_id PK, transfer_id FK→STOCK_TRANSFER, item_id FK→ITEM,
     src_bin_id FK→BIN, dest_bin_id FK→BIN, batch_id FK→BATCH, serial_id FK→SERIAL, qty)
STOCK_ADJUSTMENT(adjustment_id PK, doc_no, warehouse_id FK→WAREHOUSE,
     reason_code_id FK→REASON_CODE, created_by FK→USER, created_at)
STOCK_ADJUSTMENT_LINE(adj_line_id PK, adjustment_id FK→STOCK_ADJUSTMENT, item_id FK→ITEM,
     bin_id FK→BIN, batch_id FK→BATCH, serial_id FK→SERIAL, qty_delta)
STOCK_COUNT(count_id PK, doc_no, warehouse_id FK→WAREHOUSE, count_type, status, created_by FK→USER)
STOCK_COUNT_LINE(count_line_id PK, count_id FK→STOCK_COUNT, item_id FK→ITEM, bin_id FK→BIN,
     system_qty, counted_qty, variance)

C.7 Cross-cutting

USER(user_id PK, username, display_name, status, auth_user_id [GUID, unique] → AuthHex identity)  -- local shadow/projection of AuthHex users; user_id (int) is what all FKs reference
REASON_CODE(reason_code_id PK, code, description, context)
NUMBER_SEQUENCE(sequence_id PK, doc_type, year, last_number)
AUDIT_LOG(audit_id PK, user_id FK→USER, entity_type, entity_id, action, change_set, created_at)
JOURNAL_ENTRY_STUB(journal_id PK, source_doc_type, source_doc_id, debit_account, credit_account, amount)

C.8 Reserved (RBAC — deferred, schema placeholder only)

ROLE(role_id PK, name)
PERMISSION(permission_id PK, code)
USER_ROLE(user_id FK→USER, role_id FK→ROLE)
ROLE_PERMISSION(role_id FK→ROLE, permission_id FK→PERMISSION)

C.9 Modeling notes (load-bearing)

  • FIFO = two structures. STOCK_LAYER answers valuation ("what's on hand and at what cost"); STOCK_LEDGER answers history ("what moved, when, by whom"). Layers are keyed per item per warehouse.
  • Polymorphic source. STOCK_LEDGER.source_doc_type/source_doc_id (and AUDIT_LOG, JOURNAL_ENTRY_STUB) reference the originating document without a hard FK per type — new transaction types (Sales, Manufacturing) write to the ledger without a schema change.
  • In-transit + cost-preserving transfer. STOCK_TRANSFER holds src/dest warehouse; dispatch consumes source layers into in-transit, receive creates the destination layer at the inherited source cost.
  • FEFO ≠ FIFO. FIFO governs costing; FEFO governs physical picking of perishables via BATCH.expiry_date.
  • External IdP + shadow user. Authentication is delegated to AuthHex (RS256, issuer AuthHex/audience AuthHexClient, static public key). USER is a local shadow of AuthHex identities: auth_user_id (GUID from the token's UserId claim) is JIT-mapped to the local int user_id that every created_by/requested_by/AUDIT_LOG.user_id/STOCK_LEDGER.user_id FK references — no FK type change. A door policy admits only ERP UserType/Role holders.
  • Reserved RBAC. Role/Permission/UserRole/RolePermission exist for schema-completeness only; only USER is live (audit stamp). AuthHex's RoleCode/UserTypeCode claims drive the door gate today; per-endpoint RBAC is future work.
  • Reorder alerts are a query, not an entity — computed from ITEM_REORDER vs available. Add a table only if alert history is required.

C.10 Entity → implementation mapping

  • Entities → Domain/Entities; enums (ItemType, TrackingMode, HoldStatus, Direction, *Status, CountType) → Domain/Enums.
  • EF configurations (IEntityTypeConfiguration<T>, one per entity) → Infra/Persistence/Configurations.
  • FIFO logic → Services/Stock/FifoCostingService (Part A.2). Ledger writes only via stock services inside the UoW transaction.
  • RowVersion (concurrency) on mutable aggregates: Item, Vendor, PurchaseOrder, GRN, transfers/adjustments/counts headers.

End of 10-BACKEND-PHASE1.md. API contracts: 11-BACKEND-PHASE1.md. Record work: Backend/PROGRESS.md.