# Inspinia Migration Checklist
**Project:** CoreERP — TAN General Supply Limited
**Theme:** Inspinia v2.8 → replacing AdminLTE v3.2.0
**Last updated:** 2026-08-04 (Phase 30N)

---

## Phase 30A — Audit & Planning
- [x] Inspinia archive located at `/Users/edgarsimac/Documents/inspinia/`
- [x] Inspinia version confirmed: 2.8
- [x] Inspinia Bootstrap version confirmed: 4.1.0 (compatible with current BS4.6.1)
- [x] Inspinia Font Awesome version confirmed: **4.7.0** (CONFLICT — use FA5 instead)
- [x] CoreERP AdminLTE version confirmed: 3.2.0
- [x] CoreERP Bootstrap version confirmed: 4.6.1 (via AdminLTE dist)
- [x] CoreERP Font Awesome version confirmed: 5.15.4 (fas/far/fab prefix — keep this)
- [x] Total views using AdminLTE: 208
- [x] Views using Select2: 12
- [x] Views using plugin sections: 13
- [x] Print layout confirmed standalone (safe)
- [x] Compatibility table created
- [x] Risk analysis documented
- [x] Migration strategy documented
- [x] Do-not-overwrite list documented
- [x] Audit docs written: `docs/theme/INSPINIA_MIGRATION_AUDIT.md`
- [x] Checklist created: `docs/theme/INSPINIA_MIGRATION_CHECKLIST.md`

---

## Phase 30F (CoreERP) / 30B (Inspinia plan) — Asset Staging & Preview Layout
**Completed: 2026-08-03**

- [x] Copy `style.css` to `public/vendor/inspinia/css/style.css`
- [x] Copy `animate.css` to `public/vendor/inspinia/css/animate.css`
- [x] Copy `inspinia.js` to `public/vendor/inspinia/js/inspinia.js`
- [x] Copy metisMenu JS to `public/vendor/inspinia/js/metisMenu/`
- [x] Copy slimscroll to `public/vendor/inspinia/js/slimscroll/`
- [x] Copy pace to `public/vendor/inspinia/js/pace/`
- [x] Copy glyphicon fonts to `public/vendor/inspinia/fonts/` (required by style.css font-face)
- [x] **DID NOT copy** Inspinia `font-awesome/` — using existing FA5
- [x] **DID NOT copy** Inspinia `bootstrap.min.css` — using existing BS4.6.1
- [x] Select2 via CDN (same as AdminLTE pages — no staging needed)
- [x] Chart.js deferred to Phase 30G dashboard conversion
- [x] Create `resources/views/layouts/inspinia.blade.php`
  - [x] Yields: `title`, `content_header`, `content`, `css`, `js` (compatible with AdminLTE)
  - [x] Loads FA5 from `/vendor/fontawesome-free/`
  - [x] Loads Bootstrap via AdminLTE dist
  - [x] Loads `style.css` + `animate.css` + `coreerp-adminlte-compat.css`
  - [x] Loads jQuery from `/vendor/jquery/`
  - [x] Loads `inspinia.js` + metisMenu + slimscroll + pace
  - [x] Sidebar: `@include('layouts.partials.inspinia_sidebar')`
  - [x] Topbar: `@include('layouts.partials.inspinia_topbar')`
- [x] Create `resources/views/layouts/partials/inspinia_sidebar.blade.php` (full @can-gated menu)
- [x] Create `resources/views/layouts/partials/inspinia_topbar.blade.php` (user, branch, logout)
- [x] Create `public/vendor/inspinia/coreerp-adminlte-compat.css` (compatibility shim)
- [x] Update `resources/views/theme_preview/inspinia.blade.php` to extend `layouts.inspinia`
- [x] Route `GET /theme-preview/inspinia` already exists (auth-protected)
- [x] AdminLTE untouched on all business pages
- [x] No business file overwritten
- [x] PHP lint: clean
- [x] `php artisan route:list | grep theme` — route confirmed
- [x] `php artisan optimize:clear` — run

**Rollback plan:** Delete `public/vendor/inspinia/`, delete `resources/views/layouts/inspinia.blade.php`, delete `resources/views/layouts/partials/inspinia_*.blade.php`. Preview page reverts to Phase 30A inline CSS. AdminLTE is unaffected throughout.

---

## Phase 30C — Auth Page Conversion
- [ ] Convert `resources/views/auth/login.blade.php` to Inspinia style
  - [ ] Remove `@extends('adminlte::page')` → extend standalone layout or raw HTML
  - [ ] Apply Inspinia `.middle-box .loginscreen` pattern
  - [ ] Style: `gray-bg` body, centered login box
  - [ ] Test: login works end-to-end
- [ ] Convert `resources/views/auth/forgot-password.blade.php`
- [ ] Convert `resources/views/auth/reset-password.blade.php`
- [ ] Convert `resources/views/auth/register.blade.php` (if used)
- [ ] Verify all auth flows still work (login, logout, forgot password)
- [ ] Verify non-auth pages unaffected
- [ ] No business logic changed
- [ ] No migrations or seeders

---

## Phase 30D — Full Sidebar & Layout Shell
- [ ] Write `resources/views/partials/inspinia_sidebar.blade.php` (full menu from config)
  - [ ] Replicate all menu sections from `config/adminlte.php`
  - [ ] Apply `@can` / `@canany` permission guards per section
  - [ ] Sub-menu: `<ul class="nav nav-second-level collapse">`
  - [ ] Active state: compare current route
  - [ ] Icons: use `fas fa-*` (FA5, not `fa-*` FA4)
- [ ] Write `resources/views/partials/inspinia_topbar.blade.php`
  - [ ] Sidebar toggle button
  - [ ] User name display
  - [ ] Logout link
  - [ ] Optional: notifications dropdown
- [ ] Complete `layouts/inspinia.blade.php` with full sidebar + topbar
- [ ] Create `public/vendor/inspinia/css/adminlte-compat.css` (shim)
  - [ ] `.info-box` → replicate structure
  - [ ] `.btn-xs` → `.btn-sm` alias
  - [ ] `.elevation-1/2/3` → box-shadow aliases
  - [ ] `.card-tools` → float-right alias
  - [ ] `.badge-*` → ensure BS4.6.1 badge classes work
- [ ] Load `adminlte-compat.css` in Inspinia layout
- [ ] Test one business page with `@extends('layouts.inspinia')` — choose simple list page
- [ ] Verify sidebar renders all sections with correct permissions
- [ ] Verify active state works

---

## Phase 30E — Low-Risk List Pages Conversion
Batch switch `@extends` in simple list/index views (no Select2, no JS):

- [ ] `roles/index.blade.php`
- [ ] `branches/index.blade.php`, `create.blade.php`
- [ ] `units/index.blade.php`, `create.blade.php`, `edit.blade.php`
- [ ] `warehouses/index.blade.php`, `create.blade.php`, `edit.blade.php`
- [ ] `currencies/index.blade.php`, `create.blade.php`
- [ ] `item_categories/index.blade.php`, `create.blade.php`
- [ ] `departments/index.blade.php`, `create.blade.php`
- [ ] `designations/index.blade.php`, `create.blade.php`
- [ ] `work_shifts/index.blade.php`
- [ ] Verify each page after conversion

---

## Phase 30F — Form Pages (Controlled Conversion)
Convert one module at a time. Test before next module.

- [ ] Suppliers (index, create, edit, show)
- [ ] Customers (index, create, edit, show)
- [ ] Items (index, create, edit, show)
- [ ] Purchase Requests
- [ ] Purchase Orders (complex — multi-item, Select2 AJAX, VAT calc)
- [ ] GRNs
- [ ] Sales Orders
- [ ] Quotations / Proformas
- [ ] Stock Transfers
- [ ] HR Employees
- [ ] Attendance / Leave
- [ ] Journal Vouchers

**For each module:**
- [ ] Replace `@extends('adminlte::page')` with `@extends('layouts.inspinia')`
- [ ] Replace `@section('plugins.Select2', true)` with `@push('css'/'js')` or layout global load
- [ ] Replace AdminLTE widget classes if desired (`.card` → `.ibox`) — optional per phase
- [ ] Test form submission end-to-end
- [ ] Test Select2 AJAX search (supplier, item, customer)

---

## Phase 30G — Complex Reports & Dashboard
**Completed (dashboard): 2026-08-03**

- [x] Dashboard conversion (`resources/views/dashboard/index.blade.php`)
  - [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
  - [x] Removed `@section('plugins.Chartjs', true)` (AdminLTE-only plugin section)
  - [x] Added Chart.js 2.9.4 CDN `<script>` in `@section('js')` — all 4 charts work
  - [x] AdminLTE markup (`.info-box`, `.small-box`, `.card`) preserved — covered by compat CSS
  - [x] All DashboardController data variables and `@can` guards intact
  - [x] FA4 @font-face audit: no FA4 @font-face exists in style.css (only glyphicons) — no change needed
  - [x] PHP lint: clean
  - [x] `php artisan optimize:clear` — run
  - [x] Route `GET /dashboard` confirmed via route:list
- [ ] Management Reports
- [ ] Financial Statements (P&L, Balance Sheet) — verify table structure
- [ ] GL Reports (Trial Balance, General Ledger)
- [ ] Inventory Reports (Reorder Alerts, Stock Aging)
- [ ] Purchase Recording Report
- [ ] Stock Transfer Report
- [ ] HR Reports

---

## Phase 30H — AdminLTE Removal (Final)
**ONLY after ALL 208 views confirmed switched and tested:**

- [ ] Verify zero views still use `@extends('adminlte::page')`
- [ ] Remove `jeroennoten/laravel-adminlte` from `composer.json`
- [ ] Run `composer update`
- [ ] Remove `config/adminlte.php` or archive it
- [ ] Remove `public/vendor/adminlte/` (check not referenced anywhere)
- [ ] Remove AdminLTE from `resources/views/vendor/adminlte/` if published
- [ ] Final full-app test: login, sales flow, purchase flow, GL, HR
- [ ] Mobile responsiveness test (Inspinia is responsive)
- [ ] Print layout test (should be unaffected throughout)
- [ ] Performance check (page load, asset sizes)

---

## Rollback Plan

At any phase, revert individual views by changing:
```
@extends('layouts.inspinia')
→
@extends('adminlte::page')
```

AdminLTE is never removed until Phase 30H, so reverting any page is instantaneous.

**Git checkpoint:** Commit before each phase milestone.

---

## Print Layout Protection

**These files must NEVER be changed during theme migration:**

- `resources/views/prints/layout.blade.php`
- All views that extend `prints.layout`:
  - tax_invoices/print.blade.php
  - customer_receipts/print.blade.php
  - purchase_orders/print.blade.php
  - grns/print.blade.php
  - delivery_notes/print.blade.php
  - gate_passes/print.blade.php
  - credit_notes/print.blade.php
  - stock_transfers/print.blade.php
  - (all other print views)

These are standalone print documents with their own CSS. They have no AdminLTE dependency and no Inspinia dependency needed.

---

## Phase 30H — Auth/Login Shell Conversion (2026-08-04)
- [x] `layouts/inspinia_auth.blade.php` created — centered card, no sidebar, no topbar
- [x] `auth/login.blade.php` converted to `@extends('layouts.inspinia_auth')` with Bootstrap 4 form
- [x] `auth/register.blade.php` converted — all field names preserved
- [x] `auth/forgot-password.blade.php` converted — route `password.email` preserved
- [x] `auth/reset-password.blade.php` converted — hidden token input preserved
- [x] `auth/verify-email.blade.php` converted — POST logout form preserved
- [x] `auth/confirm-password.blade.php` converted — route `password.confirm` preserved
- [x] All 6 auth views extend `layouts.inspinia_auth` (grep confirmed)
- [x] Breeze `<x-guest-layout>` components replaced with plain Bootstrap 4 HTML
- [x] Auth controllers unchanged — no route names changed
- [x] Logout uses POST (`route('logout')`) — unchanged
- [x] Dashboard still uses `layouts.inspinia` (grep confirmed)
- [x] Theme preview still uses `layouts.inspinia` (grep confirmed)
- [x] 212 business module views still use `adminlte::page` (grep confirmed)
- [x] No migrations created — no seeders run
- [x] Print layouts untouched (grep confirmed)
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] Auth routes verified unchanged via `php artisan route:list`

---

