# AARJAY GDP Portal — Master Functional / Technical / GDP Specification

**Status:** Authoritative development command (accepted from business owner)  
**System:** AARJAY GDP Compliance Portal  
**Business:** AARJAY Healthcare Ltd  
**Sites (current):** Woodhall, Grovehill (must remain configurable — never hard-coded as the only sites)  
**Stack:** Existing PHP/MySQL MVC portal  

This file is the controlling specification for future development.  
Cursor rule `.cursor/rules/master-spec.mdc` summarises non-negotiables for every session.

Full detailed requirements are retained in conversation history and the module redesign notes.  
Key non-negotiables are summarised below and expanded in `GAP_ANALYSIS_MASTER_SPEC.md`.

**Phase 0 progress (2026-07-17):** SoftLifecycle voiding for core QMS modules; Partner Category remap + CLI test green; `returns.contractor_id`; `site_contractor_assignments` + TA responsibility matrix tables (`migrations/012_phase0_master_spec_foundation.sql`).

**Phase 0e (2026-07-18):** Temperature Logs + Stock Batches soft void (no hard delete); SoftLifecycle writes `void_reason` when present (`migrations/034_phase0e_temp_log_batch_void.sql`).

**Phase 0f (2026-07-18):** Commercial soft void — products, customers (`account_status`), suppliers, sales orders, dispatches, goods receiving, legacy POs/deliveries; hard delete blocked; void UI + reason (`migrations/035_phase0f_commercial_soft_void.sql`).

**Phase 0f+ (2026-07-18):** Residual soft void — purchase requests, contracts, legacy invoices (`migrations/036_phase0f_pr_contract_invoice_void.sql`).

**Phase 2e (2026-07-18):** Training role×site matrix engine — requirements catalog + role/site rules + Apply Rules expansion into `training_matrix` with provenance FKs (`migrations/037_phase2e_training_role_site_matrix.sql`).

**Phase 1i (2026-07-18):** Product Master ≠ Stock — available qty derived from `stock_batches`; product form no longer edits denormalized `current_stock`; deliveries stop bumping it (`migrations/038_phase1i_product_batch_stock_source.sql`).

**Phase 1j (2026-07-18):** E-sign hardening — `EsignService` (profile + phrase + password); TE / Qualification / Production Release RP gates; matrix no longer auto-completes SOP signoffs (`migrations/039_phase1j_esign_hardening.sql`).

**Phase 2f (2026-07-18):** Historic SOP CSV import — superseded history with content hash + supersedes chain; no re-sign queue (`migrations/040_phase2f_sop_historic_import.sql`).

**Phase 1k (2026-07-18):** E-sign QMS gates — Validation Pack approve, CAPA close, Change Control approve/close via `EsignService` (`migrations/041_phase1k_esign_qms_gates.sql`).

**Phase 1m (2026-07-18):** E-sign close/approve gates — Deviation close, Complaint close, Recall close, Document Control approve/effective via `EsignService` (`migrations/042_phase1m_esign_close_gates.sql`).

**Phase 1n (2026-07-18):** E-sign close gates — Audit, Risk Assessment, MHRA Inspection, Falsified Medicine close; Defective Stock closed/disposed/released via `EsignService` (`migrations/043_phase1n_esign_audit_risk_close.sql`).

**Phase 1o (2026-07-18):** E-sign Returns close via `EsignService` (`migrations/044_phase1o_esign_returns_close.sql`).

**Phase 0g (2026-07-18):** Admin SoftLifecycle — Users + Partner Categories void (no hard delete); `void_reason` (`migrations/045_phase0g_admin_soft_void.sql`).

**Phase 1p (2026-07-18):** Site pest/fire free-text cutover — Site Profile pickers write `site_contractor_assignments`; free-text columns no longer written; placeholder notes cleared (`migrations/046_phase1p_site_contractor_freetext_cutover.sql`). Columns later NULLed (`148`) and DROPped (`150` / Phase 1as).

**Phase 1q (2026-07-18):** `products.current_stock` cache synced from batches (`Product::syncCurrentStockCache`); pest/fire contractors seeded + assigned to active sites (`migrations/047_phase1q_stock_cache_pest_fire_seed.sql`). Cache later **DROPped** (Phase 1ba / key **241**).

**Phase 1r (2026-07-18):** Controlled Storage Profile master — CRUD/void, Product requires active profile, FK + permissions (`migrations/048_phase1r_storage_profile_master.sql`).

**Phase 1s (2026-07-18):** WDA(H) licence conditions catalogue + covered sites; site GDP activity multi-select (`migrations/049_phase1s_wda_licence_site_activities.sql`).

**Phase 1t (2026-07-18):** Party department contacts — shared `party_contacts` table, SoftLifecycle void, panels on customer/supplier/contractor, Contact Directory search (`migrations/050_phase1t_party_contacts.sql`).

**Phase 2g (2026-07-18):** CAPA status workflow engine — `TRANSITIONS` + `advanceStatus`, view advance buttons with e-sign on complete/close, edit/create status locked to legal steps (`migrations/051_phase2g_capa_status_workflow.sql`).

**Phase 2h (2026-07-18):** Deviation status workflow engine — open→investigate→pending CAPA→closed; major/critical cannot skip CAPA; e-sign on close (`migrations/052_phase2h_deviation_status_workflow.sql`).

**Phase 2i (2026-07-18):** Change Control status workflow engine — draft→pending→approved→in progress→completed→closed; approval_status synced; e-sign on approve/close (`migrations/053_phase2i_change_control_status_workflow.sql`).

**Phase 2j (2026-07-18):** Returns quarantine sync on create/update via stock movements; status workflow + SoftLifecycle void (`migrations/054_phase2j_returns_quarantine_workflow.sql`).

**Phase 2k (2026-07-18):** Risk Assessment status workflow — draft→active/under_review→closed; mitigation guards; e-sign on close (`migrations/055_phase2k_risk_status_workflow.sql`).

