# Phase 4 plan (corrected — implemented)

**Status:** Business Owner decisions **D1–D12** in this document are **approved**. **Phase 4 is implemented, tested, and closed.** See `docs/PHASE4_IMPLEMENTATION_REPORT.md`. Do not reopen D1–D12.  
**Phase 0–3:** Approved and **closed**. Preserve every existing route, seed quantity, ledger figure, numbering rule, and passing test, except the authorised Phase 4 seed alignment in §19 (`INV-2026-000119` line snapshots for `CRN-2026-000031`).  
**Stack (locked):** CodeIgniter 4, PHP 8.2+, MySQL 8.0.16+ InnoDB, server-rendered CI4 Views, existing CSS/JS architecture, Composer, CI4 migrations and seeders, database sessions, Spark + cron, PHPUnit.

Do not introduce React, Next.js, Node frontend, NestJS, PostgreSQL, JWT, localStorage repositories, mock APIs, Shield, Alankit, or another framework.

**Do not start Phase 5.**

---

## Binding Business Owner rules

### Original prompt rules (B1–B13)

These remain in force. D1–D12 outrank earlier Phase 4 plan recommendations wherever they conflict.

| ID | Decision |
|---|---|
| **B1** | Invoice creation and Invoice approval remain separate. Never “Save and Approve Both.” Every source Challan must already be Approved. |
| **B2** | Unbilled selection: same Company, Plant, Client, and compatible billing scope. Plant locks after the first selection. Show why an ineligible Challan cannot be selected. U2 + row locks block concurrent billing. |
| **B3** | Card/row selection where the approved responsive design already uses it. Do not invent checkboxes. |
| **B4** | Calculation engine, snapshots, nearest-rupee round-off, always-visible round-off row including ₹0.00. Decimal arithmetic only. Fixed 8-row summary order. |
| **B5** | Invoice Status and IRN Status stay separate. Manual IRN only. No API, queue, job, automatic retry, connection-health UI, or “Generate IRN.” |
| **B6** | Permission-denied actions are **absent** and direct POST returns **403**. Status-blocked actions stay visible only with the exact reason. Active role only. |
| **B7** | Draft invoice identity is `invoice_id`. A draft does not receive a visible INV-* merely because it has a primary key. |
| **B8** | Credit Note only against an Invoice with IRN Generated. Post-IRN Invoice cancellation does **not** auto-create a Credit Note. User explicitly chooses Create Credit Note. |
| **B9** | Credit Note and Invoice have separate identities, numbers, statuses, histories, and IRN attempts. CN number never reused. No physical delete. |
| **B10** | Original Invoice Credit Status changes only when the Credit Note reaches successful IRN Generated. Failed IRN must not consume extra credit. |
| **B11** | Preserve Phase 0–3 frozen figures, 190 tests / 4,807 assertions as the **minimum** baseline, 110 Phase 3 frames, mobile R1 shell, `INWARD.REVERSE` = Store Keeper + Super Admin, no `INWARD.APPROVE`. |
| **B12** | Do not add permission codes silently. Map to the existing catalogue wherever possible. D5 and D8 authorise the only new codes in this slice. |
| **B13** | Additive MySQL 8 InnoDB only. Do not rebuild Phase 0–3 tables. |

### Approved Phase 4 decisions (D1–D12)

| ID | Title | Status |
|---|---|---|
| **D1** | Credit Note approval lifecycle | **Approved** — genuine independent FSM. Use existing `CREDIT_NOTE.APPROVE`. No Draft → Pending IRN. |
| **D2** | G1 credit reservation point | **Approved with correction** — consuming set is APPROVED / PENDING_IRN / IRN_GENERATED only. Reservation begins on PENDING_APPROVAL → APPROVED. |
| **D3** | Returned for Correction | **Approved** — occurs before approval; holds no G1 reservation; Approved CN is not editable and does not move backwards. |
| **D4** | Credit Note cancellation | **Deferred** — not a Phase 4 defect. No `CREDIT_NOTE.CANCEL`. No Cancel / Delete / Void route. |
| **D5** | Credit Note permissions | **Approved** — exact catalogue names and seeded grants below. |
| **D6** | INV-SCR-012 Review states | **Approved** — parent Credit Note lifecycle context, not a separate IRN approval workflow. |
| **D7** | Manual IRN failure | **Approved** — parent stays PENDING_IRN; append-only FAILURE attempt; G1 retained. |
| **D8** | Invoice reservation permissions | **Approved** — add only `INVOICE.RESERVE_NUMBER` and `INVOICE.RELEASE_NUMBER`. |
| **D9** | Authoritative ₹75,089.00 Credit Note | **Approved** — seed and test `CRN-2026-000031` against INV-2026-000119 using §19. |
| **D10** | INV-SCR-004 | **Approved** — integration and regression only. Do not reconstruct Phase 1. |
| **D11** | Frame-name alias | **Approved** — FrameCatalog coverage key is the CSV name `D-INV-SCR-012-Create-IRNGenerated`. |
| **D12** | Source availability | **Approved** — named versions are external handoff documents; this packet records the derived Phase 4 decisions. |

Frozen regressions (must not change): **190** tests / **4807** assertions (floor); ledger **116.000 MT**; allocation **18.000 + 12.000 = 30.000 MT**; LOT-2026-00230 remaining **13.000 MT**; disposition **3.000 + 7.000 + 2.000 = 12.000 MT**; `WST-2026-000067` Approved; invoice header **₹177,592.00**; 110 Phase 3 frames; company/plant isolation; active-role IAM; production ignores `?frame=`; no production seed/fixture/demo/probe/test routes.

---

## 1. Executive scope and explicit exclusions

### In scope (seven Screen IDs, 77 CSV frames)

| Screen ID | Function | D | M | T | Total | Frame IDs |
|---|---|---:|---:|---:|---:|---|
| INV-SCR-001 | Unbilled Challan Selection | 8 | 2 | 0 | **10** | FR-0364–0373 |
| INV-SCR-002 | Later / Bulk Invoice Create and Review | 9 | 2 | 0 | **11** | FR-0374–0384 |
| INV-SCR-004 | Invoice List (Phase 1 exists — integration/regression only) | 7 | 2 | 0 | **9** | FR-0399–0407 |
| INV-SCR-007 | Invoice Number Reservation | 10 | 2 | 0 | **12** | FR-0408–0419 |
| INV-SCR-010 | Credit Note List | 7 | 2 | 0 | **9** | FR-0435–0443 |
| INV-SCR-011 | Credit Note Create and Review | 9 | 2 | 0 | **11** | FR-0444–0454 |
| INV-SCR-012 | Manual Credit Note IRN Outcome | 12 | 3 | 0 | **15** | FR-0455–0469 |
| **Total** | | **62** | **15** | **0** | **77** | |

The remaining six Screen IDs (excluding INV-SCR-004) are **68** frames (55 desktop + 13 mobile).

### Explicit exclusions

- Phase 5 and any later module.
- Reconstructing INV-SCR-003 / INV-SCR-008 (Phase 1). Phase 4 may **link** from INV-004 / Credit Notes to those existing screens.
- Reconstructing INV-SCR-004’s Phase 1 list/query/status model (D10). Integration and regression only.
- Automatic IRN, Alankit, IRN queue/job/retry, connection-health UI, “Generate IRN,” automatic Credit Note IRN retry.
- `INWARD.APPROVE`, inward approval FSM, disposition reversal.
- Changing `INWARD.REVERSE` grants (Store Keeper + Super Admin).
- New navigation components or a second mobile shell.
- Rebuilding `invoice_header` / `invoice_challan_link` / `irn_attempt` / numbering tables.
- `CREDIT_NOTE.CANCEL`, Credit Note Cancel / Delete / Void routes or actions (D4 — deferred capability).
- Pre-IRN or post-IRN Credit Note cancellation, number release/reuse, or invented credited-quantity reversal.
- Permission aliases `CREDIT_NOTE.ADD`, `CREDIT_NOTE.RECORD_IRN`, `CREDIT_NOTE.CANCEL`.
- Inferring `INVOICE.RESERVE_NUMBER` / `INVOICE.RELEASE_NUMBER` from `INVOICE.ADD`, `INVOICE.EDIT`, or `INVOICE.APPROVE`.
- Draft → Pending IRN on a Credit Note.
- G1 reservation from Draft, Pending Approval, or Returned for Correction.
- Using Figma alias `D-INV-SCR-012-IRNGenerated` as the FrameCatalog / coverage key.

