# Moon ERP × LIS × HIS × OBGY — Integration Vision & Architecture Decision (Companion MD)

> **Purpose:** Machine-consumable technical companion to `his-integration-vision-report.html`.
> Produced 2026-06-11 by a 16-agent study (9 evidence readers → 3 competing architecture proposals →
> 3 adversarial judges → chief-architect synthesis).
> **Decision:** P-PLATFORM-FIRST amended with must-steal items ("Platform-First, OBGY-Seeded"):
> OBGY is NOT the HIS core; `Modules/HIS` = thin generic clinical platform (MPI on lab_patients,
> Encounter, Folio, orders hub, forms engine, surgery, ADT); `Modules/Obgy` = first specialty plug-in.
> Binding constraints honored: HIS one module; HIS→LIS/Accounting/HRM forward-only; LIS never imports
> HIS (events backward); LIS untouchable (perf+behavior); B2B patients stay in lab_patients.

## Index
| File | Content |
|---|---|
| 01 | Moon ERP Core & 15 modules — platform services inventory |
| 02 | LIS integration surface — events, actions, inert HIS columns |
| 03 | Accounting / HRM / Inventory / NPHIES backbone contracts |
| 04 | Angular frontend architecture & module conventions |
| 05 | Settled HMS decisions & Phase-0 spec (binding) |
| 06 | OBGY reusable assets — buckets A (generic) / B (specialty) / C (discard) |
| 07 | Target HIS scope for any hospital — capability matrix |
| 08 | Patient/Encounter/Folio data spine |
| 09 | Product & market packaging (4 tiers) |
| p-* | The 3 competing proposals (platform-first / obgy-first / erp-maximalist) |
| 20 | FINAL SYNTHESIS — module map, schemas, event contracts, roadmap (execution spec) |

---


# 01 — Moon ERP Core & Modules (Backend Platform Map)

Source tree: `/home/moonui/moon-erp-be` (Laravel 12, `nwidart/laravel-modules`, Angular FE).
All 15 modules enabled in `/home/moonui/moon-erp-be/modules_statuses.json`: Core, Accounting, Inventory, Sales, Purchases, POS, HRM, LIS, WebStore, Production, CRM, CMMS, QMS, NPHIES, EInvoicing.

## 1. Application-level platform (root `app/`)

Files: `/home/moonui/moon-erp-be/app/Models/{BaseModel,Branch,Company,User,CentralSyncQueue}.php`, `app/Traits/`, `app/Services/`.

- **`BaseModel`** (`app/Models/BaseModel.php`) — abstract base used by all module models. Composes:
  - `TenantAware` (`app/Traits/TenantAware.php`): auto-stamps `company_id` from `auth()->user()` on `creating`; `scopeTenant()` filters by current user's company. Multi-company tenancy is column-based (`company_id`), not DB-per-tenant.
  - `Auditable` (`app/Traits/Auditable.php`): full dirty-only activity logging via `spatie/laravel-activitylog` (`logAll()->logOnlyDirty()`).
  - `SoftDeletes`, `HasArabicContent` (`getLocalizedName()`, `name`/`name_ar` bilingual convention used everywhere).
- **`User`** (`app/Models/User.php`) — Sanctum (`HasApiTokens`), `Spatie\Permission\Traits\HasRoles`, activity-logged, quota-tracked (`TracksQuota`, `quotaKey = 'max_users'`). `branches()` belongsToMany with pivot `is_primary`; `primaryBranch()` resolves the stamping branch (explicit primary, else first assigned).
- **`Branch`** (`app/Models/Branch.php`) — per-company branches (`is_main`, geo, bilingual address, signature/stamp images for printouts), quota `max_branches`, `Branch::mainFor($companyId)` fallback used by the B2B portal (no staff user).
- **`Company`** (`app/Models/Company.php`) — tenant root: currency, locale, timezone, `fiscal_year_start_month`, `settings` JSON.
- **SaaS layer** (`app/Services/`): `MoonLicenseClient`, `QuotaTrackingService` (+ `TracksQuota` trait), `CentralSyncService`/`CentralSyncQueue`, `UpdateManager`, `RemoteCommandExecutor`, `HealthReportCollector`, `SystemRequirementsChecker`, Installer. Moon ERP is operated as licensed multi-instance SaaS with quota enforcement.

## 2. Modules/Core deep dive

Path: `/home/moonui/moon-erp-be/Modules/Core/` — 39 migrations, 31 models. App dirs: `Actions, Contracts, Enums, Events, Http, Jobs, Listeners, Models, Notifications, Providers, Services, Support`.

### 2.1 Users / Roles / Permissions
- RBAC = `spatie/laravel-permission`. `Modules\Core\Models\Role` extends `Spatie\Permission\Models\Role` + activity log.
- Role/permission changes are audited via `Modules\Core\Providers\EventServiceProvider` listening to Spatie's `PermissionAttached/Detached`, `RoleAttached/Detached` → `Listeners\LogRolePermissionChange`.
- Roles gained columns by migration:
  - `2026_05_30_100000_add_data_scope_to_roles_table.php` → `roles.data_scope` (nullable string) + `roles.home_page` (role landing page).
  - `2026_06_10_150000_add_company_id_to_roles_table.php` → roles are now company-scoped (tenant-aware RBAC).
- **`PermissionDependencyRegistry`** (`Modules/Core/app/Support/PermissionDependencyRegistry.php`): plugin point where each module registers a permission-dependency contributor by prefix at boot (`register('lis', LisPermissionDependencies::class)` — done in `Modules/LIS/app/Providers/LISServiceProvider.php`). Contributor contract: `expand()` (close permission set over dependency edges on role save), `mapFor()` (auto-select in roles UI), `presets()` (one-click role bundles), `groupOrder()` (workflow-ordered groups). Explicitly built to fix a Core→LIS layering inversion; an HIS/OBGY module would register `his`/`obgy` prefixes the same way.
- `RoleSaveService` in `Core/app/Services` persists roles with dependency expansion.

### 2.2 Branch DataScope pattern
`Modules/Core/app/Support/DataScope.php` (docblock says "Multi-branch / role data-visibility scoping for LIS list endpoints"):
- `effectiveScope($user)`: broadest `data_scope` across the user's roles — `all > branch > own`; null/absent = `all` (backward compatible; restricted user must hold exactly one restricted role).
- `apply(Builder, $user, Request, $opts)`:
  - `own` → `where created_by = user.id`;
  - `branch` → `whereIn branch_id (user branches) OR branch_id IS NULL` (legacy-row leak kept deliberately until backfill), with optional narrowing via `?branch_id=` or `X-Branch-Id` header (Angular FE branch selector sends header globally);
  - `all` → unrestricted, honours explicit branch filter.
- `operatingBranchId($user)`: new records stamped with the user's single primary branch regardless of viewing filter.
- Consumed today by LIS controllers/services: `LabRequestController`, `LabSampleController`, `LabResultController`, `LabInvoiceController`, `LabPaymentController`, `CreateLabRequest`, `LabSampleService`, etc. Directly reusable for HIS/OBGY list endpoints.

### 2.3 Master data: BusinessPartner (no core Patient)
- **`BusinessPartner`** (`Modules/Core/app/Models/BusinessPartner.php`, table `business_partners`): the single party master. `company_id`, `branch_id`, `code`, bilingual name, enums `PartnerType/PartnerClassification/PartnerStatus`, flags `is_customer`/`is_supplier`, tax/CR numbers, credit limit, payment terms. Children: `bp_contacts`, `bp_addresses`, `debt_changes` (linked to accounting journal entries via `add_journal_entry_id_to_debt_changes_table`).
- **Extension-table pattern** over the partner master:
  - Accounting: `AccBpExt` (`accExt()` hasOne, `partner_id`) — partner GL accounts.
  - CRM: `crm_customer_ext` (`Modules/CRM/app/Models/CrmCustomerExt.php`, `partner_id`) — customer_type, industry, source, lifecycle_stage, tags.
- **There is NO Patient entity in Core.** The only patient model in the whole ERP is `Modules\LIS\Models\LabPatient` (table `lab_patients`): `mrn`, bilingual names, DOB/gender/blood group, national ID/passport/nationality, `insurance_info` JSON, `medical_history`, `partner_id` → BusinessPartner, `insurance_contract_id`, `external_lab_id`, patient-portal tokens (`portal_link_token` auto-generated `Str::random(64)` on create for QR/WhatsApp links).
- Other clinical actors also resolve to BusinessPartner via `partner_id`: `LabDoctor`, `LabInsuranceContract`, `LabMonthlyInsuranceInvoice`, `LabExternalLab`.
- Product master also lives in Core: `Product`, `ProductVariant`, `ProductUnit`, `ProductPrice`, `ProductPricingTier`, `ProductSerial`, `Brand`, `ProductCategory`, `UnitGroup/Unit` — reusable for drug/consumable catalogs.

### 2.4 Shared platform services (Core/app/Services + Models)
- `SequenceService` — document numbering per company/module/entity, optional `per_branch` counters, fiscal-year/month reset (`sequences`, `sequence_counters`).
- `SettingsService` (singleton) + `SettingDefinition` — scoped settings, `SettingScope` enum: `global | company | branch | user`. LIS seeds its own definitions (`LabSettingDefinitionSeeder`).
- `ApprovalWorkflowService` — multi-level approvals (`approval_workflows`, `approval_workflow_levels`, `approval_logs`); **`ApprovalModule` enum currently only `Sales` and `Purchases`** — needs extension for clinical document types.
- `Attachment` model (`attachments` table, `morphTo`) + root trait `HasAttachments` (`morphMany`) — polymorphic file attachments for any entity.
- AI layer: `AiChatService`, `AiSettingsService`, `AiUsageService` with tables `ai_settings`, `ai_user_preferences`, `ai_usage_logs`, `ai_response_cache`; HRM already builds on it (`HrAiAnalysis`).
- `NotificationService`, `DashboardService`, `HijriDateService` (Hijri calendar — relevant for KSA/clinical contexts), `VariantGeneratorService`, `DatabaseBackup` model + migrations.
- Geo lookups: `Country`, `Governorate` (WebStore adds `City`).
- Contracts/DI: `BusinessPartnerServiceInterface` and `ProductServiceInterface` bound in `CoreServiceProvider::register()` — other modules consume partner/product behavior through interfaces.

### 2.5 Module registration
`CoreServiceProvider` (pattern repeated in every module): boots translations, recursive config merge, views, `loadMigrationsFrom(module_path(..., 'database/migrations'))`; registers module `EventServiceProvider` + `RouteServiceProvider`. Enable/disable per module via `modules_statuses.json`.

### 2.6 Events bus
- Core domain events: `BusinessPartnerCreated/Updated/Frozen`, `ProductCreated/Updated` (dispatched from `Core/app/Actions/CreateBusinessPartner`, `UpdateBusinessPartner`, `CreateProduct`).
- Cross-module subscription example: `Modules/Accounting/app/Providers/EventServiceProvider.php` maps `BusinessPartnerCreated/Updated` → `CreatePartnerAccounts` (auto-creates partner GL accounts).
- LIS events (10): `LabSampleCollected/Received`, `AllSamplesCollected`, `LabResultEntered/Released/Corrected/Retracted`, `LabRequestCompleted/Invalidated`, `CriticalResultDetected`; 11 LIS listeners chain the workflow (e.g. `GenerateResultsOnSampleReceived`, `PostInvoiceItemOnResultRelease`, `ConsumeReagentOnResultEntry`, `CreateCriticalAlert`).
- Other modules with events: Accounting (`JournalEntryPosted/Approved/Cancelled`, `FiscalYearClosed`, `PeriodClosed`), EInvoicing (`InvoicePosted`, `EInvoiceStatusChanged`, ...), Sales/Purchases/WebStore (`StoreOrderPlaced`).
- **Financial integration is NOT events-only**: `Modules/LIS/app/Actions/PostLabInvoice.php` directly invokes `Modules\Accounting\Actions\CreateJournalEntry` (standard JE + insurance-invoice JE variants) — a sanctioned synchronous Action-call pattern for GL posting.

## 3. Per-module map (migrations / models counted from `database/migrations` and `app/Models`)

| Module | Purpose (one line) | Key entities | Mig/Models |
|---|---|---|---|
| Core | Master data + platform services (partners, products, settings, sequences, approvals, AI, RBAC support) | BusinessPartner, Product, Role, Sequence, Setting, ApprovalWorkflow, Attachment | 39/31 |
| Accounting | Full GL: chart, journals, fiscal calendar, banks, checks, assets, budgets, cost centers | Account, JournalEntry(+Line), FiscalYear/Period, BankAccount/Reconciliation, FixedAsset, Budget, PaymentVoucher, ReceiptVoucher, Currency/ExchangeRate, TaxRate | 51/44 |
| LIS | Production lab system: full request→sample→result→invoice lifecycle, QC, machine interfacing, insurance, B2B referral, commissions, patient portal | LabPatient, LabRequest, LabSample, LabResult, LabInvestigation(+NormalRange/Panel), LabInvoice/Payment, LabMachine(+TestMapping/Result), LabQcLot/Result, LabInsuranceContract, LabExternalLab(+Referral), LabDoctorCommission, LisCashierSession | 137/66 |
| HRM | HR: org, biometric attendance, leave, payroll, loans, recruitment, training, performance | Employee, Department, Position, Attendance, BiometricDevice, LeaveRequest/Balance, Payroll(+Items), SalaryStructure, Candidate/JobOpening, PerformanceReview, TrainingProgram | 47/40 |
| Inventory | Warehousing: receipts/issues/transfers/adjustments/counts, movements, cost layers | Warehouse, StockBalance, InventoryMovement, InventoryCostLayer, InventoryReceipt/Issue/Transfer/Adjustment/Count | 15/14 |
| Sales | Quote→order→delivery→invoice→payment, returns, schedules, commissions | SalesQuotation, SalesOrder, SalesDeliveryNote, SalesInvoice, SalesPayment(+Schedule), SalesReturn, SalesCommission(+Rule) | 16/14 |
| Purchases | PR→PO→GRN→bill→payment, returns, supplier price lists | PurchaseRequest, PurchaseOrder, PurchaseGrn, PurchaseBill, PurchasePayment(+Schedule), PurchaseReturn, SupplierPriceList | 14/13 |
| POS | POS terminals, cash sessions, coupons, held orders | POSTerminal, POSSession(+Payment), POSCoupon, POSHeldOrder | 7/5 |
| WebStore | Full e-commerce storefront incl. prescriptions upload | StoreCustomer(+Otp/Address), StoreCart, StoreOrder(+Status flow), StoreCoupon, StorePrescription, ProductImage/Rating/Tag | 25/29 |
| CRM | Customer-relationship extension over BusinessPartner: tickets, interactions, SLA | CrmCustomerExt, CrmTicket(+Comment), CrmCustomerInteraction, CrmSlaPolicy, CrmCustomerTag | 1/6 |
| CMMS | Maintenance: assets, work orders (labor/parts), PM schedules — relevant for medical equipment | CmmsAsset(+Category), CmmsWorkOrder(+Labor/Part), CmmsPmSchedule | 1/6 |
| Production | Manufacturing: BOMs, routing operations, production orders | BillOfMaterials, BomComponent/Operation, ProductionOrder(+Material/Operation), ProductionCenter | 3/7 |
| QMS | Quality: inspection plans/results, non-conformance, CAPA, supplier evaluation | InspectionPlan, Inspection(+Criteria/ResultRecord), NonConformance, CapaAction, SupplierEvaluation | 1/7 |
| NPHIES | Saudi NPHIES health-insurance platform integration (pre-auth, claims transactions) | NphiesConfig, NphiesPreauth, NphiesTransaction | 6/3 |
| EInvoicing | Regulatory e-invoicing documents and submissions | EInvoiceConfig, EInvoiceDocument, EInvoiceSubmission | 3/3 |

## 4. What HIS / OBGY inherit for free (answer to the study question)

1. Multi-company tenancy (`TenantAware`/`company_id`) and full audit trail (`Auditable`) by simply extending `BaseModel`.
2. Branches + role `data_scope` (`all/branch/own`) via `DataScope::apply()` — already battle-tested on LIS clinical endpoints.
3. Spatie RBAC with company-scoped roles, audit of role/permission changes, and the `PermissionDependencyRegistry` contributor pattern for module-owned permission graphs and role presets.
4. Unified party master (`BusinessPartner` + extension tables) — doctors, insurers, referral facilities already modeled this way in LIS; OBGY referring doctors/insurers map directly.
5. Document numbering (`SequenceService`), scoped settings (`SettingsService`), polymorphic attachments, notifications, AI services, Hijri dates, DB backups.
6. Financial backbone: direct GL posting pattern (`PostLabInvoice` → `CreateJournalEntry`), receivables (`debt_changes`), insurance monthly invoicing (LIS), NPHIES claims module, EInvoicing.
7. Adjacent operational modules: Inventory (drug/consumable stock), CMMS (medical equipment maintenance), HRM (clinical staff, shifts), QMS (clinical quality/CAPA), WebStore/portal patterns (patient portal tokens already in `LabPatient`).
8. Eventing conventions (domain events + listeners per module) for clinical workflow chaining, proven by 10 LIS events / 11 listeners.

### Gaps a HIS build must close (inference, marked)
- No core `Patient` entity — `lab_patients` is LIS-private; HIS needs a promoted shared patient master (or a Patients core module) with LIS migration/linking strategy. *(inference)*
- `ApprovalModule` enum covers only Sales/Purchases — clinical approvals need enum/route extension. *(inference)*
- DataScope's "broadest role wins" rule requires role-assignment discipline for clinical confidentiality. *(documented caveat in the class docblock)*
- No appointment/encounter/ADT concepts anywhere in the codebase today (`LabVisit` is lab-local). *(inference)*

---


# 02 — LIS Module Deep-Map: The Proven Clinical-Module Template Inside Moon ERP

Source tree: `/home/moonui/moon-erp-be/Modules/LIS` (Laravel 12, nwidart module). All statements below are traceable to files read on 2026-06-10/11.

## 1. Scale snapshot

| Artifact | Count | Location |
|---|---|---|
| Eloquent models | 66 | `Modules/LIS/app/Models/` |
| Migrations | 137 | `Modules/LIS/database/migrations/` |
| Domain events | 10 | `Modules/LIS/app/Events/` |
| Event listeners | 11 | `Modules/LIS/app/Listeners/` |
| Services | 26 | `Modules/LIS/app/Services/` |
| HTTP controllers | 66 | `Modules/LIS/app/Http/Controllers/` |
| Permission dependency edges | 36 explicit cross-resource edges | `Modules/LIS/app/Support/LisPermissionDependencies.php` |

## 2. Entity graph (read from `app/Models`)

### Clinical core
- **`LabPatient`** (`lab_patients`) — MRN, demographics (`name_ar`/`name_en`), `national_id`, `medical_history`, `insurance_info` (JSON). Cross-module links: `partner_id → Modules\Core\Models\BusinessPartner`, `insurance_contract_id → LabInsuranceContract`, `external_lab_id → LabExternalLab` (B2B-owned patients). Patient-portal: `portal_token` + permanent `portal_link_token` auto-generated in `booted()` (`Str::random(64)`) for QR/WhatsApp result links. Note: LIS owns its own patient table — there is **no central ERP patient registry yet**; `partner_id` is the bridge to Core.
- **`LabDoctor`** (`lab_doctors`) — referring doctor master. Links: `department_id → Modules\HRM\Models\Department`, `partner_id → BusinessPartner`, `price_list_id → LabPriceList` (doctor-preferred pricing), commission config (`commission_type/value`, `commission_account_id`/`commission_payable_account_id → Modules\Accounting\Models\Account`), `is_internal`, `specialty_id → LabDoctorSpecialty`. **NEW inert column `employee_id`** (see §7).
- **`LabRequest`** (`lab_requests`) — the order header. `request_number` (Core `SequenceService`), `patient_id`, `doctor_id`, `branch_id`, `priority` (`RequestPriority`), `status` (`RequestStatus`), `source` (`RequestSource`: `walk_in | in_patient | emergency | external` — `in_patient`/`emergency` already anticipate hospital ordering), money totals, insurance fields (`insurance_contract_id`, `coverage_percentage`, `patient_share_total`, `insurance_share_total`), `external_lab_id`, `price_list_id`, `ready_for_pickup_at`. **NEW inert column `encounter_id`** (see §7).
- **`LabRequestInvestigation`** — order lines (price, discount, net, section routing via `section_id`, `source_group`).
- **`LabSample`** (`lab_samples`) — specimen with full custody timeline columns (`picked_up_/collected_/received_/delivered_/rejected_/accepted_/processing_started_/processing_completed_/ready_for_result_ + _at/_by` pairs), parent/child split samples (`parent_id`), defer support, external-referral fields (`is_external`, `external_lab_id`, `external_referral_id`, `external_status`), `branch_id`. Authoritative chain-of-custody in `LabSampleCustodyLog`.
- **`LabResult`** (`lab_results`) — result row per investigation. Status machine (`ResultStatus`: `pending → entered → validated → approved → released`, plus `retracted | invalidated | entered_in_error`), abnormal/critical flags + ranges, delta check, three comment fields, `entry_source` (`workflow | validation | external_lab | machine`), machine metadata (`machine_id`, `raw_data`), full who/when stamps (`entered_/validated_/approved_/released_by/at`), amendment/retraction lineage (`retracted_from_id`, `retest_of`, `amended_from_value`). `booted()` auto-stamps `branch_id` from the parent request. Polymorphic `Attachment` (Core) for file results; `LabHistopathResult` 1:1 for histopathology.

### Billing
- **`LabInvoice`** (`lab_invoices`) — `invoice_type` enum (`standard | patient_invoice | insurance_invoice | external_lab_payable | external_lab_receivable`), `journal_entry_id`, `cogs_journal_entry_id`, `cancel_journal_entry_id`, `total_cost`, NPHIES claim fields (`nphies_claim_status`, `nphies_preauth_ref`, ...), posted/cancelled stamps. + `LabInvoiceItem`, `LabPayment`, `LabPaymentMethod`, `LisCashierSession` (cashier shift sessions).
- Insurance: `LabInsuranceContract` (+ per-investigation coverage `LabInsuranceContractInvestigation`), `LabMonthlyInsuranceInvoice` (monthly claims).
- Doctor commissions: `LabDoctorCommissionRule`, `LabDoctorCommission`, `LabCommissionSettlement`.

### External labs / B2B
- `LabExternalLab` (B2B client or reference lab; `pickup_config` JSON), `LabExternalLabPricing` + price lists, `LabExternalLabReferral`/`LabExternalLabReferralTest` (send-out workflow), `LabMonthlyExternalLabInvoice`, `LabExternalLabPayment`/`LabExternalLabPaymentAllocation`.
- **`LabExternalLabCourier`** (`lab_external_lab_couriers`) — courier eligibility pivot (which users may courier for which lab), created in migration `2026_06_09_150000_add_b2b_courier_pickup_foundation.php`. With pickup enabled, portal samples are born `at_external_lab` → courier `in_transit` → handover `collected`; reception/kanban unchanged (they only ever see samples once `collected`). Custody stays authoritative in `lab_sample_custody_logs`.

### Catalog / lab operations
- `LabInvestigation` (LOINC code fields, panels via `LabInvestigationPanelMember`, formulas with dependency columns, outsourced flags, gender applicability, NAFIS fields), `LabSection`, `LabSpecimenType`, `LabUnit`, `LabInvestigationNormalRange`, `LabPackage`, `LabPriceList`/`LabPriceListItem`.
- Machines (LIS interfacing): `LabMachine`, `LabDeviceModel`(+Test), `LabMachineTestMapping`, `LabMachineResult`, `LabMachineCommunicationLog`, `LabAutoVerifyRule` (+ `AutoVerificationService`).
- QC: `LabQcLot`, `LabQcResult`, `WestgardRuleService`. Inventory: reagents (`LabReagent*`), consumables. Compliance: `LabComplianceChecklist`, `LabRetentionPolicy`, `LabResultAuditLog`, `LabResultPublishLog`.

## 3. Request lifecycle (reception → release → invoice)

1. **Reception** — `Modules\LIS\Actions\CreateLabRequest::handle()` (docblock: *"HMS Phase-0 W1-3 — byte-identical extraction of LabRequestController::store so HIS can create lab requests through the same path. NO behavior change."*). Generates `request_number` via Core `SequenceService->generateNext($companyId,'lis','lab_request')`, stamps `branch_id` from `DataScope::operatingBranchId()`, resolves prices by priority *explicit → external-lab inbound pricing → doctor preferred price list → investigation default*. The reception wizard also bills and collects (creates invoice + payment) — encoded in `LisPermissionDependencies::EDGES['lis.requests.create']`.
2. **Sampling** — `LabSampleService`: collect dispatches `LabSampleCollected` (→ `UpdateRequestStatusOnSampleCollection`; request → `sample_collected`); receive dispatches `LabSampleReceived` (→ `GenerateResultsOnSampleReceived` creates pending `lab_results`, `CreateSectionProcessingOnSampleReceived` opens section worklist rows in `lab_sample_section_processing`, `CreateReferralForOutsourcedInvestigations` auto-creates send-out referrals).
3. **Worklists / processing** — section kanban (`KanbanRejectService`, `WorklistContextService`, `ReceptionWorklistService`, `SampleInvestigationService` + `lab_sample_investigations` state table), machine results inbox (`MachineResultMatchingService`, auto-verify rules).
4. **Result entry** — manual (`LabResultController` dispatches `LabResultEntered`, and `CriticalResultDetected` when critical → `CreateCriticalAlert`) or machine (`LabMachineResultController` same events). `LabResultEntered` also triggers `ConsumeReagentOnResultEntry` (inventory) and `UpdateRequestStatusOnResultEntry` (request → `in_progress`).
5. **Validate → approve → release** — `ResultStatus` ladder with separate permissions (`lis.results.validate/approve/release`, plus `release_unpaid` as a guarded override). Release dispatches **`LabResultReleased`** (from `LabResultController` and re-dispatched per promoted panel row by `RollUpPanelAndFormulaOnRelease`).
6. **On release** (ordered listeners in `app/Providers/EventServiceProvider.php`): `RollUpPanelAndFormulaOnRelease` → `AutoPublishOnResultRelease` (patient portal / report publication via `LabResultPublishService`, logged in `lab_result_publish_logs`) → `PostInvoiceItemOnResultRelease` (B2B per-item accounting posting, idempotent) → `UpdateRequestStatusOnResultRelease` flips the request to `completed` (or `partial_result`) and `LabWorkflowService` dispatches **`LabRequestCompleted`**.
7. **Post-release corrections** — first-class retract/correct/invalidate flow: events `LabResultRetracted`, `LabResultCorrected`, `LabRequestInvalidated`; lineage columns on `lab_results`; full audit in `lab_result_audit_logs`.

## 4. Events emitted (the integration surface HIS/OBGY can consume)

`Modules/LIS/app/Events/` — plain Laravel events carrying the full model:
`LabSampleCollected`, `LabSampleReceived`, `AllSamplesCollected`, `LabResultEntered`, `CriticalResultDetected`, `LabResultReleased(LabResult $result)`, `LabResultCorrected`, `LabResultRetracted`, `LabRequestCompleted(LabRequest $request)`, `LabRequestInvalidated`.

Dispatch points verified: `LabWorkflowService` (lines 40/75/120: `AllSamplesCollected`, `LabRequestCompleted` x2), `LabSampleService` (56/115), `LabResultController` (389/392/516/1058), `LabMachineResultController` (238/240), `RollUpPanelAndFormulaOnRelease` (188). Wiring in `EventServiceProvider::$listen` — listener order is deliberate and commented (roll-up before auto-publish; status update after roll-up).

## 5. Accounting integration (how LIS invoices post)

