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 { $query->whereHas('employee.user.profile', fn (Builder $query) => $query->where('full_name', 'like', "%{$search}%")) ->orWhereHas('employee.user', fn (Builder $query) => $query->where('username', 'like', "%{$search}%")) ->orWhereHas('rejection', fn (Builder $query) => $query->where('reason', 'like', "%{$search}%")); }); }); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); return $query ->paginate(10) ->withQueryString(); } /** * @param array{start_date: string, end_date: string} $validated */ public function create(array $validated, User $user): void { $employee = $this->resolveAuthenticatedEmployee($user); $hasPending = LeaveRequest::query() ->pending() ->where('employee_id', $employee->id) ->exists(); if ($hasPending) { throw ValidationException::withMessages([ 'start_date' => 'Anda masih memiliki pengajuan cuti yang aktif (menunggu persetujuan).', ]); } $startDate = Carbon::parse($validated['start_date'])->startOfDay(); $endDate = Carbon::parse($validated['end_date'])->startOfDay(); $this->ensureMinimumLeadTime($startDate); $leaveRequest = LeaveRequest::create([ 'employee_id' => $employee->id, 'start_date' => $startDate, 'end_date' => $endDate, 'total_days' => $this->calculateTotalDays($startDate, $endDate), 'status' => LeaveRequestStatus::PENDING, ]); $this->pushNotificationService->sendToRoles( '🏖️ Pengajuan Cuti Baru', "Karyawan {$user->profile?->full_name} mengajukan cuti selama {$leaveRequest->total_days} hari mulai dari tanggal {$leaveRequest->start_date_formatted}.", ['owner', 'developer'], route('admin.hr.leave_requests.index'), ); } /** * @param array{start_date: string, end_date: string} $validated */ public function update(LeaveRequest $leaveRequest, array $validated, User $user): void { $startDate = Carbon::parse($validated['start_date'])->startOfDay(); $endDate = Carbon::parse($validated['end_date'])->startOfDay(); $this->ensureMinimumLeadTime($startDate); $leaveRequest->update([ 'start_date' => $startDate, 'end_date' => $endDate, 'total_days' => $this->calculateTotalDays($startDate, $endDate), ]); $this->pushNotificationService->sendToRoles( '✏️ Pengajuan Cuti Diperbarui', "Pengajuan cuti oleh {$user->profile?->full_name} telah diperbarui.", ['owner', 'developer'], route('admin.hr.leave_requests.index'), ); } public function delete(LeaveRequest $leaveRequest): void { $totalDays = $leaveRequest->total_days; $employeeName = $leaveRequest->employee?->user?->profile?->full_name; $leaveRequest->delete(); $this->pushNotificationService->sendToRoles( '🗑️ Pengajuan Cuti Dihapus', "Pengajuan cuti {$totalDays} hari oleh {$employeeName} telah dihapus.", ['owner', 'developer'], route('admin.hr.leave_requests.index'), ); } public function approve(LeaveRequest $leaveRequest, User $user): void { try { DB::transaction(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.', ]); } $leaveRequest->loadMissing('employee.user'); if ($leaveRequest->employee?->user_id) { $this->pushNotificationService->sendToUser( '🏖️ Pengajuan Cuti Disetujui', "Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) telah disetujui.", $leaveRequest->employee->user_id, route('admin.hr.leave_requests.index'), ); } } public function reject(LeaveRequest $leaveRequest, string $reason, User $user): void { try { DB::transaction(function () use ($leaveRequest, $user, $reason): void { $leaveRequest->update([ 'status' => LeaveRequestStatus::REJECTED, 'verified_at' => Carbon::now(), 'verified_by_id' => $user->id, ]); $leaveRequest->rejection()->create([ '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.', ]); } $leaveRequest->loadMissing('employee.user'); if ($leaveRequest->employee?->user_id) { $this->pushNotificationService->sendToUser( '🏖️ Pengajuan Cuti Ditolak', "Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) ditolak dengan alasan: '{$reason}'.", $leaveRequest->employee->user_id, route('admin.hr.leave_requests.index'), ); } } private function calculateTotalDays(Carbon $startDate, Carbon $endDate): int { return (int) $startDate->diffInDays($endDate) + 1; } private function ensureMinimumLeadTime(Carbon $startDate): void { $minimumStartDate = Carbon::now()->startOfDay()->addDay(); if ($startDate->lt($minimumStartDate)) { throw ValidationException::withMessages([ 'start_date' => 'Pengajuan cuti harus diajukan minimal H-1 (tanggal mulai paling cepat besok).', ]); } } private function applySorting(Builder $query, string $sort, string $direction): void { if (in_array($sort, ['created_at', 'start_date', 'end_date', 'total_days', 'status'], true)) { $query->orderBy($sort, $direction); return; } $query->latest(); } }