# InboundShipment

## Description

InboundShipment is the inbound-shipment-owned receiving document for goods-receipt intent and posting state. It represents the inbound shipment (goods receipt) document: a stock-moving receiving document that records what physically arrived and, once posted, drives the resulting stock effects through inventory. InboundShipmentLine records the line-level received quantities owned by that header.

One inbound shipment is represented as one InboundShipment record; each received line is represented by an InboundShipmentLine. Because a single shipment can cover multiple purchase orders, source-document context is held at the line level rather than on the header: each InboundShipmentLine can independently reference its own source document (for example a purchase order line) via `sourceDocumentType`, `sourceDocumentId`, and `sourceLineId`.

InboundShipment has a deliberately small posting lifecycle:

- `DRAFT`: created but not posted to stock, ledger, balances, or valuation
- `CANCELLED`: draft shipment was discarded before posting; no stock facts were created
- `POSTED`: posted to stock and represented by inventory InventoryLedger entries

Inventory InventoryLedger references the posting via generic `sourceType`, `sourceId`, and `sourceLineId` fields, which are populated from the InboundShipment and InboundShipmentLine identifiers when posting runs. Ledger rows do not duplicate the receiving document fields; the receiving document context is owned by InboundShipment.

`effectiveDate` is the user/business date used for inventory reporting and period controls. `postedAt` is the system timestamp when the shipment transitions to POSTED.

InboundShipmentLine is the unit-conversion boundary. It keeps both the business/input quantity and the normalized inventory quantity:

- `quantity` + `unitId`: the quantity and unit provided by the source document or user
- `primaryQuantity` + `primaryUnitId`: the quantity converted to the item's primary unit at shipment creation time
- `unitConversionRate`: the multiplier used to convert `quantity` into `primaryQuantity`

Inventory InventoryLedger stores only primary-unit quantities derived from `primaryQuantity`.

Inbound shipment commands create one InboundShipment header and one or more InboundShipmentLine rows. Posting derives inbound (`direction=IN`) stock movements from those lines and hands them to inventory in a single call.

For inbound shipment lines, `unitCost` is not an input: it stays null while the shipment is DRAFT and is set at posting time to the primary-unit cost derived from the source purchase-order line price, serving as the goods-receipt valuation audit trail.

Module boundary: inbound shipments are stock-moving receiving documents, not ledger records. Inventory owns the ledger, balances, and valuation; this module owns the receiving document lifecycle. When an InboundShipment is posted, this module posts the resulting stock effects through inventory's `postInventoryLedger` API within the same transaction, so the receiving document and its stock ledger rows are committed together. This module never writes InventoryLedger, StockLevel, or valuation records directly.

## Domain Model Definitions

### Model type

Stateful

#### State Transitions

```mermaid
stateDiagram-v2
    [*] --> Draft: createInboundShipment
    Draft --> Draft: updateInboundShipment
    Draft --> Cancelled: cancelInboundShipment
    Draft --> Posted: postInboundShipment
```

Creation is represented as the initial state transition into `DRAFT`. The transition table below lists operations on existing InboundShipment records that are generated into `inboundShipmentLifecycle`.

| Operation | From | To | Command |
|-----------|------|----|---------|
| update | DRAFT | DRAFT | [updateInboundShipment](../command/UpdateInboundShipment.md) |
| cancel | DRAFT | CANCELLED | [cancelInboundShipment](../command/CancelInboundShipment.md) |
| post | DRAFT | POSTED | [postInboundShipment](../command/PostInboundShipment.md) |

### Command Definitions

- [createInboundShipment](../command/CreateInboundShipment.md) - Creates a DRAFT inbound shipment
- [updateInboundShipment](../command/UpdateInboundShipment.md) - Updates a DRAFT inbound shipment
- [cancelInboundShipment](../command/CancelInboundShipment.md) - Cancels a DRAFT inbound shipment without deleting it
- [postInboundShipment](../command/PostInboundShipment.md) - Posts a DRAFT inbound shipment

### Query Definitions

- [getInboundShipment](../query/GetInboundShipment.md) - Retrieves one InboundShipment with its shipment lines
- [listInboundShipments](../query/ListInboundShipments.md) - Lists InboundShipment records with their shipment lines

### Models

- InboundShipment
- InboundShipmentLine

### Invariants

- status must be DRAFT, CANCELLED, or POSTED
- DRAFT shipments must not affect InventoryLedger, StockLevel, or valuation
- CANCELLED shipments must not affect InventoryLedger, StockLevel, or valuation
- POSTED shipments are immutable receiving facts; posting is one-shot and corrections are modeled as separate documents when introduced
- effectiveDate is required and represents the business effective date
- postedAt is set when status becomes POSTED
- Posting writes stock effects only through inventory's posting API; this module never mutates ledger, balances, or valuation directly
- Every InboundShipmentLine references its InboundShipment header
- InboundShipmentLine itemId, quantity, unitId, primaryQuantity, primaryUnitId, unitConversionRate, storageLocationId, and stockType are required
- InboundShipmentLine quantity, primaryQuantity, and unitConversionRate must be positive
- InboundShipmentLine unitCost is null while DRAFT and is set at posting time from the source purchase-order line price converted to the primary unit
- InboundShipmentLine primaryUnitId is a snapshot of Item.unitId when the shipment line is created
- InboundShipmentLine primaryQuantity equals quantity multiplied by unitConversionRate after inventory rounding rules
- InboundShipmentLine sourceDocumentType is a required enum of `PURCHASE_ORDER`
- InboundShipmentLine sourceDocumentId and sourceLineId are required generic source references and are not cross-module foreign keys
- Purchase receipt lines use `sourceDocumentType=PURCHASE_ORDER`, `sourceDocumentId=PurchaseOrder.id`, and `sourceLineId=PurchaseOrderLine.id`

### Relationships

- **Has InboundShipmentLine**: Each shipment owns one or more line-level received quantities
- **InboundShipmentLine references Item**: Each line receives one Item from item-management
- **InboundShipmentLine references StorageLocation**: Each line receives into one inventory location
- **Referenced by inventory InventoryLedger**: Ledger rows point back to the shipment and line that produced the stock posting through generic `sourceType`, `sourceId`, and `sourceLineId`
- **Feeds purchase receipt projection**: Posting a shipment pushes its purchase-order-linked received quantities as per-line deltas to purchase's `recalculatePurchaseOrderReceiptStatus`, which owns the `PurchaseOrderLine.receivedQuantity` projection
- **Referenced by purchase ThreeWayMatchEvent**: Supplier bill matching records which InboundShipmentLine was matched to which bill line as goods-receipt evidence
