# Clinical Core — توحيد البلوكات الطبية المشتركة (روشتة / تحاليل / أشعة / …)

> تحليل فقط — 2026-10-21. المرجع: التقارير r1/r2/r3 + فحص مباشر للكود (file:line تحت كل ادّعاء). BE = `/home/moonui3/moon-erp-be/Modules`، FE = `/home/moonui3/public_html/moon-erp/src/app/features`.

---

## 1. الخلاصة

**الـ Clinical Core** = مجموعة بلوكات طبية **واحدة** بتشتغل في كل عيادة (عام / تغذية / أسنان / نسا / إقامة مستشفى) من غير ما أي تخصص يبني نسخته. كل تخصص **بيستضيف** البلوك ويضيف عليه حقول/قوالب/مفضّلات — لكن ما يعملش جداول أوامر خاصة بيه.

البلوكات اللي بنوحّدها دلوقتي (المرحلة الأولى): **(1) الروشتة والأدوية** (بما فيها أدوية الإقامة MAR وروشتة الخروج)، **(2) طلبات التحاليل**، **(3) طلبات الأشعة** (بتقريرها وصورها)، **(4) النتائج والمستندات الإكلينيكية** (نتيجة/تقرير/إجازة مرضية/تقرير طبي/شهادة)، **(5) المرفقات والتنبيهات**، **(6) محرك الطباعة**. الباقي (التشخيص، العلامات الحيوية الديناميكية، الملاحظات، المتابعة) موجود أو مخطط في الـ KB وبينضم بترتيب لاحق.

**أكبر قرار:** **موديول Clinic هو الـ Clinical Core — مفيش موديول جديد**، و**الـ Encounter هو سياق كل أمر طبي** (زيارة عيادة / إقامة / يوم عملية / متابعة تليفون) بدل ما نعمل ربط polymorphic على كل جدول. الإقامة أصلاً بتفتح encounter (`hospital_admissions.encounter_id`)، وكل جداول الأوامر مفتاحها `encounter_id`، والـ LIS عنده `RequestSource::InPatient`. يبقى اللي ناقص **نوع** للـ encounter مش **أنكر** جديد.

**الوضع الحالي باختصار:** 3 نماذج أدوية ما بيكلموش بعض (روشتة العيادة، MAR المستشفى بنص حر، صورة روشتة المتجر)، الروشتة السريعة بتخزّن «—» في الحقول المهيكلة، إعداد مصدر الأدوية ما بيتقراش، الأشعة بتقف عند `ordered` للأبد، والاستقبال بيعمل طلبات تحاليل/أشعة بيتخطى وضع standalone.

---

## 2. الوضع الحالي

| البلوك | عام (Clinic) | تغذية | أسنان | مستشفى | نسا (مخطط) | LIS | WebStore | العيب المؤكد (file:line) |
|---|---|---|---|---|---|---|---|---|
| **روشتة** | ✅ أصل | ♻️ reused (`nutrition-consult-board.html:650`) | ♻️ reused (`consultation.html:440`) | ❌ **مكرر** `hospital_med_orders` بدواء نص حر | — (هيعيد استخدام) | — | ❌ مكرر (صورة فقط) | ✔ مؤكد: preset بيكتب `'—'` في dose/frequency/duration (`prescription.component.ts:87,595-597`) · `IssuePrescription.php:51-55` بيفرض الثلاثة required + `drug_id` في `products` · `hospital_med_orders.drug` string (`2026_10_12_100005:28`) · `NursingService::ROUTES` (`:30`) ≠ `MedicationRoute` (10 vs 10 مختلفين: المستشفى فيه sublingual/ng/other وناقصه ophthalmic/otic/nasal) · FE التمريض بيبعت `drug` نص بلا product (`nursing-panel.component.ts:43`) · `clinic.pharmacy_source` **معرّف ومش مقروء في أي مكان** (seeder + `clinic-settings.registry.ts:247` فقط) · `product_drug_details.dosage_form_id` موجود (Core `2026_08_02_100300:31`) ومش مستخدم في الروشتة |
| **قفل الزيارة على الكتابة** | ⚠️ FE فقط | ⚠️ | ⚠️ | — | — | — | — | ✔ مؤكد: `Encounter::isLocked()` (`Encounter.php:233`) بيتستخدم في `EncounterController:175` و`EncounterService:314` فقط — **مش** في `IssuePrescription` / `StoreClinicalIntent` / `CreateRadOrder` / `OrderLabFromEncounter` / `SubmitExternalResult` |
| **طلب تحاليل** | ✅ intent + decide | ♻️ reused (`:672`) | ♻️ عبر dialog | ❌ **ناقص** (مفيش شاشة طلب على الإقامة) | — | ✅ أصل النتائج | — | ✔ **تصحيح للـ KB:** `clinic_service` **موجود** في whitelist النية (`StoreClinicalIntentRequest.php:33,55`) ومستهلك (`DecideClinicalIntent.php:95-121`) والـ picker بيقلب على `lab_mode` (`lab-orders.component.ts:109`) — F2/F3 اتعملوا. **الفجوة الحقيقية:** `CreateVisitRequest.php:89,94` بيقبل `lab_investigations`/`rad_procedures` ids فقط → الاستقبال في standalone ما يقدرش يطلب من الكتالوج · 5 مسارات لإنشاء سطر تحاليل (r2 §6) |
| **طلب أشعة** | ✅ RIS-lite | ♻️ (`:683`) | ❌ صور أسنان مش rad order (`dental_tooth_images`) | ❌ ناقص | — | — | — | ✔ مؤكد: `RadOrderStatus` فيه Scheduled/Performed/Reported (`RadOrderStatus.php:7-11`) لكن **الوحيد اللي بيتكتب** `Ordered` (`CreateRadOrder.php:89`) · `MarkEncounterRadComplete.php:27` بيرجع فاضي لو `encounter_id` null — وهي nullable (`170002:27`) فطلبات الاستقبال عمرها ما تكتمل · مفيش cancel/refund/templates/صور (`study_uid` ما بيتكتبش) |
| **نتائج ↔ الشيت** | ✅ `clinical_documents` | ♻️ بيقرا `lab_results` بالكود | ♻️ | ⚠️ عبر encounter الإقامة بس | — | ✅ | — | ✔ `clinical_documents.encounter_id` NOT NULL + unique `(company, encounter, ref_key)` (`clinical_documents migration:21,37`) → نتيجة بلا زيارة ما تتسجلش · `DropLabReportOnEncounter` بيتخطى no-encounter |
| **تشخيص/شكوى** | ✅ `encounter_diagnoses` | ❌ ناقص (chief_complaint بس) | ♻️ | ❌ مكرر `admissions.diagnosis` string (`100002:52`) | — | — | — | r3 §2 |
| **تنبيهات/حساسية** | ⚠️ مشتق من history | ⚠️ جزئي | ♻️ dent_ pack | ❌ ناقص | يقترح `patient_alerts` | — | — | `PatientReadService.php:249-272` |
| **مرفقات/ميديا** | ✅ `clinic_patient_attachments` | ♻️ | ♻️ | ❌ ناقص | يقترح morph+kind | — | — | الجدول patient-level فقط (`patient_id, disk, path, mime…`) بلا `attachable` |
| **مستندات (إجازة/تقرير/شهادة)** | ❌ | ❌ | ❌ | ❌ (discharge_summary نص) | — | — | — | غير موجود في أي مكان |
| **طباعة** | FE `clinic-print-shell` | ♻️ + BE print-context مكرر | ❌ window.print يدوي | ❌ | — | ✅ BE DomPDF | — | r3 §9 — 3 طرق، مفيش PDF إكلينيكي من الـ BE |
| **Record API** | ❌ | ✅ `VisitRecordService` | ❌ | ❌ | هيقلّد التغذية | — | — | r3 §10 |

