diff --git a/app/Http/Controllers/Admin/Hr/LeaveRequestController.php b/app/Http/Controllers/Admin/Hr/LeaveRequestController.php index 71c7abe..5363be7 100644 --- a/app/Http/Controllers/Admin/Hr/LeaveRequestController.php +++ b/app/Http/Controllers/Admin/Hr/LeaveRequestController.php @@ -12,6 +12,7 @@ use App\Services\Hr\LeaveRequestService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -45,6 +46,12 @@ public function store(SubmitLeaveRequest $request): RedirectResponse public function update(SubmitLeaveRequest $request, LeaveRequest $leaveRequest): RedirectResponse { + try { + $leaveRequest->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->leaveRequestService->update($leaveRequest, $request->validated(), $request->user()); $this->flashUpdated('Pengajuan cuti'); @@ -54,6 +61,12 @@ public function update(SubmitLeaveRequest $request, LeaveRequest $leaveRequest): public function destroy(LeaveRequest $leaveRequest): RedirectResponse { + try { + $leaveRequest->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->leaveRequestService->delete($leaveRequest); $this->flashDeleted('Pengajuan cuti'); diff --git a/app/Models/LeaveRequest.php b/app/Models/LeaveRequest.php index f781edc..387b466 100644 --- a/app/Models/LeaveRequest.php +++ b/app/Models/LeaveRequest.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\LeaveRequestStatus; +use App\Enums\Permission; use App\Models\Concerns\HasRejection; use App\Models\Concerns\InteractsWithActivityLog; use Illuminate\Database\Eloquent\Attributes\Appends; @@ -13,6 +14,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Validation\ValidationException; #[Guarded(['id'])] #[Appends([ @@ -136,7 +138,24 @@ public function statusLabel(): Attribute ); } - // 5. Relation + // 5. Other Methods + public function ensureEditable(): void + { + if ($this->status !== LeaveRequestStatus::PENDING) { + throw ValidationException::withMessages([ + 'status' => 'Pengajuan cuti tidak dapat diubah.', + ]); + } + } + + public static function canSubmit(User $user): bool + { + return $user->can(Permission::LEAVE_REQUESTS_CREATE->value) + && ! $user->can(Permission::LEAVE_REQUESTS_VERIFY->value) + && $user->employee !== null; + } + + // 6. Relation public function employee(): BelongsTo { return $this->belongsTo(Employee::class); diff --git a/app/Services/Hr/LeaveRequestService.php b/app/Services/Hr/LeaveRequestService.php index 631449e..1350e5c 100644 --- a/app/Services/Hr/LeaveRequestService.php +++ b/app/Services/Hr/LeaveRequestService.php @@ -3,21 +3,19 @@ namespace App\Services\Hr; use App\Enums\LeaveRequestStatus; -use App\Enums\Permission; use App\Models\LeaveRequest; use App\Models\User; use App\Services\Concerns\ResolvesAuthenticatedEmployee; +use App\Services\Concerns\RunsInTransaction; use App\Services\System\PushNotificationService; use Carbon\Carbon; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; class LeaveRequestService { - use ResolvesAuthenticatedEmployee; + use ResolvesAuthenticatedEmployee, RunsInTransaction; public function __construct( private readonly PushNotificationService $pushNotificationService, @@ -109,7 +107,12 @@ public function delete(LeaveRequest $leaveRequest): void $totalDays = $leaveRequest->total_days; $employeeName = $leaveRequest->employee?->user?->profile?->full_name; - $leaveRequest->delete(); + $this->runInTransaction( + function () use ($leaveRequest): void { + $leaveRequest->delete(); + }, + 'Gagal menghapus pengajuan cuti', + ); $this->pushNotificationService->sendToRoles( '🗑️ Pengajuan Cuti Dihapus', @@ -121,25 +124,16 @@ public function delete(LeaveRequest $leaveRequest): void public function approve(LeaveRequest $leaveRequest, User $user): void { - try { - DB::transaction(function () use ($leaveRequest, $user): void { + $this->runInTransaction( + function () use ($leaveRequest, $user): void { $leaveRequest->update([ 'status' => LeaveRequestStatus::APPROVED, 'verified_at' => Carbon::now(), 'verified_by_id' => $user->id, ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menyetujui pengajuan cuti: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal menyetujui pengajuan cuti', + ); $leaveRequest->loadMissing('employee.user'); if ($leaveRequest->employee?->user_id) { @@ -154,8 +148,8 @@ public function approve(LeaveRequest $leaveRequest, User $user): void public function reject(LeaveRequest $leaveRequest, string $reason, User $user): void { - try { - DB::transaction(function () use ($leaveRequest, $user, $reason): void { + $this->runInTransaction( + function () use ($leaveRequest, $user, $reason): void { $leaveRequest->update([ 'status' => LeaveRequestStatus::REJECTED, 'verified_at' => Carbon::now(), @@ -166,18 +160,9 @@ public function reject(LeaveRequest $leaveRequest, string $reason, User $user): 'reason' => $reason, 'rejected_by_id' => $user->id, ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menolak pengajuan cuti: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal menolak pengajuan cuti', + ); $leaveRequest->loadMissing('employee.user'); if ($leaveRequest->employee?->user_id) { @@ -190,6 +175,28 @@ public function reject(LeaveRequest $leaveRequest, string $reason, User $user): } } + public function hasPendingForEmployee(User $user): bool + { + if ($user->employee === null) { + return false; + } + + return LeaveRequest::query() + ->pending() + ->where('employee_id', $user->employee->id) + ->exists(); + } + + public function indexPageData(array $tableQuery, User $user): array + { + return [ + 'leaveRequests' => $this->paginateForIndex($tableQuery, $user), + 'authEmployeeId' => $user->employee?->id, + 'canSubmit' => LeaveRequest::canSubmit($user), + 'hasPending' => $this->hasPendingForEmployee($user), + ]; + } + private function calculateTotalDays(Carbon $startDate, Carbon $endDate): int { return (int) $startDate->diffInDays($endDate) + 1; @@ -216,33 +223,4 @@ private function applySorting(Builder $query, string $sort, string $direction): $query->latest(); } - - public function hasPendingForEmployee(User $user): bool - { - if ($user->employee === null) { - return false; - } - - return LeaveRequest::query() - ->pending() - ->where('employee_id', $user->employee->id) - ->exists(); - } - - public function canSubmit(User $user): bool - { - return $user->can(Permission::LEAVE_REQUESTS_CREATE->value) - && ! $user->can(Permission::LEAVE_REQUESTS_VERIFY->value) - && $user->employee !== null; - } - - public function indexPageData(array $tableQuery, User $user): array - { - return [ - 'leaveRequests' => $this->paginateForIndex($tableQuery, $user), - 'authEmployeeId' => $user->employee?->id, - 'canSubmit' => $this->canSubmit($user), - 'hasPending' => $this->hasPendingForEmployee($user), - ]; - } } diff --git a/resources/js/pages/admin/hr/leave-requests/Index.vue b/resources/js/pages/admin/hr/leave-requests/Index.vue index 2923c8b..32b1671 100644 --- a/resources/js/pages/admin/hr/leave-requests/Index.vue +++ b/resources/js/pages/admin/hr/leave-requests/Index.vue @@ -1,4 +1,6 @@ diff --git a/tests/Feature/Admin/Hr/LeaveRequestTest.php b/tests/Feature/Admin/Hr/LeaveRequestTest.php index 198f08b..6fd12a3 100644 --- a/tests/Feature/Admin/Hr/LeaveRequestTest.php +++ b/tests/Feature/Admin/Hr/LeaveRequestTest.php @@ -44,7 +44,7 @@ function createLeaveEmployeeUser(): User 'full_name' => fake()->name(), ]); Employee::factory()->create(['user_id' => $user->id]); - $user->assignRole('marketing'); + $user->assignRole('marketing-offline'); $user->forgetCachedPermissions(); return $user;