---

## 2. Source-precedence register

The named source documents are **external handoff documents**. They exist outside this repository. This approved packet records the Phase 4 implementation decisions derived from them. **Do not claim they do not exist.**

| Rank | Source | Version | Role in Phase 4 |
|---|---|---|---|
| 1 | Explicit Business Owner decisions in this packet | B1–B13 and **D1–D12** | Binding. Outrank earlier plan recommendations. |
| 2 | Business Requirements Document | **BRD v0.9.22** | External handoff. Recorded translations also live in `docs/PHASE1_PLAN.md`. |
| 3 | Technical Design | **Technical Design v0.4.1** | External handoff. Numbering and G3 translations in Phase 1 plan. |
| 4 | Database Architecture | **Database Architecture v1.2** | External handoff. MySQL 8 InnoDB. Translation rules in `docs/POSTGRES_MYSQL_TRANSLATION.md`. |
| 5 | Functional / UI specification | **FRIS v1.0.1** | External handoff. CSV `Source FRIS` cites 12.4 / 12.4.1 / 12.4.2 / 12.4.5 / 12.4.6 / 12.6.2. |
| 6 | Figma Production Blueprint | **Figma Production Blueprint v1.1** | External handoff. Prompt pack in `uploads/` and P28 links/tokens. |
| 7 | Authoritative CSV manifest | **Authoritative CSV manifest v1.1** | In-repo: `uploads/RMC_Crusher_ERP_Phase1_Figma_Frame_Component_Manifest_v1.1_4e22.csv` (606 frames). |
| 8 | Existing Phase 0–3 implementation | Closed code, migrations, seed, 190 tests | Closed decisions win over CSV chrome where they do not contradict D1–D12. |

CSV Frame Names are the visual coverage contract. They do not override B1–B13, D1–D12, or closed Phase 0–3 behaviour.

**Approved alias (D11):** CSV `D-INV-SCR-012-Create-IRNGenerated` may be recorded against Figma `D-INV-SCR-012-IRNGenerated` for **design traceability only**. The implementation’s FrameCatalog and coverage tests **must use the authoritative CSV Frame Name** `D-INV-SCR-012-Create-IRNGenerated`. Do not use the Figma alias as the server-render coverage key. Every other Phase 4 frame must match its CSV name directly. No other Phase 4 alias is authorised.

---

## 3. Exact 77-frame inventory from the CSV

**Phase 4 total: 77. Desktop 62. Mobile 15. Tablet 0.** Direct CSV Frame Names. Device letter is part of the name.

### INV-SCR-001 (10) — Unbilled Challan Selection — T01 — Billing Clerk — Invoice — Create — FRIS 12.6.2

`D-INV-SCR-001-List-Default`, `D-INV-SCR-001-List-Draft`, `D-INV-SCR-001-List-PermissionRestricted`, `D-INV-SCR-001-List-Empty`, `D-INV-SCR-001-List-Loading`, `M-INV-SCR-001-List-Default`, `D-INV-SCR-001-List-Filtered`, `D-INV-SCR-001-List-NoResults`, `D-INV-SCR-001-List-Error`, `M-INV-SCR-001-List-NoResults`.

### INV-SCR-002 (11) — Later / Bulk Invoice Create — T03 — Billing Clerk — Invoice — Create — FRIS 12.4.2

`D-INV-SCR-002-Create-Default`, `D-INV-SCR-002-Create-Draft`, `D-INV-SCR-002-Create-Error`, `D-INV-SCR-002-Create-Loading`, `D-INV-SCR-002-Review-PendingApproval`, `D-INV-SCR-002-Review-Approved`, `D-INV-SCR-002-Review-ReturnedForCorrection`, `D-INV-SCR-002-Review-ConcurrentUpdate`, `D-INV-SCR-002-Review-PermissionRestricted`, `M-INV-SCR-002-Create-Default`, `M-INV-SCR-002-Create-Error`.

### INV-SCR-004 (9) — Invoice List — T01 — Billing Clerk — Invoice — View — FRIS 12.4

`D-INV-SCR-004-List-Default`, `D-INV-SCR-004-List-Filtered`, `D-INV-SCR-004-List-Empty`, `D-INV-SCR-004-List-NoResults`, `D-INV-SCR-004-List-Loading`, `D-INV-SCR-004-List-Error`, `D-INV-SCR-004-List-PermissionRestricted`, `M-INV-SCR-004-List-Default`, `M-INV-SCR-004-List-NoResults`.

### INV-SCR-007 (12) — Invoice Number Reservation — T01 — Accounts Manager — Reserve Invoice Number / Release Number — FRIS 12.4.1

`D-INV-SCR-007-Create-Default`, `D-INV-SCR-007-Create-Draft`, `D-INV-SCR-007-Create-Error`, `D-INV-SCR-007-List-Default`, `D-INV-SCR-007-List-Filtered`, `D-INV-SCR-007-List-PermissionRestricted`, `M-INV-SCR-007-List-Default`, `D-INV-SCR-007-List-Empty`, `D-INV-SCR-007-List-NoResults`, `D-INV-SCR-007-List-Loading`, `D-INV-SCR-007-List-Error`, `M-INV-SCR-007-List-NoResults`.

### INV-SCR-010 (9) — Credit Note List — T01 — Accounts Manager — Credit Note — View — FRIS 12.4.5

`D-INV-SCR-010-List-Default`, `D-INV-SCR-010-List-Filtered`, `D-INV-SCR-010-List-Empty`, `D-INV-SCR-010-List-NoResults`, `D-INV-SCR-010-List-Loading`, `D-INV-SCR-010-List-Error`, `D-INV-SCR-010-List-PermissionRestricted`, `M-INV-SCR-010-List-Default`, `M-INV-SCR-010-List-NoResults`.

### INV-SCR-011 (11) — Credit Note Create and Review — T03 — Accounts Manager — Create Credit Note / Approve Credit Note — FRIS 12.4.5

`D-INV-SCR-011-Create-Default`, `D-INV-SCR-011-Create-Draft`, `D-INV-SCR-011-Create-Error`, `D-INV-SCR-011-Review-PendingApproval`, `D-INV-SCR-011-Review-Approved`, `M-INV-SCR-011-Create-Default`, `D-INV-SCR-011-Create-Loading`, `D-INV-SCR-011-Review-ReturnedForCorrection`, `D-INV-SCR-011-Review-ConcurrentUpdate`, `D-INV-SCR-011-Review-PermissionRestricted`, `M-INV-SCR-011-Create-Error`.

### INV-SCR-012 (15) — Record Credit Note IRN Outcome — T03 — Accounts Manager — Record IRN — FRIS 12.4.6

`D-INV-SCR-012-Create-Default`, `D-INV-SCR-012-Create-IRNGenerated`, `D-INV-SCR-012-Create-IRNFailed`, `D-INV-SCR-012-Create-PermissionRestricted`, `D-INV-SCR-012-Create-Loading`, `M-INV-SCR-012-Create-IRNGenerated`, `D-INV-SCR-012-Create-Draft`, `D-INV-SCR-012-Create-Error`, `D-INV-SCR-012-Review-PendingApproval`, `D-INV-SCR-012-Review-Approved`, `D-INV-SCR-012-Review-ReturnedForCorrection`, `D-INV-SCR-012-Review-ConcurrentUpdate`, `D-INV-SCR-012-Review-PermissionRestricted`, `M-INV-SCR-012-Create-Default`, `M-INV-SCR-012-Create-Error`.

Overlays: OV-07 on list Filtered frames; OV-11 on INV-002 / INV-011 / INV-012 ConcurrentUpdate.

---

## 4. Existing Phase 1 INV-SCR-004 assessment (D10)

INV-SCR-004 is **already implemented** (`InvoiceController::index`, `app/Views/pages/invoice/list.php`, `FrameCatalog` nine names). Phase 1 HTTP tests render all nine.

