feat(auth): enforce strict branch scoping for branch-assigned HR and staff accounts
- Introduce authBranchId() helper to resolve assigned branch while allowing company admin/superadmin cross-branch oversight - Restrict branch, department, and employee listings and mutations to assigned branch for branch-scoped accounts - Scope attendance records, biometric logs, leave applications/balances, payslips, shifts, and announcements by assigned branch - Add comprehensive automated feature tests in BranchScopedAccessTest to prevent branch cross-visibility regression
This commit is contained in:
@@ -12,8 +12,14 @@ class ShiftController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-shifts')) {
|
||||
$query = Shift::with(['creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-shifts')) {
|
||||
$scopedBranchId = authBranchId();
|
||||
$query = Shift::with(['creator'])->where(function ($q) use ($scopedBranchId) {
|
||||
if ($scopedBranchId) {
|
||||
$q->where(function($q2) use ($scopedBranchId) {
|
||||
$q2->where('branch_id', $scopedBranchId)
|
||||
->orWhereNull('branch_id'); // company wide shifts
|
||||
})->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-any-shifts')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} else {
|
||||
$branchId = Auth::user()->branch_id ?? Auth::user()->employee->branch_id ?? null;
|
||||
@@ -67,8 +73,13 @@ class ShiftController extends Controller
|
||||
$shifts = $query->paginate($request->per_page ?? 9);
|
||||
|
||||
// Stats always calculated from ALL records — never affected by filters or pagination
|
||||
$allShifts = Shift::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-shifts')) {
|
||||
$allShifts = Shift::where(function ($q) use ($scopedBranchId) {
|
||||
if ($scopedBranchId) {
|
||||
$q->where(function($q2) use ($scopedBranchId) {
|
||||
$q2->where('branch_id', $scopedBranchId)
|
||||
->orWhereNull('branch_id'); // company wide shifts
|
||||
})->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-any-shifts')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} else {
|
||||
$branchId = Auth::user()->branch_id ?? Auth::user()->employee->branch_id ?? null;
|
||||
@@ -90,7 +101,11 @@ class ShiftController extends Controller
|
||||
'day' => (clone $allShifts)->where('is_night_shift', false)->count(),
|
||||
];
|
||||
|
||||
$branches = \App\Models\Branch::whereIn('created_by', getCompanyAndUsersId())->get(['id', 'name']);
|
||||
$branchesQuery = \App\Models\Branch::whereIn('created_by', getCompanyAndUsersId());
|
||||
if ($scopedBranchId) {
|
||||
$branchesQuery->where('id', $scopedBranchId);
|
||||
}
|
||||
$branches = $branchesQuery->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/shifts/index', [
|
||||
'shifts' => $shifts,
|
||||
@@ -122,7 +137,10 @@ class ShiftController extends Controller
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
$validated['is_night_shift'] = $validated['is_night_shift'] ?? false;
|
||||
|
||||
if (!Auth::user()->can('manage-any-shifts')) {
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId) {
|
||||
$validated['branch_id'] = $scopedBranchId;
|
||||
} elseif (!Auth::user()->can('manage-any-shifts')) {
|
||||
$validated['branch_id'] = Auth::user()->branch_id ?? Auth::user()->employee->branch_id ?? null;
|
||||
} else {
|
||||
$validated['branch_id'] = ($request->input('branch_id') === 'none' || empty($request->input('branch_id'))) ? null : $request->input('branch_id');
|
||||
@@ -175,7 +193,10 @@ class ShiftController extends Controller
|
||||
'branch_id' => 'nullable',
|
||||
]);
|
||||
|
||||
if (!Auth::user()->can('manage-any-shifts')) {
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId) {
|
||||
$validated['branch_id'] = $scopedBranchId;
|
||||
} elseif (!Auth::user()->can('manage-any-shifts')) {
|
||||
$validated['branch_id'] = Auth::user()->branch_id ?? Auth::user()->employee->branch_id ?? null;
|
||||
} else {
|
||||
$validated['branch_id'] = ($request->input('branch_id') === 'none' || empty($request->input('branch_id'))) ? null : $request->input('branch_id');
|
||||
@@ -277,7 +298,12 @@ class ShiftController extends Controller
|
||||
$q->where('employee_status', 'active');
|
||||
});
|
||||
|
||||
if (!Auth::user()->can('manage-any-shifts')) {
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId) {
|
||||
$query->whereHas('employee', function($q) use ($scopedBranchId) {
|
||||
$q->where('branch_id', $scopedBranchId);
|
||||
});
|
||||
} elseif (!Auth::user()->can('manage-any-shifts')) {
|
||||
$branchId = Auth::user()->branch_id ?? Auth::user()->employee->branch_id ?? null;
|
||||
if ($branchId) {
|
||||
$query->whereHas('employee', function($q) use ($branchId) {
|
||||
@@ -399,8 +425,20 @@ class ShiftController extends Controller
|
||||
];
|
||||
});
|
||||
|
||||
$departments = \App\Models\Department::select('id', 'name')->get();
|
||||
$allShifts = \App\Models\Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->get();
|
||||
$departmentsQuery = \App\Models\Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active');
|
||||
if ($scopedBranchId) {
|
||||
$departmentsQuery->where('branch_id', $scopedBranchId);
|
||||
}
|
||||
$departments = $departmentsQuery->select('id', 'name')->get();
|
||||
|
||||
$allShiftsQuery = \App\Models\Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active');
|
||||
if ($scopedBranchId) {
|
||||
$allShiftsQuery->where(function($sq) use ($scopedBranchId) {
|
||||
$sq->where('branch_id', $scopedBranchId)->orWhereNull('branch_id');
|
||||
});
|
||||
}
|
||||
$allShifts = $allShiftsQuery->get();
|
||||
|
||||
return Inertia::render('hr/shifts/calendar', [
|
||||
'users' => $users,
|
||||
@@ -421,6 +459,14 @@ class ShiftController extends Controller
|
||||
public function assignShift(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-shifts')) {
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId) {
|
||||
$targetEmp = Employee::where('user_id', $request->employee_id)->first();
|
||||
if ($targetEmp && (int)$targetEmp->branch_id !== (int)$scopedBranchId) {
|
||||
return redirect()->back()->with('error', __('Permission Denied. You cannot assign shifts to employees outside your branch.'));
|
||||
}
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'start_date' => 'required|date',
|
||||
|
||||
Reference in New Issue
Block a user