**Phase 2l (2026-07-18):** Complaint status workflow — open→under_investigation→pending_capa→closed; high/critical via Pending CAPA; investigation/root cause/outcome guards; e-sign on close (`migrations/057_phase2l_complaint_status_workflow.sql`).

**Phase 2m (2026-07-18):** Recall status workflow — open→tracing→customer_contact→returns_pending→capa_required→closed; Class 1/2 via CAPA Required; final closure + e-sign on close (`migrations/058_phase2m_recall_status_workflow.sql`).

**Phase 2n (2026-07-18):** Internal Audit status workflow — planned→in_progress→report_draft→capa_required→closed; CAPA/critical/major cannot skip close; e-sign + closure notes on close (`migrations/059_phase2n_audit_status_workflow.sql`).

**Phase 2o (2026-07-18):** Supplier Audit status workflow — same forward path as internal audits; CAPA status + linked CAPA gates; e-sign on close (`migrations/060_phase2o_supplier_audit_status_workflow.sql`).

**Phase 2p (2026-07-18):** Falsified Medicines status workflow — open→quarantined→rp_review→mhra_notified→capa_required→closed; MHRA-required cases via mhra_notified; RP decision + e-sign on close (`migrations/061_phase2p_falsified_status_workflow.sql`).

**Phase 2q (2026-07-18):** Defective Stock status workflow — open→quarantined→qa_review→awaiting_disposal/released/capa_required→disposed/closed; QA/disposal guards; e-sign on dispose/release/close (`migrations/062_phase2q_defective_stock_status_workflow.sql`).

**Phase 2r (2026-07-18):** Document Control status workflow — draft→under_review→approved→effective→superseded; approval_status synced; e-sign on approve/effective (`migrations/063_phase2r_document_control_status_workflow.sql`).

**Phase 2s (2026-07-18):** Temperature Excursion status workflow — open→investigating→pending_rp→closed; quarantine guard before pending_rp; close via RP disposition + e-sign (`migrations/064_phase2s_temperature_excursion_status_workflow.sql`).

**Phase 2t (2026-07-18):** MHRA Inspection status workflow — planned→completed→findings_open→capa_in_progress→closed; critical/major cannot skip CAPA path; e-sign + closure notes on close (`migrations/065_phase2t_mhra_inspection_status_workflow.sql`).

**Phase 2u (2026-07-18):** SOP status workflow — draft→under_review→approved→effective→superseded; e-sign on approve/effective; preserves content-hash / issue-new-version / re-sign queue (`migrations/066_phase2u_sop_status_workflow.sql`).

**Phase 2v (2026-07-18):** Technical Agreement status workflow — draft→under_review→active→expired|superseded; e-sign on activate (`migrations/067_phase2v_technical_agreement_status_workflow.sql`).

**Phase 2w (2026-07-18):** Form Template status workflow — draft→under_review→approved→effective→superseded; e-sign on approve/effective (`migrations/068_phase2w_form_template_status_workflow.sql`).

**Phase 2x (2026-07-18):** Temperature Mapping status workflow — draft→under_review→approved|failed→superseded; e-sign on approve; remapping interval remains risk/SOP-based (not a universal MHRA 6-month rule) (`migrations/069_phase2x_temperature_mapping_status_workflow.sql`).

**Phase 2y (2026-07-18):** Temperature Review status workflow — draft→reviewed|deviation_required→closed; deviation-required cannot close without linked deviation; e-sign on close (`migrations/070_phase2y_temperature_review_status_workflow.sql`).

**Phase 2z (2026-07-18):** Premises/Cleaning/Pest/Fire status workflow — pass→issue_found|follow_up_required→closed (shared `SiteCompliance::TRANSITIONS`); e-sign on close; follow-up guard on premises/cleaning (`migrations/071_phase2z_premises_cleaning_pest_fire_status_workflow.sql`).

**Phase 3f (2026-07-18):** Goods Receiving status workflow — draft→inspected→accepted (quarantine/reject branches); e-sign on accept; stock batches on inspected/accepted (`migrations/072_phase3f_goods_receiving_status_workflow.sql`).

**Phase 3g (2026-07-18):** Dispatch status workflow — draft→ready→dispatched→collected→delivered; e-sign on release; stock deduction on dispatched (`migrations/073_phase3g_dispatch_status_workflow.sql`).

**Phase 3h (2026-07-18):** Purchase Request status workflow — draft→ordered→part_received→fully_received (GRN may also set receive statuses); no e-sign (`migrations/074_phase3h_purchase_request_status_workflow.sql`).

**Phase 3i (2026-07-18):** Sales Order status workflow — safe steps draft→confirmed→reserved; dispatched→invoiced→completed; dispatch-driven statuses left to Dispatch module (`migrations/075_phase3i_sales_order_status_workflow.sql`).

**Phase 1u (2026-07-18):** WDA Licence status workflow — active↔under_variation; suspend/expire with e-sign; void via SoftLifecycle (`migrations/076_phase1u_wda_licence_status_workflow.sql`).

**Phase 3j (2026-07-18):** Premises Equipment status workflow — qualification_pending→active→maintenance|broken→removed|sold|replaced; optional e-sign on removed/sold (`migrations/077_phase3j_equipment_status_workflow.sql`).

**Phase 1v (2026-07-18):** Storage Profile status workflow — active↔inactive; void via SoftLifecycle (`migrations/078_phase1v_storage_profile_status_workflow.sql`).

**Phase 2aa (2026-07-18):** Training status workflow — Matrix `MATRIX_TRANSITIONS` + `advanceMatrixStatus` (e-sign on completed); Records varchar `RECORD_STATUSES`/`RECORD_TRANSITIONS` + e-sign on complete; Matrix ≠ Record ≠ Signoff (`migrations/079_phase2aa_training_status_workflow.sql`).

**Phase 3k (2026-07-18):** Cold-chain Import status workflow — draft→processed|failed; void SoftLifecycle; CSV upload preserved; live API still blocked (`migrations/080_phase3k_cold_chain_import_status_workflow.sql`).

**Phase 4e (2026-07-18):** GDP Gap Analysis status workflow — draft→final via `TRANSITIONS`/`advanceStatus`; finalize guards + e-sign (`migrations/081_phase4e_gdp_gap_analysis_status_workflow.sql`).

**Phase 3l (2026-07-18):** Credit Note status workflow — draft→issued→sent_to_accountant; SoftLifecycle void; e-sign on issue when GDP-linked via returns (`migrations/082_phase3l_credit_note_status_workflow.sql`).

**Phase 3m (2026-07-18):** Customer Invoice commercial payment_status workflow — draft→sent/pending→sent_to_accountant; Accounts payment posting unchanged; no e-sign (`migrations/083_phase3m_customer_invoice_status_workflow.sql`).

**Phase 3n (2026-07-18):** Supplier Invoice commercial payment_status workflow — draft→received/pending→sent_to_accountant; Accounts payment posting unchanged; no e-sign (`migrations/084_phase3n_supplier_invoice_status_workflow.sql`).

**Phase 1w (2026-07-18):** Workflow Task status workflow — open→in_progress→completed; cancel branches; SoftLifecycle void; no e-sign on complete (`migrations/085_phase1w_workflow_task_status_workflow.sql`).

**Phase 3o (2026-07-18):** Stock Batch status workflow — available↔quarantined; available→damaged|dead_stock|expired; e-sign on quarantine release; qty via `recordMovement` only; SoftLifecycle void (`migrations/086_phase3o_stock_batch_status_workflow.sql`).

**Phase 4f (2026-07-18):** Production Release status workflow — draft→pending_rp→approved|rejected→released; e-sign on RP approve/reject and released; SoftLifecycle void (`migrations/087_phase4f_production_release_status_workflow.sql`).

**Phase 3s (2026-07-18):** Contractor Invoice commercial status workflow — draft→sent/pending→sent_to_accountant/exported; SoftLifecycle cancel; payment posting unchanged (`migrations/091_phase3s_contractor_invoice_status_workflow.sql`).

**Phase 1x (2026-07-18):** Qualification document status workflow — active→superseded + e-sign; SoftLifecycle void (`migrations/092_phase1x_qualification_status_workflow.sql`).

**Phase 1y (2026-07-18):** Site Profile status workflow — active↔under_review↔inactive; form lock + advance UI; no void enum (`migrations/093_phase1y_site_profile_status_workflow.sql`).

**Phase 3t (2026-07-18):** Non-saleable (Expired/Dead) Stock status workflow + `site_id` → `site_profiles`; SoftLifecycle void; e-sign on dispose/close (`migrations/094_phase3t_nonsaleable_stock_status_site.sql`).

**Phase 3u (2026-07-18):** Temperature Excursion `site_id` FK cutover; site picker; legacy `site` synced snapshot; disposition/workflow engines unchanged (`migrations/095_phase3u_temperature_excursion_site_fk.sql`).

**Phase 3v (2026-07-18):** Temperature Review `site_id` FK cutover; site picker; legacy `site` synced snapshot (`migrations/096_phase3v_temperature_review_site_fk.sql`).

**Phase 1z (2026-07-18):** External Certificate status workflow — valid→expiring→expired; →superseded (optional e-sign); SoftLifecycle void (`migrations/097_phase1z_external_certificate_status_workflow.sql`).

**Phase 1aa (2026-07-18):** Evidence attachment status workflow — active→superseded + `advanceStatus`/`status_changed_at`; SoftLifecycle void via EvidenceService; attach panels unchanged (`migrations/098_phase1aa_evidence_status_workflow.sql`).

**Phase 1ab (2026-07-18):** Product Master status workflow — active↔inactive→discontinued; form status lock + advance; SoftLifecycle void; stock remains batch-derived (`migrations/099_phase1ab_product_status_workflow.sql`).

**Phase 3w (2026-07-18):** Consignment document status workflow — issued→superseded; SoftLifecycle void; re-issue supersedes prior issued (`migrations/100_phase3w_consignment_document_status_workflow.sql`).

**Phase 1ac (2026-07-18):** Company Profile status workflow — active↔inactive; form lock + advance UI; no void enum (`migrations/101_phase1ac_company_profile_status_workflow.sql`).

**Phase 1bb–1bg / keys 250–255 (2026-07-18):** Company & Sites redesign — company create wizard (`company_type`); legal fields (legal_name / reg / VAT); company hub (sites, WDA, contractors, documents); site `company_id` FK harden; contractor `scope_mode` (all_sites / include_sites / exclude_sites) + SoftLifecycle junction; `company_documents` / `site_documents` SoftLifecycle multi-attach (never hard delete).

**Phase 3x (2026-07-18):** Stock Transfer `status_changed_at` harden — column + `transition`/receive updates (`migrations/102_phase3x_stock_transfer_status_changed_at.sql`).

**Phase 3aa (2026-07-18):** Temperature Log soft void harden — active/void only; SoftLifecycle void; hard delete → RuntimeException; `status_changed_at`; log entry UX unchanged (`migrations/107_phase3aa_temperature_log_soft_void.sql`).

**Phase 3ab (2026-07-18):** Accounting Integration status workflow — not_configured→configured↔test_mode→connected + error/disabled; `advanceStatus` + form lock; no e-sign (`migrations/108_phase3ab_accounting_integration_status_workflow.sql`).

**Phase 3ac (2026-07-18):** Asset Purchase status workflow — draft→capitalised|expensed|sent_to_accountant→disposed; SoftLifecycle void; optional e-sign on dispose (`migrations/109_phase3ac_asset_purchase_status_workflow.sql`).

**Phase 1ad (2026-07-18):** Site–Contractor Assignment status workflow — active→suspended|expired|terminated; SoftLifecycle void; view + advance (`migrations/110_phase1ad_site_contractor_assignment_status_workflow.sql`).

**Phase 0j (2026-07-18):** Partner Category status workflow — active↔inactive; SoftLifecycle void; edit preserves status (`migrations/111_phase0j_party_category_status_workflow.sql`).