**D10 is approved:** INV-SCR-004 requires **integration and regression coverage only**. Do **not** reconstruct its Phase 1 implementation. Preserve existing permissions, mobile-card work that already exists, and production behaviour.

Extend its queries and row actions so the list correctly includes:

| Capability | Phase 1 today | Phase 4 integration (no rebuild) |
|---|---|---|
| Route | `GET /invoices` + `INVOICE.LIST` | Keep |
| Immediate invoices | `INV-2026-000119` listed | Keep |
| Later/bulk invoices | None exist yet | Same `invoice_header` query; they must appear |
| Reservation-origin invoices | No reservation UI | Show consumed INV-* created from INV-007 attach |
| Invoice Status vs IRN Status | Separate columns | Keep; do not merge badges |
| Credit Status | Column exists; seed `NOT_CREDITED` | Display it; it changes only after successful Credit Note IRN (B10 / D7) |
| Manual Invoice IRN state/actions | Not on the list (INV-008 / review) | Pending IRN → Record IRN Outcome; latest attempt FAILURE → approved fresh manual recording (not automatic retry); Generated → neither |
| Create Credit Note | Absent | Only where the Invoice is **IRN Generated** and has **remaining creditable value** |
| PermissionRestricted | Banner; no create/row actions | Keep: actions removed |
| Mobile cards | Phase 1 desktop table | Preserve existing permissions and any existing mobile cards; add Phase 0–2 card pattern if still missing, without rebuilding the list model |
| 403 without `INVOICE.LIST` | Yes (Plant Manager) | Keep |

**Verdict:** The nine INV-SCR-004 Frame Names need **no functional reconstruction** of the Phase 1 list/query/status model. They need **regression coverage plus integration** (origin types, Credit Status, Invoice IRN actions, Create Credit Note gating, later invoices). Do not replace `invoice_header` or the list route.

---

## 5. Requirement traceability by Screen ID

| Screen | Authoritative behaviour | Existing code | New work (after implementation is authorised) |
|---|---|---|---|
| INV-001 | Eligible Approved + unbilled Challans; multi-select; same Company/Plant/Client; plant lock; ineligibility reasons; U2 | `invoice_challan_link.live_challan_key`; challan `APPROVED`/`BILLED`; `InvoiceService::createFromChallan` is **single** challan immediate path | New selection screen + session/draft selection; reuse U2 |
| INV-002 | One invoice from selected Challans; separate create vs approve; engine + snapshots; plant locked; row locks | `InvoiceCalculator`, `CalculationContract`, `invoice_header` statuses including `PENDING_APPROVAL` | `createFromChallans`; optional consume reserved number; reuse approve/return/cancel |
| INV-004 | D10: integration/regression only | List + review + IRN | Queries/actions for origin types, Credit Status, Invoice IRN, Create Credit Note gating |
| INV-007 | Reserve vs fixed Client + Challan set; consume on invoice create; release unused; history append-only | `NumberingService::reserveInvoice` / `consumeReserved` / `releaseForReuse`; **no** client/challan binding table | `invoice_number_reservation` + child challans; UI gated by D8 codes |
| INV-010 | CN list; separate CN status and IRN status; no Delete/Cancel/Void | **No tables, routes, or Views** | New. No Cancel/Delete/Void action. |
| INV-011 | CN against IRN Generated invoice; full/partial; remaining caps; **genuine independent FSM (D1)** | Catalogue has `CREDIT_NOTE.LIST/VIEW/CREATE/APPROVE/RECORD_IRN_DETAILS`; `CREDIT_NOTE.EDIT` is **not yet in catalogue** (D5 authorises adding it); `irn_attempt.credit_note_id` column **without FK** | New header/line/charge + D1 lifecycle + D2 approval transaction |
| INV-012 | Manual outcome; Create states are operational IRN-recording states; Review states show **parent CN genuine lifecycle (D6)** | `IrnRecordingService` invoice-only; INV-008 UX to clone | Extend `irn_attempt` FK; no IRN-specific Approve/Return |

INV-002 Review-PendingApproval / Approved / ReturnedForCorrection are **genuine invoice states** (same `invoice_header.invoice_status` as INV-003). They are not compatibility-only.

INV-011 Review-PendingApproval / Approved / ReturnedForCorrection are **genuine Credit Note operational states (D1)**, not compatibility frames. CSV approval states on INV-SCR-011 are genuine.

INV-012 Review-PendingApproval / Approved / ReturnedForCorrection display the **parent Credit Note’s genuine lifecycle context (D6)**. They are not a separate IRN approval workflow. Do not store those states on `irn_attempt`.

INV-012 Create-Draft / Create-Error exist on CSV (same as INV-008). Phase 1 treated IRN Draft as overlay, not a persisted IRN draft document. **Same rule:** no saved IRN-attempt draft; Draft/Error are visual overlays.

---

## 6. Current Phase 0–3 schema and service inventory

### Present (reuse)

| Area | Objects |
|---|---|
| Invoice | `invoice_header`, `invoice_line`, `invoice_charge_line`, `invoice_challan_link` (+ generated `live_challan_key` U2), `invoice_challan_link_status_history`, `irn_attempt` (invoice XOR credit_note CHECK; **no** `fk` on `credit_note_id`) |
| Invoice statuses | `DRAFT`, `PENDING_APPROVAL`, `RETURNED_FOR_CORRECTION`, `APPROVED_PRE_IRN`, `PENDING_IRN`, `IRN_FAILED`, `IRN_GENERATED`, `CANCELLED` |
| IRN statuses | `NOT_APPLICABLE`, `PENDING`, `FAILED`, `GENERATED` |
| Credit status | `NOT_CREDITED`, `PARTIALLY_CREDITED`, `FULLY_CREDITED` (column exists; never written except seed `NOT_CREDITED`) |
| Numbering | `document_sequence`, `document_number_registry` (`RESERVED` / `CONSUMED` / `RELEASED` / `AVAILABLE_FOR_REUSE` invoice-only), `numbering_history` |
| Numbering API | `issueAndConsume`, `reserveInvoice`, `consumeReserved`, `releaseForReuse` — **history helper has no `credit_note_id` argument** |
| Calculation | `CalculationContract`, `InvoiceCalculator::ENGINE_VERSION = phase1-v1`, C-DAT-12 |
| Immediate invoice | `InvoiceService::createFromChallan` — ISSUED→CONSUMED, U2, sets challan `BILLED` |
| IRN | `IrnRecordingService` manual SUCCESS/FAILURE; append-only |
| Idempotency | `idempotent_command` CHECK types are Phase 3 inward/wastage/disposition only |
| IAM | Active-role `PermissionCatalog`; Super Admin = full catalogue |
| Mobile shell | `c_nav_01_shell` hamburger + R1 drawer; 44×44 `--component-touchtarget-min` |

### Absent (authorised only after implementation start)

- `invoice_number_reservation`, `invoice_number_reservation_challan`, reservation history table (or equivalent)
- `credit_note_header`, `credit_note_line`, `credit_note_charge_line`, CN status/history
- G1 enforcement (no CN rows, no cumulative unique/sum)
- Catalogue codes `INVOICE.RESERVE_NUMBER`, `INVOICE.RELEASE_NUMBER` (D8 — add when implementing)
- Catalogue code `CREDIT_NOTE.EDIT` (D5 — add when implementing; do not invent `CREDIT_NOTE.ADD` / `RECORD_IRN` / `CANCEL`)
- Routes `/invoices/unbilled`, `/invoices/later`, `/invoice-reservations`, `/credit-notes`
- Plant column on `invoice_header` (plant is on `challan_header.company_plant_id` only)

---

## 7. Table/column/index/constraint gap analysis

