Moon ERP — مراجعة شاملة لنظام الصلاحيات

تحليل عميق للكود، الـ UI، الـ UX، والـ Backend — مع خريطة طريق للإصلاح

2026-04-29 · Auditor: Claude · Branch: main · Build: main-WBPULEU5.js
801
إجمالي الصلاحيات في الـ DB
8
إجمالي الأدوار
7
إجمالي المستخدمين
30
routes غير محمية بـ permissionGuard
5
مشاكل CRITICAL
11
مشاكل HIGH
7
مناطق قوة

📋 المحتويات

  1. نظرة عامة على البنية
  2. مناطق القوة
  3. المشاكل CRITICAL
  4. المشاكل HIGH
  5. المشاكل MEDIUM
  6. سيناريوهات الاختبار
  7. خريطة الطريق للإصلاح

1️⃣ نظرة عامة على بنية النظام

نظام صلاحيات Moon ERP مبني على Spatie Laravel Permission في الـ backend مع طبقة من الـ frontend logic لفلترة العرض.

🏗️ التدفق الكامل

┌─────────────────────────────────────────────────────────────────────────┐ │ 1. LOGIN: POST /api/auth/login │ └─────────────────────────────────────────────────────────────────────────┘ Returns { token, user: { roles: ['admin'], permissions: [...] } }┌─────────────────────────────────────────────────────────────────────────┐ │ 2. STORE: NgRx auth.reducer holds user{roles, permissions} │ └─────────────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────────────┐ │ 3. PERMISSION SERVICE │ │ - isSuperAdmin() = (roles.length===0 && permissions.length===0) │ │ - hasAnyPermission(prefixes) → user.permissions.some(p.startsWith) │ │ - loadNavConfig() → fetches per-role hidden routes from settings │ └─────────────────────────────────────────────────────────────────────────┘ ↓ ┌────┴────┬────────────────────┐ ↓ ↓ ↓ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ Sidebar │ │ Topbar │ │ permissionGuard │ │ filter │ │ filter │ │ (route guard) │ └──────────┘ └──────────┘ └──────────────────┘ ↓ ↓ ↓ 117 items 97 items 93 routes guarded ⚠ 30 routes UNGUARDED

📊 توزيع الأدوار والصلاحيات

IDاسم الدورمحمي؟عدد المستخدمينعدد الصلاحياتالتوزيع على المديولز
1owner4794lis 154 · accounting 116 · hrm 112 · webstore 87 · core 69 · sales 51 · purchases 45 · inventory 39 · cmms 22 · qms 22 · crm 21 · pos 20 · production 19 · nphies 8 · einvoicing 7 · reports 2
2admin2185core 69 · accounting 116
3manager0187hrm 112 · accounting 32 · core 27 · inventory 13 · sales 3
4accountant1207accounting 116 · core 69 · cmms 22 ⚠️
5cashier123pos 14 · core 5 · sales 4
6employee120hrm 16 · core 4
8hr_manager0118hrm 112 · core 6
9lab_manager0160lis 154 · core 6

2️⃣ ✅ مناطق القوة

