★ الملخّص التنفيذي
في الطلب LR-2026-00256 ظهر تحليل TSH (هرمون الغدة الدرقية) في شاشة التجميع (worklist) بحالة «بانتظار السحب» رغم إن المستخدم لم يطلبه. التحقيق أثبت إن TSH اتحطّ على الطلب وقت إنشائه بالظبط مع باقي التحاليل، وإنه تسرّب من باقة «فحص شامل» بسبب خلل في منطق إضافة/حذف الباقات في الـ wizard مدعوم بإن endpoint قائمة الباقات لا يرجّع تحاليل الباقة. النتيجة: تحليل «يتيم» (orphan) في قسم لم يُجمَّع، فيفضل عالق pending ويظهر في التجميع للأبد.
1 العرَض (Symptom)
- المستخدم أنشأ طلب فيه: CBCFBSTriglyceridesHDLLDL (صورة دم + سكر صائم + دهون).
- في شاشة التجميع/الوورك-ليست ظهر TSH «بانتظار السحب» — وهو مش ضمن اللي اختاره.
- TSH عالق ومش بيختفي → بيظهر كل مرة.
2 الأدلة (Evidence) — من قاعدة البيانات والـ API مباشرة
دليل 1: TSH موجود على الطلب من لحظة الإنشاء (مش مضاف لاحقاً)
كل الـ 22 تحليل (ومنهم TSH) اتسجّلوا بنفس الطابع الزمني بالظبط:
-- lab_request_investigations (request 67) created_at = 2026-06-03 01:27:50 → invs = 1,2,3,4,7,16,17,22,23,24,25,26,39,40,93,94,95,745,746,747,748,749 -- مجموعة زمنية واحدة = كله اتبعت في طلب إنشاء واحد (مفيش إضافة لاحقة)
دليل 2: TSH هو التحليل الوحيد العالق pending — في قسم مختلف لم يُجمَّع
| التحليل | pivot | القسم (section_id) | الحالة | ملاحظة |
|---|---|---|---|---|
| CBC + باقي 20 تحليل | — | 1 و 2 | received | اتجمّعوا 01:37 |
| TSH | 433 | 5 | pending | updated_at = created_at → اتساب من غير ما حد يلمسه |
دليل 3: TSH ليس عضو في باقة CBC (مش panel expansion)
CBC panel members = WBC,RBC,HGB,HCT,MCV,MCH,MCHC,RDW,PLT,PDW,MPV,Neutrophil,Lymphocytes,Monocytes,Eosinophils,Basophils
TSH (17) ∉ أعضاء CBC → مش جاي من توسعة البانل
دليل 4: TSH عضو في باقة «فحص شامل» ✅ (مصدر التسرّب)
| الباقة | أعضاؤها |
|---|---|
| فحص شامل (id 1) | CBC, FBS, TSH, TG, HDL, LDL, URIC, SGOT, SGPT, UA |
| باقة الغدة الدرقية (id 2) | … TSH … |
{CBC, FBS, TG, HDL, LDL, TSH} = أعضاء «فحص شامل» ناقص {URIC, SGOT, SGPT, UA}. يعني المستخدم تعامل مع باقة «فحص شامل»، شال 4 تحاليل مش عايزهم، لكن TSH فضل متعلّق.دليل 5: مفيش باقة مُسجّلة على الطلب — TSH اتبعت كتحليل «فردي»
lab_requests(67): source=walk_in, package_discount = 0.000, notes=NULL → عند الإرسال selectedPkgIds كانت فاضية. الباقة مش متسجّلة، لكن تحاليلها اتبعتت كأفراد.
دليل 6 (السبب التقني الأعمق): endpoint قائمة الباقات لا يرجّع التحاليل
GET /lis/packages → كل الباقات: investigations = NULL GET /lis/packages/1 → investigations = [CBC, FBS, TG, HDL, LDL, TSH] ✅ // القايمة فاضية من التحاليل → الـ wizard مضطر يجيبها async وقت الضغط (getById)
3 السبب الجذري (Root Cause) — في كود الـ wizard
في request-wizard-v2.component.ts منطق togglePackage():
// إضافة الباقة — لأن القايمة بترجع investigations=null، بيروح للمسار async: this.pkgSvc.getById(pkg.id).subscribe(res => { for (const inv of res.data.investigations) newTestIds.add(inv.id); // يضيف TSH وكله this.selectedTestIds.set(newTestIds); }); // حذف الباقة — بيعتمد على pkg.investigations (اللي ممكن تكون null أو لسه ماوصلتش): for (const inv of pkg.investigations) testIds.delete(inv.id); // لو null → ميشيلش حاجة!
المشاكل الثلاثة المتراكمة
- قائمة الباقات بدون تحاليل ⟵ الإضافة async (getById)، والحذف متزامن على
pkg.investigations⟵ عدم تماثُل + سباق زمني (race). selectedTestIdsعبارة عن Set مسطّح مفيهوش أي ربط «التحليل ده جاي من باقة كذا» ⟵ لو الباقة اتشالت أو اتعدّلت، تحاليلها ممكن تتعلّق يتيمة (orphans) من غير ما النظام يعرف يشيلها.- الـ UI ما بيعرضش بوضوح تحاليل الباقة كعناصر قابلة للحذف ⟵ المستخدم مايشوفش TSH علشان يشيله.
سيناريو إعادة الإنتاج (الأكثر ترجيحاً)
2. يضغط الباقة تاني بسرعة (remove) → pkg.investigations لسه null → ميشيلش أي تحليل + يشيل الباقة من pkgIds
3. getById يرجع متأخر → يضيف كل تحاليل الباقة (ومنها TSH) لـ selectedTestIds — من غير باقة!
4. المستخدم يشيل اللي يعرفهم (URIC, SGOT, SGPT, UA) يدوي… لكن TSH يفوته
5. الإرسال: {CBC, FBS, TG, HDL, LDL, TSH}، packages=[] → package_discount=0 ✅ (مطابق للداتا بالظبط)
4 ليه بيظهر في الوورك-ليست بالذات؟
لمّا التحليل اليتيم (TSH) يبقى في قسم غير الأقسام اللي المستخدم جمّعها، عيّنته عمرها ما تتعمل، فيفضل pending للأبد ⟵ شاشة التجميع شغلتها تعرض كل pending ⟵ TSH يظهر «بانتظار السحب» كل مرة، ومايختفيش لأنه فعلاً موجود على الطلب.
5 الحل الدائم (Permanent Fix)
أولاً — Backend (الأرخص والأقوى)
- خلّي قائمة الباقات ترجّع تحاليلها (
GET /lis/packagesيضمّinvestigationsأو على الأقلinvestigation_ids). ده يلغي السباق الزمني لأن الـ wizard هيبقى عارف محتوى الباقة فوراً من غير async.
ثانياً — Frontend (request-wizard-v2)
- تتبّع مصدر الباقة: استبدال الاعتماد على
pkg.investigationsبـMap<packageId, number[]>مخزّن وقت الإضافة، فالإضافة والحذف متماثلين 100% ومستقلين عن أي داتا بايتة. - إضافة atomic: تعطيل زرّار الباقة أثناء تحميل تفاصيلها (منع الـ double-click race)، أو تحميل تحاليل كل الباقات مرّة واحدة عند فتح الـ wizard (
forkJoin). - عرض كل التحاليل المختارة (ومنها أعضاء الباقات) في قائمة Step-2 ولكل واحد زرّار حذف (×)، + شاشة مراجعة نهائية بقائمة التحاليل قبل التأكيد ⟵ المستخدم يشوف TSH ويقدر يشيله.
ثالثاً — تنظيف فوري للبيانات
- إزالة TSH اليتيم من الطلب الحالي:
DELETE /lis/requests/67/investigations/17 // يشيل pivot 433 (TSH) - (اختياري) فحص باقي الطلبات بحثاً عن تحاليل
pending«يتيمة» في قسم مختلف عن باقي الطلب وتنظيفها.
getById(باقة 1) رجّع 6 تحاليل بينما جدول lab_package_investigations فيه 10 صفوف للباقة — في تضارب في داتا الباقة نفسها (غالباً 4 أعضاء غير نشطين/محذوفين). يستحق مراجعة منفصلة لباقة «فحص شامل».6 الخطوة التالية
جاهز أنفّذ بالترتيب ده فور موافقتك:
- تنظيف فوري: شيل TSH من LR-2026-00256 (يختفي من التجميع حالاً).
- Backend: قائمة الباقات ترجّع التحاليل.
- Frontend: تتبّع مصدر الباقة (Map) + إضافة atomic + قائمة مراجعة قابلة للحذف.