**Phase 2ab (2026-07-18):** Training Requirements catalog status workflow — active↔inactive; SoftLifecycle void; Matrix ≠ Record ≠ Signoff (`migrations/112_phase2ab_training_requirement_status_workflow.sql`).

**Phase 1ae (2026-07-18):** Party Contact `status_changed_at` harden — SoftLifecycle bumps timestamp when column present (`migrations/113_phase1ae_party_contact_status_changed_at.sql`).

**Phase 1af (2026-07-18):** User status workflow — active↔inactive; SoftLifecycle void; form lock + advance; `status_changed_at`; hard delete RuntimeException; login still `status=active` only (`migrations/114_phase1af_user_status_workflow.sql`).

**Phase 1ag (2026-07-18):** User–Site Assignment status workflow — active↔suspended; SoftLifecycle void; `status_changed_at`; multi-site `syncForUser` preserved (`migrations/115_phase1ag_user_site_assignment_status_workflow.sql`).

**Phase 1ah (2026-07-18):** Supplier status workflow — active↔inactive; SoftLifecycle void; form lock + view advance; `status_changed_at`; partner category maps untouched (`migrations/116_phase1ah_supplier_status_workflow.sql`).

**Phase 3ad (2026-07-18):** Account Reopening status workflow — open→under_review→rp_review→approved|rejected→closed; SoftLifecycle void; e-sign on approve/close (`migrations/117_phase3ad_account_reopening_status_workflow.sql`).

**Phase 3ae (2026-07-18):** Account Statement status workflow — draft→generated→sent; SoftLifecycle void; `status_changed_at` (`migrations/118_phase3ae_account_statement_status_workflow.sql`).

**Phase 3af (2026-07-18):** Email Template status workflow — draft→active↔inactive→superseded; no SoftLifecycle void (void not in enum); `status_changed_at` (`migrations/119_phase3af_email_template_status_workflow.sql`).

**Phase 3ag (2026-07-18):** Data Import status workflow — draft→validated|failed; validated→imported|failed; SoftLifecycle void; create/validate pipeline preserved (`migrations/120_phase3ag_data_import_status_workflow.sql`).

**Phase 3ah (2026-07-18):** Secure Credential status workflow — placeholder→configured↔rotation_due; →disabled|revoked; soft void/revoke; secrets never logged (`migrations/121_phase3ah_secure_credential_status_workflow.sql`).

**Phase 1ai (2026-07-18):** Supplier Licence status workflow — added `status` (active/inactive/void) synced from `is_active`; active↔inactive; SoftLifecycle void (`migrations/122_phase1ai_supplier_licence_status_workflow.sql`).

**Phase 3ai (2026-07-18):** Outbound Communication status workflow — draft→queued→sent|failed; failed→queued retry; SoftLifecycle void; `send()` pipeline stamps `status_changed_at` (`migrations/123_phase3ai_outbound_communication_status_workflow.sql`).

**Phase 1al (2026-07-18):** Transport / 3PL approval status workflow — `approval_status` pending→approved|conditional|rejected; suspended/inactive branches; SoftLifecycle void; view advance (`migrations/130_phase1al_transport_provider_status_workflow.sql`).

**Phase 2ac (2026-07-18):** SOP Read & Sign status workflow — pending→completed; overdue↔pending; SoftLifecycle void; Signature Profile e-sign on complete unchanged (`migrations/131_phase2ac_sop_signoff_status_workflow.sql`).

**Phase 4g (2026-07-18):** Validation Pack `status_changed_at` harden — stamps on `advanceStatus` (`migrations/132_phase4g_validation_pack_status_changed_at.sql`).

**Phase 2ad (2026-07-18):** Training Role×Site Rules active↔inactive + `status_changed_at`; SoftLifecycle void (`migrations/133_phase2ad_training_role_site_rule_status_workflow.sql`).

**Phase 4h (2026-07-18):** Inspection Report Pack `status_changed_at` harden — stamps on `advanceStatus` (`migrations/134_phase4h_inspection_pack_status_changed_at.sql`).

**Phase 1am (2026-07-18):** Party Contact status workflow — active/void; empty TRANSITIONS (create=active); SoftLifecycle void stamps `status_changed_at`; form/panel harden (`migrations/135_phase1am_party_contact_status_workflow.sql`).

**Phase 1an (2026-07-18):** Communication Log status workflow — open→waiting_response→closed; `status_changed_at`; list advance UI (`migrations/136_phase1an_communication_log_status_workflow.sql`).

**Phase 3am (2026-07-18):** Accounts Communication Log status workflow — open→waiting_response→closed; `status_changed_at`; list advance UI (`migrations/137_phase3am_accounts_communication_log_status_workflow.sql`).

**Phase 4i (2026-07-18):** AI Report Draft status workflow — draft→reviewed→approved; SoftLifecycle void; view advance (`migrations/138_phase4i_ai_report_draft_status_workflow.sql`).

**Phase 3an (2026-07-18):** Email SMTP settings status workflow — not_configured→configured↔disabled; form lock + advance; `status_changed_at`; passwords never logged (`migrations/139_phase3an_email_smtp_settings_status_workflow.sql`).

**Phase 3ao (2026-07-18):** Backup Register status workflow — scheduled→completed|failed→verified; index advance; `status_changed_at` (`migrations/140_phase3ao_backup_register_status_workflow.sql`).

**Phase 3ap (2026-07-18):** Developer Access status workflow — pending→active→expired; SoftLifecycle revoke→revoked; index advance; `status_changed_at` (`migrations/141_phase3ap_developer_access_status_workflow.sql`).

**Phase 1ao (2026-07-18):** Tax Category status workflow — active↔inactive; SoftLifecycle void; form lock + view/index advance; `status_changed_at`; invoice pickers active-only; VAT calc untouched (`migrations/142_phase1ao_tax_category_status_workflow.sql`).

**Phase 1ap (2026-07-18):** Qualification checklist item status workflow — ITEM_TRANSITIONS + `advanceChecklistItemStatus`; `status_changed_at`; verify/receive side effects kept; no e-sign on verify (`migrations/143_phase1ap_qualification_checklist_status_workflow.sql`).