قوة 1. Spatie Permission على الـ Backend
الباك إند بيستخدم Spatie Permission package وهو معيار صناعي مجرّب. الـ auth payload بيرجع مصفوفة الصلاحيات مفلطحة (flat array) فالفرونت إند مش محتاج يعمل permission lookup إضافي.
قوة 2. Prefix Matching ذكي
hasAnyPermission(['core.users']) بيتطابق مع core.users.view, core.users.create, إلخ. ده بيخلي تعريف الصلاحيات على الـ UI level جاي بشكل granular بس بيتم فحصها بشكل مرن. الفرونت ما يحتاج يعرف كل الـ actions على كل resource.
قوة 3. NgRx Store كمصدر واحد للـ user state
الـ currentUser signal بيتقرأ من الـ store. الـ permission service و الـ guards و الـ topbar/sidebar كلهم بيبصوا للنفس المصدر. ده ممتاز للـ consistency.
قوة 4. Nav Customization per Role
كل دور ممكن يعرّف "Hidden Routes" — الصلاحية موجودة لكن العنصر مخفي من الـ navigation. ده feature متقدم بيخلي الـ admin يخصص شكل الـ UI بدون ما يلمس الصلاحيات.
قوة 5. Roles Page (بعد الـ updates الأخيرة)
صفحة /core/roles بقت احترافية فيها: ترجمات كاملة (18 module + 183 resource + 87 action)، آيقونات لكل مديول، احترام is_protected، عرض users_count، بحث، Clone، nav customization tab.
قوة 6. Login response caching
الـ AuthService بيخزن الـ user في localStorage كـ fallback لو الـ /me endpoint فشل. ده بيقلل احتمالية فشل الـ login بعد reload.
قوة 7. Functional Guards (Angular 21 modern API)
الـ guards كلها functional CanActivateFn (مش class-based) — أحدث نمط في Angular وأكثر testable.

3️⃣ 🔴 المشاكل الـ CRITICAL (الأولوية القصوى)

CRITICAL #1. روابط مباشرة لـ 30 صفحة بدون أي حماية

30 route بيلودوا component بدون permissionGuard — أي user مسجل دخوله يقدر يفتحهم بكتابة الـ URL. الفلترة في الـ sidebar/topbar مش هتحميهم.

  • كل LIS (22 route): /lis/patients, /lis/results, /lis/samples, /lis/invoices, /lis/insurance-contracts, ...
  • Dashboards: /sales/dashboard, /purchases/dashboard, /hr/dashboard
  • Guides: /core/guide, /accounting/guide, /sales/guide, /purchases/guide, /lis/guide
  • /core/theme

الحل: إضافة canActivate: [permissionGuard], data: { permissions: ['lis.'] } على كل route LIS وكذا الباقي.

CRITICAL #2. Users page بـ role dropdown ثابت

في users.component.ts الـ roleOptions array هاردكود 6 أدوار (owner, admin, manager, accountant, cashier, employee). لكن الـ DB فيها 8 أدوار حالياً + أي أدوار جديدة بيعملها الأدمن.

النتيجة:

  • الأدوار الجديدة (hr_manager, lab_manager) لا تظهر في dropdown إنشاء/تعديل المستخدم.
  • أي custom role يعمله الأدمن من /core/roles → مش هيقدر يسنده لمستخدم.

الحل: تحميل الأدوار ديناميكياً عبر RolesService.list() مع translateRoleName() للعرض.

CRITICAL #3. Single role assignment فقط

الـ Users form بيستخدم p-select (single) للـ role. لكن الـ API بيدعم مصفوفة roles[]. عند التعديل:

role: user.roles?.length ? user.roles[0] : 'employee',

أي user عنده multi-role، التعديل بياخد أول role بس ويفرشه. ده بيدمر باقي الأدوار بدون تنبيه.

الحل: استخدام p-multiSelect + الباك إند يقبل array.

CRITICAL #4. ترجمة دور المستخدم في users page غلط

في users.component.html سطر 23:

<p-tag [value]="('ROLES.' + role.toUpperCase()) | translate" ...>

هذا يبحث عن مفاتيح زي ROLES.OWNER, ROLES.ADMINغير موجودة في i18n. الترجمات كلها تحت ROLES.ROLE_NAMES.{name} (lowercase).

النتيجة: الـ tags في صفحة المستخدمين هتعرض المفتاح الخام (ROLES.OWNER) بدل "المالك".

الحل: استخدام translateRoleName() الموجودة بالفعل في roles component (نقلها لـ shared utility).

CRITICAL #5. accountant role فيه cmms.* بدون سبب

دور accountant في الـ DB عنده 207 perms = accounting 116 + core 69 + cmms 22.