---

## 3. المبادئ الملزمة للتوحيد

1. **كيان واحد لكل مفهوم.** روشتة = `prescriptions`/`prescription_items` بس. طلب تحاليل/أشعة من الطبيب = `clinic_clinical_intents` بس. تقرير أشعة = `rad_reports` بس. أي تخصص (أسنان/تغذية/نسا/مستشفى) **ممنوع** يعمل جدول أوامر أو أدوية خاص بيه.
2. **السياق = Encounter، والـ Encounter ليه نوع.** `encounters.kind` (`outpatient` | `inpatient` | `day_case` | `remote`) + `encounters.admission_id` nullable. الإقامة = encounter واحد طول مدتها (موجود فعلاً). الحلقة (episode) والإجراء (clinical_procedure) بيربطوا encounters، مش بيستبدلوها. الأرضية الدنيا = `patient_id` (موجود على كل الجداول) للحالات اللي مفيهاش encounter (LIS walk-in).
3. **المفردات من مكان واحد.** طرق الإعطاء = `Clinic\Enums\MedicationRoute` (اتحاد القائمتين = 13 قيمة). التكرار = `Clinic\Enums\MedicationFrequency` (كود → أوقات/يوم، منقول من `NursingService::FREQ`). الشكل الصيدلي = Core `dosage_forms`. ممنوع قوائم ثابتة في أي كومبوننت أو سيرفس تاني.
4. **الـ BE هو اللي بيفرض القفل والتوقيع.** كل كتابة إكلينيكية على encounter موقّع = `EncounterLockedException` من الـ action نفسه. الـ FE `[locked]` تجميل بس.
5. **الوضع = إعداد، مش كود.** `clinic.ordering_mode` / `lab_mode` / `rad_mode` / `pharmacy_source` / `pos.pharmacy.*` / `clinic.print.*` موجودين — بنقراهم في **نقطة واحدة** لكل واحد (`OrderingMode`, `ClinicCatalogMode`, و`PharmacySource` الجديد).
6. **منفّذ واحد للطلب.** كل اللي بيحوّل «بند كتالوج» لـ (سطر فوترة + LabRequest/RadOrder/self) بيعدّي على action واحد `ExecuteCatalogItem` (يتستخرج من فرع `DecideClinicalIntent::here`). النية (`clinic_clinical_intents`) = سجل الطبيب وبتتطلب encounter؛ طلب الاستقبال المباشر (`CreateVisit`) بينادي نفس المنفّذ مربوط بـ `service_order_id + patient_id` بلا encounter وهمي (عشان ما يطلقش `EncounterStarted` → `in_consultation`). وضع `immediate` = النية بتتقرر «هنا» فوراً بنفس المنفّذ.
7. **الصرف مش على شاشة الطبيب أبداً.** الروشتة بتصدر event وتحمل `dispense_status`؛ الصرف/البيع في POS/Sales. الطبيب بيشوف «اتصرفت» كشارة بس.
8. **النتيجة بترجع للمريض، والزيارة اختيارية.** `clinical_documents` بتتربط بالـ patient دايماً وبالـ encounter لو موجود. العرض موحّد بشارة المصدر (LIS / RIS / خارجي).
9. **محرك طباعة واحد** في الـ BE (DomPDF — نفس اللي LIS مثبته بالعربي) بقوالب لكل نوع مستند، وإعدادات `clinic.print.*` الموجودة. الـ FE preview يفضل لحد ما الـ PDF يثبت، بعدها يتشال.
10. **ALTER قبل CREATE.** جدول جديد بس لو الـ KB (GYN §7.2) اقترحه قبل كده أو مفيش مكان منطقي له.

---

## 4. التصميم لكل Block

### 4.1 الروشتة والأدوية (Prescription / Medication)