**Phase 3aq (2026-07-18):** Premises Checks `applySiteId` site snapshot sync — picker required, no free-text; backfill + FK; cleaning/pest/fire already site_id-only (`migrations/144_phase3aq_premises_checks_site_sync.sql`).

**Phase 1aq (2026-07-18):** Emergency Contacts `applySiteId` site snapshot sync — picker required, no free-text; backfill + FK; `siteEvidence` by site_id (join); no status column so SoftLifecycle void skipped (`migrations/146_phase1aq_emergency_contact_site_sync.sql`).

**Phase 3ar (2026-07-18):** Legacy Invoice status workflow — draft→pending→paid + overdue branch; SoftLifecycle void; form lock + view advance; `status_changed_at`; primary UI still redirects to supplier_invoices (`migrations/145_phase3ar_legacy_invoice_status_workflow.sql`).

**Phase 2ae (2026-07-18):** Training Matrix `applySiteId` site snapshot sync — picker only, no free-text; backfill + FK (`migrations/147_phase2ae_training_matrix_site_sync.sql`).

**Phase 1ar (2026-07-18):** Site pest/fire free-text cleared (`NULL`) — assignments remain source of truth; columns retained until DROP (`migrations/148_phase1ar_site_contractor_freetext_clear.sql`).

**Phase 4j (2026-07-18):** GDP Gap Analysis item `status_changed_at` — RAG colours only (not a forward workflow); timestamp on colour change (`migrations/149_phase4j_gap_item_status_changed_at.sql`).

**Phase 1as (2026-07-18):** DROP `site_profiles.pest_control_contractor` / `fire_safety_contractor` — assignments are sole source of truth; no remaining PHP read/write paths (`migrations/150_phase1as_drop_site_pest_fire_freetext.sql`).

**Phase 3as (2026-07-18):** Search index `site_id` — site_profiles picker filter (legacy `site` text fallback); stock_batches rebuild joins profiles for site_name label; batch list/transfer/dispatch coalesce `site_profile_name` (`migrations/151_phase3as_search_index_site_id.sql`).

**Phase 3at (2026-07-18):** Notification soft dismiss harden — `is_dismissed` soft-delete + optional `dismiss_reason`; `dismissed_at`; READ_STATES/DISMISS/TRANSITIONS constants (unread→read; active→dismissed); hard delete → RuntimeException (`migrations/152_phase3at_notification_soft_dismiss.sql`).

**Phase 4l (2026-07-18):** AI Assistant thread soft void — active/void SoftLifecycle; empty TRANSITIONS (create=active); hard delete → RuntimeException; void UI on list/view; chat reply blocked when void (`migrations/154_phase4l_ai_assistant_thread_soft_void.sql`).

**Phase 3au (2026-07-18):** Recall Finder run soft void — active/void SoftLifecycle; history list excludes void; openRecall still non-void recalls only; linkRecall/raise recall skip void runs (`migrations/155_phase3au_recall_finder_run_soft_void.sql`).

**Phase 3av (2026-07-18):** Goods Receiving `site_id` → site_profiles — picker required on create; list/view site_name; Auth::siteScopeSql on list/KPIs; stock batches inherit GRN site (`migrations/157_phase3av_goods_receiving_site_id.sql`).

**Phase 3aw (2026-07-18):** Dispatch `site_id` → site_profiles — picker required on create; list/view site_name; Auth::siteScopeSql on list/KPIs; SoftLifecycle/TRANSITIONS unchanged (`migrations/158_phase3aw_dispatch_site_id.sql`).

**Phase 3ax (2026-07-18):** QR Trace scan log soft void — active/void SoftLifecycle; empty TRANSITIONS (create=active); hard delete → RuntimeException; history list excludes void; void UI on logs; scan recording INSERT unchanged (`migrations/160_phase3ax_qr_scan_log_soft_void.sql`).

**Phase 3bc (2026-07-18):** DROP free-text `site` VARCHAR from stock_batches, temperature_excursions, temperature_reviews, non_saleable_stock, premises_checks — `site_id` + `site_profiles` join is sole display source (`migrations/161_phase3bc_drop_site_varchar_batch1.sql`).

**Phase 3bd (2026-07-18):** DROP free-text `site` VARCHAR from training_matrix, emergency_contacts, mhra_inspections, equipment_assets (`migrations/162_phase3bd_drop_site_varchar_batch2.sql`). Does not touch `search_index.site`.

**Phase 3be (2026-07-18):** DROP `from_site` / `to_site` VARCHAR from stock_transfers — keep `from_site_id` / `to_site_id` (`migrations/163_phase3be_drop_stock_transfer_site_varchars.sql`).

**Phase 3ay (2026-07-18):** Contractors SoftLifecycle soft void — active/void; SoftLifecycle void; hard delete → RuntimeException; list/KPIs/search exclude void; void UI on master list/view; `payment_status` + site assignments intact (`migrations/164_phase3ay_contractors_soft_void.sql`).

**Phase 3az (2026-07-18):** Calibration certificates SoftLifecycle soft void — active/void; SoftLifecycle void; hard delete → RuntimeException; list/KPIs exclude void; void UI on asset view (`migrations/165_phase3az_calibration_certificates_soft_void.sql`).

**Phase 3ba (2026-07-18):** Emergency Contacts SoftLifecycle soft void — active/void; empty CONTACT_TRANSITIONS (create=active); hard delete → RuntimeException; list excludes void; void UI with reason; site_id only (varchar `site` may DROP in 162) (`migrations/166_phase3ba_emergency_contacts_soft_void.sql`).

**Phase 3bb (2026-07-18):** Training Records `void_reason` harden — SoftLifecycle void already via status=void; add `void_reason` (+ `status_changed_at` if missing); void form prompts mandatory reason; list excludes void (`migrations/167_phase3bb_training_records_void_reason.sql`).

**Phase 3bf (2026-07-18):** Qualifications dossier SoftLifecycle soft void — `status` active/void alongside `dossier_status` workflow; `void_reason` + `status_changed_at`; hard delete → RuntimeException; list/KPIs/TradeGate exclude void; documents under voided dossier locked (`migrations/168_phase3bf_qualifications_soft_void.sql`).