- `Modules\LIS\Actions\PostLabInvoice` — transactional. Calls `Modules\Accounting\Actions\CreateJournalEntry`; resolves accounts from Core `SettingsService` keys `lis.receivable_account_id`, `lis.revenue_account_id`, `lis.tax_payable_account_id`, preferring a **partner-specific AR account** via `Modules\Accounting\Models\AccBpExt`. Per-`invoice_type` JE builders: `createStandardJE`, `createInsuranceInvoiceJE` (contract-specific accounts), `createExternalLabOutboundJE`/`InboundJE`. Posts a **COGS journal entry** when `total_cost > 0` (`cogs_journal_entry_id`; costs from `LisCostCalculatorService`).
- Posting also auto-calculates **doctor commissions** (`DoctorCommissionService` → its own `CreateJournalEntry` calls + `CreatePartnerAccounts` listener reuse), skipped for insurance/external-lab invoice types to avoid double counting.
- Companion actions: `CancelLabInvoice` (reversal JE → `cancel_journal_entry_id`), `PostLabPayment` / `VoidLabPayment`, `PostLabInvoiceItem` (B2B per-item, used by the release listener), `RecordExternalLabPayment`.
- Pattern: **LIS keeps its own subledger tables and posts summarized JEs into the Accounting module — it never writes GL rows directly.**

## 6. Permissions and branch scoping

