تدقيق قوائم الأسعار وربطها بالطبيب

وحدة: المختبر (LIS) — Moon ERP  |  التاريخ: 2026-05-25  |  نطاق التدقيق: شاشة قوائم الأسعار + ربطها بشاشة إضافة الطبيب + ربطها بشاشة إنشاء الطلب (request-wizard-v2)
١٢
مشاكل اتلاقت
٣
حرجة
٤
عالية
٥
متوسطة/بسيطة
٣
قوائم موجودة
٣٤٢
إجمالي الفحوصات
تحديث: مشاكل إضافية اكتشفت بعد فحص الـ UI:

الفكرة المطلوبة وكيف يجب أن تشتغل

  1. الموظف يدخل شاشة "قوائم الأسعار" ويعمل قائمة (مثلاً "قائمة VIP")، ويختار من الفحوصات اللي يبيها، ولكل فحص يحط السعر الخاص بالقائمة.
  2. في شاشة الطبيب، يختار "قائمة الأسعار المفضلة" من القوائم الموجودة، ويحفظ.
  3. في شاشة إنشاء طلب (request-wizard-v2)، لما يختار الطبيب ده، النظام يقرأ قائمة أسعار الطبيب ويطبق أسعارها على الفحوصات اللي هتتضاف للطلب.

ما يعمل بشكل صحيح

المشاكل المكتشفة

حرجة فرونت #١ قائمة أسعار الطبيب لا تشتغل أبداً

الوصف:

عند فتح شاشة إنشاء طلب، الكود تلقائياً يختار "القائمة الافتراضية" قبل ما المستخدم يختار طبيب. بعدها لما المستخدم يختار طبيب وله قائمة مختلفة، النظام يتجاهل قائمة الطبيب لأنه يعتبر إن "في قائمة محددة دلوقتي بالفعل".

المسار في الكود:

src/app/features/lis/request-wizard-v2/request-wizard-v2.component.ts
// السطر 417 — اختيار افتراضي عند فتح الشاشة:
const defPl = lists.find((pl) => pl.is_default) || lists[0];
if (defPl) {
  this.selectedPriceListId.set(defPl.id);   // ← القائمة الافتراضية تترسم
  this.loadPriceListItems(defPl.id);
}

// السطر 763 — منطق onDoctorChange:
if (this.selectedPriceListId() && !this.priceListFromDoctor()) return;
//      ^ موجودة (الافتراضية)        ^ false → الشرط TRUE → return
//                                                          └─ قائمة الطبيب لا تُطبق!

الإصلاح المقترح:

نضيف علم منفصل priceListFromDefault ليفرق بين "اختيار افتراضي تلقائي" و "اختيار يدوي". قائمة الطبيب تطغى على الافتراضية، لكن لا تطغى على اختيار يدوي.

// عند الـ default auto-select:
this.selectedPriceListId.set(defPl.id);
this.priceListFromDefault.set(true);   // ← علم جديد

// في onDoctorChange:
const manualPick = this.selectedPriceListId()
                && !this.priceListFromDoctor()
                && !this.priceListFromDefault();
if (manualPick) return;   // الـ user اختار يدوياً → لا نطغى

// طبق قائمة الطبيب
this.selectedPriceListId.set(docPlId);
this.priceListFromDoctor.set(true);
this.priceListFromDefault.set(false);
الأثر العملي: ميزة "قائمة الطبيب المفضلة" معطّلة ١٠٠٪. أي طبيب تحط له قائمة، الـ wizard يتجاهلها.
حرجة باك + فرونت #٢ نسبة الخصم في القائمة لا تُطبق على السعر في الـ wizard

الوصف:

الـ API يرجع لكل بند: price (السعر الأصلي قبل الخصم) و discount_percentage و net_price (السعر بعد الخصم). الفرونت يستخدم price ويتجاهل الخصم، فيظهر سعر مرتفع للمستخدم.

الفرونت: src/app/features/lis/request-wizard-v2/request-wizard-v2.component.ts:443

for (const item of res.data.items || []) {
  if (item.price) map.set(item.investigation_id, parseFloat(String(item.price)));
  //                                                                ^^^^^ السعر قبل الخصم
}

الباك اند: نفس المشكلة في LabRequestController::store اللي ضفناها في T-7:

Modules/LIS/app/Http/Controllers/LabRequestController.php
if ($price === null && $doctorPriceListId) {
    $listItem = LabPriceListItem::query()
        ->where('price_list_id', $doctorPriceListId)
        ->where('investigation_id', $inv['investigation_id'])
        ->first();
    $price = $listItem ? (float) $listItem->price : null;
    //                            ^^^^^ المفروض net_price
}

الإصلاح المقترح:

