# Phase 2 plan (implemented)

**Status:** Approved, implemented, and **closed**. Phase 3 implementation has not started. The Phase 3 plan is `docs/PHASE3_PLAN.md` (awaiting approval).  
**Phase 1:** Approved and **closed**. Preserve every Phase 1 route, seed, ledger figure, and passing test.  
**Stack (locked):** CodeIgniter 4, PHP 8.2+, MySQL 8.0.16+ InnoDB, server-rendered CI4 Views, progressive JavaScript only, Composer, CI4 migrations and seeders, database sessions, PHPUnit, existing Phase 0/1 architecture.

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

**Closed Phase 1 wording (documentation only, already recorded):**  
Invoice approval is a separate action from Challan approval, but the Challan must be Approved first.

**Precedence for Phase 2:** this plan’s non-negotiable rules → BRD v0.9.22 → approved addenda (including DB-CHG-001–006) → TDD v0.4.1 if no BRD contradiction → Database Architecture v1.2 intent translated to MySQL 8 InnoDB → FRIS v1.0.1 → Blueprint/Figma → CSV frames.

Where FRIS or Blueprint page titles contradict the non-negotiable rules in this plan, this plan wins. CSV **Frame Name** strings remain the visual coverage contract.

**Authoritative CSV:** `uploads/RMC_Crusher_ERP_Phase1_Figma_Frame_Component_Manifest_v1.1_4e22.csv` (product-wide 606 frames; filename is historical).

---

## 1. Exact manifest frame inventory by Screen ID and breakpoint

**Phase 2 total: 216 frames. Desktop 175. Mobile 41. Tablet 0.**

Tablet count is zero on every Screen ID. Blueprint: the responsive rule fully describes tablet adaptation; no separate T frame is drawn.

None of the 12 approved frame-name aliases apply. Those aliases are RPT-SCR-003/006/012, INV-SCR-008/012, and AUD-SCR-001 only. All 216 Phase 2 frame names are **direct manifest matches**.

CSV QA Status on all 216 rows: Not started. Build priority: 7 P1 (STAT-SCR-001), 112 P2, 97 P3.

CSV Screen Names (Figma page titles) differ from the approved functional names for VEH and PRC. Frame **file names** stay exactly as CSV. Functional behaviour follows §2 and the non-negotiable rules.

| Screen ID | Approved function | CSV Screen Name | Template | D | M | T | Total | Frame IDs |
|---|---|---|---|---:|---:|---:|---:|---|
| CMP-SCR-001 | Company List | Company List | T01 | 7 | 2 | 0 | 9 | FR-0001–0009 |
| CMP-SCR-002 | Company Add/Edit/View | Company Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0010–0020 |
| CMP-SCR-003 | Plant/Mapping List | Plant / Mapping List | T01 | 7 | 2 | 0 | 9 | FR-0021–0029 |
| CMP-SCR-004 | Plant Add/Edit + Company Mapping | Plant Add / Edit + Company Mapping | T02 | 9 | 2 | 0 | 11 | FR-0030–0040 |
| PTY-SCR-001 | Client List | Client List | T01 | 7 | 2 | 0 | 9 | FR-0041–0049 |
| PTY-SCR-002 | Client Add/Edit/View | Client Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0050–0060 |
| PTY-SCR-003 | Client Site List | Client Site List | T01 | 7 | 2 | 0 | 9 | FR-0061–0069 |
| PTY-SCR-004 | Client Site Add/Edit/View | Client Site Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0070–0080 |
| PTY-SCR-005 | Vendor List | Vendor List | T01 | 7 | 2 | 0 | 9 | FR-0081–0089 |
| PTY-SCR-006 | Vendor Add/Edit/View | Vendor Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0090–0100 |
| PRD-SCR-001 | Product List | Product List | T01 | 7 | 2 | 0 | 9 | FR-0101–0109 |
| PRD-SCR-002 | Product Add/Edit/View | Product Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0110–0120 |
| PRD-SCR-003 | Raw Material Add/Edit/View | Raw Material Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0121–0131 |
| PRD-SCR-004 | Unit Master inline Add/Edit | Unit List and Add / Edit | T01 | 7 | 2 | 0 | 9 | FR-0132–0140 |
| RCP-SCR-001 | Recipe Definition | Recipe Definition | T02 | 9 | 2 | 0 | 11 | FR-0178–0188 |
| VEH-SCR-001 | Vendor/Supplier selection (read-only) | Vehicle Selection View | T01 | 7 | 1 | 0 | 8 | FR-0141–0148 |
| VEH-SCR-002 | Manufacturer List inline Add/Edit | Vehicle List | T01 | 7 | 2 | 0 | 9 | FR-0149–0157 |
| VEH-SCR-003 | Brand List inline Add/Edit | Vendor Vehicle List | T01 | 7 | 2 | 0 | 9 | FR-0158–0166 |
| VEH-SCR-004 | Vehicle Add/Edit/View + supplier history | Vehicle Add / Edit | T02 | 9 | 2 | 0 | 11 | FR-0167–0177 |
| PRC-SCR-001 | Universal rate configuration/history | Universal Rate Configuration | T07 | 6 | 1 | 0 | 7 | FR-0189–0195 |
| PRC-SCR-002 | Company-specific rate configuration/history | Company-Specific Rate Configuration | T07 | 6 | 1 | 0 | 7 | FR-0196–0202 |
| ACH-SCR-001 | Additional Charge Master | Additional Charge Configuration | T07 | 6 | 1 | 0 | 7 | FR-0203–0209 |
| STAT-SCR-001 | Statutory Timing Configuration | Statutory Deadline Configuration | T07 | 6 | 1 | 0 | 7 | FR-0210–0216 |
| **Total** | | | | **175** | **41** | **0** | **216** | FR-0001–0216 |

### 1.1 Frame names and states (exact CSV)

List screens (T01) — pattern for CMP-001/003, PTY-001/003/005, PRD-001/004, VEH-001/002/003:

| Device | Mode | State | Overlay | Typical data scenario |
|---|---|---|---|---|
| D | List | Default | — | Populated table |
| D | List | Filtered | OV-07 Filter Drawer | Filters applied |
| D | List | Empty | — | No records exist yet |
| D | List | NoResults | — | Filters match nothing |
| D | List | Loading | — | Eight row skeletons |
| D | List | Error | — | Retrieval failed |
| D | List | PermissionRestricted | — | Create/row actions absent (not disabled) |
| M | List | Default | — | Record cards |
| M | List | NoResults | — | Filters match nothing |

VEH-SCR-001 has **no** `M-*-List-NoResults` (8 frames, not 9).

Form screens (T02) — CMP-002/004, PTY-002/004/006, PRD-002/003, RCP-001, VEH-004:

| Device | Mode | State | Overlay | Typical data scenario |
|---|---|---|---|---|
| D | Create | Default | — | Empty form |
| D | Edit | Default | — | Populated record |
| D | Edit | Error | — | Duplicate identifier / validation rejected |
| D | Edit | Draft | OV-14 Unsaved Changes | Dirty state |
| D | View | Default | — | Read-only, role without edit |
| D | View | Cancelled | — | Inactive record, historical read-only |
| D | Edit | PermissionRestricted | OV-12 Deactivation Blocked | Deactivation blocked by dependencies |
| D | Edit | ConcurrentUpdate | OV-11 Concurrency | Record changed by another user |
| D | Create | Loading | — | Form skeleton |
| M | Create | Default | — | Empty form, stacked |
| M | Edit | Error | — | Validation errors, stacked |

