# PharmaWholesale MVC Conversion — Progress Tracker

Last updated: 2026-07-11 (Global Search and Search Index added)

This file tracks conversion progress from the legacy flat-PHP
PharmaWholesale app into the MVC structure in this `mvc/` directory.
If work is interrupted (rate limits, session restart, etc.), read this
file first to see exactly what's done and what's next — no need to
re-derive state from scratch.

## How to resume

1. Read `CONVENTIONS.md` in this same directory — it documents the
   Controller/Model/View pattern, helper functions, and the
   verification procedure.
2. Check the "DONE" list below — those modules are verified working.
3. Pick the next "TODO" module, find its legacy source file(s) under
   `../pharma-wholesale/`, and build Controller → Model → Views
   following the Contracts module as reference
   (`app/Controllers/ContractController.php`,
   `app/Models/Contract.php`, `app/Views/contracts/*.php`).
4. Test DB: MariaDB local, db `pharma_test`, user `root`, no password.
   Dev server: `php -S 127.0.0.1:8100 dev-router.php` from this `mvc/`
   dir. Login: `admin@pharma.com` / `Admin@1234`.
5. Update this file's DONE/TODO lists + the "Notes for next module"
   section after finishing each module.

## Route map reference

See `app/routes.php` — already fully wired for every module listed
below (do not edit it). Every legacy filename maps to a
Controller::action already named in that file.

## DONE ✅ (built + verified against live DB, HTTP 200, no PHP errors)