## Phase 30I — Module Index Page Pilot: Sales Orders (2026-08-04)
- [x] `sales_orders/index.blade.php` converted: `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` with `<h1>` → `@section('content_header', 'Sales Orders')` (text only; Inspinia wraps in `<h2>`)
- [x] `.card`/`.card-header`/`.card-body` replaced with `.ibox`/`.ibox-title`/`.ibox-tools`/`.ibox-content`
- [x] "New Sales Order" button moved to `.ibox-tools` (right side)
- [x] Table columns, badge classes, action buttons, pagination unchanged
- [x] Laravel pagination via `$salesOrders->links()` preserved
- [x] Flash message alerts with dismiss button preserved
- [x] `@stop` → `@endsection` (Inspinia uses standard Blade, not AdminLTE alias)
- [x] Salesman scope logic in controller — not touched
- [x] `sales_orders/create.blade.php` — still `@extends('adminlte::page')` (confirmed)
- [x] `sales_orders/edit.blade.php` — still `@extends('adminlte::page')` (confirmed)
- [x] `sales_orders/show.blade.php` — still `@extends('adminlte::page')` (confirmed)
- [x] `sales_orders/print.blade.php` — untouched print layout
- [x] `coreerp-adminlte-compat.css` — pagination styles added (`.pagination`, `.page-link`, `.page-item.active`)
- [x] 211 remaining business views still use `adminlte::page` (was 212; index.blade.php now on Inspinia)
- [x] Dashboard still uses `layouts.inspinia`
- [x] Auth pages still use `layouts.inspinia_auth`
- [x] Purchase orders index confirmed `@extends('adminlte::page')`
- [x] No migrations created — no controller logic changed
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l SalesOrderController.php` — no syntax errors
- [x] `php -l routes/web.php` — no syntax errors

---

## Phase 30J — Inspinia Index Pages Batch 1 (2026-08-04)
- [x] Sidebar route sanity check — GL Backfill, Chart of Accounts, financial statements, aging reports, GL mappings, inventory-reports, notifications, internal-mail all fixed in emergency fix prior to this phase
- [x] `quotations/index.blade.php` — converted to `layouts.inspinia`, `.ibox` structure, create button in `.ibox-tools`
- [x] `proformas/index.blade.php` — converted to `layouts.inspinia`, `.ibox` structure, create button in `.ibox-tools`
- [x] `customers/index.blade.php` — converted to `layouts.inspinia`, `.ibox` structure, create button in `.ibox-tools`, delete form preserved
- [x] `tax_invoices/index.blade.php` — converted to `layouts.inspinia`, `.ibox` structure, no create button (none existed), pagination moved from `.card-footer` into `.ibox-content`
- [x] `customer_receipts/index.blade.php` — converted to `layouts.inspinia`, `.ibox` structure, no create button (none existed), pagination moved from `.card-footer` into `.ibox-content`
- [x] All 5 create/edit/show pages confirmed still `@extends('adminlte::page')`
- [x] Customers has no show.blade.php (uses edit page) — confirmed acceptable, index untouched for that route
- [x] No controllers changed — no route names changed
- [x] No migrations created — no seeders run
- [x] 206 remaining business views still use `adminlte::page` (was 211; 5 index pages now on Inspinia)
- [x] Purchase orders index confirmed `@extends('adminlte::page')`
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] `grep "extends.*layouts.inspinia'"` confirms exactly 8 views on Inspinia: dashboard, theme_preview, sales_orders/index, quotations/index, proformas/index, customers/index, tax_invoices/index, customer_receipts/index

---

## Phase 30K — Inspinia Index Pages Batch 2: Purchase Module (2026-08-04)
- [x] `purchase_requests/index.blade.php` — converted; two-ibox filter+table; `@can purchase_request.create`; priority + status dual badges
- [x] `purchase_orders/index.blade.php` — converted; two-ibox filter+table; `@can purchase_order.create`; cross-link to purchase-requests.show
- [x] `grns/index.blade.php` — converted; two-ibox filter+table; no create button; cross-link to purchase-orders.show
- [x] `supplier_invoices/index.blade.php` — converted; two-ibox; dual status+payment badges; `text-danger` on balance_due conditional; input-group filter/reset on To Date; cross-link to grns.show
- [x] `supplier_payments/index.blade.php` — converted; two-ibox; input-group filter/reset on To Date
- [x] `suppliers/index.blade.php` — converted; single ibox; ibox-tools create button; inline DELETE form (CSRF + method DELETE) preserved
- [x] All purchase create/edit/show/print pages confirmed still `@extends('adminlte::page')`
- [x] No Purchase Flow, GRN stock-in, or supplier balance logic changed
- [x] No migrations created — no seeders run
- [x] Emergency topbar fix: `route('profile.edit')` → guarded `users.edit` + `route('notifications.index')` → `route('system-notifications.index')`
- [x] Logout updated to clean POST `<form>` button pattern
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] 14 views now on `layouts.inspinia`; 6 auth views on `layouts.inspinia_auth`

---

## Phase 30L — Inspinia Index Pages Batch 3: Inventory / Stock (2026-08-04)
- [x] Dashboard route-safety check passed — no undefined routes in topbar/sidebar
- [x] `items/index.blade.php` — converted; single ibox; ibox-tools create button; Qty + Selling Price right-aligned; S/N badge preserved; Edit + Aliases action buttons
- [x] `item_categories/index.blade.php` — converted; single ibox; ibox-tools create button; inline DELETE form preserved; active/inactive badge
- [x] `units/index.blade.php` — converted; single ibox; ibox-tools create button; inline DELETE form preserved; active/inactive badge
- [x] `warehouses/index.blade.php` — converted; single ibox; ibox-tools create button; inline DELETE form preserved; active/inactive badge
- [x] `stock_transfers/index.blade.php` — converted; two-ibox filter+table; `@canany` create button moved to ibox-tools; 6-status badge map; `hasPages()` pagination guard preserved; Full Report link preserved; `btn-xs` → `btn-sm` (Bootstrap 4 standard)
- [x] `item_aliases/index.blade.php` — converted; single ibox; ibox-tools create + back buttons; inline item-filter GET form preserved (auto-submit on change); dual Default + Status badges; inline DELETE form preserved
- [x] **Pages skipped — do not exist:**
  - `stock_adjustments/index.blade.php` — no index route or view (only create/store); skipped
  - `stock_movements/index.blade.php` — not in project (movements tracked via GRN/gate-pass/transfer); skipped
  - `item_serial_numbers/index.blade.php` — no dedicated index view; serial numbers managed via inventory-reports; skipped
- [x] All inventory create/edit/show/print pages confirmed still `@extends('adminlte::page')`
- [x] No StockService, GRN stock-in, Gate Pass stock-out, transfer release/receive, or warehouse balance logic changed
- [x] No migrations created — no seeders run
- [x] No compatibility CSS changes needed — Phase 30I pagination rules cover all batch-3 pages
- [x] AdminLTE fallback: at least one accounting/GL page confirmed still `adminlte::page`
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] 20 views now on `layouts.inspinia`; 6 auth views on `layouts.inspinia_auth`
- [x] `stock-transfer-report.index` route confirmed present (used in filter bar)

---

## Phase 30M — Inspinia Index Pages Batch 4: Accounting / Finance (2026-08-04)
- [x] `account_groups/index.blade.php` — converted; single ibox; `@can account_group.create` ibox-tools; `@can account_group.edit` btn-sm; `$groups`; `hasPages()` guard
- [x] `fiscal_years/index.blade.php` — converted; single ibox; `@can fiscal_year.create` ibox-tools; Set Current inline form (`@can fiscal_year.edit`) preserved; Close inline form (`@can fiscal_year.close`) with `onsubmit confirm` preserved; btn-xs→btn-sm; `hasPages()` guard
- [x] `journal_vouchers/index.blade.php` — converted; two-ibox filter+table; from/to/status/$statuses/search filter; `@can journal_voucher.create` ibox-tools; 9-column table; status badge via PHP array lookup; description overflow style preserved; `hasPages()` guard
- [x] `chart_of_accounts/index.blade.php` — converted; two-ibox filter+table; search/type/$types/active filter; `@can chart_of_accounts.create` ibox-tools; `@can chart_of_accounts.edit` edit btn; Cash/Bank/Control flag badges; `hasPages()` guard
- [x] `gl_account_mappings/index.blade.php` — converted; single ibox; `@can gl_mapping.preview` Journal Preview in ibox-tools; `@can gl_mapping.edit` edit btn-sm; unconfigured alert block preserved; no pagination (`@foreach`); info text moved to `<p>` in ibox-content; btn-xs→btn-sm
- [x] `expenses/index.blade.php` — converted; two-ibox filter+table; search/category/status/from/to filter; `@can expense.create` ibox-tools; `@can expense.edit` + `$expense->isDraft()` guarded edit; `withQueryString()->links()` preserved; `hasPages()` guard
- [x] `expense_categories/index.blade.php` — converted; two-ibox filter+table; search/is_active filter; `@can expense_category.create` ibox-tools; `@can expense_category.edit` + `@can expense_category.delete` inline DELETE preserved; `$categories->firstItem() + $loop->index` row numbering; `withQueryString()->links()` preserved
- [x] `expense_reports/index.blade.php` — converted; filter ibox + info-box summary cards (kept as-is, compat CSS handles) + 3 breakdown `.ibox` (By Category / By Month / By Method) + main expense list `.ibox` with `<tfoot>` page total; `@can expense.view` link guard; `withQueryString()->links()` preserved
- [x] **Pages skipped — do not exist:** `accounting_periods/index.blade.php` — no dedicated index view; skipped
- [x] All accounting create/edit/show/print pages confirmed still `@extends('adminlte::page')`
- [x] No GL posting logic, JV approval/posting/cancel, customer/supplier balance formulas, expense posting logic, or financial statement formulas changed
- [x] No migrations created — no seeders run
- [x] AdminLTE fallback: non-index pages still use adminlte::page — confirmed
- [x] `php artisan optimize:clear` — all caches cleared
- [x] 28 index views now on `layouts.inspinia` (8 sales, 6 purchase, 6 inventory, 8 accounting)
- [x] 6 auth views on `layouts.inspinia_auth`

---

## Phase 30N — Inspinia Index Pages Batch 5: HR / Attendance / Leave (2026-08-04)
- [x] `departments/index.blade.php` — converted; two-ibox filter+table; search + is_active + Reset; `@can department.edit` btn-sm; `@can department.create` inline DELETE; `$dept->employees_count`; `hasPages()` guard
- [x] `designations/index.blade.php` — converted; two-ibox filter+table; search + department_id + is_active + Reset; `@can designation.edit` btn-sm; `@can designation.create` inline DELETE; `$desig->employees_count`; `hasPages()` guard
- [x] `employees/index.blade.php` — converted; two-ibox filter+table; search + dept + status + employment_type + Reset; PHP `match()` badge; `employees.show` link on EMP No. and eye btn; `@can employee.edit` btn-sm; info alert preserved; `hasPages()` guard
- [x] `work_shifts/index.blade.php` — converted; single ibox (no filter); `@can work_shift.create` ibox-tools; night shift badge fixed: `{{ ... }}` → `{!! !!}` (was HTML-escaping the badge span); `@can work_shift.edit` btn-sm; `hasPages()` guard
- [x] `attendance_devices/index.blade.php` — converted; single ibox + info alert above; `@can attendance_device.create` ibox-tools; `@can attendance_device.edit` btn-sm; `hasPages()` guard
- [x] `attendance_imports/index.blade.php` — converted; pending batches ibox (conditional on count) + raw logs ibox; Import CSV button moved to ibox-tools of raw logs ibox; Process btn-xs→btn-sm; employee cell fixed: `{{ }}` → `{!! !!}` (was HTML-escaping "Unmatched" span); modal preserved; `hasPages()` guard
- [x] `attendance/index.blade.php` — converted; two-ibox filter+table; search + dept + date_from + date_to + status; PHP `match()` badge; Manual Entry button in ibox-tools; `@can attendance.edit` btn-sm; `hasPages()` guard
- [x] `attendance_leave_reports/index.blade.php` — converted; 5-card hub grid converted from `.card.card-X.card-outline` to `.ibox.ibox-content` tiles in a `col-md-4` grid; Route::has() guards added to all 5 report links; no table/pagination
- [x] `leave_types/index.blade.php` — converted; single ibox (no filter, no pagination); `@can leave-types.create` ibox-tools; `@can leave-types.edit` btn-sm; Paid/Attachment/Status badge spans rewritten from PHP string `{{ }}` to Blade `@if` blocks (was HTML-escaping badges)
- [x] `leave_requests/index.blade.php` — converted; two-ibox filter+table; search + leave_type + status + year; PHP `match()` badge; request_number show link; `@can leave-requests.edit` + isDraft() guarded edit; `hasPages()` guard
- [x] `leave_balances/index.blade.php` — converted; two-ibox filter+table; search + leave_type + year; Generate Balances POST form in ibox-tools; Adjust Balance modal preserved; `@push('js')` script preserved; `@can leave-balances.edit` adjust btn-sm; `hasPages()` guard
- [x] All attendance/leave/HR create/edit/show pages confirmed still `@extends('adminlte::page')`
- [x] No attendance processing logic, leave approval/rejection, leave balance formulas, employee logic, or biometric logic changed
- [x] No migrations created — no seeders run
- [x] `php artisan optimize:clear` — all caches cleared
- [x] 39 index views now on `layouts.inspinia` (8 sales, 6 purchase, 6 inventory, 8 accounting, 11 HR/attendance/leave)
- [x] 6 auth views on `layouts.inspinia_auth`

---

## Phase 30N (Sidebar Restructure) — Official Business Menu Order Applied (2026-08-04)
- [x] Sidebar rewritten to match official business menu order from project owner
- [x] Order: Dashboard → Communications → Stock Management → Customers → Suppliers → Sales → Returns → Deliveries → Internal File → Purchase → Expenses → Accounting → Human Resources → Reports → Settings
- [x] Communications moved from near bottom to position 2
- [x] Customers promoted to direct top-level link (removed from Sales sub-menu)
- [x] Suppliers promoted to direct top-level link (removed from Purchase sub-menu)
- [x] Returns group created: Credit Note (Sales Return skipped — no route)
- [x] Deliveries group created: Gate Pass + Delivery Note (Packing Slip skipped — no route)
- [x] Internal File group created: Dispatches / Internal Transfer (Order Note skipped — no route)
- [x] Stock Management: Measurement sub-group added (nav-third-level): Item Unit + Item Categories (Item Brands skipped — no route)
- [x] Item Categories + Units moved from Settings to Stock Management > Measurement
- [x] Sales group labels updated: "Proforma Invoices" → "Proforma Orders"
- [x] Stock Transfer renamed: "Stock Transfers" → "Stock Transfer"
- [x] Stock Alerts renamed: "Stock Alerts" → "Stock Alert"
- [x] Reports consolidated: Salesman Report + Profit Settlement + Customer Statements + Inventory Report + Accounting Reports + Management Reports (Customer Order Report, Deliveries Report, Supplier Reports skipped — no routes)
- [x] GL Mappings removed from Settings (remains in Accounting group)
- [x] Settings retains: Branches, Users, Roles & Permissions, Currencies, Warehouses
- [x] All 72 non-dashboard route() calls wrapped with Route::has() ✓
- [x] All permission names from existing validated sidebar (PhaseOneSeeder-verified) ✓
- [x] Topbar unchanged and confirmed safe ✓
- [x] Skipped items documented with Blade comments in sidebar ✓
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] No pages converted — sidebar only
- [x] No business logic changed
- [x] No migrations created — no seeders run

---

## Phase 30O — Inspinia Index Pages Batch 6: Communication & Settings (2026-08-04)
- [x] `internal_mail/inbox.blade.php` — converted; two-ibox filter+table; search + priority + read status filter; `@php` block for `$pivot`/`$isRead`/`$badgePriority` preserved; `tr` `font-weight-bold` for unread rows; NEW badge; `@can internal_mail.send` Compose button in ibox-tools with `Route::has()` guard; attachment icon; `hasPages()` guard
- [x] `internal_mail/sent.blade.php` — converted; two-ibox filter+table; search only filter; recipients take(3) + " more" preserved; priority badge; `@can internal_mail.send` Compose button in ibox-tools with `Route::has()` guard; `hasPages()` guard
- [x] `system_notifications/index.blade.php` — converted; two-ibox (filter + flex-div card list, NOT a table); type + category + read status filter; unread count badge in ibox-title; `@can system_notifications.manage` Mark All Read POST form in ibox-tools with `Route::has()` guard; per-notification View/Mark Read/Dismiss forms preserved; `btn-xs` → `btn-sm`; `hasPages()` guard
- [x] `email_outbox/index.blade.php` — converted; dispatch panel ibox (conditional on `$pendingCount > 0`, inside `@can email_outbox.dispatch`, styled with left border + yellow ibox-title) + filter ibox + table ibox; confirm checkbox + limit input + POST form preserved; status badge via PHP `match()`; `btn-xs` → `btn-sm` for view and requeue; `@can email_outbox.dispatch` requeue guard preserved; `hasPages()` guard
- [x] `branches/index.blade.php` — converted; single ibox; `Route::has()` guarded create button in ibox-tools; `table-sm table-hover`; inline DELETE form preserved (original had no @can guards — none added); `hasPages()` guard added (original had none)
- [x] `users/index.blade.php` — converted; single ibox; `Route::has()` guarded create button in ibox-tools; Branch name + Role join displayed; inline DELETE form preserved (no @can guards — matches original); `hasPages()` guard added
- [x] `roles/index.blade.php` — converted; single ibox; `Route::has()` guarded create button in ibox-tools; permissions pluck+join preserved; inline DELETE form preserved (no @can guards — matches original); `hasPages()` guard added
- [x] `currencies/index.blade.php` — converted; single ibox; `@can currency.create` ibox-tools with `Route::has()` guard; `table-success` row for base currency preserved; `@can currency.edit` btn-sm (was btn-xs); `@can currency.set_base` + `@unless($currency->is_base)` Set Base inline form preserved; btn-xs → btn-sm; no pagination (none in original); info alert preserved below ibox
- [x] `mail_settings/index.blade.php` — converted; two iboxes: Current Configuration (dl.row) + Send Test Email (`@can mail_settings.edit`); Edit Settings button in ibox-tools of first ibox with `Route::has()` guard; `@if($setting)` conditional preserved; test email form-inline + `is_active` guard preserved; `@if(!$setting)` alert preserved
- [x] **Skipped (confirmed not found):** `permissions/index.blade.php` — does not exist; `audit_logs/index.blade.php` — does not exist
- [x] All communication/settings create/edit/show pages confirmed still `@extends('adminlte::page')`
- [x] No email sending logic, SMTP settings, notification creation, user/role permission logic, branch enforcement, currency exchange rate logic changed
- [x] No migrations created — no seeders run
- [x] `php artisan optimize:clear` — all caches cleared
- [x] 48 content views now on `layouts.inspinia` (39 from Batches 1–5 + 9 from Batch 6)
- [x] 6 auth views on `layouts.inspinia_auth`

---

## Phase 30P — Index Migration QA and Route Safety Audit (2026-08-04)

### Part A — Converted View Inventory
- [x] 48 content views on `layouts.inspinia` confirmed (grep verified, no unexpected conversions)
- [x] 6 auth views on `layouts.inspinia_auth` confirmed
- [x] No create/edit/show/form/print page accidentally converted
- [x] Print views confirmed on `prints.layout` (standalone, no AdminLTE dependency)

### Part B — Route Safety Audit
- [x] All 74 route() calls in sidebar checked: 2 are `dashboard` (always registered), 72 wrapped in `Route::has()`
- [x] All route() calls in 48 converted content views verified against `php artisan route:list --json`
- [x] All verified as existing routes — no undefined route crashes found
- [x] Topbar: `system-notifications.index` ✓, `users.edit` guarded by `Route::has() && auth()->check()` ✓, `logout` ✓
- [x] `fiscal-years.set-current` ✓, `system-notifications.dismiss/mark-read/mark-all-read` ✓
- [x] `accounting-reports.cash-movement/customer-aging/debtors-creditors/sales-register/supplier-aging/tax-summary` ✓
- [x] `attendance-imports.import`, `attendance-imports.process` ✓
- [x] All 5 attendance-leave-reports sub-routes ✓

### Part C — Sidebar and Topbar Safety
- [x] Official 15-group business menu order intact ✓
- [x] All 72 optional sidebar links wrapped in `Route::has()` ✓
- [x] Settings remains last group ✓
- [x] Dashboard route safe (always registered) ✓
- [x] Topbar: no profile.edit crash; logout is POST-only form ✓
- [x] 7 skipped items with no routes documented as Blade comments ✓

### Part D — Permission Visibility Audit
- [x] All sidebar permission names verified against PhaseOneSeeder.php
- [x] Sales group: `sales.view`, `sales_order.view`, `tax_invoice.view`, `receipt.view`, `call_off_order.view` ✓
- [x] Purchase group: `purchase_request.view`, `purchase_order.view`, `grn.view`, `supplier_invoice.view`, `supplier_payment.view`, `supplier_statement.view`, `purchase_recording.view` ✓
- [x] Stock group: `stock.view`, `stock.adjust`, `stock.transfer.view` ✓
- [x] Accounting group: all 11 permission names verified against seeder ✓
- [x] HR group: hyphen-format permissions (`work-shifts.view`, `attendance-devices.view`, etc.) all match seeder ✓
- [x] Communication: `internal_mail.view`, `internal_mail.send`, `system_notifications.view`, `email_outbox.view`, `mail_settings.view` ✓
- [x] Settings: `branches.view`, `users.view`, `roles.view`, `currency.view`, `stock.view` ✓
- [x] **Bug fixed:** `work_shifts/index.blade.php` had wrong `@can('work_shift.create')` → corrected to `@can('work-shifts.create')` and `@can('work-shifts.edit')`
- [x] **Bug fixed:** `attendance_devices/index.blade.php` had wrong `@can('attendance_device.create')` → corrected to `@can('attendance-devices.create')` and `@can('attendance-devices.edit')`

### Part E — Page Load QA (Route-level verification)
- [x] `/dashboard` — route confirmed, view confirmed on `layouts.inspinia` ✓
- [x] Sales: quotations.index, proformas.index, customers.index, sales-orders.index, tax-invoices.index, customer-receipts.index — all routes ✓
- [x] Purchase: suppliers.index, purchase-requests.index, purchase-orders.index, grns.index, supplier-invoices.index, supplier-payments.index — all routes ✓
- [x] Stock: items.index, item-categories.index, units.index, warehouses.index, item-aliases.index, stock-transfers.index — all routes ✓
- [x] Accounting: journal-vouchers.index, chart-of-accounts.index, account-groups.index, gl-account-mappings.index, expenses.index, expense-categories.index, fiscal-years.index — all routes ✓
- [x] HR: departments.index, designations.index, employees.index, work-shifts.index, attendance-devices.index, attendance-imports.index, attendance.index, attendance-leave-reports.index, leave-types.index, leave-requests.index, leave-balances.index — all routes ✓
- [x] Communication: internal-mail.inbox, internal-mail.sent, system-notifications.index, email-outbox.index — all routes ✓
- [x] Settings: branches.index, users.index, roles.index, currencies.index, mail-settings.index — all routes ✓

### Part F — Form/Detail Fallback Audit
- [x] Spot-checked 10 create/edit/show pages — all confirmed `@extends('adminlte::page')` ✓
- [x] Print views confirmed on `prints.layout` (no AdminLTE, no Inspinia dependency) ✓
- [x] No create/edit/show/print page was converted ✓

### Part G — Visual/Compatibility Audit
- [x] ibox, ibox-title, ibox-tools, ibox-content pattern consistent across all 48 views ✓
- [x] `btn-xs` → `btn-sm` migration complete in all converted views ✓
- [x] `hasPages()` guard on pagination added to all converted views ✓
- [x] Flash alerts with dismiss button consistent ✓
- [x] Inline DELETE forms (CSRF + METHOD DELETE) preserved ✓
- [x] Modals (`leave_balances` adjust, `attendance_imports` CSV) preserved ✓
- [x] `@push('js')` script preserved in `leave_balances/index.blade.php` ✓
- [x] Measurement nav-third-level sub-group in sidebar working ✓
- [x] No new CSS changes needed — existing compat CSS covers all cases ✓

### Part H — Fixes Applied
- [x] `work_shifts/index.blade.php` — `work_shift.create` → `work-shifts.create`; `work_shift.edit` → `work-shifts.edit`
- [x] `attendance_devices/index.blade.php` — `attendance_device.create` → `attendance-devices.create`; `attendance_device.edit` → `attendance-devices.edit`

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No seeders run ✓
- [x] No stock logic changed ✓
- [x] No GL logic changed ✓
- [x] No customer/supplier balance logic changed ✓
- [x] No print layouts changed ✓

### Commands Run
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all migrations run, none pending ✓
- [x] `php artisan route:list --json` — used for route name verification ✓

---

## Phase 30Q — Inspinia Show Page Pilot: Fiscal Year (2026-08-05)
- [x] `fiscal_years/show.blade.php` — converted to `@extends('layouts.inspinia')`
- [x] `@section('content_header')` with embedded HTML → `@section('content_header', $fiscalYear->name)` (string only)
- [x] `@section('breadcrumb')` added — Dashboard → Fiscal Years → {name}
- [x] Back button moved from `content_header` HTML to `.ibox-tools`
- [x] Fiscal Year details panel: `.card.card-outline` → `.ibox` with `dl.row` for label/value pairs
- [x] Accounting Periods table panel: `.card` → `.ibox` with `.table-responsive`
- [x] `@forelse` empty state preserved: "No periods defined." centered row
- [x] Status badges preserved: `badge-success` (open) / `badge-secondary` (closed/other)
- [x] `is_current` badge: `badge-primary` Yes / plain "No"
- [x] `$fiscalYear->isOpen()`, `$period->isOpen()`, `$fiscalYear->periods->count()` — all variables preserved
- [x] Flash messages preserved: success/error alert with dismiss button
- [x] No @can guards in show page (none existed in original) — no guards added
- [x] No action forms on show page (close/set-current are index-only) — none added
- [x] No JS section in original — none added
- [x] `fiscal_years/index.blade.php` confirmed `@extends('layouts.inspinia')` (unchanged from Phase 30M)
- [x] `fiscal_years/create.blade.php` confirmed `@extends('adminlte::page')` (unchanged)
- [x] `fiscal_years/edit.blade.php` — does not exist in project; no action needed
- [x] No other show pages converted
- [x] No controller logic changed — `FiscalYearController::show()` untouched
- [x] No model changed — `FiscalYear` and `AccountingPeriod` models untouched
- [x] No migration created
- [x] No GL or accounting formula changed
- [x] No print layout changed
- [x] Compatibility CSS — no changes needed; existing compat CSS covers dl.row (Bootstrap 4 native) and badge/button spacing
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] `php artisan migrate:status` — all migrations ran, none pending
- [x] `php artisan route:list | grep -i fiscal` — 5 fiscal year routes confirmed
- [x] `php artisan route:list | grep dashboard` — dashboard route confirmed
- [x] grep `extends('layouts.inspinia')` in fiscal_years/ — show + index confirmed ✓
- [x] grep `extends('adminlte::page')` in fiscal_years/ — create confirmed ✓

### Show Page Pattern Established
- **Details layout:** `.ibox` with `.ibox-title` / `.ibox-tools` (Back button) / `.ibox-content` containing `dl.row` with `dt.col-sm-5` / `dd.col-sm-7` pairs
- **Related table layout:** Second `.ibox` in same row with `.ibox-title` heading and `.ibox-content > .table-responsive > table.table-sm.table-bordered.table-hover`
- **Empty state:** `<tr><td colspan="N" class="text-muted text-center py-3">message</td></tr>`
- **Action buttons:** Back button in `.ibox-tools` of the details ibox; destructive/status forms would follow same inline pattern from index
- **Breadcrumb:** `@section('breadcrumb')` with module index link + current-page `<strong>` active item
- **Content header:** Simple `@section('content_header', $model->name)` — never embed HTML
- **Rollback:** Change `@extends('layouts.inspinia')` back to `@extends('adminlte::page')`, restore `@section('content_header')` block with embedded HTML, replace `.ibox` with `.card`, run `php artisan optimize:clear`

---

## Phase 30R — Inspinia Show Pages Batch 1: All Targets Missing (2026-08-05)

### Finding: All 8 target show pages do not exist — no conversions performed

**Pages inspected and found missing:**

| Target | show.blade.php | show route | Notes |
|--------|---------------|------------|-------|
| `account_groups/show.blade.php` | MISSING | No show route | Module uses index + edit pattern only |
| `item_categories/show.blade.php` | MISSING | Route exists (`item-categories.show`) | Pre-existing gap: route registered, no view |
| `units/show.blade.php` | MISSING | Route exists (`units.show`) | Pre-existing gap: route registered, no view |
| `warehouses/show.blade.php` | MISSING | Route exists (`warehouses.show`) | Pre-existing gap: route registered, no view |
| `departments/show.blade.php` | MISSING | No show route | Module uses index + edit pattern only |
| `designations/show.blade.php` | MISSING | No show route | Module uses index + edit pattern only |
| `leave_types/show.blade.php` | MISSING | No show route | Module uses index + edit pattern only |
| `expense_categories/show.blade.php` | MISSING | No show route | Module uses index + edit pattern only |

- [x] All 8 target show pages confirmed missing (file system scan)
- [x] No show pages created — task says "Do NOT create missing show pages"
- [x] No view files modified
- [x] AdminLTE fallback confirmed: create/edit pages in all 8 modules still `adminlte::page`
- [x] Inspinia confirmed: index pages in all 8 modules still `layouts.inspinia` (from prior batches)
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] `php artisan migrate:status` — all migrations ran, none pending
- [x] Route checks for all 8 modules run — see route analysis in audit doc
- [x] No controller changed — no model changed — no migration created
- [x] No GL/stock/HR/print logic changed
- [x] Compatibility CSS — no changes (no conversion performed)

### Pre-existing Gap Noted (NOT fixed in this phase)
3 modules have show routes registered but no `show.blade.php` view:
- `item_categories` → `item-categories.show` route exists, no view
- `units` → `units.show` route exists, no view
- `warehouses` → `warehouses.show` route exists, no view

These will cause a `ViewNotFoundException` if navigated to. Creating the missing views is deferred to Phase 30S where they can be created AND converted in one step.

### Phase 30S Recommended Targets
From the 27 existing show pages (minus fiscal_years already done), the simplest candidates:
- `chart_of_accounts/show.blade.php` — COA detail, edit button, GL lines table. No posting.
- `email_outbox/show.blade.php` — email log, requeue form. Simple.
- `internal_mail/show.blade.php` — mail message view, archive form. Simple.
- `employees/show.blade.php` — HR employee profile. No posting.
- **Plus** create missing views for `item_categories/show`, `units/show`, `warehouses/show` (routes exist, need views).

---

## Phase 30S — Inspinia Show Pages Batch 2 + Missing Show View Gap Fix (2026-08-05)

### Controllers Fixed (show() method added — view-only, no logic)
- [x] `ItemCategoryController::show()` — added `return view('item_categories.show', compact('itemCategory'))`
- [x] `UnitController::show()` — added `return view('units.show', compact('unit'))`
- [x] `WarehouseController::show()` — added `return view('warehouses.show', compact('warehouse'))`

### Show Views Created (gap fix — ViewNotFoundException resolved)
- [x] `item_categories/show.blade.php` — CREATED; ibox, dl.row; name, code, is_active badge; Edit + Back in ibox-tools
- [x] `units/show.blade.php` — CREATED; ibox, dl.row; name, symbol, is_active badge; Edit + Back in ibox-tools
- [x] `warehouses/show.blade.php` — CREATED; ibox, dl.row; name, code, location, is_active badge; Edit + Back in ibox-tools

### Show Views Converted (existing → Inspinia)
- [x] `chart_of_accounts/show.blade.php` — CONVERTED; col-md-5 (details ibox with `@can('chart_of_accounts.edit')` Edit + Back; dl.row 8 fields; conditional sub-accounts list) + col-md-7 (Recent Posted Lines ibox with table-responsive; empty state; View Full Ledger button to general-ledger.index)
- [x] `email_outbox/show.blade.php` — CONVERTED; col-md-8 (details ibox; subject+badge in ibox-title; `@can email_outbox.dispatch` requeue form + Back in ibox-tools; dl.row with conditional fields; email body section) + col-md-4 (Dispatch Tracking ibox); `@stop` → `@endsection`
- [x] `internal_mail/show.blade.php` — CONVERTED; `$badgePriority` moved from `@section('content_header')` into `@section('content')`; col-md-8 (message ibox; priority badge in ibox-title; archive form + Back in ibox-tools; dl.row header; body div; attachments list) + col-md-4 (Message Info ibox; Compose New ibox guarded by `@can internal_mail.send`)
- [x] `employees/show.blade.php` — CONVERTED; col-md-4 (profile ibox with avatar placeholder + designation + dept + badge + Edit/Back in ibox-tools; status actions ibox `@can employee.status` with activate/suspend/terminate forms + `onclick="return confirm(...)"`) + col-md-8 (4 iboxes: Personal Details, Employment Details, Attendance/Biometric, Salary & Banking `@canany`); full-width Documents ibox with `@forelse` table, document expiry badge logic, `@can employee_document.delete` delete form, `@can employee_document.upload` upload form with `$documentTypes`; `@stop` → `@endsection`; `btn-xs` → `btn-sm`

### QA Checks
- [x] All 7 show views extend `layouts.inspinia` (grep confirmed)
- [x] Zero `@stop` directives in any of the 7 views (grep confirmed)
- [x] Zero `btn-xs` in `employees/show.blade.php` and `internal_mail/show.blade.php` (grep confirmed)
- [x] Zero `adminlte::page` in any of the 7 views (grep confirmed)
- [x] `item-categories.show` route confirmed: `GET|HEAD item-categories/{item_category}` → `ItemCategoryController@show`
- [x] `units.show` route confirmed: `GET|HEAD units/{unit}` → `UnitController@show`
- [x] `warehouses.show` route confirmed: `GET|HEAD warehouses/{warehouse}` → `WarehouseController@show`
- [x] `employees.show` route confirmed: `GET|HEAD employees/{employee}` → `EmployeeController@show`
- [x] `internal-mail.show` route confirmed: `GET|HEAD internal-mail/{internalMessage}` → `InternalMailController@show`
- [x] `email-outbox.show` route confirmed: `GET|HEAD email-outbox/{emailOutboxLog}` → `EmailOutboxController@show`
- [x] `chart-of-accounts.show` route confirmed: `GET|HEAD chart-of-accounts/{chartOfAccount}` → `ChartOfAccountController@show`
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors

### Business Safety
- [x] No GL logic changed — no stock logic changed — no posting logic changed
- [x] No models changed — no migrations created — no seeders run
- [x] No index/create/edit/print pages changed
- [x] Controller changes were minimal show() method stubs only (view-only)
- [x] No customer/supplier balance logic changed
- [x] No HR approval/attendance/leave logic changed
- [x] Print layouts untouched

---

## Phase 30T — Inspinia Show Pages Batch 3: Settings / Admin / Master Data (2026-08-05)

### Inspection Results — All 7 Targets Missing

| Target | show.blade.php | show route | Controller show() | Action |
|--------|---------------|------------|-------------------|--------|
| `branches/show.blade.php` | MISSING | `branches.show` ✓ | MISSING | Created (gap fix) |
| `users/show.blade.php` | MISSING | `users.show` ✓ | MISSING | Created (gap fix) |
| `roles/show.blade.php` | MISSING | `roles.show` ✓ | MISSING | Created (gap fix) |
| `currencies/show.blade.php` | MISSING | No show route | — | SKIPPED (no route) |
| `suppliers/show.blade.php` | MISSING | `suppliers.show` ✓ | MISSING | Created (gap fix) |
| `mail_settings/show.blade.php` | MISSING | No show route | — | SKIPPED (no route) |
| `system_notifications/show.blade.php` | MISSING | No individual show route | — | SKIPPED (no route) |

### Controllers Fixed (show() method added — view-only, no logic)
- [x] `BranchController::show(Branch $branch)` — added `return view('branches.show', compact('branch'))`
- [x] `UserController::show(User $user)` — added `$user->load(['branch','roles'])` + `return view('users.show', compact('user'))`
- [x] `RoleController::show(Role $role)` — added `$role->load('permissions')` + `return view('roles.show', compact('role'))`
- [x] `SupplierController::show(Supplier $supplier)` — added `return view('suppliers.show', compact('supplier'))`

### Show Views Created (gap fix — ViewNotFoundException resolved)
- [x] `branches/show.blade.php` — CREATED; col-md-6 ibox; dl.row: name, code, phone, email, address (pre-wrap), is_active badge; Edit + Back in ibox-tools
- [x] `users/show.blade.php` — CREATED; col-md-6 ibox; dl.row: name, email, branch name, roles (pluck+join), is_active badge, created_at; Edit + Back in ibox-tools
- [x] `roles/show.blade.php` — CREATED; col-md-4 (details ibox: name + count) + col-md-8 (Assigned Permissions ibox: grouped by prefix, badge-light per permission, empty state); Edit + Back in ibox-tools
- [x] `suppliers/show.blade.php` — CREATED; col-md-6 ibox; dl.row: name, phone, email, tin, vrn, address (pre-wrap), credit_days, current_balance (text-danger if positive), is_active badge; Edit + Back in ibox-tools

### Skipped (no show route registered)
- `currencies` — no `currencies.show` route; uses edit-only pattern
- `mail_settings` — no show route; uses index (with display) + edit pattern
- `system_notifications` — no individual show route; uses index-only pattern

### QA Checks
- [x] All 4 new show views extend `layouts.inspinia` (grep confirmed)
- [x] Zero `@stop` directives in any of the 4 views (grep confirmed)
- [x] Zero `btn-xs` in any of the 4 views (grep confirmed)
- [x] Zero `adminlte::page` in any of the 4 views (grep confirmed)
- [x] `branches.show` route confirmed: `GET|HEAD branches/{branch}` → `BranchController@show` ✓
- [x] `users.show` route confirmed: `GET|HEAD users/{user}` → `UserController@show` ✓
- [x] `roles.show` route confirmed: `GET|HEAD roles/{role}` → `RoleController@show` ✓
- [x] `suppliers.show` route confirmed: `GET|HEAD suppliers/{supplier}` → `SupplierController@show` ✓
- [x] `branches/create.blade.php` + `branches/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `users/create.blade.php` + `users/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `roles/create.blade.php` + `roles/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `suppliers/create.blade.php` + `suppliers/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `currencies/create.blade.php` + `currencies/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `mail_settings/edit.blade.php` — confirmed still `adminlte::page` ✓
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] `php artisan migrate:status` — all migrations ran, none pending

