# حكم Fable — م3 «ملف المريض والتسجيل» (Patient File & Registration)
**Fable advisor ruling — 2026-07-03. ملزم للتنفيذ (SDD: منفذ→مراجع→بوابة native+Codex). نمط حكم م1/م2.**

المصادر المعتمدة (كلها اتقريت بالكامل + اتحققت ضد الكود): scout-a/b/c + RESUME (`knowledge-base/plans/m3-patient-file/`) · `topics/clinic-execution-phases.md` (الملزم) · `topics/clinic-profiles-fields.md` (تصميم الحقول) · `topics/clinic-dynamic-fields.md` (تصميم المحرك) · تحقق كود مباشر: `Modules/Clinic/routes/api.php` · `Modules/Core/database/seeders/RolePermissionSeeder.php:1056-1058` · `migrations/2026_06_22_140001/140003` · `LabDoctorController::destroy` · `create-visit.component.ts:450`.

**🐛 اكتشاف تحقق جديد (مش في الـ scouts كتناقض محسوم):** الـ FE quick-add بيعمل `POST {apiUrl}/clinic/patients` (`create-visit.component.ts:450`) والراوت **مش موجود أصلًا** (الكلينك GET-only) → **إضافة مريض من الحجز مكسورة اليوم (404)**. ده مش بس دين — ده bug حي، وبيأكد حكم D2: الباب الكلينكي للكتابة لازم يتبني (الـ FE أصلًا بيتوقعه).

---

## Ruling 1 (D1) — حدود م3/م6 لمحرك الحقول: **execution-phases يحكم. م3 = «profile delta» فقط**

**الحكم القاطع:** `clinic-execution-phases.md` هو الملزم (status=active، «الخطة الملزمة»، الأحدث). جدول الجهد في `clinic-dynamic-fields.md` §8 اللي بيقول «م3 (core)» بيستخدم **ترقيم المحطات القديم** (محطات خطة التظبيط: م3=الكشف) — **لاغٍ كترقيم**، والتصميم نفسه سليم ويُنفَّذ في **م6** بالترقيم الجديد. (نفس فخ `clinic-test-scenarios.md` — ماتخلطش.)

**م3 يبني (الحد الأدنى اللي يخدم حقول البروفايل فقط):**
1. **Migration على `history_questions`** (اتحققت: مفيهاش scope/category/is_required): `scope` string(16) default `'encounter'` (قيم: patient|encounter) · `category` string(16) default `'history'` (قيم في م3: history|profile) · `is_required` bool default false · index `(company_id, category)`. **string مش enum** (زي `input_type` الموجود string(32)) — عشان م6 يضيف vitals|custom بلا ALTER enum.
2. **Migration على `patient_history_answers`** (اتحققت: `encounter_id` NOT NULL + unique `pha_encounter_question_unique`): `encounter_id` → nullable · عمود `scope_key` عادي = `COALESCE(encounter_id, 0)` يتصان في `saving()` hook (**نفس سابقة `ext_scope_key` في LabPatient بالظبط** — مش generated column، اتبع السابقة) + backfill · استبدال الـ unique بـ `(patient_id, history_question_id, scope_key)` (يغطي السكوبين) + إبقاء index على encounter_id للأداء.
3. **`HistoryService::saveAnswers` يتعمم** لقبول patient-scope (encounter_id NULL) + احترام `is_required` — نفس التحقق الموجود (type/options/company/active).
4. **Endpoints بروفايل:** `GET/PUT clinic/patients/{patient}/profile-fields` (تعريفات category=profile + قيم المريض؛ upsert bulk) — حراسة `clinic.patients.view`/`clinic.patients.update`.
5. **FE: مكوّن `dynamic-field-renderer` مشترك جديد** (`features/clinic/shared/dynamic-field-renderer/`، مستخلص من منطق history-panel **بدون لمس history-panel نفسه**) — يستهلكه في م3 قسم «حقول إضافية» في فورم التسجيل **فقط**.

