Commit 97db5304 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Enforce role-based data scoping across all list components

- Add branch_id accessor to User model (resolves from role_user pivot)
- Create AppliesRoleScope trait for one-liner scope enforcement
- Enhance PermissionService: include head_trainer_id in group lookup,
  add training_programs and invoices table support for own_groups scope
- Apply AppliesRoleScope to 16 list components:
  ParticipantList, GroupList, EnrollmentList, AttendanceList,
  InvoiceList, ProgramList, EvaluationList, CashSessionList,
  WalletList, AssignmentList, PersonList, FacilityList,
  ProductList, PurchaseOrderList, KitList, StockCountList

Now trainers only see their assigned groups/participants,
branch managers only see their branch data, parents only
see their children, and receptionists are branch-scoped.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent ecf6909c
...@@ -121,12 +121,20 @@ private function getAssignedGroupIds(User $user): array ...@@ -121,12 +121,20 @@ private function getAssignedGroupIds(User $user): array
return $this->cachedGroupIds[$user->id]; return $this->cachedGroupIds[$user->id];
} }
$ids = Assignment::active() // Groups via Assignment records
$assignmentIds = Assignment::active()
->forUser($user->id) ->forUser($user->id)
->where('assignable_type', TrainingGroup::class) ->where('assignable_type', TrainingGroup::class)
->pluck('assignable_id') ->pluck('assignable_id')
->toArray(); ->toArray();
// Groups where user is head_trainer_id
$headTrainerIds = TrainingGroup::where('head_trainer_id', $user->id)
->pluck('id')
->toArray();
$ids = array_unique(array_merge($assignmentIds, $headTrainerIds));
$this->cachedGroupIds[$user->id] = $ids; $this->cachedGroupIds[$user->id] = $ids;
return $ids; return $ids;
} }
...@@ -179,6 +187,11 @@ private function applyOwnGroupsScope(Builder $query, User $user, string $table): ...@@ -179,6 +187,11 @@ private function applyOwnGroupsScope(Builder $query, User $user, string $table):
'training_sessions' => $query->whereIn("{$table}.training_group_id", $groupIds), 'training_sessions' => $query->whereIn("{$table}.training_group_id", $groupIds),
'enrollments' => $query->whereIn("{$table}.training_group_id", $groupIds), 'enrollments' => $query->whereIn("{$table}.training_group_id", $groupIds),
'evaluations' => $query->whereIn("{$table}.training_group_id", $groupIds), 'evaluations' => $query->whereIn("{$table}.training_group_id", $groupIds),
'training_programs' => $query->whereIn("{$table}.id", function ($sub) use ($groupIds) {
$sub->select('training_program_id')
->from('training_groups')
->whereIn('id', $groupIds);
}),
'participants' => $query->whereIn("{$table}.id", function ($sub) use ($groupIds) { 'participants' => $query->whereIn("{$table}.id", function ($sub) use ($groupIds) {
$sub->select('participant_id') $sub->select('participant_id')
->from('enrollments') ->from('enrollments')
...@@ -190,6 +203,12 @@ private function applyOwnGroupsScope(Builder $query, User $user, string $table): ...@@ -190,6 +203,12 @@ private function applyOwnGroupsScope(Builder $query, User $user, string $table):
->from('training_sessions') ->from('training_sessions')
->whereIn('training_group_id', $groupIds); ->whereIn('training_group_id', $groupIds);
}), }),
'invoices' => $query->whereIn("{$table}.billable_id", function ($sub) use ($groupIds) {
$sub->select('participant_id')
->from('enrollments')
->whereIn('training_group_id', $groupIds)
->whereIn('status', ['active', 'pending']);
})->where("{$table}.billable_type", Participant::class),
default => $query->whereIn("{$table}.training_group_id", $groupIds), default => $query->whereIn("{$table}.training_group_id", $groupIds),
}; };
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\TrainingGroup; use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession; use App\Domain\Training\Models\TrainingSession;
use App\Livewire\Concerns\AppliesRoleScope;
use App\Models\User; use App\Models\User;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
...@@ -21,7 +22,9 @@ ...@@ -21,7 +22,9 @@
#[Title('التكليفات')] #[Title('التكليفات')]
class AssignmentList extends Component class AssignmentList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'assignments.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -130,6 +133,8 @@ public function render() ...@@ -130,6 +133,8 @@ public function render()
->when($this->userId, fn ($q) => $q->where('user_id', $this->userId)) ->when($this->userId, fn ($q) => $q->where('user_id', $this->userId))
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.assignments.assignment-list', [ return view('livewire.assignments.assignment-list', [
'assignments' => $query->paginate(20), 'assignments' => $query->paginate(20),
'statusOptions' => collect(AssignmentStatus::cases())->mapWithKeys(fn ($s) => [$s->value => $s->label()]), 'statusOptions' => collect(AssignmentStatus::cases())->mapWithKeys(fn ($s) => [$s->value => $s->label()]),
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\TrainingSession; use App\Domain\Training\Models\TrainingSession;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -14,7 +15,9 @@ ...@@ -14,7 +15,9 @@
#[Title('سجل الحضور')] #[Title('سجل الحضور')]
class AttendanceList extends Component class AttendanceList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'attendance.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -78,6 +81,8 @@ public function render() ...@@ -78,6 +81,8 @@ public function render()
->orderByDesc('session_date') ->orderByDesc('session_date')
->orderByDesc('start_time'); ->orderByDesc('start_time');
$this->applyRoleScope($query);
return view('livewire.attendance.attendance-list', [ return view('livewire.attendance.attendance-list', [
'sessions' => $query->paginate(20), 'sessions' => $query->paginate(20),
]); ]);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Financial\Models\CashSession; use App\Domain\Financial\Models\CashSession;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -14,7 +15,9 @@ ...@@ -14,7 +15,9 @@
#[Title('الورديات النقدية')] #[Title('الورديات النقدية')]
class CashSessionList extends Component class CashSessionList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'cash_sessions.list';
#[Url] #[Url]
public string $status = ''; public string $status = '';
...@@ -34,6 +37,8 @@ public function render() ...@@ -34,6 +37,8 @@ public function render()
->when($this->status, fn ($q) => $q->where('status', $this->status)) ->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderByDesc('opened_at'); ->orderByDesc('opened_at');
$this->applyRoleScope($query);
return view('livewire.cash-sessions.cash-session-list', [ return view('livewire.cash-sessions.cash-session-list', [
'sessions' => $query->paginate(20), 'sessions' => $query->paginate(20),
'statusOptions' => [ 'statusOptions' => [
......
<?php
namespace App\Livewire\Concerns;
use App\Domain\Identity\Services\PermissionService;
use Illuminate\Database\Eloquent\Builder;
/**
* Apply PermissionService::applyScope() to Livewire list queries.
*
* Usage in any list component:
* use AppliesRoleScope;
* protected string $scopePermission = 'participants.list';
*
* Then in render():
* $query = Participant::query();
* $this->applyRoleScope($query);
*/
trait AppliesRoleScope
{
protected function applyRoleScope(Builder $query, ?string $permission = null): Builder
{
$user = auth()->user();
if (!$user) {
return $query->whereRaw('1 = 0');
}
if ($user->is_super_admin) {
return $query;
}
$perm = $permission ?? $this->scopePermission ?? null;
if (!$perm) {
return $query;
}
return app(PermissionService::class)->applyScope($query, $user, $perm);
}
}
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
use App\Domain\Training\Models\TrainingGroup; use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Services\EnrollmentService; use App\Domain\Training\Services\EnrollmentService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -17,7 +18,9 @@ ...@@ -17,7 +18,9 @@
#[Title('التسجيلات')] #[Title('التسجيلات')]
class EnrollmentList extends Component class EnrollmentList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'enrollments.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -85,6 +88,8 @@ public function render() ...@@ -85,6 +88,8 @@ public function render()
->when($this->paymentFilter, fn ($q) => $q->where('payment_status', $this->paymentFilter)) ->when($this->paymentFilter, fn ($q) => $q->where('payment_status', $this->paymentFilter))
->orderByDesc('enrollment_date'); ->orderByDesc('enrollment_date');
$this->applyRoleScope($query);
return view('livewire.enrollments.enrollment-list', [ return view('livewire.enrollments.enrollment-list', [
'enrollments' => $query->paginate(20), 'enrollments' => $query->paginate(20),
'groups' => TrainingGroup::whereIn('status', ['forming', 'active', 'full'])->orderBy('name_ar')->get(['id', 'name_ar']), 'groups' => TrainingGroup::whereIn('status', ['forming', 'active', 'full'])->orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Training\Enums\EvaluationStatus; use App\Domain\Training\Enums\EvaluationStatus;
use App\Domain\Training\Models\Evaluation; use App\Domain\Training\Models\Evaluation;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -14,7 +15,9 @@ ...@@ -14,7 +15,9 @@
#[Title('التقييمات')] #[Title('التقييمات')]
class EvaluationList extends Component class EvaluationList extends Component
{ {
use WithPagination; use WithPagination, AppliesRoleScope;
protected string $scopePermission = 'evaluations.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -54,6 +57,8 @@ public function render() ...@@ -54,6 +57,8 @@ public function render()
->when($this->groupId, fn ($q) => $q->where('training_group_id', $this->groupId)) ->when($this->groupId, fn ($q) => $q->where('training_group_id', $this->groupId))
->orderByDesc('evaluation_date'); ->orderByDesc('evaluation_date');
$this->applyRoleScope($query);
$groups = \App\Domain\Training\Models\TrainingGroup::orderBy('name_ar')->get(['id', 'name_ar']); $groups = \App\Domain\Training\Models\TrainingGroup::orderBy('name_ar')->get(['id', 'name_ar']);
return view('livewire.evaluations.evaluation-list', [ return view('livewire.evaluations.evaluation-list', [
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use App\Domain\Identity\Models\Branch; use App\Domain\Identity\Models\Branch;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -19,7 +20,9 @@ ...@@ -19,7 +20,9 @@
#[Title('المنشآت')] #[Title('المنشآت')]
class FacilityList extends Component class FacilityList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'facilities.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -84,6 +87,8 @@ public function render() ...@@ -84,6 +87,8 @@ public function render()
->orderBy('sort_order') ->orderBy('sort_order')
->orderBy('name_ar'); ->orderBy('name_ar');
$this->applyRoleScope($query);
return view('livewire.facilities.facility-list', [ return view('livewire.facilities.facility-list', [
'facilities' => $query->paginate(20), 'facilities' => $query->paginate(20),
'branches' => Branch::orderBy('name_ar')->get(['id', 'name_ar']), 'branches' => Branch::orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use App\Domain\Training\Models\TrainingProgram; use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingGroupService; use App\Domain\Training\Services\TrainingGroupService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Concerns\AppliesRoleScope;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
...@@ -19,7 +20,9 @@ ...@@ -19,7 +20,9 @@
#[Title('المجموعات التدريبية')] #[Title('المجموعات التدريبية')]
class GroupList extends Component class GroupList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'groups.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -110,6 +113,8 @@ public function render() ...@@ -110,6 +113,8 @@ public function render()
->when($this->programFilter, fn ($q) => $q->where('training_program_id', $this->programFilter)) ->when($this->programFilter, fn ($q) => $q->where('training_program_id', $this->programFilter))
->orderBy($this->sortBy, $this->sortDir); ->orderBy($this->sortBy, $this->sortDir);
$this->applyRoleScope($query);
return view('livewire.groups.group-list', [ return view('livewire.groups.group-list', [
'groups' => $query->paginate(15), 'groups' => $query->paginate(15),
'programs' => TrainingProgram::orderBy('name_ar')->get(['id', 'name_ar']), 'programs' => TrainingProgram::orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use App\Domain\Inventory\Models\Warehouse; use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\KitService; use App\Domain\Inventory\Services\KitService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -16,7 +17,9 @@ ...@@ -16,7 +17,9 @@
#[Title('الأطقم')] #[Title('الأطقم')]
class KitList extends Component class KitList extends Component
{ {
use WithPagination; use WithPagination, AppliesRoleScope;
protected string $scopePermission = 'inventory.manage';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -138,6 +141,8 @@ public function render() ...@@ -138,6 +141,8 @@ public function render()
}) })
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.inventory.kit-list', [ return view('livewire.inventory.kit-list', [
'kits' => $query->paginate(20), 'kits' => $query->paginate(20),
'warehouses' => Warehouse::orderBy('name_ar')->get(['id', 'name_ar']), 'warehouses' => Warehouse::orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Domain\Inventory\Models\Product; use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\ProductCategory; use App\Domain\Inventory\Models\ProductCategory;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -15,7 +16,9 @@ ...@@ -15,7 +16,9 @@
#[Title('المنتجات')] #[Title('المنتجات')]
class ProductList extends Component class ProductList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'inventory.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -89,6 +92,8 @@ public function render() ...@@ -89,6 +92,8 @@ public function render()
}) })
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.inventory.product-list', [ return view('livewire.inventory.product-list', [
'products' => $query->paginate(20), 'products' => $query->paginate(20),
'categories' => ProductCategory::active()->orderBy('name_ar')->get(['id', 'name_ar']), 'categories' => ProductCategory::active()->orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Exceptions\InvalidStatusTransitionException; use App\Domain\Shared\Exceptions\InvalidStatusTransitionException;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -18,7 +19,9 @@ ...@@ -18,7 +19,9 @@
#[Title('أوامر الشراء')] #[Title('أوامر الشراء')]
class PurchaseOrderList extends Component class PurchaseOrderList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'inventory.manage';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -98,6 +101,8 @@ public function render() ...@@ -98,6 +101,8 @@ public function render()
->when($this->status, fn ($q) => $q->where('status', $this->status)) ->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.inventory.purchase-order-list', [ return view('livewire.inventory.purchase-order-list', [
'purchaseOrders' => $query->paginate(20), 'purchaseOrders' => $query->paginate(20),
'statuses' => PurchaseOrderStatus::cases(), 'statuses' => PurchaseOrderStatus::cases(),
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
use App\Domain\Inventory\Services\StockCountService; use App\Domain\Inventory\Services\StockCountService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Exceptions\InvalidStatusTransitionException; use App\Domain\Shared\Exceptions\InvalidStatusTransitionException;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -17,7 +18,9 @@ ...@@ -17,7 +18,9 @@
#[Title('الجرد المخزني')] #[Title('الجرد المخزني')]
class StockCountList extends Component class StockCountList extends Component
{ {
use WithPagination; use WithPagination, AppliesRoleScope;
protected string $scopePermission = 'inventory.manage';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -85,6 +88,8 @@ public function render() ...@@ -85,6 +88,8 @@ public function render()
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter)) ->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.inventory.stock-count-list', [ return view('livewire.inventory.stock-count-list', [
'stockCounts' => $query->paginate(20), 'stockCounts' => $query->paginate(20),
'statuses' => StockCountStatus::cases(), 'statuses' => StockCountStatus::cases(),
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use App\Domain\Financial\Models\Invoice; use App\Domain\Financial\Models\Invoice;
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -16,7 +17,9 @@ ...@@ -16,7 +17,9 @@
#[Title('الفواتير')] #[Title('الفواتير')]
class InvoiceList extends Component class InvoiceList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'invoices.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -50,6 +53,8 @@ public function render() ...@@ -50,6 +53,8 @@ public function render()
->when($this->status, fn ($q) => $q->where('status', $this->status)) ->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderByDesc('issue_date'); ->orderByDesc('issue_date');
$this->applyRoleScope($query);
return view('livewire.invoices.invoice-list', [ return view('livewire.invoices.invoice-list', [
'invoices' => $query->paginate(20), 'invoices' => $query->paginate(20),
'statusOptions' => collect(InvoiceStatus::cases())->mapWithKeys( 'statusOptions' => collect(InvoiceStatus::cases())->mapWithKeys(
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity; use App\Domain\Training\Models\Activity;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -15,7 +16,9 @@ ...@@ -15,7 +16,9 @@
#[Title('المشتركين')] #[Title('المشتركين')]
class ParticipantList extends Component class ParticipantList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'participants.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -69,6 +72,8 @@ public function render() ...@@ -69,6 +72,8 @@ public function render()
->when($this->skillLevel, fn ($q) => $q->where('skill_level', $this->skillLevel)) ->when($this->skillLevel, fn ($q) => $q->where('skill_level', $this->skillLevel))
->orderByDesc('created_at'); ->orderByDesc('created_at');
$this->applyRoleScope($query);
return view('livewire.participants.participant-list', [ return view('livewire.participants.participant-list', [
'participants' => $query->paginate(20), 'participants' => $query->paginate(20),
'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']), 'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace App\Livewire\People; namespace App\Livewire\People;
use App\Domain\Identity\Models\Person; use App\Domain\Identity\Models\Person;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -13,7 +14,9 @@ ...@@ -13,7 +14,9 @@
#[Title('السجل العام')] #[Title('السجل العام')]
class PersonList extends Component class PersonList extends Component
{ {
use WithPagination; use WithPagination, AppliesRoleScope;
protected string $scopePermission = 'people.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -55,6 +58,8 @@ public function render() ...@@ -55,6 +58,8 @@ public function render()
->when($this->classification, fn ($q) => $q->where('classification', $this->classification)) ->when($this->classification, fn ($q) => $q->where('classification', $this->classification))
->orderBy('name_ar'); ->orderBy('name_ar');
$this->applyRoleScope($query);
return view('livewire.people.person-list', [ return view('livewire.people.person-list', [
'people' => $query->paginate(20), 'people' => $query->paginate(20),
]); ]);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use App\Domain\Training\Models\TrainingProgram; use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingProgramService; use App\Domain\Training\Services\TrainingProgramService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -18,7 +19,9 @@ ...@@ -18,7 +19,9 @@
#[Title('البرامج التدريبية')] #[Title('البرامج التدريبية')]
class ProgramList extends Component class ProgramList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'programs.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -100,6 +103,8 @@ public function render() ...@@ -100,6 +103,8 @@ public function render()
->when($this->activityFilter, fn ($q) => $q->where('activity_id', $this->activityFilter)) ->when($this->activityFilter, fn ($q) => $q->where('activity_id', $this->activityFilter))
->orderBy($this->sortBy, $this->sortDir); ->orderBy($this->sortBy, $this->sortDir);
$this->applyRoleScope($query);
return view('livewire.programs.program-list', [ return view('livewire.programs.program-list', [
'programs' => $query->paginate(15), 'programs' => $query->paginate(15),
'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']), 'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Domain\Financial\Models\Wallet; use App\Domain\Financial\Models\Wallet;
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Traits\UsesBranchScope; use App\Domain\Shared\Traits\UsesBranchScope;
use App\Livewire\Concerns\AppliesRoleScope;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -15,7 +16,9 @@ ...@@ -15,7 +16,9 @@
#[Title('المحافظ')] #[Title('المحافظ')]
class WalletList extends Component class WalletList extends Component
{ {
use WithPagination, UsesBranchScope; use WithPagination, UsesBranchScope, AppliesRoleScope;
protected string $scopePermission = 'wallets.list';
#[Url] #[Url]
public string $search = ''; public string $search = '';
...@@ -53,6 +56,8 @@ public function render() ...@@ -53,6 +56,8 @@ public function render()
}) })
->orderByDesc('balance'); ->orderByDesc('balance');
$this->applyRoleScope($query);
return view('livewire.wallets.wallet-list', [ return view('livewire.wallets.wallet-list', [
'wallets' => $query->paginate(20), 'wallets' => $query->paginate(20),
]); ]);
......
...@@ -101,4 +101,23 @@ public function hasPermission(string $permission): bool ...@@ -101,4 +101,23 @@ public function hasPermission(string $permission): bool
->flatMap(fn (Role $role) => $role->permissions) ->flatMap(fn (Role $role) => $role->permissions)
->contains('name', $permission); ->contains('name', $permission);
} }
/**
* Resolve branch_id from role_user pivot (primary role's branch assignment).
* Falls back to session active_branch_id if no pivot branch set.
*/
public function getBranchIdAttribute(): ?int
{
// First: check role_user pivot for explicit branch assignment
$pivotBranch = $this->roles()
->wherePivotNotNull('branch_id')
->first()?->pivot?->branch_id;
if ($pivotBranch) {
return (int) $pivotBranch;
}
// Fallback: session-stored active branch (for branch switcher)
return session('active_branch_id') ? (int) session('active_branch_id') : null;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment