model->newQuery() ->with(['employee', 'approver', 'histories.user']) ->orderByDesc('start_date') ->get($columns); } public function find(int|string $id, array $columns = ['*']): ?Model { return $this->model->newQuery() ->with(['employee', 'approver', 'histories.user']) ->find($id, $columns); } public function create(array $attributes): Model { try { DB::beginTransaction(); /** @var Leave $leave */ $leave = $this->model->newQuery()->create($attributes); $this->recordHistory( $leave, null, $leave->status, $attributes['approved_by'] ?? null, $attributes['notes'] ?? null ); DB::commit(); return $leave->load(['employee', 'approver', 'histories.user']); } catch (Exception $e) { DB::rollBack(); Log::error('Error creating leave: ' . $e->getMessage(), [ 'attributes' => $attributes, 'exception' => $e, ]); throw $e; } } public function update(int|string $id, array $attributes): bool { try { DB::beginTransaction(); /** @var Leave|null $leave */ $leave = $this->model->newQuery()->find($id); if (!$leave) { DB::rollBack(); return false; } $oldStatus = $leave->status; $result = $leave->fill($attributes)->save(); if (array_key_exists('status', $attributes) && $attributes['status'] !== $oldStatus) { $this->recordHistory( $leave, $oldStatus, $leave->status, $attributes['approved_by'] ?? $leave->approved_by, $attributes['notes'] ?? null ); } DB::commit(); return $result; } catch (Exception $e) { DB::rollBack(); Log::error('Error updating leave with ID ' . $id . ': ' . $e->getMessage(), [ 'id' => $id, 'attributes' => $attributes, 'exception' => $e, ]); throw $e; } } public function getPaginated(int $perPage = 10, array $filters = []): array { $query = $this->model->newQuery()->with(['employee', 'approver', 'histories.user']); if (!empty($filters['employee_id'])) { $query->where('employee_id', (int) $filters['employee_id']); } if (!empty($filters['type'])) { $query->where('type', $filters['type']); } if (!empty($filters['status'])) { $query->where('status', $filters['status']); } if (!empty($filters['start_date'])) { $query->whereDate('start_date', '>=', $filters['start_date']); } if (!empty($filters['end_date'])) { $query->whereDate('end_date', '<=', $filters['end_date']); } if (!empty($filters['search'])) { $search = (string) $filters['search']; $query->where(function ($subQuery) use ($search) { $subQuery->where('reason', 'like', '%' . $search . '%') ->orWhere('notes', 'like', '%' . $search . '%') ->orWhereHas('employee', function ($employeeQuery) use ($search) { $employeeQuery->where('first_name', 'like', '%' . $search . '%') ->orWhere('last_name', 'like', '%' . $search . '%') ->orWhere('email', 'like', '%' . $search . '%'); }); }); } $sortField = $filters['sort_by'] ?? 'start_date'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $allowedSortFields = ['start_date', 'end_date', 'status', 'type', 'created_at']; if (!in_array($sortField, $allowedSortFields, true)) { $sortField = 'start_date'; } $sortDirection = strtolower((string) $sortDirection) === 'asc' ? 'asc' : 'desc'; $paginator = $query ->orderBy($sortField, $sortDirection) ->paginate($perPage); return [ 'leaves' => $paginator->getCollection(), 'pagination' => [ 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), 'from' => $paginator->firstItem(), 'to' => $paginator->lastItem(), ], ]; } private function recordHistory( Leave $leave, ?string $oldStatus, string $newStatus, int|string|null $changedBy = null, ?string $comment = null ): void { LeaveHistory::query()->create([ 'leave_id' => $leave->id, 'old_status' => $oldStatus, 'new_status' => $newStatus, 'changed_by' => $changedBy, 'changed_at' => now(), 'comment' => $comment, ]); } }