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:
Kalana B. Thilakarathna
2026-08-24 21:34:57 +05:30
parent a833586e83
commit 6d8b6b3e59
38 changed files with 3938 additions and 563 deletions
+107 -38
View File
@@ -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