خطة تنفيذ: الاستقبال / الكاشير / الخزن لكل فرع + نسخ التحليل

Moon ERP — وحدة المختبر (LIS) · تاريخ الإعداد: 2026-06-02 · للمراجعة قبل التنفيذ
🐞 إصلاح خطأ ✨ ميزة جديدة ✅ تم بالفعل ⚙️ باك-إند 🎨 فرونت-إند

السياق والمعطيات المؤكدة (Verified facts)

الباك-إند الفعلي: الواجهة المنشورة تستخدم https://moonui.elbaset.com/moon-erp-be/api (وهو الـ BE اللي بنعدّله، قاعدة بيانات moonui_dev_be). moon-erp.elbaset.com/api سيرفر منفصل تماماً — أي اختبار curl لازم يكون على الرابط الأول.
يوزر الاختبار «عماد» (reception): id=17، company=4، الدور reception، data_scope = all، يملك lis.payments.view/create/void، فروعه [13 «Medinah 2», 8 «الرياض», 5 «جده»] والفرع الرئيسي = 5 (جده)، و/auth/me يرجّع الفروع لكن بدون علامة is_primary داخل كل فرع.

1 قائمة الفروع فوق لا تظهر ليوزر الاستقبال 🐞 إصلاح🎨 FE

السبب الجذري (مؤكد)

في lis-layout.component.ts → ngOnInit، لما يكون data_scope = 'all' الكود بينادي branchService.listAll() (endpoint core/branches). يوزر الاستقبال مالوش صلاحية على هذا الـ endpoint:

اختبار: GET core/branches بتوكن عماد ⟶ HTTP 403. النتيجة: branchOptions = []showBranchSelector = (length > 1) = false ⟶ القائمة تختفي.

الحل المقترح

نعتمد على فروع المستخدم القادمة من /auth/me (موجودة أصلاً) بدل نداء API الفروع العام:

// lis-layout.component.ts — ngOnInit (منطق جديد)
const userBranches = (user?.branches || []).filter(b => b.is_active !== false);
if (userBranches.length) {
  this.branchOptions.set(userBranches);              // عماد ⟶ 3 فروع
} else if (scope === 'all') {
  this.branchService.listAll().subscribe({            // أدمن غير مرتبط بفروع
    next: r => this.branchOptions.set((r.data||[]).filter(b => b.is_active)),
    error: () => this.branchOptions.set([]),          // 403 ⟶ تفضل فاضية بأمان
  });
}
القائمة دورها تصفية العرض فقط (مش تسجيل العمليات) — راجع النقطة 5. التولتيب اتعدّل بالفعل ليوضّح ده.

معايير القبول

ملفات: src/app/features/lis/lis-layout/lis-layout.component.ts

2 زرّار «افتح وردية» لا يظهر ليوزر الاستقبال 🐞 فحص/إصلاح🎨 FE

المعطيات

الخلاصة: منطقياً المفروض يظهر. أرجّح السبب كاش المتصفح (عماد فاتح نسخة قديمة من التطبيق قبل إضافة الميزة).

الخطوات

  1. تأكيد أول: عماد يعمل Hard refresh (Ctrl+Shift+R) بعد آخر نشر. لو ظهر ⟶ كان كاش، خلاص.
  2. لو لسه مش ظاهر: نتحقق من:
    • هل lis-layout المنشور فعلاً بيعمل render لـ @if (canShift()) <app-cashier-shift-widget/> (نتأكد إن آخر بناء اتنشر).
    • توقيت تقييم canShift() قبل تحميل صلاحيات المستخدم — نضيف اعتماد على permission.userLoaded() (موجود) ونتأكد إن الـ computed بيعيد الحساب بعد تحميل /auth/me.

معايير القبول

ملفات (لو احتجنا تعديل): lis-layout.component.ts/html, cashier-shift-widget.component.ts

3 خزنة كاش لكل فرع — لا توجد طريقة لتخصيص فرع للخزنة ✨ ميزة⚙️ BE🎨 FE

السبب الجذري (مؤكد)

شاشة الخزن /lab/treasuries موجودة، لكن إنشاء الخزنة (LabTreasuryController@storeTreasury) بيتحقق فقط من name, name_ar, max_amount, is_activeمفيش حقل branch_id. فالخزن بتتعمل على مستوى الشركة كلها، مش لكل فرع.

