get()->map(fn ($row) => $row->toArray())->all(); $schoolYear = Configuration::getConfig('school_year'); $updaterIds = []; foreach ($incidents as $incident) { foreach (['updated_by_open', 'updated_by_closed', 'updated_by_canceled'] as $key) { if (!empty($incident[$key])) { $updaterIds[] = (int) $incident[$key]; } } } $updaterMap = $this->lookup->updaterNameMap($updaterIds); foreach ($incidents as &$incident) { foreach (['updated_by_open', 'updated_by_closed', 'updated_by_canceled'] as $key) { $id = (int) ($incident[$key] ?? 0); $incident[$key . '_name'] = $id > 0 ? ($updaterMap[$id] ?? null) : null; } } unset($incident); return [ 'incidents' => $incidents, 'grades' => $this->lookup->gradeOptionsWithActiveStudents($schoolYear), ]; } public function studentsByGrade(int $gradeId): array { $studentIds = StudentClass::query() ->active() ->where('student_class.class_section_id', $gradeId) ->pluck('student_id') ->all(); if (empty($studentIds)) { return []; } return Student::query() ->whereIn('id', $studentIds) ->where('is_active', 1) ->get() ->map(fn ($student) => [ 'id' => (int) $student->id, 'name' => trim((string) $student->firstname . ' ' . (string) $student->lastname), ]) ->all(); } public function addIncident(array $payload, int $userId): array { $studentId = (int) $payload['student_id']; $incidentType = (string) $payload['incident']; $description = (string) ($payload['description'] ?? ''); $incidentState = (string) ($payload['incident_state'] ?? 'Open'); $stateDescription = (string) ($payload['state_description'] ?? ''); $grade = (string) $payload['grade']; $student = Student::query()->find($studentId); if (!$student) { return [ 'ok' => false, 'message' => 'Student not found.', ]; } $semester = Configuration::getConfig('semester'); $schoolYear = Configuration::getConfig('school_year'); $currentDateTime = function_exists('utc_now') ? utc_now() : now('UTC')->toDateTimeString(); $existingIncident = CurrentIncident::query() ->where('student_id', $studentId) ->where('incident', $incidentType) ->first(); if ($existingIncident) { $data = [ 'incident_datetime' => $currentDateTime, 'updated_by_open' => $userId, ]; if ($incidentState === 'Closed') { $data['incident_state'] = 'Closed'; $data['close_description'] = $this->appendLine($existingIncident->close_description ?? '', $stateDescription); } elseif ($incidentState === 'Canceled') { $data['incident_state'] = 'Canceled'; $data['cancel_description'] = $this->appendLine($existingIncident->cancel_description ?? '', $stateDescription); } else { $data['open_description'] = $this->appendLine($existingIncident->open_description ?? '', $description); } $existingIncident->fill($data); $existingIncident->save(); return [ 'ok' => true, 'created' => false, 'incident_id' => (int) $existingIncident->id, ]; } $incident = CurrentIncident::query()->create([ 'student_id' => $student->id, 'student_name' => trim((string) $student->firstname . ' ' . (string) $student->lastname), 'grade' => $grade, 'incident' => $incidentType, 'incident_datetime' => $currentDateTime, 'incident_state' => 'Open', 'updated_by_open' => $userId, 'open_description' => $description, 'semester' => $semester, 'school_year' => $schoolYear, ]); return [ 'ok' => true, 'created' => true, 'incident_id' => (int) $incident->id, ]; } public function updateState(int $incidentId, string $newState): bool { return CurrentIncident::query()->whereKey($incidentId)->update([ 'incident_state' => $newState, ]) > 0; } public function closeIncident(int $incidentId, string $closeDescription, ?string $actionTaken, int $userId): array { $incident = CurrentIncident::query()->find($incidentId); if (!$incident) { return ['ok' => false, 'message' => 'Incident not found.']; } if ($incident->incident_state === 'Closed') { return ['ok' => false, 'message' => 'Incident is already closed.']; } $incident->fill([ 'incident_state' => 'Closed', 'updated_by_closed' => $userId, 'close_description' => $closeDescription, 'action_taken' => $actionTaken, ]); $incident->save(); return $this->moveToHistory($incident); } public function cancelIncident(int $incidentId, ?string $cancelDescription, ?string $actionTaken, int $userId): array { $incident = CurrentIncident::query()->find($incidentId); if (!$incident) { return ['ok' => false, 'message' => 'Incident not found.']; } if ($incident->incident_state === 'Canceled') { return ['ok' => false, 'message' => 'Incident is already canceled.']; } $incident->fill([ 'incident_state' => 'Canceled', 'updated_by_canceled' => $userId, 'cancel_description' => $cancelDescription, 'action_taken' => $actionTaken, ]); $incident->save(); return $this->moveToHistory($incident); } private function moveToHistory(CurrentIncident $incident): array { return DB::transaction(function () use ($incident) { $history = Incident::query()->create([ 'student_id' => $incident->student_id, 'student_name' => $incident->student_name, 'grade' => $incident->grade, 'incident' => $incident->incident, 'incident_datetime' => $incident->incident_datetime, 'incident_state' => $incident->incident_state, 'updated_by_open' => $incident->updated_by_open, 'open_description' => $incident->open_description, 'updated_by_closed' => $incident->updated_by_closed, 'close_description' => $incident->close_description, 'action_taken' => $incident->action_taken, 'updated_by_canceled' => $incident->updated_by_canceled, 'cancel_description' => $incident->cancel_description, 'semester' => $incident->semester, 'school_year' => $incident->school_year, 'created_at' => $incident->created_at, 'updated_at' => $incident->updated_at, ]); $incident->delete(); return [ 'ok' => true, 'incident_id' => (int) $history->id, ]; }); } private function appendLine(string $existing, string $append): string { if ($existing === '') { return $append; } return $existing . PHP_EOL . $append; } }