**المالك:** `Modules/Clinic` (موجود). الكتالوج: Core `products` (+`product_drug_details`) **أو** `clinic_drugs` حسب الإعداد.

| | التفصيل |
|---|---|
| **جداول — keep** | `prescriptions`, `prescription_items`, `clinic_drugs`, `clinic_drug_presets`, `clinic_drug_alerts`, `clinic_favorites`, Core `dosage_forms`/`product_drug_details` |
| **ALTER `prescriptions`** | `+ kind` string(16) default `outpatient` (`outpatient` \| `inpatient` \| `discharge`) · `+ subject` string(12) default `patient` (`patient` \| `spouse` \| `other`) + `subject_name` nullable (حالة «الزوج» في النسا، `gyna-reference:114`) · `+ dispense_status` string(16) default `pending` (`pending` \| `partial` \| `dispensed` \| `not_applicable`) · `+ admission_id` nullable (denormalized للسرعة — المصدر `encounter.admission_id`) · `encounter_id` يفضل NOT NULL |
| **ALTER `prescription_items`** | `dose`/`frequency`/`duration` → **nullable** · `+ sentence` text nullable (الجملة الجاهزة — بديل «—») · `+ frequency_code` string(12) nullable (من `MedicationFrequency`) · `+ times` json nullable · `+ prn` bool default 0 · `+ starts_at`/`ends_at`/`stopped_at` timestamps nullable + `stopped_by` · `+ item_status` string(10) default `active` (`active` \| `stopped` \| `completed`) · `+ dosage_form_id` nullable → `dosage_forms` + `form` string(100) snapshot · `drug_id` → **nullable** + `+ clinic_drug_id` nullable → `clinic_drugs` + `drug_name` string(191) snapshot · `+ dispensed_qty` decimal nullable · `route` يتوسع لـ 13 قيمة |
| **ALTER `clinic_drug_alerts`** | `drug_id` → nullable (الـ FK على `products` — `2026_10_09_110000:34`) `+ clinic_drug_id` nullable → `clinic_drugs` (xor) + unique يبقى `(company_id, drug_id, clinic_drug_id)`؛ `ClinicDrugAlert::notesFor()` ياخد المفتاحين — وإلا التنبيهات بتختفي في وضع `standalone_formulary` (وبتختفي من التغذية اللي بتستخدمه). |
| **قاعدة التحقق (BE)** | لكل بند: **(sentence) أو (dose+frequency+duration)** — واحد منهم على الأقل. **(drug_id) xor (clinic_drug_id)** حسب `clinic.pharmacy_source`: `products` → `drug_id` إلزامي؛ `standalone_formulary` → `clinic_drug_id` إلزامي و**ممنوع** `ResolveDrugProduct` يعمل firstOrCreate منتج (`ResolveDrugProduct.php:46-70` يتقيّد بالوضع). الحالة المختلطة الحالية (`clinic_drugs.product_id`) تفضل للتوافق. |
| **State machine** | روشتة: `issued → printed → cancelled`؛ + `dispense_status` مستقل. بند الإقامة: `active → stopped \| completed`. تعديل الروشتة مسموح **قبل الطباعة وقبل توقيع الزيارة** (endpoint جديد `PUT prescriptions/{id}`)؛ بعدها cancel + إصدار جديد (الإلغاء بعد التوقيع يفضل مسموح — `PrescriptionController:124`). |
| **API (واحد لكل السياقات)** | `GET/POST encounters/{e}/prescriptions` (موجود) + `PUT prescriptions/{p}` (جديد) + `POST prescriptions/{p}/cancel` (موجود) + `GET prescriptions/{p}/pdf` (جديد، BE PDF) · بنود الإقامة: `POST prescriptions/{p}/items/{i}/stop` (جديد) · كتالوج موحّد: `GET clinic/drugs/search?q=` يرجّع صفوف موحّدة `{source: product\|formulary, id, name, form, dosage_form_id, alert_note, presets[]}` حسب `pharmacy_source` (يستبدل البحث المزدوج في `clinic-prescription.service.ts:201-228`) |
| **FE** | `app-clinic-prescription [encounterId] [patientId] [locked] [kind=outpatient\|inpatient\|discharge] [subjectEnabled]` — نفس الكومبوننت في العيادة/التغذية/الأسنان/ملف الإقامة. يشيل `PRESET_FIELD_PLACEHOLDER` ويبعت `sentence`. الشكل من `dosage_forms` dropdown (مع إضافة inline). |
| **Settings** | موجود: `clinic.pharmacy_source` (يبقى **مقروء** في `Support/PharmacySource.php`), `pos.pharmacy.dispensing_enabled` / `allow_partial_dispense` / `prescription_validity_days` / `require_prescription_rx`, `clinic.print.*`. جديد: `clinic.rx.item_model` = `sentence` \| `structured` \| `both` (default `both`) — بيحدد الافتراضي في الفورم بس. |
| **Permissions** | موجود `clinic.prescriptions.view/create/print` + جديد `clinic.prescriptions.update`, `clinic.prescriptions.inpatient` (للتمريض/الطبيب المعالج). |
| **Events** | `PrescriptionIssued` (موجود، بلا listeners) · جديد: `PrescriptionUpdated`, `PrescriptionItemStopped`, `PrescriptionDispensed` (يطلقه POS). |

