# 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 dcIndicator (DR or CR) determines the double-entry direction of the stock effect. Each entry carries `inventoryExecutionId`, which points to the InventoryExecution record that owns the stock posting lifecycle, execution type, and source document context.

Each entry also carries `stockType`, which identifies the current stock type affected by the row (`AVAILABLE`, `BLOCKED`, or `IN_TRANSIT`). This is intentionally separate from InventoryExecution `executionType`: the execution explains why the entry exists, while `stockType` identifies which StockLevel balance slice the row increases or decreases. For example, one transfer shipment execution can create both an `AVAILABLE` credit at the source location and an `IN_TRANSIT` debit at the transit location.

InventoryLedger quantities are always stored in the item's primary unit. When an InventoryExecutionLine exists, the ledger quantity is derived from `InventoryExecutionLine.primaryQuantity`; the input/business unit remains on the execution line for auditability.

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. 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 and lot/serial tracking.

## Domain Model Definitions

### Model type

AppendOnly

### Command Definitions

- InventoryLedger entries are created internally by the following commands:
  - [postGoodsReceipt](../command/PostGoodsReceipt.md) - Creates a GOODS_RECEIPT entry (DR) when stock is received at a location
  - [postGoodsIssue](../command/PostGoodsIssue.md) - Creates a GOODS_ISSUE entry (CR) when stock is issued from a location
  - [shipTransferOrder](../command/ShipTransferOrder.md) - Creates source AVAILABLE credit and transit IN_TRANSIT debit entries
  - [receiveTransferOrder](../command/ReceiveTransferOrder.md) - Creates transit IN_TRANSIT credit and destination AVAILABLE debit entries
  - [confirmStockAdjustment](../command/ConfirmStockAdjustment.md) - Creates CORRECTION, SCRAP, BLOCK, or UNBLOCK entries 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 inventoryExecutionId, itemId, storageLocationId, dcIndicator, date range, or joined InventoryExecution source fields (to be added)

### Models

- InventoryLedger
- InventoryExecution

### Invariants

- InventoryLedger is immutable — once created, it cannot be updated or deleted
- inventoryExecutionId is required and references the InventoryExecution that produced the posting
- dcIndicator must be one of: DR (debit, stock increase), CR (credit, stock decrease)
- 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 dcIndicator determines the direction of the stock effect
- storageLocationId is required — the location where the stock change occurs
- For GOODS_RECEIPT: dcIndicator is DR, entry records stock arriving at the destination location
- For GOODS_ISSUE: dcIndicator is CR, entry records stock leaving the source location
- For BLOCK: dcIndicator is DR on blocked type, entry records stock being quarantined
- For UNBLOCK: dcIndicator is CR on blocked type, entry records stock being released
- For CORRECTION: dcIndicator is DR for INCREASE, CR for DECREASE
- For SCRAP: dcIndicator is CR, entry records stock being written off
- For TRANSFER_SHIP: one entry credits source AVAILABLE and another debits transit IN_TRANSIT
- For TRANSFER_RECEIVE: one entry credits transit IN_TRANSIT and another debits destination AVAILABLE
- For TRANSFER_DIRECT: one entry credits source AVAILABLE and another debits destination AVAILABLE in the same event
- executedAt is required (non-nullable)
- 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
- **References Lot (optional)**: For lot-tracked items, the specific lot affected
- **References SerialNumber (optional)**: For serial-tracked items, the specific serial number affected
- **Referenced by CostLayer**: Cost layers reference the InventoryLedger entry that created them via inventoryLedgerId
- **Feeds StockLevel**: StockLevel is aggregated by item, storage location, and stock type