**Phase 3bg (2026-07-18):** Customer Invoices SoftLifecycle soft void — `payment_status` += void; `void_reason` + `status_changed_at`; SoftLifecycle void (status column=`payment_status`); hard delete → RuntimeException; list excludes void; line items soft-void (no hard DELETE in `saveItems`) (`migrations/169_phase3bg_customer_invoices_soft_void.sql`).

**Phase 3bh (2026-07-18):** Supplier Invoices SoftLifecycle soft void — same pattern as 3bg on `supplier_invoices` / `supplier_invoice_items` (`migrations/170_phase3bh_supplier_invoices_soft_void.sql`).

**Phase 3bi (2026-07-18):** Communication Log SoftLifecycle soft void — open→waiting_response→closed retained; `void` added to status enum + `void_reason`; SoftLifecycle void; hard delete → RuntimeException; list/search exclude void; void UI on list (`migrations/171_phase3bi_communication_logs_soft_void.sql`).

**Phase 3bj (2026-07-18):** Accounts Communication Log SoftLifecycle soft void — open→waiting_response→closed retained; `void` added to status enum + `void_reason`; SoftLifecycle void; hard delete → RuntimeException; list excludes void; void UI on list (`migrations/172_phase3bj_accounts_communication_log_soft_void.sql`).

**Phase 3bk (2026-07-18):** Contractor Invoices SoftLifecycle soft void — `status` += void; `void_reason` + `status_changed_at`; SoftLifecycle void (replaces cancel); hard delete → RuntimeException; list/KPIs/Accounts exclude void; optional line-items soft-void if table exists (`migrations/173_phase3bk_contractor_invoices_soft_void.sql`).

**Phase 3bl (2026-07-18):** Credit Notes SoftLifecycle soft void harden — ensure `status` includes void + `void_reason`/`status_changed_at`; SoftLifecycle void; hard delete → RuntimeException; list/pickers exclude void; void UI on list/view; no line-items table (`migrations/174_phase3bl_credit_notes_soft_void.sql`).

**Phase 3bm (2026-07-18):** Outbound Communications SoftLifecycle soft void harden — ensure `status` includes void + `void_reason`/`status_changed_at` (also from 123); SoftLifecycle void; hard delete → RuntimeException; dispatch/sales-order lists exclude void; void UI on dispatch email history (`migrations/175_phase3bm_outbound_communications_soft_void.sql`).

**Phase 3bn (2026-07-18):** Search index `site_id` backfill — copy site_id (+ site_name snapshot) from stock_batches/sops/capas/deviations/recalls/dispatches; optional exact site name/code match; rebuild writes site_id when source has it; does **not** DROP `search_index.site` (`migrations/176_phase3bn_search_index_site_id_backfill.sql`).

**Phase 3bo (2026-07-18):** Forms & Templates SoftLifecycle soft void harden — ensure `void_reason` (+ `status_changed_at`); SoftLifecycle void; hard delete → RuntimeException; list/KPIs exclude void; void UI replaces trash on list/view (`migrations/177_phase3bo_forms_templates_soft_void.sql`).

**Phase 3bp (2026-07-18):** Accounts Payments SoftLifecycle soft void — `payment_status` += void; `void_reason` + `status_changed_at`; SoftLifecycle void; hard delete → RuntimeException; ledger list excludes void; void UI on Payment Tracker ledger section (`migrations/178_phase3bp_accounts_payments_soft_void.sql`).

**Phase 3bq (2026-07-18):** QMS SoftLifecycle `void_reason` batch — audits, capas, change_controls, complaints, deviations, document_controls, defective_stock, falsified_medicines, gdp_gap_analyses, mhra_inspections, recalls, risk_assessments, sops, supplier_audits, technical_agreements, temperature_excursions/mappings/reviews, training_matrix, inspection_report_packs, validation_packs (+ `status_changed_at` if missing); SoftLifecycle already writes `void_reason` when present; SystemHealth floor 200 + line-item status sample (`migrations/200_phase3bq_qms_void_reason_batch.sql`).

**Phase 3cy–3de / keys 201–207 (2026-07-18):** Child/line evidence SoftLifecycle — `technical_agreement_responsibilities`, `qualification_checklist_items` (`row_status`; domain workflow stays on `status`), `gdp_gap_analysis_items` (`row_status`; RAG stays on `status`), `validation_pack_items`, `inspection_report_pack_items`, `wda_licence_conditions` (no prior hard DELETE — SoftLifecycle harden), `qualification_licence_reviews`; catalogue sync soft-voids removed codes; lists filter active (`migrations/201_phase3cp_*`–`207_phase3cv_*`; numeric keys unique vs 210–218).

**Phase 3cp–3cx (2026-07-18):** Soft-void category maps + near-miss children (keys **210–218**) — `customer_category_map`, `supplier_party_category_map`, legacy `supplier_category_map` (surrogate id + active/void; sync soft-voids removed links); `account_statement_lines`, `cold_chain_import_rows`, `data_import_rows` (void with parent); `backup_register` status+=void + SoftLifecycle; `business_continuity_plans` SoftLifecycle; `ai_assistant_messages` void with parent thread; SystemHealth floor **218** (`migrations/210`–`218`).

**Phase 3cy–3dc / keys 219–224 (2026-07-18):** Ops `site_id` → `site_profiles` — `returns` (batch), `sales_orders`/`purchase_requests` (creator primary), `temperature_logs` (location/equipment match; keep location free-text), `temperature_mappings` (asset), `falsified_medicines`+`defective_stock` in one file `224_phase3cy_falsified_defective_site_id.sql`; picker + `applySiteId` + `Auth::siteScopeSql` + `site_name` join; SoftLifecycle/TRANSITIONS unchanged.