cmms مش متعلق بالمحاسبة — هو نظام إدارة الصيانة. في الغالب seed خاطئ في RolePermissionSeeder. نفس البرابلم اللي حليته في admin role قبل كده.

النتيجة: المحاسب اللي بيدخل بيشوف مديول CMMS في الـ sidebar من غير ما يحتاجه.

الحل: تاسك للباك إند ينظف الـ RolePermissionSeeder ويفصل CMMS عن باقي الأدوار، أو نشيلها يدوياً من الـ DB.

4️⃣ 🟠 المشاكل HIGH

HIGH #6. purchasing.* orphan permissions
الـ DB فيها 4 صلاحيات بـ prefix purchasing. (purchasing.orders.*) مش مسندة لأي دور. الفرونت إند بيستخدم prefix purchasing. في 3 أماكن (sidebar, topbar, app.routes) لكن عملياً مفيهاش حد عنده الصلاحية.
تاسك #1533 لأحمد بيغطي ده بالفعل. الفرونت بعد الـ cleanup يستخدم purchases.*.
HIGH #7. sales.approval-workflows orphan
مذكور في nav configs بس مفيش permission مطابق في DB. النتيجة: العنصر يظهر/يختفي بناءً على وجود أي permission آخر يبدأ بـ sales. — مش granular للـ approval workflows فعلياً.
الحل: تاسك للباك إند يضيف الصلاحية، أو الفرونت يستخدم أقرب صلاحية موجودة.
HIGH #8. تناقض بين nav permission و route guard
/sales/customer-statements nav بيتطلب sales.reports فقط (بعد الفيكس)، لكن route guard لسه بيقبل ['sales.reports', 'accounting.reports']. النتيجة: مستخدم بـ accounting بس مش هيشوف الرابط في الـ sidebar لكن يقدر يدخل بكتابة URL.
نفس الحالة لـ /purchases/supplier-statements.
الحل: تحديث route guards في app.routes.ts لتطابق nav config.
HIGH #9. Super-admin = أي user لسه ما اتحملش
في permission.service.ts سطر 22:
if (!user) return true; // Not loaded yet — allow all to avoid flash

أثناء الـ flash بين فتح الصفحة وتحميل الـ profile، أي مستخدم يُعتبر super admin. كل عناصر الـ sidebar/topbar تظهر له.

المدة قصيرة (~ms) لكن في حالة شبكة بطيئة هتظهر ثواني، فيها يمكن للمستخدم يضغط روابط مش له صلاحية فيها (تعتمد على route guards اللي مش حماية كافية).

الحل: عرض loading skeleton حتى يحمل الـ user، بدل ما نخلي super admin = unloaded.

HIGH #10. مفيش طريقة لعرض permissions الفعلية لمستخدم محدد

صفحة /core/users بتعرض الـ roles بس. مفيش زرار "View permissions" يعرض الـ effective permissions (مجموع كل الأدوار + المباشرة). للأدمن الـ debug صعب: "ليه User X مش شايف Module Y؟"

الحل: إضافة dialog "View Permissions" على كل صف user.

