From 1de9d321362db695c0018934b489016818790db3 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 14 Jun 2026 15:49:51 +0700 Subject: [PATCH] refactor: implement strict role-based access control and scoped data visibility for finance and HR operations --- .../Finance/EmployeeAdvanceController.php | 25 +++++++++++++++---- .../Admin/Finance/PayrollController.php | 19 ++++++++++++-- .../Admin/Finance/PayrollPeriodController.php | 5 ++-- .../Admin/Hr/AttendanceController.php | 15 ++++++++--- .../Admin/Hr/LeaveRequestController.php | 16 +++++++++--- .../Finance/EmployeeAdvanceService.php | 24 ++++++++++++------ app/Services/Finance/PayrollService.php | 24 ++++++++++++------ app/Services/Hr/LeaveRequestService.php | 6 ++++- 8 files changed, 101 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php index b104e8c..29e0b66 100644 --- a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php +++ b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php @@ -30,8 +30,8 @@ public function index(Request $request): Response $user = $request->user(); return Inertia::render('admin/finance/employee-advances/Index', [ - 'employeeAdvances' => $this->employeeAdvanceService->paginateForIndex($tableQuery), - 'summary' => $this->employeeAdvanceService->outstandingSummary(), + 'employeeAdvances' => $this->employeeAdvanceService->paginateForIndex($tableQuery, $user), + 'summary' => $this->employeeAdvanceService->outstandingSummary($user), 'authEmployeeId' => $user?->employee?->id, 'canSubmit' => $user?->can(Permission::EMPLOYEE_ADVANCES_CREATE->value) && ! $user->can(Permission::EMPLOYEE_ADVANCES_VERIFY->value) @@ -71,7 +71,12 @@ public function destroy(EmployeeAdvance $employeeAdvance): RedirectResponse public function approve(EmployeeAdvance $employeeAdvance): RedirectResponse { - $this->employeeAdvanceService->approve($employeeAdvance, auth()->user()); + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403); + } + + $this->employeeAdvanceService->approve($employeeAdvance, $user); $this->flashSuccess('Kasbon berhasil disetujui dan dicairkan dari kas.'); @@ -80,10 +85,15 @@ public function approve(EmployeeAdvance $employeeAdvance): RedirectResponse public function reject(RejectEmployeeAdvanceRequest $request, EmployeeAdvance $employeeAdvance): RedirectResponse { + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403); + } + $this->employeeAdvanceService->reject( $employeeAdvance, $request->validated('reason'), - auth()->user(), + $user, ); $this->flashSuccess('Kasbon berhasil ditolak.'); @@ -93,7 +103,12 @@ public function reject(RejectEmployeeAdvanceRequest $request, EmployeeAdvance $e public function pay(EmployeeAdvance $employeeAdvance): RedirectResponse { - $this->employeeAdvanceService->pay($employeeAdvance, auth()->user()); + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403); + } + + $this->employeeAdvanceService->pay($employeeAdvance, $user); $this->flashSuccess('Kasbon berhasil dilunasi.'); diff --git a/app/Http/Controllers/Admin/Finance/PayrollController.php b/app/Http/Controllers/Admin/Finance/PayrollController.php index 78e8440..ad120b4 100644 --- a/app/Http/Controllers/Admin/Finance/PayrollController.php +++ b/app/Http/Controllers/Admin/Finance/PayrollController.php @@ -20,10 +20,15 @@ public function __construct( public function storeAdjustment(PayrollAdjustmentRequest $request, Payroll $payroll): RedirectResponse { + $user = $request->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403, 'Anda tidak memiliki akses untuk menambah penyesuaian gaji.'); + } + $this->payrollService->addAdjustment( $payroll, $request->validated(), - $request->user(), + $user, ); $this->flashSuccess('Penyesuaian gaji berhasil ditambahkan.'); @@ -35,10 +40,15 @@ public function storeAdjustment(PayrollAdjustmentRequest $request, Payroll $payr public function updateAdjustment(PayrollAdjustmentRequest $request, PayrollAdjustment $payrollAdjustment): RedirectResponse { + $user = $request->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403, 'Anda tidak memiliki akses untuk mengubah penyesuaian gaji.'); + } + $this->payrollService->updateAdjustment( $payrollAdjustment, $request->validated(), - $request->user(), + $user, ); $this->flashSuccess('Penyesuaian gaji berhasil diperbarui.'); @@ -50,6 +60,11 @@ public function updateAdjustment(PayrollAdjustmentRequest $request, PayrollAdjus public function destroyAdjustment(PayrollAdjustment $payrollAdjustment): RedirectResponse { + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403, 'Anda tidak memiliki akses untuk menghapus penyesuaian gaji.'); + } + $payroll = $payrollAdjustment->payroll; $this->payrollService->deleteAdjustment($payrollAdjustment); diff --git a/app/Http/Controllers/Admin/Finance/PayrollPeriodController.php b/app/Http/Controllers/Admin/Finance/PayrollPeriodController.php index 274bb37..4339452 100644 --- a/app/Http/Controllers/Admin/Finance/PayrollPeriodController.php +++ b/app/Http/Controllers/Admin/Finance/PayrollPeriodController.php @@ -25,15 +25,16 @@ public function index(Request $request): Response $tableQuery = $this->parseDataTableQuery($request); $periodId = $request->integer('period_id') ?: null; $period = $this->payrollService->resolvePeriod($periodId); + $user = $request->user(); return Inertia::render('admin/finance/payroll/Index', [ 'periods' => $this->payrollService->listPeriods(), 'currentPeriod' => $period, 'payrolls' => $period - ? $this->payrollService->paginateForPeriod($period, $tableQuery) + ? $this->payrollService->paginateForPeriod($period, $tableQuery, $user) : null, 'summary' => $period - ? $this->payrollService->periodSummary($period) + ? $this->payrollService->periodSummary($period, $user) : null, 'adjustmentTypes' => PayrollAdjustmentType::selectOptions(), 'filters' => array_merge( diff --git a/app/Http/Controllers/Admin/Hr/AttendanceController.php b/app/Http/Controllers/Admin/Hr/AttendanceController.php index a61a965..2d72b88 100644 --- a/app/Http/Controllers/Admin/Hr/AttendanceController.php +++ b/app/Http/Controllers/Admin/Hr/AttendanceController.php @@ -26,10 +26,10 @@ public function index(Request $request): Response { $user = $request->user(); $employee = $user?->employee; - $canManageAll = $user?->can(Permission::ATTENDANCES_MANAGE->value) ?? false; + $isManager = $user?->hasAnyRole(['owner', 'developer', 'direktur']) ?? false; - $scopedEmployeeId = $canManageAll ? null : $employee?->id; - $hasScopedAccess = $canManageAll || $employee !== null; + $scopedEmployeeId = $isManager ? null : $employee?->id; + $hasScopedAccess = $isManager || $employee !== null; $start = $request->date('start') ?? now()->startOfMonth(); $end = $request->date('end') ?? now()->endOfMonth(); @@ -46,7 +46,7 @@ public function index(Request $request): Response : null, 'canCheckIn' => ($user?->can(Permission::ATTENDANCES_CREATE->value) ?? false) && $employee !== null, - 'canManageAll' => $canManageAll, + 'canManageAll' => $isManager, 'calendarRange' => [ 'start' => $start->toDateString(), 'end' => $end->toDateString(), @@ -74,6 +74,13 @@ public function checkOut(AttendanceCheckOutRequest $request): RedirectResponse public function destroy(Attendance $attendance): RedirectResponse { + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + if ($attendance->employee_id !== $user->employee?->id) { + abort(403, 'Anda tidak memiliki akses untuk menghapus data presensi ini.'); + } + } + $this->attendanceService->delete($attendance); $this->flashDeleted('Data presensi'); diff --git a/app/Http/Controllers/Admin/Hr/LeaveRequestController.php b/app/Http/Controllers/Admin/Hr/LeaveRequestController.php index 9482c0e..5145133 100644 --- a/app/Http/Controllers/Admin/Hr/LeaveRequestController.php +++ b/app/Http/Controllers/Admin/Hr/LeaveRequestController.php @@ -39,7 +39,7 @@ public function index(Request $request): Response } return Inertia::render('admin/hr/leave-requests/Index', [ - 'leaveRequests' => $this->leaveRequestService->paginateForIndex($tableQuery), + 'leaveRequests' => $this->leaveRequestService->paginateForIndex($tableQuery, $user), 'authEmployeeId' => $user?->employee?->id, 'canSubmit' => $user?->can(Permission::LEAVE_REQUESTS_CREATE->value) && ! $user->can(Permission::LEAVE_REQUESTS_VERIFY->value) @@ -80,7 +80,12 @@ public function destroy(LeaveRequest $leaveRequest): RedirectResponse public function approve(LeaveRequest $leaveRequest): RedirectResponse { - $this->leaveRequestService->approve($leaveRequest, auth()->user()); + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403); + } + + $this->leaveRequestService->approve($leaveRequest, $user); $this->flashSuccess('Pengajuan cuti berhasil disetujui.'); @@ -89,10 +94,15 @@ public function approve(LeaveRequest $leaveRequest): RedirectResponse public function reject(RejectLeaveRequestRequest $request, LeaveRequest $leaveRequest): RedirectResponse { + $user = auth()->user(); + if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + abort(403); + } + $this->leaveRequestService->reject( $leaveRequest, $request->validated('reason'), - auth()->user(), + $user, ); $this->flashSuccess('Pengajuan cuti berhasil ditolak.'); diff --git a/app/Services/Finance/EmployeeAdvanceService.php b/app/Services/Finance/EmployeeAdvanceService.php index f2734b3..7066fac 100644 --- a/app/Services/Finance/EmployeeAdvanceService.php +++ b/app/Services/Finance/EmployeeAdvanceService.php @@ -26,15 +26,19 @@ public function __construct( /** * @return array{outstanding_amount: int, outstanding_amount_formatted: string, outstanding_count: int} */ - public function outstandingSummary(): array + public function outstandingSummary(?User $user = null): array { - $outstandingAmount = (int) EmployeeAdvance::query() - ->where('status', EmployeeAdvanceStatus::APPROVED) - ->sum('amount'); + $query = EmployeeAdvance::query() + ->where('status', EmployeeAdvanceStatus::APPROVED); - $outstandingCount = EmployeeAdvance::query() - ->where('status', EmployeeAdvanceStatus::APPROVED) - ->count(); + if ($user !== null && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + $employeeId = $user->employee?->id ?? -1; + $query->where('employee_id', $employeeId); + } + + $outstandingAmount = (int) $query->sum('amount'); + + $outstandingCount = $query->count(); return [ 'outstanding_amount' => $outstandingAmount, @@ -46,10 +50,14 @@ public function outstandingSummary(): array /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ - public function paginateForIndex(array $tableQuery): LengthAwarePaginator + public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator { $query = EmployeeAdvance::query() ->with(['employee.user.profile', 'rejection']) + ->when(! $user->hasAnyRole(['owner', 'developer', 'direktur']), function (Builder $query) use ($user): void { + $employeeId = $user->employee?->id ?? -1; + $query->where('employee_id', $employeeId); + }) ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function (Builder $query) use ($search): void { diff --git a/app/Services/Finance/PayrollService.php b/app/Services/Finance/PayrollService.php index 1656ee9..12134c1 100644 --- a/app/Services/Finance/PayrollService.php +++ b/app/Services/Finance/PayrollService.php @@ -59,15 +59,19 @@ public function resolvePeriod(?int $periodId): ?PayrollPeriod /** * @return array{total_amount: int, total_amount_formatted: string, total_count: int, status: string, status_label: string} */ - public function periodSummary(PayrollPeriod $period): array + public function periodSummary(PayrollPeriod $period, User $user): array { - $totalAmount = (int) Payroll::query() - ->where('payroll_period_id', $period->id) - ->sum('total_amount'); + $query = Payroll::query() + ->where('payroll_period_id', $period->id); - $totalCount = Payroll::query() - ->where('payroll_period_id', $period->id) - ->count(); + if (! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { + $employeeId = $user->employee?->id ?? -1; + $query->where('employee_id', $employeeId); + } + + $totalAmount = (int) $query->sum('total_amount'); + + $totalCount = $query->count(); return [ 'total_amount' => $totalAmount, @@ -81,11 +85,15 @@ public function periodSummary(PayrollPeriod $period): array /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ - public function paginateForPeriod(PayrollPeriod $period, array $tableQuery): LengthAwarePaginator + public function paginateForPeriod(PayrollPeriod $period, array $tableQuery, User $user): LengthAwarePaginator { $query = Payroll::query() ->with(['employee.user.profile', 'payrollPeriod', 'adjustments.createdBy.profile']) ->where('payroll_period_id', $period->id) + ->when(! $user->hasAnyRole(['owner', 'developer', 'direktur']), function (Builder $query) use ($user): void { + $employeeId = $user->employee?->id ?? -1; + $query->where('employee_id', $employeeId); + }) ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function (Builder $query) use ($search): void { diff --git a/app/Services/Hr/LeaveRequestService.php b/app/Services/Hr/LeaveRequestService.php index 62b1f70..5aac8eb 100644 --- a/app/Services/Hr/LeaveRequestService.php +++ b/app/Services/Hr/LeaveRequestService.php @@ -24,10 +24,14 @@ public function __construct( /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ - public function paginateForIndex(array $tableQuery): LengthAwarePaginator + public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator { $query = LeaveRequest::query() ->with(['employee.user.profile', 'rejection']) + ->when(! $user->hasAnyRole(['owner', 'developer', 'direktur']), function (Builder $query) use ($user): void { + $employeeId = $user->employee?->id ?? -1; + $query->where('employee_id', $employeeId); + }) ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function (Builder $query) use ($search): void {