**Phase 3dd–3dg / keys 226–229 (2026-07-18):** Site_id cutover chunk — `document_controls`+`forms_templates` (company-wide NULL OK; SystemHealth SOP-style exception, no `applicable_site`); `qualifications`+`technical_agreements` picker/scope/backfill required; backfill+require create on `gdp_gap_analyses`/`workflow_tasks`/`production_releases`; `calibration_certificates.site_id` denormalized from asset; optional `purchase_orders`/`deliveries`/`contracts` site_id+backfill. Floor remains **max** settlement (`migrations/226`–`229`).

**Phase 1ba / 4r / 4s / keys 241–243 (2026-07-18):** DROP `products.current_stock` (qty = `stock_batches` only; `syncCurrentStockCache` removed); settlement handoff docs; SystemHealth floor **243** + SITE_SCOPED expand for QMS + commercial site_id tables (`migrations/241`–`243`).

## Deploy checklist (not settlement gaps)

SystemHealth may **warn** on production URL / HTTPS when running locally (`app.url` = `http://127.0.0.1:8080`). Before go-live: set production `app.url` (HTTPS), enable HTTPS-only cookies/HSTS as in `SECURITY_HARDENING.md`, change default admin password, confirm `install.lock`. These are **deployment** items — they do not reopen Master Spec settlement.

**Phase 3br–3bx (2026-07-18):** GDP/commercial line-item SoftLifecycle — `status` enum(active,void) + optional `void_reason` on `goods_receiving_items`, `dispatch_items`, `sales_order_items`, `purchase_request_items`, `purchase_order_items`, `delivery_items`, `invoice_items`; update paths soft-void then INSERT (no hard `DELETE FROM *_items`); list/aggregate queries filter active (`migrations/179`–`185`).

**Phase 3cj–3co (2026-07-18):** Child/junction soft-void chunk (keys **190–195**) — `temperature_excursion_batches`, `recall_customer_contacts`, `wda_licence_sites` (+ surrogate id), `site_profile_activities` (+ surrogate id), `stock_batch_movements` (SoftLifecycle void + compensating reverse via `recordMovement`; never DELETE), `approval_signatures`. Category maps skipped (pure junction).

**Phase 4k (2026-07-18):** System Health GDP integrity checks — site_id NULL orphans, SoftLifecycle markers, schema_migrations max key (152+), search_index stock_batches site_id, site_profiles pest/fire free-text DROP (`migrations/153_phase4k_system_health_gdp_checks.sql` marker).

**Phase 4m (2026-07-18):** System Health SOP site_id rule — company-wide Both/All may omit site_id; floor 156 (`migrations/156_phase4m_system_health_sop_site_rule.sql` marker).

**Phase 4n (2026-07-18):** System Health stock_transfers site scoping — check `from_site_id`/`to_site_id` (not `site_id`); GRN/Dispatch remain on generic `site_id` list; migration floor 156 (`migrations/159_phase4n_system_health_transfer_site_fks.sql` marker).

**Phase 1aj (2026-07-18):** Customer status workflow — `account_status` active↔inactive (+ legacy suspended/reopened/closed); SoftLifecycle void; form lock + view advance; `status_changed_at`; partner category maps untouched (`migrations/124_phase1aj_customer_status_workflow.sql`).

**Phase 1ak (2026-07-18):** Electronic Signature profile status workflow — active→expired; SoftLifecycle revoke→revoked; form status lock; `verifyPhrase` still requires active (`migrations/125_phase1ak_electronic_signature_status_workflow.sql`).

**Phase 0k (2026-07-18):** Legacy `supplier_categories` status workflow — active↔inactive + `advanceStatus` + `status_changed_at`; SoftLifecycle void (Phase 0i); Partner Categories UI remains PartyCategory (`migrations/126_phase0k_supplier_category_status_workflow.sql`).

**Phase 3aj (2026-07-18):** Email Reminder status workflow — queued→dry_run|sent|failed; dry_run→queued|sent|failed; failed→queued; SoftLifecycle cancel; no SMTP send (`migrations/127_phase3aj_email_reminder_status_workflow.sql`).

**Phase 3ak (2026-07-18):** Export History status workflow — generated→downloaded|sent|failed; downloaded→sent|failed; SoftLifecycle void (`migrations/128_phase3ak_export_history_status_workflow.sql`).

**Phase 3al (2026-07-18):** Scheduled Job status workflow — active↔paused; active|paused→disabled; disabled→active; `status_changed_at` on jobs + runs (`migrations/129_phase3al_scheduled_job_status_workflow.sql`).

**Phase 3p (2026-07-18):** Purchase Order status workflow — draft→submitted→acknowledged (+ cancel early); GRN/Delivery may set partially_received/received; SoftLifecycle void; optional e-sign on submit (`migrations/088_phase3p_purchase_order_status_workflow.sql`).

**Phase 3q (2026-07-18):** Contract status workflow — draft→active→expired|terminated|renewed; SoftLifecycle void; e-sign on activate/terminate (`migrations/089_phase3q_contract_status_workflow.sql`).

**Phase 3r (2026-07-18):** Delivery status workflow — pending→in_transit→arrived→checked→accepted|rejected|partial; SoftLifecycle void; PO receive sync preserved (`migrations/090_phase3r_delivery_status_workflow.sql`).

**Phase 1 progress (2026-07-17):** Site code/company link; Contractor Master view/edit + site assignment CRUD UI; Storage Profiles + Product Master selector (`migrations/013_phase1_core_masters.sql`). Stock remains separate.

**Phase 2 progress (2026-07-17):** TA responsibility matrix (AARJAY/Counterparty/Shared/N/A); Training Matrix ↔ Record/Signoff FKs + auto-create; SOP content hash + issue-new-version + re-sign queue (`migrations/014_phase2_quality_training.sql`).

**Phase 2b progress (2026-07-17):** CAPA source FKs (complaint/inspection/recall) + enforcement; Complaint party/batch UX + Raise CAPA; Change Control Risk/SOP/Equipment FKs (`migrations/015_phase2b_qms_linkage.sql`).