### Business Safety
- [x] No GL logic changed — no stock logic changed — no posting logic changed
- [x] No models changed — no migrations created — no seeders run
- [x] No index/create/edit/print pages changed
- [x] Controller changes were minimal view-only show() stubs only
- [x] No supplier balance logic changed (current_balance displayed as read-only stored column)
- [x] No currency exchange-rate logic changed
- [x] No mail/SMTP logic changed
- [x] No role-permission assignment logic changed
- [x] No user activation/deactivation logic changed
- [x] No branch enforcement logic changed
- [x] Print layouts untouched

### Inspinia Show Page Count After Phase 30T
- Phase 30Q: 1 (fiscal_years)
- Phase 30S: 7 (chart_of_accounts, email_outbox, internal_mail, employees, item_categories, units, warehouses)
- Phase 30T: 4 (branches, users, roles, suppliers)
- **Total show pages on Inspinia: 12**

---

## Phase 30U — Inspinia Show Pages Batch 4: Reports / Read-Only Detail Pages (2026-08-05)

### Primary Targets (all converted)
- [x] `salesman_profit_reports/show.blade.php` — converted (filter ibox, info-boxes kept, invoice table with tfoot, back + create-settlement in ibox-tools)
- [x] `salesman_profit_settlements/show.blade.php` — converted (hidden approve/provide forms at top, cancel modal moved inside @section('content'), status badge via @php match, 3-col header ibox, items ibox with tfoot)
- [x] `customer_statements/show.blade.php` — converted (customer summary ibox, filter ibox, info-box summary cards kept, ledger table ibox)
- [x] `supplier_statements/show.blade.php` — converted (@can print button in ibox-tools, supplier summary with balance badge, filter ibox, legend alert, ledger table with hasPages() pagination)

### Optional Targets (both converted)
- [x] `management_reports/profit_summary.blade.php` — converted (disclaimer alert preserved exactly, date filter ibox, 6 info-boxes kept, 3 secondary iboxes, summary table ibox with footer, conditional salesman settlements ibox)
- [x] `inventory_reports/serial_numbers.blade.php` — converted (@can('serial_numbers.manage') Assign Opening Serials in ibox-tools, 6-field filter ibox, 10-column serial table with hasPages() pagination)

### Skipped
- `accounting_reports/aging.blade.php` — file does not exist (confirmed); no view to convert

### Safety Constraints Verified
- [x] No salesman profit calculation logic changed
- [x] No profit settlement workflow logic changed (approve/provide/cancel are POST form targets unchanged)
- [x] No customer statement ledger logic changed
- [x] No supplier statement ledger logic changed
- [x] No stock/inventory logic changed
- [x] No GL/accounting logic changed
- [x] No controllers changed (show views already had controller methods)
- [x] No models changed
- [x] No migrations changed
- [x] No routes changed
- [x] Print layouts untouched
- [x] All 6 files: `grep layouts.inspinia` confirmed
- [x] All 6 files: no `adminlte::page` (grep returns empty)
- [x] All 6 files: no `@stop` (all use `@endsection`)
- [x] `php artisan optimize:clear` — all caches cleared
- [x] `php -l routes/web.php` — no syntax errors
- [x] `php artisan migrate:status` — all migrations Ran, none rolled back

### Key Conversion Notes
- Cancel modal in salesman_profit_settlements/show was originally placed AFTER `@stop` — moved inside `@section('content')` before `@endsection`
- Hidden approve/provide forms placed at top of `@section('content')` with `style="display:none"` to avoid form nesting
- customer-statements.show route is `/customers/{customer}/statement` (not `/customer-statements/{id}`) — verified correct
- supplier-statements.show route is `/suppliers/{supplier}/statement` — verified correct
- info-box elements in all pages kept as-is (covered by coreerp-adminlte-compat.css)

### Inspinia Show Page Count After Phase 30U
- Phase 30Q: 1 (fiscal_years)
- Phase 30S: 7 (chart_of_accounts, email_outbox, internal_mail, employees, item_categories, units, warehouses)
- Phase 30T: 4 (branches, users, roles, suppliers)
- Phase 30U: 6 (salesman_profit_reports, salesman_profit_settlements, customer_statements, supplier_statements, management_reports/profit_summary, inventory_reports/serial_numbers)
- **Total show/detail pages on Inspinia: 18**

---

## Phase 30V — Transactional Show Page Pilot: Quotation Show (2026-08-05)

### Scope
First controlled transactional document show-page conversion. Quotation selected because it has no stock/GL/balance impact.

### Target
- [x] `quotations/show.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Not Converted (by design)
- `quotations/create.blade.php` — still `adminlte::page` ✓
- `quotations/edit.blade.php` — still `adminlte::page` ✓
- `quotations/print.blade.php` — still `prints.layout` ✓
- `proformas/show.blade.php` — still `adminlte::page` ✓
- `sales_orders/show.blade.php` — still `adminlte::page` ✓
- `tax_invoices/show.blade.php` — still `adminlte::page` ✓

### Conversion Details
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` with `<h1>` → `@section('content_header', $quotation->quotation_number)`
- [x] `@section('breadcrumb')` added (Quotations → quotation_number)
- [x] `.card` / `.card-header` / `.card-body` → `.ibox` / `.ibox-title` / `.ibox-tools` / `.ibox-content`
- [x] Status badge added via `@php match($quotation->status)`
- [x] Document details displayed as `dl.row` (date, valid_until, currency, notes, customer, salesman, approver)
- [x] Item table wrapped in `.table-responsive` + `table-sm`; `@forelse` with `@empty` fallback
- [x] Conditional totals tfoot (discount, tax, total) in `thead-light`
- [x] Currency code displayed on TOTAL line
- [x] Expired `valid_until` badge added (display-only, no logic)
- [x] Flash messages added (session success/error/info)
- [x] `@stop` → `@endsection`

### Action Buttons / Forms Preserved
- [x] Back → `route('quotations.index')` ✓
- [x] Print → `route('quotations.print', $quotation)` target="_blank" ✓
- [x] Approve form (if draft): POST `route('quotations.approve', $quotation)` @csrf ✓
- [x] Convert to Proforma form (if approved): POST `route('quotations.convert-to-proforma', $quotation)` @csrf ✓
- [x] Direct Tax Invoice form (if approved): POST `route('quotations.direct-tax-invoice', $quotation)` @csrf + onsubmit confirm ✓

### Form Safety
- [x] No nested forms (all action forms are `d-inline` in `.ibox-tools`, not inside another form)
- [x] All CSRF tokens preserved
- [x] All `onsubmit="return confirm(...)"` preserved
- [x] No modal was present in original — no modal migration needed

### Confidentiality Check
- [x] `system_price` — NOT visible ✓
- [x] `cost_price` — NOT visible ✓
- [x] `salesman_profit` — NOT visible ✓
- [x] `margin` — NOT visible ✓
- [x] Only `selling_price` shown (customer-facing unit price — correct)

### QA
- [x] `grep layouts.inspinia resources/views/quotations/show.blade.php` — matches ✓
- [x] `grep adminlte::page resources/views/quotations` — only create.blade.php and edit.blade.php ✓
- [x] `grep system_price|cost_price|salesman_profit|margin resources/views/quotations/show.blade.php` — zero matches ✓
- [x] `grep @stop resources/views/quotations/show.blade.php` — zero matches ✓
- [x] proformas/show, sales_orders/show, tax_invoices/show — all still adminlte::page ✓
- [x] print.blade.php — still extends prints.layout ✓
- [x] All quotation routes confirmed via route:list ✓
- [x] No controller changed ✓
- [x] No model changed ✓
- [x] No migration created ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran ✓

### Compatibility CSS
No CSS changes needed. `.ibox`, `.ibox-tools`, `.table-responsive`, `dl.row`, `badge-*` all handled by existing Inspinia + compat CSS.

### Transactional Show-Page Pattern Established
| Area | Pattern |
|---|---|
| Document header | Single `.ibox` with status badge in `.ibox-title`, action buttons as `d-inline` forms in `.ibox-tools` |
| Details | `dl.row` with `dt.col-sm-4` / `dd.col-sm-8` in two-column `row` |
| Items | Second `.ibox`, `table-sm table-bordered table-hover`, wrapped in `.table-responsive`, `@forelse` |
| Totals | `<tfoot class="thead-light">` with conditional discount/tax rows + bold TOTAL row |
| Flash | Session alerts at top of `@section('content')` |
| Modal | Move from after `@stop` to inside `@section('content')` before `@endsection` |
| Rollback | Change `@extends('layouts.inspinia')` back to `@extends('adminlte::page')` |

### Inspinia Show Page Count After Phase 30V
- Prior total: 18
- Phase 30V: 1 (quotations/show)
- **New total: 19 show/detail pages on Inspinia**

---

## Phase 30W — Transactional Show Pages Batch 1: Early Sales Documents
**Completed: 2026-08-05**

### Target Pages
- [x] `resources/views/proformas/show.blade.php` — CONVERTED
- [x] `resources/views/sales_orders/show.blade.php` — CONVERTED

### Proforma Show Conversion
- [x] `@extends('layouts.inspinia')`
- [x] `@section('content_header', $proforma->proforma_number)` — plain string
- [x] Breadcrumb: Proforma Invoices → proforma_number
- [x] Status badge: draft/pending_approval/approved/converted/cancelled (5-state match)
- [x] ibox header: proforma_number + badge in ibox-title
- [x] ibox-tools: Back, Print (target=_blank), Edit (if draft), Approve form (if draft/pending_approval), Convert to SO form (if approved), Direct Tax Invoice form (if approved + onsubmit confirm), Cancel modal trigger (if not cancelled)
- [x] ibox-content: two-column dl.row (proforma_number, date, currency, notes | customer, salesman, approved_at, cancelled_at + reason)
- [x] Items ibox: table-sm table-bordered table-hover, @forelse, 7 cols (#, item, qty, selling_price, discount, tax, total)
- [x] Totals tfoot: subtotal, discount (red parens), tax, TOTAL bold with currency
- [x] Cancel modal inside @section('content') before @endsection
- [x] `@endsection` — no `@stop`
- [x] create/edit pages remain adminlte::page ✓
- [x] print page remains prints.layout ✓
- [x] No confidential fields (system_price, cost_price, salesman_profit) ✓
- [x] No nested forms ✓
- [x] All @csrf preserved ✓

### Sales Order Show Conversion
- [x] `@extends('layouts.inspinia')`
- [x] `@section('content_header', $salesOrder->sales_order_number)` — plain string
- [x] Breadcrumb: Sales Orders → sales_order_number
- [x] Status badge: draft/pending_approval/approved/cancelled/closed/default (6-state match) + FULLY APPROVED badge
- [x] ibox-tools: Back, Print, Edit (draft only), Approve form (draft/pending_approval + no multi-approvals), Create Dispatch link (approved/partially_delivered + fully approved), Cancel button
- [x] Details: two-column dl.row (SO number, customer, salesman, sale_type badge, customer_category badge, From Quotation, From Proforma | SO date, expected delivery, currency, approved_by, cancelled_by + reason)
- [x] Notes conditional display
- [x] Items ibox: 7 cols (item+alias+desc, qty, selling_price, discount, VAT%, tax, line_total)
- [x] Totals tfoot: subtotal, discount, tax, TOTAL bold with displayCurrencyCode()
- [x] Customer PO/LPO ibox: PO details, verified status, verify form (@can sales_order_customer_po.verify)
- [x] References ibox: conditional @if references isNotEmpty, SalesOrderReference::referenceTypeLabel()
- [x] Multi-Approval Panel: rejected/progress/fully-approved alerts, approval table with decide buttons (approve/reject modals), assign approvers form with Select2 (@can sales_order_approval.assign)
- [x] Cancel Modal inside @section('content') ✓
- [x] Decide Approval Modal inside @section('content') ✓
- [x] @section('js'): jQuery modal event handler + Select2 init
- [x] `@endsection` — no `@stop`
- [x] create/edit pages remain adminlte::page ✓
- [x] print page remains prints.layout ✓
- [x] No confidential fields ✓
- [x] No nested forms ✓
- [x] All @csrf preserved ✓

### QA
- [x] `grep layouts.inspinia proformas/show.blade.php` — matches ✓
- [x] `grep layouts.inspinia sales_orders/show.blade.php` — matches ✓
- [x] `grep adminlte::page proformas/{create,edit}` — correct ✓
- [x] `grep adminlte::page sales_orders/{create,edit}` — correct ✓
- [x] `grep system_price|cost_price|salesman_profit` proformas/show + sales_orders/show — zero matches ✓
- [x] `grep @stop` proformas/show + sales_orders/show — zero matches ✓
- [x] All proforma routes confirmed via route:list ✓
- [x] All sales-order routes confirmed via route:list ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran ✓
- [x] No controller changed ✓
- [x] No model changed ✓
- [x] No migration created ✓

### Inspinia Show Page Count After Phase 30W
- Prior total: 19
- Phase 30W: 2 (proformas/show, sales_orders/show)
- **New total: 21 show/detail pages on Inspinia**

---

## Phase 30X — Transactional Show Pages Batch 2: Tax Invoice and Customer Receipt
**Completed: 2026-08-05**

### Target Pages
- [x] `resources/views/tax_invoices/show.blade.php` — CONVERTED
- [x] `resources/views/customer_receipts/show.blade.php` — CONVERTED

### Tax Invoice Show Conversion
- [x] `@extends('layouts.inspinia')`
- [x] `@section('content_header', $taxInvoice->tax_invoice_number)` — plain string
- [x] Breadcrumb: Tax Invoices → tax_invoice_number
- [x] `$statusBadge` and `$payBadge` moved to `@php` block at top of `@section('content')`
- [x] Status badge: draft/approved/posted/cancelled (4-state match)
- [x] Payment status badge: unpaid/partially_paid/paid/overdue (4-state match)
- [x] ibox header: number + both badges in ibox-title
- [x] ibox-tools: Back, Print (target=_blank), Delivery Note link (conditional), Sales Order link (conditional)
- [x] Approve form POST `tax-invoices.approve` @csrf onsubmit confirm (if draft + @can)
- [x] Post form POST `tax-invoices.post` @csrf onsubmit confirm (if approved + @can)
- [x] Create Receipt link (if posted + balance_due > 0 + unpaid + @can receipt.create)
- [x] Create Credit Note link (if posted + balance_due > 0 + @can credit_note.create)
- [x] Cancel button data-toggle modal (if not posted/cancelled + @can)
- [x] Posted alert inside ibox-content (conditional)
- [x] Two-column dl.row: invoice_no, sales_order link, delivery_note link, customer, salesman, invoice_date, due_date, invoice_type, sale_type badge, credit_days, due_date conditional, payment_method, payment_reference | status badge, payment status badge, prepared_by, approved_by+at, posted_by+at, cancelled_by+at+reason
- [x] Notes/Terms conditional row preserved
- [x] Customer/Tax Snapshot sub-ibox (conditional on name/tin/vrn/billing_address)
- [x] Invoice Items ibox: 8-col table, @forelse, .table-responsive
- [x] Totals: right-offset table inside second ibox-content block: subtotal, discount, taxable_amount, tax_amount, total (table-primary), amount_paid, credit_note (conditional), balance_due (warning/success conditional row)
- [x] GL panel @include('partials.gl_journal_panel') preserved exactly
- [x] Cancel modal inside `@section('content')` — was already inside content in original
- [x] `@endsection` — no `@stop`
- [x] No confidential fields (salesman profit comment preserved as-is)
- [x] No nested forms ✓

### Customer Receipt Show Conversion
- [x] `@extends('layouts.inspinia')`
- [x] `@section('content_header', $customerReceipt->receipt_number)` — plain string
- [x] Breadcrumb: Customer Receipts → receipt_number
- [x] FX variables ($rcCurr, $rcBase, $rcRate, $rcForeign) moved to `@php` block at top
- [x] `$anyFxAlloc` dead code (never used in template) dropped safely
- [x] Status badge: draft/approved/posted/cancelled (4-state match)
- [x] ibox-tools: Back, Print (target=_blank), Approve form (draft + @can), Post form (approved + @can + confirm), Cancel button (not posted/cancelled + @can)
- [x] Posted alert inside ibox-content
- [x] Two-column dl.row: receipt_no, customer, date, payment_method, reference, bank, account, cheque, mobile_provider, mobile_number, notes | status, currency (FX badge + rate), received_amount (+ base equivalent), allocated, unapplied (warning if > 0), received_by, approved_by+at, posted_by+at, cancelled_by+at+reason
- [x] Invoice Allocations ibox: FX alert (conditional), 7/8-col table (dynamic colspan) @forelse
- [x] All FX columns conditional on $rcForeign preserved exactly
- [x] All balance_after coloring (text-warning/text-success) preserved
- [x] GL panel @include('partials.gl_journal_panel') preserved exactly
- [x] Cancel modal inside `@section('content')` — was already inside content in original
- [x] `@endsection` — no `@stop`
- [x] No confidential fields ✓
- [x] No nested forms ✓

### AdminLTE Fallback Confirmed
- [x] `tax_invoices/index.blade.php` — `layouts.inspinia` ✓
- [x] `tax_invoices/create.blade.php` — `adminlte::page` ✓
- [x] `tax_invoices/print.blade.php` — `prints.layout` ✓
- [x] `customer_receipts/index.blade.php` — `layouts.inspinia` ✓
- [x] `customer_receipts/create.blade.php` — `adminlte::page` ✓
- [x] `customer_receipts/print.blade.php` — `prints.layout` ✓
- [x] `credit_notes/show.blade.php` — `adminlte::page` ✓
- [x] `supplier_invoices/show.blade.php` — `adminlte::page` ✓
- [x] `supplier_payments/show.blade.php` — `adminlte::page` ✓
- [x] `journal_vouchers/show.blade.php` — `adminlte::page` ✓

### Compatibility CSS
No update needed — GL panel partial uses Bootstrap 4 `.card` which renders correctly in Inspinia (same Bootstrap 4 base). All other patterns covered by existing compat shim.

### QA
- [x] `grep layouts.inspinia tax_invoices/show, customer_receipts/show` — both match ✓
- [x] `grep adminlte::page tax_invoices/{create}, customer_receipts/{create}` — correct ✓
- [x] `grep system_price|cost_price|salesman_profit|margin` — zero matches ✓
- [x] `grep @stop` — zero matches ✓
- [x] All tax-invoice routes confirmed via route:list ✓
- [x] All customer-receipt routes confirmed via route:list ✓
- [x] All credit-note, journal-voucher, dashboard routes intact ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran ✓
- [x] No controller changed ✓
- [x] No model changed ✓
- [x] No migration created ✓

### Inspinia Show Page Count After Phase 30X
- Prior total: 21
- Phase 30X: 2 (tax_invoices/show, customer_receipts/show)
- **New total: 23 show/detail pages on Inspinia**

---

## Testing Checklist (Per-Phase)

After each phase:
- [ ] Login works
- [ ] Sidebar renders with correct permissions per role
- [ ] At least one sales form works (create/edit/save)
- [ ] At least one purchase form works
- [ ] Select2 search works on supplier/item/customer fields
- [ ] Print views open and render correctly (no Inspinia CSS leaking in)
- [ ] No PHP errors in `storage/logs/laravel.log`
- [ ] No JS console errors on converted pages
- [ ] Mobile/tablet view renders correctly (sidebar collapses)

---

## Phase 30Y — Transactional Show Pages Batch 3: Credit Notes and Purchase Payables

**Date:** 2026-08-05
**Target:** Convert `credit_notes/show`, `supplier_invoices/show`, `supplier_payments/show` to `layouts.inspinia`.

### Files Converted
- [x] `resources/views/credit_notes/show.blade.php` ← `adminlte::page`
- [x] `resources/views/supplier_invoices/show.blade.php` ← `adminlte::page`
- [x] `resources/views/supplier_payments/show.blade.php` ← `adminlte::page`

### Key Structural Changes Per File

**credit_notes/show:**
- [x] `@extends('layouts.inspinia')` + `@endsection` replacing `@stop`
- [x] Cancel modal moved from AFTER `@stop` → inside `@section('content')` before `@endsection`
- [x] Hidden `approveForm`/`postForm` preserved at top of `@section('content')`
- [x] `$cnPostMsg` `@php` block (uses `baseTotal()`, `displayCurrencyCode()`, `effectiveExchangeRate()`) preserved inside `isApproved()/@can` block
- [x] Status badge match moved to top `@php` block
- [x] reason_type `match()` inline `echo` preserved
- [x] Three-column `table.table-sm.table-borderless` details layout preserved
- [x] 8-col items table + tfoot total preserved
- [x] GL panel `@include` preserved
- [x] FX currency display preserved (exchange rate footnote conditional)

**supplier_invoices/show:**
- [x] Two badges: `[$sBadge, $sLabel]` status + `[$pBadge, $pLabel]` payment (only shown when isPosted())
- [x] Cancel modal moved from AFTER `@stop` → inside `@section('content')`
- [x] `Make Payment` link (`supplier-payments.create` with $supplierInvoice param) preserved with `@can supplier_payment.create`
- [x] Print wrapped in `@can('supplier_invoice.view')` preserved
- [x] Approved-not-posted warning alert between header ibox and items ibox
- [x] 9-col items table + 6-row tfoot (subtotal/discount/tax/total/amount_paid/balance_due)
- [x] Payment allocations ibox (conditional on `->isNotEmpty()`) preserved
- [x] GRN + PO reference links (→ grns.show, purchase-orders.show) preserved
- [x] GL panel `@include` preserved
- [x] Confirm strings with `\n` line breaks preserved verbatim

**supplier_payments/show:**
- [x] Status badge `[$badge, $label]` match at top `@php`
- [x] Cancel modal moved from AFTER `@stop` → inside `@section('content')`
- [x] Print wrapped in `@can('supplier_payment.view')` preserved
- [x] Approved-not-posted warning alert between header ibox and allocations ibox
- [x] Conditional payment method fields (bank/cheque/mobile) preserved
- [x] Allocations table: `@forelse` with `$alloc->supplierInvoice` relation link (→ supplier-invoices.show) preserved
- [x] GL panel `@include` preserved

### QA Checks
- [x] `grep layouts.inspinia` — all 3 show pages match ✓
- [x] `grep adminlte::page` — zero matches in all 3 show pages ✓
- [x] `grep @stop` — zero matches ✓
- [x] `grep system_price|cost_price|salesman_profit|min_price` — zero matches ✓
- [x] credit_notes/index, create, print — AdminLTE/prints.layout ✓
- [x] supplier_invoices/create, print — AdminLTE/prints.layout ✓
- [x] supplier_payments/create, print — AdminLTE/prints.layout ✓
- [x] purchase_orders/show, grns/show, journal_vouchers/show — still AdminLTE ✓
- [x] All credit-note routes confirmed via route:list ✓
- [x] All supplier-invoice routes confirmed via route:list ✓
- [x] All supplier-payment routes confirmed via route:list ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] No controller changed ✓
- [x] No model changed ✓
- [x] No migration created ✓