| Gap | Proposed additive object | Notes |
|---|---|---|
| Bind reserved INV-* to client + challans | `invoice_number_reservation` | `document_number_registry_id` UNIQUE while `RESERVED`; `client_company_id` immutable; `company_id` + `financial_year_id`; `reservation_status` RESERVED/CONSUMED/RELEASED; `row_version` |
| Bound challans | `invoice_number_reservation_challan` | UNIQUE `(reservation_id, challan_id)`; all rows same company/plant/client |
| Reservation events | `invoice_number_reservation_history` append-only | RESERVED / CONSUMED / RELEASED |
| Credit Note document | `credit_note_header` | `original_invoice_id` FK **RESTRICT**; own `document_number_registry_id`; **D1 persisted statuses only** (no `IRN_FAILED` as a Credit Note lifecycle value; no `CANCELLED`); snapshots of engine version + policy JSON copied from invoice; totals CHECKs like G3 |
| CN lines/charges | `credit_note_line`, `credit_note_charge_line` | FK to original `invoice_line_id` / `invoice_charge_line_id`; credited qty/value ≥ 0; remaining checked in service (G1) at **approval** (D2) |
| CN status history | `credit_note_status_history` append-only | Written in the same transaction as approval (D2 step 9) |
| IRN parent | `ALTER irn_attempt ADD CONSTRAINT fk_irn_credit_note` | Only after `credit_note_header` exists |
| Inward return link | `ALTER inward_entry` add FK `original_credit_note_id` | Phase 3 left nullable without FK (C12). Additive FK once CN table exists. |
| Idempotency types | Extend `chk_idem_type` | `INVOICE_LATER_CREATE`, `INVOICE_RESERVE`, `INVOICE_RELEASE`, `CREDIT_NOTE_SAVE`, `CREDIT_NOTE_APPROVE`, `CREDIT_NOTE_IRN`. **Do not** add a Credit Note cancel type. |
| Numbering history | Pass `credit_note_id` into `NumberingService::history` | Column already exists. Never RELEASED/REUSED for CREDIT_NOTE. |
| Plant snapshot | Optional `invoice_header.company_plant_id` | Additive; derive from locked challan set. If omitted, always JOIN challan links. Prefer additive column to lock plant on the invoice row. |
| G1 | **No same-table trigger** | Service sum + `FOR UPDATE` in the D2 lock order. Optional later projection only if separately documented. |

PostgreSQL partial unique `UNIQUE(challan_id) WHERE live` is **already** MySQL generated-column `live_challan_key`. Repeat that pattern for “one live reservation per challan” if a challan may be bound to a reservation before invoice persist (`DRAFT_RESERVED` link or reservation-challan live key).

Do not drop `uq_icl_live_challan`. Do not reinterpret `AVAILABLE_FOR_REUSE` for CREDIT_NOTE.

---

## 8. Additive migration plan and rollback order

**Do not run this until implementation is authorised.**

**One new migration** (suggested name `2026-09-13-400001_AddPhase4BillingSchema`) after Phase 3 `2026-09-12-300001`. Additive only.

**Up order**

1. `invoice_number_reservation`
2. `invoice_number_reservation_challan` + generated live unique if required
3. `invoice_number_reservation_history`
4. `credit_note_header` (FK invoice, registry, company, FY, client)
5. `credit_note_line`, `credit_note_charge_line`
6. `credit_note_status_history`
7. `fk_irn_credit_note` on `irn_attempt.credit_note_id`
8. Optional `fk` `inward_entry.original_credit_note_id`
9. Optional `invoice_header.company_plant_id`
10. Widen `idempotent_command` CHECK (drop/re-add CHECK — not a table rebuild)
11. Permission/role seed **only** for D5 `CREDIT_NOTE.EDIT` and D8 `INVOICE.RESERVE_NUMBER` / `INVOICE.RELEASE_NUMBER` (skip-if-exists, Phase 3 G-fix pattern). Do **not** seed `CREDIT_NOTE.CANCEL`.

**Down order (reverse):** drop FKs first, then CN history/lines/header, then reservation children/header, restore idempotent CHECK, do **not** drop Phase 0–3 tables. Do not `SET FOREIGN_KEY_CHECKS=0`.

---

## 9. Invoice-number reservation lifecycle

Scope: **Company + Financial Year + document type INVOICE**. Plants share the company sequence (Phase 1).

### Direct Invoice number (immediate path — already live)

| Event | Rule |
|---|---|
| Reserved | Not separately reserved. `issueAndConsume` ISSUED→CONSUMED in the invoice persist transaction (`InvoiceService::createFromChallan`). |
| Consumed | Same transaction as `invoice_header` insert; `invoice_number` snapshot set. |
| Released | Pre-IRN cancel only (`InvoiceService::cancelPreIrn` → `releaseForReuse`). |
| Reusable pool | `AVAILABLE_FOR_REUSE` then next `reserveInvoice` or immediate consume-from-pool. |
| Permanently prohibited | After IRN Generated (and after any successful IRN). |
| Locks | `document_sequence FOR UPDATE`; challan `FOR UPDATE`; U2 live key. |
| Unique | `uq_reg_scope_formatted`; `uq_inv_live_number`; `uq_nh_consumed_once`. |
| History | ISSUED + CONSUMED (and RELEASED/REUSED if cancelled). |
| Rollback | Entire Tx rolls back; sequence `next_value` rolls back; **not** reuse of a committed gap. |
| Idempotency | Immediate create already single-challan; later path must use `idempotent_command`. |

### Reserved Invoice number (INV-007)

| Event | Rule |
|---|---|
| Reserved | Authorised user with **`INVOICE.RESERVE_NUMBER` (D8)** calls `reserveInvoice`. Registry ISSUED→RESERVED (or AVAILABLE_FOR_REUSE→RESERVED with REUSED history). Persist `invoice_number_reservation` with **immutable** `client_company_id` and challan set. Do not infer this permission from ADD/EDIT/APPROVE. |
| Draft identity | Reservation row id is not an invoice. No `invoice_header` yet. No automatic INV-* on a future draft invoice PK. |
| Consumed | Later/bulk (or immediate) persist attaches `document_number_registry_id`, sets `invoice_number`, `consumeReserved`, reservation CONSUMED. Client/challans **cannot** change. |
| Released | Unused reservation: authorised user with **`INVOICE.RELEASE_NUMBER` (D8)**; `releaseForReuse`; reservation RELEASED; history append-only and readable. |
| Reusable pool | Yes, after unused release or pre-IRN invoice cancel (INV-BR-003). Sequence never moves backward. |
| Permanently prohibited | IRN Generated. |
| Consumed UI | Release **disabled** with exact reason “Number is consumed by {INV-*}.” |
| PermissionRestricted | Release **absent** (not disabled). |
| Concurrent | `FOR UPDATE` on sequence and registry row; UNIQUE registry while RESERVED; U2 prevents two live bills on the same challan. |
| Idempotency | Same idempotency key replays the same `registry_id`. |

### Credit Note number

| Event | Rule |
|---|---|
| Reserved | Not used. Issue-and-consume at first **successful persist** that creates `credit_note_header` (same as CHALLAN/WASTAGE: never `AVAILABLE_FOR_REUSE`). |
| Draft identity | `credit_note_id`. Visible `CRN-*` assigned in the first persist transaction. Consume on first persist, **never reuse**, matching CHL-BR-001 / wastage. |
| Consumed | Same Tx as header insert. |
| Released | **Never.** D4 forbids number release. |
| Reusable pool | **No.** CHECK already forbids `AVAILABLE_FOR_REUSE` except INVOICE. |
| Permanently prohibited | From the moment committed. Credit Note numbers remain permanently non-reusable. |
| Locks | `document_sequence FOR UPDATE`. |
| Unique | registry UNIQUEs; live CN number generated unique on header. |
| History | ISSUED + CONSUMED only. |
| Rollback | Same as challan: failed Save leaves no number. |
| Idempotency | `CREDIT_NOTE_SAVE` key. |

Preserve: Challan never reused; Wastage never reused; Invoice controlled pre-IRN reuse only; IRN-generated Invoice never reused; Credit Note never reused.

---

## 10. Later/bulk Invoice transaction design

**Entry:** INV-001 selection → INV-002 create.

**Eligibility (INV-001)**