**م6 يملك (ممنوع يدخل م3):** `unit` · `normal_min/max` · `danger_min/max` · `danger_values` · `is_computed`/`computed_key` · `value_num` · بذر/هجرة/shim الـ vitals · تقييم danger (BE flags + FE highlight) · **ترحيل vitals-panel وhistory-panel على الـ renderer المشترك** · الـ trend. **عقد التوافق الأمامي:** أسماء ودلالات أعمدة م3 مجمّدة على `clinic-dynamic-fields.md` §3.1/3.2 حرفيًا → م6 يبقى **إضافي بحت** فوقها.

**إجراء KB إلزامي مع التنفيذ:** أضِف سطر تنبيه أعلى `clinic-dynamic-fields.md` §8: «ترقيم المحطات هنا قديم — بالترقيم الملزم: profile-delta=م3، باقي المحرك=م6، شاشة الإدارة الكاملة=م9».

---

## Ruling 2 (D2) — ملكية كتابة المريض: **كيان واحد، بابان. الكلينك ياخد store/update بيفوّضوا لسيرفس LIS مشترك**

المالك: «الكور واحد» — يعني **جدول وموديل ومنطق كتابة واحد**، مش «باب API واحد». الحكم:

1. **استخلاص `LabPatientWriteService`** (في LIS — مالك الكيان، `Modules/LIS/app/Services/`) من `LabPatientController::store/update`: توليد mrn عبر SequenceService + self-heal retry + auto-create BusinessPartner + حارس `external_lab_id` (الكلينك لا يكتب مرضى B2B أبدًا). `LabPatientController` يتحول لمستهلك للسيرفس — **صفر تغيير سلوك LIS**.
2. **Clinic `PatientController::store/update`** جديدان + راوتات `POST/PUT clinic/patients` بصلاحيات جديدة `clinic.patients.create`/`clinic.patients.update` — بيفوّضوا لنفس السيرفس. **شِيم `Patient` الكلينكي يفضل guarded read-only كما هو** (الكتابة عبر `LabPatient` داخل السيرفس فقط) — الحارس ده صمام أمان، ماتفكوش.
3. **التحقق:** rules builder مشترك (`Modules/LIS/app/Support/PatientFormRules` أو مكافئ) يقرأه `StoreLabPatientRequest`/`UpdateLabPatientRequest` **والطلبان الكلينكيان** — شرطي على `clinic.patient_form_fields` (Ruling 4)، و**الديفولت عند غياب الإعداد = قواعد اليوم حرفيًا** (name/national_id/nationality required) → **صفر انحدار LIS** (Pest يثبت ذلك صراحة — بند بوابة).
4. **FE:** `ClinicPatientService` ياخد `create()/update()` · الـ raw `HttpClient.post` المكسور في create-visit يتشال ويتوصل بالسيرفس · **شاشة جديدة `clinic/patients`**: قائمة (بحث/جدول) + تسجيل/تعديل بفورم كامل مرسوم من الـ registry + عرض الملف بتوسيع نمط `ClinicPatientHeaderComponent` (هوية الـ teal `#0f766e` + `app-form-dialog` + reactive forms — مش نمط الـ p-dialog/ngModel القديم). **تبويبات زيارات/دفتر داخل الملف = مؤجلة** (م4/م8 عندهم سطوحهم) — م3 ملف = ديموغرافيا + بروفايل + الهيدر الموجود.
5. **quick-add الحجز** يترسم من subset الـ registry (locked + visible-required فقط) ويستدعي `ClinicPatientService.create` — يفضل «سريع» بطبيعته.
6. **الحذف:** **لا `destroy` كلينكي للمريض في م3** — الحذف/الدمج يفضلوا باب LIS الوحيد (merge موجود هناك)؛ الكلينك عنده `is_active` toggle عبر update. (تصعيد مالك #2.)

---

## Ruling 3 (D3) — قايمة الحقول الحاسمة: أين يعيش كل حقل

| الحقل | القرار | المكان | migration؟ |
|---|---|---|---|
| كل أعمدة `lab_patients` الحالية (~30) | core كما هي | جدول موجود | ❌ |
| **blood_group** | ✅ **core hideable** — يرجع للفورم default-OFF («أول عميل مباشر لآلية الرؤية» — بدل موت #1626) | عمود موجود | ❌ |
| **important_note (ملاحظة هامة/إنذار)** | ✅ **core جديد** — يُعرض في `clinic-patient-header` كشارة إنذار؛ hideable defaultVisible=ON | `lab_patients.important_note` string(500) nullable | ✅ M3 |
| **NID→DOB** | ✅ **اشتقاق FE فقط — لا تخزين ولا BE** | parser بمفتاح `national_id_type`+`nationality`: مصري 14-خانة (قرن+ي/ش/س) يملأ DOB **ويظل قابلًا للتعديل**؛ سعودي = no-op (مزامنة عمر↔ميلاد الموجودة تفضل)؛ لا checksum/regex في BE في م3 | ❌ |
| **حزمة الزوج (باقة OBGYN)**: اسم/لقب/ميلاد/قومي/فصيلة/مهنة/تعليم/ملاحظات/عادات الزوج + مدة زواج + أولاد/بنات + حالة اجتماعية (default-ON في الباقة) + مهنة/تعليم/لقب المريضة | ✅ **حقول ديناميكية** `scope=patient, category=profile` — **ليست أعمدة core أبدًا** (قرار مقفول) | seeder `updateOrCreate(company_id, code)` — pack `obgy` | ❌ (بيانات لا schema) |
| العدّادات الولادية (pno/ab/ectopic/svd/cs) | ⛔ **م6** (category=history/vitals — إكلينيكي مش ملف) | — | — |
| **مسودة تُستأنف (draft-resume)** | ⛔ **مؤجلة — مؤكَّد** (قيمتها تسقط مع فورم قصير مخصص؛ تعويض v2 = حفظ حالة الفورم محليًا). ⚠️ سطر م3 في execution-phases ذكرها — **أعدّله**: يتشال «مسودة تُستأنف» من سطر م3 مع تسجيل القرار في السجل (القرار الأصلي المقفول في profiles-fields line 77 هو الأصح) | — | — |
| رقم الملف | ✅ موجود = `mrn` (sequence + self-heal، أفضل من MAX+1 الجينا) — للعرض read-only في الفورم والهيدر | موجود | ❌ |

---

## Ruling 4 (D3b) — نموذج إدارة الحقول: **الهجين مؤكَّد بالكامل + الشاشة الوظيفية تُشحن في م3 (مش م9)**

1. **FE registries:** `clinic/config/profile-form-registry.ts` → `PATIENT_FORM_REGISTRY` + `DOCTOR_FORM_REGISTRY` بنمط `DocFieldDef` حرفيًا. **Locked:** مريض `name`(أو name_ar)+`gender`+`phone` · دكتور `name_ar/name_en`+`code`. **ديفولتات الـ registry تطابق سلوك اليوم بالظبط** (national_id/nationality visible+required) — ده صمام صفر-انحدار.
2. **`ProfileFormConfigService` = نسخة مصغّرة الآن** (مش تعميم DocConfigService) — أحسم ميل المؤلف: slim-now/unify-later. **قرار مقفول، لا يعاد فتحه في المراجعة.**
3. **التخزين:** إعدادان JSON `clinic.patient_form_fields` + `clinic.doctor_form_fields` عبر **`ClinicSettingDefinitionSeeder` (نمط م2)**، `value_type=json`, `is_visible=false` (زي `core.document_settings`) — **seeder، لا migration**. الشكل: `{fields:{"<key>":{visible?,required?}}}`، الغائب = ديفولت الـ registry.
4. **القاعدة الذهبية server-side (أحسم الغموض):** `hidden → **يُتجاهل/يُقصّ silently** (مش prohibited-422)` — لأن quick-add بيبعت subsets وFE قديم ممكن يبعت حقل اتخفى؛ القصّ أأمن من كسر عملاء · `visible+required(setting) → required` · `locked → required دائمًا`. FE يعطّل toggle الـ Required عند الإخفاء (درس D1 من المستندات). الإخفاء **عرضي فقط** — الأعمدة والمستهلكون الداخليون (tax/partner/portal/NPHIES) لا يتأثرون.
5. **⚠️ تصحيح scoping مهم:** تست المالك «أخفِ حقول الزوج من الإعدادات → اختفت» **يستحيل بدون UI إدارة** → **تبويب «نماذج الملفات» الوظيفي يُشحن في م3** داخل شاشة clinic-settings بتاعة م2 (السجلّان بـ Visible/Required toggles + المقفول رمادي + جدول CRUD لحقول category=profile فوق `HistoryQuestionController` الموجود) + زر ⚙️ على الفورم يفتح نفس اللوحة dialog (درس D3: live signal مش stale copy). **م9 ياخد الصقل/الدمج المركزي الكامل فقط** — مش الوظيفة.
6. **Presets في م3 (مذكورة نصًا في سطر م3 الملزم):** 3 باقات seeded — **OBGYN** (حزمة الزوج + حالة اجتماعية ON، قومي optional) · **KSA-NPHIES** (قومي/جنسية ON+required) · **تسجيل سريع** (الأدنى) — `ClinicProfilePackSeeder` بـ `updateOrCreate(company_id,code)` + زر «تطبيق باقة» بسيط في التبويب. Export/Import JSON = م9.
7. **الصلاحيات (تحديث م2 إلزامي):** إضافة `clinic.patients.create`/`clinic.patients.update`/`clinic.doctors.create`/`clinic.doctors.delete` في `RolePermissionSeeder` (اتحققت: الموجود اليوم patients.view + doctors.view/update فقط) + **تحديث `ClinicPermissionDependencies` + presets أدوار م2** (استقبال: patients.create/update · مدير: doctors.*). حقول البروفايل الديناميكية إدارتها تحت صلاحية إدارة الأسئلة الموجودة.

**مجموعة الـ migrations الكاملة لـ م3 (feature phase — مسموحة على moonui_dev_be، تُشغَّل في نفس خطوة الشحن — درس 1054):**
- M1 `add_profile_scope_to_history_questions` (scope/category/is_required + index)
- M2 `make_encounter_nullable_add_scope_key_on_patient_history_answers` (nullable + scope_key + backfill + unique swap)
- M3 `add_important_note_to_lab_patients`
- **Seeders (لا migrations):** ClinicSettingDefinitionSeeder (إعدادان) · RolePermissionSeeder (4 صلاحيات) · ClinicProfilePackSeeder (3 باقات)

---

## Ruling 5 (D4) — الأطباء: **إتمام CRUD كلينكي كامل (الدين المسجل) — نفس نمط «بابان لكيان واحد»؛ LIS لا يُمس**

1. **BE:** `Clinic DoctorController` ياخد `store` (كل الحقول) + `update` (شامل — مش identity بس؛ `updateIdentity` يفضل للتوافق أو يُدمج داخل update) + `destroy` (**soft-delete** — زي `LabDoctorController::destroy` المتحقق منه، بلا حراس إضافية لأن soft-delete يحفظ سلامة الـ FKs) — راوتات `POST/PUT/DELETE clinic/doctors` بـ `clinic.doctors.create/update/delete`. التحقق rules مشترك مشروط بـ `clinic.doctor_form_fields`. **`LabDoctorController` يفضل كما هو** (باب اللاب).
2. **`DoctorResource` الكلينكي يبطّل يحجب** phone/email/license (وراء `clinic.doctors.view` — كفاية).
3. **FE `clinic-doctors.component`:** dialog تعديل شامل (كل حقول BE مرسومة من `DOCTOR_FORM_REGISTRY`) · **التحويل من النص الحر `specialization` إلى `specialty_id` المهيكل** («يختار مش يكتب») · toggle تفعيل/تعطيل (`is_active`) بارز + حذف soft ثانوي · كتلة العمولات = toggle إخفاء واحد للكتلة كلها.
4. **«ملف الدكتور»** في م3 = الشاشة المكتملة + الفورم registry-driven — **مش صفحة بروفايل غنية جديدة** (لو المالك عايز صفحة ملف دكتور بتبويبات، دي م9/لاحق).
5. quick-add الدكتور في create-visit يتحول من `POST lis/doctors` إلى `POST clinic/doctors` (يحل مشكلة صلاحيات موظف الكلينك اللي معهوش `lis.doctors.create`).

---

## Ruling 6 (D5) — فحص التكرار الحي + توحيد الـ typeaheads

1. **Endpoint جديد** (مش تمديد search — دلالة مختلفة): `GET clinic/patients/duplicate-check?name=&phone=&national_id=` → `{name:[…], phone:[…], national_id:[…]}` مرشحين (حد 5/محور، حقول: id/mrn/name/name_ar/phone/DOB) — حراسة `clinic.patients.view`، scoped داخلي (`external_lab_id IS NULL`) + استثناء `id` الحالي في وضع التعديل.
2. **المحاور = 3 فقط** (اسم/تليفون/قومي — نص سطر م3 الملزم). قومي الزوج = يركب الباقة لاحقًا (v2) · رقم الملف = بلا معنى (mrn auto). المطابقة: phone/national_id تطابق تام (بعد تطبيع) · الاسم LIKE مطبَّع بسيط على name/name_ar/name_en — **لا fuzzy/Levenshtein في v1**.
3. **FE:** on-blur (debounced) في فورم التسجيل والـ quick-add → **تحذير ناعم غير حاجز** بشارة + زر «افتح الملف الموجود» (ينتقل للملف/يختاره في الحجز). الـ 422 الصلب على القومي يفضل كما هو (طبقة تانية).
4. **التوحيد:** مكوّن مشترك واحد `<clinic-patient-search>` (`features/clinic/shared/patient-search/`) يغلّف debounce/signals/panel — السيرفس الخلفي **`ClinicPatientService.search` فقط**؛ **حذف** `ClinicBookingService.searchPatients` (مكرر حرفيًا) · **حذف `ReceptionBookingComponent` الميت** (+html — متحقق unrouted) · إعادة توصيل create-visit + reception-order على المكوّن.

---

## Ruling 7 (D6) — WP DAG (كاتب واحد لكل ريبو، BE ‖ FE، عقود تُقفل أولًا)

**خطوة 0 — العقود (قبل أي كود):** أشكال API (patient store/update/profile-fields/duplicate-check/doctor CRUD) + شكل JSON الإعدادين + مفاتيح الـ registries + أسماء الأعمدة الثلاثة. تُكتب في contract واحد يمضي عليه التراكان.

**ترك BE (تسلسلي):**
- **WP-B1 — دلتا المحرك:** الـ3 migrations + HistoryService patient-scope + Store/Update/Resource للـ HistoryQuestion بالحقول الجديدة + endpoints البروفايل. *(بوابة native+Codex)*
- **WP-B2 — باب كتابة المريض:** استخلاص `LabPatientWriteService` + rules المشروطة + Clinic store/update + duplicate-check + الصلاحيات (+Pest صفر-انحدار LIS). *(بوابة)*
- **WP-B3 — الأطباء + الإعدادات:** Doctor store/update/destroy + rules + Resource unhide + تعريفا الإعدادين + `ClinicProfilePackSeeder` + تحديث presets م2/Dependencies. *(بوابة)*

**ترك FE (تسلسلي، بالتوازي مع BE):**
- **WP-F1 — الأساسات (لا يعتمد على BE):** registries + `ProfileFormConfigService` + `<clinic-patient-search>` + حذف الميت + قصّ السيرفس المكرر.
- **WP-F2 — شاشة المرضى** *(يعتمد B1+B2)*: `clinic/patients` (قائمة+فورم registry-driven+ملف) + NID→DOB parser + تحذيرات التكرار + قسم «حقول إضافية» بالـ renderer الجديد + إعادة توصيل quick-add (إصلاح الـ404). *(بوابة + frontend-design skill)*
- **WP-F3 — الأطباء + الإدارة** *(يعتمد B3)*: إكمال شاشة الأطباء + تبويب «نماذج الملفات» + زر ⚙️ + «تطبيق باقة». *(بوابة)*

**Integration:** تشغيل سيناريو تست المالك الثلاثي (قومي→ميلاد · تليفون مكرر→تحذير · إخفاء الزوج→اختفى) + `ng build` + suite → نشر `/app` + push hazemdev + CHANGELOG ثنائي + **تحديث سجل التقدم مع كل WP**. التقدير يطابق الخطة: ٢–٣ أيام.

---

## Ruling 8 (D7) — تصعيدات المالك (خفيفة، تُسأل مع بدء م3، غير حاجزة — ديفولتاتي سارية لحين رد)

1. **باقة co4 للتست:** أطبّق preset **OBGYN/مصر** على co4 (عشان تست «القومي→الميلاد» المصري يشتغل)؟ *(ديفولتي: نعم — التست بيفترضه.)*
2. **حذف المريض من الكلينك:** يفضل LIS-only (الكلينك تعطيل فقط)؟ *(ديفولتي: نعم — merge/الحذف أبواب معمل.)*
3. **حذف الدكتور:** حذف soft مسموح ولا تعطيل-فقط في الكلينك؟ *(ديفولتي: الاثنان، التعطيل هو الأساس UI-wise.)*
4. **important_note:** حقل core في هيدر المريض، يعدّله حامل `clinic.patients.update`؟ *(ديفولتي: نعم.)*

**محسوم مني (لا يُصعَّد):** حدود م3/م6 (Ruling 1) · بابان-كيان-واحد (2،5) · hidden=يُقصّ لا 422 (4.4) · slim ProfileFormConfigService (4.2) · 3 محاور تكرار (6) · draft-resume مؤجل + تعديل سطر م3 (3) · شحن تبويب الإدارة الوظيفي في م3 (4.5).

---

## 🚩 أعلام حمراء (فخاخ م1/م2 المكافئة — بنود بوابة مراجعة صريحة)

1. **انحدار LIS عبر التحقق الشرطي = فخ الفلوس بتاع م3.** غياب الإعداد لازم يساوي سلوك اليوم حرفيًا (national_id/nationality required) — Pest صريح يثبت أن `POST /lis/patients` بلا إعداد لم يتغير. حقل مخفي يسقط قومي معمل NPHIES = كارثة صامتة.
2. **زحف نطاق المحرك:** أي عمود من (unit/danger/normal/value_num/is_computed) أو لمسة لـ vitals-panel/clinical_examinations في م3 = **رفض فوري في المراجعة** — ده م6.
3. **فخ الترقيم القديم:** جدول جهد `clinic-dynamic-fields.md` §8 و`clinic-test-scenarios.md` بترقيم مختلف — الملزم الوحيد execution-phases (+ أضِف التنبيه في KB، Ruling 1).
4. **migrations نفس الخطوة:** moonui_dev_be حي بلا build — الـ3 migrations + الـ seeders تتشغل في نفس خطوة شحن BE وإلا 1054 يكسر الشاشات (درس مسجل).
5. **الصلاحيات الجديدة بلا wiring = فخ م0:** الأربع صلاحيات لازم تدخل presets م2 + `ClinicPermissionDependencies` وإلا الاستقبال مش هيشوف زرار التسجيل أصلًا.
6. **لا تفك حراسة شِيم `Patient`** (`$guarded=['*']`) — الكتابة عبر السيرفس/LabPatient فقط.
7. **unique swap في M2 (الأجوبة):** الاستبدال يتم في migration واحدة atomic (drop+add) بعد الـ backfill؛ اختبار idempotency + إبقاء index الـ encounter.
8. **النشر:** moonui `/app` فقط · ⛔ لا cp لعملاء/staging · CHANGELOG ثنائي · تحديث سجل التقدم فورًا مع كل WP.
9. **UI بهوية teal + `app-form-dialog` + reactive forms** — أي فورم p-dialog/ngModel جديد = رفض (نمط قديم؛ الـ quick-add الحالي نفسه يتحول).

---

## 🔧 تصحيح ملزم (Fable 2026-07-03) — دلالة «locked» (حسم تعارض 2.3↔4.4 اللي اصطادته مراجعة WP-F1)

**«locked» يُعاد تعريفه: locked = visibility-pin + required-FLOOR (مش required-دايمًا).** عبارة 4.4 «locked→required دائمًا» تُصحَّح: «locked → لا يُخفى أبدًا، وrequired لا ينزل تحت ديفولت الـ registry (الرفع بالإعداد متاح، الخفض ممنوع)». السبب: phone/gender **nullable اليوم** (`StoreLabPatientRequest:32-33`) وdoctor `code` **auto-fill من sequence** (`StoreLabDoctorRequest:49`) → «required-إدخال» يكسر المسارين ويسمّم duplicate-check (walk-in بلا تليفون = «00000000»). رغبة المالك في التليفون تُلبَّى **بالباقات لا بالكود** (OBGYN/KSA يشحنان `phone:{required:true}`، «تسجيل سريع» اختياري؛ co4=OBGYN فالتست يبيّنه).

### القاعدة لـ WP-B2 — `PatientFormRules` (كلاس واحد، لا fork):
1. **`base()` (مسار LIS):** قواعد اليوم حرفيًا (name/national_id/nationality required · gender/phone nullable · unique قومي) **ويتجاهل `clinic.patient_form_fields` كليًا وللأبد.** `Store/UpdateLabPatientRequest` يستهلكان `base()` فقط → Pest Red-Flag-#1 يثبت بالبناء.
2. **`forClinic($settings)` (مسار الكلينك):** `base()` + overlay: غياب الإعداد ⇒ = base() تمامًا (تليفون اختياري walk-in) · `hidden`(غير-locked) ⇒ يُقصّ silently في prepareForValidation (كلينكي فقط) · `visible+required` ⇒ required · locked ⇒ أي hidden عليه يُتجاهل + required floor=ديفولت الـ registry (يُرفع لا يُخفض). Update بـ `sometimes`.
3. **خريطة locked:** `name` floor=required (دايمًا المسارين) · `gender`/`phone` locked-visible + floor=optional (اختياري افتراضيًا؛ الإعداد/الباقة ترفعهما على مسار الكلينك فقط).
4. **WP-B3 `DoctorFormRules` نفس الدلالة:** `base()`=StoreLabDoctorRequest اليوم (phone required+unique · الأسماء nullable · `code` nullable+auto-fill، الـ registry يعلّمه `system:true` فتوجل Required عليه رمادي معطّل) · locked=`name_ar/name_en/code` visibility بس · **دلتا كلينكية وحيدة** (POST clinic/doctors باب جديد): `name_ar: required_without:name_en`.
5. **Pest بوابة يمتد:** «الإعداد مضبوط بأقسى صورة (كل شيء hidden/required) → `POST /lis/patients` لم يتغير بايت واحد».

### أثر على WP-F1 (fix بسيط): الـ registry: `phone`/`gender` locked=true بس **defaultRequired=false**؛ `code` system=true. الـ `ProfileFormConfigService.fieldRequired()`: **required = registryDefaultRequired \|\| settingRequired** (floor)، مش «locked ⇒ required دايمًا».

---

## 🔧 توضيح ملزم 2 (Fable 2026-07-03 — أثناء اعتماد تصميم WP-B2) — **الـ floor للحقول الـ locked فقط**

مراجعة تصميم WP-B2 كشفت قراءة خاطئة محتملة لصيغة WP-F1 أعلاه («required = registryDefault \|\| setting») لو اتطبقت على **كل** الحقول: كانت هتمنع خفض `national_id`/`nationality` لـ optional على باب الكلينك — وده **يكسر باقة OBGYN نصًّا** (Ruling 4.6: «قومي optional») وباقة co4 للتست (Ruling 8.1). **الحسم:**

1. **locked (name/gender/phone):** floor = ديفولت الـ registry — `required = registryDefault || setting.required`؛ الإخفاء يُتجاهل. (زي ما التصحيح الأول قال.)
2. **غير-locked:** **الإعداد سيادي على باب الكلينك**: `visible:false` ⇒ قصّ + إسقاط القاعدة · `required:true` ⇒ required · **`required:false` ⇒ optional (الخفض مسموح — ده جوهر آلية المالك)** · المفتاح غائب ⇒ ديفولت الـ registry = سلوك اليوم. صمام صفر-الانحدار = `base()` لباب LIS + الديفولتات، **مش** floor عام.
3. عند الخفض: `required`→`nullable` مع **إبقاء rule الـ unique** (nullable يقصّر على null؛ الـ unique يظل عند الإدخال). عند الرفع على مسار update: **`sometimes|required`** مش required مطلق (تُحترم partial updates). اتحقق DB: national_id/nationality/gender/phone كلها nullable على مستوى الجدول — الخفض آمن.
4. **overlay يشتغل على مفاتيح الـ registry فقط** — `partner_id`/`insurance_contract_id`/`is_active`/`mrn` وأمثالها لا تُقصّ ولا تُمسّ أبدًا (is_active toggle الكلينكي — Ruling 2.6 — يمر من هنا).
5. **duplicate-check (إلحاق بـ Ruling 6):** لا يستثني غير النشط — يرجّع `is_active` ضمن الحقول والـ FE يعرض شارة «غير نشط» (التوازي مع طبقة الـ 422 اللي بتستثني soft-deleted فقط). الحراسة `clinic.patients.view` + حد أدنى لطول محور الاسم (≥2) + الراوت قبل `patients/{patient}`.
6. **403 الـ B2B تفضل 403 على البابين** (مش 404 كلينكي): `show` الكلينكي أصلًا غير مفلتر داخليًا (assertSameCompany فقط) فالإخفاء بـ404 بلا قيمة، وكيان-واحد = دلالة-خطأ-واحدة.

### 🔧 تنقيح إضافي (Fable via WP-B2، 2026-07-03) — non-locked = سيادي
تصحيح على «required-floor»: **الـ floor للـ LOCKED فقط** (name/gender/phone — رفع بس، لا خفض). **الحقول NON-LOCKED = إعداد الكلينك سيادي عليها** (يقدر يخفّض required كمان، مش رفع بس؛ مع إبقاء قاعدة الـ unique) → عشان باقة OBGYN تخلّي `national_id` **اختياري لمصر**. الـ FE `ProfileFormConfigService.fieldRequired`: locked → `registryDefault||setting` · **non-locked visible → `setting.required ?? registryDefault`** (سيادي، يقدر يخفّض). (تصحيح على WP-F1-fix اللي طبّق floor للكل — يُطوى في WP-F2.) الـ base()/LIS مايتأثرش (بيتجاهل إعدادات الكلينك خالص؛ الـ Pest الأقسى يثبته).

### قرار «تطبيق باقة» = REPLACE (ratified 2026-07-03، بعد مراجعة WP-B3)
تطبيق باقة **يستبدل (REPLACE)** كامل `clinic.patient_form_fields` بشكل الباقة — **مقصود** (باقة «تسجيل سريع» لازم تخفي الحقول اللي مش فيها؛ MERGE مش هيحقق ده). الأسئلة الديناميكية upsert (لا تُحذف). **WP-F3 يعرض تأكيد «هيستبدل تهيئتك الحالية» قبل الاستدعاء.** company id دايمًا server-derived؛ atomic + idempotent (آمن للإعادة).