### Inspinia Show Page Count After Phase 30Y
- Prior total: 23
- Phase 30Y: 3 (credit_notes/show, supplier_invoices/show, supplier_payments/show)
- **New total: 26 show/detail pages on Inspinia**

---

## Phase 30Z — Transactional Show Pages Batch 4: Movement and GL-Control Show Pages
**Completed: 2026-08-05**

### Files Converted (6)
- [x] `resources/views/purchase_orders/show.blade.php` ← `adminlte::page`
- [x] `resources/views/grns/show.blade.php` ← `adminlte::page`
- [x] `resources/views/delivery_notes/show.blade.php` ← `adminlte::page`
- [x] `resources/views/gate_passes/show.blade.php` ← `adminlte::page`
- [x] `resources/views/stock_transfers/show.blade.php` ← `adminlte::page`
- [x] `resources/views/journal_vouchers/show.blade.php` ← `adminlte::page`

### Key Structural Changes Per File

**purchase_orders/show:**
- [x] `@extends('layouts.inspinia')` + `@endsection` replacing `@stop`
- [x] Status badge match (5-state) moved to top `@php` block
- [x] Hidden `approveForm` (style="display:none") preserved at top of `@section('content')`
- [x] Cancel modal moved from AFTER `@stop` → inside `@section('content')` before `@endsection`
- [x] ibox-tools: Back, Print, Edit (isDraft), Approve (isDraft onclick), Create GRN (canReceive()), Cancel (modal trigger)
- [x] 3-column detail layout (PO details | receipt/recon | PR ref/notes/workflow)
- [x] 12-col items table with `$exclVat`/`$chkBadge` inline + 3-row tfoot
- [x] Linked GRNs ibox (conditional) preserved

**grns/show:**
- [x] `@extends('layouts.inspinia')` + `@endsection`
- [x] `$serializedGrnItems`, `$hasSerializedItems` computed in top `@php` block
- [x] Status badge (4-state) in top `@php` block
- [x] `approveForm`/`postForm` hidden at top
- [x] Cancel modal moved from AFTER `@stop` → inside `@section('content')`
- [x] Serial entry ibox (id=serialPostSection): form `serialPostForm` → `grns.post`; textarea + count display
- [x] Serial display ibox (posted + receivedSerials not empty): badge-matched status table
- [x] `@section('js')` after `@endsection`: DOMContentLoaded + `confirmPost()` function

**delivery_notes/show:**
- [x] `@extends('layouts.inspinia')` + `@endsection`
- [x] Status badge match (7-state) in top `@php` block
- [x] Approve form: `class="d-inline ml-1"` with onsubmit confirm
- [x] Modals (`#confirmDeliveryModal`, `#cancelDeliveryNoteModal`) already inside content — just changed `@stop` → `@endsection`
- [x] 2-col `col-md-6` `<p>` paragraph layout preserved
- [x] `#confirmDeliveryModal` with `received_by_name*`, `received_by_phone`, `received_signature` inputs + `@error` validation preserved

**gate_passes/show:**
- [x] `@extends('layouts.inspinia')` + `@endsection`
- [x] `$statusBadge`, `$typeBadge`, `$typeLabel`, `$hasSerializedGpItems` moved to top `@php` block
- [x] Status + type badges in ibox-title h5
- [x] Approve form: `class="d-inline"` + onsubmit confirm
- [x] Release: non-serial d-inline form OR serial anchor `#serialReleaseSection`
- [x] Serial release ibox (`id=serialReleaseSection`): form `serialReleaseForm` → `gate-passes.release`; click-to-add available serials table
- [x] Serial display ibox (isReleased() + releasedSerials not empty)
- [x] `#cancelGatePassModal` already inside content — just changed `@stop` → `@endsection`
- [x] `@section('js')` after `@endsection`: `addSerial()`, `updateGpCount()`, DOMContentLoaded, `confirmRelease()`

**stock_transfers/show:**
- [x] `@extends('layouts.inspinia')` — already used `@endsection` in original
- [x] `content_header` stripped from complex HTML → plain string `$stockTransfer->transfer_number`; action buttons moved to header ibox ibox-tools
- [x] `$badgeMap` (7 states) in `@php` block at top of `@section('content')`
- [x] 2-column layout: `col-md-8` (doc info + items) + `col-md-4` (action iboxes)
- [x] 6 action iboxes (Draft/Submit, Submitted/Approve+Reject, Approved/Release, Released/Receive, Cancel, Received) converted from `.card.card-outline` → `.ibox`
- [x] All `@csrf`, input names, onsubmit confirms, `@canany` guards preserved exactly
- [x] No modal, no `@section('js')` — all forms inline

**journal_vouchers/show:**
- [x] `@extends('layouts.inspinia')` — already used `@endsection` in original
- [x] `content_header` stripped from complex HTML → plain string `$journalVoucher->journal_number`; action buttons/forms moved to header ibox ibox-tools
- [x] `$badge` (4-state) in `@php` block at top of `@section('content')`
- [x] Approve form + Post to GL form: `class="d-inline ml-1"` preserved
- [x] Journal Lines: 5-col table + tfoot Total + imbalance warning (`table-danger` if `abs(debit-credit) >= 0.01`)
- [x] `#cancelModal` was already inside `@section('content')` — no migration needed

### QA Checks
- [x] `grep layouts.inspinia` — all 6 show pages match ✓
- [x] `grep adminlte::page` — zero matches in all 6 show pages ✓
- [x] `grep @stop` — zero matches ✓
- [x] `grep unit_cost` — only in purchase_orders/show and grns/show (expected: procurement docs) ✓
- [x] `grep salesman_profit|margin|cost_price|min_price` — zero matches ✓
- [x] All fallback pages (create/edit/print/index) remain AdminLTE ✓
- [x] delivery_notes/index, gate_passes/index remain AdminLTE (not in scope) ✓
- [x] All purchase-order, grn, delivery-note, gate-pass, stock-transfer, journal-voucher routes confirmed via route:list ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] No controller changed ✓
- [x] No model changed ✓
- [x] No migration created ✓

### Inspinia Show Page Count After Phase 30Z
- Prior total: 26
- Phase 30Z: 6 (purchase_orders/show, grns/show, delivery_notes/show, gate_passes/show, stock_transfers/show, journal_vouchers/show)
- **New total: 32 show/detail pages on Inspinia**

---

## Phase 31A — Create/Edit Form Migration Pilot: Quotation Forms (2026-08-06)

### Scope
First complex create/edit form pair converted to `layouts.inspinia`. Quotations selected as pilot because they are the simplest transactional forms (no stock, no GL auto-posting) and both show/index pages are already on Inspinia.

### Files Converted
- [x] `resources/views/quotations/create.blade.php` ← `adminlte::page`
- [x] `resources/views/quotations/edit.blade.php` ← `adminlte::page`

### Not Converted (by design)
- `quotations/form.blade.php` — shared partial, untouched (Bootstrap 4 cards compatible)
- `quotations/partials/sales_modals.blade.php` — untouched
- `quotations/print.blade.php` — still `prints.layout` ✓
- `proformas/create.blade.php` — still `adminlte::page` ✓
- `proformas/edit.blade.php` — still `adminlte::page` ✓
- `sales_orders/create.blade.php` — still `adminlte::page` ✓
- `sales_orders/edit.blade.php` — still `adminlte::page` ✓
- `tax_invoices/create.blade.php` — still `adminlte::page` ✓
- `customer_receipts/create.blade.php` — still `adminlte::page` ✓

### Asset Safety
- [x] Select2 already loaded in `layouts.inspinia` head via CDN — no extra asset handling needed
- [x] `@section('plugins.Select2', true)` removed from create.blade.php and edit.blade.php (AdminLTE-only; not needed in Inspinia)
- [x] jQuery already loaded in `layouts.inspinia` footer
- [x] Bootstrap JS already loaded in `layouts.inspinia` footer
- [x] `@yield('js')` in Inspinia layout correctly receives `@section('js')` from `form.blade.php` via `@include`
- [x] Select2 CSS: `//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.css` (in layout head) ✓
- [x] Select2 JS: `//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js` (in layout footer) ✓
- [x] `coreerp-adminlte-compat.css` Select2 compatibility rules already present (`.select2-container .select2-selection--single`) ✓
- [x] `coreerp-adminlte-compat.css` card/form compat rules already present — no CSS changes needed ✓

### Conversion Details

**create.blade.php:**
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed
- [x] `@section('title', 'Create Quotation')` — string format preserved
- [x] `@section('content_header', 'Create Quotation')` — Inspinia yields in `<h2>`
- [x] `@section('breadcrumb')` added: Dashboard → Quotations → Create
- [x] Outer `.ibox` wrapper with `.ibox-title` ("New Quotation") + `.ibox-tools` (Back button) + `.ibox-content`
- [x] Form `action="{{ route('quotations.store') }}"` method="POST" preserved
- [x] `@csrf` preserved
- [x] `@include('quotations.form')` preserved — all fields, old(), validation errors, JS intact
- [x] Submit button "Save Quotation" `btn-success` preserved
- [x] Back link `btn-secondary` → `route('quotations.index')` preserved
- [x] `@include('quotations.partials.sales_modals')` outside main form preserved
- [x] `@stop` used (consistent with form.blade.php @stop convention)

**edit.blade.php:**
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed
- [x] `@section('title', 'Edit Quotation')` — string format preserved
- [x] `@section('content_header', 'Edit Quotation')` — Inspinia yields in `<h2>`
- [x] `@section('breadcrumb')` added: Dashboard → Quotations → quotation_number → Edit
- [x] Outer `.ibox` wrapper with `.ibox-title` ("Edit Quotation — {number}") + `.ibox-tools` (Back) + `.ibox-content`
- [x] Form `action="{{ route('quotations.update', $quotation) }}"` method="POST" preserved
- [x] `@csrf` preserved
- [x] `@method('PUT')` preserved
- [x] `@include('quotations.form')` preserved — all existing row data, old(), validation errors, JS intact
- [x] Submit button "Update Quotation" `btn-success` preserved
- [x] Back link `btn-secondary` → `route('quotations.index')` preserved
- [x] `@include('quotations.partials.sales_modals')` outside main form preserved

### Form Behavior Preserved (via form.blade.php, unchanged)
- [x] Customer Select2 AJAX search (`route('ajax.customers.search')`) ✓
- [x] Item/Alias Select2 AJAX search (`route('ajax.items.search')`) ✓
- [x] Main item search in alias modal (`route('ajax.main-items.search')`) ✓
- [x] Price lookup AJAX (`route('ajax.prices.lookup')`) ✓
- [x] AJAX customer create (`route('ajax.customers.store')`) ✓
- [x] AJAX alias create (`route('ajax.item-aliases.store')`) ✓
- [x] `addRow()` dynamic row template ✓
- [x] `removeRow` click handler ✓
- [x] `calculateRow()` qty/price/discount/tax → line total ✓
- [x] `calculateGrandTotal()` summing all `.line-total` ✓
- [x] Currency change handler updates exchange_rate + curr_code_hint ✓
- [x] Form submit guard: alert if no items ✓
- [x] `initializeCustomerSelect()` and `initItemSelect()` tied to Select2 ✓
- [x] `loadBestPrice()` on item select ✓
- [x] All input names: `customer_id`, `quotation_date`, `valid_until`, `currency_id`, `exchange_rate`, `notes` ✓
- [x] Item row names: `items[N][item_alias_id]`, `items[N][quantity]`, `items[N][selling_price]`, `items[N][discount_amount]`, `items[N][tax_rate]` ✓
- [x] Edit page loads existing rows with correct index (0…N-1) ✓

### Confidentiality Check
- [x] `system_price` — NOT visible (backend only) ✓
- [x] `cost_price` — NOT visible ✓
- [x] `salesman_profit` — NOT visible ✓
- [x] `margin` — NOT visible ✓
- [x] `min_price` / `selling_price` alias minimum — NOT visible (backend validation only) ✓
- [x] Only customer-facing `selling_price` field displayed ✓

### AdminLTE Fallback Confirmed
- [x] `quotations/index.blade.php` — `layouts.inspinia` ✓
- [x] `quotations/show.blade.php` — `layouts.inspinia` ✓
- [x] `quotations/print.blade.php` — `prints.layout` ✓
- [x] `proformas/create.blade.php` — `adminlte::page` ✓
- [x] `proformas/edit.blade.php` — `adminlte::page` ✓
- [x] `sales_orders/create.blade.php` — `adminlte::page` ✓
- [x] `sales_orders/edit.blade.php` — `adminlte::page` ✓

### Compatibility CSS
No changes needed. Bootstrap 4 `.card` classes from `form.blade.php` are handled by existing `coreerp-adminlte-compat.css` rules. Select2 compat rules (`.select2-container .select2-selection--single`, height 38px) already present and cover form page use.

### QA Checks
- [x] `grep layouts.inspinia resources/views/quotations/create.blade.php` — matches ✓
- [x] `grep layouts.inspinia resources/views/quotations/edit.blade.php` — matches ✓
- [x] `grep adminlte::page resources/views/quotations/` — zero matches ✓
- [x] `grep system_price|cost_price|salesman_profit|margin|min_price resources/views/quotations/create.blade.php resources/views/quotations/edit.blade.php resources/views/quotations/form.blade.php` — zero matches ✓
- [x] `grep adminlte::page resources/views/proformas/create.blade.php resources/views/proformas/edit.blade.php resources/views/sales_orders/create.blade.php resources/views/sales_orders/edit.blade.php` — all match (AdminLTE confirmed) ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All quotation routes confirmed via route:list ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store/update validation logic changed ✓
- [x] No approval/cancel/convert logic changed ✓
- [x] No price lookup backend changed ✓
- [x] No minimum selling price backend validation changed ✓
- [x] No stock logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Form Migration Pattern Established
| Area | Pattern |
|---|---|
| Layout extension | `@extends('layouts.inspinia')` |
| Select2 | Already in Inspinia layout head/footer — remove `@section('plugins.Select2', true)` |
| Title | `@section('title', 'Action Resource')` — string only |
| Content header | `@section('content_header', 'Action Resource')` — string only |
| Breadcrumb | `@section('breadcrumb')` with module index + optional show + active Create/Edit |
| Outer wrapper | `.ibox > .ibox-title + .ibox-tools (Back) + .ibox-content` |
| Form inside ibox-content | Standard Bootstrap 4 form; partial's `.card` blocks render via compat CSS |
| JS | `@section('js')` in shared `form.blade.php` → `@yield('js')` in Inspinia layout ✓ |
| Modals | Outside main `<form>` tag, inside `@section('content')` |
| Submit buttons | Wrapped in `div.form-group.mt-3` inside `.ibox-content` |

### Inspinia Form Page Count After Phase 31A
- Phase 31A: 2 (quotations/create, quotations/edit)
- **Total form pages on Inspinia: 2**

---

## Phase 31B — Sales Forms Batch 1: Proforma Create/Edit (2026-08-06)

### Scope
Proforma create/edit forms converted using the Phase 31A pattern. Proformas selected because their index + show pages are already on Inspinia, and their form structure closely mirrors the Quotation pattern.

### Files Converted
- [x] `resources/views/proformas/create.blade.php` ← `adminlte::page`
- [x] `resources/views/proformas/edit.blade.php` ← `adminlte::page`

### Not Converted (by design)
- `proformas/form.blade.php` — used only by proforma create/edit; Bootstrap 4 `.card` markup compatible; untouched
- `proformas/print.blade.php` — still `prints.layout` ✓
- `quotations/create.blade.php` — `layouts.inspinia` (Phase 31A) ✓
- `quotations/edit.blade.php` — `layouts.inspinia` (Phase 31A) ✓
- `sales_orders/create.blade.php` — still `adminlte::page` ✓
- `sales_orders/edit.blade.php` — still `adminlte::page` ✓
- `tax_invoices/create.blade.php` — still `adminlte::page` ✓
- `customer_receipts/create.blade.php` — still `adminlte::page` ✓

### Asset Safety
- [x] Select2 CSS + JS already in `layouts.inspinia` via CDN — no extra handling needed
- [x] `@section('plugins.Select2', true)` removed from create.blade.php and edit.blade.php
- [x] `proformas/form.blade.php` uses `theme: 'bootstrap4'` in Select2 JS — kept as-is (safe):
  - All Select2 containers have base class `.select2-container` regardless of theme
  - Compat CSS `.coreerp-inspinia .select2-container .select2-selection--single` (descendant selector) matches all themes
  - No `select2-bootstrap4` theme CSS needed — default Select2 CSS + compat CSS handles sizing
  - No JS errors — `theme: 'bootstrap4'` without theme CSS adds a CSS class only; Select2 functions normally
- [x] jQuery, Bootstrap JS already in Inspinia layout footer ✓
- [x] `@yield('js')` in Inspinia layout receives `@section('js')` from `form.blade.php` via `@include` ✓
- [x] No compat CSS changes needed ✓

### Proforma form.blade.php — Only by Proformas
- [x] Grep confirmed: `proformas/form.blade.php` is `@include`d only by `proformas/create.blade.php` and `proformas/edit.blade.php`
- [x] No other module references this partial

### Conversion Details

**create.blade.php:**
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed
- [x] `@section('content_header')` `<h1>` block → `@section('content_header', 'Create Proforma Invoice')` (string)
- [x] `@section('breadcrumb')` added: Dashboard → Proforma Invoices → Create
- [x] Outer `.ibox` wrapper: `.ibox-title` ("New Proforma Invoice") + `.ibox-tools` (Back) + `.ibox-content`
- [x] Form `action="{{ route('proformas.store') }}"` method="POST" preserved
- [x] `@csrf` preserved
- [x] `@include('proformas.form')` preserved — all fields, old(), validation errors, JS intact
- [x] Submit "Save Proforma" btn-success + Back btn-secondary → `route('proformas.index')` preserved
- [x] `@include('quotations.partials.sales_modals')` outside main form preserved
- [x] `@stop` (consistent with form.blade.php convention)

**edit.blade.php:**
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed
- [x] `@section('content_header')` `<h1>` block → `@section('content_header', 'Edit Proforma Invoice')` (string)
- [x] `@section('breadcrumb')` added: Dashboard → Proforma Invoices → proforma_number → Edit
- [x] Outer `.ibox` wrapper: `.ibox-title` ("Edit Proforma Invoice — {number}") + `.ibox-tools` (Back) + `.ibox-content`
- [x] Form `action="{{ route('proformas.update', $proforma) }}"` method="POST" preserved
- [x] `@csrf` preserved
- [x] `@method('PUT')` preserved
- [x] `@include('proformas.form')` preserved — all existing row data, old(), validation errors, JS intact
- [x] Submit "Update Proforma" btn-success + Back btn-secondary → `route('proformas.index')` preserved
- [x] `@include('quotations.partials.sales_modals')` outside main form preserved

### Form Behavior Preserved (via form.blade.php, unchanged)
- [x] Customer Select2 AJAX search (`ajax.customers.search`) ✓
- [x] Item/Alias Select2 AJAX search (`ajax.items.search`) ✓
- [x] Main item search in alias modal (`ajax.main-items.search`, dropdownParent: `#aliasModal`) ✓
- [x] Price lookup AJAX (`ajax.prices.lookup`) ✓
- [x] AJAX customer create (`ajax.customers.store`) ✓
- [x] AJAX alias create (`ajax.item-aliases.store`) ✓
- [x] `addRow()` dynamic row template (tax_rate defaults to 18) ✓
- [x] `.remove-row` click handler ✓
- [x] `calculateRow()` qty/price/discount/tax → line total ✓
- [x] `calculateTotals()` subtotal + discount + VAT + grand total (4 display fields) ✓
- [x] Currency change handler (`#currency_id`) updates exchange_rate + curr_code_hint ✓
- [x] `.item-select` change handler fills `item_id` + `description` + price ✓
- [x] `lookupPrice()` AJAX call on item select ✓
- [x] Input names: `customer_id`, `proforma_date`, `currency_id`, `exchange_rate`, `notes` ✓
- [x] Item row names: `items[N][item_alias_id]`, `items[N][item_id]`, `items[N][description]`, `items[N][quantity]`, `items[N][selling_price]`, `items[N][discount_amount]`, `items[N][tax_rate]` ✓
- [x] Edit page loads existing rows with correct index (0…N-1), `item_id` hidden input ✓
- [x] 4 totals display fields: `#subtotalDisplay`, `#discountDisplay`, `#taxDisplay`, `#grandTotalDisplay` ✓

