Moon Clinic · Clinical Core · تحليل (مفيش تنفيذ)

توحيد البلوكات الطبية المشتركة

الروشتة، والتحاليل، والأشعة، والمستندات، والمرفقات، والطباعة: نسخة واحدة قوية تشتغل في كل أنواع العيادات (عام / تغذية / أسنان / نسا / إقامة مستشفى) — عيادة أو مركز أو مستشفى بإعدادات بس.

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 المرفقات والتنبيهات (ينضم الآن)

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 يتعمل مرة واحدة في الطبقة المشتركة (قاعدة المالك).

Moon Clinic · Clinical Core — تحليل 2026-10-21 · مبني على حصر الكود الفعلي (file:line) + تصميم Fable · مستني قرارات المالك في القسم ٩