HIGH #11. مفيش بحث في صفحة المستخدمين
صفحة /core/users بتعرض كل المستخدمين بدون input بحث. مع نمو القائمة لمئات/آلاف، البحث يدوي مستحيل. الـ DataTable الأساسي فيها يدعم البحث لكن مش معرّف هنا.
الحل: إضافة search + filter على الـ DataTable بـ role / status / branch.
HIGH #12. صفحة المستخدمين مفيهاش الصلاحيات المباشرة
Spatie بيدعم: User → Roles + Direct Permissions. الـ UI بس بيدعم Roles. الباك إند ممكن يكون assigning direct perms لكن مش ظاهر/معدّل من الـ UI.
الحل: Tab ثاني في user dialog لـ "Direct Permissions" أو إخفاء هذه الميزة كلياً لو مش متاحة.
HIGH #13. Nav customization tab بياخد من الـ NAV_MODULES بس
Tab الـ "Nav Customization" في صفحة Roles بيعتمد على NAV_MODULES من nav-items.config.ts (الـ topbar config). لكن السايدبار له menuItems منفصل في الكود — هيكلة مختلفة.
النتيجة: Hide Routes معرّف للـ topbar items فقط، السايدبار يقرأ نفس الـ hidden routes set بس عناصره قد لا تكون مسجلة.
الحل: توحيد nav config — مصدر واحد للـ topbar + sidebar.
HIGH #14. تكرار menu config (sidebar.menuItems vs NAV_MODULES)
السايدبار له شجرة nav في sidebar.component.ts:76-430 (354 سطر). الـ topbar / module-nav يستخدمون nav-items.config.ts. التغييرات في مكان واحد لازم تتكرر يدوياً في الثاني → سبب الـ bugs اللي ظهرت في الـ session ده.
الحل: حذف menuItems من سايدبار، استخدام NAV_MODULES في الاتنين.
HIGH #15. Setup wizard يحتاج صلاحيات module مش الادمن
الـ Setup Wizard (/setup) محمي بـ core.settings. لكن إنشاء warehouse داخله يتطلب inventory.warehouses.create. لو الادمن مفيش inventory perms (زي السيناريو "core+accounting only")، الـ wizard بيوقع.
الحل (Backend): setup wizard يستخدم elevated/temporary token. أو يعرّف "setup_admin" role بصلاحيات setup-only.
تاسك #1532 لأحمد بيغطي مشكلة ذات صلة (install.php seeders).
HIGH #16. لا تظهر الصلاحيات اللي اتغيرت بعد التحديث
لو الادمن غيّر صلاحيات دور، المستخدمين الحاليين يفضلوا بنفس الصلاحيات لحد ما يعملوا logout/login (لأن الـ token mid-session بياخد من snapshot وقت الـ login). الباك إند بيرجع perms في كل /me لو طلبتها، لكن الفرونت بيخزن snapshot في NgRx + localStorage.
الحل: إضافة "Revoke Sessions" feature، أو polling /me كل X دقيقة، أو WebSocket notification.

5️⃣ 🔵 المشاكل MEDIUM

MEDIUM #17. Nav customization مش بيتفلتر بالـ permissions المختارة
Tab "Nav Customization" بيعرض كل الـ NAV_MODULES بغض النظر عن الـ permissions اللي اتاختارت في الـ tab الأول. ممكن المستخدم يعمل toggle hide لـ route ما عندوش الصلاحية أصلاً (no-op).
MEDIUM #18. لا empty state واضح في صفحات
صفحة Users بدون empty state لو مفيش مستخدمين. الجدول يطلع فاضي بدون رسالة "أضف أول مستخدم".
MEDIUM #19. كل الأدوار guard_name: 'web'
Spatie بيدعم multi-guards (web, api, sanctum, ...). كل الأدوار web فقط. لو في المستقبل احتجنا API tokens لـ third-party integrations، هنحتاج guard منفصل. مش مشكلة فعلية الآن لكن قابلة للتطور.
MEDIUM #20. مفيش audit log للـ permission changes
مفيش سجل بـ "من غيّر صلاحيات دور admin متى؟" أو "متى تم تنشيط هذا المستخدم؟". الـ Spatie ما بيوفرش هذا تلقائياً، يحتاج spatie/laravel-activitylog على الباك إند.
تاسك للباك إند: تفعيل activity log على Role + User models.
MEDIUM #21. Owner role قابل للحذف يدوياً (حتى بـ is_protected)
الفرونت بيمنع زرار Delete على protected roles ✅، لكن لو الباك إند تم استدعاؤه مباشرة بـ DELETE /api/core/roles/1 (owner) — لازم نتأكد إن الباك إند يرفض. لازم اختبار.
MEDIUM #22. لا descriptions على الأدوار
الـ Role model بيحتوي بس على name. لو الادمن عمل "Custom_role_5" مفيش حقل بيشرح "هذا الدور للمدراء الإقليمين".
تاسك للباك إند: إضافة عمود description.