| Module | Controller | Model | Views | Notes |
|---|---|---|---|---|
| Auth | AuthController.php | User.php | auth/login.php | login/logout |
| Dashboard | DashboardController.php | — (inline queries) | dashboard/index.php | KPI queries ported |
| Contracts | ContractController.php | Contract.php | contracts/{index,add,edit,view}.php | Full CRUD, reference module |
| Invoices | InvoiceController.php | Invoice.php | invoices/{index,add,edit,view}.php | Dynamic line items + VAT calc + printable view |
| Purchase Orders | PurchaseOrderController.php | PurchaseOrder.php | purchase_orders/{index,add,edit,view}.php | Full CRUD, verified incl. view.php with test PO row inserted |
| Suppliers | SupplierController.php | Supplier.php | suppliers/{index,add,edit,view}.php | Full CRUD incl. large 5-tab view.php (overview/licences/audits/invoices/activity), category checkboxes, CSV export, delete-with-audit-trail. All 4 routes verified HTTP 200, zero PHP errors. |
| Users | UserController.php | User.php (shared with Auth) | users/{index,add,edit}.php | Full CRUD, self-account protections (can't delete/change own role), password hashing + optional password change on edit. All 3 routes verified HTTP 200, zero PHP errors. |
| Products | ProductController.php | Product.php | products/{index,add,edit,view}.php | Full CRUD, stock level warnings (low/out of stock), controlled drug schedule badges, cold-chain flags, CSV export. All 4 routes + CSV export verified HTTP 200, zero PHP errors. |
| Deliveries | DeliveryController.php | Delivery.php | deliveries/{index,add,edit,view}.php | Full CRUD, delivery items with batch/expiry/temperature tracking, cold-chain breach alerts, auto stock increment + PO status update on accepted/partial, edit blocked once accepted/rejected. Live-verified end-to-end incl. POST create/edit/delete + CSRF rejection (see COMPLETED_MODULES.md). |
| Temperature Logs | TemperatureLogController.php | TemperatureLog.php | temperature_logs/{index,add,edit}.php | index/add/edit only (no view action, matches legacy). Out-of-range detection ported exactly (two slightly different legacy rules: `checkOutOfRange()` on add vs `checkOOR2()` on edit — kept both, static methods `checkOutOfRangeAdd`/`checkOutOfRangeEdit` on the model). CSV export, UK-GDP "fewer than 2 readings yesterday" warning banner, KPI cards. Live-verified end-to-end incl. POST create (OOR flagged correctly for fridge_1 @10.5°C), edit (recalculated OOR back to false @5.0°C), CSV export, CSRF rejection, delete + audit log (see COMPLETED_MODULES.md). |
| Supplier Categories | SupplierCategoryController.php | SupplierCategory.php | supplier_categories/index.php | Single combined list+add+edit+delete page (modals), matching legacy exactly. Slug uniqueness validation on add/edit, delete blocked while any supplier is still mapped to the category. Live-verified: add, edit, duplicate-slug rejection, delete-blocked-when-in-use, delete-allowed-when-unused, CSRF rejection (see COMPLETED_MODULES.md). |
| Supplier Licences | SupplierLicenceController.php | SupplierLicence.php | supplier_licences/{add,edit}.php | Add/edit licence records against a supplier (WDA(H), GMP, GDP, etc., incl. "Other" custom type). Full old/new-value audit trail with `retention_flag=1`. Live-verified: add (WDA(H) preset type) and edit (switched to "Other" custom type) both round-tripped correctly to the DB and redirected to `supplier_view.php?tab=licences` (see COMPLETED_MODULES.md). |
| Supplier Audits | SupplierAuditController.php | SupplierAudit.php | supplier_audits/{add,view}.php | GDP/MHRA audit report add form (GMP score field conditional on manufacturer category) + printable view/report page with CAPA status update sub-form. Automatically updates the supplier's `last_audit_date`/`next_audit_due`, and auto-suspends the supplier (`approved_status='suspended'`) on a critical/unsatisfactory rating. Live-verified: satisfactory-rating audit (no suspend), critical-rating audit (confirmed auto-suspend fired), view page render, CAPA status update to "completed" (see COMPLETED_MODULES.md). |
| Permissions | PermissionController.php | Permission.php | permissions/index.php | Admin-only per-user × per-module view/create/edit/delete checkbox matrix, saved as a transaction with legacy's exact "view off forces create/edit/delete off" rule. `users`/`permissions` modules always locked (admin-only). Live-verified: initial page load, save with partial permission set, save with view unchecked (confirmed create/edit/delete force-cleared), CSRF rejection, admin-user-cannot-be-edited guard (see COMPLETED_MODULES.md). |
| Activity Log | ActivityLogController.php | — (inline queries, deliberately no Model to avoid namespace collision with `App\Helpers\ActivityLog` write-side helper) | activity_log/index.php | Read/list side of the `activity_log` table: filter bar (module/action/severity/user/date range), pagination, CSV export, old/new-value JSON diff viewer. Live-verified HTTP 200, zero PHP errors, CSV export + module filter both confirmed working. |
| Notifications | NotificationController.php | Notification.php | notifications/{index,settings}.php | List/mark-read/dismiss UI + settings form (licence/contract-expiry thresholds, audit-due days, low-stock toggle) built on top of the already-ported `getUnreadNotificationCount()`/`generateSystemNotifications()` helpers. Live-verified HTTP 200 on both routes, zero PHP errors. |
| Reports | ReportController.php | Report.php | reports/{index,stock_levels,financial_summary,supplier_performance,compliance}.php | Hub page + 4 report pages, each with its own aggregate SQL, KPI cards, and CSV export. **Bug caught and fixed during verification:** the 4 sub-report controller methods were initially named without the required `Action` suffix (e.g. `stockLevels()` instead of `stockLevelsAction()`), which the `Router::dispatch()` `{action}Action()` convention requires — caused live HTTP 404s on all 4 report routes until corrected. All 5 routes + CSV export now live-verified HTTP 200, zero PHP errors. |
| Installer | InstallController.php | — (raw PDO, no Model — must work before app config/DB exist) | (raw HTML, no template — bypasses `Controller::render()`/layout entirely since header.php depends on a working DB connection) | Self-contained 4-step wizard (requirements check → DB credentials form → confirm & run import/config-write → done), replicating legacy `install.php` exactly: creates DB if requested, imports `install.sql` (custom quote-aware statement splitter), writes `app/config.php` in this app's array-return format (not legacy `define()` constants), verifies the fresh config actually connects+queries before writing `install.lock`, and blocks re-run unless `?force=reinstall` is passed. Live-verified: step 1 (requirements) and step 2 (DB form) both render HTTP 200 with zero PHP errors; full step-3 install-run/step-4 completion flow was code-reviewed but not executed against the live `pharma_test` DB in this session (would overwrite the working test config) — see Installer verification note below. |
| Site Settings | SiteSettingController.php | SiteSetting.php | site_settings/index.php | New admin-only module (no legacy source — net-new feature): manage site name, logo/favicon upload (PNG/JPG/WEBP/SVG/ICO, 2MB limit, `assets/img/site/`), tagline, website URL, contact email/phone/address/support hours, 4 social links, footer text + copyright text. Single-row `site_settings` table (id=1), migration appended to `install.sql`, `modules` table row added (slug `site_settings`, id 7). Global `getSiteSettings()` helper added to `functions.php` (cached per-request, safe-default fallback if table missing) and consumed by `layout/header.php` (title, favicon, sidebar brand name/tagline/logo) and a new `layout/footer.php` site-footer block (footer text, social icons, copyright, contact email/phone). Route `site_settings` → pretty path `/site-settings`. Access is `requireRole('admin')` (same model as Users/Permissions, not the per-user permissions matrix, since this is global config). Sidebar hamburger (`#sidebarToggle`) was also upgraded in this pass: it now toggles a persisted (`localStorage`) `body.sidebar-collapsed` class that fully hides/shows the sidebar at desktop widths (≥992px) in addition to the existing mobile off-canvas behaviour — so the same button opens/closes the module menu at any screen size. Live-verified against `pharma_test`: GET (both legacy `/site_settings.php` and pretty `/site-settings`) HTTP 200 zero errors; admin-only enforcement (manager role gets redirected with "Access denied" flash, nav link hidden); POST save round-trips all text fields correctly (incl. UTF-8 `©`); logo upload+render-in-sidebar+remove-logo (file deleted from disk + DB path cleared); field validation (required site_name, URL format, email format) re-renders form with `is-invalid`/`invalid-feedback` and no data loss; unsupported file type upload rejected with inline error. All other 16 existing modules smoke-tested afterward (suppliers/products/invoices/contracts/users/permissions/activity_log/notifications/reports/dashboard) — all still HTTP 200, zero PHP errors, footer/hamburger render correctly on every page. |
| Batch / Stock *(added 2026-07-09 — AARJAY GDP Compliance Portal build, module 1 of ~12 new GDP modules)* | StockBatchController.php | StockBatch.php | stock_batches/{index,add,edit,view,recall}.php | **Net-new module, no legacy source** — the GDP batch/stock traceability register: for every received batch, tracks product, supplier, expiry, site/storage location, quantity received vs. available, and a full immutable movement audit trail (`stock_batch_movements`: receipt/sale/transfer/adjustment/quarantine/release/write_off/recall) so "bought from who, sold to who, how many left" can be answered for any batch on one screen. Two new tables added additively to `install.sql` (`stock_batches`, `stock_batch_movements`, with FKs to `products`/`suppliers`/`purchase_orders`/`deliveries`/`users`), `modules` row added (slug `stock_batches`, id 8), sidebar nav group added to `header.php`, routes added to `routeMap.php`/`routes.php` (`batch`, `batch/create`, `batch/{id}/edit`, `batch/{id}`, `batch/{id}/movement`, `batch/recall`). Includes a dedicated **Recall Trace** screen (`batch/recall?product_id=&batch_number=`) that shows every batch + every movement for a product in one printable view — the exact question a recall notice or MHRA inspection asks. CSV export on the index. **Route-ordering bug caught and fixed during verification:** `batch/recall` and `batch/create` must be registered in `routes.php` *before* `batch/{id}` (view), otherwise `Router::matchPretty()`'s in-order pattern matching swallows `/batch/recall` into the view action with `id="recall"` (2-segment literal path vs. 2-segment placeholder pattern both match by segment count). **Double-count bug caught and fixed:** `StockBatch::create()` originally set `quantity_available = quantity_received` directly AND called `recordMovement('receipt', ...)` which also incremented it — doubling the initial available quantity. Fixed so `create()` inserts `quantity_available=0` and lets the single `recordMovement()` call be the only code path that ever changes it, keeping the movements audit trail and the snapshot column impossible to drift apart. Live-verified against `pharma_test` (3 seeded batches + seeded movement history): index/filters (search/product/supplier/status/site/expiring-days)/CSV export/add/edit/view/recall-trace all HTTP 200 zero PHP errors; full POST round-trip tested — create batch (100 received → 100 available, correct), record sale movement (-30 → 70 available + `sold_to_customer`/`sale_date`/`linked_sales_invoice` captured), record quarantine movement (status flips to `quarantined`), edit (fields updated, quantity untouched by edit form by design), delete (cascades to `stock_batch_movements`, confirmed 0 rows left). All 8 pre-existing modules smoke-tested afterward (dashboard/supplier/product/invoice/contract/temperature-log/report/activity-log) — zero regressions, all still HTTP 200 zero errors. |
| Stock Transfer *(added 2026-07-11 — Products/Stock + Orders/Dispatch)* | StockTransferController.php | StockTransfer.php | stock_transfers/{index,form,view}.php | Added dedicated Stock Transfer workflow per PDF menu item 19: transfer number, from/to site, product/batch/expiry, quantity, reason, transport method, temperature requirement, requested by, RP approval, dispatched/received by, sent/received timestamps and status. When received/completed, source batch quantity is reduced, transfer movement is recorded, a destination batch row is created, and receipt movement is recorded. Added table `stock_transfers`, routes/nav/module row id 52 and seed ST-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 

| Customers *(added 2026-07-09 — AARJAY GDP Compliance Portal build, module 2 of ~12 new GDP modules)* | CustomerController.php | Customer.php | customers/{index,add,edit,view}.php | **Net-new module, no legacy source** — GDP Compliance Portal Customer Master per `gdp_portal_dev_docs/04-customers-suppliers.md` §4.1: customer name/type, licence number+expiry+details, approval status (pending/approved/suspended/rejected), risk rating (low/medium/high/critical), full address, main/accounts/quality-recall contact emails + phone, website/portal link, delivery-collection + emergency contacts, account status (active/suspended/reopened/closed), next review date, notes. New `customers` table added directly into `install.sql` (additive, FK to `users.created_by`) — corrected same-day after initial delivery, since it was first shipped as a separate `migrations/002_customers.sql` file instead of following the project's single-installer-file convention; re-verified with a fresh-DB import test after the fix — `modules` row added (slug `customers`, id 9), sidebar nav group added to `header.php` (between Products and Invoices), routes added to `routeMap.php`/`routes.php` (`customer`, `customer/create`, `customer/{id}/edit`, `customer/{id}`). Follows the exact Product/Supplier CRUD conventions (buildWhere() shared between paginate()/exportRows(), duplicate-name validation, CSV export, delete-with-audit-trail). Includes a licence-expiry warning banner on the view page (expired = red alert, ≤60 days = amber alert), matching the same UX pattern as Batch/Stock's expiry warnings. Added `Customer::approvedActive()` helper now for the future Sales Order/Dispatch modules to consume as a customer picker. **Wired in Sales Order module (2026-07-10)** — `stock_batches.customer_id` + `sales_order_id` now populate when Sales Order has stock_reserved + batch allocated, retiring the interim free-text `sold_to_customer` shortcut for those cases (free-text column retained for historic traceability). Live-verified against `pharma_test`: full app `php -l` lint clean; index/add(GET)/view/edit(GET)/CSV export all HTTP 200 zero PHP errors; full POST round-trip — create (redirected to new record, correct `created_by`), duplicate-name rejection ("already exists" error, no DB write), edit (fields updated correctly, redirect back to view), delete (row removed, activity_log entries present for CREATE/UPDATE/DELETE, cascade clean). Zero regressions — dev server error log clean throughout. |

| Sales Orders *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 3 of ~12 new GDP modules)* | SalesOrderController.php | SalesOrder.php | sales_orders/{index,add,edit,view}.php | **Net-new module, no legacy source** — GDP Sales Order per `gdp_portal_dev_docs/06-orders-dispatch.md` §6.1: Customer PO lives here. Fields: customer_id (FK customers, must be approved/active for reservation), customer_po_number/date/file upload (PDF/JPG/PNG/DOC/XLS, 10MB), order_date, required_delivery_date, status (draft/confirmed/reserved/partially_dispatched/dispatched/invoiced/completed/cancelled), payment_status (pending/part_paid/paid/overdue), stock_reserved flag, transport_company/collection_reference/driver_name/collection_date (GDP handover evidence), proforma_invoice_number, subtotal/vat/total auto-calculated from line items. Line items table `sales_order_items`: product_id, batch_id (optional allocation to specific stock_batches), quantity_requested/reserved/dispatched, unit_price, vat_rate, line_total, notes. Two new tables `sales_orders` + `sales_order_items` added additively to `install.sql` with FKs, seed data (2 orders), `modules` row added (slug `sales_orders`, id 10, sort_order 10), sidebar nav group added to `header.php` between Customers and Invoices, routes added to `routeMap.php`/`routes.php` (`sales-order`, `sales-order/create`, `sales-order/{id}/edit`, `sales-order/{id}`). **Wiring of old free-text `stock_batches.sold_to_customer` retired:** when `stock_reserved=1` and a batch is selected in an order line, `StockBatchModel::wireStockBatches()` sets `stock_batches.customer_id` + `sales_order_id` so the batch traceability screen shows "Reserved for customer X via Sales Order Y" and the Recall Trace includes it — full stock decrement happens in Dispatch module later, keeping reservation vs dispatch distinct per GDP. `StockBatch.php` BASE_SELECT + buildWhere + findForView updated to JOIN customers + sales_orders for search and view. Live-verified against `pharma_test`: `php -l` zero errors; fresh `install.sql` import exit 0 with new tables + 2 seed orders + new columns on `stock_batches` (customer_id, sales_order_id, dispatch_id) + FKs + indexes; dev server (8300) login 200, `sales-order` index 200 shows both seed orders, `sales-order/create` 200, `sales-order/1` view 200 shows Boots Pharmacy + Financial Summary + GDP Traceability wiring, `sales-order/1/edit` 200; full POST round-trip — create SO-2026-0003 for customer 1 with product 1 batch 1 qty 2 reserved=2 (302 redirect to view), confirmed in DB `customer_id=1`, `sales_order_id=3` wired to `stock_batches` id 1, edit to status reserved + PO number edited, delete (confirmed batch unwired back to NULL), CSV export 200 valid, regression 8 modules still 200 zero errors. |