**المستشفى (MAR) ← نفس الموديل:** `hospital_med_orders` بتتقسم جزئين: **الإكلينيكي** (drug/dose/route/frequency/times/prn/starts/ends/status/ordered_by/stopped) → `prescription_items` تحت `prescriptions.kind=inpatient` على encounter الإقامة. **المالي/المخزني** (`product_id`, `warehouse_id`, `unit_price`, `charge_per_dose`) → يفضل في Hospital كجدول جديد `hospital_med_charging` (صف لكل بند: `prescription_item_id` unique, `product_id`, `warehouse_id`, `unit_price`, `charge_per_dose`) — **مش** على `hospital_med_admins` لأن الأعمدة دي لكل أمر مش لكل جرعة. `hospital_med_admins`: `+ prescription_item_id` nullable (backfill من `order_id`)، و`NursingService` بيحسب الـ slots من `prescription_items.times`. **إصلاح مرافق:** صرف المخزون (`ConsumptionService::issue`) يتفك من شرط التحصيل (`NursingService.php:129-135`) ويشتغل لو `product_id+warehouse_id` موجودين. **روشتة الخروج** = `prescriptions.kind=discharge` على نفس encounter الإقامة، بتتطبع مع ملخص الخروج، وبتظهر في «الأدوية الحالية» للمريض.

**الصرف (hand-off):** الطبيب يصدر → `PrescriptionIssued` → POS صيدلية (Phase 6) عنده worklist `GET pos/pharmacy/prescriptions?status=pending` يقرا من `prescriptions` مباشرة؛ البيع يستدعي `MarkPrescriptionDispensed` (يحدّث `dispensed_qty`/`dispense_status`). التغذية `DispensedItemsService` يفضل قارئ عرض.

### 4.2 طلبات التحاليل (Lab orders)

**المالك:** Clinic للطلب/النية/الفوترة، LIS للتنفيذ والنتائج.

| | التفصيل |
|---|---|
| **جداول — keep** | `clinic_clinical_intents`, `service_order_lines`, LIS `lab_requests`/`lab_results`, `clinical_documents` |
| **ALTER** | `clinic_clinical_intents.encounter_id` يفضل NOT NULL (كل طلب له encounter — حتى طلب الاستقبال «تحاليل بس» بيعمل encounter نوعه `outpatient` خفيف) · `lab_investigations` + `lab_packages`: `+ sbscs_code` (للمطالبات) · `clinical_documents.encounter_id` → nullable + unique يتغير لـ `(company_id, ref_key)` (الـ ref_key أصلاً global: `lab:reqinv:123`) |
| **منفّذ واحد** | جديد `Actions/ExecuteCatalogItem` = الفرع اللي جوه `DecideClinicalIntent::here` (`:86-160`): ياخد `{catalog_ref_type, catalog_ref_id, order, doctor_id, encounter?}` ويقرر حسب `ClinicCatalogMode`: `lis_investigation/lis_package` → `OrderLabFromEncounter::handleForOrder` · `rad_procedure` → `CreateRadOrder::handleForOrder` · `clinic_service` → `AddServiceOrderLine fulfillment=self`. **المستهلكون:** (أ) `DecideClinicalIntent::here` (الطبيب → الاستقبال). (ب) `OrderingMode=immediate`: `StoreClinicalIntent` ينادي `here` فوراً — endpoints `POST encounters/{e}/lab-orders|rad-orders` يبقوا wrappers (الروتس تفضل). (ج) `CreateVisit.php:132-146` ينادي `ExecuteCatalogItem` بدل الاستدعاء المباشر — بلا encounter (الزيارة «تحاليل بس» تفضل بلا encounter زي النهارده؛ `rad_orders.encounter_id` nullable لنفس السبب). (د) `CreateVisitRequest` يقبل `lab.items[] / radiology[] {catalog_ref_type, catalog_ref_id}` (التوافق: `investigation_id`/`procedure_id` القديمة تتحوّل داخلياً). |
| **التسعير** | متكامل: LIS price list للـ payer (موجود `OrderLabFromEncounter:78`). سعر تعاقدي لكل تحليل = `LabInsuranceContract` في LIS (قرار §9). مستقل: `clinic_services` بالـ 3 محاور + العقد (موجود). |
| **النتائج لكل سياق** | `DropLabReportOnEncounter` يبطّل يتخطى no-encounter: يكتب `clinical_documents` بـ `patient_id` + `encounter_id` nullable. الإقامة: `lab_requests.encounter_id` = encounter الإقامة و`source` **يفضل `clinic`** — ده قرار مسجّل (`his.md:219` «`RequestSource::clinic` قيمة جديدة مش `in_patient`»، و`RequestSource.php:10-12`)، وكبت فاتورة LIS بيتم بالـ sentinel `_suppress_invoice` (`CreateLabRequest.php:43-44`) مش بالـ source فمفيش خطر فوترة مزدوجة. التمييز «إقامة» من `encounter.kind`. لو المالك عايز LIS يعرض «مريض داخلي» → قرار §9 #13. `PatientLabHistoryService` يقرا بـ `patient_id` (مش encounter join) فيشمل زيارات LIS المباشرة. |
| **المطالبات** | `ClaimAssembler.php:68` ياخد `sbscs_code` من الكتالوج الفعلي (`lab_investigations`/`rad_procedures`/`clinic_services`) حسب `catalog_ref_type`. سطر الفوترة يفضل واحد لكل request، لكن `ClaimLine` تتفرد لكل `lab_request_investigation`. |
| **FE** | `app-clinic-lab-orders [encounterId] [locked] [orderingMode] [kind]` (موجود ويقلب على `lab_mode` — م7 WP5). يتستضاف **زيادة** في ملف الإقامة (تاب «طلبات»). شاشة قرار واحدة (`reception-order`) — كارت التغذية يستخدم نفس الـ dialog. |
| **Settings** | موجودة: `clinic.ordering_mode`, `clinic.lab_mode`, `clinic.results.external` (permission). لا جديد. |
| **Permissions** | موجودة `clinic.intents.*`, `clinic.lab-orders.*`, `clinic.lab_results.view`, `clinic.results.external`. تصحيح preset الاستقبال: يضاف `clinic.lab_results.view` (r2 §7). |
| **Events** | LIS موجودة (`LabResultReleased`, `LabRequestCompleted`, `LabRequestCancelled`…) — بلا تغيير. |

