price_list_id و الـ price_list object كامل يرجع من الـ API.priceListFromDoctor + رسالة "من القائمة المفضلة للطبيب" مكتوبة في الكود.الوصف:
عند فتح شاشة إنشاء طلب، الكود تلقائياً يختار "القائمة الافتراضية" قبل ما المستخدم يختار طبيب. بعدها لما المستخدم يختار طبيب وله قائمة مختلفة، النظام يتجاهل قائمة الطبيب لأنه يعتبر إن "في قائمة محددة دلوقتي بالفعل".
المسار في الكود:
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);
الوصف:
الـ 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:
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 اختياري (متطابق مع باقي الشاشات اللي عدلناها).
الوصف:
ضفنا رسالة "من قائمة الأسعار المفضلة للطبيب" تحت حقل اختيار القائمة، تظهر لما 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 لكل فحص يبيه في القائمة. لو نسي يفعّلها، الفحص لا يُحفظ في القائمة.
المشكلة: مفيش رسالة تخبر المستخدم "احنا حفظنا ٠ بند بس". الـ button بيرجع "تم الحفظ بنجاح" حتى لو القائمة فاضية.
الإصلاح المقترح:
includedCount === 0.الوصف:
حقل "قائمة الأسعار المفضلة" في شاشة الطبيب يظهر بدون علامة * صحيح (لأنه اختياري كما طلبت). لكن مفيش placeholder أو hint يقول للمستخدم "اختياري — لو ضفت قائمة هتظهر تلقائياً في الطلبات".
الوصف:
عند فتح "New Price List"، جدول الفحوصات يظهر فاضي تماماً (No data available). السبب: الـ filteredPricingRows يفلتر بشرط included=true، والفحوصات كلها مش مضافة بعد للقائمة الجديدة.
الكود:
src/app/features/lis/price-lists/lis-price-lists.component.ts:102filteredPricingRows = 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 ...) // ← هنا أيضاً
);
});
الإصلاح المقترح:
الوصف:
المستخدم يكتب اسم فحص في حقل البحث، البحث يفشل في إيجاد الفحص حتى لو موجود في النظام. السبب: الكود يفلتر بـ r.included && (search match) — يعني فقط يبحث في المضاف، مش في كل الفحوصات.
الإصلاح: إزالة شرط included من البحث، أو ربطه بالـ tab المختار من البق #٨.
الوصف:
عشان تضيف ٣٠ فحص لقائمة جديدة، المستخدم محتاج يضغط ٣٠ مرة على checkbox. مفيش:
included=true على كل الـ rows.الإصلاح المقترح:
الوصف:
عمود "Default Price" يظهر 0.00 لكل الفحوصات في الـ screenshots. ده مش بق في الكود — البيانات في قاعدة البيانات الفحوصات نفسها مالهاش سعر افتراضي.
النتيجة: لما المستخدم يفعّل فحص في القائمة، يبدأ السعر بـ ٠.٠٠ ولازم يدخله يدوي.
الإصلاح المقترح:
price في شاشة الفحص نفسه يبقى أوضح.الوصف:
الـ UI بيستخدم checkbox عادية في عمود "included". صعب التمييز بسرعة بين المضاف والمتاح، خاصة لما تكون قائمة طويلة.
الإصلاح المقترح:
name_en (مطلوب) و name_ar (اختياري).priceListFromDefault=true).doctor.price_list_id = 3 → يستبدل الافتراضية بـ "قائمة VIP" → يطبع تحت الحقل: "من القائمة المفضلة للطبيب".net_price فيظهر ٩٠.| الترتيب | التاسك | الجانب | التقدير |
|---|---|---|---|
| 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 | اختبار يدوي كامل للسيناريو | — | ٢٠ دقيقة |
الإجمالي: ~٣ ساعات شغل (١٨٠ دقيقة).
شاشة قوائم الأسعار بعد الإصلاح:
┌─ 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: تبدأ على "كل الفحوصات" + خيار "ابدأ من قائمة موجودة" قبل ما تختار.
Moon ERP — LIS Module | Price Lists Audit | جاهز للمراجعة