- `challan_status = APPROVED`
- No live `invoice_challan_link` (`DRAFT_RESERVED` / `FINAL_ACTIVE`)
- Not bound to an open `invoice_number_reservation` (if that reservation is still RESERVED)
- Same `company_id`, `company_plant_id`, `client_company_id`
- Compatible billing scope = those four plus same GST place-of-supply treatment (intra vs inter) so CGST/SGST and IGST never mix
- After first selected row: plant **locked**; other plants ineligible with reason “Plant is locked to {name}.”
- Other client: “Client differs from the locked selection.”
- Already billed: “Challan is already billed on {INV-*}.”
- Unapproved: “Challan is not Approved.”
- Live lock: “Challan is reserved for billing.”

Selection UI: selectable **rows** (desktop) and **cards** (mobile) per CSV/Figma. No extra checkbox column.

**Persist (INV-002)** — one transaction:

1. Lock selected `challan_header` rows in **ascending `challan_id`** (U2 / Tx 27.1).
2. Recheck eligibility.
3. If a reservation id is supplied: lock reservation + registry; verify client/challan set matches exactly; `consumeReserved`.
4. Else: `issueAndConsume` (or consume `AVAILABLE_FOR_REUSE` via existing reserve-or-issue behaviour).
5. Insert `invoice_header` (`DRAFT` or `PENDING_APPROVAL` depending on Save vs Submit — same as INV-003). Set plant snapshot. Copy engine version + policy snapshot.
6. Insert lines from all challan lines (existing rate resolution + `INVOICE.SET_MANUAL_RATE` rules).
7. Insert charges; `InvoiceCalculator`; persist G3 totals; round-off row always stored.
8. Insert `invoice_challan_link` `FINAL_ACTIVE` (or `DRAFT_RESERVED` while invoice is DRAFT — **recommend `DRAFT_RESERVED` on draft save, promote to `FINAL_ACTIVE` on submit/approve** so U2 holds during draft). Challans stay `APPROVED` until invoice reaches a billed state; Phase 1 sets `BILLED` on immediate create — **keep that for later/bulk at consume/create** so INV-001 hides them.
9. Audit + idempotent claim.
10. Commit.

Approve / return / cancel: **reuse** `InvoiceService` methods. Create ≠ Approve. No combined control.

Concurrent second invoice on an overlapping challan: U2 UNIQUE + `U2_CHALLAN_ALREADY_LINKED`.

---

## 11. Credit Note state machine (D1)

INV-SCR-011 has a **genuine independent lifecycle**. CSV approval states are **genuine operational states**, not compatibility frames.

**Persisted Credit Note lifecycle:**

```
DRAFT
  → PENDING_APPROVAL
      → APPROVED
          → PENDING_IRN
              → IRN_GENERATED
      → RETURNED_FOR_CORRECTION
          → PENDING_APPROVAL   (after correction and resubmission)
```

Do **not** implement Draft → Pending IRN directly.

`CREDIT_NOTE.APPROVE` already exists and **must be used**. Do not invent a second approve code.

Creator self-approval is allowed **only when the active role holds `CREDIT_NOTE.APPROVE`**.

| Transition | Actor permission | Notes |
|---|---|---|
| Create / Save draft | `CREDIT_NOTE.CREATE` | Prefill from immutable invoice snapshots. Original invoice must be IRN Generated. Draft does **not** reserve G1 (D2). |
| Edit draft / returned | `CREDIT_NOTE.EDIT` | D5 catalogue name. Draft and Returned for Correction only. An already Approved Credit Note is **not editable** (D3). |
| Submit | `CREDIT_NOTE.CREATE` and/or `CREDIT_NOTE.EDIT` as implemented consistently with D5 | To Pending Approval. Still **does not** reserve G1. |
| Approve | `CREDIT_NOTE.APPROVE` | PENDING_APPROVAL → APPROVED. Atomic G1 reservation (D2). Then document enters PENDING_IRN for IRN recording. |
| Return for correction | `CREDIT_NOTE.APPROVE` | PENDING_APPROVAL → RETURNED_FOR_CORRECTION. Reason required (OV-06 pattern). **No** G1 reservation (D3). |
| Resubmit after correction | `CREDIT_NOTE.EDIT` / `CREDIT_NOTE.CREATE` as implemented | RETURNED_FOR_CORRECTION → PENDING_APPROVAL. Quantities revalidated at the **later** approval transaction. |
| Record IRN | `CREDIT_NOTE.RECORD_IRN_DETAILS` | INV-012 only after parent is Approved and has entered PENDING_IRN (D6). Not an approval of the attempt. |
| Cancel / Delete / Void | **Not in Phase 4** | D4 deferred. No route, no action, no `CREDIT_NOTE.CANCEL`. |

**Not a persisted Credit Note lifecycle value:** `IRN_FAILED`. A failed attempt is stored on `irn_attempt` only (D7). The parent remains `PENDING_IRN`. The UI may show “IRN Failed” as the latest attempt outcome/fork.

**Forbidden backwards move:** an already Approved Credit Note does not transition to Returned for Correction (D3).

Invoice `credit_status` updates **only** when a Credit Note attempt outcome is SUCCESS and the Credit Note reaches `IRN_GENERATED` (B10 / D7):

- If remaining after this Credit Note is 0 → `FULLY_CREDITED`
- Else → `PARTIALLY_CREDITED`

Failed IRN: parent stays `PENDING_IRN`; invoice `credit_status` unchanged.

---

## 12. G1 consuming-state definition and approval transaction (D2)

**Do not reserve cumulative credit from the first Draft save.**

Draft, Pending Approval and Returned for Correction Credit Notes **do not consume or reserve** remaining creditable quantity/value.

### Authoritative G1 consuming set

| Credit Note status | Counts toward G1 remaining? |
|---|---|
| `DRAFT` | **No** — does not reserve |
| `PENDING_APPROVAL` | **No** — does not reserve |
| `RETURNED_FOR_CORRECTION` | **No** — holds no reservation (D3) |
| `APPROVED` | **Yes** — reservation begins here |
| `PENDING_IRN` | **Yes** — retains reservation |
| `IRN_GENERATED` | **Yes** — claim is permanent |
| Failed `irn_attempt` rows | **No extra consumption** — already counted via header `PENDING_IRN` / `IRN_GENERATED`. Failure must not double-count. |

Reservation begins **atomically** when a Credit Note transitions from `PENDING_APPROVAL` to `APPROVED`.

### Approval transaction and lock order (mandatory)

During approval, in **one transaction**:

1. Lock the original `invoice_header`.
2. Lock affected `invoice_line` rows in **ascending `invoice_line_id`** order.
3. Lock affected `invoice_charge_line` rows in **ascending `invoice_charge_line_id`** order.
4. Lock relevant existing Credit Note headers/lines.
5. Recalculate cumulative credited/reserved values.
6. Include every Credit Note in `APPROVED`, `PENDING_IRN` or `IRN_GENERATED`.
7. Exclude `DRAFT`, `PENDING_APPROVAL` and `RETURNED_FOR_CORRECTION`.
8. Reject approval if any line, charge, deduction, tax or document-level cap would be exceeded.
9. Write approval history and audit in the **same transaction**.

A second concurrent approval against overlapping Invoice lines **must wait** on the lock and **revalidate** after obtaining it.

**No MySQL trigger on the same table** (error 1442). Service recomputes:

```
remaining = original_snapshot − SUM(credited on CNs in the consuming set)
new_credit ≤ remaining
```

Lines and charges are validated **independently**. Matching grand totals **cannot** hide an over-credit on an individual line.

Recheck G1 again when recording a **successful** Credit Note IRN outcome. A successful IRN does not increase credited quantity beyond the already reserved Approved amount; it makes the G1 claim permanent.

Zero-credit lines: retain visibility in the selector (D9 line 2). Persist only according to the approved zero-line storage rule; remaining creditable amount must remain visible.

---

## 13. Returned-for-correction behaviour (D3)

Returned for Correction occurs **before approval** and therefore holds **no G1 reservation**.

- Do **not** retain a credit reservation for a Credit Note in `RETURNED_FOR_CORRECTION`.
- The corrected Credit Note must be **resubmitted and approved again**.
- Quantities and values are **revalidated at the later approval transaction**.
- An already Approved Credit Note is **not editable** and does **not** transition backwards to Returned for Correction.

---

