From 2000ba67f4d0c0fda7803238e1f75fcfe700a0a7 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 10 Sep 2026 15:53:57 +0530 Subject: [PATCH] Add refactor order_invoice_lifecycle.md file --- .../refactor_docs/order_invoice_lifecycle.md | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 Distribution/docs/refactor_docs/order_invoice_lifecycle.md diff --git a/Distribution/docs/refactor_docs/order_invoice_lifecycle.md b/Distribution/docs/refactor_docs/order_invoice_lifecycle.md new file mode 100644 index 0000000..79284c3 --- /dev/null +++ b/Distribution/docs/refactor_docs/order_invoice_lifecycle.md @@ -0,0 +1,196 @@ +# [Refactor] Order/Invoice Lifecycle — Unified PO → Invoice Pipeline + +**Status**: Proposed (design only — no code changes yet) +**Refactors**: [../05_delivery_ride.md](../05_delivery_ride.md) and +[../06_sale_ride.md](../06_sale_ride.md) (both kept as-is, this is the target design) +**Related but independent**: [ride_lifecycle.md](./ride_lifecycle.md) — that refactor removes +`RideType`/`Route`/`Status` from `Ride`; this document does not attempt to resolve its open +questions, and this document's design does not depend on it landing first. + +--- + +## Goal + +Replace the Delivery-vs-Sale ride-type split as the organizing concept for how a store's order gets +turned into money. Today `05_delivery_ride.md` (rep takes an order now, delivers days later) and +`06_sale_ride.md` (rep+driver visit together and sell on the spot) are two separate flows. Underneath, +both are the same shape: a rep collects a **PO** from the shop — whether fulfilled immediately or +days later — and once the PO exists, an **Invoice** is generated from it. That invoice always moves +through the same three statuses regardless of which scenario produced it: + +1. **Pending** — invoice just created from the PO; the PO's line items are freely editable. +2. **Draft** — the customer signs once the order is confirmed, moving it out of Pending. The PO's + line items **remain editable** in this state too — Draft is a working document, not a lock. +3. **Finalize** — the reconciliation step: item add/delete and amount deductions (returns, damage, + short-delivery, discounts) get applied here, producing the final, locked invoice amount. This is + the only state where free-form editing stops. + +The two ride scenarios differ only in *timing* through this one pipeline, not in structure — no +ride-type-specific branching is needed in the PO/Invoice model itself. + +--- + +## Data Model Changes + +### Reshaped Entity — `Order` (business term: "PO") + +The existing `Order` entity ([Order.cs](../../Models)) is reshaped into this role, keeping its class +name as `Order` in code. + +> **Naming note**: `Distribution/Models/PurchaseOrder.cs` and `Distribution/Features/PurchaseOrder/` +> already exist in the codebase — that's the *supplier-facing* procurement flow (the distributor +> buying from its own suppliers), completely unrelated to this store-facing PO. To avoid confusion, +> this document deliberately keeps the class name `Order`, using "PO" only as the business-facing +> term for what the store ordered. Do **not** name the reshaped entity/class `PurchaseOrder`. + +``` +Order ("PO") +├── Id (Guid) — PK, from Template +├── FK_StoreId (Guid) — required, unchanged +├── FK_SaleId (Guid?) — now OPTIONAL (was required). See Impact section — a PO can now +│ exist before any Sale/payment event completes. +├── TotalAmount (decimal) — unchanged, though only authoritative once the linked Invoice is +│ Finalized (see below) +├── Status (OrderStatus) — unchanged: Pending, PartiallyFulfilled, Fulfilled, Cancelled. +│ This tracks fulfillment progress, a separate axis from the new +│ InvoiceStatus below — not merged in this pass. +├── FulfilledAt (DateTime?) — unchanged +├── IsActive, CreatedAt — from Template +``` + +`OrderItem` (the PO's line items) is unchanged in shape (`FK_ItemId`, `Quantity`, +`QuantityFulfilled`, `UnitPrice`) — editability is now gated by the linked `Invoice`'s status (see +below), not by `Order.Status` directly. + +### New Entity — `Invoice` + +``` +Invoice +├── Id (Guid) — PK, from Template +├── FK_OrderId (Guid) — required. One PO → one Invoice for v1 (see Open Questions). +├── Status (InvoiceStatus) — Pending, Draft, Finalized +├── SignedAt (DateTime?) — set on Pending → Draft +├── FinalizedAt (DateTime?) — set on Draft → Finalized +├── FinalizedBy (Guid?) — UserId of whoever finalized it +├── TotalAmount (decimal) — mirrors the PO total while Pending/Draft; becomes the +│ authoritative, locked amount once Finalized +├── IsActive, CreatedAt — from Template +``` + +### New Enum — `InvoiceStatus` + +```csharp +public enum InvoiceStatus { Pending, Draft, Finalized } +``` + +An `Invoice` is created automatically the moment a PO (`Order`) is created, starting at `Pending` — +there is no separate "create invoice" action. + +--- + +## State Machine + +``` +Pending → Draft → Finalized +``` + +No branch/cancel path is designed in this pass (unlike the Ride state machine) — cancellation of a +PO/Invoice is not addressed here; flagged as an open question below. + +| Transition | Trigger | What it does | +|---|---|---| +| (implicit) → Pending | PO (`Order`) created | Invoice is created automatically, `Status = Pending`. `OrderItem` lines fully editable. | +| Pending → Draft | `POST /api/invoices/{id}/draft` | Captures customer sign-off. Sets `SignedAt`. **`OrderItem` lines remain editable** — Draft is not a lock. Signature/OTP mechanism itself is an open question (see below), matching `05_delivery_ride.md`'s own prior deferral on customer confirmation. | +| Draft → Finalized | `POST /api/invoices/{id}/finalize` | Accepts an item-adjustment/deduction payload (add/delete lines, amount deductions for returns/damage/short-delivery/discounts), recomputes and locks `TotalAmount`. This is a distinct, audited action — not the same add/edit/remove path used in Pending/Draft. Once Finalized, the Invoice and its PO's items are fully locked. | + +--- + +## Business Rules + +1. `OrderItem` add/edit/remove is allowed while the linked `Invoice.Status` is `Pending` **or** + `Draft` — both are working-document states. +2. An `Invoice` cannot be `Finalized` without first passing through `Draft` — `Pending → Finalized` + directly is not a valid transition. +3. `Order.TotalAmount`/`Invoice.TotalAmount` is only authoritative once `Finalized`; while + Pending/Draft it reflects the current (still-changeable) line items. +4. `Order.Status` (fulfillment tracking: Pending/PartiallyFulfilled/Fulfilled/Cancelled) is + untouched and operates on a separate axis from `Invoice.Status` (billing/document lifecycle) — + whether these two should eventually merge is an open question, not resolved here. +5. `Order.FK_SaleId` is now optional — a PO/Invoice can exist and progress through Pending/Draft + before any `Sale` is recorded, covering the take-order-now/deliver-later scenario. See Impact + section for what this touches. +6. Both ride scenarios (take-order-now-deliver-later vs. on-the-spot sale) create a PO the same way + and pass through the same three Invoice statuses — the only difference is how quickly they move + through Pending → Draft → Finalized. No `RideType`-specific branching applies to the PO/Invoice + model. + +--- + +## Impact on Dependent Features + +Reshaping `Order` and introducing `Invoice` touches several existing behaviors. Each is flagged here +as **known-affected, deferred follow-up work**, not solved in this pass: + +1. **`SaleService.CreateAsync`** — currently the only path that creates an `Order`, and it always + sets a non-null `FK_SaleId`. With `FK_SaleId` now optional, a new PO-creation path (not + necessarily originating from a `Sale`) needs to exist for the take-order-now/deliver-later + scenario. Any other code that assumes `Order.Sale` is always non-null also needs auditing — + not enumerated exhaustively here. +2. **`Payment` attachment** — per `06_sale_ride.md`, `Payment` currently attaches to `Sale`. Once + `Invoice` exists as the real billing document, `Payment` more naturally attaches to the `Invoice` + instead. Flagged as a design question for [19_finance_accounting.md](../19_finance_accounting.md) + to reconcile — not decided here. +3. **`19_finance_accounting.md`'s proposed Invoice status** (`Issued, PartiallyPaid, Paid`) needs + reconciling with this document's `Pending/Draft/Finalized`. These are likely two different axes + on the same `Invoice` — document lifecycle status (this doc) vs. payment-collection status (the + finance doc) — but that reconciliation is not attempted here. +4. **`DeliveryTask.CustomerConfirmed`** (boolean, from `05_delivery_ride.md`) overlaps conceptually + with the new Pending → Draft transition (both represent "the customer confirmed this"). Whether + they merge into one confirmation event, or stay deliberately separate (delivery confirmation vs. + invoice sign-off, which could happen at different times), is not decided here. +5. **`05_delivery_ride.md` and `06_sale_ride.md`** both go stale the moment this design ships and + need their own rewrite/retirement — tracked as follow-up, not attempted in this pass. + +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. **Signature/OTP mechanism for the Pending → Draft transition** — same deferral as + `05_delivery_ride.md`'s original "signature (image) or OTP (SMS code) — one, or both, in v1?" + question. Not resolved here. +2. **One Invoice per PO, or multiple?** — this document assumes a 1:1 `Order`→`Invoice` relationship + for v1. Whether a PO could ever need multiple invoices (e.g. split billing) is not designed here. +3. **Should `Order.Status` and `Invoice.Status` eventually merge into one status axis?** — kept + deliberately separate in this pass (fulfillment progress vs. document/billing lifecycle); not + decided whether that separation should be permanent. +4. **How does `Payment` attach once `Invoice` exists?** — see Impact item 2. Not designed here. +5. **Is there a cancellation/void path for a PO or Invoice?** — the state machine in this document + has no cancel branch (unlike the Ride state machine's `CancelRequested`/`Cancelled`). Not + addressed here. +6. **Does `DeliveryTask.CustomerConfirmed` merge with the Draft transition?** — see Impact item 4. + +--- + +## Migration Notes (for when this is implemented) + +Not part of this docs-only pass, but captured for the eventual implementation step: + +- Make `Order.FK_SaleId` nullable in `Models/Order.cs` and its EF/Fluent configuration; audit all + code paths that currently assume it's non-null. +- Add `Models/Invoice.cs` (`FK_OrderId`, `Status`, `SignedAt`, `FinalizedAt`, `FinalizedBy`, + `TotalAmount`, plus `Template` base fields) and the `InvoiceStatus` enum. +- New `Features/Invoice/` folder (`InvoiceController`, `InvoiceService`, `IInvoiceRepository`, + `InvoiceRepository`, `InvoiceDTOs`) — do not place this under `Features/PurchaseOrder/` or reuse + any of its DTOs/names, to avoid the supplier-PO naming collision flagged above. +- `POST /api/invoices/{id}/draft` and `POST /api/invoices/{id}/finalize` endpoints; the finalize + endpoint's adjustment/deduction payload should reuse the `CreditNote` shape from + [18_returns_claims.md](../18_returns_claims.md) where applicable rather than inventing a parallel + mechanism. +- A new PO-creation path independent of `SaleService.CreateAsync`, to support creating an + `Order`/`Invoice` before any `Sale` exists (Impact item 1) — needs its own design, not specified + here. +- Resolve Open Questions 1–2 before implementing the Draft-transition endpoint and the + one-Invoice-per-PO assumption respectively.