# InventoryLedger

## Description

InventoryLedger is an immutable, append-only record that captures every physical or logical stock-type change in the inventory system. Each record represents a single stock change for one item at one location and one stock type as a flat entity.

Quantity is always positive (> 0); the direction (IN or OUT) determines whether the row increases or decreases stock. Each entry also carries an `action` — what the movement changes: `QUANTITY_CHANGE` (net on-hand), `TRANSFER` (location), or `STOCK_TYPE_CHANGE` (stock type). The action is direction-neutral — the direction field carries the sign — while `sourceType` says why. Together (sourceType, action, direction) determine the row's costing treatment. Each entry carries generic source references — `sourceType`, `sourceId`, and optional `sourceLineId` — identifying the one-shot cause document that produced the posting. These are generic references, not typed foreign keys: the cause document lives in its owning module (inbound-shipment, outbound-shipment) or inventory aggregate (transfer order, stock adjustment), and the ledger points at it without a cross-module foreign key. Ledger rows produced by one posting event share the same `(sourceType, sourceId)` and are recognized as a bundle by grouping on those columns.

Each entry also carries `stockType`, which identifies the current stock type affected by the row (`AVAILABLE`, `BLOCKED`, or `IN_TRANSIT`). This identifies which StockLevel balance slice the row increases or decreases. For example, one transfer shipment posting can create both an `AVAILABLE` OUT row at the source location and an `IN_TRANSIT` IN row at the transit location under the same `(sourceType=TRANSFER_ORDER, sourceId)`.

InventoryLedger quantities are always stored in the item's primary unit. Unit conversion is performed by the cause document (which retains the input/business unit for auditability); the ledger stores only the primary-unit quantity.

InventoryLedger has no lifecycle status and no state machine — once created, it is permanent and cannot be updated or deleted. The `executedAt` timestamp records when the physical stock change occurred and is always non-nullable. `effectiveDate` is the business date used for inventory reporting and period controls. Only createdAt is tracked (no updatedAt) to enforce immutability.

When an InventoryLedger entry is created, it triggers stock level updates at the relevant location via the stock-tracking feature, and feeds downstream processes such as inventory valuation.

## Domain Model Definitions

### Model type

AppendOnly

### Command Definitions

- InventoryLedger entries are created internally by [postInventoryLedger](../command/PostInventoryLedger.md), the single inventory posting API.
- The following flows post through that API:
  - Inbound shipment posting (`inbound-shipment` module) - IN rows for goods receipt
  - Outbound shipment posting (`outbound-shipment` module) - OUT rows for goods issue
  - [shipTransferOrder](../command/ShipTransferOrder.md) - source AVAILABLE OUT and transit IN_TRANSIT IN
  - [receiveTransferOrder](../command/ReceiveTransferOrder.md) - transit IN_TRANSIT OUT and destination AVAILABLE IN
  - [confirmStockAdjustment](../command/ConfirmStockAdjustment.md) - CORRECTION, SCRAP, BLOCK, or UNBLOCK rows based on the adjustment type

### Query Definitions

- getInventoryLedger - Retrieve a ledger entry by id (to be added)
- listInventoryLedger - List ledger entries with optional filters by sourceType, sourceId, itemId, storageLocationId, direction, or date range (to be added)

### Models

- InventoryLedger

### Invariants

- InventoryLedger is immutable — once created, it cannot be updated or deleted
- sourceType is required and must be one of: INBOUND_SHIPMENT, OUTBOUND_SHIPMENT, TRANSFER_ORDER, STOCK_ADJUSTMENT
- sourceId is required and references the cause document; it is a generic reference, not a typed foreign key
- sourceLineId is optional and references the cause document line; it is a generic reference, not a typed foreign key
- The order reference (document type, document id, document line id) is optional, persisted on both inbound and outbound entries that fulfill a demand/supply document, and is a generic reference to that document line (e.g., a purchase order line); acquisition cost adjustments resolve receipt layers through it
- direction must be one of: IN (stock increase), OUT (stock decrease)
- action is required and must be one of: QUANTITY_CHANGE, TRANSFER, STOCK_TYPE_CHANGE — what the movement changes; together with sourceType and direction it determines the costing treatment
- stockType must be one of: AVAILABLE, BLOCKED, IN_TRANSIT
- itemId is required — each entry pertains to a specific item
- quantity is always positive (> 0), stored in the item's primary unit, and direction determines the stock effect
- storageLocationId is required — the location where the stock change occurs
- For inbound shipment postings: direction is IN, entry records stock arriving at the destination location
- For outbound shipment postings: direction is OUT, entry records stock leaving the source location
- For BLOCK: direction is IN on blocked type, entry records stock being quarantined
- For UNBLOCK: direction is OUT on blocked type, entry records stock being released
- For CORRECTION: direction is IN for INCREASE, OUT for DECREASE
- For SCRAP: direction is OUT, entry records stock being written off
- For transfer shipment: one OUT entry on source AVAILABLE and one IN entry on transit IN_TRANSIT
- For transfer receipt: one OUT entry on transit IN_TRANSIT and one IN entry on destination AVAILABLE
- executedAt is required (non-nullable)
- effectiveDate is required and represents the business effective date
- Only createdAt is tracked (no updatedAt)

### Relationships

- **References Item**: Each entry pertains to an Item from the item-management module
- **References StorageLocation**: The location where the stock change occurs
- **Feeds StockLevel**: StockLevel is aggregated by item, storage location, and stock type
- **References cause document (generic)**: sourceType/sourceId/sourceLineId reach the inbound/outbound shipment, transfer order, or stock adjustment that produced the posting, without a typed foreign key
- **References ordering document (generic)**: the persisted order reference reaches the ordering document line (e.g., purchase order line) that the receipt fulfilled, without a typed foreign key