Configure screens (T07) — PRC-001/002, ACH-001, STAT-001:

| Device | Mode | State | Overlay |
|---|---|---|---|
| D | Configure | Default | — |
| D | Configure | Draft | — |
| D | Configure | Error | — |
| D | Configure | Empty | — |
| D | Configure | Loading | — |
| D | Configure | PermissionRestricted | — |
| M | Configure | Default | — |

OV-13 Deactivation Warning is **not** a dedicated CSV frame. Blueprint lists it as a dialog on master lists/forms. Render it as an overlay on the Edit-Default / list Default surface when deactivation is allowed. Do not invent a Frame ID.

PermissionRestricted on **lists** means missing permission (controls **absent**). PermissionRestricted on **T02 Edit** is OV-12 (status-blocked deactivation, control visible path via Deactivate then dialog). Do not conflate the two.

Exact Frame Names are `D-{SCREEN}-{Mode}-{State}` and `M-{SCREEN}-{Mode}-{State}` as in the CSV (for example `D-CMP-SCR-001-List-Default`, `D-PRC-SCR-001-Configure-Default`).

---

## 2. Requirement traceability matrix

| Screen ID | BRD | FRIS | DB Arch | Blueprint | UX / addenda | Phase 2 behaviour |
|---|---|---|---|---|---|---|
| CMP-SCR-001/002 | CMP-FR-001, CMP-FR-002 | §9.1 fields/grids/actions | `company` | 12.1–12.2 | UX-DEC-001, UX-DEC-005 | Super Admin company CRUD. Timezone default Asia/Kolkata. GSTIN unique `as_cs`. Deactivate → OV-12 if deps, else OV-13. |
| CMP-SCR-003/004 | CMP-FR-003 | §9.1 mapping | `plant`, `company_plant` | 12.3–12.4 | Shared plant | Chakan shared by Apex RMC and Apex Aggregates. Removing a pairing **blocked** when any transaction exists for that `company_plant_id` (OV-12, Close only). FRIS “confirm and proceed on open txns” is **withdrawn**. |
| PTY-SCR-001/002 | PTY-FR-001 | §9.2 | `client`, `client_company` | 12.5–12.6 | GSTIN unique where supplied | Clients separate from vendors. Client GSTIN mandatory (FRIS). Do not auto-merge duplicates. Mapping table required. |
| PTY-SCR-003/004 | PTY-FR-003 | §9.2 | `client_site` | 12.7–12.8 | Site ≠ POS | No GSTIN/PAN on site. Parent client by ID. Site address never drives GST place of supply. |
| PTY-SCR-005/006 | PTY-FR-002 | §9.2 | `vendor` + **missing** `vendor_company` | 12.9–12.10 | Vendor GSTIN optional | Separate master. GSTIN optional; unique `as_cs` where supplied. Company mapping table. |
| PRD-SCR-001/002 | PRD-FR-001, PRD-FR-004 | §9.3 | `product`, `product_company`; GST today on `product` | 12.11–12.12 | Track by lot read-only | Finished Product path. Status on all non-loading form states. Created / Last Modified read-only. Track by lot read-only Yes. |
| PRD-SCR-003 | PRD-FR-003 | §9.3 | same `product` table, `RAW_MATERIAL` | 12.13 | Recipe lines | Raw Material path. Classification not user-switchable after save. |
| PRD-SCR-004 | PRD-FR-002 | §9.3 | `unit_master` | 12.14 | Inline only | Inline Add/Edit on the list. **No** separate create route or modal. **No** Delete. |
| RCP-SCR-001 | RCP-FR-001 | §9.5, DB-CHG-001 | **missing** `recipe`, `recipe_line` | 12.19 | Absolute qty | Informational only; never consumes stock. Absolute MT quantities, not %. Sample: OPC 53 Cement 0.350 MT, 20 mm Aggregate 0.700 MT, M-Sand 0.650 MT. Duplicate RM and zero qty validated independently. View removes Add, Remove, Save. One current recipe per client+finished product. Overwrite replaces `recipe_line` set; no business version history. |
| VEH-SCR-001 | VEH-FR-001 | Decision A §9.4 | `vendor` (read) | CSV “Vehicle Selection View” | User non-negotiable | Read-only Vendor/Supplier selection. **No** Add, Edit, or create route. No navigation to a supplier-entity form from this screen. |
| VEH-SCR-002 | VEH-FR-002 | §9.4 Manufacturer | **missing** `vehicle_manufacturer` | CSV “Vehicle List” | Inline | Manufacturer inline Add/Edit. Do not implement Blueprint Capacity column. |
| VEH-SCR-003 | VEH-FR-002 | §9.4 Brand | **missing** `vehicle_brand` | CSV “Vendor Vehicle List” | Inline | Brand inline Add/Edit under manufacturer. No Capacity / Unit / own-fleet / Vehicle-Type. |
| VEH-SCR-004 | VEH-FR-003/004 | §9.4 Vehicle | `vehicle` thin; **missing** brand FK + assignment history | 12.18 | DB-CHG-002 | Registration uppercase, no spaces, unique `as_cs`. Brand/Model + Current Supplier. Exactly one active assignment. Change supplier closes prior row and inserts new row in one transaction. No Delete. |
| PRC-SCR-001 | PRC-FR-003, PRC-FR-004 | CSV Universal T07, **not** FRIS Batch-2 “Rate List” Screen ID mapping | `rate_master` (`company_id` NULL) | 12.20 | Generated unique | Universal rates. History immutable. New effective rate closes prior active row transactionally. |
| PRC-SCR-002 | PRC-FR-002, PRC-FR-004 | CSV Company-Specific T07 | `rate_master` (`company_id` NOT NULL) | 12.21 | Same uniqueness | Company-specific overrides universal at invoice time (existing calculator). |
| ACH-SCR-001 | ACH-FR-001–004 | charge master | `additional_charge_master` incomplete vs DB Arch | 12.22 | Closed calc order | Ordinary charges FIXED only. Discount FIXED or PERCENTAGE. Discount reduces taxable value before GST. Charges use invoice place of supply. Keep Phase 1 `InvoiceCalculator` display order. |
| STAT-SCR-001 | STAT-FR-001–004 | § statutory | `statutory_timing_configuration`; **missing** history | 12.23, UX-DEC-005 | Deadline instant | Store UTC `DATETIME(6)`. Display full company timezone including Asia/Kolkata. Deadline instant ineligible (`<`, not `<=`). Missing config **blocks**; never silent bypass. Config history auditable. |

Security / data rules (all 23 screens):

- Active-role permissions only; never union held roles.
- Company and Plant scope in filters, services, repositories; re-validate inside every write transaction.
- Permission-denied controls **absent**. Status-blocked controls **visible, disabled, full reason**.
- Optimistic `row_version` on mutable masters.
- Ordered row locks for mapping, active-rate, and supplier-assignment changes.
- BCMath / canonical decimal strings; never PHP floats.
- `utf8mb4_0900_as_cs` for codes, GSTINs, registration numbers.
- `utf8mb4_0900_ai_ci` for human-readable names (do not rebuild existing Phase 0 unique name indexes).
- Audit event in the same transaction as the write.
- Never physically delete operational or master records.
- No prototype `?frame=` bypass of permissions or persisted state. Production ignores `?frame=`.
- No production seed, fixture, demo, probe, or test routes.

