orderBy('min_score', 'asc'); if ($activeOnly) { $query->where('is_active', 1); } return $query->get() ->map(fn ($row) => $this->transform($row)) ->values() ->all(); } public function findOrFail(int $id): array { $template = AttendanceCommentTemplate::query()->find($id); if (!$template) { throw new ModelNotFoundException("Attendance comment template not found."); } return $this->transform($template); } public function create(array $data): array { return DB::transaction(function () use ($data) { $this->ensureNoOverlap( (int) $data['min_score'], (int) $data['max_score'], null, !empty($data['is_active']) ); $template = AttendanceCommentTemplate::query()->create([ 'min_score' => (int) $data['min_score'], 'max_score' => (int) $data['max_score'], 'template_text' => (string) $data['template_text'], 'is_active' => (bool) ($data['is_active'] ?? true), ]); return $this->transform($template->fresh()); }); } public function update(int $id, array $data): array { return DB::transaction(function () use ($id, $data) { $template = AttendanceCommentTemplate::query()->find($id); if (!$template) { throw new ModelNotFoundException("Attendance comment template not found."); } $newMin = array_key_exists('min_score', $data) ? (int) $data['min_score'] : (int) $template->min_score; $newMax = array_key_exists('max_score', $data) ? (int) $data['max_score'] : (int) $template->max_score; $newIsActive = array_key_exists('is_active', $data) ? (bool) $data['is_active'] : (bool) $template->is_active; $this->ensureNoOverlap($newMin, $newMax, $id, $newIsActive); $template->fill([ 'min_score' => $newMin, 'max_score' => $newMax, 'template_text' => array_key_exists('template_text', $data) ? (string) $data['template_text'] : (string) $template->template_text, 'is_active' => $newIsActive, ]); $template->save(); return $this->transform($template->fresh()); }); } public function delete(int $id): void { $template = AttendanceCommentTemplate::query()->find($id); if (!$template) { throw new ModelNotFoundException("Attendance comment template not found."); } $template->delete(); } /** * Prevent overlapping active score ranges. */ private function ensureNoOverlap(int $min, int $max, ?int $ignoreId = null, bool $isActive = true): void { if (!$isActive) { return; // allow inactive templates to overlap } $query = AttendanceCommentTemplate::query() ->where('is_active', 1) ->where(function ($q) use ($min, $max) { $q->whereBetween('min_score', [$min, $max]) ->orWhereBetween('max_score', [$min, $max]) ->orWhere(function ($q2) use ($min, $max) { $q2->where('min_score', '<=', $min) ->where('max_score', '>=', $max); }); }); if ($ignoreId !== null) { $query->where('id', '!=', $ignoreId); } if ($query->exists()) { abort(response()->json([ 'status' => 'error', 'message' => 'Score range overlaps with an existing active template.', ], 422)); } } private function transform($row): array { return [ 'id' => (int) ($row->id ?? 0), 'min_score' => (int) ($row->min_score ?? 0), 'max_score' => (int) ($row->max_score ?? 0), 'template_text' => (string) ($row->template_text ?? ''), 'is_active' => (bool) ($row->is_active ?? false), ]; } }