استخدم net_price في المكانين. لو في خصم، السعر النهائي بعد الخصم يبقى هو المرجع.

الأثر العملي: لو حطيت قائمة فيها خصم ٢٠٪، المستخدم يشوف السعر الأصلي بدون الخصم. الفاتورة تطلع غلط.
عالية فرونت #٣ التحقق في شاشة قوائم الأسعار غير متسق مع الباك اند

الوصف:

الفرونت يطلب name_ar كحقل مطلوب، لكن الباك اند يطلب name_en كحقل مطلوب أساسي. النتيجة:

الفرونت: src/app/features/lis/price-lists/lis-price-lists.component.ts:138

this.form = this.fb.group({
  name_ar: ['', Validators.required],   // ← المطلوب هنا
  name_en: [''],                        // ← اختياري
  ...
});

الباك اند: Modules/LIS/app/Http/Requests/StoreLabPriceListRequest.php:19-22

'name'    => ['required', 'string', 'max:255'],
'name_ar' => ['nullable',  'string', 'max:255'],
'name_en' => ['required',  'string', 'max:255'],   // ← المطلوب هنا

الإصلاح المقترح: توحيد الشرط — جعل name_en هو المطلوب على الجانبين، و name_ar اختياري (متطابق مع باقي الشاشات اللي عدلناها).

الأثر العملي: لو المستخدم يدخل الاسم الإنجليزي، كل شيء سليم. لو يدخل العربي فقط، الحفظ يفشل برسالة غير واضحة.
عالية فرونت #٤ الـ caption "من القائمة المفضلة للطبيب" لا تظهر أبداً

الوصف:

ضفنا رسالة "من قائمة الأسعار المفضلة للطبيب" تحت حقل اختيار القائمة، تظهر لما priceListFromDoctor() === true. لكن بسبب البق #١، priceListFromDoctor لا يصبح true أبداً، فالرسالة معدش بتتلف.

الإصلاح: هذا تابع لإصلاح البق #١. بعد إصلاحه، الرسالة هتظهر تلقائياً.

الأثر العملي: المستخدم لا يعرف لماذا تغيرت قائمة الأسعار. نقص في الشفافية.
متوسطة فرونت #٥ تغيير الطبيب الثاني لا يعيد تطبيق قائمته

الوصف:

سيناريو: المستخدم يختار طبيب A (له قائمة) → قائمة A تتطبق. ثم يغير لطبيب B (له قائمة مختلفة) → قائمة A تبقى!

السبب: الشرط if (this.selectedPriceListId() && !this.priceListFromDoctor()) return; — في المرة الثانية، selectedPriceListId موجود و priceListFromDoctor=true فالشرط TRUE وكمان نضيف الـ negation فيصير FALSE → نمر... هممم، خليني أعيد التحقق.

في الواقع !priceListFromDoctor() = !true = false، فالشرط (true && false) = false → ما يعمل return → ينفذ ويطبق قائمة B الجديدة.

لكن لو كانت قائمة A تم اختيارها يدوياً قبل اختيار طبيب B، الـ B لن يطبق قائمته. هذا السلوك الصحيح حسب التصميم.

الأثر العملي: السيناريو ده فعلياً يشتغل صحيح بعد إصلاح البق #١. بس محتاج اختبار لتأكيد.
متوسطة فرونت #٦ بنود غير مفعلة (included=false) لا تتحفظ، لا توجد رسالة تحذير

الوصف:

في شاشة إنشاء قائمة جديدة، كل الفحوصات تظهر في الجدول، لكن المستخدم لازم يفعّل خانة included لكل فحص يبيه في القائمة. لو نسي يفعّلها، الفحص لا يُحفظ في القائمة.

المشكلة: مفيش رسالة تخبر المستخدم "احنا حفظنا ٠ بند بس". الـ button بيرجع "تم الحفظ بنجاح" حتى لو القائمة فاضية.

الإصلاح المقترح:

الأثر العملي: قوائم فارغة قد تتولد. مش بق فني لكن UX سيئ.
بسيطة فرونت #٧ الحقول الإجبارية في شاشة الطبيب لا تذكر قائمة الأسعار في validation

الوصف:

حقل "قائمة الأسعار المفضلة" في شاشة الطبيب يظهر بدون علامة * صحيح (لأنه اختياري كما طلبت). لكن مفيش placeholder أو hint يقول للمستخدم "اختياري — لو ضفت قائمة هتظهر تلقائياً في الطلبات".

الأثر العملي: مش بق، اقتراح تحسين UX فقط.

المشاكل الإضافية اللي اكتشفت بعد فحص الـ UI

حرجة فرونت #٨ القائمة الجديدة تظهر فاضية تماماً — مفيش طريقة لإضافة فحوصات

