Merge branch 'dev' into production
This commit is contained in:
@@ -45,6 +45,8 @@ quantities.
|
|||||||
a fresh set. `IsActive = true` = latest version; `IsActive = false` = history.
|
a fresh set. `IsActive = true` = latest version; `IsActive = false` = history.
|
||||||
- Editing allowed only while Shop PO (before confirmation).
|
- Editing allowed only while Shop PO (before confirmation).
|
||||||
- No Invoice exists yet at this stage.
|
- No Invoice exists yet at this stage.
|
||||||
|
- A Shop PO can be cancelled (`Order.Status → Cancelled`). No Invoice exists at this point so no
|
||||||
|
cascade required.
|
||||||
|
|
||||||
**API:**
|
**API:**
|
||||||
| Method | Route | Description |
|
| Method | Route | Description |
|
||||||
@@ -52,6 +54,7 @@ quantities.
|
|||||||
| POST | `/api/orders` | Create Shop PO for a store. |
|
| POST | `/api/orders` | Create Shop PO for a store. |
|
||||||
| PUT | `/api/orders/{id}/items` | Rewrite line items (versioned). |
|
| PUT | `/api/orders/{id}/items` | Rewrite line items (versioned). |
|
||||||
| GET | `/api/orders/{id}` | Get PO with current active items. |
|
| GET | `/api/orders/{id}` | Get PO with current active items. |
|
||||||
|
| POST | `/api/orders/{id}/cancel` | Cancel a Shop PO. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -76,6 +79,9 @@ mode — controlled by the system-level `InvoiceCreationMode` config flag.
|
|||||||
- Confirmation requires no signature or OTP.
|
- Confirmation requires no signature or OTP.
|
||||||
- `OrderItem` lines are permanently locked after confirmation.
|
- `OrderItem` lines are permanently locked after confirmation.
|
||||||
- `Order.Status` (fulfillment tracking) is untouched — separate axis.
|
- `Order.Status` (fulfillment tracking) is untouched — separate axis.
|
||||||
|
- A Confirmed PO can be cancelled even after confirmation (`Order.Status → Cancelled`). If an
|
||||||
|
Invoice was already created, cascade behaviour (void Invoice, impact on loading sheet items) is
|
||||||
|
unresolved — see Open Questions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -150,8 +156,13 @@ enters any edits the agent made into the system and submits.
|
|||||||
|
|
||||||
1. **Who can confirm a Shop PO?** — the creating sales agent, a supervisor, or either?
|
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.
|
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.
|
3. **Cancellation cascade** — both Shop PO and Confirmed PO can be cancelled. If cancelling a
|
||||||
4. **Exact adjustment payload for `POST /complete`** — the fields the office worker can edit
|
Confirmed PO that already has a linked Invoice: should the Invoice be voided automatically?
|
||||||
(returns, damage, discounts) are assumed but not fully specified.
|
If the PO's items were already pulled into a loading sheet: what happens to those loading sheet
|
||||||
5. **Does Stage 4 (loading sheet) depend on Stage 3 (invoice printed), or can they happen in
|
items? Cascade rules needed before implementation.
|
||||||
parallel?** — not decided here.
|
4. **Exact adjustment payload for `POST /api/invoices/{id}/finalize`** — the fields the office
|
||||||
|
worker can edit (returns, damage, discounts) are assumed but not fully specified. "Free issues"
|
||||||
|
handling (items given to the shop at no charge) is a known case needing its own payload field(s).
|
||||||
|
5. **Stage 3 and Stage 4 ordering** — Invoice printing (Stage 3) and loading sheet population
|
||||||
|
(Stage 4) are independent flows. They are not strictly sequential — both depend on a Confirmed
|
||||||
|
PO existing but neither requires the other to complete first.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Replace the previous loading event design with a dispatcher-driven, multi-PO mod
|
|||||||
1. **No more auto-creation.** Loading sheets are no longer generated automatically when a PO is
|
1. **No more auto-creation.** Loading sheets are no longer generated automatically when a PO is
|
||||||
confirmed. A dispatcher manually triggers population.
|
confirmed. A dispatcher manually triggers population.
|
||||||
2. **No more `Open → Active`.** `Ride.Status` collapses to `Open`/`Cancelled`/`Closed` — no
|
2. **No more `Open → Active`.** `Ride.Status` collapses to `Open`/`Cancelled`/`Closed` — no
|
||||||
`Active` stage. Loading sheet confirmation no longer changes `Ride.Status` at all.
|
`Active` stage. Loading sheet confirmation no longer changes `Ride.Status`.
|
||||||
3. **One sheet aggregates multiple POs.** A single loading sheet is created from all Confirmed POs
|
3. **One sheet aggregates multiple POs.** A single loading sheet is created from all Confirmed POs
|
||||||
matching a dispatcher's filter (route/agent/shop). No longer one sheet per PO.
|
matching a dispatcher's filter (route/agent/shop). No longer one sheet per PO.
|
||||||
4. **Ride set at populate time.** The ride is provided when the dispatcher triggers population —
|
4. **Ride set at populate time.** The ride is provided when the dispatcher triggers population —
|
||||||
@@ -22,15 +22,17 @@ Replace the previous loading event design with a dispatcher-driven, multi-PO mod
|
|||||||
loaded from different warehouses. Stock movements are generated per item's warehouse.
|
loaded from different warehouses. Stock movements are generated per item's warehouse.
|
||||||
6. **Item versioning.** Loading sheet items are versioned the same way as Shop PO items — every
|
6. **Item versioning.** Loading sheet items are versioned the same way as Shop PO items — every
|
||||||
edit soft-deletes current rows (`IsActive = false`) and writes a fresh set.
|
edit soft-deletes current rows (`IsActive = false`) and writes a fresh set.
|
||||||
|
7. **Stock moves at confirmation.** When the dispatcher confirms the sheet, stock moves from each
|
||||||
|
item's warehouse to the vehicle. No driver receipt confirmation step.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Background / Context
|
## Background / Context
|
||||||
|
|
||||||
Same physical mechanics as `04_loading_event.md`: a vehicle is a location with
|
Same physical mechanics as `04_loading_event.md`: a vehicle is a location with
|
||||||
`LocationType.Vehicle` in the stock ledger. Confirming receipt (or resolving a discrepancy)
|
`LocationType.Vehicle` in the stock ledger. Confirming the loading sheet creates stock movements —
|
||||||
creates stock movements — debit source warehouse(s), credit vehicle. What changes is *when and how
|
debit source warehouse(s), credit vehicle. What changes is *when and how a `LoadingSheet` comes
|
||||||
a `LoadingSheet` comes into existence*, *which POs it covers*, and *how warehouse selection works*.
|
into existence*, *which POs it covers*, and *how warehouse selection works*.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,18 +45,12 @@ a `LoadingSheet` comes into existence*, *which POs it covers*, and *how warehous
|
|||||||
| Id | Guid | PK |
|
| Id | Guid | PK |
|
||||||
| ~~`FK_OrderId`~~ | — | **Removed.** One sheet now aggregates multiple POs — replaced by `LoadingSheetOrder` linking table. |
|
| ~~`FK_OrderId`~~ | — | **Removed.** One sheet now aggregates multiple POs — replaced by `LoadingSheetOrder` linking table. |
|
||||||
| **`FK_RideId`** | Guid | **Required. Set at population time** — dispatcher provides the ride when triggering populate. |
|
| **`FK_RideId`** | Guid | **Required. Set at population time** — dispatcher provides the ride when triggering populate. |
|
||||||
| ~~`FK_SourceWarehouseId`~~ | — | **Removed.** Warehouse is tracked per item, not per sheet — a single sheet can be fulfilled from multiple warehouses. |
|
| ~~`FK_SourceWarehouseId`~~ | — | **Removed.** Warehouse is tracked per item, not per sheet. |
|
||||||
| Status | LoadingSheetStatus | See state machine below. |
|
| Status | LoadingSheetStatus | `Planned` or `Confirmed`. |
|
||||||
| **`PopulatedBy`** | Guid | **New.** UserId of dispatcher who triggered population. |
|
| **`PopulatedBy`** | Guid | **New.** UserId of dispatcher who triggered population. |
|
||||||
| **`PopulatedAt`** | DateTime | **New.** When population was triggered. |
|
| **`PopulatedAt`** | DateTime | **New.** When population was triggered. |
|
||||||
| ~~`AssignedAt`~~ | — | **Removed.** No separate assign step. |
|
| ConfirmedAt | DateTime? | Set when loading sheet is confirmed (items locked, stock moves). |
|
||||||
| ~~`AssignedBy`~~ | — | **Removed.** Same reason. |
|
| ConfirmedBy | Guid? | UserId of whoever confirmed the sheet. TBD — see Open Questions. |
|
||||||
| ConfirmedAt | DateTime? | Set when loading sheet is confirmed (items locked). |
|
|
||||||
| ConfirmedBy | Guid? | UserId of whoever confirmed the sheet. |
|
|
||||||
| DispatchConfirmedAt | DateTime? | Path B only — see Open Questions. |
|
|
||||||
| DispatchConfirmedBy | Guid? | Path B only. |
|
|
||||||
| ReceiptConfirmedAt | DateTime? | Path B only. |
|
|
||||||
| ReceiptConfirmedBy | Guid? | Path B only. |
|
|
||||||
| CreatedAt | DateTime | From Template. |
|
| CreatedAt | DateTime | From Template. |
|
||||||
|
|
||||||
### New Entity — `LoadingSheetOrder` (linking table)
|
### New Entity — `LoadingSheetOrder` (linking table)
|
||||||
@@ -76,8 +72,6 @@ One loading sheet aggregates multiple Confirmed POs. This table records which PO
|
|||||||
| **`FK_OrderItemId`** | Guid? | **New.** Traces back to the source `OrderItem` from the Confirmed PO. Null for manually added lines. |
|
| **`FK_OrderItemId`** | Guid? | **New.** Traces back to the source `OrderItem` from the Confirmed PO. Null for manually added lines. |
|
||||||
| FK_ItemId | Guid | FK → Item (product) |
|
| FK_ItemId | Guid | FK → Item (product) |
|
||||||
| QuantityDispatched | decimal | Quantity to load from `FK_WarehouseId` |
|
| QuantityDispatched | decimal | Quantity to load from `FK_WarehouseId` |
|
||||||
| QuantityReceived | decimal? | Driver's received qty (Path B only) |
|
|
||||||
| QuantityResolved | decimal? | Supervisor's resolved qty after discrepancy (Path B only) |
|
|
||||||
| **`IsActive`** | bool | **Versioning.** Every edit soft-deletes current items (`IsActive = false`) and writes a fresh set. Active items = latest version; soft-deleted = history. |
|
| **`IsActive`** | bool | **Versioning.** Every edit soft-deletes current items (`IsActive = false`) and writes a fresh set. Active items = latest version; soft-deleted = history. |
|
||||||
| CreatedAt | DateTime | From Template |
|
| CreatedAt | DateTime | From Template |
|
||||||
|
|
||||||
@@ -86,36 +80,30 @@ One loading sheet aggregates multiple Confirmed POs. This table records which PO
|
|||||||
```csharp
|
```csharp
|
||||||
public enum LoadingSheetStatus
|
public enum LoadingSheetStatus
|
||||||
{
|
{
|
||||||
Planned, // populated by dispatcher (ride set, items editable/versioned)
|
Planned, // populated by dispatcher — ride set, items editable and versioned
|
||||||
Confirmed, // items locked — stock movement path TBD (see Open Questions)
|
Confirmed // items locked, stock movements created
|
||||||
DispatchConfirmed, // Path B only: warehouse confirms dispatch
|
|
||||||
ReceiptConfirmed, // Path B only: driver confirms receipt, stock moves
|
|
||||||
DiscrepancyFlagged, // Path B only: received qty mismatch
|
|
||||||
Resolved // Path B only: supervisor resolves discrepancy, stock moves
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Pending` from `04_loading_event.md` is replaced by `Planned` and `Confirmed`. `Assigned` is
|
`Pending`, `Assigned`, `DispatchConfirmed`, `ReceiptConfirmed`, `DiscrepancyFlagged`, and
|
||||||
removed — ride is set at populate time, no separate assign step.
|
`Resolved` from `04_loading_event.md` are all removed. Stock moves at `Confirmed` — no driver
|
||||||
|
receipt confirmation step in this flow.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## State Machine
|
## State Machine
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Planned -> Confirmed -> [Path A: stock moves here]
|
Planned → Confirmed (stock moves)
|
||||||
\-> [Path B: DispatchConfirmed -> ReceiptConfirmed]
|
|
||||||
\-> DiscrepancyFlagged -> Resolved
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| Transition | Trigger | What it does |
|
| Transition | Trigger | What it does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| (create) → Planned | `POST /api/loading-sheets/populate` | Dispatcher provides `rideId` + filters. System finds all matching Confirmed POs, creates one `LoadingSheet` with `FK_RideId` set, populates `LoadingSheetItem` rows from active `OrderItem` lines, records PO links in `LoadingSheetOrder`. `PopulatedBy`/`PopulatedAt` set. Items editable (versioned via `IsActive`). Dispatcher sets `FK_WarehouseId` per item. |
|
| (create) → Planned | `POST /api/loading-sheets/populate` | Dispatcher provides `rideId` + filters. System finds all matching Confirmed POs, creates one `LoadingSheet` with `FK_RideId` set, populates `LoadingSheetItem` rows from active `OrderItem` lines, records PO links in `LoadingSheetOrder`. `PopulatedBy`/`PopulatedAt` set. Items editable (versioned via `IsActive`). Dispatcher sets `FK_WarehouseId` per item. |
|
||||||
| Planned → Confirmed | `POST /{id}/confirm` | Locks the sheet — `LoadingSheetItem` rows frozen. Sets `ConfirmedAt`/`ConfirmedBy`. Stock movement path TBD (see Open Questions). |
|
| Planned → Confirmed | `POST /{id}/confirm` | Locks the sheet — `LoadingSheetItem` rows frozen. Stock movements created: one debit per `LoadingSheetItem.FK_WarehouseId`, one credit to the ride's vehicle. Sets `ConfirmedAt`/`ConfirmedBy`. |
|
||||||
| Confirmed → DispatchConfirmed | `POST /{id}/confirm-dispatch` | **Path B only.** Warehouse manager confirms dispatch quantities. |
|
|
||||||
| DispatchConfirmed → ReceiptConfirmed | `POST /{id}/confirm-receipt` | **Path B only.** Driver's received quantities all match. Stock movements created — one debit per `LoadingSheetItem.FK_WarehouseId`, one credit to the ride's vehicle. No ride status transition. |
|
A `Planned` sheet stays as `Planned` indefinitely until the dispatcher confirms it — there is no
|
||||||
| DispatchConfirmed → DiscrepancyFlagged | `POST /{id}/confirm-receipt` | **Path B only.** Any line mismatch. No stock movements yet. |
|
timeout or auto-cancellation.
|
||||||
| DiscrepancyFlagged → Resolved | `POST /{id}/resolve` | **Path B only.** Supervisor enters `QuantityResolved` per line. Stock movements created using resolved quantities. |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -145,18 +133,14 @@ and writes a fresh set. Latest version = `IsActive = true` rows.
|
|||||||
|
|
||||||
## Stock Movements
|
## Stock Movements
|
||||||
|
|
||||||
One stock movement pair per `LoadingSheetItem` (active rows only). Created exactly once.
|
Created at `Confirmed`. One pair per active `LoadingSheetItem`:
|
||||||
|
|
||||||
- **Debit**: `LoadingSheetItem.FK_WarehouseId` (the warehouse that item loads from)
|
- **Debit**: `LoadingSheetItem.FK_WarehouseId` (the warehouse that item loads from)
|
||||||
- **Credit**: the vehicle on the assigned ride (`LoadingSheet.FK_RideId → DeliveryRun.FK_VehicleId`)
|
- **Credit**: vehicle on the assigned ride (`LoadingSheet.FK_RideId → DeliveryRun.FK_VehicleId`)
|
||||||
- **Type**: `Load`
|
- **Type**: `Load`
|
||||||
|
|
||||||
Since different items can have different `FK_WarehouseId` values, one loading sheet can produce
|
Since different items can have different `FK_WarehouseId` values, one loading sheet can produce
|
||||||
stock movements from multiple warehouses into one vehicle.
|
stock movements from multiple warehouses into one vehicle in a single confirmation action.
|
||||||
|
|
||||||
**When** movements are created depends on the path chosen (see Open Questions):
|
|
||||||
- **Path A** — stock moves at `Confirmed`
|
|
||||||
- **Path B** — stock moves at `ReceiptConfirmed` or `Resolved`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -166,15 +150,11 @@ Base route: `/api/loading-sheets`
|
|||||||
|
|
||||||
| Method | Route | Who | Description |
|
| Method | Route | Who | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| POST | `/populate` | Dispatcher | Trigger population: `{ rideId, routeId?, agentId?, shopId? }` — `rideId` required, at least one filter required. Creates one `LoadingSheet` at `Planned` with `FK_RideId` set. |
|
| POST | `/populate` | Dispatcher | `{ rideId, routeId?, agentId?, shopId? }` — `rideId` required, at least one filter required. Creates one `LoadingSheet` at `Planned` with `FK_RideId` set. |
|
||||||
| GET | `/` | Any | List sheets. Filter by `routeId`, `agentId`, `shopId`, `rideId`, `status`. Paginated. `routeId` resolves via `Store.FK_RouteId → Order.FK_StoreId → LoadingSheetOrder.FK_OrderId` server-side. |
|
| GET | `/` | Any | List sheets. Filter by `routeId`, `agentId`, `shopId`, `rideId`, `status`. Paginated. `routeId` resolves via `Store.FK_RouteId → Order.FK_StoreId → LoadingSheetOrder.FK_OrderId` server-side. |
|
||||||
| GET | `/{id}` | Any | Get sheet detail. Active items only by default; `?history=true` includes soft-deleted versions. |
|
| GET | `/{id}` | Any | Get sheet detail. Active items only by default; `?history=true` includes soft-deleted versions. |
|
||||||
| PUT | `/{id}/items` | Dispatcher | Rewrite items while `Planned`. Each item must include `FK_WarehouseId`. Soft-deletes current rows (`IsActive = false`) and writes a fresh set. |
|
| PUT | `/{id}/items` | Dispatcher | Rewrite items while `Planned`. Each item must include `FK_WarehouseId`. Soft-deletes current rows (`IsActive = false`) and writes a fresh set. |
|
||||||
| POST | `/{id}/confirm` | Dispatcher | Lock the sheet. `Planned → Confirmed`. Items frozen. |
|
| POST | `/{id}/confirm` | TBD | Lock the sheet. `Planned → Confirmed`. Items frozen. Stock movements created. |
|
||||||
| POST | `/{id}/confirm-dispatch` | Warehouse mgr | **Path B only.** `Confirmed → DispatchConfirmed`. |
|
|
||||||
| PUT | `/{id}/receipt-items` | Driver | **Path B only.** Set `QuantityReceived` per line. |
|
|
||||||
| POST | `/{id}/confirm-receipt` | Driver | **Path B only.** `→ ReceiptConfirmed` or `DiscrepancyFlagged`. |
|
|
||||||
| POST | `/{id}/resolve` | Supervisor | **Path B only.** Enter `QuantityResolved` per line `→ Resolved`. |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -192,11 +172,12 @@ Base route: `/api/loading-sheets`
|
|||||||
8. Items are editable (versioned via `IsActive`) while `Planned` — frozen at `Confirmed`.
|
8. Items are editable (versioned via `IsActive`) while `Planned` — frozen at `Confirmed`.
|
||||||
9. Every item edit soft-deletes all current `LoadingSheetItem` rows (`IsActive = false`) and writes
|
9. Every item edit soft-deletes all current `LoadingSheetItem` rows (`IsActive = false`) and writes
|
||||||
a fresh set. Active rows = latest version; soft-deleted = history.
|
a fresh set. Active rows = latest version; soft-deleted = history.
|
||||||
10. Stock movements are created exactly once — one debit per item warehouse, one credit to the
|
10. Stock movements are created at `Confirmed` — one debit per item's warehouse, one credit to the
|
||||||
vehicle. Timing depends on path chosen (see Open Questions).
|
vehicle — all in one transaction.
|
||||||
11. No `Ride.Status` transition is triggered at any point in this flow.
|
11. A `Planned` sheet stays `Planned` until confirmed — no timeout or auto-cancellation.
|
||||||
12. All list endpoints return paginated responses.
|
12. No `Ride.Status` transition is triggered at any point in this flow.
|
||||||
13. Vehicle capacity check is not enforced in this step.
|
13. All list endpoints return paginated responses.
|
||||||
|
14. Vehicle capacity check is not enforced in this step.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -205,50 +186,49 @@ Base route: `/api/loading-sheets`
|
|||||||
| Code | Who |
|
| Code | Who |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `loading.view` | All roles |
|
| `loading.view` | All roles |
|
||||||
| **`loading.populate`** | **New.** Dispatcher — trigger population, edit items, confirm sheet |
|
| **`loading.populate`** | **New.** Dispatcher — trigger population, edit items |
|
||||||
| `loading.dispatch` | Warehouse manager — Path B dispatch confirmation |
|
| **`loading.confirm`** | **New.** TBD — who can confirm the sheet (see Open Questions) |
|
||||||
| `loading.receive` | Driver — Path B receipt confirmation |
|
|
||||||
| `loading.resolve` | Supervisor — Path B discrepancy resolution |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fraud / Control Considerations
|
## Fraud / Control Considerations
|
||||||
|
|
||||||
- Discrepancies are immediately visible and block further progress.
|
|
||||||
- `QuantityResolved` is permanently stored per line for audit purposes.
|
|
||||||
- `PopulatedBy`/`PopulatedAt` gives an audit trail for who triggered population and when.
|
- `PopulatedBy`/`PopulatedAt` gives an audit trail for who triggered population and when.
|
||||||
|
- `ConfirmedBy`/`ConfirmedAt` records who committed stock movements.
|
||||||
- `FK_OrderItemId` on each item traces the exact source PO line for full audit.
|
- `FK_OrderItemId` on each item traces the exact source PO line for full audit.
|
||||||
|
- `IsActive = false` rows preserve the full edit history of the sheet before confirmation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
1. **Path A vs Path B — when do stock movements happen?** Stock can move at `Confirmed` (Path A)
|
1. **Who confirms the loading sheet?** — dispatcher, warehouse manager, or either? TBD. Controls
|
||||||
or at `ReceiptConfirmed`/`Resolved` (Path B). Both paths retained until decided.
|
the `loading.confirm` permission assignment.
|
||||||
2. **Who confirms the loading sheet?** — the dispatcher who populated it, the warehouse manager,
|
2. **What happens to a `Planned` sheet if its source PO is cancelled?** — if a Confirmed PO is
|
||||||
or either?
|
cancelled after a loading sheet has been populated from it, the sheet's items sourced from that
|
||||||
3. **What happens to a `Planned` sheet if it is never confirmed?** — no cancellation or timeout
|
PO become invalid. No cascade behaviour designed here.
|
||||||
path is designed here.
|
3. Vehicle capacity check — still deferred.
|
||||||
4. Vehicle capacity check — still deferred.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Migration Notes (for when this is implemented)
|
## Migration Notes (for when this is implemented)
|
||||||
|
|
||||||
- Remove `FK_OrderId` and `FK_SourceWarehouseId` from `Models/LoadingSheet.cs`. Make `FK_RideId`
|
- Remove `FK_OrderId`, `FK_SourceWarehouseId`, `AssignedAt`, `AssignedBy`,
|
||||||
required (non-nullable). Remove `AssignedAt`/`AssignedBy`. Add `PopulatedBy`, `PopulatedAt`,
|
`DispatchConfirmedAt`, `DispatchConfirmedBy`, `ReceiptConfirmedAt`, `ReceiptConfirmedBy` from
|
||||||
|
`Models/LoadingSheet.cs`. Make `FK_RideId` required. Add `PopulatedBy`, `PopulatedAt`,
|
||||||
`ConfirmedAt`, `ConfirmedBy`.
|
`ConfirmedAt`, `ConfirmedBy`.
|
||||||
- Add `Models/LoadingSheetOrder.cs` (`FK_LoadingSheetId`, `FK_OrderId`) with composite PK.
|
- Add `Models/LoadingSheetOrder.cs` (`FK_LoadingSheetId`, `FK_OrderId`) with composite PK.
|
||||||
- Add `FK_WarehouseId` (required), `FK_OrderItemId` (nullable), `IsActive` to
|
- Add `FK_WarehouseId` (required), `FK_OrderItemId` (nullable), `IsActive` to
|
||||||
`Models/LoadingSheetItem.cs`.
|
`Models/LoadingSheetItem.cs`. Remove `QuantityReceived`, `QuantityResolved`.
|
||||||
- Replace `LoadingSheetStatus.Pending` and `Assigned` with `Planned` and `Confirmed`; update all
|
- Replace `LoadingSheetStatus` entirely with `Planned` and `Confirmed`; remove all other values
|
||||||
status checks in `LoadingSheetService`/`LoadingSheetRepository`.
|
and update all status checks in `LoadingSheetService`/`LoadingSheetRepository`.
|
||||||
- Remove `POST /api/loading-sheets` (old manual create) and `POST /{id}/assign-ride`; add
|
- Remove `POST /api/loading-sheets` (old manual create), `POST /{id}/assign-ride`,
|
||||||
`POST /api/loading-sheets/populate` and `POST /{id}/confirm`.
|
`POST /{id}/confirm-dispatch`, `PUT /{id}/receipt-items`, `POST /{id}/confirm-receipt`,
|
||||||
|
`POST /{id}/resolve`; add `POST /api/loading-sheets/populate` and `POST /{id}/confirm`.
|
||||||
|
- Wire stock movements into `POST /{id}/confirm` — one debit per `LoadingSheetItem.FK_WarehouseId`,
|
||||||
|
one credit to the ride's vehicle, in a single transaction.
|
||||||
- Remove ride-route-based item population (`GetAggregatedOrderItemsByRouteIdAsync`); replace with
|
- Remove ride-route-based item population (`GetAggregatedOrderItemsByRouteIdAsync`); replace with
|
||||||
filter-based multi-PO aggregation in `LoadingSheetService`.
|
filter-based multi-PO aggregation.
|
||||||
- Remove all calls to ride-activation logic from `confirm-receipt`/`resolve` — no `Ride.Status`
|
- Remove all calls to ride-activation logic — no `Ride.Status` transition exists anymore.
|
||||||
transition exists anymore.
|
- Seed `loading.populate` and `loading.confirm` permissions.
|
||||||
- Seed the new `loading.populate` permission.
|
- Resolve Open Question 1 (who confirms) before assigning `loading.confirm` permission.
|
||||||
- Resolve Open Question 1 (stock movement timing) before implementing `POST /{id}/confirm` and
|
|
||||||
`POST /{id}/confirm-receipt` stock movement logic.
|
|
||||||
|
|||||||
@@ -168,6 +168,11 @@ Draft → Printed → Finalized
|
|||||||
10. `Order.FK_SaleId` is optional — a PO can exist and be confirmed before any `Sale` is recorded.
|
10. `Order.FK_SaleId` is optional — a PO can exist and be confirmed before any `Sale` is recorded.
|
||||||
11. Loading sheet population is a separate dispatcher-triggered flow — not triggered by PO
|
11. Loading sheet population is a separate dispatcher-triggered flow — not triggered by PO
|
||||||
confirmation or invoice creation. See [dispatch_pipeline.md](./dispatch_pipeline.md).
|
confirmation or invoice creation. See [dispatch_pipeline.md](./dispatch_pipeline.md).
|
||||||
|
12. **A Shop PO (pre-confirmation) can be cancelled** — `Order.Status → Cancelled`. No Invoice
|
||||||
|
exists at this stage so no cascade required.
|
||||||
|
13. **A Confirmed PO can also be cancelled** even after confirmation — `Order.Status → Cancelled`.
|
||||||
|
If an Invoice was already created for this PO, cascade behaviour (void/cancel the Invoice) and
|
||||||
|
behaviour for any loading sheet that included this PO are open questions — see Open Questions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -191,9 +196,14 @@ Draft → Printed → Finalized
|
|||||||
2. ~~**One Invoice per PO or multiple?**~~ — **Resolved:** 1:1 for v1.
|
2. ~~**One Invoice per PO or multiple?**~~ — **Resolved:** 1:1 for v1.
|
||||||
3. **Should `Order.Status` and `Invoice.Status` eventually merge?** — kept separate; not decided.
|
3. **Should `Order.Status` and `Invoice.Status` eventually merge?** — kept separate; not decided.
|
||||||
4. **How does `Payment` attach to `Invoice`?** — see Impact item 2. Not designed here.
|
4. **How does `Payment` attach to `Invoice`?** — see Impact item 2. Not designed here.
|
||||||
5. **Cancellation/void path for a Shop PO, Confirmed PO, or Invoice?** — not addressed here.
|
5. **Cancellation cascade — Invoice and LoadingSheet** — a Shop PO can be cancelled (no Invoice
|
||||||
|
exists yet, no cascade). A Confirmed PO can be cancelled even after confirmation. If the PO
|
||||||
|
already has a linked Invoice, what happens to that Invoice (voided? stays Draft?) is unresolved.
|
||||||
|
If the PO's items were pulled into a loading sheet, what happens to those loading sheet items is
|
||||||
|
also unresolved. Both cases need explicit cascade rules before implementation.
|
||||||
6. **Exact adjustment payload for `POST /finalize`** — quantity adjustments, returns, damage, and
|
6. **Exact adjustment payload for `POST /finalize`** — quantity adjustments, returns, damage, and
|
||||||
discounts are assumed but the payload shape is not fully specified here.
|
discounts are assumed but the payload shape is not fully specified. "Free issues" handling (items
|
||||||
|
given to the shop at no charge) is a known case that needs its own field(s) in the payload.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -219,3 +229,5 @@ Draft → Printed → Finalized
|
|||||||
`FinalizedAt`/`FinalizedBy`. Resolve Open Question 6 before specifying the exact payload shape.
|
`FinalizedAt`/`FinalizedBy`. Resolve Open Question 6 before specifying the exact payload shape.
|
||||||
- `LoadingSheet` population is a separate flow — see [dispatch_pipeline.md](./dispatch_pipeline.md).
|
- `LoadingSheet` population is a separate flow — see [dispatch_pipeline.md](./dispatch_pipeline.md).
|
||||||
- A new PO-creation path independent of `SaleService.CreateAsync` is needed — not specified here.
|
- A new PO-creation path independent of `SaleService.CreateAsync` is needed — not specified here.
|
||||||
|
- Add `POST /api/orders/{id}/cancel` — cancels a Shop PO or Confirmed PO. Resolve Open Question 5
|
||||||
|
(cascade to Invoice and LoadingSheet) before implementing for the Confirmed PO case.
|
||||||
|
|||||||
Reference in New Issue
Block a user