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,79 +1,148 @@
|
||||
# 05 — Delivery Ride Execution
|
||||
|
||||
**Status**: Not Started
|
||||
**Status**: Planning
|
||||
**Depends On**: 04 — Loading Event
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Execute pre-confirmed orders against a loaded, active delivery ride. Covers the order-status half
|
||||
of [mainPlan.md §1](./mainPlan.md#1-order-management):
|
||||
`updateOrderStatus`, `convertOrderToInvoice`, `createBackorder`. Requires `Order.Status` to exist
|
||||
first — see gap #7 in [00_existing_gaps.md](./00_existing_gaps.md).
|
||||
Fulfill pending orders against an active Delivery ride. The driver visits each store on the route,
|
||||
delivers the ordered items, gets customer confirmation, and the system creates the stock movement,
|
||||
updates the order, and completes the sale.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Today `Order` has no status and no link to a ride. This step is where the order lifecycle
|
||||
(`pending → approved → picked → dispatched → delivered → invoiced`) actually gets driven, once the
|
||||
order has been picked/loaded ([04_loading_event.md](./04_loading_event.md)).
|
||||
- A Delivery ride goes `Active` after the loading event is confirmed.
|
||||
- Orders are linked to a ride implicitly through route: `Ride → Route → Stores → Pending Orders`.
|
||||
- `QuantityFulfilled` on `OrderItem` tracks what was actually delivered (added in pre-planning).
|
||||
- No separate backorder entity — partially fulfilled orders stay `PartiallyFulfilled` and get picked
|
||||
up by the next delivery ride on the same route.
|
||||
- Customer confirmation in v1 is a boolean flag (`CustomerConfirmed`). Full signature/OTP deferred
|
||||
to Step 09 (Fraud Controls).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Delivery Task (per order on ride)
|
||||
### New Entity — DeliveryTask
|
||||
|
||||
`RideId`, `OrderId`, `Sequence` (visit order), `Status` (`Pending`, `Delivered`, `PartiallyDelivered`,
|
||||
`Failed`).
|
||||
Extends `Template` (adds `IsActive`, `CreatedAt` — consistent with all other entities).
|
||||
|
||||
### Delivery Outcome
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK (from Template) |
|
||||
| FK_RideId | Guid | FK → DeliveryRun |
|
||||
| FK_OrderId | Guid | FK → Order |
|
||||
| Sequence | int | Visit order on the route |
|
||||
| Status | DeliveryTaskStatus | State machine below |
|
||||
| CustomerConfirmed | bool | Set to true at confirm time |
|
||||
| ConfirmedAt | DateTime? | Timestamp of customer confirmation |
|
||||
| Note | string? | Driver note on failure or partial delivery |
|
||||
| IsActive | bool | From Template (soft delete) |
|
||||
| CreatedAt | DateTime | From Template |
|
||||
|
||||
Per delivery task: quantities actually delivered per line, vs. ordered — feeds `createBackorder`
|
||||
for the shortfall.
|
||||
### New Enum — DeliveryTaskStatus
|
||||
|
||||
### Customer Confirmation
|
||||
```csharp
|
||||
public enum DeliveryTaskStatus
|
||||
{
|
||||
Pending,
|
||||
Delivered,
|
||||
PartiallyDelivered,
|
||||
Failed
|
||||
}
|
||||
```
|
||||
|
||||
Signature or OTP captured against the delivery task before it can be marked complete.
|
||||
### No new columns on OrderItem
|
||||
|
||||
`QuantityFulfilled` already added in pre-planning. No further model changes needed.
|
||||
|
||||
---
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
Pending → Delivered (all lines fully delivered + customer confirmed)
|
||||
Pending → PartiallyDelivered (some lines short + customer confirmed)
|
||||
Pending → Failed (driver could not deliver, no stock movement)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stock Movements
|
||||
|
||||
Created on `Delivered` or `PartiallyDelivered` after customer confirmation. One movement per item:
|
||||
|
||||
- **Debit** vehicle: `-QuantityFulfilled` at `LocationType.Vehicle`, `FK_LocationId = VehicleId`
|
||||
- `VehicleId` is resolved from `DeliveryTask.FK_RideId → DeliveryRun.FK_VehicleId`
|
||||
- `MovementType.Sale`
|
||||
- `ReferenceId = OrderId`
|
||||
|
||||
No movement on `Failed`.
|
||||
|
||||
---
|
||||
|
||||
## Order and Sale Status Transitions (triggered internally)
|
||||
|
||||
| Condition | Order | Sale |
|
||||
|---|---|---|
|
||||
| All OrderItem lines: QuantityFulfilled == Quantity | → Fulfilled | → Completed |
|
||||
| Any OrderItem line: QuantityFulfilled < Quantity | → PartiallyFulfilled | stays Pending |
|
||||
| Task Failed | stays Pending | stays Pending |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `updateOrderStatus(orderId, status)` (§1) | `PUT /api/rides/{rideId}/tasks/{orderId}/status` |
|
||||
| `convertOrderToInvoice(orderId)` (§1) | Triggered automatically on `Delivered`/`PartiallyDelivered` — see [19_finance_accounting.md](./19_finance_accounting.md) |
|
||||
| `createBackorder(orderId, unfulfilledItems[])` (§1) | Triggered automatically on `PartiallyDelivered` |
|
||||
| — | `POST /api/rides/{rideId}/tasks/{orderId}/confirm` — captures customer signature/OTP |
|
||||
Base routes: `/api/rides/{rideId}/tasks` and `/api/delivery-tasks`
|
||||
|
||||
| Method | Route | Who | Description |
|
||||
|---|---|---|---|
|
||||
| POST | `/api/rides/{rideId}/tasks/generate` | Driver/System | Auto-create tasks from all Pending/PartiallyFulfilled orders for stores on ride's route |
|
||||
| GET | `/api/rides/{rideId}/tasks` | Driver | List all tasks for this ride with current status |
|
||||
| GET | `/api/delivery-tasks/{id}` | Driver | Task detail with order items and quantities |
|
||||
| PUT | `/api/delivery-tasks/{id}/quantities` | Driver | Set QuantityFulfilled per order item before confirming |
|
||||
| POST | `/api/delivery-tasks/{id}/confirm` | Driver | Customer confirmed — creates stock movements, transitions order/sale |
|
||||
| POST | `/api/delivery-tasks/{id}/fail` | Driver | Mark task as failed with a note |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- A delivery task cannot be marked `Delivered` without a customer confirmation.
|
||||
- `PartiallyDelivered` always creates a `createBackorder` entry and a credit note for the
|
||||
undelivered quantity (see [18_returns_claims.md](./18_returns_claims.md) for credit note shape).
|
||||
- Each confirmed delivery task creates a stock movement (`Sale`/consumption) against the vehicle
|
||||
location per [02_stock_ledger.md](./02_stock_ledger.md).
|
||||
1. Delivery tasks can only be created for a ride in `Active` status and of type `Delivery`.
|
||||
2. One task per order per ride — cannot create a second task for the same order on the same ride.
|
||||
3. `QuantityFulfilled` must be set on all order items before calling confirm.
|
||||
4. `QuantityFulfilled` cannot exceed `Quantity` on any line.
|
||||
5. `CustomerConfirmed` must be true before the task can move to `Delivered` or `PartiallyDelivered`.
|
||||
6. Stock movement is created only once — at confirm. Never on fail.
|
||||
7. A failed task does not change the order or sale status — the order stays available for the next ride.
|
||||
8. Partial delivery: the unfulfilled quantity (`Quantity - QuantityFulfilled`) remains on the same
|
||||
order. The next delivery ride on the same route will pick it up automatically via `generate`.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
## Implementation Build Order
|
||||
|
||||
- OTP/signature capture must be tamper-evident (timestamped, tied to the delivery task, not
|
||||
editable after submission).
|
||||
1. Enum — add `DeliveryTaskStatus` to `Models/Enums.cs`
|
||||
2. Entity — `DeliveryTask` in `Models/`
|
||||
3. `AppDBContext` — new DbSet + Fluent config (Restrict on Ride, Order FKs)
|
||||
4. Migration
|
||||
5. DTOs — `DeliveryTaskDTOs.cs` in `Features/DeliveryRide/`
|
||||
6. Repository — `IDeliveryTaskRepository`, `DeliveryTaskRepository`
|
||||
7. Service — `DeliveryTaskService`
|
||||
8. Controller — `DeliveryTaskController`
|
||||
9. DI registration in `Program.cs`
|
||||
|
||||
**Feature folder:** `Features/DeliveryRide/`
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
## Open Questions — Resolved
|
||||
|
||||
- Signature capture (image) or OTP (SMS code) — one, or both, in v1?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
- ✅ Customer confirmation — boolean flag in v1, full signature/OTP in Step 09
|
||||
- ✅ Backorder — no separate entity; same order stays PartiallyFulfilled for next ride
|
||||
- ✅ Task generation — on-demand via `generate` endpoint, not automatic on ride activation
|
||||
- ✅ Failed task — order stays Pending, available for next ride, no stock movement
|
||||
|
||||
Reference in New Issue
Block a user