---

## 3. Existing-table gap analysis

Phase 1 shipped **seed-only repositories** (`MasterQuery` selectors, `MasterSeeder`) and **no catalogue CRUD screens**. Those tables stay. Phase 2 adds CRUD domains and **additive** schema only.

| Table today | Phase 1 use | Phase 2 gap | Action |
|---|---|---|---|
| `company` | Seed Apex RMC / Aggregates; timezone; GSTIN | Missing legal_name, short_code, PAN, address lines, city, pin, business_type, created_by, row_version. `company_name` unique is `as_cs` (Phase 0 — keep). | ALTER ADD columns. Do not DROP/rebuild. |
| `plant` | Chakan shared; Talegaon; Ranjangaon | Missing plant_code, plant_address, plant_type, created_by, row_version | ALTER ADD |
| `company_plant` | Shared mapping | Missing permitted_business_types, row_version; no transactional unmap guard in app | ALTER ADD; service-level OV-12 |
| `client` | Beta Constructions (renamed from the earlier Horizon placeholder) plus Metro Infra, Sunrise Developers, Delta Supplies; GSTIN NOT NULL unique | Missing address, PAN, phone, created_by, row_version | ALTER ADD |
| `client_company` | One mapping | Missing created_at/updated_at, row_version | ALTER ADD |
| `client_site` | Hinjawadi Phase 2 / Wakad Tower Site / Baner Road Project | Missing site_address (mandatory in FRIS), created_by, row_version | ALTER ADD |
| `vendor` | Western Cement Suppliers and Reliable Aggregates; **owned by `company_id`**; no GSTIN | Vendor GSTIN optional; should be many-to-many via `vendor_company` | ADD `vendor_company`; ADD nullable gstin + address; **keep** `vendor.company_id` populated for Phase 1 inward (do not DROP) |
| `product` | M30 + OPC53; GST columns on product | No `product_gst` table; no plant-optional mapping; no decimal_precision; no track_by_lot; no row_version | ADD `product_gst` backfilled from product GST columns; dual-write so Phase 1 calculator (reads `product.*_rate_percent`) stays correct. ADD nullable `company_plant_id` on `product_company` only if required; default NULL = all plants. Do not DROP product GST columns. |
| `unit_master` | MT, KG | No row_version; CRUD missing | ALTER ADD row_version; inline CRUD; no DELETE |
| `vehicle` | MH12AB1234 only | No brand FK, no assignment history | ADD manufacturer/brand/history; ALTER vehicle ADD `vehicle_brand_id`, row_version |
| `rate_master` | One company-specific M30 rate; generated `active_scope_key` | No `effective_to`, no created_by, no row_version; history close-out not implemented as CRUD | ALTER ADD; keep generated unique; never UPDATE amount in place |
| `additional_charge_master` | LOADING fixed, DISC % | Missing HSN/GST on charge, allow_multiple, description | ALTER ADD; keep kind/method CHECKs |
| `statutory_timing_configuration` | Universal 72 HOUR IRN window | No history table; duration model vs “exact instant” display | ADD history; evaluate deadline as UTC instant from event + limit in company TZ; do not silently default |
| — | — | `recipe`, `recipe_line` | **New tables** (DB-CHG-001) |
| — | — | `vehicle_manufacturer`, `vehicle_brand`, `vehicle_supplier_assignment_history` | **New tables** (DB-CHG-002/003) |
| — | — | `statutory_timing_configuration_history` | **New table** |
| — | — | `vendor_company`, `product_gst` | **New tables** |
| Phase 1 txn tables | challan/invoice/inward/wastage/disposition/numbering | Must keep FKs valid | No rebuild. New FKs from new tables onto existing PKs only (RESTRICT) |

**Seed-only domains that become complete CRUD:** company, plant, company_plant, client, client_company, client_site, vendor, product (finished + raw), unit_master, rate_master, additional_charge_master, statutory_timing_configuration, vehicle.

**New CRUD domains (no Phase 1 table):** recipe, manufacturer, brand, supplier assignment history.

`MasterQuery` remains the **selector** API for Phase 1 screens (active only). Phase 2 services own writes. Inactive records stay visible on master View and on historical transactions; they are excluded from new-transaction selectors.

---

## 4. Additive migration plan

One CI4 migration per concern, dated after `2026-09-12-100009`, never rewriting Phase 0/1 migration files.

| Order | Migration | Additive work | Data backfill | Forbidden |
|---|---|---|---|---|
| 1 | Master row versions and audit columns | `row_version INT UNSIGNED NOT NULL DEFAULT 1` on company, plant, company_plant, client, client_company, client_site, vendor, product, product_company, unit_master, vehicle, rate_master, additional_charge_master | Default 1 | Changing existing PK/UK names |
| 2 | Company/plant statutory fields | company: legal_name, short_code (`as_cs`), pan (`as_cs` nullable unique where not null), address_line_1/2, city, pin_code, business_type CHECK (`RMC`,`CRUSHER`,`COMBINED`), created_by FK. plant: plant_code (`as_cs`), plant_address, plant_type, created_by. company_plant: permitted_business_types | Seed Apex RMC=`RMC`, Apex Aggregates=`CRUSHER`; Chakan `COMBINED` intersection | Recreating `company` |
| 3 | Party fields + `vendor_company` | client address/PAN; client_site.site_address; vendor.gstin nullable `as_cs` unique; vendor.registered_address; CREATE `vendor_company` (vendor_id, company_id, is_active, timestamps, UK vendor+company) | Insert vendor_company from existing vendor.company_id (Western Cement Suppliers → Apex RMC) | DROP vendor.company_id |
| 4 | `product_gst` + product extras | CREATE `product_gst` PK product_id, cgst/sgst/igst, CHECK ≥ 0. product.decimal_precision, product.track_by_lot TINYINT default 1 (read-only Yes). Optional product_company.company_plant_id NULL | Copy GST from product columns | DROP product GST columns; change classification CHECK |
| 5 | Vehicle catalogue | CREATE manufacturer, brand; ALTER vehicle ADD vehicle_brand_id NULL first; backfill Tata Motors + Signa and Ashok Leyland + Dost; then NOT NULL; CREATE assignment_history; seed MH12AB1234 → Western Cement Suppliers open assignment | See §12 | Capacity/unit/fleet columns |
| 6 | Recipe | CREATE recipe + recipe_line (DB-CHG-001) | Beta Constructions + M30 composition §12 | Percentage columns |
| 7 | Rate/charge/statutory extras | rate: effective_to DATETIME(6) NULL, closed_at_utc, created_by, change_reason. charge: description, hsn_sac_code, gst percents, allow_multiple_rows. CREATE statutory history; generated unique for one active statutory rule per (rule_type, company_id) | 72h IRN row remains; copy to history CREATED | In-place rate amount edits |
| 8 | Generated uniqueness extras | vehicle open-assignment generated key (MySQL equivalent of partial unique); vendor gstin unique allowing multiple NULLs (MySQL unique allows many NULLs — acceptable); charge ordinary+percentage CHECK | Validate zero violating rows before unique | PostgreSQL `UNIQUE … WHERE` |

