add controllers, servoices
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Incidents;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\CurrentIncident;
|
||||
use App\Models\Incident;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CurrentIncidentService
|
||||
{
|
||||
public function __construct(private IncidentLookupService $lookup)
|
||||
{
|
||||
}
|
||||
|
||||
public function listCurrent(): array
|
||||
{
|
||||
$incidents = CurrentIncident::query()->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Incidents;
|
||||
|
||||
use App\Models\Incident;
|
||||
|
||||
class IncidentAnalysisService
|
||||
{
|
||||
public function __construct(private IncidentLookupService $lookup)
|
||||
{
|
||||
}
|
||||
|
||||
public function analyze(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
$builder = Incident::query()->orderBy('incident_datetime', 'DESC');
|
||||
if ($schoolYear !== null && $schoolYear !== '') {
|
||||
$builder->where('school_year', $schoolYear);
|
||||
}
|
||||
if ($semester !== null && $semester !== '') {
|
||||
$builder->where('semester', $semester);
|
||||
}
|
||||
|
||||
$incidents = $builder->get()->map(fn ($row) => $row->toArray())->all();
|
||||
if (empty($incidents)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$gradeMap = $this->lookup->gradeNameMap(array_column($incidents, 'grade'));
|
||||
$students = [];
|
||||
|
||||
foreach ($incidents as $incident) {
|
||||
$studentId = (string) ($incident['student_id'] ?? '');
|
||||
$studentName = (string) ($incident['student_name'] ?? '');
|
||||
$studentKey = $studentId !== '' ? 'id:' . $studentId : 'name:' . strtolower(trim($studentName));
|
||||
|
||||
$gradeValue = $incident['grade'] ?? '';
|
||||
$gradeLabel = $gradeValue;
|
||||
if (is_numeric($gradeValue)) {
|
||||
$gradeLabel = $gradeMap[(int) $gradeValue] ?? (string) $gradeValue;
|
||||
}
|
||||
|
||||
if (!isset($students[$studentKey])) {
|
||||
$students[$studentKey] = [
|
||||
'student_id' => $studentId,
|
||||
'student_name' => $studentName,
|
||||
'grade' => (string) $gradeLabel,
|
||||
'total' => 0,
|
||||
'open' => 0,
|
||||
'closed' => 0,
|
||||
'canceled' => 0,
|
||||
'last_incident' => null,
|
||||
'logs' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$students[$studentKey]['total'] += 1;
|
||||
|
||||
$state = (string) ($incident['incident_state'] ?? '');
|
||||
if ($state === 'Open') {
|
||||
$students[$studentKey]['open'] += 1;
|
||||
} elseif ($state === 'Closed') {
|
||||
$students[$studentKey]['closed'] += 1;
|
||||
} elseif ($state === 'Canceled') {
|
||||
$students[$studentKey]['canceled'] += 1;
|
||||
}
|
||||
|
||||
if ($students[$studentKey]['last_incident'] === null) {
|
||||
$students[$studentKey]['last_incident'] = $incident['incident_datetime'] ?? null;
|
||||
}
|
||||
|
||||
$students[$studentKey]['logs'][] = [
|
||||
'incident' => $incident['incident'] ?? '',
|
||||
'incident_state' => $state,
|
||||
'incident_datetime' => $incident['incident_datetime'] ?? null,
|
||||
'open_description' => $incident['open_description'] ?? null,
|
||||
'close_description' => $incident['close_description'] ?? null,
|
||||
'cancel_description' => $incident['cancel_description'] ?? null,
|
||||
'action_taken' => $incident['action_taken'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
$studentRows = array_values($students);
|
||||
usort($studentRows, function ($a, $b) {
|
||||
if ($a['total'] === $b['total']) {
|
||||
return strcasecmp($a['student_name'], $b['student_name']);
|
||||
}
|
||||
return $b['total'] <=> $a['total'];
|
||||
});
|
||||
|
||||
return $studentRows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Incidents;
|
||||
|
||||
use App\Models\Incident;
|
||||
|
||||
class IncidentHistoryService
|
||||
{
|
||||
public function __construct(private IncidentLookupService $lookup)
|
||||
{
|
||||
}
|
||||
|
||||
public function history(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
$builder = Incident::query()->orderBy('incident_datetime', 'DESC');
|
||||
if ($schoolYear !== null && $schoolYear !== '') {
|
||||
$builder->where('school_year', $schoolYear);
|
||||
}
|
||||
if ($semester !== null && $semester !== '') {
|
||||
$builder->where('semester', $semester);
|
||||
}
|
||||
|
||||
return $builder->get()->map(fn ($row) => $row->toArray())->all();
|
||||
}
|
||||
|
||||
public function processed(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
$incidents = $this->history($schoolYear, $semester);
|
||||
if (empty($incidents)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$gradeMap = $this->lookup->gradeNameMap(array_column($incidents, 'grade'));
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$gradeValue = $incident['grade'] ?? '';
|
||||
if (is_numeric($gradeValue)) {
|
||||
$incident['grade'] = $gradeMap[(int) $gradeValue] ?? (string) $gradeValue;
|
||||
}
|
||||
}
|
||||
unset($incident);
|
||||
|
||||
return $incidents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Incidents;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IncidentLookupService
|
||||
{
|
||||
public function gradeOptionsWithActiveStudents(?string $schoolYear = null): array
|
||||
{
|
||||
$studentCounts = StudentClass::getStudentCountsBySection($schoolYear);
|
||||
if (empty($studentCounts)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$classSections = ClassSection::query()
|
||||
->orderBy('class_section_name', 'ASC')
|
||||
->whereIn('class_section_id', array_keys($studentCounts))
|
||||
->get()
|
||||
->map(fn ($row) => $row->toArray())
|
||||
->all();
|
||||
|
||||
$grades = [];
|
||||
foreach ($classSections as $section) {
|
||||
$grades[] = [
|
||||
'id' => $section['class_section_id'],
|
||||
'name' => $section['class_section_name'],
|
||||
];
|
||||
}
|
||||
|
||||
return $grades;
|
||||
}
|
||||
|
||||
public function gradeNameMap(array $gradeValues): array
|
||||
{
|
||||
$gradeIds = [];
|
||||
foreach ($gradeValues as $gradeValue) {
|
||||
if (is_numeric($gradeValue)) {
|
||||
$gradeIds[(int) $gradeValue] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($gradeIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = ClassSection::query()
|
||||
->select('class_section_id', 'class_section_name')
|
||||
->whereIn('class_section_id', array_keys($gradeIds))
|
||||
->get()
|
||||
->map(fn ($row) => $row->toArray())
|
||||
->all();
|
||||
|
||||
$gradeMap = [];
|
||||
foreach ($rows as $row) {
|
||||
$gradeMap[(int) $row['class_section_id']] = (string) ($row['class_section_name'] ?? '');
|
||||
}
|
||||
|
||||
return $gradeMap;
|
||||
}
|
||||
|
||||
public function updaterNameMap(array $userIds): array
|
||||
{
|
||||
$userIds = array_values(array_filter(array_unique(array_map('intval', $userIds))));
|
||||
if (empty($userIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = DB::table('users')
|
||||
->select('id, firstname, lastname')
|
||||
->whereIn('id', $userIds)
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$name = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
||||
$map[(int) $row['id']] = $name !== '' ? $name : ('User #' . $row['id']);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user