| Dispatch *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 4 of ~12 new GDP modules)* | DispatchController.php | Dispatch.php | dispatches/{index,add,edit,view}.php | **Net-new module, no legacy source** — GDP Dispatch per `gdp_portal_dev_docs/06-orders-dispatch.md` §6.5: outbound handover-evidence record. Fields: sales_order_id (FK), customer_id, dispatch_date/time, picked_by/checked_by (FK users), consignment_note, transport_company/driver_name/driver_company (GDP mandatory if buyer-arranged), collection_date/reference, logger_details, status (draft/ready/dispatched/collected/delivered/cancelled), collection_evidence_file (POD/signature upload 10MB), notes. Line items table `dispatch_items`: sales_order_item_id FK, product_id, product_name snapshot, batch_id FK stock_batches, batch_number/expiry snapshots, quantity_dispatched. Two new tables `dispatches` + `dispatch_items` added additively to `install.sql` with FKs, seed data (1 dispatch), `modules` row added (slug `dispatches`, id 11), combined nav-group "Orders & Dispatch" in header.php (Sales Orders + Dispatch), routes added. **Stock movement logic:** when status is dispatched/collected/delivered, `processStockMovementsOnDispatch()` records `stock_batch_movements` type 'sale' with negative qty, reduces `quantity_available`, sets `customer_id`, `sales_order_id`, `dispatch_id`, `sale_date`, `linked_dispatch_reference` on stock_batches for full GDP traceability; revert logic adds back qty via adjustment if status changes back from dispatched. Also auto-updates linked Sales Order status (partially_dispatched/dispatched). Delete blocked for dispatched/collected/delivered per GDP (must cancel not delete). Live-verified: index/add/view all 200, POST draft create → no stock movement, POST dispatched with buyer evidence → batch 3 qty 40→30, movement sale -10 recorded, SO status draft→dispatched, delete draft OK, delete dispatched blocked. |
| Transport / 3PL *(added 2026-07-11 — Orders & Dispatch / Contractors)* | TransportProviderController.php | TransportProvider.php | transport_providers/{index,form,view}.php | Added dedicated Transport/3PL approval module per PDF menu item 24: provider number, company/contact/direct reach fields, linked contractor, approval status, GDP declaration/insurance/licence paths, temperature capability, collection responsibility, technical agreement link, review date and RP approval. Added table `transport_providers`, routes/nav/module row id 54 and seed 3PL-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 

| Purchase Requests *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 5 of ~12 new GDP modules)* | PurchaseRequestController.php | PurchaseRequest.php | purchase_requests/{index,add,edit,view}.php | **Net-new module, no legacy source** — GDP Purchase Request / Internal PO per `gdp_portal_dev_docs/06-orders-dispatch.md` §6.3: internal traceability even when real order placed by phone/email/text/web portal/sales rep. Fields: supplier_id (FK), order_method enum(phone/email/text/web_portal/sales_rep/other), order_date, expected_delivery_date, status (draft/ordered/part_received/fully_received/cancelled), ordered_by FK users, supplier_confirmation_file + supplier_invoice_file (PDF/JPG/PNG/DOC/XLS 10MB uploads to `uploads/purchase_requests/`), subtotal/vat/total auto-calc from line items. Line items table `purchase_request_items`: product_id, product_name snapshot, quantity_ordered/received, unit_price, vat_rate, line_total, notes. Two new tables added additively to `install.sql` with FKs, seed data (2 requests), `modules` row added (slug `purchase_requests`, id 12, sort_order 12), nav link added to Orders & Dispatch group, routes added. Live-verified: index/add/view all 200, POST create PR-2026-0003 via phone method → total 222, CSV export 200, regression 200. Goods Receiving step currently manual — will be automated next. |

