Merge branch 'dev' into production
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user