### 4.3 طلبات الأشعة (Radiology)

**المالك:** Clinic (RIS-lite موجود).

| | التفصيل |
|---|---|
| **keep** | `rad_procedures`, `rad_orders`, `rad_studies`, `rad_reports` |
| **ALTER** | `rad_orders`: `+ cancel_reason`/`cancelled_by`/`cancelled_at`, `+ payer_contract_id` nullable · `rad_procedures`: `+ sbscs_code`, `+ default_template_id` nullable · `rad_reports`: `+ template_id` nullable, `+ body_structured` json nullable · `service_order_lines` نوع radiology يستخدم `result_status` الموجود |
| **جديد (صغير)** | `clinical_templates` (`kind` = `rad_report` \| `note` \| `discharge` \| `sick_leave` \| `medical_report` \| `certificate`, `scope_ref_type/id` nullable, `name_ar/en`, `body_ar/en`, `fields` json, `is_active`, `sort_order`) — جدول قوالب **واحد** لكل المستندات بدل `dental_note_templates` + قوالب أشعة + قوالب خروج (الأسنان تتهاجر له لاحقاً). |
| **State machine (نكمّل الموجود)** | Order: `ordered → scheduled` (عند إنشاء الـ study — `CreateRadStudyOnOrder`) `→ performed` (عند `RadStudyController` acquired) `→ reported` (عند `RadReportReleased`) `→ cancelled` (action جديد `CancelRadOrder` مسموح قبل `performed`، يطلق `RadOrderCancelled` → `FlagLinesForRefund` نفس اللي للتحاليل). `MarkEncounterRadComplete` يشيل شرط `encounter_id` ويحط `result_status=ready` على السطر. الـ worklist تخفي `reported`. |
| **الصور/المرفقات** | `clinic_patient_attachments` + `attachable_type/id` (رقم 4.5) — الدراسة تربط صورها `attachable=rad_study`. `study_uid` يتكتب عند الاقتناء. |
| **تقرير + طباعة** | `rad_reports` تتعرض/تتطبع عبر `GET rad-reports/{r}/pdf` من محرك الطباعة (4.6). المحرر `rad-report-editor` ياخد القالب الافتراضي للإجراء. |
| **التسعير** | متكامل: `CreateRadOrder` يمر على `CoverageService::applyCoverage` بنفس طريقة السطر العادي (دلوقتي بسعر الإجراء الخام). مستقل: `clinic_services` نوع radiology (موجود). |
| **أشعة الأسنان — قرار صريح:** | **مش rad order.** `dental_tooth_images` (PA/BW/OPG/CBCT داخل العيادة، بلا تقرير) تفضل صور على السنة عبر المرفقات. لو العيادة بتبعت OPG/CBCT لمركز خارجي → طلب أشعة عادي في وضع standalone من نفس الكومبوننت. |
| **FE** | `app-clinic-rad-orders [encounterId] [locked] [orderingMode] [kind]` (موجود). الـ worklist/report-editor زي ما هم + زرار إلغاء + طباعة PDF. |
| **Settings** | `clinic.rad_mode` (موجود). لا جديد. |
| **Permissions** | موجودة + presets جديدة: **`radiologist`** (`rad-worklist.view`, `rad-reports.enter/verify/release`) و**`rad_technologist`** (`rad-worklist.view`, `rad-studies.update`). |
| **Events** | موجود `RadOrderPlaced`, `RadReportReleased` + جديد `RadStudyAcquired`, `RadOrderCancelled`. |

### 4.4 النتائج والمستندات الإكلينيكية (ينضم الآن)

`clinical_documents` هو **كيان المستند الموحّد**: `doc_type` ينضم له `sick_leave` · `medical_report` · `certificate` · `discharge_summary` · `rx_print` (snapshot الطباعة). ALTER: `encounter_id` nullable (4.2) · `+ document_number` (SequenceService) · `+ template_id` → `clinical_templates` · `+ signed_at/signed_by` · `+ from_date/to_date` (للإجازة). API: `POST encounters/{e}/documents {doc_type, template_id, data}` + `GET documents/{d}/pdf`. FE: `app-clinical-document-dialog [encounterId] [docType]` يظهر في لوحة الخروج (`consultation.html:685-734`) وفي ملف الإقامة. Permissions: `clinic.documents.create/print`. المستشفى: `admissions.discharge_summary` يفضل نص للتوافق لكن الطباعة من المستند.

### 4.5 المرفقات والتنبيهات (ينضم الآن)

- **المرفقات:** ALTER `clinic_patient_attachments`: `+ attachable_type/attachable_id` nullable (morph: `rad_study` \| `clinical_intent` \| `prescription` \| `dental_tooth` \| `clinical_procedure`…) · `+ kind` string(16) (`image` \| `pdf` \| `photo` \| `video` \| `link`) · `+ is_private` bool. المستشفى يستخدمه من ملف الإقامة. مفيش جدول ميديا جديد (GYN §7.2 «media (موجود: attachments)» ✔).
- **التنبيهات:** جديد `patient_alerts` (كما في GYN §7.2: `patient_id, code, label, severity, source (history_answer \| manual \| drug_alert), source_ref, active, set_by`). يتغذى تلقائياً من `PatientReadService` allergies (listener على حفظ history) + يدوي. الهيدر المشترك `clinic-patient-header` يقرا منه في كل الشاشات بما فيها ملف الإقامة. Permission: `clinic.alerts.manage`.

### 4.6 محرك الطباعة (ينضم الآن)