| Change Control *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 9 of ~12 new GDP modules)* | ChangeControlController.php | ChangeControl.php | change_controls/{index,add,edit,view}.php | **Net-new module** — GDP Change Control per 07-gdp-compliance.md §7.6: planned changes workflow new fridge example. Fields: change_number CC-YYYY-0001, title, description, change_type (equipment/premises/process/document/system/supplier/other), risk_assessment, risk_rating, sop_impact, training_impact, equipment_impact, site_impact, requested_by/approved_by, approval_date/status, completion_date/evidence, status draft/pending_approval/approved/in_progress/completed/closed/void/rejected. 1 seed CC-2026-0001 Fridge 3 installation Grovehill with full IQ/OQ/PQ mapping report, logger cal, SOP-001 v2.1, training 5/5, RP approval. | 
| Forms & Templates *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS)* | FormTemplateController.php | FormTemplate.php | forms_templates/{index,add,edit,view}.php | **Net-new module** — GDP Forms & Templates per 07-gdp-compliance.md §7.3: reusable controlled forms for account opening, supplier/customer questionnaires, technical agreements, deviation, CAPA, change control, recall, returns, temperature, cleaning, fire safety, pest control and audits. Added upload support to `uploads/forms_templates/`, link to Document Control (`linked_document_id`), CSV export, KPI cards, nav routes, modules row id 21, install.sql table/index/FK/seed data (4 realistic templates). Note: PHP CLI is not available in this sandbox, so syntax/live verification still needs to be run in a PHP-enabled environment. | 
| Recall *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS / Inspection Strength)* | RecallController.php | Recall.php | recalls/{index,add,edit,view}.php | **Net-new module** — GDP Recall per 07-gdp-compliance.md §7.9: formal recall notice workflow built on Batch/Stock traceability. Fields: recall_number REC-YYYY-0001, external recall_reference, product_id, batch_id, batch_number snapshot, expiry_date, recall_class (class_1/class_2/class_3/company_led/mock), source_of_recall, date_received, quantity_in_stock, quantity_purchased, supplier_id, purchase_date, status, returned_quantity, remaining_quantity, final_closure, capa_id, assigned_to. Added child table `recall_customer_contacts` auto-populated from `dispatch_items`/`dispatches` for all customers supplied from the recalled batch; view page supports inline customer contact status, contacted_at, returned and remaining quantities. Creating a recall marks the selected stock batch as `recalled` and writes a zero-quantity `stock_batch_movements` recall audit record. Added routes/nav/module id 22, CSV export, KPIs, seed recall REC-2026-0001 + contact line. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Automated Recall Finder *(added 2026-07-11 — Stage 4 high-tech recall helper)* | RecallFinderController.php | RecallFinder.php | recall_finder/index.php | Added recall finder screen that searches product/batch/expiry across stock batches and automatically displays purchase/GRN source plus all customer dispatch rows and recall email contacts. Includes saved finder runs and quick link to open Recall workflow for the selected batch. Added table `recall_finder_runs`, route/nav/module row id 71 and seed RF-2026-0001. | 
| Falsified Medicines *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS / Inspection Strength)* | FalsifiedMedicineController.php | FalsifiedMedicine.php | falsified_medicines/{index,add,edit,view}.php | **Net-new module** — GDP suspected falsified/counterfeit medicines per 07-gdp-compliance.md §7.10: links Product/Batch/Supplier/CAPA, captures suspicion reason, evidence/photos uploads, quarantine decision, RP review, MHRA notification required/reference/date, status. Creating or updating a case with immediate/maintain quarantine automatically sets linked `stock_batches.status` to `quarantined`, writes status_reason, and adds a zero-quantity `stock_batch_movements` quarantine audit entry. Added routes/nav/module id 23, CSV export, KPI cards, upload folder `uploads/falsified_medicines/`, table/index/FK/seed FM-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Defective Stock *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS / Inspection Strength)* | DefectiveStockController.php | DefectiveStock.php | defective_stock/{index,add,edit,view}.php | **Net-new module** — GDP Defective Stock per 07-gdp-compliance.md §7.11: links Product/Batch/Supplier/Deviation/CAPA, captures damage description, photos/evidence upload, quarantine decision, QA review, disposal required/date/method/reference, status. Creating or updating a record with quarantine/disposal/return decision automatically sets linked `stock_batches.status` to `quarantined`/`damaged`, writes status_reason, and adds zero-quantity `stock_batch_movements` quarantine/write_off audit entries. Added routes/nav/module id 24, CSV export, KPI cards, upload folder `uploads/defective_stock/`, table/index/FK/seed DEF-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Expired / Dead / Non-saleable Stock *(added 2026-07-11 — Stock control / inspection strength)* | NonSaleableStockController.php | NonSaleableStock.php | non_saleable_stock/{index,form,view}.php | Added dedicated non-saleable stock workflow for expired, damaged and dead stock: product/batch/expiry/site, quantity, days in stock, last sold date, reason, suggested action, action taken, disposal reference, Deviation/CAPA links and status. Index also surfaces expiring/expired batch suggestions. Quarantine/disposal actions update linked batch status and add stock movement audit entries. Added table `non_saleable_stock`, routes/nav/module row id 53 and seed NS-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Audits *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS / Inspection Strength)* | AuditController.php | Audit.php | audits/{index,add,edit,view}.php | **Net-new module** — GDP Audits per 07-gdp-compliance.md §7.13: internal audit/self-inspection/MHRA preparation workflow with audit_type, title, audit_date, auditor, scope, findings summary, critical/major/minor findings, overall rating, CAPA required/link, next audit due, closure notes and status. Added routes/nav/module id 25, CSV export, KPI cards, table/index/FK/seed AUD-2026-0001 linked to CAPA-2026-0002. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| GDP Gap Analysis *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Stage 2 QMS / Inspection Strength)* | GdpGapAnalysisController.php | GdpGapAnalysis.php | gdp_gap_analysis/{index,add,view}.php | **Net-new module** — GDP Gap Analysis per 07-gdp-compliance.md §7.12: inspection-readiness green/amber/red checklist dashboard. Generates live checklist from current module data (SOPs, documents, suppliers/customers, temperature, CAPA, change controls, recalls, falsified medicines, defective stock, audits, training, premises/equipment), saves assessment snapshots with item evidence/action fields, supports inline item status/evidence/action updates, overall status calculation, KPI counts and seed GAP-2026-0001. Added routes/nav/module id 26, tables `gdp_gap_analyses` + `gdp_gap_analysis_items`, indexes/FKs/seed data. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Training Matrix + SOP Read & Sign + Training Records *(added 2026-07-11 — AARJAY GDP Compliance Portal build, People & Training; Phase 3bb 2026-07-18)* | TrainingController.php | Training.php | training/{matrix,signoffs,records + forms}.php | **Net-new People & Training modules** per 03-people-training.md §§3.4–3.6: separate Training Matrix requirement window, SOP Read & Sign acknowledgement window, and Training Records evidence window. Added tables `training_matrix`, `sop_signoffs`, `training_records`; routes/nav/module rows ids 27–29; seed records; KPI counts for overdue requirements/signoffs, training records and expiring certificates. Links staff/users and SOPs; captures due/read/completed dates, e-signature, quiz score, provider/certificate metadata, trainer/signature/next review. Phase 3bb: `training_records.void_reason` (+ `status_changed_at` if missing); void UI prompts mandatory reason; list excludes void (`167_phase3bb_training_records_void_reason`). | 
| External Certificates *(added 2026-07-11 — People & Training certificates)* | ExternalCertificateController.php | ExternalCertificate.php | external_certificates/{index,form,view}.php | Added dedicated External Certificates module per 03-people-training.md §3.7: staff link, certificate type, issuing body, issue/expiry dates, file path, reminder days, status, Training Matrix link, Training Record link and notes. Added table `external_certificates`, routes/nav/module row id 49 and seed CERT-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Premises & Equipment *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Premises & Equipment)* | PremisesEquipmentController.php | PremisesEquipment.php | premises_equipment/{index,add,edit,view,calibration_form,contractor_form}.php | **Net-new Premises & Equipment module** per 08-premises-equipment.md §§8.2–8.5 and 8.10: Equipment/Asset Register with fridge/cold-chain fields, qualification/mapping/logger/emergency plan, contractor link and status; Calibration Certificates linked to assets/contractors; Contractors/Service Providers with direct contact, emergency/accounts fields, agreement/insurance references and payment status. Added tables `contractors`, `equipment_assets`, `calibration_certificates`; routes/nav/module id 30; seed CoolTech/Calibrate UK, Woodhall Fridge 1, Logger 1 and calibration certificate. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Temperature Mapping *(added 2026-07-11 — Premises & Equipment cold-chain qualification)* | TemperatureMappingController.php | TemperatureMapping.php | temperature_mappings/{index,form,view}.php | Added dedicated Temperature Mapping workflow per 08-premises-equipment.md §8.3: linked fridge/asset, fridge area, mapping date, report file path, contractor, pass/fail/conditional/pending result, next mapping due, notes, approval and status. Saving a mapping synchronizes the linked asset `mapping_status`. Added table `temperature_mappings`, routes/nav/module row id 50 and seed MAP-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Enhanced Temperature Review *(added 2026-07-11 — Premises & Equipment / Temperature Monitoring)* | TemperatureReviewController.php | TemperatureReview.php | temperature_reviews/{index,form,view}.php | Added QA/RP Temperature Review workflow: site, linked equipment asset/logger, review date, period start/end, min/max temperature, alarm count, report file path, portal source (manual/cold-chain portal/PDF/CSV/API), reviewed by/date, deviation required flag, Deviation/CAPA links, status and notes. Includes “suggest from logs” helper to populate min/max/alarm count from existing temperature_logs. Added table `temperature_reviews`, routes/nav/module row id 51 and seed TRV-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Premises Checks + Cleaning + Pest Control + Fire & Safety *(added 2026-07-11 — AARJAY GDP Compliance Portal build, Premises & Equipment)* | SiteComplianceController.php | SiteCompliance.php | site_compliance/{index,form,view}.php | **Net-new site-control modules** per 08-premises-equipment.md §§8.6–8.9: structured dated records for premises condition checks, cleaning checklists, pest control contractor visits/trends/actions, and fire/safety records with next due dates and emergency contacts. Added tables `premises_checks`, `cleaning_logs`, `pest_control_logs`, `fire_safety_records`; routes/nav/module ids 31–34; seed records for Woodhall warehouse check, cleaning, pest visit, and fire alarm test. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Dashboard GDP/QMS KPI Cards *(added 2026-07-11 — AARJAY GDP Compliance Portal build, dashboard polish)* | DashboardController.php | — | dashboard/index.php | Added dashboard KPI rows for new GDP/QMS modules: open Deviations, open CAPA, open Recalls, open Falsified Medicine cases, active Defective Stock, latest GDP Gap Red/Amber counts, Audit actions, overdue Training/SOP signoffs, Calibration due, and Premises/Site-control actions. Queries are individually wrapped in try/catch so older installs render safely until `install.sql` is re-imported. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/live verification still needs to be run in a PHP-enabled environment. | 
| Dashboard Analytics *(added 2026-07-11 — Stage 4 management analytics)* | DashboardAnalyticsController.php | DashboardAnalytics.php | dashboard_analytics/index.php | Added management analytics module with summaries and trend tables for QMS, stock, training, calibration and accounts: open CAPA/deviations/recalls, stock expiring, training overdue, calibration due, accounts outstanding, deviation trends, CAPA status, stock risk, training status and VAT by period. Includes snapshot save into `dashboard_analytics_snapshots`. Added table, routes/nav/module row id 63 and seed snapshot. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Global Search + Search Index *(added 2026-07-11 — full search specification; Phase 3as/3bn 2026-07-18 site_id)* | SearchController.php | Search.php | search/index.php | Added global topbar search and search page per search specification: central `search_index` table, permission-filtered grouped results, advanced filters for module/status/site/date range, search suggestions endpoint, admin rebuild-index action, quick links to records/communication/recall finder and seeded search rows. Rebuild indexes customers, suppliers, contractors, products, batches, orders, invoices, SOPs, CAPA, deviations, recalls, documents, evidence attachments and communications. Added routes/nav/module row for `search`. Phase 3as: `site_id` column + site_profiles picker filter; stock_batches rebuild stores profile `site_name` + `site_id`. Phase 3bn (176): backfill site_id from site-scoped sources; rebuild also writes site_id for dispatches/sops/capas/deviations/recalls; keep `site` varchar. | 
| Email Reminders *(added 2026-07-11 — Stage 4 automated alerts foundation)* | EmailReminderController.php | EmailReminder.php | email_reminders/{index,form}.php | Added automated reminder queue foundation for licence/training/SOP/calibration/CAPA/technical agreement/audit/custom reminders. Supports due-item suggestion generation, queued reminders, dry-run status, target module/ID, recipient, subject/message, due date/send-after, attempt metadata. No SMTP send is performed yet; this is a safe local queue ready for future mail integration. Added table `email_reminders`, routes/nav/module row id 62 and seed REM-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Scheduled Jobs *(added 2026-07-11 — automation/cron foundation)* | ScheduledJobController.php | ScheduledJob.php | scheduled_jobs/{index,form,view}.php | Added scheduled task/cron foundation for email reminders, cold-chain imports, accounting sync, backup checks, dashboard snapshots and custom jobs. Supports job definitions, cron expression, next run, handler/config metadata, status and dry-run logs. No actual cron execution is performed by this UI; it is a safe scheduling foundation for server cron integration. Added tables `scheduled_jobs` + `scheduled_job_runs`, routes/nav/module row id 69 and seed JOB-2026-0001. | 
| Email Templates + SMTP Metadata *(added 2026-07-11 — automated alerts foundation)* | EmailTemplateController.php | EmailTemplate.php | email_templates/{index,form,view,settings}.php | Added email template and SMTP metadata module to support future automated reminders. Includes reminder template CRUD, token preview rendering, SMTP host/port/from metadata and clear warning not to store SMTP passwords here. Added tables `email_templates` + `email_smtp_settings`, routes/nav/module row id 68 and seed training due template/SMTP placeholder. | 
| Inspection Report Packs *(added 2026-07-11 — Reports / inspection evidence)* | InspectionPackController.php | InspectionPack.php | inspection_packs/{index,form,view}.php | Added inspection evidence pack builder for MHRA/internal audits: pack number, title/context, period, prepared by, status, notes, configurable evidence sections, source routes and JSON export. Added tables `inspection_report_packs` + `inspection_report_pack_items`, routes/nav/module row id 61 and seed IRP-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Export History *(added 2026-07-11 — Reports/export audit trail)* | ExportHistoryController.php | ExportHistory.php | export_history/{index,form,view}.php | Added export history module for auditability of accountant packs, inspection packs, VAT reports, payment tracker and module CSV/JSON/ZIP/PDF/XLSX exports. Tracks export number, type, module, format, file path, filters JSON, row count, status, notes and generated by. Added table `export_history`, routes/nav/module row id 70 and seed EXP-2026-0001. | 
| Accounts & Tax Stage 3 *(added 2026-07-11 — Business & Tax Strength)* | AccountsFinanceController.php | AccountsFinance.php | accounts_finance/{index,payments,vat}.php | **Net-new Accounts & Tax reporting layer** per 09-accounts-tax.md: Finance dashboard, Payment Tracker, VAT Report and Accountant Export Pack. Added tables `tax_categories`, `contractor_invoices`, `accounts_payments`, `accounts_communication_log`; seed tax categories, contractor invoices/payments and accountant communication; routes/nav/module id 35. Uses existing Sales Orders, Purchase Requests, Contractor Invoices and Equipment Assets to generate payment/VAT/asset purchase/accountant JSON export data. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Account Statements *(added 2026-07-11 — Accounts & Tax)* | AccountStatementController.php | AccountStatement.php | account_statements/{index,form,view}.php | Added customer/supplier account statement generation: party type/link, period, opening balance, generated statement lines from customer or supplier invoices, debit/credit totals, closing balance, print view and status. Added tables `account_statements` + `account_statement_lines`, routes/nav/module row id 72 and seed CST-2026-0001. | 
| Asset Purchase Register *(added 2026-07-11 — Accounts & Equipment linkage)* | AssetPurchaseController.php | AssetPurchase.php | asset_purchases/{index,form,view}.php | Added dedicated Asset Purchase Register per PDF Accounts & Tax §9.10: linked Equipment Asset, purchase date, supplier/vendor, invoice reference, net/VAT/gross, tax category, depreciation category, warranty expiry, status and accountant notes. Added table `asset_purchase_register`, routes/nav/module row id 59 and seed AST-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Tax Categories UI *(added 2026-07-11 — Accounts & Tax)* | TaxCategoryController.php | TaxCategory.php | tax_categories/{index,form,view}.php | Added dedicated Tax Categories management UI for existing shared tax category table. Supports list/create/edit/view, slug generation and usage counts across contractor/supplier/customer invoices and credit notes. Added routes/nav/module row id 58. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Customer Proforma / Invoice *(added 2026-07-11 — Accounts & Sales linkage)* | CustomerInvoiceController.php | CustomerInvoice.php | customer_invoices/{index,form,view}.php | Added dedicated customer proforma/final invoice workflow per PDF menu item 22: customer/sales order/PO links, invoice type, invoice/due dates, net/VAT/gross, payment status, paid date, bank reference, batch details added flag, final invoice generated flag, credit note link and notes. Added table `customer_invoices`, routes/nav/module row id 55 and seed PRO-2026-0001/CI-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Supplier Invoices *(added 2026-07-11 — Accounts & Supplier Purchase linkage)* | SupplierInvoiceController.php | SupplierInvoice.php | supplier_invoices/{index,form,view}.php | Added dedicated Supplier Invoices workflow per PDF Accounts & Tax section: supplier link, Purchase Request link, supplier invoice number, invoice/due/paid dates, net/VAT/gross, payment status, bank reference, tax category, attachment path and notes. Added table `supplier_invoices`, routes/nav/module row id 56 and seed SI-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Contractor Invoices *(added 2026-07-11 — Accounts & Contractor expense linkage)* | ContractorInvoiceController.php | ContractorInvoice.php | contractor_invoices/{index,form,view}.php | Added dedicated Contractor Invoices UI for existing contractor invoice data layer: contractor/service link, invoice/due/paid dates, net/VAT/gross, VAT rate, tax category, bank reference, status, attachment path and notes. Added routes/nav/module row id 57. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Credit Notes + Accounts Communication Log UI *(added 2026-07-11 — Business & Tax Strength)* | AccountsFinanceController.php | AccountsFinance.php | accounts_finance/{credit_notes,credit_note_form,communications,communication_form}.php | Extended Accounts & Tax per 09-accounts-tax.md §§9.11–9.12: added Credit Notes list/create and Accounts Communication Log list/create screens. Added `credit_notes` table, seed CN-2026-0001, route/nav links, and included credit notes in the Accountant Export Pack JSON. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Contact Directory + Global Communication Log *(added 2026-07-11 — Customers/Suppliers + Stage 3 contact layer)* | ContactDirectoryController.php | ContactDirectory.php | contact_directory/{index,communications,communication_form}.php | Added global Contact Directory per 04-customers-suppliers.md §4.7 searching Customers, Suppliers, Contractors and Staff with quick email/website/portal/log actions. Added shared `communication_logs` table and UI per §4.8 for customer/supplier/contractor/staff/accountant communications with method, reason, person contacted, summary, attachment path, follow-up due/owner and status. Added routes/nav/module rows ids 37–38 and seed communication. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Technical Agreements *(added 2026-07-11 — Customers/Suppliers + GDP responsibility control)* | TechnicalAgreementController.php | TechnicalAgreement.php | technical_agreements/{index,form,view}.php | Added dedicated GDP Technical Agreements workflow per 04-customers-suppliers.md §4.6: agreement type (supplier/customer/transport/contractor), party links, start/review/expiry dates, signed-copy path, GDP responsibility summary, cold-chain/3PL/recall/returns/complaint responsibilities, RP approval and status. Added table `technical_agreements`, route/nav/module row id 43 and seed TA-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Returns *(added 2026-07-11 — Orders/Dispatch + GDP Compliance)* | ReturnController.php | ReturnRecord.php | returns/{index,form,view}.php | Added GDP Returns workflow: return type (customer/supplier/contractor), party links, product/batch, return date/reason/quantity/condition, quarantine decision, credit note/deviation/CAPA links and status. Creating a quarantined return updates linked stock batch status_reason/status to quarantined. Added table `returns`, routes/nav/module row id 44 and seed RET-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Customer/Supplier Qualification *(added 2026-07-11 — Customers & Suppliers qualification workflows)* | QualificationController.php | Qualification.php | qualifications/{index,form,view}.php | Added dedicated Customer and Supplier Qualification workflow per 04-customers-suppliers.md §§4.2 and 4.5: party type/link, account opening/onboarding notes, licence/GDP check, questionnaire, risk assessment, approval decision, risk rating, RP approval, review cycle/due date and evidence path. Added table `qualifications`, routes/nav/module row id 45 and seed CQ-2026-0001/SQ-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| MHRA Inspection History *(added 2026-07-11 — Company/Sites + GDP Gap Analysis)* | MhraInspectionController.php | MhraInspection.php | mhra_inspections/{index,form,view}.php | Added MHRA inspection history/preparation workflow: site, inspection type/date, inspector/reference, scope, findings, critical/major/minor findings, CAPA link, closure status/date, evidence path, next inspection prep notes, responsible user and status. Added table `mhra_inspections`, routes/nav/module row id 46 and seed MHRA-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Account Reopening *(added 2026-07-11 — Customers & Suppliers requalification workflow)* | AccountReopeningController.php | AccountReopening.php | account_reopenings/{index,form,view}.php | Added workflow for inactive/suspended/pending customers requesting to trade again per 04-customers-suppliers.md §4.3: customer link, request date, inactive reason, new licence check, updated questionnaire, complaint/issue review, risk rating update, RP approval, decision (reopen/reject/conditional), conditions, evidence path and status. Approved/conditional decisions update linked customer account/approval status. Added table `account_reopenings`, routes/nav/module row id 47 and seed AR-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Company & Sites *(added 2026-07-11 — Company/Sites anchor module; Phase 3ba 2026-07-18)* | CompanySiteController.php | CompanySite.php | company_sites/{index,site_form,site_view}.php | Added Company & Sites section per 02-company-sites.md: company profile, site profiles for Woodhall/Grovehill, WDA licence records, business continuity plans and emergency contacts. Site Profile surfaces linked evidence from Equipment Assets and Premises Checks. Added tables `company_profiles`, `site_profiles`, `wda_licences`, `business_continuity_plans`, `emergency_contacts`; routes/nav/module row id 48 and seed company/sites/licence/continuity/emergency contacts. Phase 3ba: emergency_contacts SoftLifecycle active/void; list excludes void; hard delete blocked; site_id only (`166_phase3ba_emergency_contacts_soft_void`). |
| Route Coverage + Preflight Coverage Update *(added 2026-07-11 — regression coverage)* | — | — | ROUTE_COVERAGE.md, scripts/regression_smoke_test.sh, scripts/static_preflight.sh, MODULE_REGISTRY.md | Expanded smoke-test route coverage and static-preflight token/slug coverage to include late-stage modules: Technical Agreements, Returns, Qualifications, MHRA Inspections, Account Reopening, Company & Sites and Accounting Integrations. Added ROUTE_COVERAGE.md and updated MODULE_REGISTRY.md. | 
| Route Map Audit + Permissions Matrix Checklist *(added 2026-07-11 — regression and RBAC readiness)* | — | — | scripts/route_map_audit.py, PERMISSIONS_MATRIX_CHECKLIST.md, scripts/static_preflight.sh | Added static route/controller audit script that checks duplicate routeMap keys, missing route references and missing controller files. Static preflight now runs this audit when python3 is available. Added permissions matrix checklist with recommended role access and all module slugs to verify before creating non-admin users. | 
| install.sql Audit + Final Handoff Summary *(added 2026-07-11 — final staging handoff)* | — | — | scripts/install_sql_audit.py, FINAL_HANDOFF_SUMMARY.md, scripts/static_preflight.sh | Added static install.sql audit script checking required CREATE TABLE entries, required module slugs and FK-check wrapper. Static preflight now runs install_sql_audit.py. Added FINAL_HANDOFF_SUMMARY.md with current project coverage, critical PHP/MySQL validation warning and documentation index. | 
| v13 Release Candidate Handoff *(added 2026-07-11 — final handoff)* | — | — | FINAL_RELEASE_NOTES.md, NEXT_STEPS.md | Added final release-candidate notes and explicit next-step guide. v13 is positioned as a staging-regression handoff build; next action is PHP/MySQL regression and production deployment preparation, not additional feature coding. | 
| v13.1 Staging Quickstart + Monitoring Pack *(added 2026-07-11 — staging/production operations)* | — | — | STAGING_QUICKSTART.md, POST_DEPLOYMENT_MONITORING.md, scripts/run_static_release_checks.sh, scripts/check_manifest.sh | Added concise staging quickstart, aggregate static release checks, strict manifest verification helper and post-deployment monitoring checklist. Regenerated MANIFEST.sha256 for release traceability. | 
| Final Regression Plan + Test Templates *(added 2026-07-11 — staging validation package)* | — | — | FINAL_REGRESSION_PLAN.md, STAGING_TEST_REPORT_TEMPLATE.md, scripts/php_lint_all.sh, scripts/test_install_sql.sh | Added final staging validation documents and helper scripts: ordered regression plan, staging test report template, PHP lint script and install.sql import test script. These complement System Health, static_preflight and route smoke test. No runtime module changes in this checkpoint. | 
| Go-live Runbook + Known Limitations + Changelog *(added 2026-07-11 — final handoff documentation)* | — | — | GO_LIVE_RUNBOOK.md, KNOWN_LIMITATIONS.md, CHANGELOG.md | Added final production handoff documentation: go-live runbook with staging/production/rollback phases, known limitations distinguishing local MVC foundations from real API integrations, and changelog summary across v9.x–v12.x. | 
| Security Hardening + Troubleshooting + Backup/Restore Scripts *(added 2026-07-11 — production operations)* | — | — | SECURITY_HARDENING.md, TROUBLESHOOTING.md, scripts/backup_database.sh, scripts/restore_database.sh | Added production operations documentation and database backup/restore helper scripts. Security guide covers default password, permissions, install lock, .htaccess, upload execution risks, credential handling, HTTPS and operational controls. Troubleshooting guide covers HTTP 500, routing, database, installer, uploads, accountant ZIP and cold-chain import alerts. | 
| Upload Standard Helper *(added 2026-07-11 — security/production hardening)* | — | UploadHelper.php | UPLOAD_STANDARD.md | Added shared `App\Helpers\UploadHelper` for safe evidence uploads: generated filenames, module folder validation, extension allow-list, size limits, relative path output and local-file deletion helper. Added UPLOAD_STANDARD.md with rules and migration plan for modules currently using file path fields. | 
| Evidence Attachments *(added 2026-07-11 — Upload standard implementation)* | EvidenceAttachmentController.php | EvidenceAttachment.php | evidence_attachments/{index,form,view}.php | Added central evidence/correspondence attachment register using UploadHelper. Supports real file upload, record type/ID linkage, category, generated safe filenames, original filename/size/extension metadata, notes and status. Added table `evidence_attachments`, routes/nav/module row id 73 and seed metadata record. | 
| User Training + Data Migration + Checksum Manifest *(added 2026-07-11 — final handoff traceability)* | — | — | USER_TRAINING_GUIDE.md, DATA_MIGRATION_GUIDE.md, scripts/make_manifest.sh, MANIFEST.sha256 | Added role-based user training guide, data migration guide with recommended import order and mapping examples, and SHA256 manifest generation script/output for release traceability. | 
| Data Import Jobs *(added 2026-07-11 — migration tooling)* | DataImportController.php | DataImport.php | data_imports/{index,form,view}.php | Added dry-run CSV validation module for migration data: target entity, source filename, raw CSV payload, validation rows, valid/invalid counts and downloadable templates for suppliers, customers, products, stock batches, training matrix and equipment assets. No live records are inserted; this is a safe migration validation foundation. Added tables `data_import_jobs` + `data_import_rows`, routes/nav/module row id 65 and seed IMP-2026-0001. | 
| Advanced Accountant Export Pack Formats *(added 2026-07-11 — Business & Tax Strength)* | AccountsFinanceController.php | AccountsFinance.php | accounts_finance/index.php | Extended Accountant Export Pack per 09-accounts-tax.md §9.9 with `format=json` default, combined `format=csv`, and multi-file `format=zip` export where PHP `ZipArchive` is available. ZIP contains separate CSV files for VAT report, payment tracker, contractor invoices, asset purchases, accounts communications and credit notes plus README. UI now exposes Export JSON / CSV / ZIP buttons. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/live verification still needs to be run in a PHP-enabled environment. | 
| AI Report Writer *(added 2026-07-11 — Stage 4 Advanced / High-Tech)* | AdvancedToolController.php | AdvancedTool.php | advanced_tools/{reports,report_form,report_view}.php | **Net-new Stage 4 advanced helper**: template-based AI-assisted GDP report drafting from live QMS records. Supports GDP Gap, Audit, Recall, CAPA and Deviation summaries, optional source record ID and prompt notes, and stores draft text in `ai_report_drafts`. No external AI/API is called yet; this is a safe local drafting workflow ready for future LLM integration. Added routes/nav/module id 39 and seed AIR-2026-0001. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Role-based AI Assistant *(added 2026-07-11 — Stage 4 advanced helper)* | AiAssistantController.php | AiAssistant.php | ai_assistant/{index,form,view}.php | Added local/template role-based GDP assistant for RP, QA, Warehouse, Accounts, Auditor and Admin contexts. Supports assistant threads, user prompts, generated local guidance replies for recalls, CAPA, temperature, audits/inspection and accounts. No external AI/API call is made; it is a safe role-aware guidance foundation for future LLM integration. Added tables `ai_assistant_threads` + `ai_assistant_messages`, routes/nav/module row id 64 and seed thread. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| IT / Portal Admin *(added 2026-07-11 — Admin / Stage 4 control)* | ItPortalAdminController.php | ItPortalAdmin.php | it_portal_admin/{index,change_form,developer_form,backup_form}.php | Added IT/Portal Admin module per PDF menu item 50: portal settings summary, system change log, developer access log and backup register. Tracks hosting/backup/password policy/version, changes with risk/test evidence/approval, developer access scope/reason/approval/status, and backup/restore-test records. Added tables `it_portal_settings`, `system_change_logs`, `developer_access_logs`, `backup_register`, routes/nav/module row id 60 and seed records. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| QR Labels *(added 2026-07-11 — Stage 4 Advanced / High-Tech)* | AdvancedToolController.php | AdvancedTool.php | advanced_tools/qr_labels.php | Added printable QR-style label screen for Batch/Stock and Equipment Assets. Labels include deterministic QR-style SVG blocks, human-readable code/title/status/location, and payload text for batch/asset traceability fallback. Routes/nav added under Advanced Tools at `/advanced/qr-labels`. This is a local printable label foundation ready for replacement with a full QR library if PHP QR extension/library is later installed. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/live verification still needs to be run in a PHP-enabled environment. | 
| QR Trace Landing Pages *(added 2026-07-11 — Stage 4 QR traceability)* | QrTraceController.php | QrTrace.php | qr_trace/{index,batch,asset,recall}.php | Added QR scan landing pages and scan audit log for batch, asset and recall QR payloads. Routes `/qr/batch/{id}`, `/qr/asset/{id}`, `/qr/recall/{id}` show concise trace data and link to full records. Added table `qr_scan_logs`, routes/nav/module row id 67 and seed log. | 
| Xero / QuickBooks Accounting Integration Stubs *(added 2026-07-11 — Stage 4 Advanced / High-Tech)* | AccountingIntegrationController.php | AccountingIntegration.php | accounting_integrations/{index,form}.php | Added safe accounting integration foundation for Xero/QuickBooks/Sage/Other. Stores non-secret sandbox identifiers, provider/status, enabled sync domains (sales invoices, purchase invoices, contractor invoices, payments, VAT report) and notes. Adds dry-run sync logging with counts from portal records; no external API calls and no secrets/tokens are stored. Added tables `accounting_integrations` + `accounting_sync_logs`, seed Xero/QuickBooks sandbox stubs and dry-run log, routes/nav under Accounts & Tax. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| Secure Credential Store *(added 2026-07-11 — integration security foundation)* | SecureCredentialController.php | SecureCredential.php | secure_credentials/{index,form,view}.php | Added secure credential metadata registry for Xero/QuickBooks/SMTP/cold-chain/AI/other integration secrets. Stores service/environment/public identifier, secret fingerprint only, encryption hint, rotation dates, status and access notes; raw secrets are not displayed and should not be stored until encrypted secret storage is configured. Includes access log table and view/update metadata audit. Added tables `secure_credentials` + `secure_credential_access_log`, routes/nav/module row id 66 and seed placeholders. | 
| Cold-chain API/CSV Import *(added 2026-07-11 — Stage 4 Advanced / High-Tech)* | ColdChainImportController.php | ColdChainImport.php | cold_chain_imports/{index,add,view}.php | Added Stage 4 cold-chain import foundation for logger portal CSV/API-stub data. Supports CSV paste/import with source, portal, logger ID, linked equipment asset and payload rows. Parsed rows are saved to `cold_chain_import_rows` and imported into legacy `temperature_logs`; alarm=yes or min<2/max>8 creates out-of-range temperature alerts for GDP action/deviation review. Added tables `cold_chain_imports` + `cold_chain_import_rows`, seed CCI-2026-0001, routes/nav under Advanced Tools, and KPIs. No external API call yet; ready for future API connector. Note: PHP CLI/MySQL are not available in this sandbox, so syntax/import/live verification still needs to be run in a PHP-enabled environment. | 
| System Health & Release Readiness *(added 2026-07-11 — production hardening; Phase 4n/4p 2026-07-18; keys 226–229)* | SystemHealthController.php | SystemHealth.php | system_health/index.php | Added release-readiness dashboard under Advanced Tools. Checks config, installer lock, upload directory writability, required table presence/row counts across all built modules, module registration count, and provides a manual regression checklist for php -l, install.sql import, route smoke tests, installer test, permissions verification and deployment hardening. Added module row id 42 and route `/system-health`. Phase 4n: stock_transfers GDP check uses from_site_id/to_site_id (not site_id); goods_receivings/dispatches stay on site_id list. Phase 4p+226–229: SITE_SCOPED expands; docs/forms company-wide NULL OK; floor **232** (prefer max). | 
| Release Checklist + Regression Scripts *(added 2026-07-11 — production deployment packaging)* | — | — | RELEASE_CHECKLIST.md, scripts/regression_smoke_test.sh, scripts/package_release.sh | Added deployment handoff checklist and reusable smoke-test/package scripts. Checklist covers php -l, fresh install.sql import, installer disposable DB test, module route click-through, permissions matrix, upload folder writability, default admin password, production URL/DB credentials and .htaccess protection. Smoke-test script logs in and checks main module routes for HTTP 200 and PHP error output. Packaging script creates versioned ZIP from the project root. | 
| Menu + Breadcrumb + Permissions Alignment *(added 2026-07-11 — cPanel/module visibility fix)* | PermissionController.php | Permission.php | layout/header.php | Added comprehensive Module Directory safety-net to sidebar so any accessible developed module appears even if its bespoke menu group is missing/outdated. Fixed People & Training menu nesting for External Certificates/Electronic Signatures. Added breadcrumb fallback in layout header for pages without explicit breadcrumb arrays. Updated Permission model with `ensureExpectedModules()` and a complete developed-module registry so visiting Permissions can self-heal missing `modules` rows on existing cPanel databases. Added missing module rows for Products, Supplier Categories, Notifications and Reports to install.sql. | 
| Menu/Breadcrumb/Permissions Final Alignment *(added 2026-07-11 — cPanel visibility fix)* | PermissionController.php | Permission.php | layout/header.php | Removed the temporary Module Directory menu and placed missing late-stage modules into existing sidebar groups. Added/kept breadcrumb fallback for pages without explicit breadcrumbs. Permissions now self-heal expected developed module rows when admin opens Permissions, including legacy operational modules and late-stage modules. install.sql module rows aligned for Products, Supplier Categories, Notifications, Reports, Purchase Orders, Deliveries, Contracts and Temperature Logs. | 
| Menu + Breadcrumb + Permission Grouping Update *(added 2026-07-11 — cPanel visibility/RBAC polish)* | PermissionController.php | Permission.php | layout/header.php, permissions/index.php | Removed temporary Module Directory. Added missing late-stage links into existing sidebar groups. Breadcrumb fallback now shows Home → Module → Sub-module for pages without explicit breadcrumbs. Permissions matrix is grouped module/submodule-wise with section headings and slug labels. Permissions page self-heals all expected module rows in existing databases. | 
| Release Manifest + Module Registry + cPanel Guide *(added 2026-07-11 — deployment handoff)* | — | — | VERSION.txt, MODULE_REGISTRY.md, CPANEL_DEPLOYMENT.md, scripts/static_preflight.sh | Added version manifest for v11.6, full module/route registry for permission and route verification, cPanel deployment guide, and static preflight script to check important files/routes/module slugs without PHP/MySQL. This complements RELEASE_CHECKLIST.md and the PHP/MySQL smoke-test script. | 
| SOPs *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 10 of ~12 new GDP modules)* | SopController.php | Sop.php | sops/{index,add,edit,view}.php | **Net-new module** — GDP SOP Master per 07-gdp-compliance.md §7.1: central controlled library with version control. Fields: sop_number, title, version, effective_date/review_date, author, approved_by RP, applicable_site (Woodhall/Grovehill/Both/All), applicable_roles, file_path (PDF/DOC upload 20MB to uploads/sops), superseded_by_id FK, change_history, status draft/under_review/approved/effective/superseded/void. 3 seed SOPs: SOP-001 Cold Chain 2.1 effective 2026-07-10 review 2027-07-10, SOP-012 Maintenance 1.2, SOP-005 Goods Receiving 1.0. Version history via superseded chain. |
| Electronic Signatures + Approval Audit *(added 2026-07-11 — People & Training / GDP approvals)* | ElectronicSignatureController.php | ElectronicSignature.php | electronic_signatures/{index,form,approval_form}.php | Added electronic signature setup and approval signature audit trail: user signature labels with hashed phrase, purpose/status, approval signatures linked to arbitrary record type/ID with context, meaning, timestamp, IP and user agent. Added tables `electronic_signatures` + `approval_signatures`, routes/nav/module row id 74 and seed approval. | 
| Goods Receiving *(added 2026-07-10 — AARJAY GDP Compliance Portal build, module 6 of ~12 new GDP modules)* | GoodsReceivingController.php | GoodsReceiving.php | goods_receivings/{index,add,edit,view}.php | **Net-new module, no legacy source** — GDP Goods Receiving per `gdp_portal_dev_docs/06-orders-dispatch.md` §6.4: step between Purchase Request and Batch/Stock. Turns PR into stock_batches. Fields: purchase_request_id (FK), supplier_id, received_date, received_by FK users, supplier_invoice_reference, status (draft/inspected/accepted/rejected/quarantined), discrepancy_notes, notes. Line items: purchase_request_item_id FK, product_id, product_name snapshot, batch_number (required for GDP traceability), expiry_date (required), quantity_ordered/received/accepted/rejected, rejection_reason, storage_location, notes. Two new tables `goods_receivings` + `goods_receiving_items` added additively to `install.sql` with FKs, seed data (3 GRNs), `modules` row added (slug `goods_receivings`, id 13), nav link added to Orders & Dispatch group, routes added. **Automation:** On accepted/inspected, auto-creates stock_batches for each line with positive quantity_accepted (batch_number/expiry/storage_location required), records receipt movement via StockBatch model, updates linked Purchase Request status (part_received/fully_received) and its items quantity_received. Revert logic deletes batches created by this GRN if still untouched and quantity_received == quantity_available. Live-verified: index/add/view all 200, realistic seed 3 GRNs, import exit 0 with FK_CHECKS fix, products 12/customers 5/batches 10/movements 19. Installer bug fixed: added SET FOREIGN_KEY_CHECKS=0/1 for robust import, deduplicated CREATE TABLE modules, realistic seed data (was 2 products, now 12 with proper categories, suppliers, pricing, regulatory fields). |

