Add DeliveryTask, Payment, and Sale features
Implemented DeliveryTask, Payment, and Sale entities with full CRUD, business logic, and API endpoints. Updated Order/OrderItem for sale linkage and fulfillment tracking. Registered new DbSets and DI services. Revised documentation for delivery, sale, and payment workflows.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 04 — Loading Event
|
||||
# 04 - Loading Event
|
||||
|
||||
**Status**: Planning
|
||||
**Depends On**: 03 — Ride Lifecycle
|
||||
**Depends On**: 03 - Ride Lifecycle
|
||||
|
||||
---
|
||||
|
||||
@@ -9,32 +9,36 @@
|
||||
|
||||
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.
|
||||
`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`
|
||||
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:
|
||||
|
||||
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).
|
||||
- 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
|
||||
### New Entity - LoadingSheet
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_RideId | Guid | FK → DeliveryRun |
|
||||
| FK_SourceWarehouseId | Guid | FK → Warehouse |
|
||||
| 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 |
|
||||
@@ -42,18 +46,18 @@ From this point the vehicle carries live inventory that decreases with every sal
|
||||
| ReceiptConfirmedBy | Guid? | UserId of driver |
|
||||
| CreatedAt | DateTime | |
|
||||
|
||||
### New Entity — LoadingSheetItem
|
||||
### New Entity - LoadingSheetItem
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_LoadingSheetId | Guid | FK → LoadingSheet |
|
||||
| FK_ItemId | Guid | FK → Item |
|
||||
| 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
|
||||
### New Enum - LoadingSheetStatus
|
||||
|
||||
```csharp
|
||||
public enum LoadingSheetStatus
|
||||
@@ -66,23 +70,23 @@ public enum LoadingSheetStatus
|
||||
}
|
||||
```
|
||||
|
||||
No changes to existing entities. Vehicle capacity check is deferred to Step 13 (Fleet Management).
|
||||
No changes to existing entities. Vehicle capacity check is deferred to Step 13.
|
||||
|
||||
---
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
Pending → DispatchConfirmed → ReceiptConfirmed (all lines match)
|
||||
↘ DiscrepancyFlagged → Resolved
|
||||
```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`. |
|
||||
| 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`. |
|
||||
|
||||
---
|
||||
|
||||
@@ -90,10 +94,11 @@ Pending → DispatchConfirmed → ReceiptConfirmed (all lines match)
|
||||
|
||||
| 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) |
|
||||
| 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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -101,12 +106,13 @@ For Delivery rides the warehouse manager reviews the pre-populated quantities an
|
||||
|
||||
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`
|
||||
- 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.
|
||||
`QuantityFinal` = `QuantityReceived` on a clean confirmation, or `QuantityResolved` when a
|
||||
discrepancy was resolved.
|
||||
|
||||
Movement `Type` = `Load` (new `StockMovementType` value to add to enums).
|
||||
Movement `Type` = `Load`.
|
||||
|
||||
---
|
||||
|
||||
@@ -119,23 +125,25 @@ Base route: `/api/loading-sheets`
|
||||
| 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}/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 + ride activation |
|
||||
| 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 (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.
|
||||
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 — deferred to Step 13.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -145,54 +153,27 @@ Base route: `/api/loading-sheets`
|
||||
|---|---|
|
||||
| `loading.view` | All roles |
|
||||
| `loading.dispatch` | Warehouse manager and above |
|
||||
| `loading.receive` | Driver (AreaManager and above for testing) |
|
||||
| `loading.receive` | Driver |
|
||||
| `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](./09_fraud_controls.md) for the audit trail layer that will sit on top of this.
|
||||
- Discrepancies are immediately visible and block activation.
|
||||
- `QuantityResolved` is permanently stored per line for audit purposes.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
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 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.
|
||||
Build after the ride model and before full delivery/sale execution.
|
||||
|
||||
Reference in New Issue
Block a user