# 05 — Settled HMS Decisions & Phase-0 (binding constraints on the new vision)

Sources read in full (2026-06-11):
- `/home/moonui/public_html/hms-feasibility-report.html` — "دراسة جدوى معمارية — إضافة نظام إدارة المستشفيات (HIS) إلى Moon ERP", **v2**, dated 10 June 2026, methodology: 5 parallel read-only analysts + independent Codex review, line-level evidence re-verified.
- `/home/moonui/hms-phase0-spec.md` — "HMS Phase-0 'Platform Readiness NOW' — Execution Spec". Status header: **APPROVED by owner, not yet started.**
- Cross-reference for OBGY notes: `/home/amrtechogate/public_html/obgy-erp-analysis.md` (read structurally for intersection points only).

Neither HMS document mentions OBGY anywhere; all OBGY intersections below are explicitly marked **(inference)**.

---

## 1. Binding architecture decisions (spec section: `SETTLED DECISIONS (do not reopen)`)

These are owner-approved and the spec forbids reopening them. Any OBGY/HIS vision MUST treat them as constraints:

### D1 — Unified patient identity: upgrade `lab_patients` in place
- `lab_patients` becomes the shared patient entity. **No new patient table, no rename.** Rejected alternatives: a second HIS patient table with 1:1 sync ("split identity, eternal sync, two competing patient screens"), and leaving duplication.
- Measured rationale: 8 hard FKs + **144 `patient_id` references in 52 LIS files**; NPHIES builds FHIR `Patient` directly from it; `partner_id` already links every patient to a Business Partner with auto AR/AP ledger accounts.
- "Promotion to shared entity" is a **namespace/ownership decision only** (shared model location + scopes) — spec W2-1 final bullet.

