03_ride_lifecycle implemented and not tested
This commit is contained in:
@@ -1,82 +1,251 @@
|
||||
# 03 — Ride Lifecycle
|
||||
|
||||
**Status**: Not Started
|
||||
**Status**: Done
|
||||
**Depends On**: 02 — Stock Ledger
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Turn the currently-unused `DeliveryRun` model into a working ride: a session that turns a vehicle
|
||||
into a temporary warehouse. Covers `assignVehicleToRoute`, `assignDriverToVehicle`,
|
||||
`checkDriverCompliance` from [mainPlan.md §5](./mainPlan.md#5-vehicle--fleet-management)
|
||||
and `planDeliveryRoute` from §3.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Models/DeliveryRun.cs` exists (`FK_VehicleId`, `FK_RouteId`, `Meterstart`, `Meterend`) but has no
|
||||
repository, service, or controller — it is completely unwired today, per [mainPlan.md](./mainPlan.md)'s
|
||||
"Known Gaps" table. `FK_DriverId`/`Driver` navigation is commented out because no `Driver` entity
|
||||
exists yet (gap #10 in [00_existing_gaps.md](./00_existing_gaps.md)) — this step is blocked on that
|
||||
being resolved first if driver assignment is required at ride-open time.
|
||||
Turn the currently-unused `DeliveryRun` model into a fully working ride — a session that turns a
|
||||
vehicle into a temporary warehouse. Covers driver profile management, ride creation, state
|
||||
transitions, cancellation workflow, and the vehicle breakdown scenario.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Ride (extending DeliveryRun)
|
||||
### New Entity — DriverProfile
|
||||
|
||||
Add a `Status` field to `DeliveryRun` for the state machine below, and a `RideType` field
|
||||
(`Delivery` | `Sale`).
|
||||
Linked one-to-one with `User`. Holds compliance data only — no login or auth fields.
|
||||
|
||||
### Ride State Machine
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_UserId | Guid | FK → User (unique) |
|
||||
| LicenseNumber | string | |
|
||||
| LicenseExpiry | DateTime | |
|
||||
| MedicalClearanceExpiry | DateTime | |
|
||||
| IsActive | bool | Can be suspended without deleting |
|
||||
| CreatedAt | DateTime | |
|
||||
|
||||
`Draft → Open → Active → Closed`. A vehicle can only be on one `Open`/`Active` ride at a time —
|
||||
enforced in the service layer, not the database.
|
||||
No license class tied to vehicle type — not required.
|
||||
|
||||
### Ride Type (Delivery vs Sale)
|
||||
**Compliance check rule:** Driver is compliant if `IsActive = true`, plus any checks that are enabled in `DriverComplianceConfig`. By default all checks are disabled. When enabled: `LicenseExpiry > today` AND/OR `MedicalClearanceExpiry > today`.
|
||||
|
||||
`Delivery` rides fulfill pre-confirmed orders (see [05_delivery_ride.md](./05_delivery_ride.md));
|
||||
`Sale` rides create invoices on the spot with no pre-orders (see [06_sale_ride.md](./06_sale_ride.md)).
|
||||
Both share the same ride lifecycle and loading event.
|
||||
---
|
||||
|
||||
### Updated Entity — Vehicle
|
||||
|
||||
Add one nullable field:
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| FK_DefaultWarehouseId | Guid? | If set, ride always loads from this warehouse. If null, warehouse is selected at ride creation. |
|
||||
|
||||
---
|
||||
|
||||
### Updated Entity — DeliveryRun (becomes Ride)
|
||||
|
||||
Add the following fields to the existing `DeliveryRun`:
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| RideType | RideType | Delivery or Sale |
|
||||
| Status | RideStatus | State machine below |
|
||||
| FK_DriverProfileId | Guid | FK → DriverProfile |
|
||||
| FK_SourceWarehouseId | Guid | Where stock is loaded from. Must be the same warehouse stock returns to on cancel/close. |
|
||||
| StartedAt | DateTime? | Set when Draft → Open |
|
||||
| ActivatedAt | DateTime? | Set when Open → Active |
|
||||
| ClosedAt | DateTime? | Set when Active → Closed |
|
||||
|
||||
---
|
||||
|
||||
### New Enum — RideType
|
||||
|
||||
```csharp
|
||||
public enum RideType { Delivery, Sale }
|
||||
```
|
||||
|
||||
### New Enum — RideStatus
|
||||
|
||||
```csharp
|
||||
public enum RideStatus { Draft, Open, Active, CancelRequested, Cancelled, Closed }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### New Entity — RideCancelRequest
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_RideId | Guid | FK → DeliveryRun |
|
||||
| TypeCode | CancelRequestType | Reason category |
|
||||
| Reason | string | Free text explanation |
|
||||
| RequestedBy | Guid | UserId |
|
||||
| RequestedAt | DateTime | |
|
||||
| Status | CancelRequestStatus | Pending, Approved, Rejected |
|
||||
| ApprovedBy | Guid? | UserId of approver |
|
||||
| ApprovedAt | DateTime? | |
|
||||
|
||||
### New Enum — CancelRequestType
|
||||
|
||||
```csharp
|
||||
public enum CancelRequestType
|
||||
{
|
||||
VehicleBreakdown,
|
||||
DriverUnavailable,
|
||||
RouteIssue,
|
||||
Other
|
||||
}
|
||||
```
|
||||
|
||||
### New Entity — DriverComplianceConfig
|
||||
|
||||
Single-row configuration table. Controls which compliance checks are enforced at ride open time.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| EnforceLicenseExpiry | bool | Default false. If true, ride open is blocked if driver license is expired. |
|
||||
| EnforceMedicalClearance | bool | Default false. If true, ride open is blocked if medical clearance is expired. |
|
||||
| UpdatedAt | DateTime | |
|
||||
| UpdatedBy | Guid | UserId of last person who changed the config. |
|
||||
|
||||
Managed via a dedicated config endpoint. Only one row ever exists.
|
||||
|
||||
---
|
||||
|
||||
### New Enum — CancelRequestStatus
|
||||
|
||||
```csharp
|
||||
public enum CancelRequestStatus { Pending, Approved, Rejected }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
Draft → Open → Active → Closed
|
||||
↘ CancelRequested → Cancelled
|
||||
```
|
||||
|
||||
| Transition | Trigger | What it does |
|
||||
|---|---|---|
|
||||
| Draft → Open | `POST /api/rides/{id}/open` | Validates driver compliance. Checks vehicle has no other Open/Active ride. Sets `StartedAt`. |
|
||||
| Open → Active | `POST /api/rides/{id}/activate` | Called by loading event confirmation (Step 04). Sets `ActivatedAt`. |
|
||||
| Active → Closed | `POST /api/rides/{id}/close` | Called after ride reconciliation (Step 07). All stock must be returned to source warehouse. Sets `ClosedAt`. |
|
||||
| Active → CancelRequested | `POST /api/rides/{id}/cancel-request` | Driver submits cancel request with type code and reason. Ride status changes to `CancelRequested`. |
|
||||
| CancelRequested → Cancelled | `POST /api/rides/cancel-requests/{id}/approve` | Supervisor approves. Ride status changes to `Cancelled`. All stock must return to source warehouse before cancel completes. |
|
||||
| CancelRequested → Active | `POST /api/rides/cancel-requests/{id}/reject` | Supervisor rejects. Ride returns to `Active`. |
|
||||
|
||||
**Order assignment window:** Orders can be assigned to a Delivery ride from Draft until the ride transitions to Open. No new orders after Open.
|
||||
|
||||
---
|
||||
|
||||
## Warehouse Configuration
|
||||
|
||||
Per vehicle. A vehicle either has `FK_DefaultWarehouseId` set (always loads from that warehouse)
|
||||
or it is null (warehouse is selected manually at ride creation time).
|
||||
|
||||
Loading and unloading warehouse are always the same — stock must return to its source warehouse
|
||||
before a ride can close or cancel.
|
||||
|
||||
---
|
||||
|
||||
## Vehicle Breakdown Scenario
|
||||
|
||||
1. Driver submits cancel request with `TypeCode = VehicleBreakdown`.
|
||||
2. Ride status moves to `CancelRequested`.
|
||||
3. Supervisor views a breakdown assist endpoint that lists all currently `Active` rides with their
|
||||
current vehicle stock levels and the stranded ride's open orders — supervisor decides manually
|
||||
which ride takes over which orders.
|
||||
4. Supervisor approves the cancel request.
|
||||
5. Ride moves to `Cancelled` once all stock is confirmed returned to source warehouse.
|
||||
|
||||
The system surfaces the information — it does not automatically reassign orders.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignVehicleToRoute(vehicleId, routeId)` (§5) | Implicit in `POST /api/rides` — a ride binds a vehicle to a route for its duration |
|
||||
| `assignDriverToVehicle(vehicleId, driverId)` (§5) | `PUT /api/rides/{id}/driver` — blocked on Driver entity, see gap #10 |
|
||||
| `checkDriverCompliance(driverId)` (§5) | Called internally before a ride can move `Draft → Open` |
|
||||
| `planDeliveryRoute(warehouseId, orderIds[])` (§3) | `POST /api/rides` (for `RideType = Delivery`) |
|
||||
| — | `POST /api/rides/{id}/open`, `POST /api/rides/{id}/close` — state transitions |
|
||||
Base route: `/api/rides`
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| POST | `/` | Create ride (Draft). If vehicle has default warehouse, pre-populate it. |
|
||||
| GET | `/` | List rides (paginated). Filter by status, type, driverProfileId, vehicleId. |
|
||||
| GET | `/{id}` | Get ride detail |
|
||||
| PUT | `/{id}/driver` | Assign or change driver profile |
|
||||
| POST | `/{id}/open` | Transition Draft → Open |
|
||||
| POST | `/{id}/activate` | Transition Open → Active (called by loading event) |
|
||||
| POST | `/{id}/close` | Transition Active → Closed (called by reconciliation) |
|
||||
| POST | `/{id}/cancel-request` | Submit cancel request (Active rides only) |
|
||||
| GET | `/cancel-requests` | List cancel requests (paginated). Filter by status. |
|
||||
| POST | `/cancel-requests/{id}/approve` | Approve cancel request |
|
||||
| POST | `/cancel-requests/{id}/reject` | Reject cancel request |
|
||||
| GET | `/breakdown-assist/{rideId}` | Lists active rides with stock + stranded ride's open orders |
|
||||
|
||||
### DriverComplianceConfig
|
||||
|
||||
Base route: `/api/driver-compliance-config`
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get current config |
|
||||
| PUT | `/` | Update config flags |
|
||||
|
||||
---
|
||||
|
||||
### DriverProfile
|
||||
|
||||
Base route: `/api/driver-profiles`
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| POST | `/` | Create driver profile (links to a User) |
|
||||
| GET | `/` | List driver profiles (paginated) |
|
||||
| GET | `/{id}` | Get driver profile detail |
|
||||
| PUT | `/{id}` | Update compliance fields |
|
||||
| DELETE | `/{id}` | Soft deactivate |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- A ride cannot open without a compliant driver (once Driver entity exists) and a vehicle not
|
||||
already on an open ride.
|
||||
- Opening a ride creates the vehicle's stock ledger location (see [02_stock_ledger.md](./02_stock_ledger.md)).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
- `checkDriverCompliance` failures must block ride open, not just warn.
|
||||
1. A ride cannot transition Draft → Open if `IsActive = false` on the driver profile. If `EnforceLicenseExpiry = true`, also blocks if license is expired. If `EnforceMedicalClearance = true`, also blocks if medical clearance is expired.
|
||||
2. A vehicle cannot be on more than one Open or Active ride at a time.
|
||||
3. Orders can only be assigned to a Delivery ride while it is in Draft status.
|
||||
4. Loading and unloading warehouse must be the same — enforced at close/cancel.
|
||||
5. A CancelRequested ride cannot close or activate until the request is resolved.
|
||||
6. All cancel requests must have a TypeCode — free text reason alone is not sufficient.
|
||||
7. All list endpoints return paginated responses.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is a `Driver` a new entity distinct from `User`, or is "driver" just a `User` with a specific
|
||||
`Role`? Affects whether gap #10 needs a new table or just a role addition.
|
||||
1. **Cancel request type codes** — ✅ Resolved. `VehicleBreakdown`, `DriverUnavailable`, `RouteIssue`, `Other`.
|
||||
2. **Breakdown assist endpoint** — ✅ Resolved. System shows info only, supervisor decides manually. No auto-reassignment.
|
||||
3. **Driver compliance** — ✅ Resolved. Checks are configurable via `DriverComplianceConfig`. `IsActive` is always enforced. License and medical checks are optional flags, default off.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
**Build order:**
|
||||
1. ✅ Enums — `RideType`, `RideStatus`, `CancelRequestType`, `CancelRequestStatus` → added to `Models/Enums.cs`
|
||||
2. ✅ Entities — `DriverProfile`, `DriverComplianceConfig`, `RideCancelRequest` (new); updated `Vehicle` (+`FK_DefaultWarehouseId`), updated `DeliveryRun` (+`RideType`, `Status`, `FK_DriverProfileId`, `FK_SourceWarehouseId`, `StartedAt`, `ActivatedAt`, `ClosedAt`)
|
||||
3. ✅ `AppDBContext` — new DbSets + Fluent config
|
||||
4. ✅ Migrations — `RideLifecycle` (schema), `RidePermissions` (10 new permissions seeded across all roles)
|
||||
5. ✅ DTOs — `DriverProfileDTOs.cs`, `DriverComplianceConfigDTOs.cs`, `RideDTOs.cs`
|
||||
6. ✅ Repositories — `IDriverProfileRepository`, `IDriverComplianceConfigRepository`, `IRideRepository`, `IRideCancelRequestRepository`
|
||||
7. ✅ Services — `DriverProfileService`, `DriverComplianceConfigService`, `RideService`
|
||||
8. ✅ Controllers — `DriverProfileController`, `DriverComplianceConfigController`, `RideController`
|
||||
9. ✅ DI registration in `Program.cs`
|
||||
|
||||
**Feature folders:** `Features/Ride/`, `Features/DriverProfile/`, `Features/DriverComplianceConfig/`
|
||||
|
||||
**Pending:** `dotnet ef database update` to apply migrations to DB. Tests for the three new services.
|
||||
|
||||
@@ -38,7 +38,7 @@ They must be resolved before new feature steps begin because later steps depend
|
||||
| 00 | Existing Gap Fixes | Done | — | [00_existing_gaps.md](./00_existing_gaps.md) |
|
||||
| 01 | Item Catalog | Done | 00 | [01_item_catalog.md](./01_item_catalog.md) |
|
||||
| 02 | Stock Ledger | Done | 01 | [02_stock_ledger.md](./02_stock_ledger.md) |
|
||||
| 03 | Ride Lifecycle | Not Started | 02 | [03_ride_lifecycle.md](./03_ride_lifecycle.md) |
|
||||
| 03 | Ride Lifecycle | Done | 02 | [03_ride_lifecycle.md](./03_ride_lifecycle.md) |
|
||||
| 04 | Loading Event | Not Started | 03 | [04_loading_event.md](./04_loading_event.md) |
|
||||
| 05 | Delivery Ride Execution | Not Started | 04 | [05_delivery_ride.md](./05_delivery_ride.md) |
|
||||
| 06 | Sale Ride Execution | Not Started | 04 | [06_sale_ride.md](./06_sale_ride.md) |
|
||||
|
||||
Reference in New Issue
Block a user