### Confidentiality Check
- [x] `system_price` — NOT visible ✓
- [x] `cost_price` — NOT visible ✓
- [x] `salesman_profit` — NOT visible ✓
- [x] `margin` — NOT visible ✓
- [x] `min_price` — NOT visible ✓
- [x] Only customer-facing `selling_price` field displayed ✓

### AdminLTE Fallback Confirmed
- [x] `proformas/index.blade.php` — `layouts.inspinia` ✓
- [x] `proformas/show.blade.php` — `layouts.inspinia` ✓
- [x] `proformas/print.blade.php` — `prints.layout` ✓
- [x] `quotations/create.blade.php` + `quotations/edit.blade.php` — `layouts.inspinia` (Phase 31A) ✓
- [x] `sales_orders/create.blade.php` + `sales_orders/edit.blade.php` — `adminlte::page` ✓
- [x] `tax_invoices/create.blade.php` — `adminlte::page` ✓
- [x] `customer_receipts/create.blade.php` — `adminlte::page` ✓

### Compatibility CSS
No changes needed. Existing compat CSS covers: `.card` Bootstrap 4 cards from form.blade.php, `.select2-container .select2-selection--single` sizing (descendant selector works with all Select2 themes including `bootstrap4` theme), form controls, labels, buttons.

### QA Checks
- [x] `grep layouts.inspinia resources/views/proformas/` — create, edit, index, show all match ✓
- [x] `grep adminlte::page resources/views/proformas/` — zero matches ✓
- [x] `grep system_price|cost_price|salesman_profit|margin|min_price` create+edit+form — zero matches ✓
- [x] `grep adminlte::page sales_orders/create, sales_orders/edit, tax_invoices/create` — all match ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All proforma routes confirmed via route:list ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store/update validation logic changed ✓
- [x] No approval/cancel/convert logic changed ✓
- [x] No price lookup backend changed ✓
- [x] No minimum selling price backend validation changed ✓
- [x] No stock logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31B
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- **Total form pages on Inspinia: 4**

---

## Phase 31C — Sales Forms Batch 2: Sales Order Create/Edit
**Completed: 2026-08-06**

### Files Changed
- [x] `resources/views/sales_orders/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`
- [x] `resources/views/sales_orders/edit.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed (Select2 loaded globally by layouts.inspinia)
- [x] `@section('content_header')` block → string form
- [x] Breadcrumb section added (create: index only; edit: index → SO number → Edit)
- [x] Form wrapped in `.ibox` > `.ibox-title` + `.ibox-tools` (Back) + `.ibox-content`
- [x] `enctype="multipart/form-data"` preserved on both forms (customer PO/LPO file upload)
- [x] `id="salesOrderForm"` preserved on both forms
- [x] FA icon in submit button (`<i class="fas fa-save"></i>`) preserved
- [x] Create Back → `sales-orders.index` (both ibox-tools and bottom button)
- [x] Edit Back → `sales-orders.show, $salesOrder` (both ibox-tools and bottom button)
- [x] Edit: `@method('PUT')` + `route('sales-orders.update', $salesOrder)` preserved
- [x] `@include('sales_orders.form')` preserved (unchanged — all fields, JS, references section)
- [x] `@include('quotations.partials.sales_modals')` outside main form preserved

### Compat CSS
No changes needed. Existing compat CSS covers all form.blade.php elements. `sales_orders/form.blade.php` uses `theme: 'bootstrap4'` in Select2 — confirmed safe (same analysis as Phase 31B; descendant selector matches all themes).

### Sales Order-Specific Features Preserved
- [x] PO/LPO card (`.card-outline.card-warning`): customer_po_number, customer_po_date, customer_po_file upload, existing file display
- [x] References card (`.card-outline.card-info`): `#addReference` button, dynamic rows via `refIndex`, 5-column table, `#noReferencesHint`
- [x] `sale_type` and `customer_category` fields in header card
- [x] `expected_delivery_date` field preserved
- [x] Both `rowIndex` and `refIndex` JS counters preserved (in form.blade.php, untouched)

### QA Checks
- [x] `grep layouts.inspinia resources/views/sales_orders/` — create, edit, index, show all match ✓
- [x] `grep adminlte::page resources/views/sales_orders/` — zero matches ✓
- [x] `grep system_price|cost_price|salesman_profit|margin|min_price` create+edit+form — zero matches ✓
- [x] `grep adminlte::page tax_invoices/create, customer_receipts/create` — still match (untouched) ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] SO routes confirmed via route:list ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store/update validation logic changed ✓
- [x] No approval/multi-approval logic changed ✓
- [x] No PO/LPO verification logic changed ✓
- [x] No dispatch creation logic changed ✓
- [x] No price lookup backend changed ✓
- [x] No minimum selling price backend validation changed ✓
- [x] No stock logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31C
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- **Total form pages on Inspinia: 6**

---

## Phase 31D — Finance Forms Pilot: Tax Invoice Create
**Completed: 2026-08-06**

### Files Changed
- [x] `resources/views/tax_invoices/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped / Not Applicable
- `tax_invoices/edit.blade.php` — does not exist; skip confirmed
- `tax_invoices/form.blade.php` — does not exist; all content is inline in create.blade.php

### Tax Invoice Create — Key Structural Differences vs Sales/Proforma Forms
- No Select2, no AJAX, no jQuery — plain vanilla JS only
- No `@section('plugins.Select2', true)` to remove
- No dynamic item rows — items are read-only preview from Delivery Note
- Form takes `$deliveryNote` route parameter: `route('tax-invoices.store', $deliveryNote)`
- Back button → `route('delivery-notes.show', $deliveryNote)` (NOT tax-invoices.index)
- Two-ibox layout: (1) DN Summary read-only ibox with Back in ibox-tools; (2) Invoice Details ibox containing the `<form>`
- Conditional fields: `#credit_days_wrapper` (shown when sale_type=credit); `#payment_reference_wrapper` (shown for bank_deposit/cheque_payment)
- Items preview table wrapped in `.table-responsive` (added for mobile safety)

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block → string form
- [x] Breadcrumb added (Tax Invoices → Create)
- [x] DN Summary `.card.card-outline.card-primary` → `.ibox` with Back in `.ibox-tools`
- [x] Invoice form `.card` → `.ibox` with `<form>` inside `.ibox-content`
- [x] `card-footer` submit/Back buttons → `div.form-group.mt-3` inside `.ibox-content`
- [x] Items table wrapped in `.table-responsive`
- [x] `@section('js')` vanilla JS preserved exactly (toggleCredit, toggleReference)

### QA Checks
- [x] `grep layouts.inspinia resources/views/tax_invoices/` — create, index, show all match ✓
- [x] `grep adminlte::page resources/views/tax_invoices/` — zero matches ✓
- [x] `grep system_price|cost_price|salesman_profit|margin|min_price` create — zero matches ✓
- [x] `grep adminlte::page customer_receipts/create, credit_notes/create` — still match (untouched) ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store validation logic changed ✓
- [x] No approve/post/cancel logic changed ✓
- [x] No direct invoice logic changed ✓
- [x] No delivery-note-to-tax-invoice logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No customer ledger logic changed ✓
- [x] No payment status logic changed ✓
- [x] No receipt allocation logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No stock logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31D
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create; edit does not exist)
- **Total form pages on Inspinia: 7**

---

## Phase 31E — Finance Forms Batch 2: Customer Receipt Create
**Completed: 2026-08-06**

### Files Changed
- [x] `resources/views/customer_receipts/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped / Not Applicable
- `customer_receipts/edit.blade.php` — does not exist; skip confirmed
- `customer_receipts/form.blade.php` — does not exist; all content is inline in create.blade.php

### Customer Receipt Create — Key Structural Notes
- No Select2, no AJAX, no jQuery — plain vanilla JS IIFE only
- No `@section('plugins.Select2', true)` to remove
- Three-ibox layout: (1) TI Summary read-only, (2) Allocation Preview read-only, (3) Receipt Details form
- Back button → `route('tax-invoices.show', $taxInvoice)` (ibox-tools on first ibox + bottom of form)
- Form takes `$taxInvoice` route param: `route('customer-receipts.store', $taxInvoice)`
- FX JS: vanilla IIFE toggles `#exchange_rate_group` visibility when non-base currency selected
- `$invCurrCode`, `$invRate`, `$invBasecode`, `$invForeign` @php block moved before first ibox (used in both title and body)
- `allocated_amount` optional — defaults to full received amount in controller
- `max="{{ $taxInvoice->balance_due }}"` preserved on allocated_amount input

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block → string form
- [x] Breadcrumb added (Customer Receipts → Create)
- [x] TI Summary `.card.card-outline.card-primary` → `.ibox` with Back in `.ibox-tools`
- [x] Allocation Preview `.card.card-outline.card-warning` → `.ibox`
- [x] Receipt form `.card` → `.ibox` with `<form>` inside `.ibox-content`
- [x] `card-footer` submit/Back buttons → `div.form-group.mt-3` inside form
- [x] `@section('js')` vanilla IIFE preserved exactly (updateExchangeRateVisibility)

### QA Checks
- [x] `grep layouts.inspinia resources/views/customer_receipts/` — create, index, show all match ✓
- [x] `grep adminlte::page resources/views/customer_receipts/` — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] Tax Invoice/SO/Quotation forms remain Inspinia ✓
- [x] Credit Note/Supplier Invoice/Payment remain AdminLTE ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No receipt allocation logic changed ✓
- [x] No invoice amount_paid/balance_due/payment_status logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No customer ledger logic changed ✓
- [x] No FX/exchange-rate logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31E
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create; edit does not exist)
- Phase 31E: 1 (customer_receipts/create; edit does not exist)
- **Total form pages on Inspinia: 8**

---

## Phase 31F — Finance Forms Batch 3: Credit Note Create
**Completed: 2026-08-06**

### Files Changed
- [x] `resources/views/credit_notes/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped / Not Applicable
- `credit_notes/edit.blade.php` — does not exist; skip confirmed
- `credit_notes/form.blade.php` — does not exist; all content is inline in create.blade.php

### Index Discrepancy Note
- Task stated "Credit Note index already on Inspinia" — INCORRECT. `credit_notes/index.blade.php` is still on `adminlte::page`. Only `credit_notes/show.blade.php` was converted in Phase 30Y. The index remains on AdminLTE and is out of scope for Phase 31F (form pages only).

### Credit Note Create — Key Structural Notes
- No Select2, no AJAX, no jQuery — vanilla JS only (`document.addEventListener('input', ...)`)
- No `@section('plugins.Select2', true)` to remove
- Uses `@push('js')` + `@endpush` — preserved as-is (Inspinia layout has `@stack('js')` at line 112)
- Form wraps two iboxes: (1) Credit Note Details (header fields), (2) Invoice Lines (items table)
- TI Summary ibox is OUTSIDE the form (read-only), Back button in `.ibox-tools`
- Form action: `route('credit-notes.store', $taxInvoice)` (nested route under tax invoice)
- Back button → `route('tax-invoices.show', $taxInvoice)`
- Items table: hidden `tax_invoice_item_id` inputs per row, editable `quantity/selling_price/discount_amount/tax_rate/remarks`
- `@error('items')` inside the ibox-content before the table-responsive div
- Client-side `est-{{ $i }}` cell updates on input (server-side is authoritative)

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block → string form
- [x] Breadcrumb added (Credit Notes → Create)
- [x] TI Summary `.card.card-outline.card-secondary` → `.ibox` with Back in `.ibox-tools`
- [x] Credit Note Details `.card.card-outline.card-primary` → `.ibox` (inside form)
- [x] Invoice Lines `.card` → `.ibox` with items in `.table-responsive` inside `.ibox-content`
- [x] `card-footer d-flex justify-content-between` → `div.d-flex.justify-content-between.mt-3` inside `.ibox-content`
- [x] `@push('js')` / `@endpush` preserved exactly (layout has @stack('js'))

### QA Checks
- [x] `grep layouts.inspinia credit_notes/` — create, show match; index remains adminlte (expected) ✓
- [x] `grep adminlte::page credit_notes/create` — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] Supplier Invoice/Payment remain AdminLTE ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No credit note store/approve/post/cancel logic changed ✓
- [x] No tax invoice balance/payment_status logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No customer ledger logic changed ✓
- [x] No FX/exchange-rate logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31F
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create; edit does not exist)
- Phase 31E: 1 (customer_receipts/create; edit does not exist)
- Phase 31F: 1 (credit_notes/create; edit does not exist)
- **Total form pages on Inspinia: 9**

---

## Phase 31G — Purchase Payable Forms Batch 1: Supplier Invoice Create
**Completed: 2026-08-06**

### Files Changed
- [x] `resources/views/supplier_invoices/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped / Not Applicable
- `supplier_invoices/edit.blade.php` — does not exist; skip confirmed
- `supplier_invoices/form.blade.php` — does not exist; all content is inline in create.blade.php

### Known Migration Gap (Not Fixed in This Phase)
- `credit_notes/index.blade.php` remains on `adminlte::page` — discovered in Phase 31F; out of scope for Phase 31G

### Supplier Invoice Create — Key Structural Notes
- No Select2, no AJAX — jQuery used only for row calculation (`calcRow`, `updateGrand`)
- No `@section('plugins.Select2', true)` to remove
- Uses `@push('js')` + `@endpush` — preserved as-is (Inspinia layout has `@stack('js')` at line 112)
- jQuery loaded at line 89 of Inspinia layout — `$(row).find(...)` works correctly
- Three-ibox structure: (1) GRN Summary read-only + Back in ibox-tools (outside form); (2) Invoice Details header fields; (3) Invoice Items table + footer (both inside form)
- Form action: `route('supplier-invoices.store', $grn)` — nested route under GRN
- Back button → `route('grns.show', $grn)`
- Hidden `goods_received_note_item_id` per row
- `line_total` display pre-filled from `$grnItem->line_total`; JS recalculates on input
- `credit_days` default from `$grn->purchaseOrder?->purchaseRequest?->supplier?->credit_days ?? 0`
- Info alert about supplier balance preserved inside ibox-content below table
- Submit button text: `Create Supplier Invoice (Draft)`

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block → string form
- [x] Breadcrumb added (Supplier Invoices → Create)
- [x] GRN Summary `.card.card-outline.card-secondary` → `.ibox` with Back in `.ibox-tools`
- [x] Invoice Details `.card.card-outline.card-primary` → `.ibox` (inside form)
- [x] Invoice Items `.card` → `.ibox` with table in `.table-responsive` + alert + buttons inside `.ibox-content`
- [x] `card-footer d-flex justify-content-between` → `div.d-flex.justify-content-between` inside `.ibox-content`
- [x] `@push('js')` / `@endpush` preserved exactly

### QA Checks
- [x] `grep layouts.inspinia supplier_invoices/` — create, index, show all match ✓
- [x] `grep adminlte::page supplier_invoices/` — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] Supplier Payment/PO remain AdminLTE ✓
- [x] `credit_notes/index.blade.php` confirmed AdminLTE (known gap, unchanged) ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No supplier invoice store/approve/post/cancel logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No supplier ledger logic changed ✓
- [x] No GRN/PO logic changed ✓
- [x] No stock logic changed ✓
- [x] No FX/exchange-rate logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31G
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create; edit does not exist)
- Phase 31E: 1 (customer_receipts/create; edit does not exist)
- Phase 31F: 1 (credit_notes/create; edit does not exist)
- Phase 31G: 1 (supplier_invoices/create; edit does not exist)
- **Total form pages on Inspinia: 10**

---

## Phase 31H — Purchase Payable Forms Batch 2: Supplier Payment Create/Edit
**Completed: 2026-08-07**

### Files Changed
- [x] `resources/views/supplier_payments/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped / Not Applicable
- `supplier_payments/edit.blade.php` — does not exist; skip confirmed
- `supplier_payments/form.blade.php` — does not exist; all content is inline in create.blade.php

### Known Migration Gap (Not Fixed in This Phase)
- `credit_notes/index.blade.php` remains on `adminlte::page` — discovered in Phase 31F; not fixed here

### Supplier Payment Create — Key Structural Notes
- No Select2, no AJAX — jQuery used only for `togglePaymentFields()` on `#paymentMethod`
- No `@section('plugins.Select2', true)` to remove
- Uses `@push('js')` + `@endpush` — preserved as-is (Inspinia layout has `@stack('js')`)
- Two-ibox structure: (1) Invoice Summary read-only + Back in ibox-tools (outside form); (2) Payment Details fields + alert + buttons inside form ibox-content
- Form action: `route('supplier-payments.store', $supplierInvoice)` — nested route under Supplier Invoice
- Back button → `route('supplier-invoices.show', $supplierInvoice)` (appears in both ibox-tools and button row)
- Conditional payment fields: `#refBlock`, `#bankBlock`, `#chequeBlock`, `#mmBlock` toggled by `togglePaymentFields()`
- `allocated_amount` has `max="{{ $supplierInvoice->balance_due }}"` — preserved
- Info alert about supplier balance preserved inside ibox-content above buttons
- Submit button text: `Create Payment (Draft)`

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block with `<h1>` → string form `@section('content_header', 'Create Supplier Payment')`
- [x] Breadcrumb added (Supplier Payments → Create)
- [x] Invoice Summary `.card.card-outline.card-secondary.mb-3` → `.ibox` with Back in `.ibox-tools`
- [x] Payment Details `.card` → `.ibox` (inside form)
- [x] `.card-footer` content moved into `.ibox-content` (alert + d-flex buttons)
- [x] `@push('js')` / `@endpush` preserved exactly

### QA Checks
- [x] `grep layouts.inspinia supplier_payments/` — create, index, show all match ✓
- [x] `grep adminlte::page supplier_payments/` — zero matches ✓
- [x] `grep adminlte::page credit_notes/index.blade.php` — confirmed still AdminLTE (known gap) ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No supplier payment store/approve/post/cancel logic changed ✓
- [x] No supplier invoice allocation formulas changed ✓
- [x] No supplier invoice amount_paid/balance_due/payment_status logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No supplier ledger logic changed ✓
- [x] No FX/exchange-rate logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31H
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create; edit does not exist)
- Phase 31E: 1 (customer_receipts/create; edit does not exist)
- Phase 31F: 1 (credit_notes/create; edit does not exist)
- Phase 31G: 1 (supplier_invoices/create; edit does not exist)
- Phase 31H: 1 (supplier_payments/create; edit does not exist)
- **Total form pages on Inspinia: 11**

### Recommended Next Phase
**Phase 31I — AdminLTE Gap Cleanup: Credit Note Index**
`credit_notes/index.blade.php` is the only known AdminLTE index page remaining after all index batch phases (30I–30P). It was missed during Phase 31F and documented as a known gap. Converting it will complete the index migration.

---

## Phase 31I — AdminLTE Gap Cleanup: Credit Note Index
**Completed: 2026-08-07**

### Files Changed
- [x] `resources/views/credit_notes/index.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Credit Note Module — Full Layout Status
- [x] `credit_notes/index.blade.php` — `layouts.inspinia` ✓
- [x] `credit_notes/create.blade.php` — `layouts.inspinia` ✓ (Phase 31F)
- [x] `credit_notes/show.blade.php` — `layouts.inspinia` ✓ (Phase 30Y)
- [x] `credit_notes/print.blade.php` — `prints.layout` ✓ (untouched)
- [x] No `adminlte::page` remains in `resources/views/credit_notes/` ✓

### Credit Note Index — Key Structural Notes
- No inline approve/post/cancel forms on the index (those are on the show page)
- No Create button visible (CNs are created from Tax Invoice show page)
- No confidential fields (no system_price, cost_price, salesman_profit, margin, min_price)
- Two-ibox pattern: (1) Filters ibox + (2) Table ibox
- Filter fields: search, status (draft/approved/posted/cancelled), from_date, to_date
- Table columns: Credit Note No., Date, Customer, Tax Invoice, Reason, Total Amount, Status, Actions (show)
- `@php match()` blocks for statusBadge and reasonLabel preserved exactly
- Pagination: `$creditNotes->links()` guarded by `$creditNotes->hasPages()`

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block with `<h1>` → string form
- [x] Breadcrumb added (Credit Notes active)
- [x] Filter `.card.card-outline.card-primary` → `.ibox` with Filters title
- [x] Table `.card` → `.ibox` with table title, `.table-responsive` wrapper added
- [x] `.card-footer` pagination → inside `.ibox-content` with `hasPages()` guard
- [x] All three flash message types (success, error, info) preserved

### Remaining AdminLTE Index Audit (read-only, not converted)
17 index pages remain on AdminLTE — all are report/dispatch/GL/HR/secondary modules:
- salesman_profit_reports, customer_statements, financial_statements, trial_balance
- salesman_profit_settlements, management_reports, accounting_reports
- sales_order_dispatches, hr_reports, gate_passes, delivery_notes
- purchase_recording, supplier_statements, inventory_reports
- call_off_orders, gl_backfill, general_ledger

### QA Checks
- [x] `grep layouts.inspinia credit_notes/` — index + create + show all match ✓
- [x] `grep adminlte::page credit_notes/` — zero matches ✓
- [x] `grep prints.layout credit_notes/print.blade.php` — confirmed ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No credit note approve/post/cancel logic changed ✓
- [x] No tax invoice balance logic changed ✓
- [x] No customer balance logic changed ✓
- [x] No customer ledger logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Recommended Next Phase
**Phase 31J — Purchase Forms Pilot: Purchase Order Create/Edit**
Purchase Orders are the most complex purchase-side forms (Select2 supplier AJAX, item row AJAX, dynamic row add/remove, multi-field totals). Converting them will be the hardest form migration and will establish the pattern for GRN create/edit.

---

## Phase 31J — Purchase Forms Pilot: Purchase Order Create/Edit
**Completed: 2026-08-22**

### Files Changed
- [x] `resources/views/purchase_orders/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`
- [x] `resources/views/purchase_orders/edit.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Not Converted (by design)
- `purchase_orders/create_from_pr.blade.php` — still `adminlte::page` (not in explicit scope; separate PR-to-PO flow)
- `purchase_orders/index.blade.php` — `layouts.inspinia` (unchanged from prior phases) ✓
- `purchase_orders/show.blade.php` — `layouts.inspinia` (unchanged) ✓
- `purchase_orders/print.blade.php` — `prints.layout` (untouched) ✓

### Purchase Order Module — Layout Status After Phase 31J
| View | Layout |
|---|---|
| `purchase_orders/index.blade.php` | `layouts.inspinia` |
| `purchase_orders/show.blade.php` | `layouts.inspinia` |
| `purchase_orders/create.blade.php` | `layouts.inspinia` (31J) |
| `purchase_orders/edit.blade.php` | `layouts.inspinia` (31J) |
| `purchase_orders/create_from_pr.blade.php` | `adminlte::page` (pending) |
| `purchase_orders/print.blade.php` | `prints.layout` (untouched) |

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed (Select2 loaded globally in Inspinia layout)
- [x] `@section('content_header')` block with `<h1>` → string form
- [x] `@section('breadcrumb')` added for both pages
- [x] Header `.card.card-outline.card-primary/card-warning` → `.ibox` with `.ibox-title` + Back button in `.ibox-tools` + `.ibox-content`
- [x] Items `.card` → `.ibox` with `.ibox-title` + Add Item button in `.ibox-tools` + `.ibox-content`
- [x] `.card-footer` Back + Submit buttons → `div.d-flex.justify-content-between.p-3` inside `.ibox-content`
- [x] `@stop` → `@endsection`; `@push('js')` preserved as-is (Inspinia layout has `@stack('js')`)

