Dev #23

Merged
DeepnaPooja merged 65 commits from dev into production 2026-09-14 11:59:21 +05:30
Showing only changes of commit b8836a1e87 - Show all commits
@@ -0,0 +1,221 @@
# [Refactor] Ride Lifecycle — Route-Free, Type-Free Rides
**Status**: Proposed (design only — no code changes yet)
**Refactors**: [../03_ride_lifecycle.md](../03_ride_lifecycle.md) (kept as-is, this is the target design)
**Depends On**: 02 — Stock Ledger
---
## Goal
Ride should not require a `Route`, should not carry a `RideType`, should not be gated by a warehouse's `Type`, and should not even pin down a source warehouse on the ride record itself. Per direction from the business owner, the ride record carries only what a real dispatch needs to track — **"a ride only carries vehicle, driver assign, and meter reading from start to end."** Everything route-shaped (which stores get visited, which orders/items travel on this trip) and everything warehouse-shaped (where stock loads from/returns to) moves out of the Ride entity's responsibility entirely; neither is replaced by another matching mechanism here.
This is a genuine pivot from [03_ride_lifecycle.md](../03_ride_lifecycle.md), where `Route` and `FK_SourceWarehouseId` are both **required** on every ride, and `Route` is the *sole* mechanism two other features use to resolve "which orders belong to this ride." See [Impact on Dependent Features](#impact-on-dependent-features) below — those gaps are flagged, not solved, in this pass. The Draft → Open → Active → Closed workflow and the cancel-request flow are kept — only the fields that used to back warehouse reconciliation and ride classification are removed; how those transitions work without them is intentionally left as an open question (see below), not resolved by this document.
---
## Data Model Changes
### Updated Entity — Ride (`DeliveryRun`)
```
Ride (DeliveryRun)
├── Id (Guid) — PK, from Template
├── FK_VehicleId (Guid) — required
├── FK_DriverProfileId (Guid) — required
├── Meterstart (int)
├── Meterend (int)
├── StartedAt (DateTime?) — set when Draft → Open
├── ActivatedAt (DateTime?) — set when Open → Active
├── ClosedAt (DateTime?) — set when Active → Closed
├── IsActive, CreatedAt — from Template
```
That's the whole record: a vehicle, a driver, a meter start/end reading, and the three workflow
timestamps. Everything else is removed:
- **`FK_RouteId`** — gone (see the original goal above; unchanged from the first pass of this doc).
- **`RideType`** — gone. Nothing in the Ride record itself needs to distinguish Delivery from Sale
once the ride no longer drives order/task auto-population (that was the only place `RideType`
mattered inside the Ride entity's own logic — see
[Impact on Dependent Features](#impact-on-dependent-features)).
- **`Status` (`RideStatus`)** — gone as a stored column. The workflow stages (Draft, Open, Active,
CancelRequested, Cancelled, Closed) are **kept as a concept** — see
[State Machine](#state-machine) — but this document does not resolve how "current state" is
represented without a stored enum. Left as an open question (see below), not solved here.
- **`FK_SourceWarehouseId`** — gone. The ride record no longer pins down a warehouse to load from
or reconcile stock against at all. `Warehouse.Type` accordingly becomes moot for rides (there's no
ride-side warehouse selection left to gate); the enum itself is untouched on `Warehouse`.
### Warehouse
No changes to the `Warehouse` entity — `Warehouse.Type` (`WareHouseType`: `DistributionCenter`,
`FulfillmentCenter`, `CrossDocking`, `ColdStorage`, `BondedWarehouse`, `AutomatedWarehouse`) stays
exactly as-is; it's still useful for warehouse management/reporting elsewhere in the system. What's
different from [03_ride_lifecycle.md](../03_ride_lifecycle.md) is that **rides no longer reference
a warehouse at all**, so there's nothing on the ride side left to filter or gate by `Type` — this
supersedes the first pass of this document, which kept `FK_SourceWarehouseId` and only dropped the
type constraint on it. See [Impact on Dependent Features](#impact-on-dependent-features) for what
that means for stock reconciliation, which previously depended on `FK_SourceWarehouseId`.
---
## State Machine
The workflow stages and endpoints are **kept** from [03_ride_lifecycle.md](../03_ride_lifecycle.md#state-machine)
— none of the transitions reference `Route`, `RideType`, or a warehouse by name. What's removed is
the *storage*: there is no `Status` column to hold "which stage is this ride in." This document does
not resolve what replaces it (e.g., deriving state purely from which timestamp fields are set —
`StartedAt` null = Draft, `StartedAt` set + `ActivatedAt` null = Open, etc. — is one option, but not
decided here). Flagged as [Open Question 4](#open-questions).
```
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. Sets `ActivatedAt`. |
| Active → Closed | `POST /api/rides/{id}/close` | Called after ride reconciliation. Sets `ClosedAt`. Previously required "all stock returned to source warehouse" — with no `FK_SourceWarehouseId` on the ride, this check has nothing to reconcile against; see [Impact on Dependent Features](#impact-on-dependent-features). |
| Active → CancelRequested | `POST /api/rides/{id}/cancel-request` | Driver submits cancel request with type code and reason. |
| CancelRequested → Cancelled | `POST /api/rides/cancel-requests/{id}/approve` | Supervisor approves. Previously required stock returned to source warehouse first — same gap as Close above. |
| CancelRequested → Active | `POST /api/rides/cancel-requests/{id}/reject` | Supervisor rejects. Ride returns to `Active`. |
---
## Business Rules
Carried over from [03_ride_lifecycle.md](../03_ride_lifecycle.md#business-rules), with the
route-dependent rules removed:
1. A ride cannot transition Draft → Open if `IsActive = false` on the driver profile, or (when the
relevant `DriverComplianceConfig` flags are enabled) if license/medical clearance is expired.
2. A vehicle cannot be on more than one Open or Active ride at a time.
3. A CancelRequested ride cannot close or activate until the request is resolved.
4. All cancel requests must have a `TypeCode` — free text reason alone is not sufficient.
5. All list endpoints return paginated responses.
6. **A ride carries no order, task, route, or warehouse linkage of its own.** Creating a ride never
touches `Order`, `DeliveryTask`, `LoadingSheet`, or `StockBalance`/`StockMovement` records —
those remain entirely separate concerns, attached (if at all) by whatever future mechanism
replaces route-derived linkage and warehouse reconciliation.
**Removed rules** (no longer apply once `Route`, `RideType`, `Status`, and `FK_SourceWarehouseId`
are gone):
- ~~"A ride uses a single route for now."~~
- ~~"Orders can only be assigned to a Delivery ride while it is in Draft status."~~ — there is no
order-assignment mechanism on the ride at all anymore, in Draft or otherwise.
- ~~"Loading and unloading warehouse must be the same — enforced at close/cancel."~~ — there is no
warehouse on the ride record to compare "loading" against "unloading" anymore. See
[Impact on Dependent Features](#impact-on-dependent-features).
---
## API Endpoints
Same routes as [03_ride_lifecycle.md](../03_ride_lifecycle.md#api-endpoints), with `routeId`,
`rideType`, and `sourceWarehouseId` removed from the request/response shapes:
| Method | Route | Description |
|---|---|---|
| POST | `/api/rides` | Create ride (Draft): `{ vehicleId, driverProfileId }`. Nothing else required. |
| GET | `/api/rides` | List rides (paginated). Filter by driverProfileId, vehicleId. (Filtering by `status`/`type` depends on how [Open Question 4](#open-questions) resolves.) |
| GET | `/api/rides/{id}` | Get ride detail — response no longer includes `routeId`/`routeName`, `rideType`, or `sourceWarehouseId`/`sourceWarehouseName`. |
| PUT | `/api/rides/{id}/driver` | Assign or change driver profile |
| POST | `/api/rides/{id}/open` | Transition Draft → Open |
| POST | `/api/rides/{id}/activate` | Transition Open → Active |
| POST | `/api/rides/{id}/close` | Transition Active → Closed |
| POST | `/api/rides/{id}/cancel-request` | Submit cancel request (Active rides only) |
| GET | `/api/rides/cancel-requests` | List cancel requests (paginated). Filter by status. |
| POST | `/api/rides/cancel-requests/{id}/approve` | Approve cancel request |
| POST | `/api/rides/cancel-requests/{id}/reject` | Reject cancel request |
| GET | `/api/rides/breakdown-assist/{rideId}` | See [Impact on Dependent Features](#impact-on-dependent-features) — this endpoint currently keys off the stranded ride's route and needs rework. |
`DriverComplianceConfig` and `DriverProfile` endpoints are unaffected by this refactor.
---
## Impact on Dependent Features
Removing `FK_RouteId`, `RideType`, `Status`, and `FK_SourceWarehouseId` from `Ride` is not a purely
additive/subtractive field change — several existing behaviors currently derive their entire input
from these fields, and none of them get a replacement in this pass. Each is flagged here as
**known-broken, deferred follow-up work**, not solved:
1. **Delivery task generation** (`Features/DeliveryRide/DeliveryTaskService.GenerateAsync`) —
currently calls `orderRepo.GetPendingByRouteIdAsync(ride.FK_RouteId)` to auto-create delivery
tasks for a Delivery ride. With no route (and no `RideType` to even tell Delivery from Sale) on
the ride, this call has nothing to key off. Needs a redesign once "how orders attach to a ride"
is decided — out of scope here.
2. **Loading sheet auto-population** (`Features/LoadingEvent/LoadingSheetService` +
`LoadingSheetRepository.GetAggregatedOrderItemsByRouteIdAsync`) — auto-fills a Delivery ride's
loading-sheet items by aggregating orders on `Order.Store.FK_RouteId == ride.FK_RouteId`. Same
gap as above; documented in [04_loading_event.md](../04_loading_event.md), which will need its
own refactor pass once this lands.
3. **Breakdown-assist endpoint** (`RideService.GetBreakdownAssistAsync`) — uses
`strandedRide.FK_RouteId` to list the stranded route's open orders for manual reassignment. With
no route, this listing has no scope; the endpoint's shape needs to change (e.g., list all open
orders regardless of route, or drop this listing) — not decided here.
4. **Stock reconciliation at Close/Cancel** — [03_ride_lifecycle.md](../03_ride_lifecycle.md) closes
or cancels a ride only once "all stock is returned to source warehouse," checked against
`FK_SourceWarehouseId`. With no warehouse on the ride, there is nothing to reconcile the vehicle's
stock balance against at all. This is the biggest open gap in this pass — closing/cancelling a
ride previously guaranteed the vehicle ends up empty (stock-wise); without a target warehouse,
that guarantee has no mechanism to enforce it. Needs its own design — not attempted here.
5. **[05_delivery_ride.md](../05_delivery_ride.md)** describes the whole order-to-ride relationship
as `Ride → Route → Stores → Pending Orders`. That document goes stale the moment this refactor
ships and needs its own rewrite — tracked as follow-up, not attempted in this pass.
6. **[04_loading_event.md](../04_loading_event.md)**'s loading-event flow currently records which
warehouse stock was loaded from via the ride's `FK_SourceWarehouseId`; without that field, each
loading event presumably needs to carry its own warehouse reference directly instead of inheriting
it from the ride — flagged, not designed, here.
None of the above are implemented or redesigned by this document. They're called out so the gap is
visible and intentional rather than discovered later as a silent regression.
---
## Open Questions
1. **How does a Delivery ride's orders/tasks/items get attached, if not via Route?** —
Deliberately unresolved. Per the business owner's direction, the Ride entity itself should not
carry that responsibility going forward; whatever mechanism replaces route-derived linkage (if
any) is a separate design effort, not part of slimming down the Ride record.
2. **What happens to `GetBreakdownAssistAsync`'s route-scoped order listing?** — Needs its own
decision once (1) is resolved; not designed here.
3. **`04_loading_event.md` / `05_delivery_ride.md` rewrite** — scheduled as follow-up work once (1)
is resolved; explicitly out of scope for this document.
4. **How is a ride's current workflow stage tracked without a stored `Status` column?** — The
Draft/Open/Active/CancelRequested/Cancelled/Closed stages are kept conceptually (see
[State Machine](#state-machine)), but this document doesn't decide whether state is derived from
the existing timestamp fields (`StartedAt`/`ActivatedAt`/`ClosedAt` plus the presence of an open
`RideCancelRequest`), computed some other way, or whether a status-like field comes back under a
different name. Left for the business owner/implementer to resolve.
5. **What replaces stock-return enforcement at Close/Cancel without `FK_SourceWarehouseId`?** — See
[Impact on Dependent Features](#impact-on-dependent-features) item 4. Not designed here.
6. **Does `RideType` (Delivery vs Sale) need to exist somewhere else** — e.g. on whatever
loading-event or order-linkage mechanism eventually replaces the route-derived flow — even though
it's removed from `Ride` itself? Not decided here.
---
## Migration Notes (for when this is implemented)
Not part of this docs-only pass, but captured for the eventual implementation step:
- Drop the `FK_RouteId`, `RideType`, `Status`, and `FK_SourceWarehouseId` columns from
`DeliveryRun`/`Ride` and their Fluent/EF configuration.
- Remove `RouteId`, `RideType`, `Status`, and `SourceWarehouseId`/`SourceWarehouseName` from
`CreateRideRequest`, `RideResponse`, and `BreakdownAssistRideResponse` in `RideDTOs.cs`.
- Remove the `.Include(r => r.Route)`/`.Include(r => r.SourceWarehouse)` calls and
`GetActiveOrdersByRouteIdAsync` usage in `RideRepository.cs`/`RideService.cs` once the
breakdown-assist redesign (Open Question 2) lands.
- Remove the required `Route` and `Source Warehouse` `<Select>` fields (and the `RideType` picker)
from `ride-sheet.tsx` (frontend create form).
- `DeliveryTaskService.GenerateAsync` and `LoadingSheetService`'s route-based aggregation need a
redesign or removal before this ships — this is the load-bearing blocker, not a cleanup item.
- Resolve Open Question 4 (how workflow stage is tracked without `Status`) before touching the
`RideController`/`RideService` transition methods — they currently branch on the stored enum.
- Resolve Open Question 5 (stock-return enforcement without a source warehouse) before touching
`Close`/`CancelRequest` approval logic in `RideService.cs`.