MySQL generated-column uniqueness (existing rate pattern):

```sql
-- vehicle: at most one open assignment
assigned_open_key VARCHAR(32) GENERATED ALWAYS AS (
  CASE WHEN assigned_until_utc IS NULL THEN CAST(vehicle_id AS CHAR) END
) STORED, UNIQUE KEY uq_vehicle_open_assignment (assigned_open_key)

-- statutory: at most one active row per rule + company scope
active_rule_key VARCHAR(80) GENERATED ALWAYS AS (
  CASE WHEN is_active = 1 THEN CONCAT(rule_type, ':', IFNULL(company_id, 0)) END
) STORED
```

Keep existing `rate_master.active_scope_key` unchanged: `CONCAT(IFNULL(company_id,0), ':', client_company_id, ':', product_id)` when `is_active = 1`.

---

## 5. Table, FK, index and generated-column constraint plan

### 5.1 New tables

**vendor_company**  
PK `vendor_company_id`. FK vendor RESTRICT, company RESTRICT. UK `(vendor_id, company_id)`. CHECK is_active 0/1.

**product_gst**  
PK `product_id` FK product RESTRICT. cgst/sgst/igst `DECIMAL(5,2)` NOT NULL CHECK ≥ 0.

**vehicle_manufacturer**  
PK `manufacturer_id`. `manufacturer_name` varchar ai_ci unique. `is_active`, `row_version`, timestamps, created_by.

**vehicle_brand**  
PK `vehicle_brand_id`. FK manufacturer RESTRICT. UK `(manufacturer_id, brand_name)`. `is_active`, `row_version`.

**vehicle_supplier_assignment_history**  
PK `assignment_id`. FK vehicle, vendor RESTRICT. `assigned_from_utc DATETIME(6)` NOT NULL, `assigned_until_utc DATETIME(6)` NULL. Generated unique open key. CHECK until IS NULL OR until > from.

**recipe**  
PK `recipe_id`. FK `client_company_id`, `finished_product_id` (product, classification enforced in app + CHECK trigger-equivalent: app validates FINISHED_PRODUCT). UK `(client_company_id, finished_product_id)`. `is_active`, `row_version`, created_by, timestamps.

**recipe_line**  
PK `recipe_line_id`. FK recipe, raw_material product RESTRICT. UK `(recipe_id, raw_material_id)`. `quantity DECIMAL(14,4)` CHECK `quantity > 0`. `sequence INT`. No percentage column.

**statutory_timing_configuration_history**  
PK history_id. FK configuration RESTRICT. event_type CHECK (`CREATED`,`DEACTIVATED`). snapshot of rule_type, company_id, limit_value, limit_unit, effective_from, change_reason, actor, `recorded_at_utc DATETIME(6)`. Append-only. No UPDATE/DELETE in app.

### 5.2 FKs from new/altered masters into Phase 1

All ON DELETE/UPDATE RESTRICT. No SET NULL on operational history.

Vehicle.brand → manufacturer. Assignment.vendor → vendor (not a new supplier table). Recipe.finished_product → product. Recipe_line.raw_material → product.

### 5.3 Indexes

List filters: `(is_active, name)`, GSTIN/cs codes unique, `client_id` on site, `(company_id, is_active)` on mappings, `recipe (client_company_id, finished_product_id)`, `rate (client_company_id, product_id, effective_from)`, `assignment (vehicle_id, assigned_from_utc)`.

---

## 6. Master lifecycle and deactivation dependency matrix

Never physical DELETE. View-Cancelled / Inactive remains historically viewable.

| Entity | Activate/Deactivate permission | OV-12 block when | OV-13 when | Selector after inactive |
|---|---|---|---|---|
| Company | `COMPANY.ACTIVATE_DEACTIVATE` | Any active plant mapping, user grant, FY sequence, challan/invoice/inward/wastage for that company | No blocking deps; reason required | Hidden from new-tx company pickers |
| Plant | `PLANT.ACTIVATE_DEACTIVATE` | Any active company_plant with transactions or grants; inventory lots | Allowed | Hidden from new-tx plant pickers |
| Company–Plant association | `PLANT.EDIT` | **Any** transaction (inward, challan, invoice link, wastage, disposition, lot balance) for that `company_plant_id` | Association unused | Pairing cannot be selected |
| Client | `CLIENT.ACTIVATE_DEACTIVATE` | Active client sites; **nonterminal** challans (`DRAFT`, `PENDING_APPROVAL`, `RETURNED_FOR_CORRECTION`, `APPROVED`); **nonterminal** invoices; active rates; active recipes | Unused, or only terminal historical documents | Hidden from challan client selector |
| Client Site | `CLIENT_SITE.ACTIVATE_DEACTIVATE` | **Nonterminal** challans on `client_site_id` | Terminal billed/cancelled challans do not alone block | Hidden from site selector |
| Vendor | `VENDOR.ACTIVATE_DEACTIVATE` | Current open vehicle supplier assignment; open/nonterminal inward for an active company mapping (inward has **no** status — posted rows are historical) | A completed historical inward alone does not block | Hidden from inward/VEH-001 |
| Product (FP or RM) | `PRODUCT` / `RAW_MATERIAL`.ACTIVATE_DEACTIVATE | Positive available/rejected/reserved stock; active recipe header/lines; **nonterminal** challan/invoice lines; allocations on **nonterminal** challans | Terminal historical rows alone do not keep the product Active | Hidden from new-tx product pickers |
| Unit | `UNIT.EDIT` (no Delete) | Referenced by any product, inward, challan line, or movement | Never delete; deactivate only if unused | Hidden from new product unit select |
| Recipe | `RECIPE.EDIT` | (Header inactive does not delete lines.) Block product deactivation instead when referenced | Recipe overwrite is not deactivation | Inactive recipe not offered as current |
| Manufacturer | `VEHICLE.EDIT` | An **active** brand or an **active** vehicle references it | Allowed when no active brand/vehicle remains; historical inactive vehicles stay readable | Hidden from new brand/vehicle select |
| Brand | `VEHICLE.EDIT` | An **active** vehicle references this brand | Allowed if no active vehicle; inactive vehicles remain readable | Hidden from new vehicle select |
| Vehicle | `VEHICLE.ACTIVATE_DEACTIVATE` | **Nonterminal** challans using the vehicle | Billed/cancelled challans do not alone block | Hidden from challan vehicle select |
| Rate | `RATE.ADD` + deactivate prior | N/A — “deactivate” = close prior active by inserting successor | History remains | Lookup uses active row only |
| Additional charge | `CHARGE.ACTIVATE_DEACTIVATE` | Nonterminal invoice statuses: `DRAFT`, `PENDING_APPROVAL`, `RETURNED_FOR_CORRECTION`, `APPROVED_PRE_IRN`, `PENDING_IRN`, `IRN_FAILED` | Terminal `IRN_GENERATED` or `CANCELLED` snapshots do not block | Hidden from add-charge picker |
| Statutory config | `STATUTORY.MANAGE` | Deactivation would leave the rule type without an active **applicable** rule (`IrnRecordingService` company-specific or Universal). Last Universal active row is OV-12. Unique `uq_stat_active_rule` prevents two active rows in the same rule+company scope. | Company-specific row may deactivate when a Universal active rule remains; successor replace also closes the prior row in the same transaction | Evaluator requires an applicable active row |