## IN PROGRESS 🔧

- **AARJAY GDP Compliance Portal integration** — building ~12 net-new modules from `gdp_portal_dev_docs/*.md` into this codebase, one module (or small related group) at a time, verified live before moving to the next. **Stage 1 Orders & Dispatch Complete:** Batch/Stock (1), Customer Master (2), Sales Order (3, wiring), Dispatch (4, handover + SO status), Purchase Requests (5, phone/email/text/web portal), Goods Receiving (6, PR→Batch/Stock). **Stage 2 QMS Core Started:** Deviations (7, anything wrong, links to Product/Batch/Dispatch/CAPA), CAPA (8, hub for Deviation/Audit/Complaint), Change Control (9, new fridge workflow: CC→Site→Fridge Asset→Contractor→Mapping→Logger→SOP→Training→RP), SOPs (10, controlled library v2.1 with version history, effective/review dates, file upload, superseded chain). Realistic seed data (12 products, 5 customers, 10 batches, 19 movements, 5 SO, 3 dispatches, 5 PRs, 3 GRNs, 3 deviations, 2 CAPAs, 1 CC, 3 SOPs, 6 suppliers) + FK_CHECKS fix + supplier category delete fix (removed disabled, fixed JS confirm escaping for all delete forms) + supplier licence/audit add fix (hardcoded 0 + POST priority + checkbox robust). See `gdp_portal_dev_docs/00-OVERVIEW.md` §7 and `12-REMAINING-WORK-AND-RESUME.md` §4 for full Stage 1–4 build order.

