الروشتة، والتحاليل، والأشعة، والمستندات، والمرفقات، والطباعة: نسخة واحدة قوية تشتغل في كل أنواع العيادات (عام / تغذية / أسنان / نسا / إقامة مستشفى) — عيادة أو مركز أو مستشفى بإعدادات بس.
تحليل فقط — 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.
الـ 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.
| البلوك | عام (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 |
prescriptions/prescription_items بس. طلب تحاليل/أشعة من الطبيب = clinic_clinical_intents بس. تقرير أشعة = rad_reports بس. أي تخصص (أسنان/تغذية/نسا/مستشفى) ممنوع يعمل جدول أوامر أو أدوية خاص بيه.encounters.kind (outpatient | inpatient | day_case | remote) + encounters.admission_id nullable. الإقامة = encounter واحد طول مدتها (موجود فعلاً). الحلقة (episode) والإجراء (clinical_procedure) بيربطوا encounters، مش بيستبدلوها. الأرضية الدنيا = patient_id (موجود على كل الجداول) للحالات اللي مفيهاش encounter (LIS walk-in).Clinic\Enums\MedicationRoute (اتحاد القائمتين = 13 قيمة). التكرار = Clinic\Enums\MedicationFrequency (كود → أوقات/يوم، منقول من NursingService::FREQ). الشكل الصيدلي = Core dosage_forms. ممنوع قوائم ثابتة في أي كومبوننت أو سيرفس تاني.EncounterLockedException من الـ action نفسه. الـ FE [locked] تجميل بس.clinic.ordering_mode / lab_mode / rad_mode / pharmacy_source / pos.pharmacy.* / clinic.print.* موجودين — بنقراهم في نقطة واحدة لكل واحد (OrderingMode, ClinicCatalogMode, وPharmacySource الجديد).ExecuteCatalogItem (يتستخرج من فرع DecideClinicalIntent::here). النية (clinic_clinical_intents) = سجل الطبيب وبتتطلب encounter؛ طلب الاستقبال المباشر (CreateVisit) بينادي نفس المنفّذ مربوط بـ service_order_id + patient_id بلا encounter وهمي (عشان ما يطلقش EncounterStarted → in_consultation). وضع immediate = النية بتتقرر «هنا» فوراً بنفس المنفّذ.dispense_status؛ الصرف/البيع في POS/Sales. الطبيب بيشوف «اتصرفت» كشارة بس.clinical_documents بتتربط بالـ patient دايماً وبالـ encounter لو موجود. العرض موحّد بشارة المصدر (LIS / RIS / خارجي).clinic.print.* الموجودة. الـ FE preview يفضل لحد ما الـ PDF يثبت، بعدها يتشال.المالك: 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 يفضل قارئ عرض.
المالك: 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…) — بلا تغيير. |
المالك: 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. |
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 يفضل نص للتوافق لكن الطباعة من المستند.
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.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) عليه.
| البلوك | القرار |
|---|---|
| تشخيص/شكوى | 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=§ions= في Clinic (PatientRecordService بنفس نمط safe() بتاع التغذية) — التغذية والنسا بيمدّوه بأقسامهم بدل ما يبنوا endpoint لكل تخصص. |
| access_audit | قبل تشغيل أي عميل طبي (GYN مرحلة 0). |
التخصص = 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 على محرك الطباعة. يتم بالاتفاق مش بتعديل كودهم من هنا.
| الإعداد | عيادة واحدة | مركز | مستشفى |
|---|---|---|---|
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.* + توقيع الطبيب |
نفسه | نفسه + ملخص خروج |
مفيش سطر كود يتغير بين الأعمدة الثلاثة.
كل الخطوات additive وقابلة للرجوع؛ البيانات الحية على moonui3 صغيرة.
prescription_items حيث dose='—' OR frequency='—' OR duration='—' → sentence = instructions، الحقول الثلاثة = NULL، form = القيمة اللي كانت في dose لو مش «—». instructions تفضل زي ما هي. الطباعة القديمة اللي كانت بتمسح «—» (clinic-prescription.service.ts:295-305) تتشال.dosage_forms بالقائمة العربية الشائعة (من COMMON_DRUG_FORMS_AR في الكومبوننت) لكل شركة (updateOrCreate على code) → backfill prescription_items.dosage_form_id / clinic_drugs / clinic_drug_presets بمطابقة الاسم؛ اللي ما يتطابقش يفضل نص في form.MedicationRoute يتوسع (إضافة قيم بس = backward-safe). MedicationFrequency جديد؛ بنود الإقامة القديمة frequency كود صغير يتنقل لـ frequency_code.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.acquired → performed؛ له report released → reported + سطر result_status=ready. مفيش بيانات بتتمسح.clinic_services.lis_investigation_id/rad_procedure_id: يُحذف من الـ API (الحارسان + الـ import) ويفضل العمود ريليس واحد ثم drop. السبب: القرار المعتمد «بلا mirror» + الوضع المتكامل بيختار من كتالوج LIS مباشرة، فالجسر ما لوش مستهلك.clinical_documents.encounter_id nullable + unique جديد: migration: drop unique القديم → add (company_id, ref_key). تتأكد الأول إن مفيش تكرار (ref_key global فعلاً).CreateVisit: الـ payload القديم (lab.investigations[].investigation_id, radiology[].procedure_id) يتقبل ويتحوّل داخلياً — الـ FE يتحدّث في نفس المرحلة، مفيش كسر للعملاء.| # | المرحلة | التسليم | الحجم | المخاطر | المالك يختبر على /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.
ClinicalCore؟ Clinic بيملك فعلاً 30+ جدول إكلينيكي وكل التخصصات بتعتمد عليه. موديول جديد = نقل جداول وكسر namespaces بلا فايدة. توصية: Clinic، مع مجلد app/Clinical/ للـ actions المشتركة.hospital_admissions.encounter_id + service_order_id موجودين، كل جداول الأوامر encounter_id، LIS عنده in_patient، والسياق الوحيد بلا encounter هو LIS walk-in وده مغطّى بـ patient_id. Polymorphic = فقد FKs + إعادة كتابة كل القرّاء والـ listeners. توصية: Encounter + kind.LabInsuranceContract في LIS (التوصية) ولا تمديد عقود العيادة؟ توصية: LIS — الكتالوج هناك والسعر هناك.pos.pharmacy.* مزروعة ومكتوب عليها «لسه هتتبني». توصية: بعد P3 — البنية جاهزة من P1 (dispense_status + event).PatientMedicationsService بيعرضه. قرار: مين يعدّل قارئ التغذية (moonui) وإمتى؟ ومعاه تبنّي لوحة التشخيص والـ record المشترك.clinic_services.lis_investigation_id/rad_procedure_id (التوصية: يتشال — بلا مستهلك، والقرار «بلا mirror» معتمد) ولا يتفعّل؟ClaimLine لكل تحليل (التوصية) ولا سطر واحد للطلب؟ NPHIES محتاج كود لكل خدمة.radiologist, rad_technologist) وتصحيح preset الاستقبال بإضافة clinic.lab_results.view — موافقة.POST encounters/{e}/lab-orders|rad-orders: تفضل كـ wrappers (التوصية) ولا تتشال؟ إبقاؤها = صفر كسر للـ FE.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 يتعمل مرة واحدة في الطبقة المشتركة (قاعدة المالك).