OV-12: Close only. No override. No destructive button.  
OV-13: only when deactivation is allowed; mandatory reason; Cancel or Deactivate.

---

## 7. Transaction and row-lock matrix

All writes go through `TransactionManager`. AuditWriter in the same transaction. Scope re-checked after locks.

Lock order (lowest id first to avoid deadlock): `company` → `plant` → `company_plant` → party/product/vehicle → mapping children → `rate_master` → assignment_history.

| Tx | Locks (`SELECT … FOR UPDATE`) | Writes | Failure |
|---|---|---|---|
| Company save | company row (edit) | company, audit | OV-11 on row_version mismatch |
| Plant save + mapping replace | plant; company rows by id; existing company_plant by id | plant; insert/deactivate mapping rows; audit per mapping | OV-12 if unmapping a pairing with txns |
| Client save + mappings | client; client_company | client, mappings, audit | Duplicate GSTIN CMP/PTY-ERR-003 |
| Site save | client (share lock), site | site, audit | Parent inactive blocks new site |
| Vendor save + mappings | vendor; vendor_company | vendor, mappings; keep vendor.company_id = first active mapping company | Duplicate GSTIN if supplied |
| Product/RM save | product; product_company; product_gst | dual-write GST onto product columns + product_gst | Classification immutable after insert |
| Unit inline save | unit_master | insert/update; never delete | Duplicate unit_code |
| Recipe save | client_company; finished product; raw materials; existing recipe | replace recipe_line set atomically; audit | RCP-ERR-003 and RCP-ERR-005 independent |
| Manufacturer/Brand inline | manufacturer / brand | insert/update | Duplicate name |
| Vehicle save | vehicle; brand; vendor | vehicle; if first assignment, insert history | Registration normalised before UK |
| Change supplier | vehicle; current open assignment; vendor | set assigned_until_utc = now; insert new open row; audit | Generated UK rejects two opens |
| New rate | matching active rate row(s) for scope; client_company; product | set prior is_active=0, effective_to=now; insert new active row | Generated UK; never UPDATE rate_amount |
| Charge save | charge row | insert/update defaults; ordinary cannot be PERCENTAGE | CHECK |
| Statutory successor | active config row for rule+scope | deactivate old; insert new; insert two history events | Missing successor → operation stays blocked |

Recipe View: GET only; no POST that would persist lines.

---

## 8. Route and permission matrix

Map CSV labels onto the **existing** Phase 0 catalogue. Do not add `Company Admin` as a sixth sample role. Do not add `RATE.MANAGE_UNIVERSAL` / `Company-Plant Mapping – Manage` codes.

CSV “Company Admin” is a Figma primary-role label. Runtime uses Super Admin (global masters), Accounts Manager (statutory view, rates view), Plant Manager (recipe, vehicle view), Store Keeper (vendor/raw/unit view), Billing Clerk (client/rate add).

| Method | Path | Screen | Permission | Guard |
|---|---|---|---|---|
| GET | `/companies` | CMP-001 | `COMPANY.LIST` | Global list scoped to grants; Super Admin all |
| GET | `/companies/create` | CMP-002 Create | `COMPANY.ADD` | Absent control if missing |
| POST | `/companies` | CMP-002 | `COMPANY.ADD` | Unique GSTIN |
| GET | `/companies/{id}` | CMP-002 Edit/View | `COMPANY.VIEW` | Edit POST needs `COMPANY.EDIT` |
| POST | `/companies/{id}` | CMP-002 | `COMPANY.EDIT` | row_version |
| POST | `/companies/{id}/deactivate` | OV-12/13 | `COMPANY.ACTIVATE_DEACTIVATE` | Dependency check |
| GET/POST | `/plants`, `/plants/create`, `/plants/{id}` | CMP-003/004 | `PLANT.LIST/VIEW/ADD/EDIT` | Mapping in same POST |
| POST | `/plants/{id}/mappings/{companyPlantId}/deactivate` | CMP-004 | `PLANT.EDIT` | Block if txns exist |
| GET/POST | `/clients`, `/clients/create`, `/clients/{id}` | PTY-001/002 | `CLIENT.*` | Company mapping required |
| GET/POST | `/clients/{clientId}/sites`, `…/sites/create`, `…/sites/{id}` | PTY-003/004 | `CLIENT_SITE.*` | Parent client_id immutable after save |
| GET/POST | `/vendors`, `/vendors/create`, `/vendors/{id}` | PTY-005/006 | `VENDOR.*` | GSTIN optional |
| GET/POST | `/products`, `/products/create`, `/products/{id}` | PRD-001/002 | `PRODUCT.*` | classification forced FINISHED_PRODUCT |
| GET/POST | `/raw-materials/create`, `/raw-materials/{id}` | PRD-003 | `RAW_MATERIAL.*` | classification forced RAW_MATERIAL. List: GET `/raw-materials` operational T01, **not** extra CSV Screen ID |
| GET | `/units` | PRD-004 | `UNIT.LIST` | Inline only |
| POST | `/units` | PRD-004 inline add | `UNIT.ADD` | Same path, no `/units/create` |
| POST | `/units/{id}` | PRD-004 inline edit | `UNIT.EDIT` | No DELETE route |
| GET | `/recipes` | RCP-001 list (operational; CSV has form frames only) | `RECIPE.LIST` | |
| GET/POST | `/recipes/create`, `/recipes/{id}` | RCP-001 | `RECIPE.ADD/EDIT/VIEW` | View omits Add/Remove/Save |
| GET | `/vehicle-suppliers` | VEH-001 | `VENDOR.VIEW` or `VEHICLE.VIEW` | **GET only**. No POST. No create. |
| GET | `/vehicle-manufacturers` | VEH-002 | `VEHICLE.LIST` | Inline POST `/vehicle-manufacturers`, `/vehicle-manufacturers/{id}` with `VEHICLE.EDIT` |
| GET | `/vehicle-brands` | VEH-003 | `VEHICLE.LIST` | Inline POST similarly |
| GET/POST | `/vehicles`, `/vehicles/create`, `/vehicles/{id}` | VEH-004 | `VEHICLE.*` | `/vehicles` list is operational T01 |
| POST | `/vehicles/{id}/supplier` | VEH-004 | `VEHICLE.EDIT` | Close+insert assignment |
| GET/POST | `/rates/universal` | PRC-001 | `RATE.VIEW` / `RATE.ADD` | `company_id` NULL; Super Admin only for write |
| GET/POST | `/rates/company` | PRC-002 | `RATE.VIEW` / `RATE.ADD` | company_id required; company grant |
| GET/POST | `/charges` | ACH-001 | `CHARGE.VIEW/ADD/EDIT` | Ordinary FIXED only |
| GET/POST | `/statutory-timing` | STAT-001 | `STATUTORY.VIEW` / `STATUTORY.MANAGE` | Successor+history same tx |
| POST | `/statutory-timing/{id}/deactivate` | OV-12/13 | `STATUTORY.MANAGE` | Last active scope rule is OV-12 |

No `/seed`, `/fixture`, `/demo`, `/probe`, `/test` routes. CSRF on every POST. `?frame=` development/testing overlay only.

Permission-denied: omit Add/Edit/Deactivate. Direct URL without VIEW → 403 page-scope message (existing 403 view). Status-blocked Deactivate remains visible until OV-12.

---

## 9. CI4 module/file plan

Follow existing `app/Modules/{Domain}/Services` + `app/Controllers` + `app/Views/pages` + `app/Database/Migrations|Seeds`.

| Area | New PHP (planned) | Views | Reuse |
|---|---|---|---|
| Org | `Modules/Org/Services/CompanyService.php`, `PlantService.php`, `DeactivationGuard.php` | `pages/company/list.php`, `form.php`; `pages/plant/list.php`, `form.php` | ScopeService, AuditWriter, TransactionManager |
| Party | `ClientService`, `ClientSiteService`, `VendorService` | `pages/client/*`, `pages/client_site/*`, `pages/vendor/*` | MasterQuery selectors stay read-only for Phase 1 |
| Product | `ProductService` (classification param), `UnitService`, `RecipeService` | `pages/product/*`, `pages/raw_material/form.php`, `pages/unit/list.php` (inline), `pages/recipe/form.php` | InvoiceCalculator untouched |
| Vehicle | `ManufacturerService`, `BrandService`, `VehicleService`, `SupplierAssignmentService` | `pages/vehicle/suppliers.php` (read-only), `manufacturers.php`, `brands.php`, `form.php` | — |
| Pricing | `RateService` (universal vs company), `ChargeService`, `StatutoryTimingService` | `pages/rate/universal.php`, `company.php`; `pages/charge/configure.php`; `pages/statutory/configure.php` | Existing rate lookup in invoice path |
| HTTP | `CompanyController`, `PlantController`, `ClientController`, `ClientSiteController`, `VendorController`, `ProductController`, `RawMaterialController`, `UnitController`, `RecipeController`, `VehicleSupplierController`, `VehicleManufacturerController`, `VehicleBrandController`, `VehicleController`, `RateController`, `ChargeController`, `StatutoryController` | — | Auth + scope filters |
| UI | Extend `FrameCatalog` with 216 Phase 2 frames | Reuse `layouts/app.php`, OV-11 component; add `ov_12_blocked.php`, `ov_13_deactivate.php`, `ov_14_unsaved.php`, `ov_07_filters.php` | tokens.css, T01/T02/T07 markup matching Phase 1 lists/forms |
| Seed | Additive `Phase2MasterSeeder` called from DatabaseSeeder **after** Phase 1 seeds | — | Do not change 116.000 MT TransactionSeeder quantities |
| Tests | `tests/feature/Phase2FrameHttpTest.php` (216), integration services, browser subset | — | Existing 112 tests must stay green |

Progressive JS: unsaved-changes flag for OV-14, filter drawer, inline unit/manufacturer/brand row edit, recipe add/remove row (disabled on View). No SPA, no client-side repositories.

---

## 10. Figma-to-View/component mapping

| Template | Views | Components |
|---|---|---|
| T01 lists | `{entity}/list.php` | App Shell, Breadcrumb, Data Table, Row Action Menu, Status Badge, Empty State, Loading Skeleton, Pagination, OV-07 |
| T02 forms | `{entity}/form.php` | Text Input, Searchable Select, Inline Error, Validation Summary, Sticky Action Bar, Definition List, Status Badge, OV-11/12/14 |
| T07 configure | `{entity}/configure.php` | Same as T02 plus history table (immutable rows) and Empty configure state |
| VEH-001 | `vehicle/suppliers.php` | T01 without primary Add |
| PRD-004 / VEH-002 / VEH-003 | list + inline editors in table rows | No modal; no second route |

List columns (Blueprint, minus rejected vehicle Capacity):

- Company: name, GSTIN, State, Timezone (P3 hide on mobile), Status, Actions  
- Plant: name, code, type, associated companies (chips), Status  
- Client: name, GSTIN, state, associated companies, Status  
- Site: name, address, parent client, Status  
- Vendor: name, GSTIN (may be empty), associated companies, Status  
- Product: name, HSN, unit, CGST/SGST/IGST, shelf-life, companies, Status  
- Unit: code, name, Status + inline editors  
- Manufacturer: name, Status + inline  
- Brand: name, manufacturer, Status + inline  
- Vehicle form: registration, brand/model, current supplier, assignment history table, Status  
- Rates: scope, client, product, amount, effective from/to, Status, history  
- Charges: code, name, kind, method, default amount/%, Status  
- Statutory: rule type, company (or Universal), limit, unit, effective from, timezone label, history  

Recipe composition grid: raw material, unit (read-only), quantity MT, order. View: grid without Add/Remove/Save.

---

## 11. Responsive implementation plan

| Breakpoint | Rule |
|---|---|
| Desktop ≥ 1024 | CSV D frames. T01 table; T02 two-column fields; T07 configure + history |
| Tablet 768–1023 | **No CSV T frames.** Filters in drawer with count badge; P3 columns behind chooser; row height 44; form single column cap 560; sticky bar 72 |
| Mobile < 768 | CSV M frames only. Lists → record cards; primary action 64px sticky footer; pagination → Load more; forms single column inputs 48px; stacked errors for M Edit-Error |

VEH-SCR-001 mobile: Default only (no M-NoResults frame). Implement NoResults as the desktop behaviour if filters match nothing; do not invent `M-VEH-SCR-001-List-NoResults`.

Loading skeletons after 300ms as Blueprint. Empty vs NoResults copy from Blueprint §12.

---

## 12. Exact sample-data plan

Do not alter Phase 1 operational numbers: challan `CH-2026-000481`, invoice `INV-2026-000119` (177,592.00, round-off ₹0.00), inward `INW-2026-000212`, allocation 18.000+12.000, LOT-2026-00230 remaining 13.000, ledger **116.000 MT**, SYSTEM_EXPIRY, wastage `WST-2026-000067`.

Additive seed only:

| Domain | Rows |
|---|---|
| Companies | Apex RMC Pvt Ltd (RMC, GSTIN existing, TZ Asia/Kolkata); Apex Aggregates Pvt Ltd (CRUSHER, TZ Asia/Kolkata) |
| Plants | Chakan Plant **shared** both companies; Talegaon Crusher Plant; Ranjangaon RMC Plant |
| Client | Beta Constructions `27AAAAA0000A1Z5` Pune, Metro Infra `27AACCM8765D1Z7` Pune, Sunrise Developers `27AABCS4321C1Z2` Mumbai, Delta Supplies `27BBBBB1111B1Z3` Nashik — all mapped to Apex RMC. Sites: Hinjawadi Phase 2 (Beta), Wakad Tower Site (Metro), Baner Road Project (Sunrise). Delta has no site. Site address is not used for POS. |
| Vendor | Western Cement Suppliers (`V-CEM-01`) and Reliable Aggregates (`V-REL-01`) mapped via `vendor_company` to Apex RMC; GSTIN optional (seed leaves null) |
| Units | MT, KG (existing). Inline-capable. No extra delete |
| Finished product | M25 RMC, M30 RMC (existing), Crushed Sand. Track-by-lot Yes read-only |
| Raw materials | OPC 53 Cement (existing); **20 mm Aggregate**; **M-Sand**. All MT |
| Recipe | **Client Beta Constructions + finished product M30 RMC** (not a recipe name). OPC 53 Cement **0.350** MT; 20 mm Aggregate **0.700** MT; M-Sand **0.650** MT. Uniqueness: one current recipe per **client_company_id + finished_product_id** (`uq_recipe_scope`). Approved R12: one current recipe per client + finished product. |
| Manufacturer / Brand | Tata Motors / Signa; Ashok Leyland / Dost. Do not seed LPT. |
| Vehicle | `MH12AB1234` Signa / Tata Motors / current supplier Western Cement Suppliers; `MH14XY5678` Dost / Ashok Leyland / Reliable Aggregates. Registration uppercase, no spaces; exactly one open assignment each. Preserve assignment history. |
| Rates | Keep company-specific M30 `4966.6667` Apex RMC + Beta Constructions. A universal example must **not** change invoice 177,592.00 |
| Charges | Loading 2500 FIXED ordinary; Commercial Discount 0.6700% ; do not change invoice snapshots |
| Statutory | Active universal `IRN_RECORDING_WINDOW` 72 HOUR from 2026-04-01; history CREATED; timezone display Asia/Kolkata |