## TODO 📋

- Continue the GDP Compliance Portal build per `gdp_portal_dev_docs/00-OVERVIEW.md` / `12-REMAINING-WORK-AND-RESUME.md` — next up: **run full PHP/MySQL regression in staging, fix any runtime issues, then production deployment**. Stage 3 Business & Tax Strength (Accounts/Finance, VAT) and Stage 4 High-Tech (QR, AI, Xero) remain.

## Final steps after all modules done

- [x] Finish Suppliers edit.php + view.php (biggest remaining view — legacy `supplier_view.php` was ~855 lines with tabs) — DONE 2026-07-06
- [x] Finish Users index/add/edit views — DONE 2026-07-06
- [x] Finish Products index/add/edit/view + CSV export — DONE 2026-07-06
- [x] Activity Log, Notifications, Reports, Installer — DONE 2026-07-07 (this checkpoint)
- [ ] Full regression pass: log in, click through every module's list/add/edit/view once against `pharma_test` DB (done for Activity Log/Notifications/Reports/Installer steps 1-2 this session; full click-through of all 16 modules together not yet re-run in one pass)
- [ ] Execute a full live end-to-end run of the Installer's step 3 (actual DB creation + `install.sql` import + `config.php` write + `install.lock` creation) against a throwaway/fresh DB — not yet done this session because the sandbox's `pharma_test` DB was already the live test DB for every other module's verification and re-running the installer against it would risk clobbering that state. Recommend testing against a disposable DB name (e.g. `pharma_installer_test`) before shipping.
- [ ] Restore `app/config.php` to placeholder/production-appropriate DB values (currently pointed at local `pharma_test` for testing) — do NOT ship with real aarjayhealthcare.co.uk production creds baked in; use placeholders + note in README that installer/config must be filled in at deploy time
- [ ] Remove `dev-router.php` (dev-only convenience, not needed with real Apache `.htaccess`) or keep but clearly mark as dev-only in README
- [ ] Remove/clean any `config.php.bak` or test artifacts
- [ ] Write/update top-level `mvc/README.md` explaining the new MVC structure, how to install/run, and how it maps to the old flat-file routes
- [ ] Zip the full `mvc/` directory
- [ ] Upload zip to gofile.io and share the link

