# 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. --- ## Current Business Understanding - A `Ride` is one operational trip for one vehicle and one driver on one route for now. - `RideType` distinguishes business behavior: - `Delivery` ride: used to fulfill existing orders. - `Sale` ride: used by sales agents in the field to create sales, and every sale creates an order. - `Sale` and `Order` are different: - `Sale` is the commercial transaction created by the sales agent. - `Order` is the fulfillment document created from every sale. - Item lines are maintained once in `OrderItem`; `Sale` does not keep a separate item-line table. - `Sale` statuses use sales language such as `Completed`. - `Order` statuses use logistics language such as `Fulfilled`. --- ## 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 } ``` Meaning of statuses: - `Draft`: ride exists but operational work has not started. - `Open`: ride is prepared and ready for loading/preparation work. - `Active`: ride is live in the field. - `CancelRequested`: active ride is waiting for cancellation approval or rejection. - `Cancelled`: ride ended through approved cancellation. - `Closed`: ride ended normally. --- ### 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 ```text 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 plus 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 block if license is expired. If `EnforceMedicalClearance = true`, also block 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. 8. A ride uses a single route for now. --- ## 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. 4. Multi-route ride assignment - deferred for now. Current model stays single-route. --- ## Implementation Notes **Build order:** 1. Enums - `RideType`, `RideStatus`, `CancelRequestType`, `CancelRequestStatus` 2. Entities - `DriverProfile`, `DriverComplianceConfig`, `RideCancelRequest` 3. `AppDBContext` - new DbSets plus Fluent config 4. Migrations - ride lifecycle schema and permissions 5. DTOs - driver profile, compliance config, ride 6. Repositories - ride and ride cancel request support 7. Services - driver profile, compliance config, ride 8. Controllers - driver profile, compliance config, ride 9. DI registration in `Program.cs` **Feature folders:** `Features/Ride/`, `Features/DriverProfile/`, `Features/DriverComplianceConfig/`