### D1b — B2B lab patients STAY in `lab_patients`
- Owner asked the question directly; a dedicated analysis answered: current single-table design is correct, splitting is an architectural mistake. Decision table **37/40 single hardened table vs 19/40 split**.
- `external_lab_id` owner column = FHIR `Patient.managingOrganization` pattern (industry standard).
- Dev-environment evidence: 11,769 own patients vs 15 B2B; B2B patients anchor **live clinical rows** (15 requests, 18 samples, 45 results, 16 invoices); splitting would force polymorphic refs across the whole clinical pipeline or duplicate it.
- Staff patient list showing B2B rows **with a lab badge** is deliberate design, not a leak (`lis-patients.component.html:141-144`).
- Guarding mechanism is constrained: **NO blanket Global Scope on `lab_patients`** (spec constraint #2 — note the report v2 body text in decision 1 still mentions a Global Scope, but the Phase-0 constraints box and the spec override it explicitly: named scopes `internal()`/`forLab($labId)` only, consumed by HIS and specific fixed call sites; LIS worklists/kanban/results intentionally see BOTH populations and stay untouched).

### D2 — Module topology & dependency direction
- HIS is **ONE module `Modules/HIS`** (matches house style — LIS alone has 275+ routes / 135 migrations; internal split by folders, not modules). "Several small modules (HIS+Bedding+Appointments)" explicitly deferred/rejected because the platform doesn't enforce inter-module dependencies anyway.
- **Golden direction rule:** HIS imports LIS/Accounting/HRM forward (direct actions/services — the existing house pattern); **LIS never imports HIS**. Information flows backward via the two events that already exist: `LabResultReleased` / `LabRequestCompleted`, which HIS listens to. This keeps LIS sellable standalone.
- Background facts: integration fabric is direct imports (LIS→Core 93 refs, LIS→Accounting 68, Sales→Core 42), exactly **one** cross-module event subscription exists system-wide (`PostLabInvoice.php:7,43`), and three layering inversions exist (Core→Accounting 42, Core→WebStore 12, Core→LIS 1 at `RoleSaveService.php:10`) — HIS must not add a fourth.

### D3 — "Reception orders labs" contract
- `LabRequestController::store` is ~294 lines (lines ~172–471 of a 1,236-line file). Extract `Modules/LIS/app/Actions/CreateLabRequest.php` (Actions/ dir already has 8 actions), **byte-identical behavior** (same `StoreLabRequestRequest` validation, same response). LIS controller delegates to it; HIS calls it programmatically.
- Add inert nullable `encounter_id` (unsignedBigInteger, **no FK** — HIS table doesn't exist yet, comment why) on `lab_requests`.
- FE: the LIS request wizard already supports embedded Dialog mode — HIS reception opens it as-is.
- This contract is the declared template for radiology and any future service-performer module.

### D4 — Encounter/Folio & central billing — **design approved, creation DEFERRED to HIS kickoff**
- Design: Encounter entity (outpatient visit or inpatient stay) + multi-source charge lines (consultation, service, bed-day via nightly job, lab invoice **by reference**, pharmacy dispense) posted via generalization of the existing B2B progressive line-by-line billing pattern; deposits/advance payments built on existing receipt vouchers + FIFO allocation.
- Anti-double-posting rule: lab invoice enters the folio **by reference only — its lines are never re-posted** (single posting source per line).
- Spec: "HIS scaffold + his.* permissions + Encounter/Folio tables are **DEFERRED to HIS kickoff** (design approved, creation later)."

### D5 — Owner governing constraint (binds every Phase-0 item)
- **"LIS must NOT be affected — neither performance nor behavior."**
- Guarantees: (1) zero added queries on hot paths (class moves + byte-identical extractions only); (2) no blanket global scope on `lab_patients`; (3) the new unique index is performance-positive; (4) every intended behavioral change ships **BE+FE together in one deploy** (no "new BE old FE" moment); (5) acceptance gate per milestone = all suites green + before/after curl response-time comparison on worklist + reception endpoints — **any regression reverts the item**.

---

## 2. Approved overlap matrix (report section 3)

| HIS capability | Exists today | Grade | Strategy |
|---|---|---|---|
| Patient master + accounting link | `lab_patients` ≈80%: MRN, per-company national-id uniqueness, insurance, portal links, `partner_id`→auto ledger accounts | Strong partial | Upgrade in place (D1) |
| Reception orders labs | `POST /lis/requests` already accepts source `in_patient`/`emergency` (enum since day 1) + auto invoice | **Ready** | Integrate via `CreateLabRequest` (D3) |
| Doctors | Mature LIS registry (specialties, license, signature, full commission/settlement engine) but **zero HRM link** (FK deferred by explicit migration comment) | Partial | Add `employee_id` → unified practitioner registry |
| Visit/Encounter | `lab_visits` = thin 1:1 wrapper around a lab request | Seed | New HIS Encounter entity modeled on the pattern |
| Appointments/scheduling | **Nothing** in all 15 modules ("appointment" is just an enum label; CRM = tickets only) | Missing (1/10) | New build — biggest FE item too |
| Hoteling: wards/rooms/beds + ADT | **Zero** tables/concepts (exhaustive search confirmed) | Missing (0–1/10) | Full new build (Phase 2) |
| Catalog: pricing + insurance coverage | Named price lists & insurance coverage (patient/company share, caps, monthly billing) live in LIS, FK-bound to tests only | Split | Generalize LIS pattern to polymorphic HIS items |
| Pharmacy | Store side works today (batch/expiry inventory + POS); clinical pharmacy (prescriptions/eMAR/narcotics) absent | Store ready | Phase 3 on top of Inventory/POS |
| Patient folio (consolidated bill) | No folio/deposit entity, but the pattern works: B2B progressive billing + FIFO payment allocation + receipt vouchers | Pattern ready | Generalize + Encounter/Folio (D4) |
| Advance payments/deposits | Unsupported (payment requires posted invoice, no overpay) | Missing | Build on receipt vouchers + FIFO precedents |
| Reception cashier & treasuries | Complete in LIS: dynamic payment methods (Tamara/Tabby), treasuries with auto ledger accounts, shifts with Z-report | **Ready** | Direct reuse |
| Insurance & claims + NPHIES | Approved-price contracts, shares, monthly billing, NPHIES FHIR (eligibility/auth/claims) | **Ready** | Generalize to non-lab HIS items |

Readiness scorecard: financial backbone 9/10; module/multitenancy recipe 8/10; FE pattern 8/10 (app-in-app proven 3×: POS/HR/Lab); patient & practitioner identity 7/10; **integration discipline 4/10** (one event system-wide; documented duplication history — the four print engines); appointments 1/10; hoteling/beds/folio 0–1/10; **overall ≈6/10 — "ready with conditions, no hard blockers; gaps are domain-build, not platform-surgery."**

---

## 3. Approved roadmap

- **P0 Foundation** — "Platform Readiness NOW" package, ~6–9 dev-days, +1 small item (permission registry hoist) → **~7–10 dev-days** total. Runs BEFORE any HIS work (owner decision). Report's original P0 estimate: 3–5 person-weeks incl. the four decisions + scaffold.
- **P1 Outpatient clinics** — 8–14 person-weeks: reception desk + patient 360 file, **appointments engine (biggest new build)**, clinic Encounter + consultation/services, embedded LIS order wizard, folio + cashier + insurance. Demo: fully operational outpatient hospital.
- **P2 Inpatient & hoteling** — 10–16 person-weeks: wards/rooms/beds + bed map, ADT (admit/transfer/discharge), daily stay charges (nightly job → folio), deposits/advances, basic nursing station + ward orders. Demo: full admission cycle.
- **P3 Extensions** — 4–10 person-weeks each, independent: clinical pharmacy (over Inventory/POS), OR/radiology (same D3 contract), catering, patient appointment portal.
- Total to end of P2 (full inpatient demo): **21–35 person-weeks**.

Generalization Register (report section 5+), governing rule "hoist now only what fixes a live defect or is needed by >1 module at no cost; otherwise hoist at first real need":
- NOW: branch data scope (`LisDataScope`→Core), permission dependency registry (fixes `RoleSaveService.php:10` inversion, prepares `his.*`).
- With HIS P1: payment methods/treasuries/cashier shifts hoist; unified charge-posting service (also kills the AR-account selection logic copied 3× inside LIS); named price lists + insurance generalization (largest item).
- Later/cheap: general tax service (VAT is `lis.*` settings today); print contract rename-only (already generalized in shared/print).
- Already platform-level, no move needed: permissions/roles (+company isolation), per-company/branch sequences, settings, business partners + auto accounts, guarded journal-entry action, branches, attachments, notifications.

---

## 4. Phase-0 work items and status

Spec status block (all unchecked as of reading — approved, not started):

| Item | Content | Size | Behavioral risk | Gate | Status |
|---|---|---|---|---|---|
| W1-1 | Hoist `Modules/LIS/app/Support/LisDataScope.php` → `Modules/Core/app/Support/DataScope.php`; LIS class kept as compatibility shim; all 14 call sites byte-identical | S | None | LIS suites | ☐ not started |
| W1-2 | Inert nullable `employee_id` on `lab_doctors` (FK→`employees.id`, nullOnDelete); `employees.user_id` already exists (HRM migration 2026_03_01_100003:18) → doctor→employee→user chain complete | S | Zero | `LabDoctorApiTest` (11) | ☐ not started |
| W1-3 | Extract `CreateLabRequest` action (byte-identical) + inert `encounter_id` on `lab_requests` | M | Low | `LabRequestApiTest` (42) + `ExternalLabPortalB2bTest` (43) + B2bCourier (25) + B2bRejectionVisibility (6) | ☐ not started |
| W1-4 | `PermissionDependencyRegistry` in Core; modules register dependency maps/presets/home-pages (LIS contributor registered from `LISServiceProvider` boot, `class_exists` guard); response shapes byte-identical | S/M | Zero | RoleApi (20) + RoleLifecycle (11) + RoleTenantScoping (16) + LisPresetLoginMatrix (8) | ☐ not started |
| W2-1 | B2B hardening, 8 verified items + shared-entity promotion (see below) | M | Low — intended changes only | `LabPatientApiTest` (20) + new B2B-guard tests + portal suites | ☐ not started; **item 8 blocked on owner policy** |
| W2-2 | Module gating **LOG-ONLY** middleware (route→module, log on disabled-module hit, NO blocking; review after ~2 weeks) | S | None (no blocking) | log review | ☐ not started |

W2-1's 8 hardening items (all locations verified 2026-06-10):
1. Dashboard KPI `lis_active_patients` counts B2B — fix `whereNull('external_lab_id')` at `Modules/Core/app/Services/DashboardService.php:191-195` (*intended number change*).
2. Six FE pickers → internal-only via existing `internalOnly` param of `LisPatientService.search()`: `request-wizard-v2.component.ts:594`, legacy `lis-request-wizard.component.ts:544`, `lis-layout.component.ts:441` (topbar search), visits component :105, referrals component :164, merge picker :664.
3. **Merge guard (worst gap)**: `LabPatientController::merge` (~:238) currently allows merging a B2B patient into an internal one (corrupts the client lab's records) — reject on `external_lab_id` mismatch, 422 translated, FE dialog shipped together.
4. Company-scope every `exists:lab_patients,id` rule (StoreLabRequestRequest:24, StoreLabVisitRequest:22, StoreLabInvoiceRequest:21, StoreLabReferralRequest:20, MergeLabPatientRequest:17-18) + consistency rule (request patient's `external_lab_id` must match request's or be NULL).
5. Policy guard: staff update/destroy/regeneratePortalLink on B2B patient → 403; FE hides actions for badge rows.
6. DB partition guard: plain `ext_scope_key` column (= `external_lab_id ?? 0`, model-maintained) + `UNIQUE(company_id, ext_scope_key, national_id)` — closes the app-only race documented in migration 2026_06_03_010000.
7. Named scopes `scopeInternal()` / `scopeForLab($labId)`; HIS will consume `internal()`.
8. **OWNER POLICY PENDING**: B2B patients currently get patient-portal link tokens (`LabPatient.php:60-65` booted hook) — keep or stop?

W2-2 measured facts: BE never reads `system.enabled_modules` (sole hit = license DTO `app/DTOs/License/HealthReport.php:49`); FE-only enforcement via `moduleGuard` (`system-modules.service.ts`, `auth.guard.ts:162`). Dev company 4 setting `[core,accounting,cmms,lis]` vs live data sales_invoices=6, purchase_bills=2, warehouses=7, employees=50, pos_sessions=2 → **immediate enforcement would break working flows**, hence log-only.

Test gate inventory: combined quick gate `--filter="B2b|Role|LabReportPdfParity|LisPresetLoginMatrix"` (144+ passed last run); FE has NO test runner except `npm run test:reports` (30-case print contract harness). Execution style: max-parallel agents with strict file ownership, Codex review after each milestone, BE `/home/moonui/moon-erp-be` branch `lis/maintenance-completion`, FE `/home/moonui/public_html/moon-erp` branch `fix/doctor-revamp`, Laravel **12**.

---

## 5. Open questions the documents left unanswered

1. B2B patient portal-link policy (W2-1 item 8) — explicitly awaiting owner answer.
2. Module-gating enforcement — decision deferred until ~2 weeks of log data + company-settings cleanup.
3. Encounter/Folio detailed schema — principle approved, tables deliberately not designed/created until HIS kickoff.
4. Clinical depth: HIS v1 is **explicitly administrative-financial** (operations + billing); full EMR/clinical documentation is "a separate later decision" (Risk #7: EMR scope creep).
5. Practitioner registry full hoist to Core — evaluated only if non-health modules ever need it.
6. Top risks register (8): B2B leakage, patient identity split, creeping duplication (the four-print-engines lesson), accounting double-posting, practitioner triplication, no module-dependency enforcement, EMR scope creep, hosting fragility (`fix-autoload.php:10` knows only 5 modules — must be updated for HIS).

---

## 6. What the OBGY acquisition might change or enrich — all (inference)

The HMS report and Phase-0 spec contain **zero references to OBGY**. Intersections, marked as inference, based on `/home/amrtechogate/public_html/obgy-erp-analysis.md`:

1. **(inference) OBGY's domain knowledge lands exactly on the two biggest HMS gaps**: appointments (scored 1/10 — OBGY has a real visits/appointments module: `visits`, `visit_periods`, queueing workflows) and outpatient clinical content (OBGY has full specialty sheets: antenatal `ancsheet`/`ancnewvisit`, IVF/ICSI, ultrasound, prescriptions). These can inform the P1 appointments engine and the Encounter design as a domain model — not as code.
2. **(inference) OBGY cannot be the HIS core technically**: its own analysis documents a legacy custom "aw framework" with confirmed widespread SQL injection, no declared FKs anywhere, latin1/MyISAM remnants, and dead/parallel duplicate table paths — irreconcilable with the settled `Modules/HIS` Laravel-12 decision and the LIS-untouchable constraint. Its value is workflows + data migration, per its own "Migration-Relevant Conclusions" section.
3. **(inference) D1/D1b absorb OBGY patients unchanged**: the upgraded shared `lab_patients` entity is the natural migration target for OBGY's `patients` table; nothing in OBGY justifies reopening the single-registry or B2B decisions.
4. **(inference) D3's contract generalizes to OBGY services**: the "service-performer called via Action + `encounter_id`" pattern designed for lab/radiology fits ultrasound and gynecological procedures.
5. **(inference) New question OBGY raises that the docs don't answer**: is OB/GYN a *specialty layer* inside P1 outpatient (clinical sheets on top of the generic Encounter) or a separate specialty module? D2 (one module, no premature splitting) and Risk #7 (EMR scope creep — v1 admin-financial) both push toward "inside HIS, clinical depth deferred", but the docs themselves do not decide this. OBGY's rich clinical documentation would, if adopted early, directly collide with the v1 admin-financial scoping — the new vision must reconcile that tension explicitly.
6. **(inference) Roadmap pressure**: OBGY is outpatient-only (no beds/ADT), so it strengthens the case that P1 (outpatient) delivers sellable value alone, and could supply the first real-world P1 pilot tenant/dataset; it contributes nothing to P2 hoteling.
