# MedRegistry — medical module registry, presets & setup wizard

Implements **P1-T2** (module registry), **P1-T3** (manifest convention + retrofit),
**P1-T5** (per-module settings) and **P1-T6** (business-type presets + setup wizard)
of `docs/Shifa-one/SIFA-MEDICAL-PLAN.md`, per the binding decisions in
`docs/audit/REUSE-MAP.md` (§6) and `docs/audit/TENANCY-PERMISSIONS-AUDIT.md` (D1–D8).

Everything lives inside `Modules/MedRegistry/` — zero core edits, zero edits to
other modules (module-only rule).

## What it is (and is not)

The registry **wraps** the existing module machinery, it never replaces it:

| Concern | Owner (unchanged) | MedRegistry adds |
|---|---|---|
| Module presence/loading | nwidart (`module.json`, `modules_statuses.json`) | catalog row per module (`medreg_modules`) |
| Host install/update | each module's `InstallController` + `system.{module}_version` | presence + install badges in the catalog UI |
| Tenant entitlement | Superadmin subscription `package_details` via `ModuleUtil::hasThePermissionInSubscription()` | consulted (never reimplemented) inside `isEnabled()` |
| Per-facility/department activation | — | **new** `medreg_module_instances` layer + lifecycle |
| Marketplace download/rollback | out of scope per decision **D4** | uninstall = delete the *instance row*, keep all data |

## Tables

- `medreg_modules` — host-global catalog (no `business_id`): `module_key` (unique,
  nwidart StudlyCase), `alias`, `display_name`, `description`, `icon`,
  `category` (core/medical/specialty/facility/support/integration),
  `spec_key` (SIFA spec module id, e.g. `dental`), `dependencies`,
  `optional_dependencies`, `conflicts_with` (json), `installable_per`
  (tenant/facility/department), `settings_schema` (json), `version`,
  `is_present`, `sort_order`.
- `medreg_module_instances` — per-tenant rows: `business_id`, `module_id`,
  `business_location_id` (nullable), `department_id` (nullable),
  `status` (installed/enabled/disabled/soft_locked), `settings` (json),
  `installed_at`. Unique composite index `medreg_instance_unique`.
- `medreg_business_types` — host-global presets (10 + custom) with
  `default_modules` / `optional_modules` as spec_keys.
- `medreg_tenant_setups` — chosen type per business (`business_id` unique,
  `business_type_key`, `selected_modules`, `wizard_completed_at`,
  `completed_by`). Satellite table — the core `business` table is untouched.

## Manifest convention (P1-T3)

Modules **may** declare medical metadata in their own `module.json`:

```json
{
    "name": "DentalClinic",
    "extra": {
        "medical": {
            "category": "specialty",
            "installable_per": "facility",
            "spec_key": "dental",
            "dependencies": ["medical_core", "scheduling"],
            "settings_schema": {
                "online_booking":   { "type": "boolean", "label": "Enable online booking", "default": true },
                "xray_integration": { "type": "select",  "label": "X-ray integration",
                                      "options": { "none": "None", "builtin": "Built-in viewer" },
                                      "default": "none" }
            }
        }
    }
}
```

Recognised keys: `category`, `installable_per`, `spec_key`, `dependencies`,
`optional_dependencies`, `conflicts_with`, `settings_schema`, `display_name`,
`description`, `icon`, `sort_order`.

`MedregCatalogSeeder` / `php artisan medreg:sync-catalog` (scheduled weekly)
scans `Modules/*/module.json` via the nwidart facade and upserts one catalog row
per module. Precedence: `extra.medical` block → curated retro-fit map in
`Support/CatalogSync.php` (existing modules are **never edited**) → safe
defaults (category `core`, per `tenant`, sorted last).

Retro-fit highlights: ClinicCore → `medical_core` (also serving the
`scheduling`/`outpatient` spec ids via `MedregModule::SPEC_ALIASES`);
LabCore + ClinicLab → `laboratory` (per facility); the 45 `*Clinic` specialty
modules → category `specialty`, per facility, spec_key derived from the name
(`DentalClinic`→`dental`, `DermatologyClinic`→`derma`, `GynaecologyClinic`→`obgyn`, …);
Essentials → `hr_payroll`; PortalApi → `patient_portal`; WhatsApp/AiCore/
SmartStorage/ActivityLog → support; Connector/TPConnector/lab analyzers →
integration; Vet*/Bms/Vrc* → category `core` + no spec_key (non-medical,
sorted last).

## The helper

```php
medreg_enabled($key, $businessId = null, $locationId = null, $departmentId = null): bool
```

`$key` = module_key (`DentalClinic`) or spec_key (`dental`, `medical_core`).
Resolution order (`ModuleLifecycleService::isEnabled`):

1. **Host gate** — folder present + `ModuleUtil::isModuleInstalled()`.
2. **Tenant gate** — `ModuleUtil::hasThePermissionInSubscription()` with the
   module's own package key (discovered from its `DataController::superadmin_package()`),
   fallback-allow mirroring core semantics.
3. **Instance chain** — department → location → tenant, most specific wins.
   No row anywhere → default **disabled** for medical categories
   (medical/specialty/facility), **enabled** for everything else — existing
   non-medical behavior never changes.

The helper is fail-closed (returns `false` if the registry tables are missing),
so it is safe to call from any module.

## Lifecycle (P1-T2)

`ModuleLifecycleService` — `install / enable / disable / softLock / uninstall`
over `medreg_module_instances`. Every change fires
`Modules\MedRegistry\Events\ModuleInstanceChanged` (`$instance`, `$action`,
`$previousStatus`). `uninstall()` deletes the instance row **only** — data is
kept (D4). Semantics: `disable` hides UI / denies API but keeps data;
`soft_locked` means "reject new operations"; only `enabled` makes
`medreg_enabled()` return true.

## Settings (P1-T5)

Each instance stores JSON `settings`; the settings screen
(`/med-registry/instances/{id}/settings`) renders a form generically from the
module's `settings_schema` (types: `boolean`, `select`, `text`, `number`; with
`label`, `default`, `options`). DentalClinic ships the example schema from the
plan (online booking toggle + x-ray integration select) via the curated map.

## Presets & wizard (P1-T6)

`PresetService::apply($businessId, $typeKey, $selectedSpecKeys, $userId)`
resolves spec_keys → catalog modules, auto-adds dependencies (catalog
`dependencies` json, falling back to the spec dependency notes in
`PresetService::SPEC_DEPENDENCIES` — icu → inpatient+bed_management,
pharmacy → inventory, …), enables tenant-level instances (idempotent), records
`medreg_tenant_setups` and returns
`{enabled, skipped_missing, dependency_added}`. Spec modules with no installed
host module (e.g. `billing`, `inventory` = ERP core features; `inpatient`,
`icu` = later phases) are reported under `skipped_missing`.

Wizard (admin-only + `medreg_module` subscription): `/med-setup` →
choose type (10 cards + Custom) → confirm modules (defaults locked, optionals
toggleable, Custom = full medical catalog) → Confirm & Install → report.

CLI twin: `php artisan medreg:apply-preset {business_id} {type_key} [--select=…] [--user=…]`.

## Routes, permissions, install

- `/med-registry` (catalog), `/med-registry/instances` (+`/set`),
  `/med-registry/instances/{id}/settings` — `medreg.registry.view`,
  `medreg.registry.manage`, `medreg.settings.manage`.
- `/med-setup` wizard — admin-only (`ModuleUtil::is_admin`) + `medreg.wizard.run`
  surfaced in the role editor.
- `/med-registry/install|update|repair|uninstall` — superadmin; install gate is
  the **lowercase** `medregistry_version` System property (the exact key
  `ModuleUtil::isModuleInstalled()` reads).
- Superadmin package flag: `medreg_module`.

Demo: `MedregDemoSeeder` applies the `polyclinic` preset (+ radiology + all
installed specialties) to the business named **Shifa One**, skipping silently
when absent.