`Clinic/app/Services/ClinicalPdfService` فوق DomPDF (نفس `LabReportPdfService`): قوالب Blade لكل نوع (`prescription`, `rad_report`, `document`, `visit_summary`, `referral`). الهيدر/الفوتر/اللوجو/الورق من `clinic.print.*` (موجودة) + توقيع لكل طبيب (`lab_doctors.signature_attachment_id` جديد). endpoint نمطي `GET clinic/print/{kind}/{id}.pdf`. الـ FE `clinic-print-shell.service.ts` يفضل للـ preview السريع حتى يتأكد المالك من الـ PDF، بعدها نحوّل الـ 5 طباعات + الأسنان + التغذية (بتنسيق مع moonui) عليه.

### 4.7 البلوكات اللاحقة (مخططة في الـ KB — ترتيبها بعد المراحل دي)

| البلوك | القرار |
|---|---|
| تشخيص/شكوى | `encounter_diagnoses` + `ClinicComplaint` هم الكيان. المستشفى يستخدمهم على encounter الإقامة (`admissions.diagnosis` يبقى «سبب الدخول» فقط). التغذية تضيف لوحة التشخيص (moonui). |
| علامات حيوية | محرك الحقول الديناميكية (م6 — `clinic-dynamic-fields`). `hospital_vital_rounds` تفضل (time-series تمريضي بـ NEWS2) لكن تعريفاتها/حدود الخطر من المحرك. |
| ملاحظات/addenda | `clinical_notes` (GYN §7.2) — immutable + addendum؛ `EncounterAmendment` يفضل للزيارة الموقّعة. `hospital_nursing_notes` تتهاجر له بـ `kind=nursing` لاحقاً. |
| متابعة/إحالة | `ClinicReferral` موجود · `follow_up_tasks` (GYN) يوحّد `dental_recalls` + `admissions.follow_up_on` + متابعة التغذية. |
| Patient record API | `GET clinic/patients/{p}/record?encounter_id=&sections=` في Clinic (`PatientRecordService` بنفس نمط `safe()` بتاع التغذية) — التغذية والنسا بيمدّوه بأقسامهم بدل ما يبنوا endpoint لكل تخصص. |
| access_audit | قبل تشغيل أي عميل طبي (GYN مرحلة 0). |

---

## 5. الـ Extension contract للتخصصات

**التخصص = `ClinicalType` (enum بالكود) + entry في registry الـ FE + باقة حقول.** اللي يقدر يعمله:

| مسموح | ممنوع |
|---|---|
| حقول إضافية عبر محرك `history_questions` (packs بـ prefix: `nut_`, `dent_`, `gyn_`) بـ scope patient/encounter + `show_if` لما يتنفذ | جدول روشتة/أدوية/أوامر تحاليل/أشعة خاص |
| Presets أدوية وقوالب (`clinic_drug_presets`, `clinical_templates` بـ `scope_ref_type=clinical_type`) ومفضّلات لكل طبيب | قوائم route/frequency/form خاصة |
| جداول خاصة للي **بيتعرض كـ cards أو بيتبحث فيه** (odontogram، InBody، دورات العقم، حمل…) — زي GYN §7.2 | نسخ من `clinical_documents`/`attachments`/`patient_alerts` |
| استضافة الكومبوننتات المشتركة بالـ inputs (`[encounterId] [patientId] [locked] [kind]`) وإخفاء/إظهار بلوك بإعداد `{type}.feature.*` (نمط `dental.feature.*`) | فورك كومبوننت مشترك أو طباعة يدوية (`window.print`) |
| قراءة الـ record API المشترك وإضافة أقسام عليه (`PatientRecordService::extend('nutrition', fn)`) | endpoint record خاص بكل تخصص |

**المستشفى = سياق مش تخصص.** الإقامة ما تبقاش `clinical_type`؛ القسم المعالج يفضل بتخصصه (جراحة/نسا/باطنة)، و`encounters.kind=inpatient` هو اللي بيفتح بلوكات الإقامة (MAR، التمريض، الخروج) جنب نفس الروشتة/التحاليل/الأشعة. ملف الإقامة (`admissions/:id`) يستضيف `app-clinic-prescription [kind=inpatient]`, `app-clinic-lab-orders`, `app-clinic-rad-orders`, المستندات، المرفقات. `AdmissionService.php:125-131` يفتح الـ encounter بـ `kind=inpatient` + `admission_id`.

**التغذية (moonui):** بتستضيف الكومبوننتات فعلاً. التغيير المطلوب منهم: (1) `PatientMedicationsService` يقرا `sentence ?? dose/frequency/duration` (وإلا تظهر فراغات بعد الهجرة)، (2) تبنّي لوحة التشخيص المشتركة، (3) تحويل `print-context` على محرك الطباعة. يتم بالاتفاق مش بتعديل كودهم من هنا.

---

## 6. عيادة / مركز / مستشفى — نفس الكور بإعدادات

| الإعداد | عيادة واحدة | مركز | مستشفى |
|---|---|---|---|
| `his.mode` | single_clinic | center | center |
| `clinic.ordering_mode` | immediate (الدكتور بينفذ) | intent (الاستقبال بينفذ) | intent |
| `clinic.lab_mode` / `rad_mode` | standalone (معمل برّه) | integrated أو standalone | integrated |
| `clinic.pharmacy_source` | standalone_formulary | products | products |
| `pos.pharmacy.dispensing_enabled` | false | true (صيدلية POS) | true |
| `encounters.kind` المستخدمة | outpatient / remote | outpatient / day_case | + inpatient (Hospital module) |
| `clinic.procedures.mode` (GYN §7.6) | external | center | hospital |
| `clinic.visit_stations` | مدمجة | منفصلة | منفصلة |
| الطباعة | `clinic.print.*` + توقيع الطبيب | نفسه | نفسه + ملخص خروج |

مفيش سطر كود يتغير بين الأعمدة الثلاثة.

---

## 7. الهجرة بدون كسر

كل الخطوات additive وقابلة للرجوع؛ البيانات الحية على moonui3 صغيرة.