6️⃣ 🧪 سيناريوهات الاختبار — ماذا يرى كل مستخدم؟

تم الاختبار بـ simulator يحاكي منطق الـ sidebar filtering لـ 5 سيناريوهات. = ظاهر، × = مخفي.

السيناريو DashboardCoreAccountingSalesPurchasesInventoryHRProductionCRMCMMSQMSLISPOS
Owner (794 perms)
Admin (185: core+accounting) ××××××××××
Cashier (23 perms) ×××××××××
Employee (HR self-service) ××××××××××
Lab Manager (LIS + Core) ××××××××××

📸 سيناريو موكاب: ماذا يرى Admin (hazem@google.com)

Sidebar (الجانبية)

🏠 لوحة التحكم
⚙️ النظام الأساسي
↳ الشركة، الفروع، المستخدمون، الأدوار، الإعدادات، التسلسلات
↳ المنتجات، الفئات، الكتالوج، وحدات القياس
↳ الشركاء، فئات الشركاء
💰 المحاسبة
↳ شجرة الحسابات، القيود، الأستاذ العام، السنوات المالية
↳ العملات، الضرائب، أسعار الصرف
↳ سندات القبض/الصرف
↳ المصروفات، الإيرادات
↳ الحسابات البنكية، الشيكات، صندوق المصروفات
↳ الأصول الثابتة
↳ القيود التلقائية، التسوية البنكية، AR/AP
↳ الأرصدة الافتتاحية، إقفال السنة
↳ التقارير

Topbar (Mega Menu)

⚙️ النظام الأساسي ← (يفتح Mega Menu)
💰 المحاسبة ← (يفتح Mega Menu)

Mega Menu يعرض نفس البنية من Sidebar.

❌ غير ظاهر:
- المبيعات
- المشتريات
- المخزون
- الموارد البشرية
- الإنتاج
- CRM, CMMS, QMS
- المختبر، نقاط البيع

7️⃣ 🗺️ خريطة الطريق للإصلاح

المرحلة 1 — الأمان (هذا الأسبوع)

الأولويةالمهمةالمسؤولالجهد
P1إضافة permissionGuard على 30 route غير محمي (LIS, dashboards, guides)Frontend30 دقيقة
P1توحيد route guards مع nav permissions (customer-statements, supplier-statements)Frontend10 دقائق
P1إصلاح Users page → role dropdown ديناميكي + multi-role + ترجمة صحيحةFrontend2 ساعات
P1تنظيف cmms.* من دور accountant (و audit أدوار أخرى)Backend (تاسك جديد لأحمد)30 دقيقة

المرحلة 2 — UX / DX (الأسبوع القادم)

الأولويةالمهمةالمسؤولالجهد
P2توحيد nav config — حذف sidebar.menuItems واستخدام NAV_MODULESFrontend3 ساعات
P2إضافة "View Permissions" dialog في Users pageFrontend1 ساعة
P2إضافة بحث + فلتر (role/status/branch) في Users pageFrontend1 ساعة
P2تنظيف orphan purchasing.* + إضافة sales.approval-workflowsBackend (تاسك #1533 + جديد)1 ساعة
P2إضافة column description على Role modelBackend (تاسك جديد)30 دقيقة

المرحلة 3 — تحسينات عامة

الأولويةالمهمةالمسؤولالجهد
P3تفعيل Activity Log على Role + UserBackend2 ساعات
P3Loading skeleton بدل super-admin-while-loadingFrontend1 ساعة
P3Setup wizard scoped permissions / elevated tokenBackend3 ساعات
P3"Revoke active sessions" featureBackend + Frontend4 ساعات