Merge branch 'dev' into production

This commit is contained in:
Kalana B. Thilakarathna
2026-09-16 10:41:32 +05:30
@@ -0,0 +1,195 @@
# [Refactor] Dispatch Pipeline — Shop PO to Invoice Finalization
**Status**: Proposed (design only — no code changes yet)
**Depends On**: [order_invoice_lifecycle.md](./order_invoice_lifecycle.md), [loading_event.md](./loading_event.md), [ride_lifecycle.md](./ride_lifecycle.md)
---
## Goal
Define the end-to-end order processing pipeline from a shop's initial PO through to invoice
completion. This replaces the separate Delivery ride and Sale ride flows
([05_delivery_ride.md](../05_delivery_ride.md), [06_sale_ride.md](../06_sale_ride.md)) with a
single unified pipeline.
---
## Pipeline Overview
```
[Stage 1] Shop PO created (editable)
↓
[Stage 2] PO Confirmed + Invoice Created → Draft (Mode A: combined / Mode B: separate)
↓
[Stage 3] Invoice Printed → agent takes to field
↓
[Stage 4] Loading Sheet Population (dispatcher-triggered, separate flow)
↓
[Stage 5] Delivery (TBD)
↓
[Stage 6] Agent returns printed invoice → office worker enters edits → Invoice Finalized
```
---
## Stage 1 — Shop PO
**Business term**: Shop PO
**System state**: `Order` (no Invoice yet)
The entry point to the pipeline. A sales agent creates a PO for a shop capturing line items and
quantities.
**Key rules:**
- Items editable — every edit soft-deletes current `OrderItem` rows (`IsActive = false`) and writes
a fresh set. `IsActive = true` = latest version; `IsActive = false` = history.
- Editing allowed only while Shop PO (before confirmation).
- No Invoice exists yet at this stage.
**API:**
| Method | Route | Description |
|---|---|---|
| POST | `/api/orders` | Create Shop PO for a store. |
| PUT | `/api/orders/{id}/items` | Rewrite line items (versioned). |
| GET | `/api/orders/{id}` | Get PO with current active items. |
---
## Stage 2 — Confirmed PO + Invoice Created
**Business term**: Confirmed PO
**System state**: `Order` (items locked) + `Invoice` at `Draft`
PO items are locked and an Invoice is created at `Draft`. How this happens depends on the active
mode — controlled by the system-level `InvoiceCreationMode` config flag.
**Mode A — combined action (one button):**
- `POST /api/orders/{id}/confirm-and-create-invoice`
- Locks PO items and creates Invoice at `Draft` atomically.
**Mode B — separate actions:**
- `POST /api/orders/{id}/confirm` — locks PO items only.
- Office user then browses Confirmed POs (filtered by `agentId`, `routeId`, `shopId`) and creates
the invoice: `POST /api/invoices { orderId }`.
**Key rules:**
- Confirmation requires no signature or OTP.
- `OrderItem` lines are permanently locked after confirmation.
- `Order.Status` (fulfillment tracking) is untouched — separate axis.
---
## Stage 3 — Invoice Printed
**Trigger**: `POST /api/invoices/{id}/print`
Office user prints the invoice. The agent takes the physical copy to the field.
**Key rules:**
- Agent may make handwritten edits on the printed copy during the ride (quantity adjustments,
returns, damage, discounts).
- Invoice is locked from system edits between `Printed` and `Finalized`.
---
## Stage 4 — Loading Sheet Population
A dispatcher manually triggers population via `POST /api/loading-sheets/populate` with one or more
filters. The system finds all matching Confirmed POs and aggregates them into **one loading sheet**.
**Filters (at least one required):**
- `routeId` — all Confirmed POs for shops on this route
- `agentId` — all Confirmed POs created by this agent
- `shopId` — all Confirmed POs for this specific shop
**What happens:**
1. System finds all `Order` records matching the filter where items are locked (Confirmed PO)
2. One `LoadingSheet` created at `Planned` status
3. `LoadingSheetItem` rows populated from the active `OrderItem` lines of those POs
4. Each source PO recorded in `LoadingSheetOrder` linking table
5. Each item carries `FK_OrderItemId` pointing back to its source `OrderItem`
**After population — editing:**
- Dispatcher can edit item quantities while the sheet is `Planned` or `Assigned`
- Every edit soft-deletes current `LoadingSheetItem` rows (`IsActive = false`) and writes a fresh
set — same versioning as Shop PO items
**Confirmation and stock movement:**
- Ride is provided at populate time — no separate assign step
- Each item has its own `FK_WarehouseId` — one sheet can load from multiple warehouses
- Dispatcher confirms the sheet (`Planned → Confirmed`) — items frozen
- Stock movements: one debit per item's warehouse, one credit to the ride's vehicle
- Timing: open question (Path A at `Confirmed` vs Path B at `ReceiptConfirmed`)
See [loading_event.md](./loading_event.md) for the full state machine.
---
## Stage 5 — Delivery
**Status**: TBD — to be designed in a subsequent pass.
---
## Stage 6 — Invoice Finalized
**Trigger**: `POST /api/invoices/{id}/complete`
After the ride or day-end, the agent returns the printed invoice to the office. The office worker
enters any edits the agent made into the system and submits.
**What happens:**
- Office worker enters adjustments (quantity changes, returns, damage, discounts) from the paper copy
- `Invoice.TotalAmount` is recomputed and locked
- `Invoice.Status` → `Finalized`
- `FinalizedAt` / `FinalizedBy` set
---
## Stage 3 — Loading Sheet Population
A dispatcher manually triggers population via `POST /api/loading-sheets/populate` with one or more
filters. The system finds all matching Confirmed POs and aggregates them into **one loading sheet**.
**Filters (at least one required):**
- `routeId` — all Confirmed POs for shops on this route
- `agentId` — all Confirmed POs created by this agent
- `shopId` — all Confirmed POs for this specific shop
**What happens:**
1. System finds all `Order` records matching the filter where `Invoice.Status = Draft`
2. One `LoadingSheet` created at `Planned` status
3. `LoadingSheetItem` rows populated from the active `OrderItem` lines of those POs
4. Each source PO recorded in `LoadingSheetOrder` linking table
5. Each item carries `FK_OrderItemId` pointing back to its source `OrderItem`
**After population — editing:**
- Dispatcher can edit item quantities while the sheet is `Planned` or `Assigned`
- Every edit soft-deletes all current `LoadingSheetItem` rows (`IsActive = false`) and writes a
fresh set — same versioning mechanism as Shop PO items
- Active rows (`IsActive = true`) = latest version; soft-deleted = history
**Confirmation and stock movement:**
- Dispatcher assigns a ride + source warehouse (`Planned → Assigned`)
- Dispatcher confirms the sheet (`Assigned → Confirmed`) — items frozen
- Stock movement timing is an open question — see [loading_event.md](./loading_event.md) Open Question 1
See [loading_event.md](./loading_event.md) for the full loading sheet state machine and dispatch/receipt flow.
---
## Stage 4+ — Delivery and Invoice Finalization
**Status**: TBD — to be designed in a subsequent pass.
---
## Open Questions
1. **Who can confirm a Shop PO?** — the creating sales agent, a supervisor, or either?
2. **Can a Confirmed PO be reverted to Shop PO?** — no un-confirm path designed here.
3. **Cancellation path for a Shop PO or Confirmed PO?** — not addressed here.
4. **Exact adjustment payload for `POST /complete`** — the fields the office worker can edit
(returns, damage, discounts) are assumed but not fully specified.
5. **Does Stage 4 (loading sheet) depend on Stage 3 (invoice printed), or can they happen in
parallel?** — not decided here.