Inactive sample: one inactive client **or** unused extra vendor for View-Cancelled frames, without attaching it to CH-2026-000481.

---

## 13. Validation and error-code matrix

Use FRIS codes. Surface Blueprint Section 3.2 wording where it exists. Independent checks: do not collapse duplicate-RM and zero-qty into one error.

| Code | When | UI |
|---|---|---|
| CMP-ERR-001 | Mandatory company/plant field | Inline + summary |
| CMP-ERR-002 | GSTIN format | GSTIN field |
| CMP-ERR-003 | Duplicate GSTIN (global, no merge) | GSTIN field |
| CMP-ERR-004 | Mapping business type outside intersection | Mapping row |
| PTY-ERR-001 | Mandatory party/site field | Inline |
| PTY-ERR-002 | GSTIN format (client always; vendor if supplied) | GSTIN |
| PTY-ERR-003 | Duplicate GSTIN | GSTIN |
| PRD-ERR-001 | Mandatory product/unit field | Inline |
| PRD-ERR-002 | GST % < 0 | GST fields |
| PRD-ERR-003 | Shelf-life duration missing when controlled | Duration |
| PRD-ERR-004 | Duplicate unit_code | Unit code |
| RCP-ERR-001 | Client/product mandatory | Fields |
| RCP-ERR-002 | Duplicate current recipe for client+product | Summary |
| RCP-ERR-003 | Duplicate raw material row | Row (independent) |
| RCP-ERR-004 | Zero composition rows | Grid |
| RCP-ERR-005 | Quantity ≤ 0 | Row (independent of 003) |
| VEH-ERR-001 | Mandatory manufacturer/brand/vehicle/supplier | Inline |
| VEH-ERR-002 | Duplicate registration after uppercase/strip | Registration |
| PRC-ERR-001 | Mandatory rate scope/client/product | Inline |
| PRC-ERR-002 | Rate amount not > 0 (app); DB CHECK ≥ 0 remains — reject 0 in service | Amount |
| PRC-ERR-003 | Duplicate active scope (generated UK) | Summary |
| ACH: ordinary + PERCENTAGE | Reject | Method field |
| STAT missing active rule | Block IRN/credit-note operation | Existing Phase 1 path must keep blocking; STAT screen Error state |
| OV-11 | row_version mismatch | Overlay Reload |
| OV-12 | Dependency block | Close only |
| Scope failure | Write tx | 403, no partial write |

Normalisation: GSTIN and registration `strtoupper` + remove spaces before unique check. Names: trim; compare with ai_ci.

---

## 14. Automated unit / integration / feature / browser test plan

Phase 2 tests are **not** written until implementation approval. Planned:

| Layer | Coverage |
|---|---|
| Feature HTTP | All **216** frames render via `/resource` + optional `?frame=` in testing env only; production ignores frame |
| Feature auth | Missing VIEW → 403; missing ADD → control absent; Plant Manager cannot COMPANY.ADD; S. Patil Store Keeper active role does not union Billing Clerk |
| Integration org | Shared Chakan mapping; unmap blocked when inward/challan exists; timezone Asia/Kolkata stored |
| Integration party | Duplicate GSTIN hard-block; vendor GSTIN null allowed; two vendors same name different GSTIN allowed; site without GSTIN; inactive excluded from selectors, visible on GET historical |
| Integration product | Classification immutable; dual-write GST; deactivation blocked with stock / recipe / allocation |
| Integration unit | POST list inline; DELETE route 404; unit used by OPC53 cannot delete/deactivate |
| Integration recipe | Sample 0.350/0.700/0.650; duplicate RM + zero qty both returned; View has no Save; overwrite replaces lines; no ledger movement |
| Integration vehicle | `mh 12 ab 1234` persists `MH12AB1234`; two open assignments rejected; change supplier history length +1; VEH-001 POST 405/404 |
| Integration rate | Insert new active closes old in one tx; historical amount unchanged; universal + company-specific coexistence; generated UK; calculator still 177,592.00 |
| Integration statutory | History CREATED/DEACTIVATED; missing config blocks; deadline instant excluded |
| Browser | Desktop list+form happy path per module; mobile card list; OV-12 Close; OV-13 reason; OV-14; VEH-001 no Add |
| PHPUnit | BCMath quantities; no float in services |

---

## 15. Phase 0/1 regression plan

Must remain green after Phase 2 implementation (when approved):

- Entire current PHPUnit suite (112 tests / 1426 assertions at Phase 1 close)
- Vendor Inward Save + MLB-1
- Challan list/create/review; number on first Save; cancel rules
- Separate Challan vs Invoice approval: **Invoice approval is a separate action from Challan approval, but the Challan must be Approved first.**
- Invoice list/review; manual IRN INV-SCR-008
- Material Ledger closing **116.000 MT**
- Audit History + AUDIT.EXPORT Super Admin
- SYSTEM_EXPIRY; SYSTEM actor
- Allocation 18.000 + 12.000; lot remaining 13.000
- Application tests verify **216** expected Frame Names and server-rendered visual states via PHPUnit HTTP tests (`Phase2Frames` / `Phase2FrameHttpTest`).
- Application tests **do not** verify Figma component attachment or detachment.
- Application tests **do not** inspect or preserve Figma prototype reactions.
- The 57 prototype reactions belong to the separately approved Figma P28 handoff. They are not measured by `FrameCatalog` or `Phase2Frames`.
- This implementation **did not modify the Figma file**.
- Application component-partial rendering (CI4 views such as OV-07/11/12/13/14) is separate from Figma component integrity.
- Do not treat HTTP 200 plus a Frame Name in the response as full visual/state verification. Browser QA is recorded in `docs/PHASE2_CORRECTION_REPORT.md`.
- Existing Phase 0/1 migrations unchanged and still apply on empty DB
- No production seed/probe routes
- `InvoiceCalculator` display order and G3 round-off ₹0.00
- Active-role never unions (Patil)

Regression tests run **before** claiming Phase 2 exit.

---

## 16. Phase 2 entry and exit criteria

### Entry (for a later implementation turn)

