# 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-Match` → `412` 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`.*
