# W2-BE-C — layout settings API for in-place settings: fields / elements / features / facility type / shell switch; complaint HPI+duration; diagnosis text mode (BE)

Read _COMMON.md first. Review rows: A #47-#56; C #22-#25; B #56 #59; D C1 G13. Design: /home/moonui3/public_html/knowledge-base/plans/visit-shell-completion/design/FABLE-inplace-settings.md (§A — read it; the BE must support everything it needs). LEDGER Decisions 3 and 6.

## Today
`Modules/Clinic/app/Http/Controllers/VisitLayoutController.php` (GET, PUT client, PUT doctor); company setting `clinic.visit_layouts` = `{ <type>: {layout, fields, features} }`; user setting `clinic.visit_layout_doctor` = layout only; `clinic.layout.allow_doctor_override`; GynFeatures reads `clinic.visit_layouts['gyn_ob'].features`; dental features are their own `dental.feature.*` settings; per-type shell switch = `clinic.visit_layouts[type].features.shell`. FE save sends only `{clinical_type, layout}`.

## Contract from the Fable spec (§1 — it governs where this brief differs)
Rules merge PER KEY (default ← client ← doctor); the layout still replaces whole. `visible`, `required` and `features` are client-only; `input` and elements are doctor-overridable unless the client rule has `locked: true`. New `locked` flag on FieldRule + ElementRule. The doctor JSON gains fields/elements. Elements are stored per clinical type (the header elements per type too — orchestrator decision). Read the spec's §1 and §2.3 key table before coding.

## Tasks
1. **Client level PUT** accepts (each optional, independently; omitted = keep): `layout`, `fields` (`{<field_key>: {visible?, required?, input?: 'select'|'text'}}`), `elements` (`{<element_key>: bool}` — keys: hdr_insurance, hdr_last_visit, hdr_doctor, complaint_quick, complaint_hpi, complaint_duration, complaint_note, lab_prev, rx_interact, dx_secondary, dx_mode? (dx input is a field rule `diagnosis.input`)), `features` (`{<feature>: bool}` incl. `shell`). Validate keys against a registry (one PHP class listing allowed field keys per type incl. dynamic `exam_finding_<region code>` keyed by the region `code`, element keys, and features per type: gyn_ob → GynFeatures list; dental → map to the existing `dental.feature.*` settings (write-through so there is ONE source of truth — reading back returns their values); nutrition/dental/general/gyn_ob → `shell`). `null` for a whole section resets it. Permission clinic.layout.manage.
2. **Doctor level PUT** accepts `layout`, `fields`, `elements` (not features) when allow_doctor_override is on; a client rule marked `locked:true` (add `locked?: bool` to field/element rules at client level) cannot be overridden by the doctor (422 with the key). Store in `clinic.visit_layout_doctor` as `{<type>: {layout, fields, elements}}` — migrate the old layout-only shape on read (backward compatible). 
3. **GET** returns for the type: `default`(none — FE has defaults), `client: {layout, fields, elements, features}`, `doctor: {…}` (when allowed), `effective: {fields, elements, features}` (default ← client ← doctor, respecting locks) with per-key `source` ('default'|'client'|'doctor'), `can_manage`, `allow_doctor_override`, `facility_type`.
4. **Facility type (Decision 3):** setting `clinic.facility_type` ∈ clinic|center|hospital|custom (definition in ClinicSettingDefinitionSeeder, group clinic_modes, visible). Setting it (via the normal settings endpoint AND a `PUT clinic/visit-layout/facility-type` used by the in-shell panel) writes the 4 existing settings: clinic → ordering immediate, lab standalone, rad standalone, pharmacy standalone_formulary; center → intent, integrated, integrated, products; hospital → intent, integrated, integrated, products (check the real enum values in OrderingMode/ClinicCatalogMode/PharmacySource); custom → leaves them. Changing any of the 4 individually flips facility_type to custom. Run the seeder on dev; set dev company 1 to its matching type. Tests.
5. **Settings visibility:** make the per-type shell switch and gyn features appear in `/clinic/settings` too? NO — Decision 6: they're edited in the shell's panel; but the global `clinic.visit_shell` stays in /clinic/settings. Make sure `GynFeatures` and the consultation redirect keep reading the same storage.
6. **Complaint HPI + duration (B #56):** add nullable `complaint_duration` (string ≤60) and `hpi` (text) to encounters (migration, run on dev), fillable, in EncounterResource, editable through the same endpoint the complaint card uses for the note (find it), respecting the lock. Tests.
7. **Diagnosis text mode (C #22):** when the field rule `diagnosis.input = 'text'`, the FE records a free-text diagnosis. Make sure the diagnosis endpoint accepts a row with `description` and no `code` (it may already — check encounter_diagnoses), and that the insured sign gate (DiagnosisService::blockSignForInsured requires a code) still blocks insured visits (text mode is not allowed for insured: the FE forces ICD there — document in the registry).
8. Tests for all of the above (controller + registry validation + locks + facility mapping).

## Out of scope
FE; lookup files (W1-BE-B); PatientReadService/Hospital (W1-BE-A); HistoryService (W3-BE-D).