**Phase 3a progress (2026-07-18):** Stock Transfer site FKs + qty guard + Approve→Send→Receive workflow + void; temperature excursion asset join fixed to `equipment_assets`; mapping form SOP/risk-based interval note (`migrations/016_phase3a_stock_transfers.sql`).

**Phase 3b progress (2026-07-18):** Recall Finder ↔ Recall case FK (`finder_run_id` / `recall_id` / `results_json`); unified dispatch trace; duplicate-batch guard; Raise CAPA on recall view; calibration certificate file upload (`migrations/017_phase3b_recall_finder_linkage.sql`).

**Phase 3c progress (2026-07-18):** Premises/Cleaning/Pest/Fire `site_id` FKs + void with reason; contractor lists prefer site assignments (`migrations/018_phase3c_site_compliance_fks.sql`).

**Phase 3d progress (2026-07-18):** Cold-chain CSV file upload + safer parse + SoftLifecycle void; feature flag ON for local; live API fetch explicitly blocked (`migrations/019_phase3d_cold_chain_upload.sql`).

**Phase 3e progress (2026-07-18):** Batch/Stock `site_id` → `site_profiles` FK; add/edit site picker; GRN + transfer receipt set `site_id`; legacy `site` varchar synced as display snapshot (`migrations/056_phase3e_stock_batch_site_fk.sql`).

**Phase 1e progress (2026-07-18):** Staff multi-site assignments (`user_site_assignments`); SOP signoff bound to Signature Profile + phrase + password; TE Raise CAPA via linked Deviation (`migrations/020_phase1e_staff_sites_signature.sql`).

**Phase 1f progress (2026-07-18):** `EvidenceService` + embedded evidence panels on CAPA / Deviation / Complaint / Recall + SoftLifecycle void on evidence register; **My Tasks** inbox (`/my-tasks`); migration `021` adds `complaint` to evidence `record_type` enum.

**Phase 1g progress (2026-07-18):** Workflow task engine (`workflow_tasks`) — create/assign/start/complete/soft-void; appears in My Tasks; module `workflow_tasks` (`migrations/022_phase1g_workflow_tasks.sql`).

**Phase 1g+ (2026-07-18):** Auto-emit workflow tasks from CAPA / Deviation / Complaint / Recall / Change Control; `auto_emitted` flag; linked tasks panel on record views (`migrations/033_phase1g_workflow_auto_emit.sql`).

**Phase 4a progress (2026-07-18):** Inspection packs — `mhra_inspection_id` FK, readiness %, draft→prepared→reviewed→issued transitions, soft void, MHRA “Raise Inspection Pack”, non-claim disclaimer on export (`migrations/023_phase4a_inspection_pack_mhra.sql`).

**Phase 4b progress (2026-07-18):** GDP Gap Analysis — real live checks (TE, training, calibration/mapping, workflow tasks, evidence); `status` draft/final/void; site + MHRA FKs; deep-links; finalize; JSON export disclaimer (`migrations/024_phase4b_gdp_gap_hardening.sql`).

**Phase 4c progress (2026-07-18):** Validation Packs — URS/IQ/OQ/PQ-UAT sections, CC/site FKs, readiness %, draft→in_progress→reviewed→approved (RP gate), soft void, JSON disclaimer (`migrations/025_phase4c_validation_packs.sql`).

**Phase 4d progress (2026-07-18):** Production Releases — RP-controlled gate linked to validation pack (+ optional gap analysis/CC); checklist; submit→RP decide→released; soft void; non-claim (`migrations/026_phase4d_production_releases.sql`). Phase 4 planned track complete.

**Phase 1h progress (2026-07-18):** Site-scoped access — `Auth::userSiteIds` / `siteScopeSql` / `canAccessSite`; session primary site + switcher; filters on workflow tasks / My Tasks / site compliance / validation packs; User Add sites + PermissionSeeder (`migrations/027_phase1h_site_scoped_access.sql`).

**Phase 1h+ (2026-07-18):** QMS site visibility — `site_id` on deviations + capas; list/KPI/My Tasks filters; form site picker + view assert (`migrations/028_phase1h_qms_site_ids.sql`).

**Phase 1h++ (2026-07-18):** Extended QMS site visibility — `site_id` on complaints, recalls, change_controls (`migrations/029_phase1h_qms_site_ids_more.sql`).

**Phase 1h+++ (2026-07-18):** Risk Assessments + MHRA Inspections site visibility (`migrations/030_phase1h_risk_mhra_site_ids.sql`).

**Phase 1h++++ (2026-07-18):** Audits + Supplier Audits site visibility (`migrations/031_phase1h_audits_site_ids.sql`).

**Phase 1h+++++ (2026-07-18):** SOPs + Training Matrix/Records + Equipment Assets site visibility (`migrations/032_phase1h_sop_training_equipment_site_ids.sql`).


## Non-negotiables

1. One connected system — foreign keys, not free-text links.
2. No hard delete of GDP/finance-critical records — Void/Cancel/Supersede/Obsolete with reason + audit + signature where required.
3. Single source of truth — Contractor Master, Product Master vs Batch/Stock, Staff/User once.
4. Electronic signatures: attributable, owner-controlled, bound to exact record/version/hash; PIN is secondary only.
5. Technical Agreements: AARJAY / Counterparty / Shared / N/A responsibility matrix.
6. Temperature mapping: risk/SOP based; never claim 6-month mapping as a universal MHRA rule.
7. Preserve working logic; inspect before rebuild; migrate existing data; server-side permissions.
8. Never claim software alone is “MHRA approved” or automatically “GDP compliant”.

## Implementation order (locked)

- **Phase 0** — Repair risks (delete, permissions, uploads, categories, integrity, tests)
- **Phase 1** — Core masters (Company/Site, Party/Contacts, Contractors↔Sites, Product/Storage, Staff, Evidence, Tasks, Signatures)
- **Phase 2** — Quality & Training
- **Phase 3** — Operations & Premises
- **Phase 4** — Inspection intelligence & release

## Acceptance bar

A screen opening is not completion. Completion requires migration, permissions, audit, signatures where required, responsive UI, UAT evidence, and documented known limitations.