## 14. Credit Note cancellation deferral (D4)

**Do not add `CREDIT_NOTE.CANCEL` in Phase 4.**  
**Do not build a Credit Note Cancel, Delete or Void route or action.**

The statutory conditions for cancelling or invalidating a Credit Note remain unresolved. Therefore:

- No pre-IRN Credit Note cancellation.
- No post-IRN Credit Note cancellation.
- No number release or reuse.
- No reversal of credited quantities through an invented cancellation process.
- Credit Note numbers remain permanently non-reusable.

This is an **explicitly deferred capability**, not a missing Phase 4 defect. Tests must assert that no Cancel/Delete/Void route exists and that no Credit Note number is ever reused.

---

## 15. Credit Note permission names and seeded grants (D5)

Use the existing catalogue names **exactly**. Do not create `CREDIT_NOTE.ADD`, `CREDIT_NOTE.RECORD_IRN` or `CREDIT_NOTE.CANCEL` aliases.

`CREDIT_NOTE.LIST`, `VIEW`, `CREATE`, `APPROVE` and `RECORD_IRN_DETAILS` already exist. **`CREDIT_NOTE.EDIT` does not exist in the current catalogue**; D5 **authorises adding that one code** at implementation time, seeded as below. It is not a silent extra.

### Seeded grants

| Permission | Accounts Manager | Super Admin | Billing Clerk | Plant Manager | Store Keeper |
|---|---|---|---|---|---|
| `CREDIT_NOTE.LIST` | Yes | Yes | No | No | No |
| `CREDIT_NOTE.VIEW` | Yes | Yes | No | No | No |
| `CREDIT_NOTE.CREATE` | Yes | Yes | No | No | No |
| `CREDIT_NOTE.EDIT` | Yes | Yes | No | No | No |
| `CREDIT_NOTE.APPROVE` | Yes | Yes | No | No | No |
| `CREDIT_NOTE.RECORD_IRN_DETAILS` | Yes | Yes | No | No | No |

Only the **active role** applies. Creator self-approval remains allowed when the **active role** has `CREDIT_NOTE.APPROVE`.

PermissionRestricted frames **remove actions**. Direct unauthorised POST requests return **403**.

Patil does not union Billing Clerk while Store Keeper is active (closed Phase 0). Plant Manager remains 403 on invoice/CN routes he does not hold.

---

## 16. Invoice reservation permissions (D8)

Add **only** these two permission codes (when implementation is authorised):

- `INVOICE.RESERVE_NUMBER`
- `INVOICE.RELEASE_NUMBER`

Seed them to:

| Permission | Accounts Manager | Super Admin | Billing Clerk | Plant Manager | Store Keeper |
|---|---|---|---|---|---|
| `INVOICE.RESERVE_NUMBER` | Yes | Yes | No | No | No |
| `INVOICE.RELEASE_NUMBER` | Yes | Yes | No | No | No |

Do **not** infer these permissions from `INVOICE.ADD`, `INVOICE.EDIT` or `INVOICE.APPROVE`.

---

## 17. INV-SCR-012 parent-state compatibility (D6)

INV-SCR-012 is the Credit Note IRN Outcome form.

- Its **Create** states are operational IRN-recording states.
- Its **Review-PendingApproval**, **Review-Approved** and **Review-ReturnedForCorrection** frames display the **parent Credit Note’s genuine lifecycle context**. They are **not** a separate IRN approval workflow.

These Review states must:

- Show the corresponding parent Credit Note state.
- **Not** expose an IRN-specific Approve or Return action.
- Route approval actions through **INV-SCR-011 only**.
- Allow IRN outcome recording **only after** the parent Credit Note is Approved and has entered Pending IRN.

Create-IRNFailed is a **latest-attempt visual fork** on a parent that remains `PENDING_IRN` (D7). It is not a Credit Note lifecycle value.

---

## 18. Manual failure-attempt behaviour (D7)

A failed Credit Note IRN attempt **does not** change the parent document out of `PENDING_IRN`.

Store the failure as an **append-only** `irn_attempt` with outcome `FAILURE` and **mandatory remarks**.

The UI may show “IRN Failed” as the latest attempt outcome/fork, but the **persisted Credit Note lifecycle remains `PENDING_IRN`**.

After failure:

- Show the approved **fresh manual recording** action.
- Do **not** call it an automatic retry.
- Do **not** release G1 reservation.
- Do **not** modify Invoice Credit Status.
- Do **not** create another Credit Note.
- Do **not** permit Edit, Cancel, Delete or number release.

A successful attempt:

- Sets the Credit Note to `IRN_GENERATED`.
- Terminates further outcome recording.
- Makes its G1 claim **permanent**.
- Recalculates the original Invoice Credit Status.
- Rechecks G1 (D2).

Forbidden: Generate IRN, queue, job, automatic retry, connection-health UI.

INV-012 Create-Draft: overlay only (no persisted IRN draft), matching Phase 1 INV-008.

Success fields: IRN, acknowledgement number, acknowledgement datetime **with timezone** (UX-DEC-005). Failure fields: remarks. Unselected field set is **absent**, not disabled.

---

## 19. Authoritative ₹75,089.00 seed and test worksheet (D9)

Seed and test `CRN-2026-000031` against IRN-generated `INV-2026-000119` using this worksheet. Do not invent other balancing values.

### Frozen original Invoice (header totals — must remain)

`INV-2026-000119` from `CH-2026-000481` / Beta Constructions / Chakan / intra-state CGST+SGST. Engine `phase1-v1`. Previously credited: **none**. `credit_status = NOT_CREDITED` until a **successful** Credit Note IRN. IRN Generated (`112026000123456`). Header G3:

| Step | Amount (₹) |
|---|---:|
| Line Subtotal | 149,000.00 |
| Additional Charges (LOADING fixed) | 2,500.00 |
| Discount Deduction (0.67% of 149,000.00) | 998.30 |
| Taxable Value (`149000 − 998.30 + 2500`) | 150,501.70 |
| GST (CGST 13,545.15 + SGST 13,545.15) | 27,090.30 |
| Total Before Round-off | 177,592.00 |
| Round-off Adjustment | **0.00** (row still shown) |
| Final Payable Total | **177,592.00** |

Tax split: intra-state only. IGST ₹0.00. Never mixed.

**Authorised seed alignment (implementation time only):** current Phase 1 seed stores INV-119 as a **single** M30 line totaling ₹149,000.00. D9 requires two product **value** snapshots whose subtotal remains ₹149,000.00, **without** changing header G3 totals or frozen material figures (116.000 / 18+12 / lot 230 remaining 13 / WST 067). Align `invoice_line` snapshots to the table below when Phase 4 seed work is authorised.

### Original Invoice lines (D9)

| Line | Product | Original Value | Already Credited | Remaining Before | Credit Now | Remaining After |
|---|---|---:|---:|---:|---:|---:|
| 1 | M30 RMC | ₹126,000.00 | ₹0.00 | ₹126,000.00 | ₹63,000.00 | ₹63,000.00 |
| 2 | Crushed Sand | ₹23,000.00 | ₹0.00 | ₹23,000.00 | ₹0.00 | ₹23,000.00 |

**Line Credit Subtotal: ₹63,000.00**

Line 2 remains **visible** with zero Credit Now. Do **not** physically delete it from the rendered selector. Persist only according to the approved zero-line storage rule, but its remaining creditable amount must remain visible.

### Charge and discount credits

| Item | Original | Already Credited | Remaining Before | Credit Now | Remaining After |
|---|---|---:|---:|---:|---:|
| Volume Discount | −₹998.30 | ₹0.00 | −₹998.30 | −₹422.10 | −₹576.20 |
| Loading Charge | ₹2,500.00 | ₹0.00 | ₹2,500.00 | ₹1,056.60 | ₹1,443.40 |

The partial-credit proportion for the affected charge base is approximately **42.28%**, **not** a blanket 50% across every Invoice component.

### Credit Note calculation