- This plan approved explicitly.
- Phase 1 remains closed; wording in `docs/PHASE1_PLAN.md` and `docs/PHASE1_ROUTE_MATRIX.md` recorded.
- Stack unchanged.
- No unresolved item in §17 marked **blocking** left open (non-blocking items may ship with the documented default).

### Exit

- All **216** CSV frames renderable with exact Frame Names.
- Additive migrations only; Phase 1 tables not rebuilt.
- Chakan shared; timezone Asia/Kolkata default and used in statutory evaluation.
- VEH-SCR-001 GET-only.
- Unit / manufacturer / brand inline; no unit delete; no vehicle Capacity/Unit/fleet/type fields.
- Recipe sample quantities; View without Add/Remove/Save; informational (no stock impact).
- Rate history immutable; one active per generated scope; universal + company-specific.
- Ordinary charges fixed; discount before GST; calculator unchanged.
- Statutory UTC DATETIME(6); missing config blocks; history append-only.
- OV-12 Close only; OV-13 only when allowed.
- Selectors exclude inactive; historical view includes inactive.
- 216 frame tests + Phase 0/1 regression green.
- No React/Next/Nest/Postgres/JWT/localStorage/mock API.
- README runbook updated only after implementation.

### Stop condition

**Do not implement Phase 2 until this plan is approved.**

---

## 17. Conflicts, unresolved decisions, missing dependencies

### Resolved in this plan (do not reopen in implementation)

| ID | Conflict | Resolution for Phase 2 |
|---|---|---|
| R1 | Phrase “Immediate invoice independent of challan approval” | Replaced in docs only: **Invoice approval is a separate action from Challan approval, but the Challan must be Approved first.** No working-code change in this turn. |
| R2 | 12 frame aliases | None apply to these 23 Screen IDs. Direct CSV names only. |
| R3 | CSV/Blueprint VEH titles vs FRIS Decision A vs user | Functional: 001 supplier selection read-only; 002 manufacturer inline; 003 brand inline; 004 vehicle + history. CSV Frame Names unchanged. Blueprint Capacity columns **out**. |
| R4 | FRIS company-plant unmap “confirm and proceed” vs UX-DEC-001 / user | **OV-12 block** when any transaction exists for the pairing. No override. |
| R5 | FRIS Batch 2 PRC-SCR-001 = Rate List, PRC-SCR-002 = combined Add/Edit vs CSV T07 Universal vs Company-Specific | Follow **CSV + user**: 001 universal configure/history; 002 company-specific configure/history. History grid lives on each T07 screen. |
| R6 | FRIS VEH-001 “Add Vendor → PTY-006” vs user no create route | **No Add** on VEH-001. Vendors created only on PTY-006. |
| R7 | FRIS Client GSTIN mandatory vs “GSTIN unique where supplied” | Client GSTIN **mandatory**. Vendor GSTIN **optional**, unique when supplied. No auto-merge. |
| R8 | FRIS Unit permission = Product Edit vs catalogue `UNIT.*` | Use existing `UNIT.*`. |
| R9 | FRIS Rate Manage Universal vs catalogue `RATE.*` | No new permission codes. Universal write: `RATE.ADD` and global company access (Super Admin). Company-specific: `RATE.ADD` within grant. |
| R10 | Blueprint product single GST% vs FRIS CGST/SGST/IGST | Persist three rates (Phase 1 columns + `product_gst`). UI shows three percentages. |
| R11 | Blueprint classification radio on PRD-002 vs separate PRD-003 | PRD-002 forces Finished Product; PRD-003 forces Raw Material; not user-switchable after save. |
| R12 | FRIS recipe overwrite vs View-Cancelled | Overwrite lines for the same client+product; `is_active` supports inactive view; no version table. |
| R13 | DB Arch PostgreSQL partial indexes | MySQL generated-column unique (already used on rates). |
| R14 | Track by lot | Read-only Yes as Blueprint; no user toggle. |
| R15 | Company Admin Figma role | Not seeded. Five Phase 0 identities only. |
| R16 | Full statutory company field list still BRD Open Decision | Implement Blueprint CMP-SCR-002 field set (legal name, short code, GSTIN, PAN, state, address, PIN, timezone, currency INR read-only). Additive columns. |

### Non-blocking (documented defaults)

| ID | Item | Default if implementation starts without further instruction |
|---|---|---|
| N1 | CSV has no List frames for RCP-SCR-001 or VEH-SCR-004 | Operational T01 lists at `/recipes` and `/vehicles` without new Frame IDs |
| N2 | CSV has no Raw Material list Screen ID | Operational `/raw-materials` T01 analog to PRD-001, classification filter, no extra Frame IDs |
| N3 | VEH-001 missing M-NoResults | Use empty-card copy; do not add a Frame Name |
| N4 | `vendor.company_id` NOT NULL vs vendor_company | Keep column; dual-maintain first active mapping company so Phase 1 inward keeps working |
| N5 | product GST columns vs product_gst | Dual-write; do not change InvoiceCalculator source in Phase 2 |
| N6 | company.company_name unique `as_cs` vs names ai_ci | Keep existing unique index |
| N7 | UNIT.ACTIVATE_DEACTIVATE in catalogue vs “no deletion” | No DELETE route; deactivate allowed only when unused |
| N8 | Own-fleet / insurance / vehicle type | Out of scope (FRIS open decision). Do not add fields |
| N9 | Charge “protected” deactivation flag | Block deactivation when the charge is on a **nonterminal** invoice (`DRAFT`, `PENDING_APPROVAL`, `RETURNED_FOR_CORRECTION`, `APPROVED_PRE_IRN`, `PENDING_IRN`, `IRN_FAILED`). Terminal `IRN_GENERATED` or `CANCELLED` snapshots do **not** block. |
| N10 | Document prefix/length on company (BRD CMP-FR-001) | Out of this 23-screen slice unless a CMP-002 field is required by Blueprint — Blueprint does not show prefix; **omit** numbering config from CMP-002 |
| N11 | FRIS “Company-Plant Mapping – View” permission | Use `PLANT.LIST` / `PLANT.VIEW` / `PLANT.EDIT` |
| N12 | Recipe LIST frames vs combined screen | Form frames FR-0178–0188 are the CSV contract; list is operational |

### Blocking until this plan is approved

| ID | Item |
|---|---|
| B1 | Implementation of any Phase 2 route, view, or migration |
| B2 | Changing Phase 1 working code for the invoice-approval sentence (already correct in `InvoiceService`; docs updated only) |
| B3 | Introducing a sixth sample role or new permission module codes |
| B4 | Rebuilding Phase 1 tables or moving GST/vendor ownership in a way that breaks 116.000 MT / inward FKs |

### Missing dependencies (must be created in Phase 2, not present today)

- Tables: `vendor_company`, `product_gst`, `vehicle_manufacturer`, `vehicle_brand`, `vehicle_supplier_assignment_history`, `recipe`, `recipe_line`, `statutory_timing_configuration_history`
- Columns: master `row_version`, company/plant statutory fields, client_site.site_address, vendor.gstin, vehicle.vehicle_brand_id, rate close-out columns
- UI overlays OV-07/12/13/14 as shared views
- Raw materials 20 mm Aggregate and M-Sand for the approved recipe sample
- FrameCatalog entries for FR-0001–0216

---

*End of Phase 2 plan. Stop. Wait for approval. Do not write Phase 2 code.*
