Files
ERP_Distribution/Distribution/docs/04_loading_event.md
Kalana B. Thilakarathna ac1b2fbc4b loading event
2026-08-24 11:23:32 +05:30

8.3 KiB

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 is the first place the stock movement model gets exercised end-to-end.


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 (Steps 05/06) and must return to zero before the ride can close (Step 07).


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

public enum LoadingSheetStatus
{
    Pending,
    DispatchConfirmed,
    ReceiptConfirmed,
    DiscrepancyFlagged,
    Resolved
}

No changes to existing entities. Vehicle capacity check is deferred to Step 13 (Fleet Management).


State Machine

Pending → DispatchConfirmed → ReceiptConfirmed (all lines match)
                            ↘ 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
Sale Manually entered by warehouse manager via PUT /{id}/items (no orders to derive from)

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 (new StockMovementType value to add to enums).


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 (required for Sale rides, optional for Delivery)
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 + 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 (not Resolved or ReceiptConfirmed) 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. No bulk "accept driver" or "accept warehouse" shortcut.
  6. All list endpoints return paginated responses.
  7. Vehicle capacity check is not enforced in this step — deferred to Step 13.

Permissions to Seed

Code Who
loading.view All roles
loading.dispatch Warehouse manager and above
loading.receive Driver (AreaManager and above for testing)
loading.resolve Supervisor and above

Fraud / Control Considerations

  • Discrepancies are immediately visible and block activation — they cannot be silently absorbed.
  • QuantityResolved is permanently stored per line for audit purposes, even after the sheet is resolved.
  • See 09_fraud_controls.md for the audit trail layer that will sit on top of this.

Open Questions

  • ✅ Vehicle capacity check — deferred to Step 13 (Fleet Management).
  • ✅ Discrepancy resolution — supervisor enters QuantityResolved per line item.
  • ✅ Ride activation trigger — confirm-receipt and resolve auto-trigger Open → Active internally.
  • ✅ Item population — Delivery rides auto-populated from orders; Sale rides manually entered.

Implementation Notes

Build order:

  1. Enum — add LoadingSheetStatus to Models/Enums.cs; add Load to StockMovementType
  2. Entities — LoadingSheet, LoadingSheetItem (new)
  3. AppDBContext — new DbSets + Fluent config
  4. Migration — LoadingEvent (schema) + LoadingPermissions (4 new permissions seeded)
  5. DTOs — LoadingSheetDTOs.cs
  6. Repository — ILoadingSheetRepository, LoadingSheetRepository
  7. Service — LoadingSheetService
  8. Controller — LoadingSheetController
  9. DI registration in Program.cs

Feature folder: Features/LoadingEvent/

What was built:

  1. ✅ Enums — added LoadingSheetStatus and Load to MovementType in Models/Enums.cs
  2. ✅ Entities — LoadingSheet, LoadingSheetItem created in Models/
  3. ✅ AppDBContext — LoadingSheets, LoadingSheetItems DbSets added; Fluent config for FK restrictions and ride/warehouse relationships
  4. ✅ DTOs — LoadingSheetDTOs.cs in Features/LoadingEvent/
  5. ✅ Repository — ILoadingSheetRepository, LoadingSheetRepository in Features/LoadingEvent/
  6. ✅ Service — LoadingSheetService in Features/LoadingEvent/
  7. ✅ Controller — LoadingSheetController in Features/LoadingEvent/
  8. ✅ DI registration in Program.cs
  9. ⚠️ Permissions added to seed (loading.view, loading.dispatch, loading.receive, loading.resolve) — authorization layer is being replaced, skip this

Pending (run manually):

  • dotnet ef migrations add LoadingEvent — blocked by a pre-existing duplicate Permission seed ID in AppDBContextModelSnapshot.cs (ID 20000000-0000-0000-0000-000000000040 is assigned to both categories.delete and driver_profiles.create). This is a snapshot consistency issue from a previous session and needs to be resolved before migration generation will succeed.
  • dotnet ef database update after migration is generated.