| Step | Amount |
|---|---:|
| Line Subtotal | ₹63,000.00 |
| Additional Charges | +₹1,056.60 |
| Discount Deduction | −₹422.10 |
| Taxable Value | ₹63,634.50 |
| CGST @ 9% | ₹5,727.11 |
| SGST @ 9% | ₹5,727.11 |
| Total Before Round-off | ₹75,088.72 |
| Round-off Adjustment | +₹0.28 |
| Final Credit Note Value | **₹75,089.00** |

### Authoritative arithmetic

```
₹63,000.00 + ₹1,056.60 − ₹422.10 = ₹63,634.50

₹63,634.50 × 9% = ₹5,727.105, rounded to ₹5,727.11 for CGST
₹63,634.50 × 9% = ₹5,727.105, rounded to ₹5,727.11 for SGST

₹63,634.50 + ₹5,727.11 + ₹5,727.11 = ₹75,088.72
₹75,088.72 + ₹0.28 = ₹75,089.00
```

`CRN-2026-000031` must reconcile **exactly** to ₹75,089.00 in seed and tests.

---

## 20. Route/controller/service/view matrix (planned after implementation is authorised)

CSRF on POST. No `/seed` `/fixture` `/demo` `/probe` `/test`. **No Credit Note cancel/delete/void path.**

| Method | Path | Screen | Permission |
|---|---|---|---|
| GET | `/invoices/unbilled` | INV-001 | `INVOICE.ADD` to create; `INVOICE.VIEW`/`LIST` as already used for invoice surfaces |
| POST | `/invoices/later` | INV-002 save | `INVOICE.ADD` |
| GET/POST | `/invoices/later/{id}` | INV-002 | VIEW/ADD/EDIT |
| POST | `/invoices/{id}/submit` · `/approve` · `/return` · `/cancel` | existing | existing |
| GET | `/invoices` | INV-004 | `INVOICE.LIST` |
| GET/POST | `/invoice-reservations` | INV-007 | `INVOICE.RESERVE_NUMBER` |
| POST | `/invoice-reservations/{id}/release` | INV-007 | `INVOICE.RELEASE_NUMBER` |
| GET | `/credit-notes` | INV-010 | `CREDIT_NOTE.LIST` |
| GET/POST | `/credit-notes/create`, `/credit-notes/{id}` | INV-011 | VIEW / CREATE / EDIT |
| POST | `/credit-notes/{id}/submit` | INV-011 | CREATE / EDIT |
| POST | `/credit-notes/{id}/approve` · `/return` | INV-011 | `CREDIT_NOTE.APPROVE` |
| GET/POST | `/credit-notes/{id}/irn` | INV-012 | VIEW / `RECORD_IRN_DETAILS` |

There must be **no** `/credit-notes/{id}/cancel`, `/delete`, or `/void`.

Views: extend `layouts/app`; reuse `c_nav_01_shell`, C-DAT-12, OV-06/07/11, card-list mobile-only, sticky-actions only when a control exists.

Services: `LaterInvoiceService` (or extend `InvoiceService`), `InvoiceReservationService`, `CreditNoteService`, extend `IrnRecordingService` + `NumberingService::history(..., creditNoteId)`.

---

## 21. Figma Frame Name to CI4 state-render mapping (D11)

`?frame=` overlay only when `CI_ENVIRONMENT` is `development` or `testing`. Production ignores `?frame=` (`FrameSimulation`). Testing omits the yellow preview banner.

| State token | Render |
|---|---|
| Default | Operational data |
| Draft | Saved selection / saved document |
| Filtered / Empty / NoResults / Loading / Error | List chrome |
| PermissionRestricted | Actions **removed**; copy explains missing permission |
| PendingApproval / Approved / ReturnedForCorrection | Genuine on INV-002 and **INV-011 (D1)**. On **INV-012** they show parent Credit Note genuine lifecycle context (D6) — not an IRN approval workflow |
| IRNGenerated / IRNFailed | Exclusive success/failure field sets. IRNFailed is latest-attempt fork; parent CN remains PENDING_IRN (D7) |
| ConcurrentUpdate | OV-11 |
| INV-001 PermissionRestricted | CSV: other-client/other-plant rows **disabled with reasons** (scope/status), plus missing Create if no `INVOICE.ADD` |

Exact CSV Frame Name in `data-frame`. FrameCatalog coverage key for the generated-IRN create frame is **`D-INV-SCR-012-Create-IRNGenerated`**. Record Figma `D-INV-SCR-012-IRNGenerated` for design traceability only. All remaining Phase 4 frames match CSV names directly.

---

## 22. Desktop/mobile responsive plan

All **15** `M-*` frames reuse the Phase 0–3 approved mobile shell:

- QA viewport 390×844
- `header.top-header` + 44×44 `button.nav-toggle`
- `#primary-nav` closed drawer (`translateX(-100%)`); no stacked sidebar
- `.card-list.mobile-only` instead of compressed tables
- `.form-grid` single column
- `.sticky-actions` only when an action exists
- `min-height: var(--component-touchtarget-min)` (44px)
- No horizontal overflow (`scrollWidth = 390`)

Do not add a new nav component.

| Mobile frame | Content |
|---|---|
| `M-INV-SCR-001-List-Default` | Selection cards, pinned summary |
| `M-INV-SCR-001-List-NoResults` | Empty filters |
| `M-INV-SCR-002-Create-Default` / `Error` | Line cards, stacked; sticky Save |
| `M-INV-SCR-004-List-Default` / `NoResults` | Invoice cards; preserve existing behaviour |
| `M-INV-SCR-007-List-Default` / `NoResults` | Reservation cards; reuse history expands |
| `M-INV-SCR-010-List-Default` / `NoResults` | CN + IRN badges; no Cancel |
| `M-INV-SCR-011-Create-Default` / `Error` | Credit input inside cards; line 2 visible at zero |
| `M-INV-SCR-012-Create-Default` / `Error` / `IRNGenerated` | Full-width fields; IRN wraps |

---

## 23. Audit, idempotency, locking and concurrency design

- Every reserve/consume/release, later invoice persist, CN save/approve/return, IRN attempt: `audit_event` append-only.
- Approval history + audit in the **same transaction** as G1 reservation (D2 step 9).
- `idempotent_command` new types (§7). Replay returns the existing document. No Credit Note cancel type.
- Locks: challans ascending; **D2 CN approval lock order**; sequence `FOR UPDATE`; never SKIP LOCKED on billing (expiry-only).
- `row_version` on invoice, reservation, CN headers → OV-11.
- U2 generated unique remains the concurrent billing brake.
- Concurrent CN approvals: second waiter revalidates after lock; cannot over-credit.
- IRN attempts: INSERT only.

---

## 24. Automated tests and exit criteria

Floor: **190 tests, 4807 assertions** remain green. The complete Phase 0–3 regression suite remains green.

When implementation is authorised, the corrected plan **explicitly requires** tests for all of the following:

1. Draft Credit Notes do not reserve G1.
2. Pending Approval Credit Notes do not reserve G1.
3. Returned for Correction Credit Notes do not reserve G1.
4. Approved Credit Notes reserve G1.
5. Pending IRN Credit Notes retain G1.
6. Failed IRN attempts retain G1 without double-counting.
7. IRN Generated Credit Notes permanently consume G1.
8. Two concurrent approvals cannot over-credit.
9. Line and charge caps are validated independently.
10. Matching grand totals cannot hide an over-credit on an individual line.
11. Zero-credit line 2 remains visible.
12. `CRN-2026-000031` reconciles exactly to ₹75,089.00.
13. Invoice Credit Status changes only after successful Credit Note IRN.
14. No Credit Note Cancel/Delete/Void route exists.
15. No Credit Note number is ever reused.
16. No automatic IRN job, retry or queue exists.
17. Permission absence removes controls and direct POST returns 403.
18. All 77 CSV frames render with meaningful state assertions.
19. All 15 mobile frames use the approved mobile shell.
20. The complete Phase 0–3 regression suite remains green.

### Additional coverage (still required)