### Form Fields / Business Logic Preserved Exactly
- [x] Supplier Select2 AJAX search (`ajax.suppliers.search`), VRN auto-fill on select ✓
- [x] Warehouse select with `old()` fallback ✓
- [x] `purchase_order_date`, `expected_delivery_date`, `payment_method` fields ✓
- [x] `receipt_number` (EFD Receipt No.) + `receipt_date` ✓
- [x] `supplier_vrn` (auto-filled from supplier, editable) ✓
- [x] `source_file_reference`, `reconciliation_status`, `reconciliation_notes`, `notes` ✓
- [x] `#receiptDuplicateWarning` AJAX check against `ajax.purchases.check-receipt` ✓
- [x] Duplicate check includes `exclude_id: {{ $purchaseOrder->id }}` in edit form ✓
- [x] Item Search Select2 AJAX (`ajax.items.search`): fills `item_id`, `item_alias_id`, `description` ✓
- [x] Item row fields: `item_id` (hidden), `item_alias_id` (hidden), `purchase_request_item_id` (hidden), `description`, `quantity`, `unit_id`, `unit_cost`, `tax_rate`, `discount_amount` (hidden=0), `items_check` ✓
- [x] Display-only fields: `vat-display`, `excl-display`, `total-display` (readonly) ✓
- [x] Totals footer: `#totalVat`, `#totalExcl`, `#grandTotal` (readonly) ✓
- [x] `calcRow()`: qty × cost = excl; excl × tax/100 = vat; excl + vat = incl ✓
- [x] `updateTotals()`: sums all rows ✓
- [x] Dynamic `addRow` button appends new item row with fresh Select2 init ✓
- [x] `remove-row` button — guard: at least 1 item required ✓
- [x] `rowIndex` counter for new rows ✓
- [x] `unitOptions` JS variable for dynamic unit dropdown ✓
- [x] Edit form: existing PO items loaded with server values; `tax_amount`/`subtotal`/`total_amount` pre-filled in tfoot ✓
- [x] No nested forms ✓

### Confidentiality Check
- [x] No `margin`, `profit`, `salesman_profit`, `markup` in create or edit ✓
- [x] `unit_cost` is purchase document cost — correct and expected in procurement forms ✓
- [x] No customer-facing selling price, margin, or salesman profit exposed ✓

### AdminLTE Fallback Confirmed
- [x] `purchase_orders/index.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_orders/show.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_orders/print.blade.php` — `prints.layout` ✓
- [x] `purchase_orders/create_from_pr.blade.php` — `adminlte::page` ✓
- [x] `purchase_requests/create.blade.php` — `adminlte::page` ✓
- [x] `grns/create.blade.php` — `adminlte::page` ✓
- [x] `supplier_invoices/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_payments/create.blade.php` — `layouts.inspinia` ✓

### Compatibility CSS
No changes needed. Existing compat CSS covers all form elements. Select2 CDN already in Inspinia layout head and footer. `@push('js')` works with `@stack('js')` in Inspinia layout.

### QA Checks
- [x] `grep layouts.inspinia purchase_orders/` — create, edit, index, show all match ✓
- [x] `grep adminlte::page purchase_orders/` — only create_from_pr ✓
- [x] `grep prints.layout purchase_orders/print.blade.php` — confirmed ✓
- [x] `grep plugins.Select2 create, edit` — zero matches (removed) ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All purchase-order routes confirmed via route:list ✓
- [x] dashboard route confirmed ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No PO store/update validation logic changed ✓
- [x] No approve/cancel logic changed ✓
- [x] No duplicate receipt validation logic changed ✓
- [x] No supplier lookup logic changed ✓
- [x] No item lookup logic changed ✓
- [x] No VAT/tax formula changed ✓
- [x] No stock logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31J
- Phase 31A: 2 (quotations/create, quotations/edit)
- Phase 31B: 2 (proformas/create, proformas/edit)
- Phase 31C: 2 (sales_orders/create, sales_orders/edit)
- Phase 31D: 1 (tax_invoices/create)
- Phase 31E: 1 (customer_receipts/create)
- Phase 31F: 1 (credit_notes/create)
- Phase 31G: 1 (supplier_invoices/create)
- Phase 31H: 1 (supplier_payments/create)
- Phase 31J: 2 (purchase_orders/create, purchase_orders/edit)
- **Total form pages on Inspinia: 13**

### Recommended Next Phase
**Phase 31K — Purchase Forms Batch 2: GRN Create/Edit**
GRN create is the most consequential purchase-side form (stock-in occurs on GRN post, serial number entry). The create form is pre-loaded from a Purchase Order, making it structurally similar to the Tax Invoice create pattern. Converting it will complete the core purchase flow on Inspinia.

---

## Phase 31K — Purchase Forms Batch 2: GRN Create/Edit
**Completed: 2026-08-22**

### Files Changed
- [x] `resources/views/grns/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Skipped (by design / does not exist)
- `grns/edit.blade.php` — does NOT exist in this project; GRNs are immutable after creation (only approve/post/cancel workflow). Skipped as instructed.
- `grns/form.blade.php` — does NOT exist. No shared partial.
- `grns/index.blade.php` — `layouts.inspinia` (unchanged from Phase 30K) ✓
- `grns/show.blade.php` — `layouts.inspinia` (unchanged) ✓
- `grns/print.blade.php` — `prints.layout` (untouched) ✓

### GRN Module — Layout Status After Phase 31K
| View | Layout |
|---|---|
| `grns/index.blade.php` | `layouts.inspinia` |
| `grns/show.blade.php` | `layouts.inspinia` |
| `grns/create.blade.php` | `layouts.inspinia` (31K) |
| `grns/print.blade.php` | `prints.layout` (untouched) |

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block with `<h1>` → plain string form
- [x] `@section('breadcrumb')` added: GRNs → PO Number → Create GRN
- [x] PO summary `.card.card-outline.card-secondary` → `.ibox` with `.ibox-title` + Back in `.ibox-tools` + `.ibox-content` (outside form)
- [x] GRN Details `.card.card-outline.card-primary` → `.ibox` with `.ibox-title` + `.ibox-content` (inside form)
- [x] Items `.card` → `.ibox` with `.ibox-title` + `.ibox-content p-0` (table-responsive) + `div.p-3` (alert + buttons)
- [x] `.card-footer` alert + Back/Submit buttons → `div.p-3` inside `.ibox-content p-0` Items ibox
- [x] `@stop` → `@endsection`
- [x] No `@push('js')` needed (GRN create has no JavaScript)
- [x] No `@section('plugins.Select2', true)` existed (not an AJAX-driven form)

### Form Fields / Business Logic Preserved Exactly
- [x] Form action: `route('grns.store', $purchaseOrder)` POST @csrf ✓
- [x] `grn_date` date input with `old()`, default today ✓
- [x] `supplier_delivery_note_number` text input with `old()` ✓
- [x] `supplier_invoice_number` text input with `old()` ✓
- [x] `notes` text input with `old()` ✓
- [x] PO summary: PO number, supplier name, warehouse name, PO date — read-only display ✓
- [x] Items loop: `$receivableItems` with `$idx` counter ✓
- [x] Hidden: `items[{{ $idx }}][purchase_order_item_id]` = `$item->id` ✓
- [x] Display columns: description, ordered qty, prev received, remaining qty, unit, unit cost ✓
- [x] `items[{{ $idx }}][received_quantity]` — number, min=0.01, max=remainingQuantity(), step=0.01, default=remainingQuantity() ✓
- [x] `items[{{ $idx }}][remarks]` — text input with `old()` ✓
- [x] Per-item `@error("items.{$idx}.received_quantity")` validation feedback ✓
- [x] Root-level `@error('items')` alert above table ✓
- [x] Over-receipt constraint enforced via `max="{{ $item->remainingQuantity() }}"` attribute ✓
- [x] Stock-warning alert info preserved exactly ✓
- [x] Back button: `route('purchase-orders.show', $purchaseOrder)` ✓
- [x] Submit: "Create GRN (Draft)" btn-success ✓
- [x] Session error flash preserved ✓
- [x] No serial number fields (not present in this project's GRN create form) ✓
- [x] No JavaScript present in create form — nothing to preserve ✓

### Receiving / Stock Safety
- [x] Ordered quantity (display-only): `$item->quantity` ✓
- [x] Previously received quantity (display-only): `$item->received_quantity` ✓
- [x] Remaining quantity (display-only): `$item->remainingQuantity()` ✓
- [x] Over-receipt constraint: `max="{{ $item->remainingQuantity() }}"` HTML attribute preserved ✓
- [x] No StockService call in the view ✓
- [x] No stock movement triggered from the form ✓
- [x] Stock-warning note "Stock will only increase when the GRN is approved then posted" preserved ✓

### Confidentiality Check
- [x] No margin, profit, salesman_profit, markup, system_price, min_price in GRN create ✓
- [x] `unit_cost` is GRN procurement cost — correct and expected ✓

### AdminLTE Fallback Confirmed
- [x] `grns/index.blade.php` — `layouts.inspinia` ✓
- [x] `grns/show.blade.php` — `layouts.inspinia` ✓
- [x] `grns/print.blade.php` — `prints.layout` ✓
- [x] `purchase_orders/create.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_orders/edit.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_orders/create_from_pr.blade.php` — `adminlte::page` ✓
- [x] `purchase_requests/create.blade.php` — `adminlte::page` ✓
- [x] `purchase_requests/edit.blade.php` — `adminlte::page` ✓
- [x] `stock_transfers/create.blade.php` — `adminlte::page` ✓
- [x] `journal_vouchers/create.blade.php` — `adminlte::page` ✓
- [x] `supplier_invoices/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_payments/create.blade.php` — `layouts.inspinia` ✓

### QA Checks
- [x] `grep extends resources/views/grns/` — create=layouts.inspinia, index=layouts.inspinia, show=layouts.inspinia, print=prints.layout ✓
- [x] No `adminlte::page` in grns/ ✓
- [x] No `plugins.Select2`, no `push('js')`, no `StockService` in create ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All GRN/PO/PR/SI/SP/warehouse/serial/dashboard routes confirmed ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No GRN store/update validation changed ✓
- [x] No GRN approve/post/cancel logic changed ✓
- [x] No StockService changed ✓
- [x] No stock movement logic changed ✓
- [x] No warehouse balance logic changed ✓
- [x] No PO received quantity/status logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31K
- Phases 31A–31J: 13 form pages
- Phase 31K: +1 (grns/create)
- **Total form pages on Inspinia: 14**

### Recommended Next Phase
**Phase 31L — Purchase Forms Batch 3: Purchase Request Create/Edit**
Purchase Request create/edit are the remaining major purchase-side forms not yet on Inspinia. They do not trigger stock changes and are lower-risk than GRN. Converting them completes the core purchase initiation flow on Inspinia and leaves only `create_from_pr` (a PR→PO conversion form) for a final cleanup phase.

---

## Phase 31L — Purchase Forms Batch 3: Purchase Request Create/Edit
**Completed: 2026-08-22**

### Files Changed
- [x] `resources/views/purchase_requests/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`
- [x] `resources/views/purchase_requests/edit.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Not Changed (by design / pre-existing state)
- `purchase_requests/index.blade.php` — `layouts.inspinia` (pre-existing) ✓
- `purchase_requests/show.blade.php` — `adminlte::page` (not in scope for this phase)
- `purchase_requests/print.blade.php` — `prints.layout` (untouched) ✓
- `purchase_requests/form.blade.php` — does NOT exist ✓

### Purchase Request Module — Layout Status After Phase 31L
| View | Layout |
|---|---|
| `purchase_requests/index.blade.php` | `layouts.inspinia` |
| `purchase_requests/create.blade.php` | `layouts.inspinia` (31L) |
| `purchase_requests/edit.blade.php` | `layouts.inspinia` (31L) |
| `purchase_requests/show.blade.php` | `adminlte::page` (pending) |
| `purchase_requests/print.blade.php` | `prints.layout` (untouched) |

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed from both (Select2 globally loaded in Inspinia layout)
- [x] `@section('content_header')` block with `<h1>` → plain string (create) / PR number (edit)
- [x] `@section('breadcrumb')` added to both: create → PRs → Create; edit → PRs → PR# → Edit
- [x] Header `.card.card-outline.card-primary/card-warning` → `.ibox` with `.ibox-title` + Back in `.ibox-tools` + `.ibox-content`
- [x] Items `.card` → `.ibox` with `.ibox-title` + Add Item in `.ibox-tools` + `.ibox-content p-0`
- [x] `.card-footer` Back + Submit → `div.d-flex.justify-content-between.p-3` inside `.ibox-content`
- [x] `@stop` → `@endsection`; `@push('js')` preserved verbatim (Inspinia layout has `@stack('js')`)

### Form Fields / Business Logic Preserved Exactly (Create)
- [x] Form action: `route('purchase-requests.store')` POST @csrf ✓
- [x] `supplier_id` Select2 AJAX → `ajax.suppliers.search`; optional ✓
- [x] `request_date` with `old()`, default today ✓
- [x] `required_date` with `old()` ✓
- [x] `priority` select with `old('priority', 'normal')` fallback ✓
- [x] `purpose` textarea with `old()` ✓
- [x] `notes` textarea with `old()` ✓
- [x] `@php $oldItems = old('items', [[]])` fallback for initial row ✓
- [x] Item row hidden: `items[N][item_id]`, `items[N][item_alias_id]` ✓
- [x] Item search Select2 AJAX → `ajax.items.search`; fills `item_id`, `item_alias_id`, `desc-input` ✓
- [x] Item fields: `description`, `quantity`, `unit_id`, `estimated_unit_cost`, `total-display` (readonly), `remarks` ✓
- [x] Per-item `@error` validation on `description` and `quantity` ✓
- [x] Root-level `@error('items')` alert above table ✓
- [x] `#addRow`, `remove-row`, `reindexRows()`, `calcRowTotal()` JavaScript preserved verbatim ✓
- [x] `rowIndex = {{ count(old('items', [[]])) }}` ✓
- [x] `unitOptions` template literal for dynamic unit dropdown ✓
- [x] Back: `route('purchase-requests.index')` ✓
- [x] Submit: "Save Purchase Request" btn-success ✓

### Form Fields / Business Logic Preserved Exactly (Edit)
- [x] Form action: `route('purchase-requests.update', $purchaseRequest)` POST @csrf @method('PUT') ✓
- [x] Existing supplier rendered as pre-selected `<option>` if `$purchaseRequest->supplier` exists ✓
- [x] `request_date` with `old()` + `$purchaseRequest->request_date` fallback ✓
- [x] `required_date` with `old()` + `$purchaseRequest->required_date` fallback ✓
- [x] `priority` select with `old('priority', $purchaseRequest->priority)` ✓
- [x] `purpose`, `notes` with `old()` + `$purchaseRequest->purpose/notes` ✓
- [x] Items loop: `$purchaseRequest->items` with `$oldItems[$i]` → `$item->field` fallback pattern ✓
- [x] `estimated_total_cost` pre-filled in `total-display` readonly ✓
- [x] `rowIndex = {{ $purchaseRequest->items->count() }}` ✓
- [x] Back: `route('purchase-requests.show', $purchaseRequest)` ✓
- [x] Submit: "Update Purchase Request" btn-warning ✓

### Confidentiality Check
- [x] No margin, profit, salesman_profit, markup in create or edit ✓
- [x] `estimated_unit_cost` and `estimated_total_cost` are PR document fields — correct and expected in procurement forms ✓

### AdminLTE Fallback Confirmed
- [x] `purchase_requests/show.blade.php` — `adminlte::page` (untouched) ✓
- [x] `purchase_requests/print.blade.php` — `prints.layout` ✓
- [x] `purchase_orders/create_from_pr.blade.php` — `adminlte::page` ✓
- [x] `grns/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_invoices/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_payments/create.blade.php` — `layouts.inspinia` ✓
- [x] `stock_transfers/create.blade.php` — `adminlte::page` ✓
- [x] `journal_vouchers/create.blade.php` — `adminlte::page` ✓

### QA Checks
- [x] `grep extends purchase_requests/` — create=layouts.inspinia, edit=layouts.inspinia, index=layouts.inspinia, show=adminlte::page, print=prints.layout ✓
- [x] `grep plugins.Select2` create/edit — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All PR/PO/supplier/item/GRN/dashboard routes confirmed ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No PR store/update validation changed ✓
- [x] No approve/cancel logic changed ✓
- [x] No PR→PO conversion logic changed ✓
- [x] No supplier lookup logic changed ✓
- [x] No item lookup logic changed ✓
- [x] No stock logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31L
- Phases 31A–31K: 14 form pages
- Phase 31L: +2 (purchase_requests/create, purchase_requests/edit)
- **Total form pages on Inspinia: 16**

### Recommended Next Phase
**Phase 31M — Purchase Forms Cleanup: Purchase Order Create From PR**
`purchase_orders/create_from_pr.blade.php` is the last major purchase-side form still on `adminlte::page`. It is structurally similar to the PO create form (same item structure with added `discount_amount` and `remarks` per row, plus a `purchase_request_id` hidden input and a PR summary header). Converting it completes the full procurement cycle on Inspinia.

---

## Phase 31M — Purchase Forms Cleanup: Purchase Order Create From PR
**Completed: 2026-08-22**

### Files Changed
- [x] `resources/views/purchase_orders/create_from_pr.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Purchase Order Module — Full Layout Status After Phase 31M
| View | Layout |
|---|---|
| `purchase_orders/index.blade.php` | `layouts.inspinia` |
| `purchase_orders/show.blade.php` | `layouts.inspinia` |
| `purchase_orders/create.blade.php` | `layouts.inspinia` |
| `purchase_orders/edit.blade.php` | `layouts.inspinia` |
| `purchase_orders/create_from_pr.blade.php` | `layouts.inspinia` (31M) |
| `purchase_orders/print.blade.php` | `prints.layout` (untouched) |

All five non-print Purchase Order views now use `layouts.inspinia`. The full procurement cycle (PR create/edit → PR→PO conversion → PO create/edit → GRN create) is now 100% on Inspinia for form pages.

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('plugins.Select2', true)` removed (Select2 globally loaded in Inspinia layout)
- [x] `@section('content_header')` block with `<h1>` → plain string
- [x] `@section('breadcrumb')` added: Purchase Requests → PR Number → Create Purchase Order
- [x] PR summary `.card.card-outline.card-secondary` → `.ibox` with `.ibox-title` + Back in `.ibox-tools` + `.ibox-content` (outside form — read-only)
- [x] PO Details `.card.card-outline.card-primary` → `.ibox` with `.ibox-title` + `.ibox-content` (inside form)
- [x] Items `.card` → `.ibox` with `.ibox-title` + Add Item in `.ibox-tools` + `.ibox-content p-0`
- [x] `.card-footer` Back + Submit → `div.d-flex.justify-content-between.p-3` inside Items `.ibox-content`
- [x] `@stop` → `@endsection`; `@push('js')` preserved verbatim

### Form Fields / Business Logic Preserved Exactly
- [x] Form action: `route('purchase-orders.store')` POST @csrf ✓
- [x] `<input type="hidden" name="purchase_request_id" value="{{ $purchaseRequest->id }}">` ✓
- [x] PR summary: PR number, requested by, request date, required by, purpose — read-only outside form ✓
- [x] `supplier_id` Select2 AJAX → `ajax.suppliers.search`; pre-selected from `$purchaseRequest->supplier` if exists ✓
- [x] `warehouse_id` select with `old()` from `$warehouses` ✓
- [x] `purchase_order_date` with `old()`, default today ✓
- [x] `expected_delivery_date` with `old()`, default `$purchaseRequest->required_date` ✓
- [x] `notes` textarea with `old()` ✓
- [x] Items loop: `$purchaseRequest->items` with `$i` counter ✓
- [x] Hidden per row: `items[N][purchase_request_item_id]` = `$prItem->id` ✓
- [x] Hidden per row: `items[N][item_id]` = `$prItem->item_id` ✓
- [x] Hidden per row: `items[N][item_alias_id]` = `$prItem->item_alias_id` ✓
- [x] Item search Select2 AJAX → `ajax.items.search` ✓
- [x] `items[N][description]` — `old("items.{$i}.description", $prItem->description)` ✓
- [x] `items[N][quantity]` — `old("items.{$i}.quantity", $prItem->quantity)` ✓
- [x] `items[N][unit_id]` — `old("items.{$i}.unit_id", $prItem->unit_id)` ✓
- [x] `items[N][unit_cost]` — `old("items.{$i}.unit_cost", $prItem->estimated_unit_cost)` ✓
- [x] `items[N][discount_amount]` — number, default 0 ✓
- [x] `items[N][tax_rate]` — number, min=0 max=100, default 0 ✓
- [x] `total-display` — readonly, default "0.00" ✓
- [x] `items[N][remarks]` — `old("items.{$i}.remarks", $prItem->remarks)` ✓
- [x] tfoot Grand Total: `#grandTotal` readonly ✓
- [x] `calcRow`: `taxable = (qty*cost) - discount; tax = taxable * taxRate/100; total = taxable + tax` ✓
- [x] `updateGrandTotal()`: sums all `.total-display` ✓
- [x] `rowIndex = {{ $purchaseRequest->items->count() }}` ✓
- [x] `unitOptions` JS variable ✓
- [x] `#addRow` appends row with `purchase_request_item_id` hidden="" (empty for added rows) ✓
- [x] `.remove-row` guard (≥1 item); calls `updateGrandTotal()` ✓
- [x] Initial `calcRow` call for all existing rows on page load ✓
- [x] Back: `route('purchase-requests.show', $purchaseRequest)` ✓
- [x] Submit: "Save Purchase Order" btn-success ✓
- [x] No EFD fields, no reconciliation fields, no `items_check`, no duplicate receipt check ✓

### Confidentiality Check
- [x] No margin, profit, salesman_profit, markup in create_from_pr ✓
- [x] `unit_cost` defaults to `$prItem->estimated_unit_cost` — procurement cost from PR, correct and expected ✓

### AdminLTE Fallback Confirmed
- [x] All 5 non-print PO views now `layouts.inspinia` ✓
- [x] `purchase_orders/print.blade.php` — `prints.layout` ✓
- [x] `purchase_requests/create.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_requests/edit.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_requests/index.blade.php` — `layouts.inspinia` ✓
- [x] `purchase_requests/show.blade.php` — `adminlte::page` (unchanged) ✓
- [x] `grns/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_invoices/create.blade.php` — `layouts.inspinia` ✓
- [x] `supplier_payments/create.blade.php` — `layouts.inspinia` ✓
- [x] `stock_transfers/create.blade.php` — `adminlte::page` ✓
- [x] `journal_vouchers/create.blade.php` — `adminlte::page` ✓