الوصف:

عند فتح "New Price List"، جدول الفحوصات يظهر فاضي تماماً (No data available). السبب: الـ filteredPricingRows يفلتر بشرط included=true، والفحوصات كلها مش مضافة بعد للقائمة الجديدة.

الكود:

src/app/features/lis/price-lists/lis-price-lists.component.ts:102
filteredPricingRows = computed(() => {
  const search = this.pricingSearch.toLowerCase().trim();
  const rows = this.pricingRows();
  if (!search) return rows.filter((r) => r.included);   // ← فلتر دائم بـ included
  return rows.filter(
    (r) => r.included && (... search match ...)         // ← هنا أيضاً
  );
});

الإصلاح المقترح:

  1. إضافة tabs/toggle على الجدول: "كل الفحوصات (٣٤٢)" | "المضافة بس (٥٩)"
  2. الافتراضي في الـ New mode: "كل الفحوصات".
  3. الافتراضي في الـ Edit mode: "المضافة بس" (السلوك الحالي).
  4. المستخدم يفعّل خانة included على الفحص اللي يبيه ويدخل سعره مباشرة في نفس الصف.
الأثر العملي: المستخدم مش قادر يعمل قائمة جديدة من الصفر. ميزة معطّلة.
حرجة فرونت #٩ البحث مش شغّال — لأنه يبحث جوا الفحوصات المضافة فقط

الوصف:

المستخدم يكتب اسم فحص في حقل البحث، البحث يفشل في إيجاد الفحص حتى لو موجود في النظام. السبب: الكود يفلتر بـ r.included && (search match) — يعني فقط يبحث في المضاف، مش في كل الفحوصات.

الإصلاح: إزالة شرط included من البحث، أو ربطه بالـ tab المختار من البق #٨.

الأثر العملي: البحث مش شغّال للحالة الشائعة (لقاء فحص جديد لإضافته للقائمة).
عالية فرونت #١٠ مفيش أزرار سريعة لإضافة كل الفحوصات أو فحوصات فئة معينة

الوصف:

عشان تضيف ٣٠ فحص لقائمة جديدة، المستخدم محتاج يضغط ٣٠ مرة على checkbox. مفيش:

الإصلاح المقترح:

الأثر العملي: شغل تكراري لمستخدم يضع قائمة سعر كاملة. تجربة سيئة جداً.
عالية بيانات #١١ ٣٣٨ فحص من ٣٤٢ بدون سعر افتراضي (= 0.00)

الوصف:

عمود "Default Price" يظهر 0.00 لكل الفحوصات في الـ screenshots. ده مش بق في الكود — البيانات في قاعدة البيانات الفحوصات نفسها مالهاش سعر افتراضي.

النتيجة: لما المستخدم يفعّل فحص في القائمة، يبدأ السعر بـ ٠.٠٠ ولازم يدخله يدوي.

الإصلاح المقترح:

  1. إضافة شاشة "أسعار الفحوصات الافتراضية" منفصلة — أو حقل price في شاشة الفحص نفسه يبقى أوضح.
  2. أو في شاشة قوائم الأسعار: زر "نسخ من قائمة موجودة" — يقرأ كل الـ items من قائمة افتراضية معينة (مثلاً "Default") ويبدأ منها.
  3. للقائمة الجديدة: option "ابدأ بأسعار من قائمة..." يعرض dropdown بالقوائم الموجودة.
الأثر العملي: المستخدم يدخل ٨١ سعر يدوي لكل قائمة جديدة. ساعات شغل ضايعة. زر "نسخ من قائمة موجودة" بالفعل موجود لكن للقوائم بس، مش للفحوصات الفردية.
متوسطة فرونت #١٢ مفيش مؤشر بصري للفحوصات المفعلة vs المتاحة

الوصف:

الـ UI بيستخدم checkbox عادية في عمود "included". صعب التمييز بسرعة بين المضاف والمتاح، خاصة لما تكون قائمة طويلة.

الإصلاح المقترح:

الأثر العملي: UX سيئ في القوائم الطويلة. لكن لا يمنع الاستخدام.

الـ Flow بعد إصلاح كل المشاكل

