# inbound-shipment-processing

## Overview

Handles the receipt of goods from suppliers against confirmed purchase orders. In erp-kit 0.45 an inbound shipment is backed by an `InboundShipment` header + `InboundShipmentLine` rows (`inbound-shipment` module); every receipt line must reference a purchase-order line — **manual / standalone (non-PO) receipts are no longer supported**. When a PO is approved, an `InventorySupplyPlan` row is created per line representing the expected receipt quantity — **no receipt document is auto-created**. The Purchaser explicitly creates an inbound shipment linked to the PO (drawing line items from the supply plan), reviews / adjusts quantities for partial receipts, then settles it. Settling an inbound shipment updates on-hand inventory, costs the receipt at the item's effective standard cost — creating and posting a journal entry in financial-accounting within the same transaction — and reduces incoming inventory (the underlying supply plan is consumed). Multiple inbound shipments can be created against a single PO to handle split deliveries.

**Resilience.** The settle path is idempotent at the lifecycle level: re-posting an already-POSTED inbound shipment is refused with `INBOUND_SHIPMENT_INVALID_STATUS` (the lifecycle transition `DRAFT → POSTED` only fires from DRAFT). Create/settle also reject empty line sets (`INBOUND_SHIPMENT_EMPTY_SHIPMENT_LINES`) and non-positive line quantities (`INBOUND_SHIPMENT_INVALID_QUANTITY`).

> **Known module gaps (not in diagram).** The underlying erp-kit module does **not** currently enforce: (a) **over-receipt vs PO line** — `createInboundShipment` / `postInboundShipment` never compare received qty against the source PO line's ordered qty; supply-plan consumption silently caps at the open quantity rather than rejecting overflow. (b) **Settling against a CANCELLED / CLOSED PO** — `postInboundShipment` does not inspect the source PO's `orderStatus`; when the PO is cancelled, its supply plans are already closed, but the post path treats a missing OPEN supply plan as a no-op (`return ok({ supplyPlans: [] })`) so the inbound shipment still posts and creates its costing journal entry. (c) **Item not in PO line set for a PO-linked inbound shipment** — neither command validates that each line's `itemId` matches the linked PO line's item. If downstream policy requires hard rejects, they must be added upstream in erp-kit or as a thin app-level guard in the `createInboundShipment` / `settleInboundShipment` resolvers.

## Actors Involved

- [Purchaser](../../actor/purchaser.md) — reviews, edits, and settles inbound shipments

See [action-matrix.md](./action-matrix.md) for models involved and the action visibility matrix.

## Flow Diagram

```mermaid
sequenceDiagram
    participant P as Purchaser
    participant S as System

    Note over S: PO approval (purchase-order-lifecycle) creates an InventorySupplyPlan row per line — not a draft inbound shipment document
    P->>S: Create Inbound Shipment against confirmed PO (lines drawn from PO line items)
    S-->>P: Inbound Shipment in DRAFT status

    P->>S: Review Inbound Shipment (adjust quantities for partial receipt if needed)
    S-->>P: Inbound Shipment updated

    P->>S: Settle (post) Inbound Shipment
    S-->>P: Inbound Shipment status → POSTED
    S->>S: Create inventory ledger entry (on-hand +received qty)
    S->>S: Create and post costing journal entry (inventory at standard vs accrual, variance to PPV)
    S->>S: Update incoming inventory (ordered − received)
    S->>S: Update PO deliveryStatus (NOT_RECEIVED / PARTIALLY_RECEIVED / RECEIVED)

    alt Partial receipt — more goods expected
        P->>S: Create additional Inbound Shipment for remaining quantity
        P->>S: Settle additional Inbound Shipment
        S->>S: Additional costing journal entry posted, incoming inventory updated
        S->>S: PO deliveryStatus updated
    end

    Note over S: Every receipt line must reference a PO line — manual / standalone (non-PO) receipts are no longer supported
    Note over S: Rejects with INBOUND_SHIPMENT_INVALID_STATUS when settling an inbound shipment already in POSTED status (DRAFT→POSTED lifecycle gate)
    Note over S: Rejects with INBOUND_SHIPMENT_EMPTY_SHIPMENT_LINES on create/settle with no lines; INBOUND_SHIPMENT_INVALID_QUANTITY on non-positive line quantities
```

## Stories

- [Review Inbound Shipment](./story/purchaser--review-inbound-shipment.md)
- [Settle Inbound Shipment](./story/purchaser--settle-inbound-shipment.md)
- [Create Additional Inbound Shipment](./story/purchaser--create-additional-inbound-shipment.md)
- [Create Remaining Inbound Shipment](./story/purchaser--create-remaining-inbound-shipment.md)
- [View Inbound Shipments](./story/purchaser--view-inbound-shipments.md)
- [Create Standalone Inbound Shipment](./story/purchaser--create-standalone-inbound-shipment.md)
