# 03 — Ride Lifecycle **Status**: Done **Depends On**: 02 — Stock Ledger --- ## Goal 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 ### New Entity — DriverProfile Linked one-to-one with `User`. Holds compliance data only — no login or auth fields. | 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 | | No license class tied to vehicle type — not required. **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`. --- ### 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 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 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 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 **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.