جدول petty_cash عنده عمود branch_id وعلاقة branch() جاهزين — فالناقص بس الربط في الإنشاء/التعديل والواجهة.

الحل المقترح

⚙️ باك-إند

🎨 فرونت-إند

ربط التحصيل النقدي بخزنة الفرع (routing — مرحلة تالية اختيارية)

بعد ما الخزن تبقى لكل فرع: التحصيل الكاش يروح تلقائياً لخزنة الفرع الرئيسي للكاشير. حالياً method_accounts بيربط طريقة الدفع بحساب واحد ثابت. الاقتراح:

معايير القبول

ملفات: Modules/LIS/app/Http/Controllers/LabTreasuryController.php, Modules/Accounting/.../PettyCashResource.php، src/app/features/lis/treasuries/lis-treasuries.component.ts/html, src/app/core/services/lis-treasury.service.ts

4 «نسخ تحليل» (Copy / Duplicate Investigation) ✨ ميزة⚙️ BE🎨 FE

الهدف

زرّار «نسخ» على التحليل بيعمل نسخة كاملة منه باسم/كود جديد، علشان نعمل بانل شبيه (مثلاً CBC Auto تانية) من غير ما نبنيه من الصفر.

الحل المقترح

⚙️ باك-إند — endpoint جديد

// POST /lis/investigations/{id}/duplicate  { name, name_en?, code? }
public function duplicate(Request $r, int $id) {
  $src = LabInvestigation::with(['normalRanges','panelMembers','consumables',
                                 'machineMappings','reagents','panelSections'])
         ->where('company_id',$companyId)->findOrFail($id);
  $copy = $src->replicate(['code']);                 // انسخ كل الأعمدة عدا الكود
  $copy->name    = $r->input('name', $src->name.' (Copy)');
  $copy->name_ar = $r->input('name_ar', $src->name_ar);
  $copy->code    = $r->input('code') ?: $seq->generateNext($companyId,'lis','investigation');
  $copy->created_by = auth()->id();  $copy->save();
  // انسخ العلاقات:
  foreach ($src->normalRanges as $nr)  $copy->normalRanges()->create($nr->toArrayCopy());
  $copy->panelMembers()->sync($src->panelMembers->mapWithKeys(fn($m)=>[$m->id=>['sort_order'=>$m->pivot->sort_order]]));
  foreach ($src->consumables as $c)    $copy->consumables()->create($c->toArrayCopy());
  foreach ($src->machineMappings as $m)$copy->machineMappings()->create($m->toArrayCopy());
  $copy->reagents()->sync(... pivot ...);
  foreach ($src->panelSections as $s)  $copy->panelSections()->create($s->toArrayCopy());
  return new LabInvestigationResource($copy->load([...]));
}

🎨 فرونت-إند

معايير القبول

ملفات: LabInvestigationController.php, routes/api.php, lis-investigation.service.ts, investigations/lis-investigations.component.ts/html

5 الصلاحيات: (أ) اعتماديات منطقية + (ب) إخفاء الأزرار حسب الصلاحية ✨ ميزة⚙️ BE🎨 FE

المشكلة

الصلاحيات حالياً مسطّحة ومستقلة (lis.{resource}.{action}) من غير أي ربط منطقي بينها. النتيجة: لو الدور خد lis.requests.create بس، الكاشير مش هيقدر يضيف مريض وهو بيعمل الطلب لأنه ماخدش lis.patients.create/view — فالدور بيبان «شغّال» بس بيكسر في المنتصف. محتاجين ترتيب لوجي للاعتماديات علشان نقدر نبني ونختبر الأدوار بثقة.

الحالي: شاشة أدوار المختبر lis-roles.component.ts بتجمّع الصلاحيات حسب المورد وبتوضّح كل صلاحية بتفتح أي شاشة — لكن مفيش مفهوم اعتمادية (تحديد صلاحية ما بيجرّش معاها متطلباتها).

الحل المقترح — خريطة اعتماديات

نعرّف خريطة «الصلاحية ⟵ متطلباتها»، ولما المستخدم يفعّل صلاحية بتتفعّل متطلباتها تلقائياً (في الواجهة) وكمان يتم توسيعها عند الحفظ (في الباك-إند كمصدر موثوق).

أمثلة الاعتماديات في تدفّق المختبر