- **Permissions**: `lis.{resource}.{action}` strings enforced as controller middleware (`new Middleware('permission:lis.requests.create', only:['store'])` — `LabRequestController` lines 48-55). `Modules\LIS\Support\LisPermissionDependencies` encodes 36 explicit cross-resource prerequisite edges + a generic `{action}→view` rule, expanded as a transitive closure on role save and used to auto-select in the roles UI. **HMS Phase-0 W1-4**: `LISServiceProvider` registers this map into `Modules\Core\Support\PermissionDependencyRegistry::register('lis', ...)` — "fixes Core→LIS inversion", i.e., the registry is now a Core extension point any module (HIS, OBGY) can plug into.
- **Branch scoping**: `Modules\LIS\Support\LisDataScope` is now a deprecated shim extending `Modules\Core\Support\DataScope` (*"logic hoisted to Core (HMS Phase-0 W1-1); 14 existing call sites keep working"*). Core `DataScope` implements `own | branch | all` role-based data visibility (broadest-wins across roles), `?branch_id=`/`X-Branch-Id` header filtering, and `operatingBranchId()` (user's primary branch stamps every new record). Generic — ready for reuse by HIS/OBGY.

## 7. The inert HIS-readiness columns (HMS Phase-0)

- `2026_06_10_160000_add_encounter_id_to_lab_requests.php` — nullable unsigned `encounter_id` on `lab_requests`. Docblock: *"HIS encounters table does not exist yet — FK + consumers arrive at HIS kickoff (Phase-0 W1-3 inert column). Column is not read anywhere."* Confirmed inert: not in `LabRequest::$fillable`, zero references in `Modules/LIS/app/`.
- `2026_06_10_150000_add_employee_id_to_lab_doctors.php` — nullable `employee_id` + index on `lab_doctors`; FK to `employees` guarded by `Schema::hasTable('employees')` (HRM may not be migrated). Docblock: *"HMS Phase-0 W1-2 doctor→employee→user identity chain... No consumer yet."* Also confirmed absent from `LabDoctor::$fillable`.
- Together with W1-1 (DataScope hoist), W1-3 (CreateLabRequest action extraction) and W1-4 (PermissionDependencyRegistry), these form a deliberate, already-shipped **Phase-0 HIS preparation track inside LIS**.

## 8. How HIS / OBGY will order labs and receive results (the contract)

1. **Order**: call `Modules\LIS\Actions\CreateLabRequest` (extracted for exactly this purpose) with `source = in_patient` (enum value already exists) and set `lab_requests.encounter_id` once the HIS encounters table exists. Pricing, invoicing, insurance, sequencing, branch stamping all come for free.
2. **Identity**: patient bridged through `lab_patients.partner_id` (Core `BusinessPartner`) until/unless a central patient registry exists; ordering physician resolved through `lab_doctors.employee_id → employees → users`.
3. **Results back**: subscribe to `LabResultReleased` (per-test, includes critical flagging upstream via `CriticalResultDetected`) and `LabRequestCompleted` (order-level), plus `LabResultCorrected`/`LabResultRetracted` for amendments — the same in-process event bus LIS already uses internally for billing and publication side effects.
4. **Module shape to copy**: domain tables prefixed and self-contained; enums for every state machine; events + ordered listeners for side effects; Actions for cross-module entry points; posting to Accounting via `CreateJournalEntry` only; `lis.{res}.{action}` permissions with a dependency map registered to Core; Core `DataScope` for branch visibility.

## Key file citations
- Models: `/home/moonui/moon-erp-be/Modules/LIS/app/Models/{LabRequest,LabPatient,LabDoctor,LabSample,LabResult,LabInvoice,LabExternalLabCourier}.php`
- Events/wiring: `/home/moonui/moon-erp-be/Modules/LIS/app/Events/`, `/home/moonui/moon-erp-be/Modules/LIS/app/Providers/EventServiceProvider.php`
- Accounting: `/home/moonui/moon-erp-be/Modules/LIS/app/Actions/PostLabInvoice.php`, `.../Listeners/PostInvoiceItemOnResultRelease.php`, `.../Services/DoctorCommissionService.php`
- Permissions/scoping: `/home/moonui/moon-erp-be/Modules/LIS/app/Support/{LisPermissionDependencies,LisDataScope}.php`, `/home/moonui/moon-erp-be/Modules/Core/app/Support/DataScope.php`, `.../Providers/LISServiceProvider.php`
- HIS-readiness migrations: `.../database/migrations/2026_06_10_150000_add_employee_id_to_lab_doctors.php`, `2026_06_10_160000_add_encounter_id_to_lab_requests.php`; HIS entry point `.../app/Actions/CreateLabRequest.php`

---


# Moon ERP Business Backbone Survey: Accounting, HRM, Inventory, NPHIES

Scope: the four modules a hospital (HIS) module would reuse, surveyed at `/home/moonui/moon-erp-be/Modules/{Accounting,HRM,Inventory,NPHIES}`. All claims traceable to files read; inferences marked **(inference)**.

Module counts (from directory listings):

| Module | Models | Services | Migrations | Notable |
|---|---|---|---|---|
| Accounting | 44 | 27 | 51 | + 40 Actions, 5 Events, 1 Contract |
| HRM | 40 | 13 | 47 | biometric attendance sub-system |
| Inventory | 14 | 1 (`StockService`) | 15 | FIFO cost layers |
| NPHIES | 3 | 7 | 6 | FHIR client + builders |

---

## 1. Accounting — the central general ledger

### Key entities
- **Chart of accounts**: `accounts` table (`2026_02_10_100003_create_accounts_table.php`), model `Modules/Accounting/app/Models/Account.php`. Hierarchical (`parent_id`, `level`, `has_children`), bilingual (`name`/`name_ar`), `classification`, `nature`, `account_type` (Header/Detail enums), `status`, `is_system`, `zakat_classification`. Header accounts only aggregate; postings allowed on detail accounts only.
- **Journal**: `journal_entries` + `journal_entry_lines` (`2026_02_10_100007/100008`), models `JournalEntry.php`, `JournalEntryLine.php`. Status lifecycle `Draft → Approved → Posted / Cancelled` (helpers `isDraft()/isApproved()/isPosted()/isCancelled()`), balance check `isBalanced()` via `bccomp(total_debit, total_credit, 3)`. Crucial integration fields: `source_type`, `source_id` (added by `2026_02_23_080008_add_source_fields_to_journal_entries.php`), `partner_id` → `Modules\Core\Models\BusinessPartner` (`2026_03_10_080728`), `reversed_entry_id` for reversals, quota tracking via `TracksQuota` (`max_journal_entries`).
- **Fiscal calendar**: `fiscal_years`, `fiscal_periods`; period resolution logic in `Modules/Accounting/app/Services/JournalEntryService.php::resolvePeriod()` — Open period accepts any entry type; SoftClosed accepts adjustments only; Closed accepts nothing.
- **Treasury**: `bank_accounts`, `checks_issued`, `checks_received`, `petty_cash` + `petty_cash_transactions`, `account_transfers`; bank reconciliation (`bank_reconciliations`, `bank_statements`, `reconciliation_matches`, `reconciliation_rules`).
- **Vouchers**: `ReceiptVoucher`/`ReceiptVoucherLine`, `PaymentVoucher`/`PaymentVoucherLine` models with Approve/Cancel actions (`ApproveReceiptVoucher.php`, `ApprovePaymentVoucher.php`, ...).
- **Other**: multi-currency (`currencies`, `exchange_rates`, `CurrencyRevaluationService`), fixed assets + depreciation, budgets, entry templates + recurring entries, tax (`tax_rates`, `withholding_tax_rates`), Zakat (`zakat_calculations`), year-end closing, opening balances.

### Integration contract for HIS
- **Formal interface**: `Modules/Accounting/app/Contracts/AccountingServiceInterface.php`:
  - `createJournalEntry(JournalEntryDTO $dto): JournalEntry`
  - `getAccountBalance(int $companyId, int $accountId, ?string $asOfDate): array`
  - `getTrialBalance(int $companyId, ?string $asOfDate): Collection`
  - Implemented by `Modules/Accounting/app/Services/AccountingService.php` (delegates to `CreateJournalEntry` action and `GeneralLedgerService`).
- **The real workhorse**: `Modules/Accounting/app/Actions/CreateJournalEntry.php::execute(array $data, array $lines)`. Inside one DB transaction it: rejects header-account lines ("every programmatic flow (sales payments, vouchers, payroll, …) funnels through this action" — comment in file), resolves fiscal period, computes totals via bcmath, creates Draft entry + lines, handles multi-currency lines (explicit `exchange_rate` or `ExchangeRateService`, throws if unresolvable).
- **Auto account creation**: `Modules/Accounting/app/Services/AutoAccountService.php::createChildAccount(companyId, parentCode, name, nameAr)` — creates child accounts under a parent code, converting detail→header only when the parent has no journal lines. Listener `Modules/Accounting/app/Listeners/CreatePartnerAccounts.php` is injected directly by LIS (`PostLabInvoice` constructor).
- **Events** (`app/Events/`): `JournalEntryPosted`, `JournalEntryApproved`, `JournalEntryCancelled`, `PeriodClosed`, `FiscalYearClosed`.

### Proven consumers (auto-posting precedent)
Grep for `CreateJournalEntry`/`source_type` consumers outside Accounting found, among others:
- LIS: `Modules/LIS/app/Actions/PostLabInvoice.php` (revenue JE with `entry_type: 'lab_invoice'`, `source_type: 'lab_invoice'`, `source_id: invoice id`; separate COGS JE `entry_type: 'lab_cogs'`; plus `lab_insurance_invoice`, `external_lab_inbound/outbound` entry types), `PostLabPayment.php`, `PostLabInvoiceItem.php`, `CancelLabInvoice.php`, `RecordExternalLabPayment.php`, `DoctorCommissionService.php`.
- Sales: `PostSalesInvoice.php`, `PostSalesPayment.php`, `PostSalesReturn.php`; Purchases: `PostPurchaseBill.php`, `PostPurchasePayment.php`; HRM: `PayrollAccountingService.php`; Core: `CreateDebtChangeJournalEntry.php`.

**HIS implication**: hospital billing posts revenue/COGS/payment JEs through `CreateJournalEntry` with HIS-specific `entry_type`/`source_type` values. No ledger, voucher, treasury, or tax engine should be built in HIS.

---

## 2. HRM — staff, shifts, attendance, payroll

### Key entities
- **Employee**: `employees` table (`2026_03_01_100003_create_employees_table.php`), model `Modules/HRM/app/Models/Employee.php`. Fields: `employee_number` (unique), bilingual names, `user_id`, `department_id`, `position_id`, `branch_id`, `manager_id` (self-FK hierarchy), contract dates/type, `basic_salary`, `payment_method`, `bank_iban`, `national_id`, `passport_number`, plus nursing/special-needs flags (`is_nursing`, `nursing_start_date`, `is_special_needs` — added by `2026_04_11_100002`).
- **The user_id chain**: migration line — `$table->foreignId('user_id')->nullable()->unique()->constrained('users')->nullOnDelete();`. So: `users` (auth identity) → `employees.user_id` (1:1, nullable) → `department/position/branch/manager`. A HIS practitioner profile only needs an `employee_id` (or `user_id`) FK; no new staff tables. **(inference on HIS usage; the FK chain itself is verified)**
- **Org structure**: `departments` (hierarchical: `parent_id`, `manager_id`, `branch_id` — `Department.php`), `positions`.
- **Shifts**: `Modules/HRM/app/Models/Shift.php` — `start_time/end_time`, `break_duration_minutes`, `is_night_shift`, `grace_period_minutes`, `overtime_start_after_minutes`, late/early-leave thresholds and "counts full" rules, `flexibility_minutes`, and hospital-relevant `nursing_extra_minutes`, `special_needs_extra_minutes`, `presence_check_enabled`, `presence_check_interval`. Per-employee assignment via `shift_schedules` (`ShiftSchedule`).
- **Attendance**: `attendances` (`Attendance.php`: `check_in/check_out`, `worked_hours`, `overtime_hours`, `late_minutes`, `early_leave_minutes`, `check_in_source`, `device_name`, `punch_type`), corrections (`attendance_corrections`), presence checks, and a biometric sub-system: `biometric_devices`, `biometric_employee_mappings`, `biometric_sync_logs`, `attendance_punch_types` (migrations `2026_04_09_1000xx`), service `BiometricAttendanceService.php`.
- **Payroll**: `salary_components`, `salary_structures`, `payrolls` → `payroll_items` → `payroll_item_details`, `payroll_adjustments`, `employee_loans` + `loan_installments`, `eos_settlements`. Leave: `leave_types/balances/requests/approvals`. Also recruitment (job openings/candidates/offers), performance (KPIs/reviews), training.

### Integration contract for HIS
- `Modules/HRM/app/Services/PayrollAccountingService.php::postPayroll()` posts payroll to GL via `CreateJournalEntry` (`entry_type: 'payroll'`, `source_type: 'payroll'`), using settings `hrm.salary_expense_account_id` / `hrm.salary_payable_account_id`; `createPaymentEntry()` clears per-employee payable sub-accounts against a bank account.
- `Modules/HRM/app/Services/EmployeeAccountService.php::getOrCreateSubAccount()` auto-creates per-employee GL sub-accounts with code `{parent_code}-{employee_number}` — the pattern HIS can reuse for doctor commission/payable accounts. **(inference on reuse; mechanism verified)**

**HIS implication**: doctors/nurses/technicians are `Employee` rows linked to `users`; duty rosters = `shifts` + `shift_schedules` (nursing fields already exist); payroll and attendance must not be rebuilt.

---

## 3. Inventory — pharmacy & consumables backbone

### Key entities
- **Warehouse**: `warehouses` (`Warehouse.php`): `branch_id`, `code`, `type` (WarehouseType enum), `parent_warehouse_id` (hierarchy), `manager_id`, `allow_negative_stock`, `account_id` (GL link).
- **StockBalance**: `inventory_stock_balances` — per `product_id`/`product_variant_id`/`warehouse_id`: `quantity`, `average_cost`, `total_value`.
- **InventoryMovement**: `inventory_movements` — full audit trail per movement: `movement_type` (enum `Modules/Inventory/app/Enums/MovementType.php`: `receipt, issue, transfer_in, transfer_out, adjustment, opening, return`), polymorphic `reference_type`/`reference_id`, `quantity_in/out`, `unit_cost`, `total_cost`, `balance_after`, `cost_after`.
- **InventoryCostLayer**: `inventory_cost_layers` — FIFO layers (`original_quantity`, `remaining_quantity`, `unit_cost`), always created on receipt.
- **Documents**: receipts, issues, transfers, counts, adjustments (header + items models each).
- **Item master lives in Core**: `Modules/Core/app/Models/Product.php`, `ProductVariant`, `ProductUnit`, `ProductCategory`, `ProductSerial`, etc. Inventory tracks quantities/costs only.

### Integration contract for HIS
`Modules/Inventory/app/Services/StockService.php` (the single service, 324 lines):
- `increaseStock(array)` / `decreaseStock(array)` — both take `company_id, product_id, product_variant_id, warehouse_id, quantity, unit_cost, movement_type, reference_type, reference_id, date, notes`; update balance, write movement, manage FIFO layers.
- `getIssueCost()`, `getProductCost()`, `getValuationMethod()` — valuation per company setting `inventory.valuation_method` (`fifo` | `weighted_avg`).
- Consumers found outside Inventory: Sales (`ConfirmDeliveryNote`, `PostSalesInvoice`, returns), Purchases (`PostPurchaseBill`, returns).
- **Medical precedent**: `Modules/LIS/app/Models/LabReagent.php` links `product_id` and reads average cost directly from `Modules\Inventory\Models\StockBalance` when `cost_source === 'product'`; LIS also has `LabInvestigationConsumable`.

**HIS implication**: a hospital pharmacy/sub-store = `Warehouse` rows (optionally a new WarehouseType); drug dispensing = `decreaseStock()` with `movement_type: issue` and `reference_type` pointing at the prescription/encounter document. No stock engine in HIS. **(reference_type usage for prescriptions is inference; the open polymorphic mechanism is verified)**

---

## 4. NPHIES — Saudi insurance eligibility, preauth, claims

### Key entities (3 models)
- `NphiesConfig` (`nphies_config`): `provider_license`, `facility_nphies_id`, `sandbox_mode`, `certificate_path`, `private_key_path`, `sender_id`, `receiver_id`, `is_active`.
- `NphiesTransaction` (`nphies_transactions`): audit log of every FHIR exchange — `type` (enum incl. Eligibility/Claim), `bundle_id`, full `request_json`/`response_json`, `status`, `http_status_code`, `response_time_ms`, and links `patient_id`, `lab_request_id`, `lab_invoice_id`.
- `NphiesPreauth` (`nphies_preauths`): `preauth_ref`, `insurer_code`, `status`, `valid_from/valid_to`, `approved_amount`, `approved_items` (json), `diagnosis_code`, `isValid()` guard.

### Services (7)
- `NphiesFhirClient` — bundle building (`buildMessageBundle`) + HTTP send with transaction logging.
- `FhirPatientBuilder`, `FhirServiceItemBuilder` — FHIR resource builders.
- `NphiesEligibilityService::check(LabPatient $patient, string $insurerCode, ?string $serviceDate)` — builds `eligibility-request` bundle (Patient + Coverage + provider/payer Organizations + CoverageEligibilityRequest), returns `{eligible, disposition, benefits, transaction_id, raw, error}`.
- `NphiesPreauthService` — pre-authorization requests.
- `NphiesClaimService::submit(LabPatient, LabInvoice, insurerCode, diagnosisCode, ?preauthRef)` — validates preauth, builds claim items from `invoice->items` (via each item's `investigation` and `net_price`), submits `claim-request` bundle, returns `{outcome, total_submitted, total_covered, total_copay, payment_date, items, transaction_id, error}`, and writes back to the invoice.
- `NphiesPollingService` — async response polling.

### Critical coupling finding
NPHIES is **hard-coupled to LIS models today**: services type-hint `Modules\LIS\Models\LabPatient` and `Modules\LIS\Models\LabInvoice`; its own migrations modify LIS tables:
- `2026_04_02_200001_add_saudi_id_fields_to_lab_patients.php` → `lab_patients.national_id_type`, `passport_country`
- `2026_04_02_200002_add_sbscs_code_to_lab_investigations.php` → SBS billing codes on `lab_investigations`
- `2026_04_03_000001_add_nphies_claim_fields_to_lab_invoices.php` → `lab_invoices.nphies_claim_status`, `nphies_preauth_ref`, `nphies_covered_amount`, `nphies_copay_amount`

**HIS implication**: the FHIR client, transaction log, preauth store, and config are reusable as-is; the eligibility/claim services need their inputs generalized (patient + billable-item abstractions, or HIS-specific builders alongside `FhirPatientBuilder`/`FhirServiceItemBuilder`) before HIS encounters/invoices can flow through. This is a generalization gap, not a build-from-scratch gap. **(inference from the coupling evidence above)**

---

## 5. Conclusions for the HIS architecture decision

1. **Billing, payroll, pharmacy stock, and insurance connectivity already exist** in production modules with formal contracts (`AccountingServiceInterface`, `CreateJournalEntry`, `StockService`, NPHIES services). HIS must consume, not rebuild.
2. **The platform's specialty-module pattern is proven by LIS**: LIS owns clinical documents (`lab_invoices`, `lab_patients`, …) and delegates GL posting (`PostLabInvoice` → `CreateJournalEntry`), costing (`LabReagent` → `StockBalance`), and insurance (NPHIES services) to backbone modules. This is the ready-made template for HIS — and for OBGY functionality.
3. **`journal_entries.source_type/source_id` + free-form `entry_type` strings** give HIS an immediate posting slot (e.g. `his_invoice`, `his_pharmacy_issue`) with zero Accounting changes.
4. **HRM already contains hospital-grade workforce features** (nursing shift extras, biometric attendance, per-employee GL sub-accounts) — practitioner management in HIS reduces to a clinical profile referencing `employees`.
5. **The only backbone investment HIS requires is NPHIES generalization** away from `LabPatient`/`LabInvoice` type-hints toward a patient/invoice abstraction shared by LIS and HIS.

---


# 04 — Angular Frontend Architecture (Moon ERP)

Source tree analyzed: `/home/moonui/public_html/moon-erp` (`src/app`). All facts below were read directly from the files cited; inferences are marked **(inference)**.

## 1. Stack and project shape

From `package.json` and `src/app/CLAUDE.md` / project `CLAUDE.md`:

- **Angular `^21.1.0`**, standalone components only (no NgModules); components generated with `--standalone --style=scss --skip-tests` (tests disabled in `angular.json`).
- **NgRx `@ngrx/store ^21.0.1`** with `@ngrx/entity`; **PrimeNG `^21.1.1`** (+ `@primeng/themes ^21.0.4`, Aura); **ngx-translate `^17.0.0`**; **chart.js `^4.5.1`**; **jspdf `^4.2.0` + jspdf-autotable `^5.0.7`** for PDF generation.
- RTL-first bilingual (Arabic/English).

### `src/app/` layout

| Dir | Contents |
|---|---|
| `core/` | `config`, `constants`, `guards`, `interceptors`, `models`, `services`, `store`, `utils` |
| `features/` | ~80 feature directories (accounting, inventory, hr, sales, purchases, pos, crm, cmms, qms, production, **lis**, …), each lazy-loaded |
| `layout/` | `main-layout`, `sidebar`, `topbar` |
| `shared/` | `components/` (`data-table`, `form-dialog`, `page-header`, `module-nav`, `sales-filter-bar`, `ai-assistant`), `directives/` (`can.directive.ts`, `latin-digits.directive.ts`), `pipes/`, `print/`, `services/` (`confirm-delete.service.ts`) |

## 2. Core services pattern (`core/services/`)

- 65+ API services, one per resource, all `@Injectable({ providedIn: 'root' })`, `HttpClient` against `${environment.apiUrl}/{module}/{resource}`.
- **60 of them are `lis-*.service.ts`** (e.g. `lis-request.service.ts`, `lis-result.service.ts`, `lis-worklist.service.ts`, `lis-kanban.service.ts`, `lis-report-pdf.service.ts`, `lis-html-report.service.ts`, `lis-barcode-label.service.ts`, `lis-cashier.service.ts`, `lis-treasury.service.ts`, `lis-tax-invoice.service.ts`, `lis-critical-alert.service.ts` …) — the LIS service surface alone matches the ~120 LIS API endpoints documented in `CLAUDE.md`.
- Convention: `list` / `listAll` / `search` / `create` / `update` / `delete` plus workflow verbs (`enter`, `validate`, `approve`, `release`, `bulkRelease` on `LisResultService`; `collect`, `receive`, `reject`, `aliquot` on `LisSampleService`).
- `listAll()` exists because the API ignores `per_page` and caps at 25 rows — services auto-paginate with `forkJoin` (project `CLAUDE.md`).
- Auth: `core/interceptors/auth.interceptor.ts` adds the non-standard **`X-Authorization: Bearer <token>`** header and must skip `/assets/` requests (translation JSONs).

### NgRx (`core/store/`)

27+ feature slices (accounts, partners, products, hr, sales, purchases, … and `lis/`). Registered effects include `LisSectionsEffects, LisSpecimenTypesEffects, LisInvestigationCategoriesEffects, LisInvestigationsEffects, LisPatientsEffects, LisDoctorsEffects, LisRequestsEffects` (`app.config.ts`). Canonical state shape (`CLAUDE.md`):

```ts
interface FeatureState extends EntityState<Model> {
  meta: PaginationMeta | null;
  loading: boolean; saving: boolean; error: string | null; loaded: boolean;
}
```

Data flow: **Component → NgRx Action → Effect → Service → API → Effect → Reducer → Selector → Component**. Note: only LIS *reference data* (sections, investigations, patients, doctors, requests) goes through NgRx; the heavy operational screens (worklists, board, reception) call services directly with `signal()`/`computed()` local state (verified in `dept-worklist.component.ts`, `lis-board.component.ts`).

## 3. Routing and permissions

`src/app/app.routes.ts` — everything lazy via `loadComponent`/`loadChildren`:

- `/login`, `/register` → `guestGuard`.
- `/catalog` (public product catalog), `/p/:token` + `/portal/*` (public patient portal, `features/lis/patient-portal-public/`, no auth).
- `''` → `authGuard` + `MainLayoutComponent` with children `/core/*`, `/accounting/*`, and `/core/lis/*` (parent route `data: { permissions: ['lis.'] }`, line ~184).
- `/lab` → `loadChildren` → `features/lis/lis-standalone.routes.ts` (`lisStandaloneRoutes`) with its own `LisLayoutComponent` (sidebar nav groups, search, breadcrumbs, pending counts).
- `/external-lab-portal/*` → separate auth (`portal-auth.guard.ts`) and layout for B2B external labs.

### Guards (`core/guards/auth.guard.ts`)

- `authGuard` — token check, then forces a fresh `/auth/me` round-trip (`profileFresh` flag, `markProfileFresh()`); cached localStorage user is treated as stale; on 401/403 cache+token are cleared and the user is sent to login. Denied users are routed to `LandingService.firstAllowedRoute()` or `/access-denied` (anti-redirect-loop design documented in file comments).
- `permissionGuard` — reads `route.data['permissions']` prefixes; matching is **segment/dot-boundary aware** via `permissionMatches` in `core/services/permission.service.ts`; super admin (`super-admin` role or legacy empty-arrays sentinel, `isSuperAdminUser`) bypasses everything; empty prefix list = allowed.
- `moduleGuard` — blocks URL-typing into system-wide deactivated modules (`SystemModulesService.isRouteEnabled`), complementing sidebar hiding.
- Button-level: structural directive `*appCan` (`shared/directives/can.directive.ts`) renders an element only if `PermissionService.can()` passes (string or any-of array); re-evaluates via `effect()` when the user loads. Explicitly documented as defence-in-depth — backend remains authoritative.

Route-data examples: `{ permissions: ['core.users'] }`, `{ permissions: ['lis.results'] }`, multi-prefix `{ permissions: ['core.products', 'inventory.products'] }`.

## 4. LIS screen structure (the production blueprint)

`features/lis/` has 60+ subdirectories plus its own `CLAUDE.md`. Three routing contexts: embedded (`/core/lis/*`), standalone (`/lab/*`), public portal (`/p/:token`).

### Operational screens (sizes via `wc -l`)

| Screen | File | Lines | Role |
|---|---|---|---|
| Dept worklist | `dept-worklist/dept-worklist.component.ts` | 2,848 | Per-section result entry (numeric/text/selection/formula rows, file/culture/histopath cells, flags) |
| Validation worklist | `validation-worklist/validation-worklist.component.ts` | 3,727 | Review & release: `validate / approve / release / print` |
| Collection worklist | `collection-worklist/collection-worklist.component.ts` | 1,646 | Phlebotomy: barcode scanning, tube grouping; mounted at `/lab/samples` |
| Reception | `reception/reception.component.ts` | 574 | Specimen receiving |
| Board | `board/lis-board.component.ts` | 315 | 6-stage progress matrix per request/section (`Stage`, `TestUnit`, `BoardCard` with `dist[]`, `elapsedH`) |
| Kanban | `kanban/lis-kanban.component.ts` | 1,490 | **Retired in standalone mode** — `lis-standalone.routes.ts` redirects `/lab/kanban → /lab/worklist` ("Kanban retired — superseded by Worklist after the workflow rework"); `/lab/results → /lab/worklist` likewise |
| Request wizard v2 | `request-wizard-v2/` | — | 4 steps: patient/external-lab → tests/packages → billing (individual/insurance/lab) → payment; used at `/core/lis/requests/new` and `:id/edit` |

### Server-driven worklist contract

`core/services/lis-worklist.service.ts` defines the worklist API contract: `WorklistCard`, `WorklistCardsResponse`, `WorklistRow` (display types `panel | subsection | test`), `WorklistRowsResponse`, `WorklistSearchResponse`, result flags `'' | 'LL' | 'HH' | 'L' | 'H' | 'N'`, `WorklistPrimaryAction = 'validate' | 'approve' | 'release' | 'print' | null`, and a runtime feature flag `USE_SERVER_WORKLIST = signal<boolean>(ENV_FLAG)` with `setUseServerWorklist()`. Architectural lesson: work-queue ordering/status logic moved server-side; the FE renders and dispatches actions.

### LIS-local shared widgets

`features/lis/shared/`: `bulk-action-bar`, `test-action-dialog`, `test-filter-bar` — module-scoped reusables sitting below the global `shared/` layer.

## 5. Translation (English-first convention)

- `app.config.ts` lines 266–272: `provideTranslateService({ loader: provideTranslateHttpLoader({ prefix: './assets/i18n/', suffix: '.json' }), fallbackLang: 'en' })`.
- Files: `src/assets/i18n/en.json` + `ar.json` — **7,494 leaf keys, 85 top-level namespaces** (measured). Keys are UPPER_SNAKE English namespaces: `APP, AUTH, NAV, COMMON, …, LIS`. The `LIS` namespace has **48 sub-namespaces** (`WIZARD, REQUESTS, SAMPLES, RECEPTION, RESULTS, KANBAN, QC, COMPLIANCE, PATIENT_PORTAL, STATUS, …`).
- `core/services/language.service.ts` sets `document.documentElement` `dir='rtl'` and toggles `body.rtl` when lang is `ar`.
- Convention for HIS/OBGY: add an `HIS` (and/or `OBGY`) namespace to both JSONs; author English first, Arabic as the shipped UI language.

## 6. Report / print engine

Two layers:

**Generic engine — `shared/print/`** (ERP-wide documents: invoices, vouchers):
- `print.interfaces.ts` (110 lines): `GenericPrintData` (party/items/totals + `dir: 'rtl'|'ltr'`, `lang`, translator fn `t`, `extraFields`, `extraSections`) and `CustomTemplateConfig` (colors, font, show/hide toggles, header/footer text).
- `print-templates.ts` (470 lines): `GENERIC_TEMPLATES` map, default `'classic'`.
- `print.service.ts` (230 lines): resolves template id from settings, falls back to `classic`.
- Admin screen: `shared/print/print-settings/` mounted at `/core/print-settings` (perm `core.settings`).

**LIS report engine — `core/services/`** (medical result reports):
- `lis-report-pdf.service.ts` (1,620 lines): jsPDF + autotable; exports `ReportData/ReportSection/ReportResultRow/LabInfo/ReportSettings`; prints via hidden `iframe` + `contentWindow.print()`.
- `lis-html-report.service.ts` (1,311 lines): HTML report rendering with print CSS (`@media screen/print`, fixed footer), header config and histopath section ordering from `lis-lab-info.service.ts` (`ReportTemplate`, `HeaderConfig`, `PrintCommentConfig`, `DEFAULT_HISTO_SECTION_ORDER`).
- Plus `lis-barcode-label.service.ts` (specimen labels), `lis-print-report.service.ts`, `lis-result-report.service.ts`, and batch printing screen `features/lis/print-queue/` (parallel PDF generation).

## 7. Reusable building blocks for HIS/OBGY

- **`shared/components/data-table/data-table.component.ts`** (283 lines): `TableColumn` interface, loading/error/lazy states, filter `TemplateRef` slot, Excel/PDF export through `core/services/export.service.ts` (`ExportColumn`, `exportTransform`), column customization dialog. Drop-in for every HIS list screen.
- **`form-dialog`**, **`page-header`**, **`module-nav`** (`shared/components/`), **`confirm-delete.service.ts`**, PrimeNG `MessageService`/`ConfirmationService` toasts/confirms.
- **`*appCan`** directive + `permissionGuard` route data → permission model is purely conventional (`his.` prefix works with zero framework change).
- **Dual-context layout precedent**: `LisLayoutComponent` (403 lines per LIS `CLAUDE.md`) proves a clinical specialty can ship a fullscreen optimized workspace (`/lab/*`) while reusing the same components inside the ERP shell (`/core/lis/*`). An HIS would replicate this as e.g. `/hospital/*` or `/clinic/*` **(inference)**.
- **Public portal precedent**: token-link patient portal (`/p/:token`, `PatientPortalService`, QR/WhatsApp share via `PortalLinkDialogComponent`) is directly reusable for OB/GYN patients (appointments, ultrasound reports) **(inference)**.
- **Server-driven worklist contract** (`lis-worklist.service.ts`) is the right pattern for clinic queues (today's appointments, pending consultations, ultrasound worklist) **(inference)**.

## 8. Conventions a new HIS/OBGY UI must follow (checklist)

1. `features/his/` (or `features/obgy/`) directory, standalone components, `signal()` UI state, reactive forms.
2. `core/services/his-*.service.ts`, `providedIn: 'root'`, `list/listAll/search/create/update/delete` + workflow verbs; respect `X-Authorization` interceptor and 25-row pagination workaround.
3. `core/models/his-*.model.ts` interfaces mirroring API resources.
4. Routes: embedded under `/core/his/*` with parent `data: { permissions: ['his.'] }` + per-route `his.<resource>`; optional standalone layout module like `lis-standalone.routes.ts`.
5. `HIS` namespace in `en.json`/`ar.json`; English keys first; all UI strings via `| translate`.
6. Reuse `data-table`, `page-header`, `form-dialog`, generic print engine; build a specialty report service modeled on `lis-html-report.service.ts` for clinical documents (antenatal record, ultrasound report) **(inference)**.
7. NgRx slices only for reference data (clinics, services, fee schedules); direct service + signals for operational screens — matching how LIS evolved.

## 9. Facts bearing on the HIS-core vs separate-module decision (frontend view)

- LIS frontend is deeply lab-shaped (worklists, barcode, QC Levey-Jennings, machine results); its **patterns** transfer to HIS, its **screens** do not.
- LIS owns its own patient/doctor/invoice models (`lis-patient.model.ts` with `mrn`, `lis-doctor.model.ts`, `lis-invoice.model.ts`) — there is **no shared clinical patient master in the FE today**; a real HIS needs one consumed by both LIS and OBGY **(inference)**.
- The router + permission system already supports independently activated modules (`moduleGuard`, `module-activation` feature), multiple layouts, and module-prefixed permissions — adding OBGY as a separate specialty module is architecturally cheap on the frontend.
- The kanban→worklist pivot shows the team iterates workflow UIs against real usage; any OBGY UI inherited from the legacy system should be re-expressed in the worklist/wizard idiom rather than ported visually **(inference)**.

---


# 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.

---


# 06 — OBGY Reusable Assets: Three-Bucket Classification

> Source of truth: `/home/amrtechogate/public_html/obgy-erp-analysis.md` (complete 21-agent analysis of
> the legacy system at `/home/amrtechogate/public_html/obgy`; schema `_db/obgy_12-7-2024.sql`,
> **312 tables, zero declared FKs, 18 functional modules**; full migration blueprint at section 95).
> Purpose: input for the board decision — *is OBGY the core of HIS, or a specialty module on top of HIS?*
> Everything below is traceable to that file; analytical estimates are explicitly marked **(inference)**.

## Quantification

| Anchor (documented) | Value | Citation |
|---|---|---|
| Total legacy tables | 312 (307 InnoDB, 5 MyISAM incl. `awusers`) | §00.1 |
| Lookup-shaped tables (`id + title/name + del`, ≤4 cols) | **~190 of 312 (61%)** | §90.4 pattern 2 |
| Migration target | 312 → **~85** new tables (≈55 domain models + lookups/pivots/media) | §95 preamble |
| Dead/derived/dev tables dropped outright | **~40** | §95 preamble, §90.5 |
| Tables referencing `patients` hub | 73 | §90.1 |
| Tables referencing `infertilitysheet` hub | 17 | §90.1 |

**Bucket split (inference, derived from the anchors above):**

| Bucket | ≈ tables | ≈ % | Nature |
|---|---|---|---|
| A — Generic HIS assets | ~100 | **~32%** | encounter/queue, exam, Q&A engine, sheet pattern (18 clone children), operations, follow-up, media, generic lookups, pharmacy/financial/board bridges |
| B — OB/GYN specialty | ~150 | **~48%** | ANC, gyna, infertility, IVF, andrology, imaging, endoscopy + most lookup *content* |
| C — Discard | ~60 | **~20%** | ~40 dead/derived/dev tables + ~17 platform `aw*`/settings tables replaced by ERP core |

Important nuance: bucket A is **proven data models and clinically-tuned workflows**, not portable code.
The legacy implementation (PHP 5.6.40 EOL, Smarty 3.1.11, RedBeanPHP 4.3 fluid mode, no front
controller, no tests, no git — §00.2/§00.6) is 100% rewrite territory; what transfers is the *design*.

---

## Bucket A — GENERIC hospital/HIS assets (reusable for ANY specialty)

### A1. Encounter / visit + queue model
- **Legacy:** `visits` (appointment + queue + payment ledger in one table; `visitorder` drag-sortable
  queue, `enterordered` entry sequence, `view` waiting/entered flag, magic `detectionid`
  -99/999/9999 for installment/rest-pay/refund), `visit_periods` (slot capacity `max_no`),
  `endvisitreports` (treatment closure), `vacations` (never wired), `detections` (service/fee
  catalog). Controllers `core/controllers/visits.php` (1,946 lines), `core/controllers/index.php`
  (daily queue, hospital vs clinic modes), waiting-room display app `obgy/screen/`.
- **Becomes:** `Appointment` + `Encounter` (queue_no, status enum) + `Service` + `VisitPeriod`
  (capacity enforced in DB transaction) + `ClinicClosure` + `TreatmentClosure` +
  `PatientLedgerEntry` (explicit entry_type enum kills magic codes) + `Invoice/Payment` posted to
  ERP accounting via `VisitInvoiced`/`PaymentReceived`/`RefundIssued` events (§95 1.2, 2.4).
- **Critical caveat (§90.1):** *clinical tables never reference `visits`* — no `visitid` column
  exists outside `visits`/`visits_updates`. The Encounter backbone HIS needs **does not exist in
  OBGY** and must be built new; ETL uses a patient+date heuristic matcher (risk R2, §95.4).

### A2. Clinical-sheet pattern (sheet + prescriptions + lab-orders children)
- **Legacy:** the core design pattern (§90.4 #1): parent sheet per specialty + cloned children —
  **10 identical `*drugs` tables** (`ancsheetdrugs`, `mainantenentaldrugs`, `gynadrugs`,
  `gynasheetdrugs`, `infertilitydrugs`, `infertilitysheetdrugs`, `mointoringsheetdrugs`,
  `followupcarddrugs`, `operativedetailsdrugs`, `recorddrugs`) and **8 identical `*invest` tables**
  (`ancsheetinvest`, `mainantenentalinvest`, `gynainvestigation`, `gynasheetinvest`,
  `infertilityinvest`, `infertilitysheetinvest`, `mointoringsheetinvestigation`, `followupinvest`).
- **Becomes (§95 consolidation table):** `Prescription`/`PrescriptionItem` (polymorphic
  `prescribable`, `subject` enum patient/husband) and `LabOrder`/`LabOrderItem` (polymorphic
  `orderable`, `lab_test_id` → LIS catalog) — **4 generic tables replace 18**, usable by any
  future specialty sheet (cardiology, ortho, …).

### A3. Configurable history Q&A engine
- **Legacy:** `presenthistoryquestions` (categories + `displayorder` grouping),
  `presenthistoryanswers` (predefined answers, `favorite` flag), runtime answer table `gynaph`
  (patientid, date, catid, answerid, doctorid — **absent from the dump**, created by RedBean fluid
  mode; must be exported from production). Admin controller
  `core/controllers/addpresenthistory.php`; capture in `gyna.php` (§examination module).
- **Becomes:** `HistoryQuestion` / `HistoryAnswerOption` / `PatientHistoryAnswer` with explicit
  `display_group` — a fully generic clinical-questionnaire engine for any department.

### A4. Examination-per-body-system model
- **Legacy:** `examination` (vitals: weight/height/bmi/b_p+bp2/pulse/rgb + 5 mixed text-or-id
  body-system columns) + 9 structurally identical finding lookups (`examinationhead`,
  `examinationchest`, `examinationabdomen`, `examinationpelvis`, `examinationextremitis`,
  `examinationlocalse`, `generalbreast`, `generalhirsutism`, `generalthyroid`); per-system
  free-text vs lookup toggled by `programesetting` flags. Controller
  `core/controllers/examination.php` (cell-level autosave UX).
- **Becomes:** `ClinicalExamination` (typed vitals) + `ExaminationFinding` (nullable
  finding_option_id / free_text, `body_system` enum) + options in `obgy_lookups(domain=exam.*)`.
  Breast/hirsutism/thyroid findings stay OB/GYN-leaning content, but the *model* is generic.

### A5. Operations / surgical waiting-list workflow
- **Legacy:** `op_wait_list` (surgical booking, day-grouped), `operativedetails` (full operative
  note: anesthesia cascade `anasthesa`→`generalanasthesa`, incision cascade
  `jncision`→`midlinejncision`, 4 CSV staff-team columns, complications, recommendation),
  `operativedetailsdrugs` (intra-op drugs auto-pushed to pharmacy `recepittmp`/`receiptdrugs`),
  `hospitalnames`, `pathology`/`histopath`. Controllers `operations.php`, `operativedetails.php`.
- **Becomes:** `SurgicalBooking` + `OperativeNote` + `operative_note_staff` pivot (role enum) +
  `AnesthesiaType`/subtype + self-referencing `incision_types` + `Hospital` +
  `PathologyResult`/`HistopathologyFinding`. **Blueprint §95 2.6 designs these explicitly
  module-agnostic "so the HMS module can consume them"** — the strongest documented signal that
  these belong in the HIS core, not inside OBGY.

### A6. Follow-up cards
- **Legacy:** `followupcard` + `followupcarddrugs` (printable per-encounter card; confirm/print
  pushes a prescription into pharmacy via `recepittmp` + `receiptdrugs`), `instruction`
  (instruction-template library + hospital transfer letters). Controller `followupcard.php`,
  `instruction.php`.
- **Becomes:** encounter-linked card (`visit_id` FK replaces the "latest visit" copy),
  `PrescriptionDispensed` domain event to pharmacy, `InstructionTemplate` + `Referral` model
  (transfer letters become persisted entities instead of print-only).

### A7. Media attachments
- **Legacy:** `sonar` (images/videos, MyISAM), `patientfiles` (DB index that active code bypasses),
  `gtimage`/`gtdetail` (annotated diagram + positioned markers); `obgy/upload/` = 3.2 GB inside the
  web root (`upload/sonar` alone 3.1 GB / 1,846 files); 4.8 GB total media in migration phase P7.
- **Becomes:** `MediaAttachment` via spatie medialibrary (checksummed ETL, signed URLs) +
  `AnnotatedDiagram`/`DiagramMarker` (relative coordinates) — generic for any imaging-using module.

### A8. Lookup-vocabulary engine (~190 curated clinical lists)
- **Legacy:** ~190 of 312 tables (61%) share the shape `id + title/name + del` — one table per
  dropdown, including per-column clones (the `ssemen` quadruplet, 9 `mointoringsheet*` lookups,
  24 `lapar*`/`copy*` endoscopy lists, `tvs*`/`hsg*` finding lists). The dump ships most lists
  EMPTY — **the clinically curated content lives only in production** (risk R7).
- **Becomes:** single `obgy_lookups (id, domain, parent_id, title_ar, title_en, sort, is_active,
  deleted_at)` + PHP Enums for clinically fixed sets (blood types, delivery mode SVD/CS, loss
  types). The *engine + seeding pipeline* is a generic HIS asset; most *content* is bucket B.

### A9. Other generic bridges
- **Investigations catalog + EAV:** `investcats` (22 categories), `invests` (276 tests),
  `otherinvestigations`/`otherinvestigationsrows`/`otherinvestigationsvalues` EAV trio → mapping
  layer `obgy_lis_test_map` onto the **existing LIS catalog**; ad-hoc tests created in LIS (§95 1.13, 2.2).
- **Pharmacy/prescribing:** `drugs` (~2000 items), `pharmacystore`, `importbill/importdetails` →
  ERP inventory/procurement; module keeps only the prescription layer (§95 1.14, 2.3).
- **Patient financial ledger:** `totalbalance` (+ magic-code rows in `visits`) → `PatientLedgerEntry` (§95 1.2).
- **Medical board:** `brequests`/`bsession`/`bsessionmembers`/`bmemberopenion`/`bcomments` →
  rebuilt `Board*` models (tables empty in dump; legacy lacks patient_id — biggest gap) (§95 1.15).
- **Fine-grained RBAC concept:** `awcontroll`/`awcontrollprop`/`awrolecontrollprop`/`awrolebtn`
  (~590 actions / ~3000 grants) — §00.7 conclusion 1: "worth porting conceptually" →
  `spatie/laravel-permission`, one permission per legacy action + named button rights.
- **Couple/companion pattern:** `forhusband`/`subject` discriminator across history and
  prescriptions → generic `subject` enum useful wherever a companion is treated **(inference)**.

---

## Bucket B — OB/GYN-SPECIALTY assets (valuable only inside a women's-health module)

| Asset | Legacy source | Laravel target (§95) |
|---|---|---|
| ANC / pregnancy | **3 parallel generations**: `ancsheet`+`ancnewvisit`, `mainantenental`+`antenalvisit`, `followup`+`followupvisit`; `mainantenentalus`; `wifeepc(+type/ttt/obst)`; `registeration`; `op_4d_list` | `Pregnancy` (merge by patient+lmp), `AntenatalVisit` (GA computed accessor), `AntenatalUltrasound`, `PregnancyLoss`, `DeliveryRegistration`, `FourDScanBooking`, `ExternalProvider` |
| IVF/ICSI/IUI cycle monitoring | `ivfsheet` + `mointoringsheet` (+10 children: `ovst` 26-column stimulation grid, `eprep`, `mointoringsheetvisits/drugs/investigation`, 9 lookups); derived `icsi` archive; runtime `sefo` | `IvfCycle` (one active per patient via partial unique index), `IvfCycleVisit` + `FollicleMeasurement`, stage tables `OocyteRetrieval`/`SemenSample`/`EmbryoTransfer`/`Cryopreservation`/`EndometrialPrep`/`CycleOutcome` |
| Andrology | `semen`, `semen2` (wide), `semeninfertility`, `hormon`+`hormonalprofile`+`hormonalprofile2`, 11 `semen2*`/`semenplace`/`sementype` lookups | `SemenAnalysis` (WHO flags), `AndrologyInvestigation` (wide→long), `HormoneResult` + `HormoneDefinition` (reference ranges, links to LIS) |
| Gyn ultrasound templates | `ultrasound`/`ultrasoundobst` + per-fetus `*detail`, `ultrasoundgyna`, `gynaus`+`gynausficils` (per-follicle), `tvs`/`dtvs`/`sis`/`hsg`(+`hsginfertility`), `mrict`, 16 vocab tables | `ImagingStudy` (unified header, study_type), `FetalScanFinding`, `GynaScanFinding`, follicle grid |
| Endoscopy findings | `laparoscopy`/`hysteroscopy` (free-text) + `laparoscopyinfertility`/`hysteroscopyinfertility` (structured), 24 `lapar*`/`copy*` lookups, `jncision`/`midlinejncision` (→ Operations) | `EndoscopyProcedure` (kind enum) + `EndoscopyFinding` + `EndoscopyTerm` dictionary |
| Gynecology visits/sheets | `gyna`, `gynasheet`+`maingyna`, `newvisitg`, `gynainfertility(+plan)`, menstrual/local-exam lookups | `GynaVisit`, `GynaSheet`, `GynaSheetVisit`, `InfertilityProfile`/`InfertilityPlan` |
| Infertility couple file | gen-1 `infertility`(+4 children) + gen-2 `infertilitysheet` (64 cols, hub for **17 tables**) | `InfertilityFile` with structured children; CSV columns → pivots |
| Specialty history | `phmenstrual`, `phobstetric` (3-baby denormalization, G/P formula on hardcoded ids 1–5), `phcontraception`, `phpastart`+`phpasticsi`, `wifep`/`awifep` | `MenstrualHistory`, `ObstetricHistory`+`ObstetricBaby`, `ContraceptionHistory`, `PastArtCycle`+detail; termination mode → fixed Enum |
| Lookup *content* | most of the ~190 lists: stimulation protocols, TVS/HSG findings, ICSI places/results, menstrual descriptors… | seeded into `obgy_lookups` **from production** (dump lists are empty — risk R7) |

---

## Bucket C — DISCARD (do not migrate, do not imitate)

1. **The framework itself:** "aw framework" — no front controller (23-line `?ac=` dispatcher
   `core/controllers/imp/_imp.php`; every public method URL-invokable), 81 controllers all named
   `Controllers`, ~60-line constructor boilerplate ×81, PHP 5.6.40 (EOL 2018), Smarty 3.1.11
   (2012), RedBeanPHP 4.3 with `R::freeze(FALSE)` in production, `SET NAMES latin1` against utf8
   tables (Arabic double-encoding), no Composer/tests/git/CI (§00.2–6).
2. **Security flaws (fix on legacy now; never port — blueprint rule R12):** confirmed widespread
   SQL injection (`patients.php` ~311–419, `financialreport.php`, `visits.php`, `sonar.php:243`,
   `index.php`); unauthenticated CORS-`*` mobile API (`mobileservices.php`, all auth commented
   out); zero CSRF; generic AJAX writers accepting client-supplied table/column names
   (mass-write + SQLi primitives, some with **no auth at all**, e.g. `patienthistory.php::update`,
   `delRowsommedel`, `examination.php::update`); hardcoded DB/API credentials in ≥5 configs;
   1.6 GB of DB dumps web-accessible in `core/db_backups/` + `_db/`; `mysqldump` password on the
   process command line (§00.5).
3. **Dead/duplicate tables (~40):** dev leftovers `table2/table3/tablename/testtbl1/testtbl2/
   patients_tmp`; clone quadruplet `ssemen/sseemen/sseemmen/sseemmeen`; shadow-audit copies
   `patients_updates`/`visits_updates` (→ laravel-auditing); archives/derived `old_visits`,
   `lastvisit`, `icsi`; orphans `operationids`, `anprotocol`, `drugdos`, `importtdetails`,
   `excelinfo`; unused `php-jwt-master` library (§90.5, §95 per-domain "Dropped" rows).
4. **Platform replaced, not migrated:** 10 `aw*` RBAC/menu tables, `programesetting` (75-column
   singleton → key/value + `.env`), `devices/floors/device_tracking` (incomplete feature — keep
   design note for HMS), `messages`, `screen_slider` mechanics (§95 1.16).

---

## Verdict inputs (for the HIS-core vs specialty-module decision)

1. **No code is salvageable** — only data models, workflows and ~190 curated vocabularies; the
   rewrite cost is identical whether OBGY is "the core" or "a module".
2. **The Encounter backbone HIS needs is missing from OBGY** (§90.1: clinical rows never reference
   visits) — the single most HIS-critical entity must be designed new either way.
3. **~48% of the schema is women's-health-only (inference)**; generic assets are ~32% and are
   patterns, not subsystems.
4. **The blueprint itself already separates the layers:** §95 2.6 designs `Encounter`,
   `OperativeNote`, `SurgicalBooking`, `Hospital`, `incision_types` and the queue service
   module-agnostic *for the HMS module to consume*; §95 2.1–2.5 delegate patient master to ERP,
   lab to LIS, drugs to inventory, money to accounting.
5. **Recommended reading of the evidence (inference):** harvest bucket A into the generic HIS
   core (encounter/queue, sheet+prescription+lab-order pattern, Q&A engine, per-system exam,
   surgical workflow, lookup engine, media); build `Modules/Obgy` as the first specialty module
   on top of that core, owning bucket B. OBGY is the *proof-of-content* and pattern donor for
   HIS — not its core.

*Companion Arabic fragment: `his-vision-work/sections/06-obgy-assets.html`.*

---


# 07 — Target HIS Scope (Any Hospital): Canonical Capability Definition & Sourcing Matrix

Author scope: hospital-information-systems domain analysis for the Moon ERP board decision
(ERP ↔ LIS ↔ HIS ↔ OBGY). Source of all OBGY claims:
`/home/amrtechogate/public_html/obgy-erp-analysis.md` (sections 02 Visits, 10 Operations,
13 Follow-up, 95 Migration Blueprint, plus 11 Examination, 14 Investigations, 15 Pharmacy,
16 Financial, 18 Platform headings). Moon ERP coverage statements (Accounting, Inventory,
HRM, LIS, NPHIES) are taken as given per the study brief (the ERP codebase is not present
in this environment) and are marked **[brief assumption]**.

---

## 1. Method and naming convention

Capability names are aligned with FHIR R4 resources because the existing OBGY analysis
already uses FHIR-shaped target models (`Appointment`, `Encounter`, `EpisodeOfCare`
mentioned for `endvisitreports`, prescription → `MedicationRequest` semantics,
`LabOrder` → `ServiceRequest`/`DiagnosticReport`, `Procedure`, `ImagingStudy`).
Evidence: blueprint §1.2 ("`Appointment` | `visits` (booking half)", "`Encounter` |
`visits` (queue half) + `followupcard`"), §2.2 LIS events `LabOrderPlaced` /
`LabResultReceived`, §2.3 `PrescriptionDispensed`, §2.4 `VisitInvoiced` /
`PaymentReceived` / `RefundIssued` — all in
`/home/amrtechogate/public_html/obgy-erp-analysis.md` (section "95 — ERP Migration Blueprint").

Sourcing classes used in the matrix:

- **ERP** — capability already exists in Moon ERP modules (Accounting / Inventory / HRM /
  LIS / NPHIES) **[brief assumption]**.
- **OBGY-seed** — the OBGY analysis produced a concrete, validated target design
  (tables, models, events) that can seed the HIS build. Note the hard rule in the
  blueprint risk register (R12): *"no legacy endpoint ported"* — seeds are **data-model
  and workflow designs**, never legacy PHP code (SQL injection confirmed system-wide,
  section 00 §5.1; unauthenticated mobile API, section 02).
- **Net-new** — nothing usable exists; must be designed and built from scratch.

---

## 2. Canonical HIS capability matrix

| # | Capability | FHIR alignment | Exists in Moon ERP | Seeded by OBGY analysis | Net-new build | v1 priority |
|---|------------|----------------|--------------------|--------------------------|---------------|-------------|
| 1 | **MPI — Master Patient Index** | `Patient`, `Person`, identifiers | Partial — ERP patient/client master exists; blueprint §1.1: "The legacy patient **becomes** the ERP patient/client" **[brief assumption + blueprint]** | Yes — dedup requirements traced (risk R10: MAX+1 file numbers, duplicate national IDs, drafts `done=0`); profile-split pattern `obgy_patient_profiles` | Merge/duplicate-review workflow, per-facility MRN issuance, identifier domains | **P0 Critical** |
| 2 | **Appointments & Scheduling** | `Appointment`, `Schedule`, `Slot` | No | **Strong** — `visits` (booking half), `visit_periods` (capacity `max_no`), waiting list (`urgent=1`), mobile booking (`mobileservices.php` workflows), `vacations` → `ClinicClosure`; target models `Appointment`, `VisitPeriod` (§1.2) | Multi-resource scheduling (practitioner + room + equipment), recurring schedules | **P0 Critical** |
| 3 | **OPD clinics & outpatient Encounter** | `Encounter` (ambulatory), `EpisodeOfCare` | No | **Strong** — `Encounter` model from `visits` queue half + `followupcard` (§1.2); queue mechanics `visitorder`/`enterordered`/`view`, sheet routing `lastvisit`, waiting-screen `screen_slider` + "queue websocket feed" (§1.16); `endvisitreports` → `TreatmentClosure` (analysis suggests modeling "as status on an EpisodeOfCare", section 02) | Multi-specialty clinic configuration, generic department model | **P0 Critical** |
| 4 | **ADT — Admission / Discharge / Transfer** | `Encounter` (inpatient), `EpisodeOfCare`, `Location` | No | Faint only — `Hospital` (`hospitalnames`) catalog, transfer letters (`instruction.php::printtransfer`, print-only, no persistence); `branches`/`floors`/`devices`/`device_tracking` exist but were an **incomplete feature, explicitly dropped** with "keep schema design note for HMS" (§1.16) | **Yes — entire admission lifecycle**: admit orders, transfer, discharge summary, LOS, EpisodeOfCare state machine | **P0 Critical** |
| 5 | **Bed & Ward Management** | `Location` (ward/room/bed tree), `Encounter.location` | No | No (the `floors`/`devices` tables are not a bed model) | **Yes** — location hierarchy, bed states (occupied/reserved/cleaning), census board | **P0 Critical** |
| 6 | **Emergency Room (ER)** | `Encounter` (emergency), triage `Observation` | No | Faint — urgent waiting list (`urgent=1`, section 02) is a priority flag, not triage (استنتاج/inference) | **Yes** — triage scales (CTAS/ESI), ER tracking board, disposition | **P1 High** (v1.5 acceptable) |
| 7 | **CPOE — unified orders (lab/rad/pharmacy/procedure)** | `ServiceRequest`, `MedicationRequest` | Lab execution side exists (LIS) **[brief assumption]** | **Strong shape** — blueprint consolidations: 8 `*invest` clones → `LabOrder`/`LabOrderItem` (morphs `orderable`); 10 `*drugs` clones → `Prescription`/`PrescriptionItem` (morphs `prescribable`); `op_wait_list` → `SurgicalBooking`; events `LabOrderPlaced`, `LabResultReceived`, `PrescriptionDispensed` (§2.2, §2.3) | Unified order hub (one `ServiceRequest` spine), radiology orders, order sets, order status lifecycle | **P0 Critical** |
| 8 | **Clinical Documentation (notes/forms)** | `Condition`, `Observation`, `QuestionnaireResponse`, `DocumentReference` | No | **Strong** — `ClinicalExamination` (vitals, section 11), configurable Q&A engine `presenthistoryquestions`/`presenthistoryanswers` → `HistoryQuestion`/`HistoryAnswerOption`/`PatientHistoryAnswer` (§1.3), `ClinicalNote` with `note_type` enum (merge of `followupdiagnosis`+`followupexam`+`followupinvest`, section 13), `Diagnosis` + `encounter_diagnoses` pivot | Generic form-builder for any specialty, ICD-10/SNOMED coding, sign-off/amend workflow | **P0 Critical** |
| 9 | **eMAR & Inpatient Pharmacy** | `MedicationRequest`, `MedicationDispense`, `MedicationAdministration` | Stock/item master exists (Inventory) **[brief assumption]**; blueprint §1.14: "drug catalog and stock delegate to ERP inventory item master; the module keeps only the prescription layer" | Partial — outpatient dispense chain (`recepittmp`/`receiptdrugs` → `PrescriptionDispensed` event, §2.3); intra-op drugs (`operativedetailsdrugs`, section 10) | **Yes — administration layer**: scheduled doses, nurse administration record, ward stock, IV management | **P0 Critical** (minimal eMAR in v1) |
| 10 | **OR Management** | `Procedure`, `ServiceRequest`, `Encounter` | No | **Strong** — `SurgicalBooking` (`op_wait_list`), `OperativeNote` (`operativedetails`) with `operative_note_staff` pivot (anesthetist/assistant teams), `AnesthesiaType` cascade (`anasthesa`→`generalanasthesa`), `incision_types` (`jncision`/`midlinejncision`), `PathologyResult` (`pathology`), intra-op drug grid wired to pharmacy (section 10); §2.6: these are "designed module-agnostic so the HMS module can consume them" | OR room/slot scheduling board, pre-op checklist (WHO SSC), PACU/recovery, sterilization link | **P1 High** |
| 11 | **Nursing Station** | `Observation` (vitals), `Task`, `CarePlan` | No | Faint — vitals fields in `examination` (section 11); queue/waiting-screen service (§1.16) (استنتاج for reuse) | **Yes** — nursing assessments, care plans, task lists, shift handover, fluid balance | **P1 High** |
| 12 | **Billing / Patient Folio + Insurance cycle** | `Account`, `ChargeItem`, `Invoice`, `Claim`, `Coverage` | **Yes (backbone)** — Accounting + NPHIES (Saudi insurance) **[brief assumption]** | **Strong bridge** — `PatientLedgerEntry` (from `totalbalance` + magic `detectionid` -99/999/9999 codes), `Invoice`/`Payment` via events `VisitInvoiced`/`PaymentReceived`/`RefundIssued` (§2.4), `Service` price catalog (`detections`) | Inpatient folio (charge accumulation per admission from CPOE/eMAR/OR), package pricing, pre-authorization workflow per encounter | **P0 Critical** |
| 13 | **Lab orders → LIS** | `ServiceRequest`, `DiagnosticReport` | **Yes** — mature LIS module in production **[brief assumption]** | Yes — catalog mapping layer `obgy_lis_test_map`; `LabOrderPlaced` → LIS order, LIS publishes `LabResultReceived` → result on `lab_order_items` (§2.2, §1.13: `investcats` 22 categories, `invests` 276 tests) | Thin — HIS just consumes the same contract | **P0 Critical (reuse)** |
| 14 | **Radiology (RIS/PACS)** | `ImagingStudy`, `ServiceRequest`, `DiagnosticReport` | No | Partial — `ImagingStudy` unified report header design (section 08/§1.10: `ultrasound*`, `tvs`, `hsg`, `mrict`, `sonar` media), structured findings + `MediaAttachment` | **Yes** — modality worklist, DICOM/PACS integration, radiologist reporting queue | **P2 Medium** (reporting-only in v1) |

### Matrix totals

- 14 canonical capabilities.
- **4** materially covered by Moon ERP backbone (MPI partial, billing backbone, LIS, pharmacy stock) **[brief assumption]**.
- **7** meaningfully seeded by OBGY's analyzed target designs (scheduling, OPD encounter/queue, CPOE shape, clinical documentation, OR, billing bridge, lab-order UX).
- **5** are net-new HIS core with no OBGY precedent: **ADT, beds/wards, ER, eMAR administration layer, nursing station** — i.e. the entire *inpatient* spine.

---

## 3. What the OBGY analysis actually contributes (and what it does not)

### 3.1 Reusable, module-agnostic seeds (explicit in blueprint §2.6)

> "HMS (planned): `Encounter`, `OperativeNote`, `SurgicalBooking`, `Hospital`,
> `incision_types`, and the waiting-queue service are designed module-agnostic so the
> HMS module can consume them."
> — `/home/amrtechogate/public_html/obgy-erp-analysis.md`, §2 Integration Contracts, row 2.6

Concrete seeds with file-traceable evidence:

1. **Appointment/Encounter split** — section 02 ERP Migration Notes propose `Appointment`
   (status enum booked/arrived/in_consultation/done/cancelled), queue position, and
   `PaymentTransaction` that "eliminates magic detectionid -99/999/9999".
2. **Encounter-linkage repair** — risk R2: legacy clinical rows "never reference visits";
   the new design makes `encounter_id` mandatory on clinical records — exactly the HIS-grade
   discipline a general hospital needs.
3. **CPOE consolidation patterns** — §1 cross-cutting table: 10 `*drugs` tables →
   `visit_prescriptions` + `prescription_items`; 8 `*invest` tables →
   `visit_investigations` + `lab_order_items` with polymorphic `orderable`/`prescribable`.
   This is a ready blueprint for `MedicationRequest`/`ServiceRequest` semantics.
4. **Surgical lifecycle** — section 10: booking (`op_wait_list`), operative note
   (`operativedetails` 25+ structured fields), team pivot, anesthesia cascade, intra-op
   pharmacy integration, histopathology/pathology results.
5. **Patient ledger** — section 16 `totalbalance`/`totalbalancepaids` + section 02 magic
   codes → typed `PatientLedgerEntry` (charge/payment/refund/package_credit/installment),
   posting to ERP accounting via events (§2.4).
6. **Configurable history/forms engine** — `presenthistoryquestions`/`presenthistoryanswers`
   (section 11) is a primitive `Questionnaire`/`QuestionnaireResponse` — a seed for the HIS
   clinical-forms builder (استنتاج: generalization is ours, the seed is theirs).

### 3.2 What OBGY does NOT provide (net-new HIS core)

- **No admission concept**: every encounter is same-day ambulatory; `visits.view` 0/1 is
  waiting/entered, not admitted (section 02).
- **No beds**: `floors`/`devices`/`device_tracking` were "incomplete feature" and the
  blueprint **drops** them (§1.16) keeping only a "schema design note for HMS".
- **No ER/triage**, **no nursing documentation**, **no medication administration** —
  prescriptions end at dispense (`PrescriptionDispensed`), never at administration.
- **No insurance**: the legacy system is cash/visa only (`detectionvalue_cash`,
  `detectionvalue_visa`, section 02); the whole payer/claim cycle relies on Moon ERP NPHIES
  **[brief assumption]**.
- **Specialty bulk is not HIS**: ~9 of 19 analyzed modules (antenatal, gynecology,
  infertility, IVF, andrology, imaging-OB, endoscopy, history-OB, board) are OB/GYN-specific
  clinical content with no general-hospital reuse (استنتاج from module purposes).
- **Zero code reuse**: risk R12 hard rule — "no legacy endpoint ported"; SQL injection
  confirmed widespread (section 00 §5.1), generic table/column AJAX writers, unauthenticated
  mobile API. Seeds are designs and migrated data only.

---

## 4. Sellable v1 cut (priority rationale)

A hospital will not buy an HIS without the inpatient spine, even though OBGY contributes
nothing there. Recommended v1 line (استنتاج — product judgment on top of traced facts):

- **v1 (P0)**: MPI + Appointments + OPD Encounter/queue + ADT + Beds/Wards + CPOE
  (lab+pharmacy+procedure) + Clinical documentation + minimal eMAR + Billing folio/NPHIES
  bridge + LIS reuse. (Rows 1–5, 7–9, 12–13.)
- **v1.5 (P1)**: ER tracking/triage, OR management (fast-follow, strongly OBGY-seeded),
  nursing station.
- **v2 (P2)**: RIS/PACS radiology, full eMAR/IV, care plans.

Architecture corollary for the board (fact base for the separate verdict section): the
shared seeds (`Encounter`, CPOE shapes, ledger, queue service) belong in a **shared
clinical core** consumed by both HIS and a `Modules/Obgy` specialty module — exactly the
direction §2.6 of the blueprint already points to; OBGY as-is cannot be "the HIS core"
because the 5 inpatient capabilities and the insurance cycle are absent from it.

---

## 5. Source citations

- `/home/amrtechogate/public_html/obgy-erp-analysis.md` — section "Module: Visits & Appointments (visits)" (tables `visits`, `visit_periods`, `lastvisit`, `endvisitreports`, `vacations`; workflows 1–8; ERP Migration Notes).
- Same file — section "Module: Operations & Deliveries (operations)" (tables `operation`, `operations`, `operativedetails`, `operativedetailsdrugs`, `op_wait_list`, `anasthesa`, `generalanasthesa`, `hospitalnames`, `pathology`, `detections`; workflows 1–9).
- Same file — section "Module: Follow-up & Follow-up Cards (followup)" (tables `followup`, `followupcard`, `followupcarddrugs`, `followupvisit`, `instruction`; ghost table `followupdrugs`).
- Same file — section "95 — ERP Migration Blueprint" (§1 consolidation patterns, §1.2 Encounters/Billing bridge, §1.3 Clinical core, §1.13 LIS mapping, §1.14 Pharmacy, §1.16 Platform, §2 Integration contracts 2.1–2.6, §3 phases, §4 risk register R1–R12).
- Same file — section "00 — Technical Architecture Deep-Dive" §5.1 (SQLi confirmed) — basis for the no-code-reuse rule.
- Moon ERP module coverage (Accounting, Inventory, HRM, LIS, NPHIES): **study brief assumption**; ERP source not present under `/home/amrtechogate/public_html` (verified: directory contains only `obgy/`, analysis outputs, and work folders).

---


# 08 — The Shared Data Spine: Patient / Encounter / Billing across ERP + LIS + HIS + OBGY

Technical companion to the Arabic management fragment `sections/08-data-spine.html`.
Everything below is traced to real files; design proposals are explicitly marked **(proposal)** or **(inference)**.

---

## 1. Patient identity — where the MPI lives

### 1.1 The three patient-shaped entities that exist today

| Entity | Location | Nature | Key columns |
|---|---|---|---|
| `lab_patients` | `/home/moonui/moon-erp-be/Modules/LIS/database/migrations/2026_03_01_000006_create_lab_patients_table.php` | Clinical person registry (production, ~11,769 internal + 15 B2B rows per `/home/moonui/hms-phase0-spec.md` line 25) | `company_id`, `mrn` (unique), `name`/`name_ar`, `date_of_birth`, `gender`, `phone`, `national_id`, `insurance_info` JSON, `medical_history`, `partner_id` → `business_partners` (nullOnDelete), `blood_group` |
| `business_partners` | `/home/moonui/moon-erp-be/Modules/Core/database/migrations/2026_02_16_200001_create_business_partners_table.php` | Commercial counterpart (customer/supplier): `is_customer`, `is_supplier`, `credit_limit`, `payment_terms_days`, `tax_number`, unique `(company_id, code)` | No clinical fields at all |
| CRM customer | `/home/moonui/moon-erp-be/Modules/CRM/database/migrations/2026_03_31_100001_create_crm_tables.php` | `crm_customer_ext` / `crm_customer_tags` / `crm_customer_interactions` — an *extension* of `business_partners`, not an independent person registry | `customer_type` only |

Plus the legacy OBGY `patients` table (`/home/amrtechogate/public_html/obgy-erp-analysis.md`, section "Module: Patients & Registration"):
- One row = the **couple**: wife demographics + embedded husband columns (`husdandname` [sic], `husbandnationalid`, `husbandbl`, …).
- File number `statusno` generated by `MAX(statusno)+1` (race-prone), Egyptian 14-digit NID parsing, **zero declared FKs anywhere** in the DB.
- Already syncs outward today: synchronous cURL dual-DB write creating `client.obygyPatientId -> patients.id` rows in an external ERP (`patients.php::erpClient/curlAddClient`). The analysis' own migration note: *"in the target ERP the patient IS the client (patient_id on the client/account record)"*.

### 1.2 How `lab_patients` already behaves as an MPI

Accreted columns show it has outgrown "lab patient":

- **B2B multi-tenancy**: `external_lab_id` owner column (NULL = internal) — `2026_05_23_000002_add_external_lab_and_tax_status_to_lab_patients_table.php`, FK → `lab_external_labs`. This is the FHIR `Patient.managingOrganization` pattern (settled decision, spec line 25, scored 37/40 vs 19/40 for splitting).
- **Identity uniqueness**: `UNIQUE (company_id, external_lab_id, national_id)` — `2026_06_03_010000_scope_lab_patient_uniqueness_by_external_lab.php` (with documented MariaDB NULL-semantics gap closed at app layer; Phase-0 W2-1 item 6 adds the `ext_scope_key` partition guard).
- **KSA payer identity**: `national_id_type`, `passport_country` added by the **NPHIES module** (`Modules/NPHIES/database/migrations/2026_04_02_200001_add_saudi_id_fields_to_lab_patients.php`) and `tax_status` (saudi/resident VAT branch) — i.e., the national-claims layer already treats `lab_patients` as *the* patient source.
- **Finance bridge**: `partner_id` → `business_partners`; `Modules/LIS/app/Actions/PostLabInvoice.php` resolves per-partner AR accounts through `Modules\Accounting\Models\AccBpExt.ar_account_id` (lazily created by `Modules\Accounting\Listeners\CreatePartnerAccounts`). Patient↔client linkage that OBGY hacks with cURL already exists natively here.
- **Patient portal**: portal token issuance (`2026_03_02_300001_add_portal_token_to_lab_patients_table.php`, `LabPatient.php:60-65` booted hook).

### 1.3 Verdict on MPI placement

**The future MPI is `lab_patients`, promoted to a shared (Core/HIS-consumable) namespace without a table rename** — exactly what Phase-0 W2-1 already schedules: *"Promotion to shared entity: namespace/ownership decision only (shared model location + scopes) — NO table rename, NO global scope"* (spec line 75), with named scopes `LabPatient::scopeInternal()` / `scopeForLab($labId)` (W2-1 item 7) and the governing constraint *"NO blanket Global Scope on lab_patients"* (spec line 19). HIS consumes `internal()`; B2B rows stay (settled, do not reopen).

OBGY migration consequence **(proposal, consistent with the analysis' own notes)**:
- OBGY `patients` ETLs into `lab_patients` as internal rows (`external_lab_id = NULL`): `statusno` → `mrn`, wife demographics → patient columns, NID → `national_id` (+ `national_id_type='egyptian_nid'` value reuse of the NPHIES column).
- Husband/couple data does **not** belong in the MPI: a specialty-owned `his_patient_spouses` (1:1) table per the analysis' `Spouse`/`PatientHusband` recommendation; same for `ObstetricBaseline`.
- The cURL dual-DB sync dies: `partner_id` + `AccBpExt` is the in-process replacement for `client.obygyPatientId`.
- `business_partners`/CRM remains the *financial* identity; never a clinical registry. The only join point is `lab_patients.partner_id`.

---

## 2. Encounter spine — Encounter + Folio that every clinical module FKs into

### 2.1 What exists today

- **LIS**: `lab_requests.encounter_id` — nullable `unsignedBigInteger`, **no FK, read nowhere** (inert by design): `2026_06_10_160000_add_encounter_id_to_lab_requests.php`, comment: *"HIS encounters table does not exist yet — FK + consumers arrive at HIS kickoff (Phase-0 W1-3 inert column)"*. Also `lab_visits` (`2026_03_01_000010`) — a lightweight reception visit (`visit_number`, `patient_id`, `lab_request_id`, `visit_type='walk_in'`, `status`), not a hospital encounter.
- **OBGY**: the `visits` table is *"appointment + encounter + payment ledger in one table"* (analysis, visits module): queue columns (`visitorder`, `enterordered`, `view`), clinical routing via `lastvisit.control`, AND money columns (`totaldetectionvalue`, `detectionvalue_cash`, `detectionvalue_visa`, `restdetectionvalue`) with **magic `detectionid` values −99 = installment, 999 = pay-rest, 9999 = refund** and a self-FK `visitid` for payment rows. This conflation is the single strongest argument for a separated Encounter/Folio spine.
- **HIS**: Encounter/Folio tables are *"DEFERRED to HIS kickoff (design approved, creation later)"* (spec line 27). Module rule (spec line 26): HIS is ONE module `Modules/HIS`; **HIS imports LIS/Accounting/HRM forward; LIS never imports HIS** — backward flow only via existing events `LabResultReleased` / `LabRequestCompleted` (`Modules/LIS/app/Events/`).

### 2.2 Proposed schema (proposal — follows existing LIS column conventions: `company_id` first, `decimal(12,3)`, `created_by/updated_by`, softDeletes)

```php
// his_encounters — the clinical timeline anchor (FHIR Encounter)
Schema::create('his_encounters', function (Blueprint $t) {
    $t->id();
    $t->foreignId('company_id')->constrained('companies');
    $t->string('encounter_number')->unique();          // like lab_requests.request_number / lab_visits.visit_number
    $t->foreignId('patient_id')->constrained('lab_patients'); // MPI; HIS writes only internal() rows
    $t->string('type');            // opd | ipd | er | daycase
    $t->string('status')->default('planned'); // planned|arrived|in_progress|discharged|cancelled (FHIR Encounter.status subset)
    $t->unsignedBigInteger('department_id')->nullable();   // clinic/ward
    $t->foreignId('attending_doctor_id')->nullable()->constrained('lab_doctors'); // chains to HRM via lab_doctors.employee_id (W1-2)
    $t->foreignId('parent_encounter_id')->nullable()->constrained('his_encounters'); // episode linkage
    $t->unsignedInteger('queue_position')->nullable(); // replaces OBGY visitorder/enterordered/view triple
    $t->boolean('urgent')->default(false);
    $t->string('source')->default('reception');        // reception|portal|mobile|referral
    $t->timestamp('started_at')->nullable();
    $t->timestamp('ended_at')->nullable();
    $t->foreignId('branch_id')->nullable()->constrained('branches')->nullOnDelete();
    $t->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    $t->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
    $t->timestamps(); $t->softDeletes();
    $t->index(['company_id', 'status']); $t->index(['patient_id']); $t->index(['started_at']);
});

// his_folios — the patient account per admission/visit (financial container)
Schema::create('his_folios', function (Blueprint $t) {
    $t->id();
    $t->foreignId('company_id')->constrained('companies');
    $t->string('folio_number')->unique();
    $t->foreignId('encounter_id')->constrained('his_encounters');
    $t->foreignId('patient_id')->constrained('lab_patients');  // denormalized, mirrors lab_invoices.patient_id
    $t->string('payer_type')->default('self_pay');  // self_pay | insurance | b2b_account
    $t->unsignedBigInteger('insurance_contract_id')->nullable(); // generalized lab_insurance_contracts
    $t->string('status')->default('open');          // open | closed | invoiced | cancelled
    $t->decimal('total_charges', 12, 3)->default(0);
    $t->decimal('total_payments', 12, 3)->default(0);
    $t->decimal('balance', 12, 3)->default(0);
    $t->timestamp('opened_at')->nullable();
    $t->timestamp('closed_at')->nullable();
    // + branch_id / created_by / updated_by / timestamps / softDeletes as above
    $t->index(['company_id', 'status']); $t->index(['encounter_id']);
});

// his_folio_charges — every module posts billable lines here
Schema::create('his_folio_charges', function (Blueprint $t) {
    $t->id();
    $t->foreignId('company_id')->constrained('companies');
    $t->foreignId('folio_id')->constrained('his_folios');
    $t->string('source_type');                      // morph: lab_request_investigation | obgy_service | pharmacy_dispense | bed_day ...
    $t->unsignedBigInteger('source_id');
    $t->string('service_code')->nullable();         // SBS/NPHIES code — pattern exists: lab_investigations.sbscs_code (NPHIES 2026_04_02_200002) + nafis fields (2026_05_31_140000)
    $t->string('description'); $t->string('description_ar');
    $t->decimal('quantity', 8, 2)->default(1);
    $t->decimal('unit_price', 12, 3)->default(0);
    $t->decimal('discount_amount', 12, 3)->default(0);
    $t->decimal('tax_amount', 12, 3)->default(0);
    $t->decimal('total', 12, 3)->default(0);
    $t->decimal('unit_cost', 12, 4)->default(0);    // COGS pattern from lab_invoice_items (2026_03_07_211313_add_cogs_fields)
    $t->unsignedBigInteger('cost_center_id')->nullable(); // Accounting cost centers, as in PostLabInvoice::createCogsJournalEntry
    $t->string('status')->default('pending');       // pending | invoiced | voided
    $t->unsignedBigInteger('invoice_id')->nullable();
    $t->timestamp('charged_at');
    $t->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    $t->timestamps();
    $t->unique(['source_type', 'source_id']);       // a clinical act bills exactly once
    $t->index(['folio_id', 'status']);
});
```

### 2.3 FK contracts per module

| Module | FK into spine | Mechanism |
|---|---|---|
| LIS | `lab_requests.encounter_id` → `his_encounters.id` (FK added at HIS kickoff, column already live) | HIS calls `Modules/LIS/app/Actions/CreateLabRequest` (extracted in W1-3 from `LabRequestController::store` lines ~172-471) passing `encounter_id` — forward import, allowed direction |
| LIS → spine results | `LabResultReleased` / `LabRequestCompleted` events (existing: `Modules/LIS/app/Events/`, dispatched at `LabResultController.php:516,1058`, `LabWorkflowService.php:75,120`) | HIS listener attaches results to the encounter; LIS code untouched |
| OBGY (future specialty module) | `obgy visits` row → `his_encounters` (queue/booking half) + payment rows → folio payments; clinical sheets (`ancsheet`, `gynasheet`, `infertilitysheet`) FK `encounter_id` | ETL: rows with `detectionid IN (−99, 999, 9999)` are NOT encounters — split to payment transactions (analysis, visits ERP-migration notes) |
| Pharmacy/Radiology (later) | same `his_folio_charges` morph | `FolioChargePosted` event (proposal) |

`lab_visits` stays as-is for standalone-lab operation; when a lab order originates from an encounter, `lab_requests.encounter_id` is the link (no change to walk-in flow — governing constraint "LIS must NOT be affected", spec line 16).

---

## 3. Billing flow — today's LIS posting, generalized to Folio → Invoice → NPHIES claim

### 3.1 As-built LIS flow (all real code)

1. `lab_invoices` (`2026_03_02_000006`): `lab_request_id`, `patient_id`, `insurance_contract_id`, `insurance_amount`/`patient_amount` split, `journal_entry_id`, `cancel_journal_entry_id`, `cogs_journal_entry_id`, `posted_by/posted_at`.
2. **Posting** — `Modules/LIS/app/Actions/PostLabInvoice.php`:
   - Resolves AR account: partner-specific `AccBpExt.ar_account_id` (lazily created via `CreatePartnerAccounts::ensurePartnerAccounts`) → fallback `SettingsService` key `lis.receivable_account_id`.
   - Builds JE lines: **DR** AR (total) / **CR** revenue (`lis.revenue_account_id`) / **CR** VAT (`lis.tax_payable_account_id`); insurance split re-debits `patient_amount` to patient AR + `insurance_amount` to insurance receivable (contract → partner → settings fallback chain).
   - Calls `Modules\Accounting\Actions\CreateJournalEntry::execute()` with `source_type='lab_invoice'`, `source_id`, `entry_type` per invoice type (`lab_invoice`, `lab_insurance_invoice`, `external_lab_outbound`, `external_lab_inbound`).
   - Separate **COGS JE** (`DR lis.cogs_account_id / CR lis.reagent_expense_account_id`) grouped by `investigation.section.cost_center_id`.
   - Triggers `DoctorCommissionService::calculateForInvoice` (skipped for insurance/external-lab types).
3. **Payment** — `Modules/LIS/app/Actions/PostLabPayment.php`: DR `receiving_account_id` (treasury) / CR the *same* receivable resolved by the same fallback chain; `entry_type='lab_payment'`; then `invoice->recalculatePaymentStatus()`.
4. **NPHIES** — `Modules/NPHIES`: claim fields ON `lab_invoices` (`nphies_claim_status`, `nphies_preauth_ref`, `nphies_covered_amount`, `nphies_copay_amount` — `2026_04_03_000001`); `NphiesClaimService::submit()` logs to `nphies_transactions` (`lab_invoice_id`, `patient_id`, `type`) and writes the outcome back to `nphies_claim_status`. FHIR builders: `FhirPatientBuilder`, `FhirServiceItemBuilder`.

### 3.2 Generalization (proposal — this IS the "unified charge-posting service" the spec defers, line 96)

```
clinical act (any module)
  └─ event FolioChargePosted ──▶ his_folio_charges (+ folio running totals)
folio close (discharge / visit end)
  └─ event FolioClosed ──▶ generate invoice(s) per payer split:
        patient share  → invoice payer_type=self_pay
        insurance share → invoice payer_type=insurance (preauth ref from nphies_preauths)
invoice post
  └─ Core PostInvoice (generalized PostLabInvoice):
        DR AccBpExt.ar_account_id | his.receivable_account_id
        CR his.revenue_account_id (+ CR tax payable)
        COGS JE by cost_center_id
        CreateJournalEntry(source_type='his_invoice')
payment
  └─ generalized PostLabPayment (treasury DR / AR CR)
claim
  └─ NphiesClaimService::submit(invoice) → nphies_transactions → nphies_claim_status
```

Key engineering point: `CreateJournalEntry` is already module-agnostic (Accounting action). The lab-specific part of `PostLabInvoice` is only the **settings-key prefix and account fallback chains** — extract to a Core `ChargePostingService` parameterized by module prefix (`lis.*` → `his.*`), exactly what the Phase-0 spec's Generalization Register anticipates (*"payment-methods/treasuries hoist, generalized price-lists/insurance, unified charge-posting service"*, spec line 96). OBGY's `erpSellbill` cURL hack is replaced by the same in-process flow.

### 3.3 Event contract summary

| Event | Direction | Status | File / proposal |
|---|---|---|---|
| `LabResultReleased` | LIS → HIS (backward) | EXISTS | `Modules/LIS/app/Events/LabResultReleased.php` |
| `LabRequestCompleted` | LIS → HIS (backward) | EXISTS | `Modules/LIS/app/Events/LabRequestCompleted.php` |
| `CriticalResultDetected` | LIS → HIS | EXISTS | `Modules/LIS/app/Events/CriticalResultDetected.php` |
| `EncounterOpened` / `EncounterClosed` | HIS internal + consumers | proposal | `Modules/HIS/app/Events/` at kickoff |
| `FolioChargePosted` | any clinical module → HIS billing | proposal | morph payload `(folio_id, source_type, source_id, total)` |
| `FolioClosed` | HIS → invoicing | proposal | triggers payer-split invoice generation |
| `InvoicePosted` | HIS → Accounting (via action, not event) | pattern exists | generalized `PostLabInvoice` |
| Claim lifecycle | invoice → NPHIES | pattern exists | `NphiesClaimService::submit`, `nphies_transactions` |

---

## 4. Inputs to the board verdict (OBGY-as-HIS-core vs specialty module)

1. OBGY `patients` cannot be the MPI: couple-in-one-row, no FKs, varchar lookup ids, `MAX+1` file numbers, mojibake latin1 lookups — vs `lab_patients` with NPHIES Saudi-ID fields, B2B governance, partner/AR bridge, and 11.7k production rows.
2. OBGY `visits` conflates appointment + encounter + payments with magic ids — the spine must be *designed*, and OBGY data ETL-ed into it; porting OBGY's model would bake the conflation into HIS.
3. OBGY's own cURL ERP sync proves the demand for patient↔client and visit↔bill integration — Moon ERP already solves both natively (`partner_id`+`AccBpExt`, `PostLabInvoice`→`CreateJournalEntry`).
4. The settled module rule (one `Modules/HIS`, forward imports only, LIS events backward) leaves OBGY exactly one clean seat: a women's-health specialty package *inside/alongside* HIS that FKs into `his_encounters`/`his_folios`, contributing its clinical sheets (ANC/gyna/infertility/IUI) as encounter documentation.

---


# 09 — Product & Packaging View: What We Sell, To Whom

**Role:** healthcare-software product strategy input to the ERP ↔ LIS ↔ HIS ↔ OBGY architecture decision.
**Sources actually read:**
- `/home/moonui/public_html/hms-feasibility-report.html` — HIS feasibility study v2 (readiness scorecard, 4 governing decisions, Phase 0 package, roadmap, generalization register).
- `/home/amrtechogate/public_html/obgy-erp-analysis.md` — full OBGY legacy analysis (§00 technical architecture, §02 visits/appointments, §90 master ERD, §95 ERP migration blueprint incl. integration contracts §2 and phases §3, risk register §4).

Everything below is traceable to those two files; market-sizing and competitor statements are explicitly marked **(inference)**.

---

## 1. The four assets on the table

| Asset | Status | Evidence |
|---|---|---|
| Moon ERP platform | 15 modules in production, Laravel 12 + Angular 21 | hms-feasibility-report.html hero meta; "15 موديول" |
| `Modules/LIS` | Production, the most complex module: 135 migrations, ~311 routes; cashier shifts + Z report, safes with auto ledger accounts, insurance contracts, monthly billing, **NPHIES (eligibility/approvals/claims, FHIR)**, B2B reference-lab portal with live customers | hms report §1, §3 rows "كاشير الاستقبال", "التأمين والمطالبات + NPHIES"; decision-1 note (B2B patients with live clinical rows) |
| `Modules/HIS` | Approved, not built. Roadmap: Phase 0 foundation 3–5 person-weeks; Phase 1 outpatient 8–14; Phase 2 inpatient 10–16; Phase 3 extensions 4–10 each. Hard gaps: appointments engine 1/10, beds/ADT/folio 0–1/10 | hms report §6 roadmap, §7 scorecard |
| OBGY legacy system | 312 tables, zero FKs, PHP 5.6 / Smarty / RedBeanPHP; full migration blueprint to `Modules/Obgy` exists (312 → ~85 tables, phases P0–P7, risk register R1–R12) | obgy-erp-analysis.md §00, §95 |

Key qualifier on the OBGY asset: **the legacy code is unsellable as-is** — confirmed SQL injection in search/report controllers (`patients.php` ~311–419, `financialreport.php`, `visits.php`), unauthenticated CORS-open mobile API (`mobileservices.php`, auth calls commented out), DB dumps inside the web root (`core/db_backups/`, `_db/`), PHP 5.6.40 EOL since 2018 (obgy-erp-analysis.md §5, §6). The sellable asset is the **domain model + Arabic clinical vocabularies + the migration blueprint**, not the code.

---

## 2. Product tiers — one platform, license-keyed module sets

The architectural rule that makes tiering possible is already decided (hms report, Decision 2): `HIS` imports `LIS/Accounting/HRM`; **`LIS` never imports `HIS`** (feedback via existing events `LabResultReleased` / `LabRequestCompleted`), so LIS stays sellable standalone.

### Tier 1 — Moon Lab (sells today)
- Modules: `Core` + `Accounting` + `Modules/LIS`.
- Buyers: standalone labs, lab chains, B2B reference labs (portal exists with live customers — decision-1 analysis: 15 B2B patients with 15 requests / 18 samples / 45 results / 16 invoices on dev env).
- Zero build. This tier is the company's current revenue engine.

### Tier 2 — Moon Clinic (HIS Phase 1)
- Adds `Modules/HIS` core: appointment engine, `Encounter`, folio, reception cashier (reuses LIS safes/shifts), generalized insurance/price lists (generalization register row "قوائم الأسعار المسماة + التغطية التأمينية" — lifted from LIS, polymorphic items).
- Buyers: outpatient clinics and polyclinics — the widest customer count in Egypt/Saudi **(inference)**.
- Build: 8–14 person-weeks after the 3–5-week Phase 0 (hms report figures, unchanged).

### Tier 3 — Moon Women's Health (Clinic + `Modules/Obgy`)
- Adds the specialty plugin per blueprint §95: `Pregnancy` + `AntenatalVisit` (3-generation merge of `ancsheet`/`mainantenental`/`followup`), `GynaSheet`, `InfertilityFile`, `IvfCycle` + `FollicleMeasurement` + stage sub-tables (`OocyteRetrieval`, `EmbryoTransfer`, `Cryopreservation`, `EndometrialPrep`, `CycleOutcome`), `SemenAnalysis`/`HormoneResult`, `ImagingStudy` (US/TVS/HSG/4D incl. 3.1 GB sonar media), `EndoscopyProcedure`, `OperativeNote`/`SurgicalBooking`, history engine (`ph*` domain), follow-up cards, medical board.
- Buyers: OB/GYN clinics, IVF/ICSI/IUI centers.
- Build: blueprint phases P1–P4 (each sized L); **the analysis is done** — 312 tables fully documented, model mapping complete, ETL risk register written (R1–R12).
- Launch customer: the legacy OBGY clinic itself (see §6).

### Tier 4 — Moon Hospital (HIS Phase 2 + 3)
- Adds inpatient: wards/rooms/beds + bed map, ADT, nightly bed-day charge job → folio, deposits/advance payments, basic nursing station (HIS roadmap Phase 2, 10–16 person-weeks); then Phase 3 extensions (clinical pharmacy on existing `Inventory`/POS, OR/radiology via the same `CreateLabRequest`-style executor contract).
- Buyers: small/mid hospitals.
- This is where the platform's genuinely missing pieces live (hoteling 0–1/10) — deliberately last.

---

## 3. How ONE platform serves all four tiers

1. **One patient identity.** Decision 1 (approved, "not to be reopened"): upgrade `lab_patients` in place to the shared patient entity — it already carries MRN, per-company unique national ID, insurance, portal links, and `partner_id` → business partner with auto AR/AP ledger accounts. Every tier reads/writes the same record; B2B lab patients are fenced by `external_lab_id IS NULL` scoping + guard tests.
2. **One financial spine.** Journals, safes, cashier shifts, insurance, NPHIES — scored 9/10 "battle-tested". Folio rule (Decision 4): lab invoices enter the folio **by reference, never re-posted** — single posting source per line. The unified charge-posting service (generalization register) prevents "the 6th copy" of receivable-account selection logic.
3. **One service-execution contract.** `CreateLabRequest` action + nullable `encounter_id` on lab requests (Phase 0 items): reception, an OBGY pregnancy sheet, or an inpatient ward all *order*; LIS/radiology *execute* and report back via events. The OBGY blueprint plugs into exactly this: `LabOrderPlaced` → LIS order via mapping table `obgy_lis_test_map` (legacy `invests` catalog: 276 tests, 22 categories → mapped/created in LIS); `LabResultReceived` lands results inside clinical sheets (blueprint §2.2).
4. **One pharmacy/inventory.** Blueprint §2.3: `PrescriptionDispensed` event → ERP inventory issue + sale line; drug catalog (~2,000 legacy items) mastered in the ERP item master; OBGY keeps only prescribing metadata.
5. **One frontend shell.** App-in-app pattern proven 3× (POS/HR/Lab) — HIS is the 4th, OBGY the 5th (hms report §7 UI row 8/10).
6. **The licensing prerequisite (must-fix).** `system.enabled_modules` is enforced **frontend-only** (`moduleGuard` in `auth.guard.ts:162`); the server never checks it — a "disabled" module's API remains callable, and the central licensing layer exists but is not wired as middleware (hms report §2). **You cannot sell differentially priced tiers until API-level enforcement ships.** It is already a Phase 0 item, in log-only mode first (measured reality: existing company configs are stale — immediate enforcement would break live flows).

---

## 4. Competitive differentiation — Egypt / Saudi

Capability claims below are file-backed; competitive positioning statements are **(inference)**.

| Differentiator | Backing |
|---|---|
| **NPHIES-ready in production, not on a roadmap** — FHIR eligibility/approvals/claims live in LIS today; extending to HIS items is a planned generalization, not a build | hms report §3 row "التأمين والمطالبات + NPHIES" (grade: جاهز); FHIR Patient built from `lab_patients` (Decision 1 note) |
| **Arabic-first clinical vocabularies harvested from OBGY** — ~190 legacy lookup tables (diagnoses, complaints, menstrual/local-exam terms, IVF protocols, TVS/HSG/endoscopy term dictionaries) consolidate into `obgy_lookups (domain, parent_id, title_ar, title_en, …)`, seeded from production data = years of real Arabic clinical usage | blueprint §1 consolidation table; §1.6, §1.10, §1.11. Hardest thing to buy off-the-shelf **(inference)** |
| **Full IVF/ICSI/IUI depth** — cycle entity with partial unique index (one active cycle/patient), follicle grid replacing 26 cryptic columns, embryo-transfer/cryo stages | blueprint §1.8. Rare in generic HIS products **(inference)** |
| **ERP-grade finance inside the medical system** — per-line real-time journal entries, patient-as-business-partner ledgers, FIFO payment allocation; the classic weak spot of clinic-software competitors **(inference)**, our documented 9/10 strength | hms report §7 row 1 |
| **Existing B2B lab network** — reference-lab portal with live customers = natural cross-sell channel into Tiers 2–4 | decision-1 B2B analysis |
| **Migration story as a sales weapon** — clinics on legacy PHP systems have a forced-move security motive (SQLi, open APIs, web-root DB dumps are typical of that generation); our P0–P7 idempotent-ETL playbook with risk register is itself a billable service **(inference on market; playbook is real)** | obgy-erp-analysis.md §5, §95 phases & risks |

---

## 5. Packaging → architecture: why OBGY must be a plugin on a HIS core, not the HIS core

The board question is whether OBGY becomes the core of HIS. The packaging analysis answers **no**, on four grounds:

1. **Price/feature separability.** A general hospital must not pay for (or carry) 85 OB/GYN specialty tables; an IVF center must not pay for ADT/beds. Toggleable licensing requires the specialty content isolated in `Modules/Obgy` behind a license key. OBGY-as-core destroys the tier matrix.
2. **OBGY lacks the HIS core itself.** Zero inpatient/beds/ADT, no insurance/NPHIES, finance done via magic codes in `visits.detectionid` (-99 installment / 999 settle / 9999 refund) + `totalbalance` — explicitly replaced, not ported (blueprint §1.2, §2.4). It cannot anchor a general hospital product.
3. **The OBGY blueprint already assumes this split.** Blueprint §2.6: `Encounter`, `OperativeNote`, `SurgicalBooking`, `Hospital`, `incision_types`, and the waiting-queue service are "designed module-agnostic so the HMS module can consume them."
4. **LIS standalone sale is protected** only if dependency direction stays HIS→LIS, never reverse (Decision 2). Same logic applies one level up: Clinic tier must not depend on Obgy.

**But OBGY meaningfully de-risks the HIS core** — this is the nuance the board should hear:

| OBGY blueprint capability | Lands in | Why |
|---|---|---|
| `Appointment` / `Encounter` / `VisitPeriod` / `ClinicClosure` / queue service (heir of `visits`, `visit_periods`, `vacations`, `detections` service catalog; mobile booking source enum) | **HIS core** | The platform's biggest gap (appointments 1/10) gets a domain design distilled from a system that ran real queues, day-period capacity limits, and mobile bookings for a decade **(architectural inference, supported by blueprint §1.2 and §2.6)** |
| `visit_prescriptions` / `visit_investigations` (morph-based prescription + order headers replacing 10+8 legacy clone tables) | **HIS core** | Generic clinical primitives any future specialty (cardio, dental, derma) reuses **(inference)** |
| Specialty sheets: `Pregnancy`, `GynaSheet`, `InfertilityFile`, `IvfCycle`, `SemenAnalysis`, `ImagingStudy`, `EndoscopyProcedure`, `OperativeNote` | **`Modules/Obgy` plugin** | Separately priced Tier-3 content, license-toggled |
| Legacy visit-money coupling (`PatientLedgerEntry` derivation, cURL sell-bill sync) | **Not built at all** | Replaced by HIS folio + existing accounting events (`VisitInvoiced` / `PaymentReceived` / `RefundIssued`, blueprint §2.4); single posting source per line (Decision 4) |

This also keeps EMR scope-creep risk (hms report risk #7) contained: HIS v1 stays administrative-financial; deep clinical documentation arrives only inside the priced specialty plugin where a paying segment funds it.

---

## 6. Rollout sequencing — who funds what

| Step | What ships | Who pays |
|---|---|---|
| **Now** | Phase 0 readiness package (~7–10 dev-days incl. permission-dependency registry): `CreateLabRequest` extraction, `encounter_id`, B2B hardening (8 items), branch scope to Core, module-key enforcement in log-only mode | Existing Tier-1 (lab) revenue. Non-negotiable licensing prerequisite for all tier sales |
| **Next 2 quarters** | HIS Phase 1 (outpatient) → launch **Moon Clinic** | Lab revenue + first clinic customers |
| **Parallel → capstone** | `Modules/Obgy` P1–P4 → launch **Moon Women's Health**. The legacy OBGY clinic is the reference customer: it has a forced security motive to migrate (SQLi, open `mobileservices.php`, web-root dumps — obgy-erp-analysis.md §7 lists 3 immediate-remediation items), and its production DB is the ETL rehearsal target (blueprint requires fresh production exports — risk R7). Migration services themselves are billable **(inference)** | OBGY clinic engagement + women's-health segment |
| **After Tiers 2–3 prove** | HIS Phase 2 (inpatient: beds/ADT/deposits/nightly charges) → launch **Moon Hospital**; Phase 3 extensions per market demand, item by item | Tier 2+3 revenue funds the most expensive net-new build |

Sequencing logic mirrors both source roadmaps: HIS report's phased plan (outpatient before inpatient) and the OBGY blueprint's P0–P7 ("each phase ships behind module permissions; legacy stays read-only reference until P7 sign-off").

---

## 7. Bottom line for the board

- **Sell one platform with four entry doors**: Lab (today) → Clinic (HIS Phase 1) → Women's Health (Clinic + Obgy plugin) → Hospital (HIS Phase 2/3).
- **OBGY is not the HIS core.** It decomposes: its generic scheduling/encounter/prescription patterns inform the HIS core (filling the platform's worst gap), while its 85-table specialty depth becomes the license-keyed `Modules/Obgy` — the strongest differentiation asset in the line, and the one no generic competitor can copy quickly because its value is a decade of Arabic clinical vocabulary and OB/GYN workflow, not code **(inference)**.
- **Two hard gates before any tier marketing**: server-side module/license enforcement (today frontend-only), and the Phase 0 contract work (`CreateLabRequest` + `encounter_id`) that makes "one platform" literally true.

---


# Proposal P-PLATFORM-FIRST — "HIS as a Thin Clinical Platform, OBGY as the First Plug-in Specialty"

Competing proposal to the architecture review board — senior solution architect submission, 2026-06-11.

Stance: **`Modules/HIS` is a thin, specialty-agnostic clinical platform** (MPI bridge, Encounter, Folio,
orders hub, forms engine, scheduling/queue, beds/ADT). **`Modules/Obgy` is a specialty clinical-content
module that plugs into HIS encounters** — its simple sheets become forms-engine content, its complex
sheets (IVF cycles, pregnancies) become dedicated `obgy_*` tables FK-ing into `his_encounters`.

All claims below trace to the evidence pack (`md/01`–`md/09`), `/home/moonui/hms-phase0-spec.md`,
`/home/moonui/public_html/hms-feasibility-report.html`, and `/home/amrtechogate/public_html/obgy-erp-analysis.md`.

---

## 1. Thesis

This is the only architecture that simultaneously satisfies **every binding decision already approved**
(D1 lab_patients-in-place MPI, D2 one-module HIS with forward-only imports, D3 CreateLabRequest
service-performer contract, D4 Encounter/Folio deferred-but-approved, D5 LIS untouchable) **and** the
commercial requirement of license-separable tiers (Moon Clinic must sell without 85 OB/GYN tables;
an IVF center must not pay for ADT/beds). The OBGY analysis itself takes this side: blueprint §95 2.6
designs `Encounter`, `OperativeNote`, `SurgicalBooking`, `Hospital`, `incision_types` and the queue
service *module-agnostic "so the HMS module can consume them"*, and §95 2.1–2.5 delegate patient,
lab, drugs and money to ERP/LIS/Inventory/Accounting — leaving OBGY, by its own blueprint, a thin
specialty clinical layer. We are not inventing the split; we are executing the split the evidence
already drew.

Three facts make any "OBGY-as-core" alternative untenable and any "everything-inside-HIS" alternative
commercially weaker:

1. **OBGY has no Encounter.** §90.1: no clinical table references `visits`; the single most
   HIS-critical entity must be designed new either way. The platform's spine cannot come from a
   system that lacks the spine.
2. **Zero OBGY code is portable** (PHP 5.6 EOL, system-wide SQLi, no FKs, rule R12 "no legacy
   endpoint ported"). The rewrite cost is identical under every architecture — so architecture
   should be chosen on ownership and salability, where platform-first wins.
3. **~48% of OBGY's 312 tables are women's-health-only content** (ANC, IVF, andrology, gyn US,
   endoscopy, infertility). A general-hospital core that is half specialty content is unsellable as
   Tier-2 "Moon Clinic" and unmaintainable as a platform.

LIS is the proof the pattern works: a 137-migration, 66-model clinical vertical living as a
self-contained module on platform rails (BaseModel tenancy/audit, DataScope, PermissionDependencyRegistry,
SequenceService, `CreateJournalEntry`-only accounting). `Modules/Obgy` becomes the second instance of
that proven recipe — with HIS supplying the clinical primitives LIS never needed.

---

## 2. Module layout (exact nwidart modules and key tables)

### 2.1 `Modules/HIS` — the thin clinical platform (ONE module per settled D2; internal split by folders)

Internal domain folders (folders, not modules — D2 explicitly rejects premature module splitting):
`Mpi/`, `Scheduling/`, `Encounter/`, `Folio/`, `Orders/`, `Forms/`, `Adt/`, `Surgery/`.

| Domain | Key tables | Provenance |
|---|---|---|
| MPI bridge | *(no new patient table — D1)* `his_patient_spouses` (1:1 → `lab_patients`, replaces OBGY couple-in-one-row), `his_referrals`, `his_hospitals` | spec W2-1 item 7 (`scopeInternal()`), obgy §95 1.1, A5 |
| Scheduling/queue | `his_appointments`, `his_visit_periods` (slot capacity `max_no`, DB-transaction enforced), `his_clinic_closures` | OBGY A1 (`visits` booking half, `visit_periods`, `vacations`) — fills the 1/10 appointments gap |
| Encounter | `his_encounters` (encounter_number via SequenceService; `patient_id`→`lab_patients` internal; `type` opd/ipd/er/daycase; `status` FHIR subset; `attending_doctor_id`→`lab_doctors`; `parent_encounter_id`; `queue_position` replacing OBGY's `visitorder`/`enterordered`/`view` triple), `his_encounter_diagnoses` | D4 approved design; DDL per `md/08-data-spine.md` §2.2 |
| Folio/billing | `his_folios` (payer_type self_pay/insurance/b2b_account; decimal(12,3)), `his_folio_charges` (morph `source_type`/`source_id` + `UNIQUE(source_type, source_id)` — a clinical act bills exactly once; `service_code` per SBS/`sbscs_code` pattern; `unit_cost`+`cost_center_id` COGS fields), `his_folio_payments`, `his_deposits` (receipt-voucher + FIFO precedents) | D4; PostLabInvoice generalization (Generalization Register, spec line 96) |
| Orders hub (CPOE shape) | `his_prescriptions` + `his_prescription_items` (morph `prescribable`, `subject` enum patient/spouse — collapses OBGY's 10 `*drugs` clones), `his_service_orders` + `his_service_order_items` (morph `orderable` — collapses 8 `*invest` clones; lab items delegate to `CreateLabRequest`) | OBGY A2 — 4 generic tables replace 18 |
| Forms engine | `his_form_templates`, `his_form_sections`, `his_form_fields` (typed: text/number/lookup/diagram), `his_form_responses`, `his_form_response_values`, `his_lookups` (`domain`, `parent_id`, `title_ar`, `title_en`, `sort`) — generalization of OBGY's `presenthistoryquestions`/`presenthistoryanswers`/`gynaph` Q&A engine + the ~190-list lookup engine | OBGY A3, A8 |
| Clinical exam | `his_clinical_exams` (typed vitals), `his_exam_findings` (`body_system` enum, lookup-or-free-text) | OBGY A4 |
| ADT/beds (Phase 2) | `his_wards`, `his_rooms`, `his_beds`, `his_bed_assignments`, `his_encounter_movements` (admit/transfer/discharge) | net-new (0–1/10 today) |
| Surgery (platform-level per §95 2.6) | `his_surgical_bookings`, `his_operative_notes`, `his_operative_note_staff` (role enum), `his_anesthesia_types`, `his_incision_types` (self-referencing) | OBGY A5 — blueprint designed these module-agnostic for HMS |
| Media | spatie medialibrary + `his_annotated_diagrams`, `his_diagram_markers` (relative coordinates) | OBGY A7 |

### 2.2 `Modules/Obgy` — the specialty plug-in (bucket B only)

Two mechanisms, by sheet complexity:

- **Simple/configurable sheets → forms-engine content, not tables.** Present-history Q&A,
  per-body-system exam options, instruction templates, most of the ~190 curated vocabularies seed
  `his_lookups` under `obgy.*` domains and `his_form_templates` rows. Shipped as Obgy seeders
  (production export mandatory first — risk R7: dump lists are empty).
- **Complex structured sheets → dedicated `obgy_*` tables**, every header FK-ing
  `encounter_id → his_encounters` and `patient_id → lab_patients`:

| Asset | Tables |
|---|---|
| Pregnancy/ANC (3-generation merge) | `obgy_pregnancies` (merge by patient+LMP), `obgy_antenatal_visits` (GA computed accessor), `obgy_antenatal_ultrasounds`, `obgy_pregnancy_losses`, `obgy_delivery_registrations`, `obgy_4d_scan_bookings` |
| IVF/ICSI/IUI | `obgy_ivf_cycles` (one active per patient via partial unique index), `obgy_ivf_cycle_visits`, `obgy_follicle_measurements` (replaces the 26-column `ovst` grid), `obgy_oocyte_retrievals`, `obgy_embryo_transfers`, `obgy_cryopreservations`, `obgy_endometrial_preps`, `obgy_cycle_outcomes` |
| Andrology | `obgy_semen_analyses` (WHO flags), `obgy_hormone_results` + `obgy_hormone_definitions` (links to LIS) |
| Imaging | `obgy_imaging_studies` (unified header, study_type), `obgy_fetal_scan_findings`, `obgy_gyna_scan_findings` (+ follicle grid) |
| Endoscopy | `obgy_endoscopy_procedures` (kind enum), `obgy_endoscopy_findings`, `obgy_endoscopy_terms` |
| Gyn/infertility | `obgy_gyna_sheets`, `obgy_infertility_files` (replaces the 17-table `infertilitysheet` hub) |
| Specialty history | `obgy_menstrual_histories`, `obgy_obstetric_histories` + `obgy_obstetric_babies`, `obgy_contraception_histories`, `obgy_past_art_cycles` |
| LIS bridge | `obgy_lis_test_map` (276 legacy `invests` → LIS catalog; unmapped created in LIS) |

### 2.3 What does NOT exist

- No `Modules/Patients`, no `Modules/Appointments`, no `Modules/Bedding` (D2 rejected splitting).
- No new patient table, ever (D1).
- No Obgy billing, no Obgy drug catalog, no Obgy staff tables — Folio/ChargePosting, Core item
  master + Inventory `StockService`, and HRM `employees` respectively.

---

## 3. Role of OBGY assets — exact

OBGY contributes **four things and only four things**:

1. **Pattern donor for the HIS core (bucket A, ~32%).** The encounter/queue model, visit-period
   capacity, the sheet→prescription/order morph consolidation (18 clones → 4 tables), the Q&A
   engine that becomes the forms engine, per-body-system examination, surgical waiting-list +
   operative note, follow-up card, annotated-diagram media, and the lookup-engine shape. These are
   *designs distilled from ~10 years of production use* — they de-risk exactly the platform's two
   weakest scores (appointments 1/10, encounter 0/10). They enter HIS as **new Laravel-12 designs**,
   never as ported code (rule R12).
2. **First specialty content (bucket B, ~48%)** inside `Modules/Obgy`: forms templates + the
   dedicated `obgy_*` tables above, plus the production-only curated vocabularies (~190 lists)
   seeded into `his_lookups`.
3. **First pilot tenant + ETL rehearsal.** The legacy clinic has a forced security motive to migrate
   (confirmed SQLi, open mobile API, web-accessible dumps — immediate remediation on the legacy box
   regardless). Its production DB is the mandatory ETL target: `patients` → `lab_patients` internal
   rows (`statusno`→`mrn`, NID→`national_id` reusing NPHIES' `national_id_type`), husband data →
   `his_patient_spouses`, `visits` split (booking/queue half → `his_encounters`; rows with
   `detectionid IN (−99, 999, 9999)` are payments → folio transactions, never encounters), clinical
   rows attached via the patient+date heuristic matcher (risk R2).
4. **Domain validation of HIS P1.** OBGY's outpatient-only nature proves the P1 outpatient tier has
   standalone sellable value; it contributes nothing to P2 hoteling and therefore must not gate it.

OBGY is explicitly **not**: the MPI (couple-in-one-row, MAX+1 file numbers, zero FKs), the encounter
model (conflates appointment+encounter+payments with magic `detectionid` codes), the billing layer
(its own cURL `erpSellbill`/`client.obygyPatientId` hacks are natively replaced by
`partner_id`+`AccBpExt`+`CreateJournalEntry`), or a code source (R12).

---

## 4. Dependency graph and rules

```
                Core ◄── everything (BaseModel, DataScope, PermissionDependencyRegistry,
                          SequenceService, SettingsService, Attachments, BusinessPartner)
                  ▲
   Accounting ◄── HIS ──► LIS (forward: CreateLabRequest action)
        ▲          ▲ ▲──► HRM (employees), Inventory (StockService), NPHIES (claims)
        │          │
        │        Obgy ──► LIS (obgy_lis_test_map, lab catalogs — forward, read/order only)
        └──(JE action)
   Events backward only:  LIS ──events──► HIS │ Obgy     HIS ──events──► Obgy
```

Rules (numbered, enforceable in review):

1. **HIS imports Core, LIS, Accounting, HRM, Inventory, NPHIES — forward, via Actions/Services**
   (settled D2). LIS never imports HIS; LIS→HIS information flows only via the existing events
   `LabResultReleased` / `LabRequestCompleted` / `CriticalResultDetected`.
2. **Obgy imports HIS, Core, LIS — forward. HIS never imports Obgy.** This is D2's golden rule
   applied one level up, and it is what keeps Tier-2 "Moon Clinic" sellable without OBGY and the
   Obgy license key meaningful. No other module may import Obgy.
3. **HIS extension points are registries, not imports** — exactly the mechanism W1-4 builds:
   Obgy registers `ObgyPermissionDependencies` via `PermissionDependencyRegistry::register('obgy', …)`
   in `ObgyServiceProvider` boot (the LIS precedent at `LISServiceProvider`), registers its encounter
   tabs/sheet types in a HIS `EncounterContentRegistry` (same pattern), and posts charges through the
   `his_folio_charges` morph. HIS code never names a specialty.
4. **All GL posting funnels through `Modules\Accounting\Actions\CreateJournalEntry`** (the only
   accounting entry point), called by a Core `ChargePostingService` parameterized by settings prefix
   (`his.*`), extracted from `PostLabInvoice` per the approved Generalization Register. Obgy never
   posts GL directly.
5. **D5 stands for both modules:** LIS untouched in performance and behavior; zero added hot-path
   queries; BE+FE ship together; acceptance gate = suites green + before/after curl timing on
   worklist + reception; regression reverts the item.
6. **Anti-double-posting:** lab invoices enter the folio **by reference only** (D4);
   `UNIQUE(source_type, source_id)` on `his_folio_charges` makes single-posting a DB constraint.
7. Because nwidart does not enforce inter-module dependencies (known platform gap, Risk register #6),
   rules 1–4 are enforced by CI grep gates on `use Modules\\Obgy` outside `Modules/Obgy` and
   `use Modules\\HIS` inside `Modules/LIS`.

---

## 5. Event / FK contracts (real names)

### Existing (consumed, zero LIS change)

| Contract | Detail |
|---|---|
| `LabResultReleased`, `LabRequestCompleted`, `CriticalResultDetected` | `Modules/LIS/app/Events/`; HIS listener attaches results to the encounter; Obgy listener renders results inside clinical sheets via `obgy_lis_test_map`; critical alerts feed the HIS physician inbox |
| `lab_requests.encounter_id` | inert column (2026_06_10_160000) gets its FK → `his_encounters.id` at HIS kickoff (W1-3 plan) |
| `Modules/LIS/app/Actions/CreateLabRequest` | HIS/Obgy order labs by calling it with `encounter_id` and `source=in_patient` (enum exists since day 1); pricing, invoice, insurance, sequence numbering come free (D3 — declared template for radiology and all performers) |
| `lab_doctors.employee_id → employees.user_id` | W1-2 chain; `his_encounters.attending_doctor_id → lab_doctors` resolves clinician → HR → login identity |
| `lab_patients` named scopes | HIS/Obgy consume `LabPatient::internal()` only; B2B isolation by `external_lab_id` + `UNIQUE(company_id, ext_scope_key, national_id)` (W2-1) |
| `BusinessPartnerCreated → CreatePartnerAccounts` | AR sub-accounts auto-provisioned via `AccBpExt.ar_account_id` — replaces OBGY's cURL client sync |
| NPHIES | claim columns on the invoice (per `lab_invoices` 2026_04_03_000001 pattern) + `NphiesClaimService::submit()` → `nphies_transactions`; `FhirPatientBuilder` input generalized from `LabPatient` to the shared patient (budgeted task) |

### New in `Modules/HIS` (proposal)

| Event | Payload | Consumers |
|---|---|---|
| `EncounterOpened` / `EncounterClosed` | encounter_id, patient_id, type | Obgy (open sheet context), notifications |
| `PatientAdmitted` / `PatientTransferred` / `PatientDischarged` (P2) | encounter_id, bed ids | folio nightly bed-day job, nursing station |
| `FolioChargePosted` | folio_id, source_type, source_id, total | folio running totals; future revenue dashboards |
| `FolioClosed` | folio_id, payer split | payer-split invoice generation → `ChargePostingService` → `CreateJournalEntry(source_type='his_invoice')` |
| `AppointmentBooked` / `AppointmentCheckedIn` | appointment_id, encounter_id | queue screen, portal notifications |

### New in `Modules/Obgy` (proposal)

| Event | Payload | Consumers |
|---|---|---|
| `PregnancyOpened` / `PregnancyClosed` | pregnancy_id, outcome | HIS patient-360 timeline |
| `IvfCycleStageCompleted` | cycle_id, stage | HIS worklists, notifications |
| `PrescriptionDispenseRequested` | prescription_id | Inventory `StockService::decreaseStock` (movement_type `issue`, reference = prescription) per blueprint §2.3 |

Obgy never emits financial events — it posts `his_folio_charges` rows (`source_type='obgy_service'`,
`'obgy_imaging_study'`, …) and HIS owns the money from there.

---

## 6. Angular frontend organization

Follows the proven three-context LIS pattern (`md/04-frontend.md`):

- **`features/his/`** — standalone components, embedded at `/core/his/*` under MainLayout with
  `data: { permissions: ['his.'] }`; registered in module-activation so `moduleGuard` gates it.
  Plus a fullscreen clinic workspace `/clinic/*` with `HisLayoutComponent`, mirroring
  `lis-standalone.routes.ts` / `/lab/*` (app-in-app proven 3×: POS/HR/Lab).
- **`features/obgy/`** — embedded `/core/obgy/*` with `data: { permissions: ['obgy.'] }`; its
  screens mount **inside** the HIS encounter workspace as routed tabs contributed via a route-config
  registry (the FE twin of `EncounterContentRegistry`), so a Clinic-only license simply never loads
  the Obgy chunks (lazy `loadComponent` everywhere).
- **Services:** `core/services/his-*.service.ts`, `core/services/obgy-*.service.ts` —
  `providedIn:'root'`, `listAll()` forkJoin pagination, `X-Authorization` interceptor, unchanged.
- **Reuse, not rebuild:** clinic queues implement the server-driven worklist contract
  (`lis-worklist.service.ts` `WorklistCard`/`WorklistRow` + `USE_SERVER_WORKLIST`); the booking flow
  clones `request-wizard-v2` (patient → services → billing → payment); LIS lab ordering opens the
  existing wizard in embedded Dialog mode (D3 FE clause); clinical reports (antenatal record,
  ultrasound, operative note) clone `lis-html-report.service.ts` / `lis-report-pdf.service.ts`
  configurable-header engines; patient-facing released reports ride the `/p/:token` portal pattern.
- **Forms engine FE:** one dynamic-form renderer component in `features/his/forms/` consumes
  `his_form_templates`; Obgy sheets that are forms content need **zero new components**.
- **i18n:** `HIS` and `OBGY` UPPER_SNAKE namespaces in `en.json`/`ar.json`, English-first,
  RTL handled by `LanguageService`.
- **License/tier note:** server-side module gating is FE-only today (`auth.guard.ts:162`) — W2-2
  log-only middleware then enforcement is a hard prerequisite for selling Obgy as a priced key.

---

## 7. Migration & build phases (S ≈ ≤1 wk, M ≈ 2–4 wk, L ≈ 5–10 wk; one team unless noted)

| # | Phase | Content | Size |
|---|---|---|---|
| 0 | **Phase-0 readiness** (approved, not started — hard gate) | W1-1 DataScope hoist, W1-2 `employee_id`, W1-3 `CreateLabRequest` + `encounter_id`, W1-4 PermissionDependencyRegistry, W2-1 B2B hardening + `internal()` scopes, W2-2 gating log-only | **S** (~7–10 dev-days) |
| 0b | **Legacy OBGY remediation** (parallel, ops not dev) | Patch/firewall SQLi entry points, kill `mobileservices.php`, remove web-accessible dumps; **fresh production export** of `gynaph`, `sefo`, all lookup content (R7) | **S** |
| 1 | **HIS P1 — outpatient platform** | `Modules/HIS` scaffold + `his.*` permission registry; `his_encounters`/`his_appointments`/`his_visit_periods`/queue; FK `lab_requests.encounter_id`; Folio + `ChargePostingService` extraction (`lis.*`→`his.*`) + deposits; embedded LIS ordering; price-list/insurance generalization (largest register item); FE `/core/his` + `/clinic` workspace + appointments engine (biggest FE build) | **L** (8–14 pw, matches approved roadmap) |
| 1b | **Forms engine + lookup engine** (inside HIS P1, sequenced last so it is extracted against real Obgy templates) | `his_form_*`, `his_lookups`, dynamic-form FE renderer, seeding pipeline | **M** |
| 2 | **Obgy P1 — module + identity ETL** | `Modules/Obgy` scaffold, `ObgyPermissionDependencies`, EncounterContentRegistry tabs; ETL: `patients`→`lab_patients` + `his_patient_spouses` (dedup per R10), `visits` split into encounters vs folio payments (magic-code rule), vocab seeding from production export | **M** |
| 3 | **Obgy P2 — specialty clinical depth** | Pregnancy/ANC merge (3 generations), `obgy_ivf_*` stage tables, andrology, imaging studies + annotated diagrams, endoscopy, infertility file; simple sheets shipped as forms content; clinical report templates (FE) | **L** (≈ blueprint P3–P5) |
| 4 | **Obgy P3 — media + go-live** | 4.8 GB checksummed staged rsync → medialibrary (sonar 3.1 GB), signed URLs; pilot tenant cutover, parallel-run, legacy decommission | **M** |
| 5 | **HIS P2 — inpatient/ADT** (independent of Obgy; funded by Tier-2/3 revenue) | wards/rooms/beds + bed map, ADT movements, nightly bed-day charges → folio, nursing station | **L** (10–16 pw) |
| 6 | **HIS P3 extensions** | clinical pharmacy/eMAR over Inventory+POS, OR module consuming `his_surgical_bookings`/`his_operative_notes`, radiology via the same D3 contract | **M–L** each, independent |

Critical path to a **sellable Tier-3 "Moon Women's Health"**: Phase 0 → HIS P1 (+1b) → Obgy P1–P2
≈ **22–34 person-weeks**. Tier-2 "Moon Clinic" is sellable at the end of HIS P1 alone. HIS P2
(hospital tier) overlaps Obgy P2/P3 with a second stream if staffing allows.

---

## 8. Honest pros / cons

### Pros

1. **Zero reopened decisions.** Conforms to D1/D1b/D2/D3/D4/D5 verbatim; Phase-0 spec items are the
   literal first milestone. Competing proposals must formally reopen owner-approved decisions.
2. **License-separable tiers from one codebase**: Lab → Clinic → Women's Health → Hospital; the
   Clinic-never-imports-Obgy rule is the same mechanism that today keeps LIS sellable standalone.
3. **Proven template, third application**: Obgy repeats the LIS module recipe (prefixed tables, state
   enums, events+ordered listeners, Actions as entry points, JE-only accounting, registered
   permission maps, Core DataScope) — lowest-novelty path the codebase is already optimized for.
4. **OBGY's value lands where the platform is weakest** (appointments 1/10, encounter 0/10) as
   field-proven designs, while its unsalvageable 20% (bucket C) and its conflations (visits magic
   codes, couple-in-one-row) are structurally excluded.
5. **Forms engine turns specialty sheets into content, not code** — the next specialty (cardio,
   derma, dental) ships mostly as seeders + a few tables, compounding the platform investment.
6. **EMR scope-creep (Risk #7) is contained by construction**: HIS v1 stays administrative-financial;
   clinical depth lives only in the priced Obgy plugin where a paying segment funds it.
7. **Single posting source per charge is a DB constraint**, not a convention
   (`UNIQUE(source_type, source_id)`), closing the accounting double-posting risk (#4).

### Cons (honest)

1. **Obgy go-live waits on the platform critical path.** The legacy clinic's security urgency is
   real, but its replacement needs Phase 0 + HIS P1 first (~3–4 months). Mitigation: phase 0b
   remediates the legacy box immediately; OBGY waiting is bounded, not open-ended.
2. **The forms engine is net-new infrastructure with no Moon ERP precedent** and a known failure
   mode (over-generalization before a second consumer exists). Mitigation: extract it *against*
   OBGY's concrete templates (phase 1b sequenced last in P1), and allow complex sheets to bypass it
   into dedicated tables — which this proposal already does for IVF/ANC.
3. **Abstraction risk on Folio morphs and EncounterContentRegistry**: designed with one specialty in
   hand; the second specialty may force rework. Accepted: cheaper than the alternative (specialty
   logic fused into the core, unremovable later).
4. **Two-module coordination overhead**: every OBGY feature now crosses a contract (registry, event,
   morph) instead of a direct table join; more design discipline, slightly slower feature velocity
   inside Obgy.
5. **Dependency direction is convention-only** (nwidart enforces nothing; three inversions already
   exist). Mitigation is CI grep gates — workable but not architectural enforcement.
6. **Tension with the approved "v1 admin-financial" scoping**: Obgy P2 pulls clinical documentation
   earlier than the HMS roadmap planned. This proposal resolves it by funding/owning that depth in
   the plugin, but it is still more total scope in year one than the HMS roadmap alone.
7. **ETL risks unchanged by architecture**: patient+date heuristic encounter matching (R2),
   production-only vocabularies (R7), patient dedup (R10) — they exist under every option; this
   proposal at least rehearses them on a single pilot tenant.

---

## 9. Why not the alternatives (one paragraph each)

- **OBGY-as-HIS-core**: lacks Encounter (§90.1), lacks beds/ADT/ER/eMAR entirely, lacks any
  insurance cycle (cash/visa only) in a NPHIES-mandatory market, is 48% specialty content, and its
  code is barred by its own rule R12. It would also force reopening D1 (its patient model vs
  lab_patients) and D2 — formally blocked.
- **Everything-inside-Modules/HIS (OBGY sheets as HIS folders, no Obgy module)**: defensible under
  D2's "one module" letter, but it kills tier licensing (general hospitals carry OB/GYN tables and
  permissions), bloats the his.* permission graph, violates Risk #7's admin-financial v1 scoping
  inside the core itself, and forfeits the LIS-proven plugin economics. The board should treat
  "one Modules/HIS" as governing the *platform*, with specialties as peers — exactly the seat LIS
  already occupies.

---


# Competing Proposal — "OBGY-AS-SEED": Build Modules/HIS by Instantiating the OBGY Rebuild Blueprint

**Author stance:** senior solution architect, architecture review board submission
**Date:** 2026-06-11
**Thesis in one line:** Do not design the HIS core from abstract FHIR whiteboarding — *instantiate it from the only field-validated clinical design we own*: the OBGY rebuild blueprint (`obgy-erp-analysis.md` §95, 312 → ~85 tables), whose generic layer was *explicitly designed module-agnostic "so the HMS module can consume them"* (§95 2.6). Ship the women's-health hospital first against a real, forced-to-migrate paying customer, and let the generic HIS core be hardened by production use before any second specialty arrives.

---

## 0. What this proposal is NOT (honesty up front)

1. **Not a code port.** Zero legacy OBGY code is reused — hard rule R12 stands (confirmed system-wide SQLi in `patients.php` ~311–419, `financialreport.php`, `visits.php`, `sonar.php:243`; unauthenticated CORS-`*` `mobileservices.php`; 1.6 GB web-accessible dumps). What is reused is the **rebuild blueprint** — the already-written, Laravel-shaped target design of ~85 tables in §95 — plus the production data and ~190 curated Arabic clinical vocabularies.
2. **Not OBGY-schema-as-core verbatim.** The legacy `visits` table conflates appointment + encounter + payment ledger (magic `detectionid` −99/999/9999) and clinical tables never reference visits (§90.1). The blueprint *already fixes both* — Appointment/Encounter split, `PatientLedgerEntry` with explicit entry types, polymorphic order/prescription headers. We instantiate the *fixed* design, not the legacy one.
3. **Not a reopening of settled decisions.** All four binding constraints are honored and load-bearing here:
   - HIS is **ONE module `Modules/HIS`** (D2) — bucket-A generic tables live there under `his_*` names from migration #1.
   - **HIS imports LIS/Accounting/HRM forward; LIS never imports HIS** — backward only via `LabResultReleased` / `LabRequestCompleted` / `CriticalResultDetected`.
   - **LIS production behavior untouchable** (D5 acceptance gates apply to every phase below).
   - **B2B patients stay in `lab_patients`** (D1/D1b) — OBGY's `patients` ETLs *into* the shared `lab_patients` MPI as `internal()` rows; the couple/husband data goes to a 1:1 `his_patient_spouses` table, never into the MPI.

The difference from the rival "generic-HIS-first" proposal is **sequencing and source of truth**, not topology: instead of designing `Modules/HIS` top-down and hoping a specialty shows up to validate it, we derive `Modules/HIS`'s schema from the OBGY blueprint's bucket-A layer (~100 legacy tables → ~30 clean `his_*` tables), build `Modules/Obgy` (bucket B) immediately on top, and go live at the OBGY clinic — which has a *forced security motive* to migrate and supplies 10 years of real clinical data as the acceptance suite.

---

## 1. Why seed-from-OBGY beats design-from-scratch

| Argument | Evidence |
|---|---|
| **It attacks the platform's two weakest scores with proven material.** | HMS feasibility scorecard: appointments **1/10**, hoteling/folio 0–1/10, overall 6/10. OBGY blueprint §1.2 delivers a field-proven Appointment/Encounter/VisitPeriod/queue domain (day-period capacity `max_no`, drag-sortable `visitorder`, waiting-room display app `obgy/screen/`) distilled from years of real bookings. Nothing else in the company's possession addresses the 1/10 gap with anything but a whiteboard. |
| **The design cost is already paid.** | §95 is a complete migration blueprint: consolidation tables (18 clone tables → 4 generic), DDL-level target models, ETL phases P0–P7, risk register R1–R12. The rival proposal must produce an equivalent design document from zero; this proposal starts from a reviewed one. |
| **Every core table gets a production consumer on day one.** | Abstract cores rot: the feasibility report's own Risk #3 is "creeping duplication — the four print engines lesson". An Encounter/CPOE/Q&A engine with no tenant is exactly how a fifth print engine happens. Here, `his_encounters`, `his_prescriptions`, `his_lab_orders`, `his_history_questions`, `his_clinical_examinations` are all exercised by a paying clinic within the first release train. |
| **The genericity is by construction, not by hope.** | 10 identical `*drugs` clones + 8 identical `*invest` clones collapse into 4 polymorphic tables (`prescribable`, `orderable`) usable by cardiology/ortho/derma unchanged; the Q&A history engine (`presenthistoryquestions`/`answers`) and per-body-system exam model are specialty-neutral schemas with OB/GYN *content*. Content is data, not schema — a second specialty seeds different rows, not different tables. |
| **Unique content moat.** | ~190 curated Arabic clinical vocabularies (61% of the legacy schema) exist nowhere else in the market in this form; they seed `his_lookups`/`obgy_lookups` and make the product demo *clinically credible in Arabic* from the first screen. |
| **Earliest revenue, self-funding sequence.** | Tier 3 "Moon Women's Health" is sellable at the end of Phase C (~20–29 pw) — before the expensive inpatient build — and the OBGY clinic engagement itself is billable migration work. The rival sequence reaches its first clinical-content sale later. |

---

## 2. Module layout (exact nwidart modules and key tables)

### 2.1 `Modules/HIS` — the generic clinical core, harvested from OBGY blueprint bucket A

All tables follow LIS column conventions (`company_id` first, `decimal(12,3)`, `created_by/updated_by`, softDeletes, `branch_id` + Core `DataScope`), extend `App\Models\BaseModel` (TenantAware + Auditable + HasArabicContent).

| Sub-domain | Tables | Seeded from (legacy → blueprint) |
|---|---|---|
| Encounter spine (D4, design approved) | `his_encounters` (encounter_number, patient_id→`lab_patients`, type opd/ipd/er/daycase, status, attending_doctor_id→`lab_doctors`, parent_encounter_id, **queue_position**, branch_id), `his_folios` (payer_type self_pay/insurance/b2b_account, totals), `his_folio_charges` (morph source_type/source_id + `UNIQUE(source_type, source_id)`, service_code per `sbscs_code` pattern, cost_center_id, unit_cost COGS) | `visits` encounter-half + `endvisitreports` + `totalbalance` → Encounter/Folio/PatientLedgerEntry (§95 1.2); per data-spine DDL (md/08) |
| Scheduling & queue | `his_appointments`, `his_visit_periods` (capacity `max_no` enforced in DB transaction), `his_clinic_closures`, `his_queue` columns on encounters; public waiting-screen display | `visits` booking-half, `visit_periods`, `vacations`, `obgy/screen/` app (§95 1.2) |
| Service catalog | `his_services` (price, branch, SBS code field for NPHIES) | `detections` (§95 1.2) |
| CPOE — prescriptions | `his_prescriptions`, `his_prescription_items` (morph `prescribable`, `subject` enum patient/companion) | 10 `*drugs` clones → 2 tables (§95 consolidation) |
| CPOE — lab/service orders | `his_lab_orders`, `his_lab_order_items` (morph `orderable`, lab_test_id → LIS catalog) | 8 `*invest` clones → 2 tables; executes via `Modules/LIS/app/Actions/CreateLabRequest` |
| Clinical questionnaire engine | `his_history_questions`, `his_history_answer_options`, `his_patient_history_answers` | `presenthistoryquestions`/`answers` + runtime `gynaph` (§95, A3) |
| Examination engine | `his_clinical_examinations` (typed vitals), `his_examination_findings` (body_system enum, finding_option_id / free_text) | `examination` + 9 finding lookups (A4) |
| Perioperative (explicitly designed for HMS by §95 2.6) | `his_surgical_bookings`, `his_operative_notes`, `his_operative_note_staff` (role enum), `his_anesthesia_types`, `his_incision_types` (self-ref), `his_hospitals`, `his_pathology_results` | `op_wait_list`, `operativedetails`, anesthesia/incision cascades, `hospitalnames`, `pathology`/`histopath` (A5) |
| Encounter documentation | `his_followup_cards`, `his_instruction_templates`, `his_referrals` | `followupcard`, `instruction` (A6) |
| Companion pattern | `his_patient_spouses` (1:1 to `lab_patients`) | `patients` husband columns; generic `subject` discriminator (A9) |
| Vocabulary engine | `his_lookups` (domain, parent_id, title_ar, title_en, sort, is_active) | the ~190-list lookup engine (A8) — engine in HIS, OB/GYN *content* seeded by Obgy module |
| Media | spatie medialibrary + `his_annotated_diagrams`, `his_diagram_markers` | `sonar` 3.1 GB, `gtimage`/`gtdetail` (A7) |

### 2.2 `Modules/Obgy` — the women's-health specialty module (bucket B)

Owns: `obgy_pregnancies`, `obgy_antenatal_visits`, `obgy_antenatal_ultrasounds`, `obgy_pregnancy_losses`, `obgy_delivery_registrations`, `obgy_ivf_cycles` (one active per patient via partial unique index), `obgy_ivf_cycle_visits` + `obgy_follicle_measurements`, stage tables (`obgy_oocyte_retrievals`, `obgy_semen_samples`, `obgy_embryo_transfers`, `obgy_cryopreservations`, `obgy_endometrial_preps`, `obgy_cycle_outcomes`), `obgy_semen_analyses` (WHO flags), `obgy_hormone_results` + `obgy_hormone_definitions`, `obgy_imaging_studies` + `obgy_fetal_scan_findings` + `obgy_gyna_scan_findings`, `obgy_endoscopy_procedures/findings/terms`, `obgy_infertility_files`, `obgy_menstrual_histories`, `obgy_obstetric_histories` + `obgy_obstetric_babies`, `obgy_contraception_histories`, `obgy_past_art_cycles`, `obgy_lis_test_map` (276 legacy tests → LIS catalog), plus the OB/GYN lookup **content** rows in `his_lookups` namespaced `obgy.*`.

Every clinical row FKs `encounter_id → his_encounters.id`. Every billable act posts a `his_folio_charges` row through the HIS charge service. The module ships its own `ObgyPermissionDependencies` and setting definitions, exactly like `LisPermissionDependencies` / `LabSettingDefinitionSeeder`.

### 2.3 Untouched modules

`Modules/LIS` (governing constraint), `Modules/Accounting`, `Modules/HRM`, `Modules/Inventory`, `Modules/NPHIES` (generalized in Phase D, not rebuilt), `Modules/Core` (gains only the Phase-0 items: `DataScope`, `PermissionDependencyRegistry`, later `ChargePostingService`).

---

## 3. Role of OBGY assets — exact statement

**OBGY is the validated seed, the content donor, and the first proof tenant of Modules/HIS — never its codebase and never a dependency of it.**

- **Seed:** bucket A of the rebuild blueprint (~100 legacy tables → ~30 `his_*` tables) *is* the initial schema of `Modules/HIS`; D4's approved-but-deferred Encounter/Folio design is enriched with OBGY's proven queue/period/closure mechanics.
- **Content donor:** ~190 production-curated Arabic vocabularies (exported fresh from production — risk R7: the 2024 dump ships them empty, and RedBean fluid-mode tables `gynaph`/`sefo`/`followupdrugs` exist only in production) seed `his_lookups`; the ~2,000-item drug catalog masters into the Core Product item master.
- **Proof tenant:** the OBGY clinic — with three immediate-remediation security findings forcing migration — is the first paying customer; its 10-year dataset is the ETL rehearsal and acceptance corpus (patient dedup R10, encounter heuristic matcher R2).
- **Specialty module:** bucket B (~150 tables → ~40 `obgy_*` tables) becomes `Modules/Obgy`, importing HIS forward; **HIS never imports Obgy**, so Tier 2 "Moon Clinic" remains sellable without a single OB/GYN table installed.
- **Discarded outright:** bucket C (~60 tables), the entire `aw` framework, all legacy endpoints (R12), the cURL ERP sync (`client.obygyPatientId`, `erpSellbill`) — replaced natively by `lab_patients.partner_id` + `AccBpExt` + `CreateJournalEntry`.

---

## 4. Dependency graph and rules

```
                Modules/Core (DataScope, PermissionDependencyRegistry,
                              SequenceService, SettingsService, ChargePostingService*)
                     ▲  ▲  ▲  ▲
   Modules/Accounting│  │HRM│  │Inventory          Modules/LIS
        ▲            │  ▲  │  ▲                       ▲   │ events only
        │            │  │  │  │   forward imports     │   ▼
        └────────────┴──┴──┴──┴───── Modules/HIS ─────┘  (LabResultReleased,
                                          ▲               LabRequestCompleted,
                                          │ forward        CriticalResultDetected)
                                    Modules/Obgy
```

Rules (each one testable in CI):

1. `Modules/HIS` imports LIS/Accounting/HRM/Inventory/Core **forward** via Actions/Services only (`CreateLabRequest`, `CreateJournalEntry`, `StockService`, `SequenceService`).
2. `Modules/LIS` never imports HIS — backward information flows exclusively through the three existing events; no LIS migration, route, or hot path changes (D5 gate: suites green + before/after curl timing on worklist + reception, regression reverts the item).
3. `Modules/Obgy` imports HIS forward; **HIS never imports Obgy** — same golden rule one level up, protecting Tier 2 vs Tier 3 license separation.
4. Patient access from HIS/Obgy only via `LabPatient::internal()` named scope; no global scope on `lab_patients`; B2B rows never visible to clinical UIs.
5. All GL posting via `Modules/Accounting/Actions/CreateJournalEntry` behind the generalized `ChargePostingService` (settings prefix `his.*`); a clinical act bills exactly once — enforced by `UNIQUE(source_type, source_id)` on `his_folio_charges`; lab invoices enter the folio **by reference, never re-posted** (D4 anti-double-posting).
6. Permissions: `PermissionDependencyRegistry::register('his', HisPermissionDependencies::class)` and `::register('obgy', ...)` from each ServiceProvider boot — the W1-4 pattern.
7. Specialty modules never define their own patient, doctor, drug, service-price, or treasury tables — those are `lab_patients`, `lab_doctors` (+`employee_id` chain), Core Product, `his_services`, and the LIS/Core cashier stack respectively.

---

## 5. Event / FK contracts (real names)

### Existing — consumed as-is
| Contract | Direction | Source |
|---|---|---|
| `LabResultReleased`, `LabRequestCompleted`, `CriticalResultDetected` | LIS → HIS (events, backward) | `Modules/LIS/app/Events/`; dispatched at `LabResultController.php:516,1058`, `LabWorkflowService.php:75,120` |
| `Modules/LIS/app/Actions/CreateLabRequest(..., encounter_id)` | HIS → LIS (forward action, W1-3) | extracted byte-identical from `LabRequestController::store` ~172–471 |
| `lab_requests.encounter_id` → `his_encounters.id` | FK added at HIS kickoff | inert column live since migration `2026_06_10_160000` |
| `lab_doctors.employee_id` → `employees.id` → `users.id` | identity chain (W1-2) | HRM migration `2026_03_01_100003:18` |
| `lab_patients.partner_id` → `business_partners` → `AccBpExt.ar_account_id` | finance bridge | `CreatePartnerAccounts::ensurePartnerAccounts` |
| `Modules/Accounting/Actions/CreateJournalEntry` (`source_type='his_invoice'`) | HIS → Accounting | pattern: `PostLabInvoice.php` / `PostLabPayment.php` |
| `NphiesClaimService::submit()` → `nphies_transactions` | invoice → claims | generalized in Phase D (LabPatient/LabInvoice type-hints widened) |

### New — defined by Modules/HIS
| Event | Payload | Consumers |
|---|---|---|
| `EncounterOpened` / `EncounterClosed` | encounter_id, patient_id, type | Obgy sheet routing, dashboards |
| `AppointmentBooked` / `AppointmentCancelled` | appointment_id, period_id | queue board, portal, notifications |
| `FolioChargePosted` | folio_id, source_type, source_id, total | folio running totals, audit |
| `FolioClosed` | folio_id, payer split | payer-split invoice generation (self_pay / insurance) |
| `PrescriptionDispensed` | prescription_id, items[] | Inventory `StockService::decreaseStock` (movement_type=issue, reference morph) — replaces legacy `recepittmp`/`receiptdrugs` push |
| `SurgicalBookingScheduled` / `OperativeNoteSigned` | booking/note ids | OR list, pathology, folio charges |

`Modules/Obgy` defines its own domain events (`PregnancyOpened`, `DeliveryRecorded`, `IvfCycleStageCompleted`) consumed only inside Obgy and by reporting — HIS never listens to Obgy (rule 3).

---

## 6. Angular frontend organization

Follows the proven LIS triple-context pattern (embedded `/core/lis/*`, standalone `/lab/*`, public portal):

- **`features/his/`** — standalone components: reception desk + patient 360, appointment book + period management, **queue board** (server-driven, cloned from the `lis-worklist.service.ts` contract: WorklistCard/WorklistRow + primary actions, behind a feature flag), encounter workspace shell, folio/cashier screens (reusing LIS treasuries/cashier-shift/payment-method screens' structure and services), surgical booking list, generic sheet renderer (Q&A engine + exam engine driven by `his_history_questions`/`his_lookups` metadata).
- **`features/obgy/`** — specialty sheets (ANC, gyna, infertility, IVF monitoring grid with follicle chart, andrology, imaging with annotated diagrams) mounted *inside* the HIS encounter workspace via route children; lazy-loaded, hidden when the Obgy module/license key is off.
- **Standalone clinic layout** `/clinic/*` with `ClinicLayoutComponent`, mirroring `lis-standalone.routes.ts` + `LisLayoutComponent` (`/lab/*`) — same components as embedded mode.
- **Public surfaces:** waiting-room display route (reborn `obgy/screen/` app) and patient portal reusing the `/p/:token` + `PortalLinkDialogComponent` (QR/WhatsApp) pattern for appointment and report access.
- **Services:** `core/services/his-*.service.ts`, `obgy-*.service.ts` — `providedIn:'root'`, `listAll()` forkJoin pagination, `X-Authorization` interceptor; NgRx only for reference data (periods, services, lookups); operational screens use signals like the LIS worklists.
- **Permissions/i18n:** route `data.permissions=['his.']` / `['obgy.']` with `permissionGuard` dot-boundary matching + `*appCan`; `HIS`/`OBGY` UPPER_SNAKE namespaces in `en.json`/`ar.json`, English-first, RTL handled by `LanguageService`.
- **Reports:** `his-report.service.ts` and `obgy-report.service.ts` cloned from `lis-html-report.service.ts` (configurable header) + `lis-report-pdf.service.ts`; financial documents via the shared `shared/print` GenericPrintData engine. The booking wizard reuses `request-wizard-v2`'s 4-step pattern (patient → services → billing → payment); LIS lab ordering opens the existing LIS wizard in embedded Dialog mode (D3).

---

## 7. Build & migration phases (S/M/L; 1 person-week ≈ 5 dev-days)

| Phase | Content | Size | Estimate |
|---|---|---|---|
| **P0 — Platform Readiness** (approved, prerequisite, unchanged) | W1-1 DataScope hoist, W1-2 `employee_id`, W1-3 `CreateLabRequest` + `encounter_id`, W1-4 PermissionDependencyRegistry, W2-1 B2B hardening, W2-2 gating log-only | **S** | ~7–10 dev-days |
| **A — Spine + Front desk** | `his_encounters`/`his_folios`/`his_folio_charges` (FK `lab_requests.encounter_id` activated), `his_appointments`/`his_visit_periods`/queue board + waiting screen, `his_services`, Core `ChargePostingService` (generalized `PostLabInvoice`/`PostLabPayment`, `his.*` settings keys), cashier/treasury reuse, **ETL-1: OBGY patients → `lab_patients` internal rows** (`statusno`→`mrn`, NID→`national_id`, dedup per R10) + `his_patient_spouses` | **M** | 6–9 pw |
| **B — Generic clinical engines** | `his_prescriptions`/`his_lab_orders` (LIS ordering via `CreateLabRequest`, results via `LabResultReleased` listener), Q&A history engine, examination engine, `his_lookups` + **production vocabulary export** (R7 gate: fresh prod dump incl. `gynaph`), follow-up cards/instructions/referrals, media pipeline start (checksummed staged rsync of 4.8 GB) | **M** | 5–8 pw |
| **C — Modules/Obgy + go-live** | ANC (3-generation merge → `obgy_pregnancies`), gyna, infertility file, IVF cycle + monitoring grid, andrology, imaging + annotated diagrams, endoscopy; perioperative core tables (`his_surgical_bookings`/`his_operative_notes`) shipped here driven by real OR demand; **ETL-2: clinical history** (patient+date heuristic encounter matcher, R2) ; first tenant live = **Tier 3 Moon Women's Health** | **L** | 8–12 pw |
| **D — Generalize + second specialty pilot** | NPHIES generalization (widen `LabPatient`/`LabInvoice` type-hints, `nphies_*` columns on HIS invoices per `2026_04_03_000001` pattern), price-list/insurance generalization (the spec's largest register item), one thin second specialty (e.g. general-medicine clinic) to *prove* the engines are content-only | **M** | 4–6 pw |
| **E — Inpatient patch** (honest: OBGY contributes ~nothing here) | wards/rooms/beds + bed map, ADT, nightly bed-day charges → folio, deposits on receipt-voucher/FIFO precedents, basic nursing station + eMAR, ER triage | **L** | 10–16 pw |

**Cumulative:** women's-health hospital sellable at ~20–29 pw (after P0); proven-generic outpatient platform at ~24–35 pw; full general hospital (Tier 4) at ~34–51 pw. Comparable to the approved roadmap's 21–35 pw to inpatient demo, but with a paying reference customer and a content-rich product landed roughly halfway through instead of at the end.

---

## 8. Where this honestly breaks — and the patches

| Break | Honest statement | Patch |
|---|---|---|
| **Inpatient spine (ADT/beds/ER/eMAR/nursing)** | OBGY is outpatient-only; `floors`/`devices`/`device_tracking` were incomplete and dropped (§1.16). 5 of 14 canonical HIS capabilities get **zero** seed from this proposal. | Phase E is budgeted as a full net-new build (10–16 pw) per the approved P2 design; the Encounter spine carries `type=ipd` and `parent_encounter_id` from day one so admission becomes a new encounter type + bed-day charge source, not a schema rework. |
| **OB/GYN bias leaking into the "generic" core** | The exam finding lookups (breast/hirsutism/thyroid), the `subject=husband` discriminator, and the day-period scheduling model all carry women's-clinic fingerprints. | Hard review gate at every Phase A/B PR: schema must be content-free (findings/lookups are data rows in `his_lookups`, `subject` generalized to patient/companion); Phase D's second-specialty pilot is the *binding acceptance test* of genericity, scheduled before inpatient money is spent. |
| **Scheduling model may be too weak** | OBGY's capacity-per-day-period model is simpler than slot/diary/multi-resource scheduling that dental/radiology will demand. | `his_visit_periods` is the v1 engine; the appointment table carries `scheduled_at` + `resource` columns from day one so a slot engine is additive. Acknowledged as the highest-risk seed: budget +1–2 pw contingency in Phase A. |
| **Collision with approved v1 admin-financial scoping (Risk #7, EMR scope creep)** | This proposal ships deep clinical documentation in Phase C — earlier than the HMS report's "EMR is a separate later decision". | Explicit owner reconciliation required at board level: clinical depth lives **only behind the Obgy license key**, funded by the women's-health segment; the HIS core itself stays admin-financial + thin generic engines, consistent with the tiering rule. This is a scoping amendment, not a topology change. |
| **No insurance seed** | Legacy OBGY is cash/visa only; the Saudi-critical NPHIES/claims flow gets zero acceleration from OBGY. | Phase D reuses the production NPHIES module wholesale (generalizing type-hints + invoice columns) — same cost in either proposal; it is simply not a point in OBGY's favor. |
| **Single-customer gravity** | Designing against one clinic risks overfitting and revenue concentration. | The Phase D second-specialty gate + the rule "HIS never imports Obgy" structurally prevent overfit; Tier 2 Moon Clinic (HIS without Obgy) is demoable at end of Phase B for pipeline diversification. |
| **ETL risks R2/R7** | Clinical rows never reference `visits` → encounter linkage is heuristic (patient+date); curated vocabularies exist only in production. | R7: a fresh production export is a Phase B entry gate (blocking). R2: heuristic matcher with confidence scores, unmatched rows land as date-anchored "historical record" encounters flagged `source=migration`; clinicians review queues, never silent guesses. |
| **Legacy security exposure during the build** | The legacy system stays in production for months. | Immediate remediation on legacy (already mandated): close `mobileservices.php`, remove web-accessible dumps, parameterize the four confirmed SQLi sites — billed as part of the migration engagement, independent of this architecture. |

---

## 9. Summary verdict requested from the board

Approve `Modules/HIS` whose initial schema is the OBGY blueprint's module-agnostic layer (§95 2.6 designed it for exactly this), with `Modules/Obgy` as its first specialty consumer and the OBGY clinic as first tenant; sequence revenue Tier 1 → 3 → 2 → 4; honor every settled decision (one HIS module, forward-only imports, untouchable LIS, shared `lab_patients` MPI); and accept two explicit amendments: (1) clinical depth ships early but only behind the Obgy license key, (2) a second-specialty genericity gate is mandatory before inpatient (Phase E) funding.

---


# Proposal P-1 — ERP-Maximalist / Thin-HIS
## "HIS as an Orchestration Spine: maximum ERP reuse + a metadata-driven clinical forms engine"

Submitted to the architecture review board, 2026-06-11.
Stance: build the **smallest defensible HIS**. Every administrative capability rides an existing,
audited Moon ERP engine; clinical documentation is **content, not schema**, served by one generic
forms/EAV engine seeded with OBGY's ~190 production vocabularies. Hard-coded clinical tables are
admitted only where metadata demonstrably breaks (and this proposal names those places honestly).

All binding decisions are honored as-is: HIS is ONE module `Modules/HIS` (D2); dependency
direction HIS → LIS/Accounting/HRM forward, LIS NEVER imports HIS, events backward only
(`LabResultReleased` / `LabRequestCompleted` / `CriticalResultDetected`); LIS production behavior
untouchable (D5); `lab_patients` is the MPI, B2B rows stay (D1/D1b); D3 `CreateLabRequest`
contract; D4 Encounter/Folio design.

---

## 1. Thesis

The HMS feasibility scorecard already says it: **financial backbone 9/10, cashier "ready",
insurance+NPHIES "ready", pharmacy store "ready", patient master ~80%** — while the gaps
(appointments 1/10, folio 0–1/10) are thin coordination entities, not engines. Meanwhile the
single biggest cost driver in every competing proposal is the **~85-table clinical schema** from
the OBGY blueprint and the 19 specialty screens it implies.

This proposal removes that cost driver. Observation from the legacy evidence: OBGY itself ran its
most-used clinical history capture for a decade on a **metadata engine**
(`presenthistoryquestions` / `presenthistoryanswers` + runtime `gynaph`) and kept **61% of its
schema (~190 tables) as pure lookup vocabulary**. The legacy system is, accidentally, a
proof-of-concept that women's-health documentation is mostly *configurable content*. We
industrialize that insight instead of transcribing 312 tables into 85 hard ones.

What is actually net-new hard schema in this proposal: **~17 tables in `Modules/HIS`** plus **3 in
a content pack**. Everything else is reuse:

| Hospital capability | Engine reused (no new build) |
|---|---|
| Patient master / MPI | `lab_patients` via `LabPatient::internal()` (D1, W2-1 item 7) |
| Patient ledger / AR | `business_partners` + `AccBpExt.ar_account_id` auto-created by `Modules\Accounting\Listeners\CreatePartnerAccounts` |
| Folio → GL posting | `Modules\Accounting\Actions\CreateJournalEntry` via a Core `ChargePostingService` extracted from `Modules/LIS/app/Actions/PostLabInvoice.php` (the spec's own deferred "unified charge-posting service") |
| Deposits / advances | Accounting receipt vouchers + FIFO allocation (D4 approved pattern) |
| Cashier, treasuries, shifts, Tamara/Tabby | LIS cashier stack, hoisted per the approved Generalization Register (HIS P1 item) |
| Insurance contracts, price lists | LIS named price lists + coverage, generalized to polymorphic billables (largest register item, already scheduled) |
| Claims / eligibility / preauth | `Modules/NPHIES` (`NphiesEligibilityService`, `NphiesClaimService::submit`, `nphies_transactions`) with `FhirPatientBuilder` input generalized from `LabPatient` to the shared patient |
| Pharmacy store | Inventory warehouses (`warehouse` per pharmacy) + `StockService::increaseStock/decreaseStock/getIssueCost` + Core Product master; POS for OTC |
| Lab | LIS **as-is**: orders via `Modules/LIS/app/Actions/CreateLabRequest` (D3) with `lab_requests.encounter_id`; results back via existing events |
| Hormones / semen analysis | **LIS catalog entries**, not HIS tables — LIS already owns reference ranges, validation workflow, machine interfacing; WHO semen flags are LIS reference ranges |
| Staff, rosters, payroll | HRM (`employees.user_id` chain; shifts already carry `is_night_shift`, `nursing_extra_minutes`; `PayrollAccountingService`) |
| Doctor identity | `lab_doctors.employee_id` (W1-2) → `employees` → `users` |
| Equipment maintenance | CMMS work orders |
| Numbering, settings, attachments, audit, tenancy, branches | `SequenceService`, `SettingsService` (`his.*` keys), Core `Attachment`/`HasAttachments`, `BaseModel` (TenantAware + Auditable + SoftDeletes + HasArabicContent), `DataScope` (W1-1) |
| Permissions | spatie + `PermissionDependencyRegistry::register('his', ...)` (W1-4) |
| Approvals | extend `ApprovalModule` enum with `HIS` |
| Patient portal | LIS portal-token pattern (`portal_link_token`, `/p/:token`) |

---

## 2. Module layout (exact nwidart modules and key tables)

### 2.1 `Modules/HIS` — the only new platform module (one module per D2; internal split by folders)

**Encounters/** (D4 design, columns per `md/08-data-spine.md`):
- `his_encounters` — `encounter_number` (SequenceService), `patient_id → lab_patients` (internal-only writes), `type` (opd|ipd|er|daycase), `status`, `attending_doctor_id → lab_doctors`, `parent_encounter_id`, `queue_position`, `branch_id`, `started_at/ended_at`
- `his_folios` — `encounter_id`, `payer_type` (self_pay|insurance|b2b_account), `insurance_contract_id`, decimal(12,3) totals
- `his_folio_charges` — morph `source_type/source_id` with **UNIQUE(source_type, source_id)** (a clinical act bills exactly once), `service_code` (SBS, per `lab_investigations.sbscs_code` pattern), `unit_cost`/`cost_center_id` for COGS JEs
- `his_deposits` — thin wrapper rows referencing Accounting receipt vouchers; FIFO allocation against folio invoices

**Scheduling/** (the 1/10 gap; domain model lifted from OBGY `visits`/`visit_periods`/`vacations` design, not code):
- `his_appointments` — booking half of legacy `visits`; `source` (reception|portal|mobile|referral)
- `his_visit_periods` — capacity slots (`max_no` enforced in a DB transaction)
- `his_clinic_closures` — replaces never-wired `vacations`

**Forms/** (the clinical documentation engine — the heart of this proposal):
- `his_form_definitions` — `code`, `title`/`title_ar`, `specialty_key` (license gate), `schema` JSON (field defs: type, lookup_domain, validation, repeat-group, computed expressions), `print_template_id`
- `his_form_versions` — immutable published versions; a response always points at the version it was captured with
- `his_form_responses` — `form_version_id`, `encounter_id → his_encounters`, `episode_id` nullable, `patient_id`, `subject` enum (patient|spouse — OBGY's `forhusband` discriminator generalized), `values` JSON, status workflow (draft|signed|amended)
- `his_form_response_index` — typed projection for reporting: `(response_id, field_key, value_numeric, value_text, value_date, value_lookup_id)`, maintained on save — this is the honest answer to "EAV can't be queried"
- `his_lookups` — `(domain, parent_id, code, title_ar, title_en, sort, is_active)` — the single table replacing OBGY's ~190 lookup tables; content seeded **from production export** (risk R7)

**Registries/** (hard tables where metadata legitimately breaks — see §6):
- `his_episodes` — generic EpisodeOfCare header (`type`: pregnancy|infertility_file|chronic_program, `started_at`, `status`, key dates JSON); a pregnancy is an episode, not a bespoke table
- `his_observations` — typed clinical time-series: `(patient_id, episode_id?, encounter_id?, code, value_numeric, unit, body_site, observed_at)` — handles ANC growth curves, IVF follicle measurements, vitals trending as **rows**, indexable and chartable
- `his_patient_spouses` — 1:1 specialty companion record (legacy `husdandname…` columns); the MPI stays clean
- `his_prescriptions` / `his_prescription_items` — orders are workflow objects, not documentation: `product_id → Core products`, dose metadata, `subject` enum; dispense fires `PrescriptionDispensed` → Inventory issue

**Actions/** (house pattern): `OpenEncounter`, `PostFolioCharge`, `CloseFolio`, `GenerateFolioInvoices`, `PostHisInvoice`/`PostHisPayment` (thin wrappers over Core `ChargePostingService`), `DispensePrescription`.

### 2.2 `Modules/ObgyPack` — a license-keyed **content pack**, not a clinical platform

- **Zero controllers for sheets, near-zero migrations.** Contents: seeders publishing ~19 OBGY
  form definitions (ANC visit, gyna sheet, infertility file sections, endoscopy report, ultrasound
  templates with diagram-annotation fields), `his_lookups` content for the ~190 production
  vocabularies, observation-code catalog (follicle, endometrium, BHCG, GA…), print templates,
  `PermissionDependencyRegistry::register('obgy', …)`, and the ETL artisan commands (P0–P7
  playbook from `obgy-erp-analysis.md` §95).
- The **only hard tables** (metadata genuinely breaks — §6): `obgy_ivf_cycles` (stage state
  machine, one-active-per-patient partial unique), `obgy_embryos` (cryopreservation inventory has
  legal/chain-of-custody weight; cannot be JSON), `obgy_lis_test_map` (276 legacy `invests` →
  LIS catalog ids).
- Andrology/hormones ship as **LIS investigation seeders** (reference ranges = WHO flags), not tables.

### 2.3 Core hoists (no new module; all already named in the approved Generalization Register)

- `Modules/Core/app/Services/ChargePostingService.php` — extraction of `PostLabInvoice`/`PostLabPayment`
  parameterized by settings prefix (`lis.*` → `his.*`); still calls `CreateJournalEntry` with
  `source_type='his_invoice'`; COGS JE by `cost_center_id` preserved; LIS call sites unchanged.
- Payment methods / treasuries / cashier shifts hoist with LIS compatibility bridges.
- Price lists + insurance coverage generalized to polymorphic billables.
- `FhirPatientBuilder` input widened from `Modules\LIS\Models\LabPatient` to the shared patient model
  (same table, promoted namespace — D1).

### 2.4 Angular FE organization

- `features/his/` standalone components; `core/services/his-*.service.ts` (`providedIn:'root'`,
  `listAll()` forkJoin pagination, `X-Authorization`); routes under `/core/his/*` with parent
  `data.permissions=['his.']`; optional fullscreen `/clinic/*` layout cloning `LisLayoutComponent`.
- **One dynamic renderer instead of 19 sheet screens**: `his-form-renderer` + a field-widget
  library (lookup select fed by `his_lookups`, numeric-with-range, date/Hijri, repeating group,
  diagram annotator over Core attachments, computed fields e.g. GA/BMI). OBGY's screens become
  JSON, reviewed by clinicians, not Angular code.
- Two bespoke widgets are admitted (see §6): an **observation grid** (spreadsheet-like IVF
  monitoring / ANC flow-sheet over `his_observations`) and an **episode timeline**.
- Queues reuse the server-driven worklist contract (`lis-worklist.service.ts` shape → `his-queue.service.ts`);
  booking reuses the `request-wizard-v2` 4-step pattern; clinical printing clones the configurable
  `lis-html-report.service.ts` engine driven by form definitions; patient portal reuses `/p/:token`.
- `features/obgy-pack/` holds only the IVF cycle screen and ETL/admin views; everything else is content.
- Translations: `HIS` + `OBGY` namespaces in `en.json`/`ar.json`; module activation via `moduleGuard`
  + the Phase-0 W2-2 server-side gating once enforced (tier licensing requirement).

---

## 3. Dependency graph and rules

```
            ┌────────────────────────── events only (backward) ──────────────┐
            ▼                                                                 │
Modules/ObgyPack ──▶ Modules/HIS ──▶ Modules/LIS (CreateLabRequest, internal())
                          │  ├─────▶ Modules/Accounting (CreateJournalEntry via Core ChargePostingService)
                          │  ├─────▶ Modules/HRM (employees, shifts)
                          │  ├─────▶ Modules/Inventory (StockService)
                          │  ├─────▶ Modules/NPHIES (eligibility/preauth/claims)
                          │  └─────▶ Modules/Core (SequenceService, SettingsService, Attachment, DataScope, PermissionDependencyRegistry)
```

Rules (extending the settled D2 golden rule one level up):
1. HIS imports LIS/Accounting/HRM/Inventory/NPHIES/Core **forward** (actions/services); **LIS never imports HIS** (binding); LIS→HIS information flows only via existing events.
2. **HIS never imports ObgyPack**; ObgyPack imports HIS. This keeps the Clinic tier sellable without women's-health content — the exact rule that keeps LIS sellable standalone, applied again.
3. No module reads `lab_patients` except through the promoted shared model's named scopes (`internal()` / `forLab($labId)`); no blanket global scope (spec constraint #2).
4. All GL postings go through `CreateJournalEntry` only, with module `source_type`/`entry_type`; no module writes `journal_entries` directly; folio absorbs lab invoices **by reference only** (D4 anti-double-posting).
5. ObgyPack contributes content via seeders + registries (`PermissionDependencyRegistry`, form definitions, lookups) — never via Core/HIS code edits.
6. Specialty hard tables are allowed only with a board-approved "metadata-break justification" (this proposal pre-approves exactly three: `obgy_ivf_cycles`, `obgy_embryos`, `obgy_lis_test_map`).
7. D5 stands: zero added queries on LIS hot paths; BE+FE ship together; before/after timing gate on worklist + reception endpoints.

---

## 4. Event & FK contracts

**Existing (consumed, never modified):**
- `Modules/LIS/app/Events/LabResultReleased.php`, `LabRequestCompleted.php`, `CriticalResultDetected.php` → HIS listeners attach results/alerts to the encounter (dispatched today at `LabResultController.php:516,1058`, `LabWorkflowService.php:75,120`).
- `BusinessPartnerCreated` → `Modules\Accounting\Listeners\CreatePartnerAccounts` (patient AR accounts come free via `lab_patients.partner_id`).

**New (Modules/HIS/app/Events):**
- `EncounterOpened(encounter_id, patient_id, type)` / `EncounterClosed(encounter_id)`
- `FolioChargePosted(folio_id, source_type, source_id, total)` — the universal charge-capture hook for any clinical module (and for radiology/OR later, same D3 template)
- `FolioClosed(folio_id)` → payer-split invoice generation (self_pay / insurance shares; preauth ref from `nphies_preauths`)
- `PrescriptionDispensed(prescription_id, encounter_id)` → Inventory `StockService::decreaseStock(reference_type='his_prescription')` + folio charge
- `FormResponseSubmitted(form_response_id, form_code, encounter_id)` — generic reaction hook (e.g., ObgyPack listener advances an IVF cycle stage when the retrieval form is signed)
- `AppointmentBooked` / `AppointmentCheckedIn(appointment_id → encounter created)`

**FK contracts:**
- `lab_requests.encounter_id` → `his_encounters.id` (column live and inert since 2026_06_10_160000; FK added at HIS kickoff per W1-3 docblock)
- `his_encounters.patient_id` → `lab_patients.id` (internal rows only); `his_encounters.attending_doctor_id` → `lab_doctors.id` → `employees` via `lab_doctors.employee_id` (W1-2) → `users`
- `his_folio_charges` UNIQUE(`source_type`,`source_id`); `his_form_responses.form_version_id` → immutable version; `his_prescription_items.product_id` → Core `products`
- Invoices carry the `nphies_claim_status` / `nphies_preauth_ref` / `nphies_covered_amount` / `nphies_copay_amount` column set proven on `lab_invoices` (2026_04_03_000001); claims log to `nphies_transactions`.

---

## 5. Role of OBGY assets in this architecture

OBGY contributes **content, vocabulary, domain knowledge, and data — zero code** (its own blueprint
rule R12 forbids porting any endpoint; SQLi confirmed system-wide):

1. **The ~190 curated vocabularies** (production export mandatory — dump lists are empty, risk R7) become `his_lookups` rows: the single most valuable seeding asset for the forms engine.
2. **The Q&A history engine** (`presenthistoryquestions`/`presenthistoryanswers`/`gynaph`) is the *existence proof* of metadata-driven clinical capture and maps 1:1 onto `his_form_definitions`.
3. **19 specialty sheets** (ANC, gyna, infertility, endoscopy, ultrasound templates…) become reviewed JSON form definitions in ObgyPack — bucket B as content.
4. **Bucket-A patterns** inform the thin spine: `visits`/`visit_periods` → appointments+queue design; the consolidation of 18 `*drugs`/`*invest` clone tables → `his_prescriptions` + the D3 lab-order path; per-body-system exam → an examination form definition + `his_observations` vitals.
5. **Historical data** ETLs into: `lab_patients` (wife → patient, `statusno`→`mrn`; husband → `his_patient_spouses`), `his_encounters` (legacy `visits` minus the `detectionid IN (−99,999,9999)` payment rows, which split into folio payments), form responses (patient+date heuristic matcher, risk R2), `his_observations` (follicle grids, growth data), Core attachments (4.8 GB media, checksummed).
6. **The OBGY clinic is the pilot tenant** for Tier 3, with a forced security motive to migrate.

OBGY is explicitly **not** the HIS core and **not even a clinical schema donor** here: it is the
content pack that proves the forms engine on day one.

---

## 6. Honest confrontation: where metadata-driven breaks

1. **IVF cycle management is not a form.** The stimulation grid (legacy `ovst`, 26 columns,
   day-by-day per-ovary follicle counts), the stage state machine
   (retrieval→fertilization→transfer→cryo), and embryo inventory carry workflow, computation and
   legal weight. Concession: `obgy_ivf_cycles` + `obgy_embryos` hard tables, monitoring data in
   typed `his_observations`, and a bespoke grid widget. The "one renderer" claim erodes exactly at
   the product's flagship feature — acknowledged.
2. **Reporting performance.** "All patients with BMI>30 and a failed cycle in 2025" against JSON
   blobs is slow and miserable. Mitigation is real but costly: the `his_form_response_index`
   projection maintained on every save, generated/indexed columns for hot fields, and nightly
   marts for KPIs. This is permanent engineering overhead, and projection drift is a new bug class.
3. **No referential integrity inside JSON.** `value_lookup_id`s can dangle; validation is
   app-layer only — a step down from the platform's FK discipline. Partial mitigation via the
   index table FKs and save-time validation against the form version.
4. **Clinical logic leaks out of metadata.** GA computation, G/P formula, WHO semen flags,
   Westgard-style rules: computed-field expressions cover the simple half; the rest becomes a
   rules layer or hard code (semen/hormones are deliberately pushed to LIS for this reason).
5. **Form versioning burden.** Editing a definition with 10k responses demands version pinning,
   migration tooling, and version-aware printing. Solved by `his_form_versions`, but it is
   discipline the team must keep forever.
6. **NPHIES/FHIR mapping** from generic responses is harder than from typed tables;
   `FhirServiceItemBuilder` wants codes. Mitigation: billables never live in forms — charges go
   through `his_folio_charges.service_code`; clinical FHIR export (if ever needed) maps per
   form definition.
7. **The engine can become its own product** — the platform's "four print engines" lesson (risk
   register #3). Guard: the renderer and schema spec are frozen behind a small contract; new field
   types require board sign-off.
8. **Commercial optics**: "deep IVF support" implemented as forms may look shallow against
   specialty competitors; the IVF hard-table concession plus the grid widget is the answer, and it
   must be demo-quality.

---

## 7. Build phases and sizing

| Phase | Content | Size | Estimate |
|---|---|---|---|
| P0 | Approved Phase-0 spec as-is (W1-1..W2-2): DataScope hoist, `employee_id`, `CreateLabRequest`+`encounter_id`, PermissionDependencyRegistry, B2B hardening, gating log-only | S | ~7–10 dev-days (already approved) |
| P1a | Spine: `his_encounters/folios/folio_charges/deposits`, `OpenEncounter`/`CloseFolio` actions, Core `ChargePostingService` extraction, cashier/treasury hoist, `lab_requests.encounter_id` FK + LIS-order embed (D3), price-list/insurance generalization | L | 6–9 person-weeks |
| P1b | Scheduling: appointments + visit periods + queue (server-driven worklist contract), reception desk, patient-360 over MPI | M | 3–5 pw |
| P1c | Forms engine: definitions/versions/responses/index, `his-form-renderer` + widget library, lookup admin, configurable clinical print | M–L | 4–6 pw |
| P2 | ObgyPack: 19 form definitions authored+clinically reviewed, ~190 vocab ETL from production, IVF cycle tables + grid widget, semen/hormone LIS seeders, full data ETL (P0–P7 playbook) + pilot tenant go-live | L | 8–10 pw |
| P3 | Inpatient (unchanged from approved roadmap: ADT, beds, bed-day charges to folio, nursing) — deferred until tiers 2–3 revenue | L | 10–16 pw |

**Total to a sellable Moon Women's Health tier (through P2): ≈ 21–30 person-weeks**, with the
Clinic tier (P1a–P1c) sellable standalone at ≈ 13–20 pw. Inpatient later, unchanged.

---

## 8. Pros / Cons summary

**Pros**
- Smallest net-new surface in any proposal: ~17 HIS tables + 3 pack tables vs ~85 blueprint tables; admin functions ride engines already graded 9/10 and "ready".
- Honors every binding decision natively; v1 stays administrative-financial (Risk #7 contained *by construction* — clinical depth is content, license-keyed, separately funded).
- Any future specialty (cardio, derma, dental) = a content pack with zero migrations — the fastest specialty onboarding story in the market, perfectly aligned with the 4-tier licensing model.
- One FE renderer replaces ~19 specialty screens; OBGY clinicians review JSON content, not code.
- ETL friendliness: legacy clinical rows (which never reference `visits`) land as form responses without violating hard FKs; the patient+date matcher tolerance lives in content, not schema.
- The OBGY vocabulary corpus and Q&A engine are immediately monetized as seed content.

**Cons**
- EAV/JSON reporting costs are real and permanent (projection maintenance, marts, a new bug class).
- The flagship IVF feature needs hard tables and a bespoke grid anyway — the purity claim has a hole at the most visible spot.
- Referential integrity and type safety inside responses are app-layer only.
- Forms-engine scope creep risk (the print-engines lesson) requires standing governance.
- Clinical analytics/research queries will always be second-class vs typed schemas; if the product later pivots to research-grade EMR, a partial re-materialization to hard tables is the exit path (the index table makes that migration mechanical, but it is a cost).
- Form authoring + clinical review becomes a new discipline/role the team does not have today.

---


# 20 — Final Architecture Synthesis (Execution Spec)

**Status:** Board-level final recommendation, post 3-proposal / 3-judge adversarial review.
**Decision:** Adopt **P-PLATFORM-FIRST** (winner on 2 of 3 judge lenses: Clinical Operations 8, Business/Product 8) **amended with the must-steal items** from OBGY-AS-SEED (Engineering lens winner, 8) and Thin-HIS/ERP-Maximalist (rejected as architecture, raided for clinical-safety ideas).
**Working name:** *Platform-First, OBGY-Seeded.*

Binding constraints honored verbatim (never reopen):
- **D1:** MPI = `lab_patients` upgraded in place; no second patient table; access via named scopes `internal()` / `forLab()`; **no blanket global scope**. B2B patients stay in `lab_patients` (`external_lab_id` + `UNIQUE(company_id, ext_scope_key, national_id)`).
- **D2:** HIS is **ONE** module `Modules/HIS`. HIS imports LIS/Accounting/HRM/Inventory/Core/NPHIES **forward only**; LIS **never** imports HIS; backward info via existing LIS events only.
- **D3:** Service-performer contract = Action entry point + `encounter_id` (template: `Modules/LIS/app/Actions/CreateLabRequest`, W1-3 extraction).
- **D4:** Encounter/Folio design approved, tables created at HIS kickoff; lab invoices enter the folio **by reference**, never re-posted.
- **D5:** LIS untouched in performance and behavior. Zero added hot-path queries. BE+FE in one deploy. Acceptance gate per item: all suites green + before/after curl timing on worklist + reception; regression reverts the item.

---

## 1. Verdict summary

**Q: Is OBGY the core of HIS? — No.**
- OBGY has no Encounter (clinical tables never reference `visits` — analysis §90.1); no inpatient/ADT/ER/eMAR; no insurance cycle (cash/visa only); ~48% women's-health-only content; zero portable code (PHP 5.6, system-wide SQLi, rule R12).
- Its own migration blueprint (§95 2.6) designs Encounter/OperativeNote/SurgicalBooking/queue module-agnostic "so the HMS module can consume them".

**Q: Separate module or merged? — Separate.**
- `Modules/HIS` = generic clinical platform (one nwidart module, internal folder split).
- `Modules/Obgy` = first specialty plug-in. **HIS never imports or names Obgy**; Obgy registers itself via registries. Same golden rule that keeps LIS sellable standalone, applied one level up → keeps Tier-2 "Moon Clinic" sellable without women's-health content.

**OBGY's role (4 things, no code):**
1. Pattern donor for the HIS core (bucket A ~32%: encounter/queue/visit-periods, 18-clone → 4 morph-table consolidation, Q&A history engine, per-body-system exam, perioperative workflow, lookup engine, annotated diagrams).
2. Specialty content (bucket B ~48%) inside `Modules/Obgy` — complex sheets as typed tables, simple sheets as seeded forms content.
3. First **paying pilot tenant** (signed during Phase 0) + ETL rehearsal corpus (10y data, ~190 production-curated Arabic vocabularies).
4. Proof that outpatient P1 is sellable standalone.

**Relationship sentences:**
- ERP↔LIS: LIS rides platform rails (BaseModel/DataScope/SequenceService) and posts GL only via `CreateJournalEntry`; platform knows nothing of LIS internals.
- ERP↔HIS: HIS consumes Core/Accounting/HRM/Inventory/NPHIES forward via Actions/Services; platform changes limited to the approved Generalization Register (chiefly `ChargePostingService`).
- HIS↔LIS: HIS calls `Modules\LIS\Actions\CreateLabRequest(encounter_id, source: in_patient|…)`; LIS never imports HIS; results return via `LabResultReleased` / `LabRequestCompleted` / `CriticalResultDetected`.
- HIS↔OBGY: Obgy imports HIS forward (encounters, folio charges, forms, lookups); registers via `PermissionDependencyRegistry::register('obgy', …)` + `EncounterContentRegistry`; HIS never names a specialty.
- OBGY↔LIS: lab orders via the HIS path + `obgy_lis_test_map` (276 legacy tests); **semen analyses & hormone panels become LIS catalog investigations with WHO reference ranges** (steal from ERP-Maximalist) — at most a thin obgy view over LIS results.
- OBGY↔ERP: patients = `lab_patients` internal rows (`partner_id → AccBpExt` replaces legacy cURL sync); drugs = Core Product + Inventory StockService; money only via `his_folio_charges → ChargePostingService → CreateJournalEntry`.

Judge scores: P-PLATFORM-FIRST 7 / **8** / **8**; OBGY-AS-SEED **8** / 7 / 7; Thin-HIS 4.5 / 4.5 / 6.

---

## 2. Final module map

### Modules/HIS (new, ONE module; folders: Mpi/, Scheduling/, Encounter/, Folio/, Orders/, Forms/, Registries/, Surgery/, Adt/)
P1 tables:
- Spine: `his_encounters`, `his_encounter_diagnoses` (typed ICD anchor for NPHIES), `his_episodes` (generic EpisodeOfCare; pregnancy = episode type — steal), `his_patient_spouses`, `his_referrals`.
- Scheduling: `his_appointments` (**`scheduled_at` + resource columns from day one** — steal; upgradeable to slot/diary/multi-resource), `his_visit_periods` (capacity), `his_clinic_closures`.
- Folio: `his_folios`, `his_folio_charges` (morph `source_type/source_id`, **`UNIQUE(source_type, source_id)`**, `service_code` per `sbscs_code` pattern, `cost_center_id`, unit_cost COGS), `his_folio_payments`, `his_deposits` (receipt-voucher backed, FIFO allocation).
- Orders: `his_prescriptions` + `his_prescription_items` (morph prescribable; subject patient|spouse; `product_id → Core products`), `his_service_orders` + items (morph orderable).
- Forms engine (built **last** in P1, extracted from real OBGY templates): `his_form_templates`, `his_form_versions` (**immutable; responses pin a version** — steal), `his_form_responses` (**status draft|signed|amended** — steal), `his_form_response_values` + typed projection index.
- Clinical primitives: `his_observations` (**typed FHIR-Observation time-series**: patient_id, episode_id?, encounter_id?, code, value_numeric, unit, body_site, observed_at — steal; vitals/ANC curves/IVF follicles), `his_clinical_exams`, `his_exam_findings` (body_system enum), `his_lookups` (domain, parent_id, title_ar, title_en).
- Surgery (platform-level from day one, per blueprint §95 2.6): `his_hospitals`, `his_surgical_bookings`, `his_operative_notes` (+ `his_operative_note_staff`), `his_anesthesia_types`, `his_incision_types`.

P2 (inpatient): `his_wards`, `his_rooms`, `his_beds`, `his_bed_assignments`, `his_encounter_movements`; ER triage model (**net-new design — no proposal designed it**: triage scale, tracking board, disposition); nursing basics; allergy/problem-list registry (flagged gap).

### Modules/Obgy (new, specialty plug-in; bucket B pruned by the content-vs-table rule)
Hard tables (metadata-break justified): `obgy_pregnancies`, `obgy_antenatal_visits`, `obgy_antenatal_ultrasounds`, `obgy_pregnancy_losses`, `obgy_delivery_registrations`, `obgy_ivf_cycles` + stage tables (`obgy_oocyte_retrievals`, `obgy_embryo_transfers`, `obgy_cryopreservations`, `obgy_endometrial_preps`, `obgy_cycle_outcomes`), `obgy_imaging_studies` + fetal/gyna scan findings, `obgy_endoscopy_procedures/findings/terms`, `obgy_infertility_files`, obstetric/menstrual/contraception histories, `obgy_lis_test_map`.
Pruned vs the blueprint (steals): follicle measurements → `his_observations` rows + observation-grid widget; semen/hormone results → LIS catalog seeders; simple sheets → forms-engine content (seeders); no patient/doctor/drug/price/treasury tables ever.

### Untouched / minimally touched
- `Modules/LIS`: zero changes except FK activation on the already-shipped inert `lab_requests.encounter_id` at kickoff (D5 gates apply).
- `Modules/Core`: Phase-0 items + `ChargePostingService` (extract `PostLabInvoice`/`PostLabPayment` parameterized by settings prefix `lis.*` → `his.*` — already named in the approved Generalization Register).
- `Modules/NPHIES`: widen `FhirPatientBuilder` / eligibility / claim type-hints from `LabPatient`/`LabInvoice` to the shared patient + HIS invoice (generalization, not rebuild). HIS invoices carry the proven `nphies_claim_status / nphies_preauth_ref / nphies_covered_amount / nphies_copay_amount` column set.
- `Accounting`, `HRM`, `Inventory`: consumed as-is (CreateJournalEntry, employees.user_id chain + shifts/payroll, warehouses + StockService FIFO/WAC).

---

## 3. Data spine schema sketch

```sql
-- MPI: NO new table. lab_patients promoted in place (D1).
-- HIS access: LabPatient::internal()  (external_lab_id IS NULL)
-- OBGY ETL: statusno→mrn, NID→national_id(+national_id_type); spouse → his_patient_spouses.

his_encounters (
  id, company_id, branch_id, encounter_number,            -- SequenceService('his','encounter'), per-branch
  patient_id  FK lab_patients (internal only),
  episode_id  FK his_episodes NULL,
  type ENUM(opd, ipd, er, daycase), status ENUM(planned, in_progress, completed, cancelled),
  attending_doctor_id FK lab_doctors,                     -- → employees via employee_id (W1-2) → users
  parent_encounter_id FK his_encounters NULL,
  queue_position INT NULL, source ENUM(reception, appointment, referral, migration),
  ... BaseModel: TenantAware + Auditable + SoftDeletes
)

his_episodes ( id, company_id, patient_id, type,          -- pregnancy | infertility_file | ivf_cycle | chronic_program
  status, opened_at, closed_at, owner_module VARCHAR )    -- longitudinal EpisodeOfCare header

his_folios ( id, company_id, folio_number, encounter_id FK,
  payer_type ENUM(self_pay, insurance, b2b_account), insurance_contract_id NULL,
  status, total_charges DECIMAL(12,3), total_payments, balance )

his_folio_charges ( id, folio_id FK,
  source_type, source_id,                                  -- morph: lab_invoice(by-ref), obgy_service, his_prescription_item, bed_day, or_procedure...
  UNIQUE(source_type, source_id),                          -- a clinical act bills exactly once (DB constraint, not convention)
  service_code,                                            -- SBS/nafis pattern from lab_investigations
  qty, unit_price, total, cost_center_id, unit_cost )

his_observations ( id, company_id, patient_id, encounter_id NULL, episode_id NULL,
  code, value_numeric, value_text NULL, unit, body_site NULL, observed_at, recorded_by )

his_form_versions ( id, form_template_id, version_no, schema_json, published_at )   -- immutable
his_form_responses ( id, form_version_id FK, encounter_id, patient_id, subject ENUM(patient, spouse),
  status ENUM(draft, signed, amended), signed_by, signed_at, amended_from_id NULL )
```

Posting path: `FolioClosed` → payer-split invoice(s) → `Core\ChargePostingService` (settings prefix `his.*`: receivable/revenue/tax/COGS account keys, AccBpExt AR fallback chain — exact PostLabInvoice semantics) → `Modules\Accounting\Actions\CreateJournalEntry(source_type='his_invoice')` → insurance share → `NphiesClaimService::submit()` → `nphies_transactions`.

---

## 4. Event contracts

| Event | Emitter | Consumers | Payload |
|---|---|---|---|
| `LabResultReleased` (existing) | LIS | HIS (attach to encounter), Obgy (render in sheet) | LabResult |
| `LabRequestCompleted` / `CriticalResultDetected` (existing) | LIS | HIS (order closure / clinician inbox) | LabRequest / alert |
| `BusinessPartnerCreated` (existing) | Core | Accounting `CreatePartnerAccounts` (patient AR) | partner |
| `EncounterOpened/Closed` (new) | HIS | Obgy, reporting, notifications | encounter_id, patient_id, type |
| `AppointmentBooked/CheckedIn` (new) | HIS | waiting screen, portal | appointment_id, period_id |
| `FolioChargePosted` (new, universal hook) | HIS | any clinical module; declared D3 template for radiology/OR | folio_id, source_type, source_id, total |
| `FolioClosed` (new) | HIS | invoice generation → ChargePostingService → JE → NPHIES | folio_id |
| `PatientAdmitted/Transferred/Discharged` (new, P2) | HIS | nightly bed-day folio job, bed board | encounter_id, bed_id |
| `PrescriptionDispensed` (new) | HIS | Inventory `StockService::decreaseStock(reference_type='his_prescription')` + folio charge | prescription_id, encounter_id |
| `FormResponseSubmitted` (new, steal) | HIS | specialty listeners (e.g., Obgy advances IVF stage on signed retrieval form) | form_response_id, form_code, encounter_id |
| `PregnancyOpened/Closed`, `IvfCycleStageCompleted` (new) | Obgy | inside Obgy + reporting only — HIS never listens to a specialty | pregnancy_id / cycle_id, stage |

FK contracts: `lab_requests.encounter_id → his_encounters.id` (FK at kickoff); `his_encounters.attending_doctor_id → lab_doctors → employees(employee_id, W1-2) → users`; `lab_patients.partner_id → business_partners → AccBpExt.ar_account_id`.

---

## 5. Dependency rules (enforced)

1. HIS → {Core, LIS, Accounting, HRM, Inventory, NPHIES} forward via Actions/Services only.
2. LIS never imports HIS (D2). Obgy → {HIS, Core, LIS} forward; **nobody imports Obgy**.
3. **CI grep gates** (steal — nwidart enforces nothing; 3 inversions already existed): fail build on `use Modules\\Obgy` outside Modules/Obgy and `use Modules\\HIS` inside Modules/LIS.
4. Patient access only via `LabPatient::internal()` / `forLab()`; no global scope (spec constraint #2).
5. All GL via ChargePostingService → CreateJournalEntry; Obgy never posts GL — it only inserts `his_folio_charges` rows.
6. Permissions: `PermissionDependencyRegistry::register('his', …)` / `('obgy', …)` in ServiceProvider boot (W1-4 / LIS precedent); FE route `data.permissions=['his.']/['obgy.']` + `*appCan`.
7. Governance (steals): any new **generic engine** in HIS core requires a named second consumer or passage of the second-specialty gate (board-approved); any new **specialty hard table** requires a metadata-break justification. Forms/lookup generalization is sequenced LAST, extracted against real OBGY templates.
8. Release rule (steal): every core table must have a production consumer in its first release train (OBGY pilot exercises encounters/appointments/forms/lookups before Tier-2 GA).

Frontend: `features/his/` standalone components at `/core/his/*` (+ fullscreen `/clinic/*` HisLayoutComponent mirroring `/lab/*`); `features/obgy/` mounting encounter-workspace tabs via registry; `core/services/his-*.service.ts` / `obgy-*.service.ts` (listAll() convention, X-Authorization); queue = lis-worklist server-driven contract clone; booking = request-wizard-v2 clone; clinical printing = lis-html-report/lis-report-pdf clones; portal = `/p/:token` pattern; HIS/OBGY namespaces in en.json/ar.json (English-first UPPER_SNAKE). Conceded bespoke widgets: observation grid (IVF/ANC flowsheets) + episode timeline.

---

## 6. Phase plan

| Phase | Scope | Size | Sellable increment |
|---|---|---|---|
| **0** (approved, NOT started) | hms-phase0-spec W1-1..W2-2: DataScope hoist, lab_doctors.employee_id, CreateLabRequest extraction + encounter_id, PermissionDependencyRegistry, B2B hardening (internal()/forLab()), module-gating API enforcement log-only | S, ~7–10 dev-days | gate for everything; unblocks tier licensing |
| **0b** (parallel, ops) | Legacy remediation (SQLi, mobileservices.php, web-root dumps); **fresh production export** of ~190 vocabularies + RedBean tables (R7 blocking gate); **sign OBGY clinic as paying pilot** (billable migration engagement) | S | migration-services revenue starts pre-code |
| **1** HIS P1 outpatient | Modules/HIS scaffold; encounter/episode/folio spine + ChargePostingService extraction; appointments/queue/periods; price-list + insurance generalization + FhirPatientBuilder widening (**NPHIES with first new tier**); /clinic workspace; forms+lookups+observations engine LAST | L, 8–14 pw | **Tier-2 Moon Clinic** (release-gated on pilot exercising every core table) |
| **2** Modules/Obgy + pilot go-live | Pruned obgy_* tables; simple sheets as form content; semen/hormones as LIS seeders; ETL (patients→lab_patients; visits split — `detectionid ∈ (−99,999,9999)` rows → folio payments; unmatched clinical rows → `source=migration` encounters with confidence scores + clinician review queues, R2); 4.8GB media migration; go-live | L, 8–12 pw (overlaps P1 tail) | **Tier-3 Moon Women's Health** (~22–34 pw cumulative) |
| **3** Second-specialty genericity gate (**binding**) | One thin specialty (e.g., general medicine) as pure content — zero hard tables; proves the core is specialty-neutral | S–M, 2–4 pw | live "add a specialty in weeks" demo; **prerequisite for Phase 4 funding** |
| **4** Inpatient/ADT | wards/rooms/beds/assignments/movements; admission/transfer/discharge events; nightly bed-day charges; deposits; **ER triage (net-new design: scale, tracking board, disposition)**; nursing basics; allergy/problem list | L, 10–16 pw | **Tier-4 Moon Hospital** |
| **5** Extensions | Clinical pharmacy + eMAR (MedicationAdministration layer — absent from all proposals, must be designed); OR generalization (validated first on OBGY pilot's real surgical caseload); radiology via D3 contract | M–L each, 4–10 pw | priced upgrades within Tier-3/4 |

**Decisions needed from ownership:** (1) one scoping amendment — deep clinical documentation allowed only inside licensed specialty modules (reconciles Obgy depth with the approved admin-financial HIS v1, Risk #7); (2) date for switching module/license enforcement from log-only to enforce; (3) production-export access authorization; (4) clinician review-time budget inside the paid migration contract; (5) the second-specialty gate as a binding financial precondition for Phase 4; (6) Tier-4 not marketed before ER/nursing items complete.

---

## 7. Deliverables index
- `sections/20-verdict.html` — Arabic board verdict (this synthesis, management form)
- `sections/21-scenarios.html` — 5 end-to-end operational walkthroughs
- `sections/22-roadmap.html` — phases + risk/decision table
- Companion evidence: `md/01..09-*.md`, proposals `md/p-*.md`, `/home/moonui/hms-phase0-spec.md`, `obgy-erp-analysis.md` §90.1/§95

---