## Legacy source reference

All legacy flat-PHP source files (read-only, do not modify) live at:
`/root/.openclaw/workspace/projects/pharma-wholesale/pharma-wholesale/`

## Environment notes

- PHP 8.4-cli, php-mysql, php-mbstring installed in sandbox.
- MariaDB running locally; test DB `pharma_test` imported from
  `3_aarjayhealthcare_pharma_wholesale.sql` (note: this seed dump has
  0 rows for `purchase_orders`, `deliveries`, `temperature_logs` — insert
  a throwaway test row manually when verifying those view/edit pages,
  e.g.:
  ```sql
  INSERT INTO purchase_orders (po_number, supplier_id, po_date, expected_delivery_date, status, subtotal, vat_amount, total_amount, ordered_by, notes)
  VALUES ('PO-TEST-0001', 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 7 DAY), 'draft', 100.00, 20.00, 120.00, 1, 'test row');
  ```
  )
- Dev server for testing (`.htaccess` doesn't apply to PHP's built-in
  server, so use the `dev-router.php` shim):
  `cd mvc && php -S 127.0.0.1:8100 dev-router.php`
- A previous batch of 5 parallel sub-agents hit a `429 Rate limit
  exceeded` provider error partway through (2026-07-06 ~19:45 IST).
  Their partial output was still good (passed `php -l`, matched
  conventions) and has been kept/merged rather than discarded — see
  DONE/IN PROGRESS tables above for what survived.