الصلاحيةتتطلّب تلقائياًالسبب
lis.requests.create requests.view, patients.view, patients.create, investigations.view, doctors.view إنشاء الطلب يحتاج اختيار/إضافة مريض، اختيار تحاليل، واختيار طبيب
lis.samples.collectsamples.view, requests.viewالتجميع يبدأ من طلبات اليوم
lis.results.enter (لو موجودة)results.view, requests.viewإدخال النتيجة على طلب
lis.payments.createpayments.view, invoices.viewالتحصيل على فاتورة
أي .create / .update / .deleteنفس المورد .viewقاعدة عامة: ماينفعش تعدّل من غير ما تشوف

⚙️ باك-إند

🎨 فرونت-إند (شاشة أدوار المختبر)

الجزء ب: إخفاء الأزرار/الإجراءات حسب صلاحية المستخدم 🎨 FE

المطلوب: لو اليوزر معاهوش صلاحية معيّنة، الزرّار مايظهرش أصلاً (مش معطّل — مخفي تماماً). مثال: يوزر معاه patients.create بس ⟶ في شاشة المرضى يلاقي زرّار «إضافة» فقط، ومفيش «تعديل» ولا «حذف».

النطاق: مراجعة كل شاشات LIS (المرضى، الأطباء، التحاليل، الطلبات، العينات، النتائج، الفواتير، المدفوعات، البيانات الأساسية…) وتغليف كل زر إجراء بـ @if (can(...)). شغل واسع لكن متكرر النمط.

معايير القبول

ملفات: Modules/Core/.../PermissionDependencies.php, RoleController (توسيع عند الحفظ)، core/permissions route، src/app/features/lis/roles/lis-roles.component.ts/htmlroles.component.ts العام لو لزم).

6 أُنجز بالفعل في هذه الجلسة ✅ تم

البندالحالةتفاصيل
بحث محرّر الأسعارمنشور تحويل البحث لـ signal + debounce + لودنج، والبحث بالاسم/الكود/كود نفيس(SBS)/LOINC. اتحل مشكلة «ما بيفلترش إلا لما أقلب التابات».
تصدير كود نفيسمنشور عمود nphies_code في Excel/CSV والقالب.
تسجيل العمليات على الفرع الرئيسيمنشور كل العمليات الجديدة (طلب/عينة/فاتورة/دفع) تُسجَّل على الفرع الرئيسي للمستخدم من إعداداته (عماد ⟶ فرع 5)، وبشكل مُوحّد (مواضع العينات كانت بتستخدم «أول فرع» عشوائي — اتظبطت للفرع الرئيسي). القائمة فوق = تصفية عرض فقط.
ملاحظة مهمة: النموذج اللي اتفقنا عليه = «كل عملية تتسجّل على الفرع الرئيسي للمستخدم». اتأكد إن كل كاشير فرعه الرئيسي مظبوط في إعداداته (شاشة LIS Users)، لأن ده الفرع اللي هتروح له خزنته.

ترتيب التنفيذ المقترح (Execution order)

  1. النقطة 1 (قائمة الفروع — FE صغير ومؤكد) ثم نشر + تأكيد عماد.
  2. النقطة 2 (الوردية — تأكيد بـ refresh أولاً؛ تعديل فقط لو لزم).
  3. النقطة 3 (خزنة لكل فرع — BE ثم FE) ثم نشر. الربط التلقائي مرحلة تالية.
  4. النقطة 5 (اعتماديات الصلاحيات — BE خريطة + توسيع، ثم FE) ثم نشر — مهمة علشان نختبر الأدوار صح.
  5. النقطة 4 (نسخ التحليل — BE ثم FE) ثم نشر.
مخاطر: أي تعديل على الخزن/الدفع يمسّ الحسابات — نختبر بـ curl على الـ dev BE الصحيح قبل وبعد، ونتأكد من العزل بين الشركات (branch تابع لنفس company). نسخ التحليل: ننسخ العلاقات بحذر علشان مايحصلش ربط بسجلات الأصل.
كل نشر: FE عبر البناء + تنظيف الـ chunks القديمة، BE عبر bash local-deploy.sh. التحقق دايماً على moonui.elbaset.com/moon-erp-be/api.

في انتظار موافقتك على الخطة قبل بدء التنفيذ — تقدر تقول «ابدأ» أو تطلب تعديل أي نقطة.