1. **بنود «—»:** migration تمشي على `prescription_items` حيث `dose='—' OR frequency='—' OR duration='—'` → `sentence = instructions`، الحقول الثلاثة = NULL، `form` = القيمة اللي كانت في `dose` لو مش «—». `instructions` تفضل زي ما هي. الطباعة القديمة اللي كانت بتمسح «—» (`clinic-prescription.service.ts:295-305`) تتشال.
2. **الشكل الصيدلي:** seed `dosage_forms` بالقائمة العربية الشائعة (من `COMMON_DRUG_FORMS_AR` في الكومبوننت) لكل شركة (updateOrCreate على `code`) → backfill `prescription_items.dosage_form_id` / `clinic_drugs` / `clinic_drug_presets` بمطابقة الاسم؛ اللي ما يتطابقش يفضل نص في `form`.
3. **الطرق/التكرار:** `MedicationRoute` يتوسع (إضافة قيم بس = backward-safe). `MedicationFrequency` جديد؛ بنود الإقامة القديمة `frequency` كود صغير يتنقل لـ `frequency_code`.
4. **MAR → prescriptions:** migration تعمل لكل إقامة عندها `hospital_med_orders` روشتة واحدة `kind=inpatient` على encounter الإقامة، وتنسخ كل order لبند (`drug_name=drug`, `drug_id=product_id`, `times`, `prn`, `starts_at`, `ends_at`, `item_status`) وتكتب `hospital_med_admins.prescription_item_id`. **`hospital_med_orders` يفضل للقراءة ريليس واحد** (`NursingService` يكتب في الاتنين خلف flag `hospital.mar_on_prescriptions`) ثم يتجمّد. الروتس `api.hospital.nursing.orders.*` تفضل وتنادي الـ Clinic actions.
5. **الأشعة الواقفة عند ordered:** migration تصلّح الحالة من الواقع: order له study `acquired` → `performed`؛ له report `released` → `reported` + سطر `result_status=ready`. مفيش بيانات بتتمسح.
6. **الجسر `clinic_services.lis_investigation_id/rad_procedure_id`:** **يُحذف من الـ API** (الحارسان + الـ import) ويفضل العمود ريليس واحد ثم drop. السبب: القرار المعتمد «بلا mirror» + الوضع المتكامل بيختار من كتالوج LIS مباشرة، فالجسر ما لوش مستهلك.
7. **`clinical_documents.encounter_id` nullable + unique جديد:** migration: drop unique القديم → add `(company_id, ref_key)`. تتأكد الأول إن مفيش تكرار (ref_key global فعلاً).
8. **`CreateVisit`:** الـ payload القديم (`lab.investigations[].investigation_id`, `radiology[].procedure_id`) يتقبل ويتحوّل داخلياً — الـ FE يتحدّث في نفس المرحلة، مفيش كسر للعملاء.
9. **الطباعة:** PDF endpoints تتضاف جنب الـ FE preview؛ التحويل الكامل بعد اختبار المالك.

---

## 8. المراحل

| # | المرحلة | التسليم | الحجم | المخاطر | المالك يختبر على /app |
|---|---|---|---|---|---|
| **P0** | الأساس | `MedicationRoute` 13 قيمة + `MedicationFrequency` + seed `dosage_forms` · `encounters.kind/admission_id` (+ `AdmissionService` يملاهم) · قفل BE على الـ 5 actions · `clinic_patient_attachments` morph · `clinical_documents.encounter_id` nullable · `PharmacySource` reader | **S–M** | unique الجديد على المستندات | يفتح زيارة موقّعة ويحاول يضيف روشتة/طلب من الـ API → 409؛ dropdown «الشكل» في الروشتة بيقرا من `dosage_forms` ويقبل إضافة inline |
| **P1** | روشتة v2 | نموذج البند (جملة أو مهيكل) بلا «—» · `PUT prescriptions` قبل الطباعة · subject (زوج) · `clinic.pharmacy_source` فعّال (standalone بلا منتج) · بحث أدوية موحّد · `GET prescriptions/{p}/pdf` · migration «—» | **M** | التغذية بتقرا الحقول القديمة (تنسيق moonui) · DomPDF عربي (مثبت في LIS) | روشتة من preset → الطباعة فيها الجملة صح وبلا «—»؛ يعدّل بند ويطبع؛ يقلب الإعداد لـ standalone ويروشت دوا من `clinic_drugs` بلا ما يتخلق منتج |
| **P2** | أدوية الإقامة على الروشتة | MAR على `prescription_items kind=inpatient` · `hospital_med_charging` · إصلاح صرف المخزون · روشتة الخروج تتطبع مع ملخص الخروج · ملف الإقامة يستضيف `app-clinic-prescription` | **M–L** | migration الـ admins · لوحة التمريض بتتغير | يدخل مريض، يأمر دوا من نفس كومبوننت الروشتة (كاتالوج حقيقي بدل نص)، التمريض يدي جرعة → تتحاسب ويتصرف المخزون؛ روشتة خروج تظهر في «الأدوية الحالية» |
| **P3** | منفّذ واحد + إكمال الأشعة | استخراج `ExecuteCatalogItem` · `immediate` عبر النية · `CreateVisit` على نفس المنفّذ (standalone للاستقبال، بلا encounter) · state machine الأشعة + إلغاء/refund + coverage · `result_status` للأشعة · طلبات من ملف الإقامة على encounter الإقامة · presets radiologist/technologist | **L** (يتقسم 4 WPs) | أكبر تغيير في فلو الاستقبال | في standalone: الاستقبال يحجز زيارة فيها تحليل من كتالوج العيادة ويتحاسب؛ أشعة: طلب → worklist → اقتناء → تقرير → الطلب `reported` وبيختفي من الـ worklist وبيظهر «جاهز» عند الاستقبال؛ إلغاء طلب أشعة قبل التنفيذ يعلّم السطر للاسترداد |
| **P4** | النتائج والسجل | نتائج بلا زيارة على ملف المريض · `PatientLabHistory` بالمريض · تقرير أشعة بقالب + صور + PDF · `sbscs_code` في المطالبات · `GET clinic/patients/{p}/record` في الكور (التغذية تمدّه) | **M** | تنسيق التغذية على الـ record | طلب LIS مباشر (walk-in) يظهر في تاريخ تحاليل المريض من شاشة الكشف؛ تقرير أشعة بقالب يتطبع PDF بصورة مرفقة |
| **P5** | المستندات والتنبيهات | إجازة مرضية / تقرير طبي / شهادة من `clinical_documents` + `clinical_templates` + PDF · `patient_alerts` في الهيدر في كل الشاشات بما فيها الإقامة · تحويل الطباعات القديمة على المحرك | **M** | — | يطلع إجازة مرضية برقم متسلسل من لوحة الخروج ويطبعها؛ حساسية مسجلة في history تظهر كتنبيه أحمر في ملف الإقامة |
| **P6** | صرف الصيدلية (POS) | worklist روشتات معلقة في POS · بيع → `PrescriptionDispensed` · `dispense_status` شارة عند الطبيب · إعدادات `pos.pharmacy.*` فعّالة | **M** | يعتمد على POS | يروشت → يفتح POS يلاقي الروشتة → يبيع → الشارة «اتصرفت» عند الطبيب |