١. الموظف يفتح شاشة قوائم الأسعار، يضيف "قائمة VIP" بـ name_en (مطلوب) و name_ar (اختياري).
٢. يضيف ٢٠ فحص مع أسعار مخصصة (مثلاً تحليل دم: ١٠٠ بدل ٧٥)، يحفظ.
٣. في شاشة الطبيب، يفتح طبيب "د. محمد"، يختار "قائمة VIP" في حقل "قائمة الأسعار المفضلة"، يحفظ.
٤. في شاشة إنشاء طلب: الـ wizard يفتح، يختار القائمة الافتراضية تلقائياً (مع علم priceListFromDefault=true).
٥. المستخدم يختار "د. محمد" → الـ wizard يقرأ doctor.price_list_id = 3 → يستبدل الافتراضية بـ "قائمة VIP" → يطبع تحت الحقل: "من القائمة المفضلة للطبيب".
٦. المستخدم يضيف "تحليل دم" → السعر يظهر ١٠٠ (من قائمة VIP) بدلاً من ٧٥ (السعر الأصلي).
٧. لو في خصم في القائمة (مثلاً ١٠٪)، الـ wizard يستخدم net_price فيظهر ٩٠.
٨. الـ user يقدر يغير القائمة يدوياً لو حب، والـ wizard يحترم اختياره ولا يطغى عليه حتى لو غير الطبيب.

قائمة التاسكات للإصلاح (المحدّثة)

الترتيبالتاسكالجانبالتقدير
F-1إضافة علم priceListFromDefault + تعديل منطق onDoctorChange (إصلاح #١)فرونت٣٠ دقيقة
F-2تعديل loadPriceListItems ليستخدم net_price (إصلاح #٢)فرونت٥ دقائق
B-1تعديل LabRequestController ليستخدم net_price (إصلاح #٢)باك٥ دقائق
F-3تعديل validation: name_en مطلوب، name_ar اختياري (إصلاح #٣)فرونت١٠ دقائق
F-4إضافة tabs "كل الفحوصات" / "المضافة بس" في شاشة قوائم الأسعار (إصلاح #٨)فرونت٣٠ دقيقة
F-5إصلاح البحث ليعمل على كل الفحوصات (إصلاح #٩)فرونت١٠ دقائق
F-6أزرار سريعة: "تفعيل الكل" + "تفعيل الظاهر بس" + "إزالة الكل" (إصلاح #١٠)فرونت٢٠ دقيقة
F-7تمييز بصري للـ rows المفعلة + تعطيل أعمدة السعر للـ rows غير المفعلة (إصلاح #١٢)فرونت٢٠ دقيقة
F-8"ابدأ من قائمة" — option في الـ New لنسخ الأسعار من قائمة موجودة (إصلاح #١١)فرونت٣٠ دقيقة
F-9تحذير إذا قائمة جديدة بدون أي بنود مفعّلة (إصلاح #٦)فرونت١٠ دقائق
F-10اختبار يدوي كامل للسيناريو٢٠ دقيقة

الإجمالي: ~٣ ساعات شغل (١٨٠ دقيقة).

تصور الـ UI الجديد

شاشة قوائم الأسعار بعد الإصلاح:

┌─ Investigation Pricing ──────────────────────────────────────┐
│  [ كل الفحوصات (٣٤٢) ]  [ المضافة بس (٥٩) ]                  │
│                                                              │
│  ┌─ بحث ──────────────────────┐  [ +تفعيل الكل ]            │
│  │ 🔍 ابحث بالاسم أو الكود... │  [ تفعيل الظاهر بس ]        │
│  └────────────────────────────┘  [ - إزالة الكل ]            │
│                                                              │
│  📊 ضفت ١٢ من ٣٤٢ | %نسبة [10%] [↑زيادة] [↓تخفيض]           │
│                                                              │
│  ☑ | Investigation | Default | List Price | Disc %          │
│  ☑   تحليل دم CBC       0.00       75.00         0%        │
│  ☑   سكر FBS            0.00       50.00         0%        │
│  ☐   كرياتين Crea       0.00       —             —         │ (معطّل)
│  ☐   هيموغلوبين HB      0.00       —             —         │ (معطّل)
└──────────────────────────────────────────────────────────────┘

للـ Edit mode: تبدأ على "المضافة بس" (السلوك الحالي) لكن المستخدم يقدر يلف على "كل الفحوصات" ويضيف جدد.

للـ New mode: تبدأ على "كل الفحوصات" + خيار "ابدأ من قائمة موجودة" قبل ما تختار.

قرار محتاج تأكيدك

قبل ما أبدأ التنفيذ، قول لي:
  1. ابدأ في تنفيذ كل التاسكات بالترتيب؟
  2. ولا فيه تاسك معين أبدأ بيه؟ مقترح: T-1 (إصلاح الربط مع الطبيب) ثم T-4..T-7 (الشاشة الجديدة) — دول الحرجة.
  3. سؤال: في #١١ — تحب نضيف زر "ابدأ من قائمة موجودة" في الـ New، ولا نضيف حقل سعر افتراضي على الفحوصات نفسها (تحتاج تعديل في شاشة الفحوصات)؟

Moon ERP — LIS Module  |  Price Lists Audit  |  جاهز للمراجعة