خطأ صلاحيات الرولز: There is no permission named lis.critical-alerts.view

تحليل كامل لنظام البيرمشن في الـLIS — المشكلة، السبب الجذري، ليه بيحصل، الإصلاح، والوقاية النهائية

التاريخ: 4 يونيو 2026  •  الموديول: LIS / Core (Spatie Permissions)  •  الحالة: اتصلّح + اتأكد (HTTP 200)

1) المشكلة

عند تعديل أي رول من /lab/roles فيه صلاحية معيّنة، الحفظ بيفشل بـ500 والرسالة:

There is no permission named `lis.critical-alerts.view` for guard `web`.

ده استثناء من Spatie Permission — بيتحصل لما الكود يحاول يربط بالرول اسم صلاحية مش موجود في جدول permissions.

2) السبب الجذري (ليه بيحصل)

نظام البيرمشن فيه "موسّع تبعيات" (LisPermissionDependencies) بيضمن إن أي رول متماسك. وفيه قاعدة عامة:

// LisPermissionDependencies::dependenciesOf()
// أي lis.{res}.{action} غير الـview بيحتاج lis.{res}.view
if (count($parts) === 3 && $parts[2] !== 'view') {
    $deps[] = "lis.{$parts[1]}.view";   // ← بيضيف الـview تلقائياً
}
تختار في الرولlis.critical-alerts.acknowledge
(موجودة ✓)
التوسيع يضيفlis.critical-alerts.view
(القاعدة العامة)
syncPermissionsبيلاقي .view مش متزرّعة في الـDB
Spatie ترمي"There is no permission named…" → 500

🎯 الخلاصة

المورد critical-alerts اتزرّعت له صلاحية acknowledge بس من غير view. القاعدة العامة بتفترض إن كل مورد عنده view — والافتراض ده مكسور لـ5 موارد. أول ما رول يحتوي على واحدة من أفعالها، التوسيع بيضيف .view الغير موجودة → كراش.

3) الـAudit — كل الألغام (مش بس واحدة)

فحصت الـ155 صلاحية lis.* — لقيت 5 موارد ليها أفعال من غير .view، كل واحدة كانت هتعمل نفس الكراش:

الموردالأفعال الموجودةالناقصالحالة دلوقتي
lis.critical-alertsacknowledgeviewاتزرعت
lis.insurance-invoicesgenerateviewاتزرعت
lis.qc-resultscreate, deleteviewاتزرعت
lis.rejection-reasonsmanageviewاتزرعت
lis.result-reportsgenerate, publish, view-historyviewاتزرعت

4) الإصلاح (اتعمل)

أ) إصلاح البيانات — زرع الـ5 views الناقصين

اتعملوا في الـDB الحيّة (firstOrCreate, guard=web) + اتضافوا لـRolePermissionSeeder.php عشان أي تنصيب جديد ياخدهم.

ب) إصلاح دفاعي — الـsync بقى آمن ضد أي فجوة مستقبلية

في LabRoleController::syncLisPermissions — بقى يفلتر القائمة الموسّعة على الصلاحيات الموجودة فعلاً قبل الـsync. فلو أي مورد جديد اتزرع غلط (من غير view)، الرول يتحفظ عادي بدل ما يعمل 500:

// قبل: بيرمي 500 لو فيه اسم مش موجود
$role->syncPermissions(array_merge($keep, $newLis));

// بعد: يفلتر على الموجود فعلاً
$existing = Permission::where('guard_name', 'web')
    ->whereIn('name', $wanted)->pluck('name')->all();
$role->syncPermissions($existing);   // مفيش كراش أبداً

✔ التأكيد

PUT /lis/roles/{id} بصلاحية lis.critical-alerts.acknowledgeHTTP 200، والـ.view اتضافت صح. الـaudit دلوقتي: 0 ألغام باقية.

5) الوقاية — عشان ميحصلش تاني

  1. قاعدة ثابتة: أي مورد lis.{res} له أي فعل لازم يكون له lis.{res}.view في الـseeder. الـview هو "بوابة" المورد — مفيش مورد من غير view.
  2. الـsync الدفاعي (اتعمل) — شبكة أمان: أي اسم صلاحية مش موجود بيتفلتر بدل ما يعمل كراش. ده بيحمي من أي فجوة seeder مستقبلية تلقائياً.
  3. فحص اتساق (مقترح): أمر/تست بسيط بيتأكد إن لكل مورد عنده أفعال، عنده .view — يتشغّل في CI أو بعد أي تعديل على الـseeder، فالفجوة تتكشف قبل ما توصل للمستخدم:
    // كل lis.{res} له فعل لازم له view
    foreach (group(perms) as $res => $actions) {
       assert(in_array('view', $actions),
         "Resource lis.$res has actions but no .view");
    }
  4. عند إضافة مورد/شاشة جديدة: ضيف صلاحياته كـمجموعة كاملة (view + باقي الأفعال) دايماً، حتى لو الشاشة بتعمل فعل واحد بس.

6) الملفات المتأثرة

ملحوظة: ده غير guard مختلف — كل الصلاحيات على guard web (مفيش تعارض guards). المشكلة كانت بيرمشن ناقصة، مش guard غلط.