| Area | Tests |
|---|---|
| Unit | `Phase4Frames` count 77 (62 D / 15 M / 0 T); exact CSV names; D11 alias documented not substituted as coverage key; catalogue uses D5/D8 names only; no `INWARD.APPROVE`; calculator 8-step + ₹0.00 round-off |
| Schema | New tables InnoDB; G3 CHECKs on CN header; no same-table trigger; `chk_reg_reuse_invoice` unchanged; no CREDIT_NOTE reuse |
| INV-001/002 | Eligibility; plant lock; U2 concurrent; create ≠ approve; all source challans Approved; G3 totals |
| INV-007 | Reserve binds client/challans; consume; release; consumed Release disabled+reason; IRN-generated never reused; concurrent reserve; D8 403 |
| INV-004 | Nine frames still HTTP 200; later and reservation-origin invoices appear; Credit Status; Invoice IRN actions; Create Credit Note only if IRN Generated and remaining creditable |
| INV-012 | Swap fields; success terminates; Review frames have no IRN Approve/Return; 403 without `CREDIT_NOTE.RECORD_IRN_DETAILS`; recording only after parent PENDING_IRN |
| IAM | Active role; Patil no union; Plant Manager 403; Billing Clerk no CN and no reserve/release; unauthorized POST 403 |
| Frozen | Ledger 116.000; 18+12; lot 230 remaining 13; 3+7+2; WST 067 Approved; INV 119 header = 177592.00; 110 Phase 3 frames |

Browser: desktop happy path per new screen; 390×844 all 15 mobile frames; no new shell.

---

## 25. Phase 0–3 frozen-regression matrix

| Identity | Must remain |
|---|---|
| PHPUnit floor | 190 / 4807 |
| Ledger M30 Chakan | 116.000 MT |
| Allocation CH-2026-000481 | 18.000 + 12.000 = 30.000 |
| LOT-2026-00230 remaining | 13.000 MT |
| REJ-2026-000044 | 3.000 + 7.000 + 2.000 = 12.000 |
| WST-2026-000067 | Approved |
| INV-2026-000119 | Header **₹177,592.00**, IRN Generated, `NOT_CREDITED` until a **successful** CN IRN. Line snapshots aligned to D9 at Phase 4 seed time without changing this header total. |
| Phase 3 frames | 110 (91 D + 19 M) |
| `INWARD.REVERSE` | Store Keeper + Super Admin only |
| `INWARD.APPROVE` | Absent |
| Mobile shell | Header + hamburger drawer |
| Production routes | No seed/fixture/demo/probe/test; `?frame=` ignored |
| Manual invoice IRN | Unchanged INV-008 behaviour |

---

## 26. Entry and exit criteria

### Entry (for implementation)

- This corrected plan is approved **and** the Business Owner **authorises implementation**. D1–D12 are already decided; they are not reopened as blockers.
- Phase 0–3 closed; 190 / 4807 green.
- External handoff documents (D12) remain the named sources; this packet is the implementation decision record.

### Exit

- Tests 1–20 in §24 are green.
- 77 frames render with exact CSV names and meaningful state assertions.
- INV-004 integration without Phase 1 reconstruction.
- Later/bulk invoice + reservation + CN + manual CN IRN as specified.
- G1 consuming set is exactly APPROVED / PENDING_IRN / IRN_GENERATED.
- Frozen matrix §25 holds.
- No IRN job/API/retry/queue. No Credit Note Cancel/Delete/Void. No Phase 5.

### Stop until implementation is authorised

Do not write application code, migrations, routes, Views, seeders, or permissions. Do not start Phase 5.

---

## 27. Decision register (D1–D12 closed)

Earlier plan conflicts 1–10 are **closed** by D1–D12. Do not re-open them as implementation blockers.

| Closed item | Binding answer |
|---|---|
| D1 Credit Note lifecycle | DRAFT → PENDING_APPROVAL → APPROVED → PENDING_IRN → IRN_GENERATED. Alternate: PENDING_APPROVAL → RETURNED_FOR_CORRECTION → PENDING_APPROVAL. Use `CREDIT_NOTE.APPROVE`. Creator self-approval only if active role holds that permission. No Draft → Pending IRN. INV-011 CSV approval states are genuine operational states. |
| D2 G1 reservation | Do not reserve from Draft. Consuming set: APPROVED, PENDING_IRN, IRN_GENERATED. Reservation begins atomically on PENDING_APPROVAL → APPROVED with the specified lock order. Concurrent second approval waits and revalidates. Recheck G1 on successful CN IRN. |
| D3 Returned for Correction | Before approval; no G1 reservation; must resubmit and re-approve; Approved CN is not editable and does not move backwards. |
| D4 Cancellation | Deferred. Not a Phase 4 defect. No `CREDIT_NOTE.CANCEL`. No Cancel/Delete/Void. No number release. Numbers never reused. |
| D5 CN permissions | Exact names and grant matrix in §15. No ADD / RECORD_IRN / CANCEL aliases. PermissionRestricted removes actions; unauthorised POST 403. |
| D6 INV-012 Review | Parent CN genuine lifecycle context. No IRN Approve/Return. Approval via INV-011 only. IRN recording only after Approved + Pending IRN. |
| D7 Manual IRN failure | Parent stays PENDING_IRN. Append-only FAILURE + remarks. Fresh manual recording, not automatic retry. Retain G1. No invoice credit_status change. Success → IRN_GENERATED, G1 permanent, recalc invoice credit_status. |
| D8 Invoice reservation permissions | Add only `INVOICE.RESERVE_NUMBER` and `INVOICE.RELEASE_NUMBER`. AM + Super Admin Yes; Billing Clerk / Plant Manager / Store Keeper No. Do not infer from ADD/EDIT/APPROVE. |
| D9 ₹75,089.00 | Authoritative worksheet in §19. Seed and test CRN-2026-000031. Charge proportion ≈ 42.28%, not blanket 50%. Line 2 visible at zero. |
| D10 INV-SCR-004 | Integration and regression only. Include immediate, later/bulk, reservation-origin, Credit Status, Invoice IRN actions, Create Credit Note only if IRN Generated and remaining creditable. |
| D11 Frame alias | Coverage key `D-INV-SCR-012-Create-IRNGenerated`. Figma `D-INV-SCR-012-IRNGenerated` is design traceability only. Remaining frames match CSV names directly. |
| D12 Sources | BRD v0.9.22, Technical Design v0.4.1, Database Architecture v1.2, FRIS v1.0.1, Figma Production Blueprint v1.1, Authoritative CSV manifest v1.1 are external handoff documents. This packet records the approved Phase 4 decisions derived from them. |

---

## 28. Proposed implementation checkpoints (do not start)

No checkpoint starts until the Business Owner **authorises implementation** of this corrected plan.

| Checkpoint | Contents | Tests |
|---|---|---|
| **0** | This corrected plan on `main` (docs only) | None new |
| **A** | Additive migration; D5 `CREDIT_NOTE.EDIT` + D8 reservation codes + skip-if-exists grants; `Phase4Frames` catalog using CSV names | Schema + unit frame count; D11 key |
| **B** | INV-001 / INV-002 later-bulk + U2 | Integration + HTTP frames 21 |
| **C** | INV-007 reservation UI on existing `NumberingService`; D8 403 | Numbering + HTTP 12 |
| **D** | INV-004 integration (origins, Credit Status, IRN actions, Create CN gating) | HTTP 9 regression |
| **E** | INV-010 / INV-011 + D1 FSM + D2 G1 | Tests 1–4, 8–12, 14–15, 17; HTTP 20 |
| **F** | INV-012 + `irn_attempt` FK + D6/D7 | Tests 5–7, 13, 16; IRN swap; HTTP 15 |
| **G** | Seed CRN-2026-000031 per §19; INV-119 line alignment without header-total change; frozen regression | ₹75,089.00; 190+ tests; 116.000 / 177592.00 |
| **H** | Closeout; production-route scan; 77-frame + 15-mobile QA; Phase 0–3 suite | Tests 18–20 |

Suggested commit subjects after implementation is authorised, one per checkpoint, same style as Phase 3. **Do not implement in this turn.**

---

## Confirmation

- Phase 4 application code, migrations, routes, Views, seeders, and permissions were **not** started in this turn.
- Phase 5 was **not** started.
- D1–D12 are recorded as approved decisions in this plan.
- Implementation waits for explicit Business Owner authorisation of this corrected plan.