### QA Checks
- [x] `grep extends purchase_orders/` — all 5 views=layouts.inspinia, print=prints.layout ✓
- [x] No `adminlte::page` in purchase_orders/ ✓
- [x] `grep plugins.Select2` create_from_pr — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — clean ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All PO/PR/supplier/item/receipt/GRN/dashboard routes confirmed ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No PR→PO conversion logic changed ✓
- [x] No PO store validation changed ✓
- [x] No approve/cancel logic changed ✓
- [x] No duplicate receipt logic changed ✓
- [x] No supplier lookup logic changed ✓
- [x] No item lookup logic changed ✓
- [x] No VAT/tax formula changed ✓
- [x] No stock logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31M
- Phases 31A–31L: 16 form pages
- Phase 31M: +1 (purchase_orders/create_from_pr)
- **Total form pages on Inspinia: 17**

### Recommended Next Phase
**Phase 31N — Expense Voucher Forms: Create/Edit**
Expense Voucher create/edit are the next-simplest internal forms (no Select2 AJAX, no file upload, pure JS for payment-field toggling and subtotal+tax calculation). Converting them completes the Expenses module on Inspinia.

---

## Phase 31N — Expense Voucher Forms: Create/Edit
**Completed: 2026-08-24**

### Files Changed
- [x] `resources/views/expenses/create.blade.php` — converted from `adminlte::page` to `layouts.inspinia`
- [x] `resources/views/expenses/edit.blade.php` — converted from `adminlte::page` to `layouts.inspinia`

### Not Changed (by design / pre-existing state)
- `expenses/index.blade.php` — `layouts.inspinia` (pre-existing) ✓
- `expenses/show.blade.php` — `adminlte::page` (not in scope) ✓
- No `expenses/form.blade.php` exists — all fields inline in create/edit ✓
- No `expenses/print.blade.php` exists — not applicable ✓

### Expenses Module — Layout Status After Phase 31N
| View | Layout |
|---|---|
| `expenses/index.blade.php` | `layouts.inspinia` (pre-existing) |
| `expenses/create.blade.php` | `layouts.inspinia` (31N) |
| `expenses/edit.blade.php` | `layouts.inspinia` (31N) |
| `expenses/show.blade.php` | `adminlte::page` (pending) |

### Conversion Pattern Applied
- [x] `@extends('adminlte::page')` → `@extends('layouts.inspinia')`
- [x] `@section('content_header')` block with `<h1>` → plain string (create) / string (edit)
- [x] `@section('breadcrumb')` added to both: create → Expenses → Create; edit → Expenses → EV# → Edit
- [x] Outer `.card.card-body` wrapper → `.ibox` with `.ibox-title` + Back in `.ibox-tools` + `.ibox-content`
- [x] Right-column Payment Details `.card.card-outline.card-secondary` → `.ibox` with `.ibox-title` + `.ibox-content`
- [x] `@stop` → `@endsection` (both content and js sections)
- [x] No Select2, no AJAX, no `@section('plugins.Select2', true)` — none existed; nothing to remove

### Form Fields / Business Logic Preserved Exactly
- [x] Form action: `route('expenses.store')` POST @csrf (create) ✓
- [x] Form action: `route('expenses.update', $expense)` POST @csrf @method('PUT') (edit) ✓
- [x] `expense_category_id` select with `$categories` loop; `old()` fallback; validation error ✓
- [x] `expense_date` date input; `old()` with `date('Y-m-d')` default (create) / `$expense->expense_date->format()` (edit) ✓
- [x] `description` textarea; `old()` with `$expense->description` fallback (edit) ✓
- [x] `payee_name` text input; `old()` with model fallback ✓
- [x] `supplier_id` select with `$suppliers` loop; `old()` with model fallback (edit) ✓
- [x] `notes` textarea; `old()` with model fallback (edit) ✓
- [x] `payment_method` select with `$paymentMethods` loop; `togglePaymentFields()` onchange ✓
- [x] `payment_reference` text input ✓
- [x] `bank_name` — `d-none` by default in create (class toggle); visible by default in edit (style.display toggle) ✓
- [x] `cheque_number` — same d-none / style.display pattern preserved ✓
- [x] `mobile_money_number` — same pattern preserved ✓
- [x] `subtotal` number input; oninput=calcTotal() ✓
- [x] `tax_amount` number input; oninput=calcTotal() ✓
- [x] `total_display` readonly input; initial value: old('subtotal','0.00') create / `$expense->total_amount` edit ✓
- [x] Submit: "Save as Draft" (create) / "Update Expense" (edit) ✓
- [x] Cancel: `route('expenses.index')` (create) / `route('expenses.show', $expense)` (edit) ✓

### JavaScript Preserved Exactly
- [x] `calcTotal()` — parseFloat subtotal + tax → toFixed(2) in total_display ✓
- [x] `togglePaymentFields()` in create — uses classList.add/remove('d-none') ✓
- [x] `togglePaymentFields()` in edit — uses style.display = 'none' / '' ✓
- [x] DOMContentLoaded: create calls calcTotal() + togglePaymentFields(sel.value) if value ✓
- [x] DOMContentLoaded: edit calls togglePaymentFields(sel.value) if value only ✓
- [x] `@section('js')` used (same as original) — compatible with `@yield('js')` in Inspinia layout ✓

### Confidentiality Check
- [x] No system_price, cost_price, salesman_profit, margin, markup, min_price ✓

### AdminLTE Fallback Confirmed
- [x] `expenses/show.blade.php` — `adminlte::page` (untouched) ✓
- [x] No `expenses/print.blade.php` exists ✓
- [x] `journal_vouchers/create.blade.php` — `adminlte::page` (untouched) ✓
- [x] `stock_transfers/create.blade.php` — `adminlte::page` (untouched) ✓
- [x] All purchase/sales/HR/attendance forms unchanged ✓

### Compatibility CSS
No changes needed. Existing ibox, form-group, badge, button, validation rules cover all elements. No Select2, no attachment fields, no AJAX in these forms.

### QA Checks
- [x] `grep layouts.inspinia resources/views/expenses/` — create, edit, index all match ✓
- [x] `grep adminlte::page resources/views/expenses/` — only show.blade.php ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓
- [x] All expense routes confirmed via route:list ✓
- [x] Dashboard route confirmed ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store/update validation logic changed ✓
- [x] No approve/post/cancel logic changed ✓
- [x] No GL/accounting logic changed ✓
- [x] No expense category logic changed ✓
- [x] No branch scoping changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31N
- Phases 31A–31M: 17 form pages
- Phase 31N: +2 (expenses/create, expenses/edit)
- **Total form pages on Inspinia: 19**

---

## Phase 31O — Journal Voucher Create/Edit Forms
**Completed: 2026-08-24**

### Pages Converted
- [x] `journal_vouchers/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `journal_vouchers/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓

### Partials (NOT changed — theme-neutral)
- [x] `journal_vouchers/_form.blade.php` — no `@extends`, pure partial ✓
- [x] `journal_vouchers/_form_js.blade.php` — pure `<script>` partial ✓

### Form Fields Preserved (via _form partial)
- [x] `journal_date` date input with nullsafe fallback and `now()` default ✓
- [x] `fiscal_year_id` select with `$fiscalYears`, `id="fySelect"`, `data-url` per option ✓
- [x] `accounting_period_id` select with `$periods`, `id="periodSelect"` ✓
- [x] `reference_number` text input with nullsafe fallback ✓
- [x] `description` textarea with nullsafe fallback, required ✓
- [x] `lines[N][account_id]` select per row from `$accounts` ✓
- [x] `lines[N][description]` text per row ✓
- [x] `lines[N][debit]` number per row, class `debit-input` ✓
- [x] `lines[N][credit]` number per row, class `credit-input` ✓
- [x] Remove-line button `.remove-line` per row ✓
- [x] `#addLine` button ✓
- [x] `#totalDebit`, `#totalCredit`, `#balanceStatus` badge ✓
- [x] `<template id="lineTemplate">` with `__IDX__` placeholder ✓

### JS Preserved (via _form_js partial)
- [x] IIFE wrapper ✓
- [x] `recalc()` — sums `.debit-input`, `.credit-input`; updates badge ✓
- [x] Balance badge: `badge-success` (balanced) / `badge-danger` (unbalanced) ✓
- [x] Min-2-rows guard on remove ✓
- [x] `addLine` clones `<template>`, replaces `__IDX__` with `lineIndex++` ✓
- [x] `linesContainer` input delegation → `recalc()` ✓
- [x] `recalc()` called on init ✓

### Ibox Structure
- [x] `create`: ibox-title "New Journal Voucher" + Back→index in ibox-tools ✓
- [x] `edit`: ibox-title "Edit Journal Voucher — {journal_number}" + Back→show in ibox-tools ✓
- [x] Breadcrumb create: Journal Vouchers → Create ✓
- [x] Breadcrumb edit: Journal Vouchers → {journal_number} (linked to show) → Edit ✓
- [x] Error block above ibox (outside form) ✓
- [x] Form action/id/CSRF preserved exactly ✓
- [x] `@method('PUT')` in edit preserved ✓
- [x] `@push('js') @include('journal_vouchers._form_js') @endpush` preserved ✓

### AdminLTE Fallback Confirmed
- [x] `journal_vouchers/index.blade.php` — `layouts.inspinia` ✓
- [x] `journal_vouchers/show.blade.php` — `layouts.inspinia` ✓
- [x] `journal_vouchers/create.blade.php` — now `layouts.inspinia` ✓
- [x] `journal_vouchers/edit.blade.php` — now `layouts.inspinia` ✓
- [x] JV module 100% Inspinia ✓

### QA Checks
- [x] `grep layouts.inspinia resources/views/journal_vouchers/` — all 4 layout-owning files match ✓
- [x] `grep adminlte::page resources/views/journal_vouchers/` — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No store/update validation logic changed ✓
- [x] No approve/post/cancel logic changed ✓
- [x] No debit/credit balancing logic changed ✓
- [x] No fiscal year/period validation changed ✓
- [x] No GL posting logic changed ✓
- [x] No branch scoping changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31O
- Phases 31A–31N: 19 form pages
- Phase 31O: +2 (journal_vouchers/create, journal_vouchers/edit)
- **Total form pages on Inspinia: 21**

---

## Phase 31P — Accounting Master Forms: COA, Fiscal Years, GL Mapping
**Completed: 2026-08-24**

### Directory Existence Check
- [x] `chart_of_accounts/` — EXISTS ✓
- [x] `fiscal_years/` — EXISTS ✓
- [x] `fiscal_periods/` — DOES NOT EXIST (no directory) — SKIPPED ✓
- [x] `gl_mappings/` — DOES NOT EXIST (actual dir: `gl_account_mappings/`) — SKIPPED ✓
- [x] `gl_account_mappings/` — EXISTS ✓

### Pages Converted
- [x] `chart_of_accounts/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `chart_of_accounts/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `fiscal_years/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `gl_account_mappings/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓

### Pages Skipped (Do Not Exist)
- [x] `fiscal_years/edit.blade.php` — file does not exist — SKIPPED ✓
- [x] `fiscal_periods/create.blade.php` — directory does not exist — SKIPPED ✓
- [x] `fiscal_periods/edit.blade.php` — directory does not exist — SKIPPED ✓
- [x] `gl_mappings/create.blade.php` — directory does not exist — SKIPPED ✓
- [x] `gl_mappings/edit.blade.php` — directory does not exist — SKIPPED ✓
- [x] `gl_account_mappings/create.blade.php` — file does not exist (GL mappings are seeded, edit-only) — SKIPPED ✓

### Pages Out of Scope (Exist But Not Create/Edit)
- [x] `gl_account_mappings/preview.blade.php` — read-only preview, not a create/edit form — SKIPPED ✓

### Partials (NOT changed — theme-neutral)
- [x] `chart_of_accounts/_form.blade.php` — no `@extends`, pure HTML partial, Bootstrap-compatible — untouched ✓

### Chart of Accounts Fields Preserved (via _form partial)
- [x] `account_code` text input, required, old() fallback ✓
- [x] `account_name` text input, required, old() fallback ✓
- [x] `account_group_id` select with `$groups`, type shown, old() fallback ✓
- [x] `parent_id` select with `$parents`, "None (top level)" default ✓
- [x] `account_type` select with `$types`, disabled+hidden when `$locked` ✓
- [x] `normal_balance` select debit/credit, disabled+hidden when `$locked` ✓
- [x] `opening_balance` number input, old() fallback ✓
- [x] `opening_balance_type` select, auto (normal balance) default ✓
- [x] `notes` textarea ✓
- [x] `is_cash_account` checkbox ✓
- [x] `is_bank_account` checkbox ✓
- [x] `is_control_account` checkbox ✓
- [x] `is_active` checkbox ✓
- [x] `$locked` guard — `isset($chartOfAccount) && hasPostedLines()` — hidden inputs for type/normal_balance ✓
- [x] Posted-lines warning banner in edit (above ibox) ✓

### Fiscal Year Fields Preserved
- [x] `name` text input, required, old() ✓
- [x] `start_date` date input, required, old() ✓
- [x] `end_date` date input, required, old() ✓
- [x] "Monthly accounting periods will be auto-generated" notice ✓
- [x] POST → `fiscal-years.store` ✓

### GL Account Mapping Fields Preserved
- [x] `account_id` select from `$accounts` (Option A — single account) ✓
- [x] `debit_account_id` select from `$accounts` (Option B — Dr) ✓
- [x] `credit_account_id` select from `$accounts` (Option B — Cr) ✓
- [x] `description` textarea ✓
- [x] `is_active` checkbox ✓
- [x] PUT → `gl-account-mappings.update` ✓
- [x] Info alert (single vs pair usage explanation) preserved ✓
- [x] Right column info panels (`.card.card-outline`) preserved (compat CSS covers them) ✓
- [x] `$usageMap` PHP block and mapping_key lookup preserved ✓
- [x] `isConfigured()` badge status preserved ✓
- [x] Nullsafe `->account?->displayNameWithCode()` calls preserved ✓

### AdminLTE Fallback Confirmed
- [x] `chart_of_accounts/index.blade.php` — `layouts.inspinia` (Phase 30M) ✓
- [x] `chart_of_accounts/show.blade.php` — `layouts.inspinia` (Phase 30S) ✓
- [x] `chart_of_accounts/create.blade.php` — now `layouts.inspinia` ✓
- [x] `chart_of_accounts/edit.blade.php` — now `layouts.inspinia` ✓
- [x] `fiscal_years/index.blade.php` — `layouts.inspinia` (Phase 30M) ✓
- [x] `fiscal_years/show.blade.php` — `layouts.inspinia` (Phase 30Q) ✓
- [x] `fiscal_years/create.blade.php` — now `layouts.inspinia` ✓
- [x] `gl_account_mappings/index.blade.php` — `layouts.inspinia` (Phase 30M) ✓
- [x] `gl_account_mappings/edit.blade.php` — now `layouts.inspinia` ✓
- [x] `gl_account_mappings/preview.blade.php` — `adminlte::page` (out of scope) ✓
- [x] Journal Voucher module — 100% Inspinia (Phase 31O) ✓
- [x] Expense create/edit — `layouts.inspinia` (Phase 31N) ✓

### QA Checks
- [x] `grep layouts.inspinia resources/views/chart_of_accounts/` — create, edit, index, show match ✓
- [x] `grep layouts.inspinia resources/views/fiscal_years/` — create, index, show match ✓
- [x] `grep layouts.inspinia resources/views/gl_account_mappings/` — index, edit match ✓
- [x] `grep adminlte::page resources/views/chart_of_accounts/` — zero matches ✓
- [x] `grep adminlte::page resources/views/fiscal_years/` — zero matches ✓
- [x] `grep adminlte::page resources/views/gl_account_mappings/` — preview.blade.php only ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No COA validation changed ✓
- [x] No fiscal year validation changed ✓
- [x] No GL mapping validation changed ✓
- [x] No account hierarchy changed ✓
- [x] No normal balance logic changed ✓
- [x] No fiscal period logic changed ✓
- [x] No GL posting logic changed ✓
- [x] No financial statement logic changed ✓
- [x] No branch scoping changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31P
- Phases 31A–31O: 21 form pages
- Phase 31P: +4 (COA create/edit, fiscal_years create, gl_account_mappings edit)
- **Total form pages on Inspinia: 25**

---

## Phase 31Q — HR Forms: Employees, Departments, Designations
**Completed: 2026-08-24**

### HR Directory Discovery
- [x] `resources/views/employees/` — EXISTS (flat, no `hr/` prefix) ✓
- [x] `resources/views/departments/` — EXISTS (flat, no `hr/` prefix) ✓
- [x] `resources/views/designations/` — EXISTS (flat, no `hr/` prefix) ✓
- [x] `resources/views/hr/` — does NOT exist; all HR views are flat ✓

### Pages Converted
- [x] `employees/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `employees/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `departments/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `departments/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `designations/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `designations/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓

### Partials (NOT changed — theme-neutral)
- [x] `employees/_form.blade.php` — no `@extends`; uses `.card.card-outline.*` sections (compat CSS covers) — untouched ✓
- [x] `departments/_form.blade.php` — no `@extends`, pure Bootstrap — untouched ✓
- [x] `designations/_form.blade.php` — no `@extends`, pure Bootstrap — untouched ✓

### Employee Form Fields Preserved (via _form partial)
- [x] `first_name`, `middle_name`, `last_name` (required) ✓
- [x] `gender` select with `$genders` ✓
- [x] `date_of_birth` date input with `->format('Y-m-d')` ✓
- [x] `national_id`, `phone`, `email`, `address` ✓
- [x] `emergency_contact_name`, `emergency_contact_phone` ✓
- [x] `employee_number` (auto-generated if blank) ✓
- [x] `department_id` select with `$departments` ✓
- [x] `designation_id` select with `$designations` (title field) ✓
- [x] `branch_id` select with `$branches` ✓
- [x] `employment_type` select with `$employmentTypes`, required ✓
- [x] `status` select with `$statuses`, required, default 'active' ✓
- [x] `hire_date`, `probation_end_date`, `confirmation_date`, `termination_date` ✓
- [x] `user_id` select with `$users` (linked user account) ✓
- [x] `basic_salary`, `bank_name`, `bank_account_name`, `bank_account_number` ✓
- [x] `biometric_id`, `device_user_id`, `attendance_pin` ✓
- [x] `notes` textarea ✓
- [x] No file upload (`enctype` not needed — documents handled separately) ✓

### Department Form Fields Preserved (via _form partial)
- [x] `name` text input, required ✓
- [x] `code` text input ✓
- [x] `is_active` — hidden input (value=0) + `custom-control custom-switch` checkbox ✓
- [x] `description` textarea ✓

### Designation Form Fields Preserved (via _form partial)
- [x] `title` text input, required ✓
- [x] `code` text input ✓
- [x] `is_active` — hidden input (value=0) + `custom-control custom-switch` checkbox ✓
- [x] `department_id` select with `$departments` ✓
- [x] `description` textarea ✓

### Cancel/Back Route Notes
- Employees edit → Cancel/Back → `employees.show` (show page exists) ✓
- Departments edit → Cancel/Back → `departments.index` (no show route) ✓
- Designations edit → Cancel/Back → `designations.index` (no show route) ✓