**بعد P6:** `clinical_notes`, `follow_up_tasks`, هجرة `dental_note_templates`/`dental_recalls`, vitals الديناميكية (م6), `access_audit`.

---

## 9. المخاطر والقرارات المطلوبة من المالك

1. **مكان الكور: موديول Clinic (التوصية) ولا موديول جديد `ClinicalCore`؟** Clinic بيملك فعلاً 30+ جدول إكلينيكي وكل التخصصات بتعتمد عليه. موديول جديد = نقل جداول وكسر namespaces بلا فايدة. **توصية: Clinic، مع مجلد `app/Clinical/` للـ actions المشتركة.**
2. **السياق: Encounter بنوع (التوصية) ولا polymorphic context على كل جدول؟** الدليل: `hospital_admissions.encounter_id + service_order_id` موجودين، كل جداول الأوامر `encounter_id`، LIS عنده `in_patient`، والسياق الوحيد بلا encounter هو LIS walk-in وده مغطّى بـ `patient_id`. Polymorphic = فقد FKs + إعادة كتابة كل القرّاء والـ listeners. **توصية: Encounter + `kind`.**
3. **هجرة MAR على الروشتة (P2) — دلوقتي ولا بعد P3؟** المستشفى اتبنى على moonui3 (نفس الإنستانس) فالتنسيق سهل. **توصية: P2 زي الترتيب** لأن الإقامة هي أكبر مكرر.
4. **محرك الطباعة: BE PDF (التوصية) ولا نكمل FE HTML؟** LIS مثبت DomPDF بالعربي. **توصية: BE PDF مع إبقاء الـ preview لحد ما تختبر.**
5. **سعر تعاقدي لكل تحليل في الوضع المتكامل:** `LabInsuranceContract` في LIS (التوصية) ولا تمديد عقود العيادة؟ **توصية: LIS** — الكتالوج هناك والسعر هناك.
6. **أشعة الأسنان:** مش rad order (التوصية) — صور على السنة؛ الخارجي = طلب standalone. **محتاج تأكيد.**
7. **الصرف (P6): دلوقتي ولا بعدين؟** الإعدادات `pos.pharmacy.*` مزروعة ومكتوب عليها «لسه هتتبني». **توصية: بعد P3** — البنية جاهزة من P1 (`dispense_status` + event).
8. **التغذية:** الهجرة في P1 هتغيّر اللي `PatientMedicationsService` بيعرضه. **قرار: مين يعدّل قارئ التغذية (moonui) وإمتى؟** ومعاه تبنّي لوحة التشخيص والـ record المشترك.
9. **حذف جسر `clinic_services.lis_investigation_id/rad_procedure_id`** (التوصية: يتشال — بلا مستهلك، والقرار «بلا mirror» معتمد) ولا يتفعّل؟
10. **المطالبات:** تفريد `ClaimLine` لكل تحليل (التوصية) ولا سطر واحد للطلب؟ NPHIES محتاج كود لكل خدمة.
11. **presets الصلاحيات الجديدة** (`radiologist`, `rad_technologist`) وتصحيح preset الاستقبال بإضافة `clinic.lab_results.view` — موافقة.
12. **الروتس المباشرة `POST encounters/{e}/lab-orders|rad-orders`:** تفضل كـ wrappers (التوصية) ولا تتشال؟ إبقاؤها = صفر كسر للـ FE.
13. **مصدر طلبات التحاليل من الإقامة في LIS:** يفضل `source=clinic` (التوصية — قرار مسجّل في `his.md:219`، والتمييز من `encounter.kind`) ولا نرجع لـ `in_patient` عشان شاشات LIS تعرض «مريض داخلي»؟ لو `in_patient`: تعديل سطر واحد في `OrderLabFromEncounter` + فلاتر LIS اللي بتعتمد على `clinic`.

**مخاطر عامة:** (أ) حجم P3 — يتقسم WPs صغيرة (immediate-via-intent / CreateVisit / rad SM / coverage) كل واحدة بسيناريو على /app. (ب) الاختبارات على SQLite ما بتكشفش أعمدة ناقصة — كل migration تتراجع على MySQL moonui3. (ج) أي تعديل في `AddServiceOrderLine`/`CollectReceptionReceipt` يتعمل مرة واحدة في الطبقة المشتركة (قاعدة المالك).
