السياق والمعطيات المؤكدة
https://moonui.elbaset.com/moon-erp-be/api
(وهو الـ BE اللي بنعدّله، قاعدة بيانات moonui_dev_be). moon-erp.elbaset.com/api سيرفر منفصل تماماً — أي اختبار curl لازم يكون على الرابط الأول.
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 ⟶ تفضل فاضية بأمان }); }
معايير القبول
- عماد يشوف قائمة فيها فروعه الثلاثة فوق في الهيدر.
- لا يظهر أي خطأ 403 في الكونسول من نداء الفروع.
- الأدمن (hazem) يفضل يشوف كل الفروع كالمعتاد.
ملفات: src/app/features/lis/lis-layout/lis-layout.component.ts
2 زرّار «افتح وردية» لا يظهر ليوزر الاستقبال 🐞 فحص/إصلاح🎨 FE
المعطيات
- عماد يملك
lis.payments.create⟶ شرطcanShift()مفروض يكون true. GET lis/cashier-sessions/currentبتوكن عماد ⟶ HTTP 200 (الـ widget بيظهر بعد رجوع النداء).- كود الـ widget موجود فعلاً في الـ bundle المنشور.
الخطوات
- تأكيد أول: عماد يعمل Hard refresh (
Ctrl+Shift+R) بعد آخر نشر. لو ظهر ⟶ كان كاش، خلاص. - لو لسه مش ظاهر: نتحقق من:
- هل
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() جاهزين — فالناقص بس الربط في الإنشاء/التعديل والواجهة.الحل المقترح
⚙️ باك-إند
- storeTreasury / updateTreasury: نضيف
'branch_id' => ['nullable','integer','exists:branches,id']ونمرّره لـPettyCash::create/update. - treasuries (list): بيحمّل
branchأصلاً — نتأكد إن الـ Resource بيرجّعbranch_id+ اسم الفرع. - التحقق متعدد الشركات: نتأكد إن الفرع المختار تابع لنفس الشركة.
🎨 فرونت-إند
- boxForm في
lis-treasuries.component.ts: نضيفbranch_id. - قائمة منسدلة
<p-select>للفرع في ديالوج إضافة/تعديل الخزنة (اختياري — «خزنة عامة» لو فاضي). - إظهار عمود «الفرع» في جدول الخزن.
ربط التحصيل النقدي بخزنة الفرع
بعد ما الخزن تبقى لكل فرع: التحصيل الكاش يروح تلقائياً لخزنة الفرع الرئيسي للكاشير.
حالياً method_accounts بيربط طريقة الدفع بحساب واحد ثابت. الاقتراح:
- لطريقة «كاش» تحديداً: الواجهة تختار خزنة الكاش اللي
branch_idبتاعها = الفرع الرئيسي للكاشير (من/payments/routingاللي بيرجّعaccounts[]ومعاهاbranch_id). - لو مفيش خزنة لفرعه ⟶ ترجع للخزنة العامة (fallback) مع تنبيه.
معايير القبول
- من
/lab/treasuriesأقدر أنشئ خزنة وأختار لها فرع، وتظهر في الجدول باسم الفرع. - كل فرع ليه خزنة كاش مستقلة بحساب GL مستقل (auto-link موجود).
- (مرحلة الربط) تحصيل الكاشير الكاش يروح لخزنة فرعه الرئيسي.
ملفات: 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([...])); }
- route قبل
apiResource('investigations')علشان{investigation}ما يبلعش المسار. - صلاحية:
lis.investigations.create. - الكود الجديد فريد لكل شركة؛ الاسم الافتراضي «(Copy)» لو ما اتبعتش.
🎨 فرونت-إند
- زرّار/أكشن «نسخ»
pi pi-copyفي صف التحليل بشاشة Investigations. - ديالوج صغير يطلب: الاسم (EN/AR) + كود اختياري ⟶ ينادي
duplicate()⟶ يفتح النسخة للتعديل أو يحدّث القائمة. lis-investigation.service.ts: ميثودduplicate(id, body).
معايير القبول
- نسخ تحليل عادي ⟶ تحليل جديد بكود جديد ونفس الإعدادات (نوع النتيجة، المعدلات الطبيعية، الوحدة…).
- نسخ بانل ⟶ النسخة تحتوي نفس أعضاء البانل بنفس الترتيب.
- تعديل النسخة لا يؤثر على الأصل (سجلات منفصلة تماماً).
ملفات: 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.collect | samples.view, requests.view | التجميع يبدأ من طلبات اليوم |
lis.results.enter | results.view, requests.view | إدخال النتيجة على طلب |
lis.payments.create | payments.view, invoices.view | التحصيل على فاتورة |
أي .create / .update / .delete | نفس المورد .view | قاعدة عامة: ماينفعش تعدّل من غير ما تشوف |
⚙️ باك-إند
- ثابت
PERMISSION_DEPENDENCIES(خريطة) في مكان مشترك (Core). - عند حفظ/تحديث الدور: توسيع الصلاحيات المختارة لتشمل كل المتطلبات (إغلاق متعدٍّ — closure) قبل المزامنة — ضمان إن الدور دايماً متماسك حتى لو اتبعت من API.
- كشف الخريطة للواجهة عبر
GET /core/permissions/dependencies(أو ضمن استجابة قائمة الصلاحيات).
🎨 فرونت-إند (شاشة أدوار المختبر)
- تحميل خريطة الاعتماديات؛ عند تحديد صلاحية ⟵ تحديد متطلباتها تلقائياً وإظهارها «مقفولة/موروثة» مع تولتيب «مطلوبة لـ …».
- منع إلغاء صلاحية لسه متطلوبة من صلاحية تانية مفعّلة (أو تحذير).
- زرّار «تطبيق دور جاهز» اختياري: استقبال / فني / كاشير — يحدّد الباقة الكاملة دفعة واحدة.
الجزء ب: إخفاء الأزرار/الإجراءات حسب صلاحية المستخدم 🎨 FE
المطلوب: لو اليوزر معاهوش صلاحية معيّنة، الزرّار مايظهرش أصلاً (مش معطّل — مخفي تماماً).
مثال: يوزر معاه patients.create بس ⟶ في شاشة المرضى يلاقي زرّار «إضافة» فقط، ومفيش «تعديل» ولا «حذف».
- قاعدة موحّدة على كل شاشات LIS: زرّار «إضافة» محكوم بـ
*.create، «تعديل» بـ*.update، «حذف» بـ*.delete، وأي إجراء خاص بصلاحيته (collect/receive/approve/void…). - نستخدم
PermissionService.can('lis.patients.update')داخل@ifحول كل زرّار/أكشن. - إخفاء عناصر القوائم/التبويبات اللي المستخدم مالوش صلاحية عليها (مش بس الأزرار).
- الباك-إند بالفعل بيحمي الـ endpoints بالصلاحيات (دفاع في العمق) — ده تحسين تجربة المستخدم على الواجهة.
@if (can(...)). شغل واسع لكن متكرر النمط.معايير القبول
- تفعيل
requests.createيجرّ معهpatients.view/create+investigations.viewتلقائياً. - يوزر بصلاحية «إضافة مريض» فقط ⟶ يشوف زر الإضافة فقط؛ التعديل/الحذف غير ظاهرين خالص.
- كل زر إجراء عبر شاشات LIS محكوم بصلاحيته المقابلة.
- الدور المحفوظ يحتوي المجموعة الموسّعة (متماسك) حتى لو اتبعت ناقص من الـ API.
- اختبار الأدوار بقى متوقّع: «دور الاستقبال» يقدر يكمّل دورة الطلب من غير صلاحية ناقصة.
ملفات: Modules/Core/.../PermissionDependencies.php,
RoleController (توسيع عند الحفظ)، core/permissions route،
src/app/features/lis/roles/lis-roles.component.ts/html (وroles.component.ts العام لو لزم).
6 أُنجز بالفعل في هذه الجلسة ✅ تم
| البند | الحالة | تفاصيل |
|---|---|---|
| بحث محرّر الأسعار | منشور | تحويل البحث لـ signal + debounce + لودنج، والبحث بالاسم/الكود/كود نفيس(SBS)/LOINC. اتحل مشكلة «ما بيفلترش إلا لما أقلب التابات». |
| تصدير كود نفيس | منشور | عمود nphies_code في Excel/CSV والقالب. |
| تسجيل العمليات على الفرع الرئيسي | منشور | كل العمليات الجديدة (طلب/عينة/فاتورة/دفع) تُسجَّل على الفرع الرئيسي للمستخدم من إعداداته (عماد ⟶ فرع 5)، وبشكل مُوحّد (مواضع العينات كانت بتستخدم «أول فرع» عشوائي — اتظبطت للفرع الرئيسي). القائمة فوق = تصفية عرض فقط. |
ترتيب التنفيذ المقترح
- النقطة 1 (قائمة الفروع — FE صغير ومؤكد) ثم نشر + تأكيد عماد.
- النقطة 2 (الوردية — تأكيد بـ refresh أولاً؛ تعديل فقط لو لزم).
- النقطة 3 (خزنة لكل فرع — BE ثم FE) ثم نشر. الربط التلقائي مرحلة تالية.
- النقطة 5 (اعتماديات الصلاحيات — BE خريطة + توسيع، ثم FE) ثم نشر — مهمة علشان نختبر الأدوار صح.
- النقطة 4 (نسخ التحليل — BE ثم FE) ثم نشر.
bash local-deploy.sh. التحقق دايماً على moonui.elbaset.com/moon-erp-be/api.