# 04 - Loading Event **Status**: Planning **Depends On**: 03 - Ride Lifecycle --- ## Goal Move stock from a source warehouse onto an opened ride's vehicle, with two-party confirmation. When loading is confirmed, the vehicle's stock ledger is credited and the ride transitions `Open -> Active` automatically. This loading flow serves both ride types: - `Delivery` rides load stock to fulfill orders already linked to the ride. - `Sale` rides load stock so the sales agent can sell in the field and create sale-linked orders. --- ## Background / Context A vehicle is just a location with `LocationType.Vehicle` in the stock ledger - the same math as a warehouse. When loading is confirmed, two stock movements are created per item: - Debit source warehouse: `-qty` at `LocationType.Warehouse` - Credit vehicle: `+qty` at `LocationType.Vehicle` From this point the vehicle carries live inventory that decreases with every sale or delivery and must return to zero before the ride can close. --- ## Data Model Changes ### New Entity - LoadingSheet | Column | Type | Notes | |---|---|---| | Id | Guid | PK | | FK_RideId | Guid | FK -> DeliveryRun | | FK_SourceWarehouseId | Guid | FK -> Warehouse | | Status | LoadingSheetStatus | State machine below | | DispatchConfirmedAt | DateTime? | Set when warehouse confirms dispatch | | DispatchConfirmedBy | Guid? | UserId of warehouse manager | | ReceiptConfirmedAt | DateTime? | Set when driver confirms receipt | | ReceiptConfirmedBy | Guid? | UserId of driver | | CreatedAt | DateTime | | ### New Entity - LoadingSheetItem | Column | Type | Notes | |---|---|---| | Id | Guid | PK | | FK_LoadingSheetId | Guid | FK -> LoadingSheet | | FK_ItemId | Guid | FK -> Item | | QuantityDispatched | decimal | Set by warehouse manager at confirm-dispatch | | QuantityReceived | decimal? | Set by driver before confirm-receipt | | QuantityResolved | decimal? | Set by supervisor when resolving a discrepancy | ### New Enum - LoadingSheetStatus ```csharp public enum LoadingSheetStatus { Pending, DispatchConfirmed, ReceiptConfirmed, DiscrepancyFlagged, Resolved } ``` No changes to existing entities. Vehicle capacity check is deferred to Step 13. --- ## State Machine ```text Pending -> DispatchConfirmed -> ReceiptConfirmed \-> DiscrepancyFlagged -> Resolved ``` | Transition | Trigger | What it does | |---|---|---| | Pending -> DispatchConfirmed | `POST /{id}/confirm-dispatch` | Warehouse manager locks in `QuantityDispatched` per line. Ride stays `Open`. | | DispatchConfirmed -> ReceiptConfirmed | `POST /{id}/confirm-receipt` | Driver's received quantities all match dispatched. Creates stock movements. Auto-triggers ride `Open -> Active`. | | DispatchConfirmed -> DiscrepancyFlagged | `POST /{id}/confirm-receipt` | Any line mismatch. No stock movements created. Ride stays `Open`. | | DiscrepancyFlagged -> Resolved | `POST /{id}/resolve` | Supervisor enters `QuantityResolved` per line. Creates stock movements using resolved quantities. Auto-triggers ride `Open -> Active`. | --- ## Item Population by Ride Type | Ride Type | How items are added to the sheet | |---|---| | Delivery | Auto-populated on sheet creation from the aggregate of all order lines assigned to the ride's route | | Sale | Manually entered by warehouse manager via `PUT /{id}/items` based on expected field selling demand | For Delivery rides the warehouse manager reviews the pre-populated quantities and may adjust before confirming dispatch. `PUT /{id}/items` is still available to override if needed. --- ## Stock Movements Created at `ReceiptConfirmed` or `Resolved`. One pair per line item: - Debit warehouse: `-QuantityFinal` at `LocationType.Warehouse`, `FK_LocationId = SourceWarehouseId` - Credit vehicle: `+QuantityFinal` at `LocationType.Vehicle`, `FK_LocationId = VehicleId` `QuantityFinal` = `QuantityReceived` on a clean confirmation, or `QuantityResolved` when a discrepancy was resolved. Movement `Type` = `Load`. --- ## API Endpoints Base route: `/api/loading-sheets` | Method | Route | Who | Description | |---|---|---|---| | POST | `/` | Warehouse mgr | Create sheet for an Open ride. Delivery rides auto-populate items from orders. | | GET | `/` | Any | List sheets. Filter by `rideId`, `status`. Paginated. | | GET | `/{id}` | Any | Get sheet detail with all line items | | PUT | `/{id}/items` | Warehouse mgr | Set or override item quantities | | POST | `/{id}/confirm-dispatch` | Warehouse mgr | Pending -> DispatchConfirmed | | PUT | `/{id}/receipt-items` | Driver | Set `QuantityReceived` per line | | POST | `/{id}/confirm-receipt` | Driver | -> ReceiptConfirmed or DiscrepancyFlagged | | POST | `/{id}/resolve` | Supervisor | Enter `QuantityResolved` per line -> Resolved plus ride activation | --- ## Business Rules 1. A loading sheet can only be created for a ride in `Open` status. 2. A ride can only have one active loading sheet at a time. 3. `QuantityDispatched != QuantityReceived` on any line -> `DiscrepancyFlagged`. No partial activation. 4. Stock movements are created only once - at `ReceiptConfirmed` or `Resolved`. Never on dispatch. 5. Discrepancies must be resolved per line item with an explicit `QuantityResolved` value. 6. All list endpoints return paginated responses. 7. Vehicle capacity check is not enforced in this step. 8. Immediate handover from a Sale ride does not remove the need for an `Order`; every sale still creates an order, and that order is fulfilled immediately when stock is handed over at sale time. --- ## Permissions to Seed | Code | Who | |---|---| | `loading.view` | All roles | | `loading.dispatch` | Warehouse manager and above | | `loading.receive` | Driver | | `loading.resolve` | Supervisor and above | --- ## Fraud / Control Considerations - Discrepancies are immediately visible and block activation. - `QuantityResolved` is permanently stored per line for audit purposes. --- ## Open Questions 1. Vehicle capacity check - deferred to Step 13. 2. Discrepancy resolution - supervisor enters `QuantityResolved` per line item. 3. Ride activation trigger - `confirm-receipt` and `resolve` auto-trigger `Open -> Active`. 4. Delivery item population - from assigned orders on the ride's route. --- ## Implementation Notes Build after the ride model and before full delivery/sale execution.