diff --git a/Distribution/docs/refactor_docs/order_invoice_lifecycle.md b/Distribution/docs/refactor_docs/order_invoice_lifecycle.md index edd44f3..1ebcc60 100644 --- a/Distribution/docs/refactor_docs/order_invoice_lifecycle.md +++ b/Distribution/docs/refactor_docs/order_invoice_lifecycle.md @@ -82,6 +82,89 @@ Invoice ├── IsActive, CreatedAt — from Template ``` +### Invoice Line Items & Version History + +> **Implemented.** Class/column names follow the code: the PO is `ShopPO`/`ShopPOItem`, and the +> invoice FK is `FK_ShopPOId` (not `FK_OrderId` as sketched above). + +The invoice no longer bills straight from the PO. It owns its own lines, and every change to them is +snapshotted, so the original, printed and finalized quantities can all be compared. + +``` +InvoiceItem — the live billing lines +├── FK_InvoiceId, FK_ShopPOItemId, FK_ItemId +├── Quantity (int) — what is billed now (0..ShopPOItem.Quantity) +├── UnitPrice (decimal) +└── IsActive, CreatedAt — from Template + +InvoiceVersion — immutable snapshot of the invoice +├── FK_InvoiceId, VersionNumber — unique per invoice, starts at 1 +├── Stage (InvoiceVersionStage) — Created, DraftEdited, Printed, Finalized +├── TotalAmount, CreatedBy +└── Items → InvoiceVersionItem — FK_ShopPOItemId, FK_ItemId, Quantity, UnitPrice +``` + +- **Add / remove lines (Draft):** `PUT /api/invoices/{id}/items` takes edits keyed by `invoiceItemId` + (change quantity/price, or `isRemoved: true` to strike the line — it is kept, excluded from the + total, shown struck through on the invoice view and print copy in every status, and can be + restored while Draft) or by `itemId` (add a catalog item + that was not on the PO; `unitPrice` required, quantity > 0, no duplicate of a live line). Added + lines have no `ShopPOItem` (`FK_ShopPOItemId` is nullable → no `orderedQuantity`, no max). Versions + store `IsRemoved` per line. At `Finalize`, adjustments are keyed by `invoiceItemId` and can also + set `isRemoved`. When the loading sheet is linked to invoices, it must count added lines. +- **Original PO quantity** stays on `ShopPOItem.Quantity` (locked once the PO is confirmed) and is + returned as `orderedQuantity` on each invoice line. +- `InvoiceItem` rows are copied from the PO's active lines at invoice creation (Mode A and Mode B). +- A version is written, in the same transaction as the change, when the invoice is: + +| Stage | Written when | +|---|---| +| `Created` (v1) | Invoice created | +| `DraftEdited` | `PUT /api/invoices/{id}/items` while `Draft` | +| `Printed` | `POST /api/invoices/{id}/print` — the quantities on the paper copy | +| `Finalized` | `POST /api/invoices/{id}/finalize` — after the agent's edits are applied | + +- Comparing `Printed` vs `Finalized` shows what the agent changed on paper. +- Migrations `AddInvoiceItems` and `AddInvoiceVersions` backfill lines and one baseline version for + invoices that already existed (stage mapped from the invoice's status at migration time, so they + have no earlier history). + +### Invoice Screens & Printing (frontend) + +> **Implemented** in `distribution_fe`. + +**Invoice document** — one shared component (`components/invoice/invoice-template.tsx`) renders the +invoice for the preview, the draft editor and the print tab: + +- **A4**: `INVOICE` header with Invoice No. (`INV-xxxxxxxx`), date and PO ref; a Status row shown + until the invoice is Finalized; *Bill To* (store name, plus address/phone from the PO); item table + (Item · Qty · Unit Price · Amount); totals (Subtotal, Total, and Paid / Balance Due when payments + exist); payments list; notes lines for the agent's edits on non-finalized copies; signature lines. + Soft slate rules, no heavy colour. +- **80mm / 58mm receipts**: single black-and-white column with dashed rules (`*** DRAFT ***` / + `*** PRINTED ***`, `qty x price` lines, heavy-ruled TOTAL, Paid/Balance, payments, "Thank you!"). +- Money `Rs. 12,450.00`, quantities up to 3 decimals, dates `dd/MM/yyyy`. +- Deleted lines are shown **struck through in every status** and excluded from all totals. +- Not included (no data in the app): company/"From" block, logo, bank details, discount, tax, due date. + +**Print tab** — `/invoices/{id}/print` (own route group `(print)`, no dashboard chrome). Has a +paper-size selector (A4 / 80mm / 58mm), sets `@page` with `margin: 0` (so the browser's own +URL/date header and footer are not printed; the A4 sheet pads itself with 14mm), and opens the +print dialog automatically. *Print* on a Draft asks who printed it, calls `POST /print`, then opens +this tab (the tab is opened inside the click so popup blockers allow it). Printed/Finalized +invoices have *Reprint*, which opens the tab without changing status. + +**Draft editor** — pencil icon (Draft only, also *Edit Items* on the invoice page) opens a full page, `/sales/invoices/{id}/edit` (not a modal), showing the invoice template in edit mode: quantity/price +inputs, a trash icon per line (undo icon restores a deleted line), and an add-item row aligned to +the table columns (item dropdown excluding items already on the invoice, quantity, price +auto-filled from the store's price list, Enter or **+** to add). Save is disabled until something +changes, a summary shows what is pending (the browser warns before leaving with unsaved edits), and an item picked but not yet added is included on save. Non-Draft invoices redirect back to the invoice page. +Each save writes a `DraftEdited` version. + +**Configuration** — *Invoice Creation* card: two toggles, **Auto** (Mode A) and **Manual** (Mode B, +default), mutually exclusive (turning one on turns the other off; the active one is locked on), each +with a one-line explanation and a process line (`Confirm PO → … → Edit draft → Print → Finalize`). + ### New Enum — `InvoiceStatus` ```csharp @@ -140,6 +223,7 @@ Draft → Printed → Finalized |---|---|---| | (create) → Draft | **Mode A:** combined with PO confirm above | Invoice created at `Draft`. `Order.InvoiceId` set. | | (create) → Draft | **Mode B:** `POST /api/invoices` `{ orderId }` | Office user selects a Confirmed PO (filtered by `agentId` / `routeId` / `shopId`) and creates the Invoice at `Draft`. `Order.InvoiceId` set. | +| Draft (edit) | `PUT /api/invoices/{id}/items` | Office user edits the draft's lines (quantity `0..ordered`, price `>= 0`). Status stays `Draft`; `TotalAmount` recomputed; a `DraftEdited` version is saved. | | Draft → Printed | `POST /api/invoices/{id}/print` | Invoice is physically printed. `PrintedAt`/`PrintedBy` set. Agent takes the printed copy to the field and makes handwritten edits during the ride. | | Printed → Finalized | `POST /api/invoices/{id}/finalize` | After ride/day-end, agent returns printed invoice. Office worker enters any edits the agent made (quantity adjustments, returns, damage, discounts) and submits. `TotalAmount` locked. `FinalizedAt`/`FinalizedBy` set. | @@ -162,7 +246,10 @@ Draft → Printed → Finalized Invoice creation for both modes. It is stored as a plain column with no DB-level FK constraint. 7. An Invoice cannot skip `Printed` — `Draft → Finalized` directly is not a valid transition. 8. `Invoice.TotalAmount` is only authoritative once `Finalized`; through `Draft`/`Printed` it - reflects the current line items. + reflects the current invoice lines (`InvoiceItem`), not the PO lines. +8a. Invoice lines are editable only while `Draft`. At `Finalize`, only quantities can be adjusted + (never above the ordered quantity). Every change writes an `InvoiceVersion` snapshot — see + [Invoice Line Items & Version History](#invoice-line-items--version-history). 9. `Order.Status` (fulfillment tracking: Pending/PartiallyFulfilled/Fulfilled/Cancelled) is a separate axis from `Invoice.Status` — not merged in this pass. 10. `Order.FK_SaleId` is optional — a PO can exist and be confirmed before any `Sale` is recorded. @@ -201,8 +288,9 @@ Draft → Printed → Finalized 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 - discounts are assumed but the payload shape is not fully specified. "Free issues" handling (items +6. **Exact adjustment payload for `POST /finalize`** — *partly resolved:* quantity adjustments + (`{ shopPOItemId, adjustedQuantity }`) are implemented and persisted on the invoice lines. + Returns, damage and discounts are still open, as is the payload shape for them. "Free issues" handling (items given to the shop at no charge) is a known case that needs its own field(s) in the payload. ---