### AdminLTE Fallback Confirmed
- [x] `employees/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `employees/show.blade.php` — `layouts.inspinia` (Phase 30S) ✓
- [x] `employees/create.blade.php` — now `layouts.inspinia` ✓
- [x] `employees/edit.blade.php` — now `layouts.inspinia` ✓
- [x] `departments/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `departments/create.blade.php` — now `layouts.inspinia` ✓
- [x] `departments/edit.blade.php` — now `layouts.inspinia` ✓
- [x] `designations/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `designations/create.blade.php` — now `layouts.inspinia` ✓
- [x] `designations/edit.blade.php` — now `layouts.inspinia` ✓
- [x] Attendance/Leave forms unchanged ✓
- [x] Accounting/Purchase/Sales/Stock forms unchanged ✓

### QA Checks
- [x] `grep layouts.inspinia resources/views/employees/` — create, edit, index, show match ✓
- [x] `grep layouts.inspinia resources/views/departments/` — create, edit, index match ✓
- [x] `grep layouts.inspinia resources/views/designations/` — create, edit, index match ✓
- [x] `grep adminlte::page resources/views/employees/` — zero matches ✓
- [x] `grep adminlte::page resources/views/departments/` — zero matches ✓
- [x] `grep adminlte::page resources/views/designations/` — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No employee validation changed ✓
- [x] No department/designation validation changed ✓
- [x] No branch scoping changed ✓
- [x] No biometric fields changed ✓
- [x] No employee document upload/storage changed ✓
- [x] No attendance/leave/payroll logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31Q
- Phases 31A–31P: 25 form pages
- Phase 31Q: +6 (employees/create, employees/edit, departments/create, departments/edit, designations/create, designations/edit)
- **Total form pages on Inspinia: 31**

---

## Phase 31R — Attendance and Leave Forms
**Completed: 2026-08-24**

### Attendance/Leave Directory Discovery
- [x] `resources/views/work_shifts/` — EXISTS ✓
- [x] `resources/views/attendance_devices/` — EXISTS ✓
- [x] `resources/views/attendance/` — EXISTS ✓
- [x] `resources/views/attendance_imports/` — EXISTS but only `index.blade.php` — SKIPPED (no form) ✓
- [x] `resources/views/leave_types/` — EXISTS ✓
- [x] `resources/views/leave_requests/` — EXISTS ✓
- [x] `resources/views/leave_balances/` — EXISTS but only `index.blade.php` — SKIPPED (no form) ✓
- [x] `resources/views/attendance_leave_reports/` — report views, out of scope ✓

### Pages Converted
- [x] `work_shifts/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `work_shifts/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `attendance_devices/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `attendance_devices/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `attendance/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `attendance/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `leave_types/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `leave_types/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `leave_requests/create.blade.php` — `adminlte::page` → `layouts.inspinia` ✓
- [x] `leave_requests/edit.blade.php` — `adminlte::page` → `layouts.inspinia` ✓

### Pages Skipped (No Form Exists)
- [x] `attendance_imports/` — index.blade.php only — SKIPPED ✓
- [x] `leave_balances/` — index.blade.php only — SKIPPED ✓
- [x] `leave_requests/show.blade.php` — show page, out of scope — SKIPPED ✓

### Partials (NOT changed — theme-neutral)
- [x] `work_shifts/_form.blade.php` — untouched ✓
- [x] `attendance_devices/_form.blade.php` — untouched ✓
- [x] `leave_types/_form.blade.php` — untouched ✓
- [x] `leave_requests/_form.blade.php` — untouched ✓
- [x] `attendance/` — no partial; create/edit are fully inline ✓

### Work Shift Fields Preserved (via _form)
- [x] `name` (required), `code`, `start_time`, `end_time`, `grace_minutes`, `break_minutes` ✓
- [x] `is_night_shift` — hidden(0) + custom-switch ✓
- [x] `is_active` — hidden(0) + custom-switch ✓
- [x] `description` textarea ✓

### Attendance Device Fields Preserved (via _form)
- [x] `name` (required), `brand`, `model`, `serial_number`, `ip_address`, `port` ✓
- [x] `connection_type` select with `$connectionTypes` ✓
- [x] `location`, `notes` ✓
- [x] `is_active` — hidden(0) + custom-switch ✓
- [x] Info alert (CSV/manual, no fingerprint storage) preserved ✓

### Attendance Record Fields Preserved (inline)
- [x] Create: `employee_id` (required), `work_shift_id`, `attendance_date` (required, today default), `status` (required, 'present' default), `check_in_at`, `check_out_at`, `notes` ✓
- [x] Edit: readonly employee display, `work_shift_id`, readonly date display, `status`, `check_in_at` with `->format('Y-m-d\TH:i')`, `check_out_at`, `notes / correction reason` ✓

### Leave Type Fields Preserved (via _form)
- [x] `name` (required), `code` (required), `max_days_per_year`, `carry_forward_days`, `description` ✓
- [x] `is_paid` checkbox, `requires_attachment` checkbox, `is_active` checkbox ✓

### Leave Request Fields Preserved (via _form)
- [x] `employee_id` select (required), `leave_type_id` select (required) ✓
- [x] `start_date`, `end_date` with `->format('Y-m-d')` ✓
- [x] `reason` textarea ✓
- [x] Dual submit buttons: `name="action" value="draft"` + `name="action" value="submit"` ✓
- [x] Create Cancel → `leave-requests.index` ✓
- [x] Edit Cancel/Back → `leave-requests.show` ✓

### AdminLTE Fallback Confirmed
- [x] Employees/Departments/Designations — `layouts.inspinia` (Phase 31Q) ✓
- [x] `work_shifts/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `attendance_devices/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `attendance/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `leave_types/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `leave_requests/index.blade.php` — `layouts.inspinia` (Phase 30N) ✓
- [x] `leave_requests/show.blade.php` — `adminlte::page` (out of scope) ✓
- [x] Accounting/Purchase/Sales/Stock forms unchanged ✓

### QA Checks
- [x] All 10 target files extend `layouts.inspinia` ✓
- [x] `grep adminlte::page` in target modules — only leave_requests/show (expected) ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No attendance processing logic changed ✓
- [x] No raw log import logic changed ✓
- [x] No leave approval/rejection logic changed ✓
- [x] No leave balance calculation changed ✓
- [x] No shift matching logic changed ✓
- [x] No device integration changed ✓
- [x] No branch/employee scoping changed ✓
- [x] No payroll logic added ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31R
- Phases 31A–31Q: 31 form pages
- Phase 31R: +10 (work_shifts ×2, attendance_devices ×2, attendance ×2, leave_types ×2, leave_requests ×2)
- **Total form pages on Inspinia: 41**

---

## Phase 31S — Settings/Admin Forms: Users, Roles, Branches, Currencies, Mail Settings

### Real Directories Used
- `resources/views/users/` — create.blade.php, edit.blade.php ✓ (form.blade.php is theme-neutral partial)
- `resources/views/roles/` — create.blade.php, edit.blade.php ✓ (form.blade.php is theme-neutral partial)
- `resources/views/branches/` — create.blade.php, edit.blade.php ✓ (form.blade.php is theme-neutral partial)
- `resources/views/currencies/` — create.blade.php, edit.blade.php ✓ (form.blade.php is theme-neutral partial with submit buttons)
- `resources/views/mail_settings/` — edit.blade.php ✓ (no create — settings form only; fully inline)
- No settings/ or system_settings/ directories exist ✓

### Converted (9 files)
- [x] `users/create.blade.php` → `layouts.inspinia` ✓
- [x] `users/edit.blade.php` → `layouts.inspinia` ✓
- [x] `roles/create.blade.php` → `layouts.inspinia` ✓
- [x] `roles/edit.blade.php` → `layouts.inspinia` ✓
- [x] `branches/create.blade.php` → `layouts.inspinia` ✓
- [x] `branches/edit.blade.php` → `layouts.inspinia` ✓
- [x] `currencies/create.blade.php` → `layouts.inspinia` ✓
- [x] `currencies/edit.blade.php` → `layouts.inspinia` ✓
- [x] `mail_settings/edit.blade.php` → `layouts.inspinia` ✓

### Skipped/Not Applicable
- `mail_settings/create.blade.php` — does not exist (settings form is edit-only) ✓
- All form partials (users/form, roles/form, branches/form, currencies/form) — theme-neutral, no @extends — untouched ✓
- index/show pages for all modules — already Inspinia (Phases 30O/30T) — unchanged ✓
- No settings/ or system_settings/ directories exist — nothing to convert ✓

### Admin/Security Behavior Safety
- [x] `users/form` partial (name, email, branch_id, role select, password, password_confirmation, is_active) untouched ✓
- [x] Password field: type="password", no value= exposed, leave-blank-to-keep behavior preserved ✓
- [x] Role assignment select (Spatie `name` field) preserved exactly ✓
- [x] `roles/form` partial (name, permissions[] checkboxes with checked states) untouched ✓
- [x] Permission names unchanged ✓
- [x] `branches/form` partial (name, code, phone, email, address, is_active) untouched ✓
- [x] `currencies/form` partial (code readonly on edit, name, symbol, decimal_places, is_active, exchange_rate_to_base, is_base readonly guard) untouched ✓
- [x] Base currency alert + locked exchange_rate preserved in currencies/edit ✓
- [x] `mail_settings/edit` SMTP fields (mailer, host, port, encryption, username, password, from_address, from_name, notes, is_active) all preserved verbatim ✓
- [x] Mail password field: type="password", autocomplete="new-password", no value= ✓ — stored secret NOT exposed ✓
- [x] mail-settings.update PUT route preserved ✓
- [x] No JS in any of the 9 converted pages — no errors possible ✓

### QA Checks
- [x] All 9 target files extend `layouts.inspinia` ✓
- [x] `grep adminlte::page` in all 5 target module dirs — zero matches ✓
- [x] Mail password grep — no value= attribute on password field ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No auth/password logic changed ✓
- [x] No role/permission (Spatie) logic changed ✓
- [x] No branch scoping changed ✓
- [x] No currency/exchange-rate/base-currency logic changed ✓
- [x] No mail encryption/SMTP/test dispatch logic changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31S
- Before Phase 31S: 41 form pages
- Phase 31S: +9 (users ×2, roles ×2, branches ×2, currencies ×2, mail_settings ×1)
- **Total form pages on Inspinia: 50**

---

## Phase 31T — Inventory & Item Master Forms

### Real Directories Used
- `resources/views/items/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral partial)
- `resources/views/item_categories/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral partial)
- `resources/views/item_aliases/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral partial)
- `resources/views/units/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral partial)
- `resources/views/warehouses/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral partial)
- `resources/views/serial_numbers/` — only opening_assignment.blade.php (no create/edit) ✓ SKIPPED
- `resources/views/stock/` — no master create/edit forms (adjustment/bin_card/report views) ✓ SKIPPED
- `resources/views/inventory_reports/` — report views only ✓ SKIPPED

### Converted (10 files)
- [x] `items/create.blade.php` → `layouts.inspinia` ✓
- [x] `items/edit.blade.php` → `layouts.inspinia` ✓
- [x] `item_categories/create.blade.php` → `layouts.inspinia` ✓
- [x] `item_categories/edit.blade.php` → `layouts.inspinia` ✓
- [x] `item_aliases/create.blade.php` → `layouts.inspinia` ✓
- [x] `item_aliases/edit.blade.php` → `layouts.inspinia` ✓
- [x] `units/create.blade.php` → `layouts.inspinia` ✓
- [x] `units/edit.blade.php` → `layouts.inspinia` ✓
- [x] `warehouses/create.blade.php` → `layouts.inspinia` ✓
- [x] `warehouses/edit.blade.php` → `layouts.inspinia` ✓

### Skipped/Not Applicable
- All 5 `form.blade.php` partials — theme-neutral, no @extends — untouched ✓
- `serial_numbers/opening_assignment.blade.php` — not a master create/edit form (out of scope) ✓
- `stock/` views — no master create/edit forms ✓
- index/show pages for all modules — already Inspinia or out of scope ✓

### Inventory Behavior Safety
- [x] `items/form` partial: warehouse_id, item_category_id, unit_id selects; name/sku/description; opening_quantity (create-only guard preserved); cost_price, selling_price (existing internal admin fields); reorder_level, out_of_stock_level; is_active + requires_serial_numbers checkboxes — untouched ✓
- [x] `@push('js')` duplicate-name check script in `items/form` preserved verbatim — `ajax.items.check-duplicate-name` route call untouched ✓
- [x] `item_categories/form`: name, code, is_active — untouched ✓
- [x] `item_aliases/form`: item_id select, alias_name, brand, barcode, selling_price, is_default, is_active — untouched ✓
- [x] Item alias Back links preserve `['item_id' => $selectedItem ?? null]` (create) and `['item_id' => $itemAlias->item_id]` (edit) ✓
- [x] `units/form`: name, symbol, is_active — untouched ✓
- [x] `warehouses/form`: name, code, location, is_active — untouched ✓

### Price/Stock Confidentiality
- [x] `cost_price` in `items/form.blade.php` is PRE-EXISTING in internal item master — not added, partial untouched ✓
- [x] No new system_price, salesman_profit, margin, markup, min_price added anywhere ✓
- [x] No customer-facing sales forms changed ✓
- [x] PriceLookupController untouched ✓

### QA Checks
- [x] All 10 target files extend `layouts.inspinia` ✓
- [x] `grep adminlte::page` in all 5 target module dirs — zero matches ✓
- [x] Price/confidentiality grep — cost_price only in pre-existing items/form partial ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No stock balance logic changed ✓
- [x] No StockService changed ✓
- [x] No stock movement logic changed ✓
- [x] No serial number logic changed ✓
- [x] No price lookup logic changed ✓
- [x] No branch/warehouse scoping changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31T
- Before Phase 31T: 50 form pages
- Phase 31T: +10 (items ×2, item_categories ×2, item_aliases ×2, units ×2, warehouses ×2)
- **Total form pages on Inspinia: 60**

---

## Phase 31U — Customer and Supplier Master Forms

### Real Directories Used
- `resources/views/customers/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral with @push('js'); no show.blade.php)
- `resources/views/suppliers/` — create.blade.php, edit.blade.php ✓ (form.blade.php theme-neutral; show.blade.php already Inspinia)
- `resources/views/customer_statements/` — index only (out of scope) ✓ SKIPPED
- `resources/views/supplier_statements/` — index only (out of scope) ✓ SKIPPED
- `resources/views/customer_receipts/` — no create/edit master forms ✓ SKIPPED
- `resources/views/supplier_payments/` — no create/edit master forms ✓ SKIPPED

### Converted (4 files)
- [x] `customers/create.blade.php` → `layouts.inspinia` ✓
- [x] `customers/edit.blade.php` → `layouts.inspinia` ✓
- [x] `suppliers/create.blade.php` → `layouts.inspinia` ✓
- [x] `suppliers/edit.blade.php` → `layouts.inspinia` ✓

### Skipped/Not Applicable
- Both `form.blade.php` partials — theme-neutral, no @extends — untouched ✓
- Statement/receipt/payment/report pages — out of scope ✓
- `suppliers/show.blade.php` — already Inspinia (Phase 30T) ✓
- No `customers/show.blade.php` exists ✓

### Customer/Supplier Behavior Safety
- [x] `customers/form` partial: name (with #customerDuplicateWarning), document_prefix, phone, email, tin, vrn, credit_limit, credit_days, address, is_active — untouched ✓
- [x] `@push('js')` duplicate-name check (fires `ajax.customers.check-duplicate-name` with exclude_id) preserved verbatim ✓
- [x] `suppliers/form` partial: name, phone, email, tin, vrn, credit_days, address, is_active — untouched ✓
- [x] No JS in suppliers form — no errors ✓

### Confidentiality Safety
- [x] No system_price, cost_price, salesman_profit, margin, markup, min_price anywhere in customers or suppliers views ✓

### QA Checks
- [x] All 4 target files extend `layouts.inspinia` ✓
- [x] `grep adminlte::page` in customers + suppliers dirs — zero matches ✓
- [x] Confidentiality grep — zero matches ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all ran, none pending ✓

### Business Safety
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No customer balance logic changed ✓
- [x] No supplier balance logic changed ✓
- [x] No customer/supplier ledger logic changed ✓
- [x] No customer/supplier statement logic changed ✓
- [x] No sales/purchase document logic changed ✓
- [x] No receipt/payment posting logic changed ✓
- [x] No branch scoping changed ✓
- [x] No print layouts changed ✓

### Inspinia Form Page Count After Phase 31U
- Before Phase 31U: 60 form pages
- Phase 31U: +4 (customers ×2, suppliers ×2)
- **Total form pages on Inspinia: 64**

---

## Phase 31V — Sales/Purchase Supporting Transaction Forms (2026-08-24)

### Files Converted (7 of 7)
- [x] `call_off_orders/create.blade.php` — `adminlte::page` → `layouts.inspinia`; single ibox; form includes `call_off_orders.form` partial (untouched); buttons in `<div class="mt-3 text-right">`; POST `call-off-orders.store`; Back→index
- [x] `call_off_orders/edit.blade.php` — `adminlte::page` → `layouts.inspinia`; ibox-title shows `call_off_number`; Back→`call-off-orders.show`; PUT `call-off-orders.update`
- [x] `call_off_orders/convert.blade.php` — `adminlte::page` → `layouts.inspinia`; warning alert above ibox; ibox wraps two-column layout (col-md-7 COO summary + col-md-5 conversion form); all inner `.card.card-outline.*` preserved verbatim (compat CSS covers); conversion POST → `call-off-orders.convert` with `onclick confirm()`; cancel → show
- [x] `sales_order_dispatches/create.blade.php` — `adminlte::page` → `layouts.inspinia`; dispatch_type="direct_dispatch" hidden input preserved; ibox wrapper with Back→`sales-orders.show($salesOrder)`; dispatch summary card + stock control alerts ABOVE ibox-content; items table shows ordered/already-issued/remaining qty + conditional disabled inputs when `$fullyDispatched`; POST `sales-order-dispatches.store($salesOrder)`
- [x] `gate_passes/create.blade.php` — `adminlte::page` → `layouts.inspinia`; stock control alert above ibox; dispatch summary card INSIDE ibox-content; form fields: gate_pass_date, destination_name, security_officer_name, vehicle_number, driver_name, driver_phone, remarks; items table READ-ONLY from `$dispatch->items`; POST `gate-passes.store($dispatch)`; Back→`sales-order-dispatches.show($dispatch)`
- [x] `delivery_notes/create.blade.php` — `adminlte::page` → `layouts.inspinia`; session('info') flash preserved; dispatch summary card + optional released gate pass summary card (`@if(isset($gatePass))`) INSIDE ibox-content; pre-fill from `$gatePass->destination_name/security_officer_name/driver_phone/vehicle_number/driver_name`; pre-fill notice alert shown when gate pass has data; items table READ-ONLY; POST `delivery-notes.store($dispatch)`; Back→`sales-order-dispatches.show($dispatch)`
- [x] `salesman_profit_settlements/create.blade.php` — `adminlte::page` → `layouts.inspinia`; two-step form: Step 1 ibox = GET filter (salesman_id, from_date, to_date → `salesman-profit-settlements.create`); Step 2 two iboxes = POST settlement details + eligible invoices table; hidden salesman_id; settlement_date, period_from, period_to, notes; invoice_ids[] checkboxes; system_cost + salesman_profit columns preserved (pre-existing internal settlement fields); `@push('js')/@endpush` select-all checkbox; POST `salesman-profit-settlements.store`; Cancel→index

### Partials Untouched (theme-neutral)
- [x] `call_off_orders/form.blade.php` — untouched; has `@section('js')/@stop` with Select2 + dynamic row JS; works with `layouts.inspinia` `@yield('js')`
- [x] No partial for sales_order_dispatches, gate_passes, delivery_notes, salesman_profit_settlements (fully inline forms)

### Business Safety
- [x] Dispatch request does NOT reduce stock — `SalesOrderDispatchController` untouched ✓
- [x] Delivery Note confirmation does NOT reduce stock — `DeliveryNoteController` untouched ✓
- [x] Stock-out only at Gate Pass Release — `GatePassController` release logic untouched ✓
- [x] Customer balance only changes on posted Tax Invoice/Receipt/Credit Note — untouched ✓
- [x] Supplier balance only changes on posted Supplier Invoice or Payment — untouched ✓
- [x] Call-Off conversion: draft SO only, no stock/balance/GL — `CallOffOrderController` untouched ✓
- [x] Salesman Profit Settlement: internal only — system_cost/salesman_profit are pre-existing internal fields; no customer-facing exposure added ✓
- [x] No controllers changed ✓
- [x] No models changed ✓
- [x] No migrations created ✓
- [x] No routes changed ✓
- [x] No print layouts changed ✓

### QA Checks
- [x] 7/7 Phase 31V files on `layouts.inspinia` (grep confirmed) ✓
- [x] 0/7 Phase 31V files still on `adminlte::page` ✓
- [x] Confidentiality grep (system_price, cost_price, min_price, markup, margin) — zero matches in non-settlement files ✓
- [x] `php artisan optimize:clear` — all caches cleared ✓
- [x] `php -l routes/web.php` — no syntax errors ✓
- [x] `php artisan migrate:status` — all migrations ran, none pending ✓
- [x] 69 AdminLTE views remaining sitewide (down from 76 before Phase 31V) ✓

### Inspinia Form Page Count After Phase 31V
- Before Phase 31V: 64 form pages
- Phase 31V: +7 (call_off_orders ×3, sales_order_dispatches ×1, gate_passes ×1, delivery_notes ×1, salesman_profit_settlements ×1)
- **Total form pages on Inspinia: 71**
- **AdminLTE views remaining sitewide: 69**

---

## Phase 31W — Remaining Form Pages (2026-08-24)
10 form files (purchase_requests, purchase_orders, grns, supplier_invoices, expenses, expense_reports, journal_vouchers, and 3 HR forms) converted. Details in prior session.
- **AdminLTE views remaining after 31W: 59**

---

## Phase 31X — Show/Detail/Preview Pages (2026-08-24)

### Files Converted (7 of 7)
- [x] `expenses/show.blade.php` — single ibox; workflow actions (Edit/Approve/Post/Cancel) in ibox-tools; cancel modal inside @section('content'); GL journal panel partial preserved
- [x] `leave_requests/show.blade.php` — two-column layout (col-md-8 details + col-md-4 workflow); approve/reject inline forms; @can guards; reject reason input preserved
- [x] `purchase_requests/show.blade.php` — hidden approve form `<form id="approveForm" style="display:none">@csrf</form>` at top of @section('content'); three-column details; items ibox; linked POs ibox; cancel modal preserved
- [x] `sales_order_dispatches/show.blade.php` — complex ibox-tools with 7 conditional workflow buttons; customer dispatch banner; dispatch items table; cancel modal #cancelDispatchModal
- [x] `call_off_orders/show.blade.php` — ibox-tools with Edit/Approve/Convert/Cancel; items table with tfoot Grand Total; linked documents ibox; cancel modal inside @can
- [x] `gl_backfill/preview.blade.php` — filter ibox; preview table with rowspan logic preserved; execute ibox with orange border (was card-warning); @can('gl_backfill.execute') guard
- [x] `gl_account_mappings/preview.blade.php` — search ibox; info-box summary cards; journal lines table; quick reference ibox

### QA Checks
- [x] 7/7 Phase 31X files on `layouts.inspinia` ✓
- [x] `grep -r "adminlte::page" resources/views/` — 52 remaining (index/report pages only) ✓

---

## Phase 31Y — Index / Report / Read-Only Pages (2026-08-24)
52 files converted using minimal edit approach (extends + content_header → single line + breadcrumb + @stop → @endsection). Inner `.card.*` elements left unchanged — compat CSS handles them.

### Accounting Reports (12 files)
- [x] `accounting_reports/index.blade.php`
- [x] `accounting_reports/aging_index.blade.php`
- [x] `accounting_reports/cash_movement.blade.php`
- [x] `accounting_reports/customer_aging.blade.php`
- [x] `accounting_reports/customer_collections.blade.php`
- [x] `accounting_reports/debtors_creditors.blade.php`
- [x] `accounting_reports/expense_register.blade.php`
- [x] `accounting_reports/purchase_register.blade.php`
- [x] `accounting_reports/sales_register.blade.php`
- [x] `accounting_reports/supplier_aging.blade.php`
- [x] `accounting_reports/supplier_payments.blade.php`
- [x] `accounting_reports/tax_summary.blade.php`

### Attendance / Leave Reports (5 files)
- [x] `attendance_leave_reports/attendance_summary.blade.php`
- [x] `attendance_leave_reports/leave_balance_report.blade.php`
- [x] `attendance_leave_reports/leave_summary.blade.php`
- [x] `attendance_leave_reports/monthly_attendance.blade.php`
- [x] `attendance_leave_reports/raw_log_exceptions.blade.php`

### Standalone Index Pages — Batch 1 (4 files)
- [x] `call_off_orders/index.blade.php` — btn-xs → btn-sm also fixed
- [x] `customer_statements/index.blade.php`
- [x] `delivery_notes/index.blade.php`
- [x] `gate_passes/index.blade.php`

### Financial Statements / GL (5 files)
- [x] `financial_statements/index.blade.php`
- [x] `financial_statements/profit_loss.blade.php`
- [x] `financial_statements/balance_sheet.blade.php`
- [x] `general_ledger/index.blade.php`
- [x] `gl_backfill/index.blade.php`

### HR Reports (4 files)
- [x] `hr_reports/index.blade.php`
- [x] `hr_reports/department_summary.blade.php` — inline Back button moved to breadcrumb
- [x] `hr_reports/document_expiry.blade.php` — inline Back button moved to breadcrumb
- [x] `hr_reports/employee_register.blade.php` — inline Back button moved to breadcrumb

### Inventory Reports (5 files)
- [x] `inventory_reports/index.blade.php`
- [x] `inventory_reports/reorder_alerts.blade.php` — btn-xs → btn-sm also fixed
- [x] `inventory_reports/stock_aging.blade.php`
- [x] `inventory_reports/stock_movement.blade.php`
- [x] `inventory_reports/stock_valuation.blade.php`

### Management Reports (6 files)
- [x] `management_reports/index.blade.php`
- [x] `management_reports/customers.blade.php`
- [x] `management_reports/items.blade.php`
- [x] `management_reports/purchases.blade.php`
- [x] `management_reports/sales.blade.php`
- [x] `management_reports/suppliers.blade.php`

### Remaining Standalone (11 files)
- [x] `purchase_recording/index.blade.php` — @section('plugins.Select2', true) removed; @stop before @push('js') → @endsection
- [x] `sales_order_dispatches/index.blade.php`
- [x] `salesman_profit_reports/index.blade.php`
- [x] `salesman_profit_reports/invoice.blade.php`
- [x] `salesman_profit_settlements/index.blade.php` — Create button moved from content_header to top of @section('content')
- [x] `stock/alerts.blade.php`
- [x] `stock/bin_card.blade.php`
- [x] `stock/warehouse_balances.blade.php`
- [x] `stock_transfers/report.blade.php`
- [x] `supplier_statements/index.blade.php`
- [x] `trial_balance/index.blade.php`

---

## Phase 31Z — Final AdminLTE Audit (2026-08-24)

### Verification Commands
- [x] `grep -rl "adminlte::page" resources/views/ --include="*.blade.php"` → **zero results** ✓
- [x] `php artisan optimize:clear` → all caches cleared ✓
- [x] `php -l routes/web.php` → no syntax errors ✓
- [x] `php artisan migrate:status` → all migrations ran, none pending ✓

### Final Status
- **AdminLTE views remaining: 0**
- **Total views on Inspinia: all 208+ business views**
- **Print views:** unchanged (extend `prints.layout`, not `adminlte::page`)
- **Theme-neutral partials:** unchanged (no @extends)
- **Auth views:** on `layouts.inspinia_auth` (Phase 30H)
- **Migration: COMPLETE**
