reconstruction of the project
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\AttendanceTracking;
|
||||
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceTracking;
|
||||
use App\Models\Student;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
class AttendanceCaseQueryService
|
||||
{
|
||||
public function __construct(
|
||||
protected Student $studentModel,
|
||||
protected AttendanceData $attendanceDataModel,
|
||||
protected AttendanceTracking $attendanceTrackingModel,
|
||||
protected ViolationRuleEngineService $violationRuleEngine,
|
||||
protected AttendanceParentLookupService $parentLookupService
|
||||
) {
|
||||
}
|
||||
|
||||
public function getStudentCase(
|
||||
int $studentId,
|
||||
?string $codeParam = null,
|
||||
?string $incidentDate = null,
|
||||
bool $includeNotified = false,
|
||||
bool $pendingOnly = false,
|
||||
?string $schoolYearFilter = null,
|
||||
?string $semesterFilter = null,
|
||||
?string $defaultSchoolYear = null,
|
||||
?string $defaultSemester = null
|
||||
): array {
|
||||
$student = $this->studentModel->query()->find($studentId);
|
||||
if (!$student) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Student not found',
|
||||
'status' => 404,
|
||||
];
|
||||
}
|
||||
|
||||
$student = $student->toArray();
|
||||
$student['name'] = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));
|
||||
|
||||
$codeParam = strtoupper((string) ($codeParam ?? ''));
|
||||
$incidentDate = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $incidentDate) ? (string) $incidentDate : '';
|
||||
|
||||
$statusFilter = ['absent', 'late'];
|
||||
if (str_starts_with($codeParam, 'ABS')) {
|
||||
$statusFilter = ['absent'];
|
||||
} elseif (str_starts_with($codeParam, 'LATE')) {
|
||||
$statusFilter = ['late'];
|
||||
} elseif (str_starts_with($codeParam, 'MIX')) {
|
||||
$statusFilter = ['absent', 'late'];
|
||||
}
|
||||
|
||||
$schoolYear = $schoolYearFilter
|
||||
?? ($incidentDate !== ''
|
||||
? $this->violationRuleEngine->schoolYearForDate($incidentDate, $defaultSchoolYear)
|
||||
: (string) $defaultSchoolYear);
|
||||
|
||||
$semester = $semesterFilter;
|
||||
if ($semester === null && $codeParam === '' && $incidentDate === '') {
|
||||
$semester = (string) $defaultSemester;
|
||||
}
|
||||
|
||||
[$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: date('Y'));
|
||||
$today = Carbon::today();
|
||||
$start = Carbon::parse($syStart)->startOfDay();
|
||||
$rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay();
|
||||
|
||||
$windowWeeks = $this->violationRuleEngine->windowWeeksForViolationCode($codeParam);
|
||||
$endYmd = $incidentDate !== '' ? $incidentDate : date('Y-m-d');
|
||||
$startYmd = $this->violationRuleEngine->activeWeekWindowStartYmd($endYmd, $windowWeeks, $schoolYear, $semester);
|
||||
|
||||
$studentIdValues = [(int) $studentId];
|
||||
$studentCodeRaw = trim((string) ($student['student_code'] ?? $student['school_id'] ?? ''));
|
||||
$studentCodeValues = [];
|
||||
|
||||
if ($studentCodeRaw !== '') {
|
||||
if (is_numeric($studentCodeRaw)) {
|
||||
$studentCodeNum = (int) $studentCodeRaw;
|
||||
if ($studentCodeNum > 0 && $studentCodeNum !== (int) $studentId) {
|
||||
$studentIdValues[] = $studentCodeNum;
|
||||
}
|
||||
} else {
|
||||
$studentCodeValues[] = $studentCodeRaw;
|
||||
}
|
||||
}
|
||||
|
||||
$studentIdValues = array_values(array_unique(array_filter($studentIdValues, fn ($v) => $v > 0)));
|
||||
$studentCodeValues = array_values(array_unique(array_filter($studentCodeValues, fn ($v) => $v !== '')));
|
||||
|
||||
$qb = $this->attendanceDataModel->query()
|
||||
->select([
|
||||
'id',
|
||||
'student_id',
|
||||
'class_id',
|
||||
'class_section_id',
|
||||
'date',
|
||||
'status',
|
||||
'reason',
|
||||
'school_year',
|
||||
'semester',
|
||||
'is_reported',
|
||||
'is_notified',
|
||||
])
|
||||
->where(function ($q) use ($studentIdValues, $studentCodeValues) {
|
||||
$q->whereIn('student_id', $studentIdValues);
|
||||
if (!empty($studentCodeValues)) {
|
||||
$q->orWhereIn('student_id', $studentCodeValues);
|
||||
}
|
||||
})
|
||||
->where('date', '>=', max($start->format('Y-m-d'), $startYmd))
|
||||
->where('date', '<', $this->violationRuleEngine->dayBounds($endYmd)[1]);
|
||||
|
||||
if ($statusFilter === ['absent']) {
|
||||
$qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) = 'a')");
|
||||
} elseif ($statusFilter === ['late']) {
|
||||
$qb->whereRaw("(LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) = 'l')");
|
||||
} else {
|
||||
$qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) IN ('a','l'))");
|
||||
}
|
||||
|
||||
if ($pendingOnly) {
|
||||
$this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable());
|
||||
|
||||
if (!$includeNotified) {
|
||||
$qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))");
|
||||
}
|
||||
}
|
||||
|
||||
if ($schoolYearFilter !== null) {
|
||||
$qb->where('school_year', $schoolYearFilter);
|
||||
}
|
||||
|
||||
if ($semesterFilter !== null) {
|
||||
$qb->where('semester', $semesterFilter);
|
||||
}
|
||||
|
||||
$attendanceRows = $qb->orderByDesc('date')->get()->toArray();
|
||||
|
||||
$data = [
|
||||
'student' => $student,
|
||||
'attendance' => $attendanceRows,
|
||||
'violation_code' => $codeParam !== '' ? $codeParam : null,
|
||||
'violation_date' => $incidentDate,
|
||||
'violation_notified' => null,
|
||||
'show_violation_summary' => true,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
];
|
||||
|
||||
if ($data['violation_date'] === '' && !empty($data['attendance'][0]['date'])) {
|
||||
$data['violation_date'] = substr((string) $data['attendance'][0]['date'], 0, 10);
|
||||
}
|
||||
|
||||
if (!empty($data['attendance'][0]['is_notified'])) {
|
||||
$data['violation_notified'] = $data['attendance'][0]['is_notified'];
|
||||
}
|
||||
|
||||
if ($data['violation_date'] !== '') {
|
||||
foreach ($attendanceRows as $row) {
|
||||
$ymd = substr((string) ($row['date'] ?? ''), 0, 10);
|
||||
if ($ymd === $data['violation_date']) {
|
||||
$data['show_violation_summary'] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
public function getNotifiedViolations(
|
||||
?string $schoolYearParam = null,
|
||||
?string $semesterParam = null,
|
||||
?string $defaultSchoolYear = null,
|
||||
?string $defaultSemester = null
|
||||
): array {
|
||||
$schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear;
|
||||
$semester = filled($semesterParam) ? (string) $semesterParam : (string) $defaultSemester;
|
||||
|
||||
$trackingData = $this->attendanceTrackingModel->query()
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->where(function ($q) {
|
||||
$q->where('is_notified', 1)
|
||||
->orWhere('is_notified', '1')
|
||||
->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
|
||||
})
|
||||
->orderByDesc('date')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($trackingData)) {
|
||||
$latest = $this->attendanceTrackingModel->query()
|
||||
->select(['school_year', 'semester'])
|
||||
->where(function ($q) {
|
||||
$q->where('is_notified', 1)
|
||||
->orWhere('is_notified', '1')
|
||||
->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
|
||||
})
|
||||
->orderByDesc('date')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
if ($latest) {
|
||||
$schoolYear = (string) ($latest->school_year ?? $schoolYear);
|
||||
$semester = (string) ($latest->semester ?? $semester);
|
||||
|
||||
$trackingData = $this->attendanceTrackingModel->query()
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->where(function ($q) {
|
||||
$q->where('is_notified', 1)
|
||||
->orWhere('is_notified', '1')
|
||||
->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'");
|
||||
})
|
||||
->orderByDesc('date')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($trackingData)) {
|
||||
return [
|
||||
'students' => [],
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'show_actions' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$studentIds = array_unique(array_map(fn ($r) => (int) ($r['student_id'] ?? 0), $trackingData));
|
||||
|
||||
$students = $this->studentModel->query()
|
||||
->whereIn('id', $studentIds)
|
||||
->where('is_active', 1)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$activeIdSet = array_flip(array_map(
|
||||
static fn ($row) => (int) ($row['id'] ?? 0),
|
||||
$students
|
||||
));
|
||||
|
||||
$trackingData = array_values(array_filter(
|
||||
$trackingData,
|
||||
static fn ($row) => isset($activeIdSet[(int) ($row['student_id'] ?? 0)])
|
||||
));
|
||||
|
||||
$studentIds = array_keys($activeIdSet);
|
||||
|
||||
if (empty($trackingData)) {
|
||||
return [
|
||||
'students' => [],
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'show_actions' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$studentMap = [];
|
||||
foreach ($students as $s) {
|
||||
$studentMap[$s['id']] = $s;
|
||||
}
|
||||
|
||||
$classByStudent = [];
|
||||
try {
|
||||
$rows = DB::table('student_class as sc')
|
||||
->select([
|
||||
'sc.student_id',
|
||||
'sc.class_section_id',
|
||||
'cs.class_section_name',
|
||||
'cs.class_id',
|
||||
'c.class_name',
|
||||
])
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
|
||||
->whereIn('sc.student_id', $studentIds)
|
||||
->where('sc.school_year', $schoolYear)
|
||||
->orderByDesc('sc.id')
|
||||
->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$sid = (int) ($row->student_id ?? 0);
|
||||
if ($sid > 0 && !isset($classByStudent[$sid])) {
|
||||
$classByStudent[$sid] = [
|
||||
'class_section_id' => $row->class_section_id ?? null,
|
||||
'class_section_name' => (string) ($row->class_section_name ?? ''),
|
||||
'class_id' => $row->class_id ?? null,
|
||||
'class_name' => (string) ($row->class_name ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
$mergedData = [];
|
||||
foreach ($trackingData as $row) {
|
||||
$sid = (int) ($row['student_id'] ?? 0);
|
||||
if (!isset($studentMap[$sid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$merged = array_merge($studentMap[$sid], $row);
|
||||
|
||||
if (isset($classByStudent[$sid])) {
|
||||
$merged['class_section_name'] = (string) ($classByStudent[$sid]['class_section_name'] ?? '');
|
||||
$merged['class_name'] = (string) ($classByStudent[$sid]['class_name'] ?? '');
|
||||
} elseif (!empty($merged['registration_grade'])) {
|
||||
$merged['grade'] = (string) $merged['registration_grade'];
|
||||
}
|
||||
|
||||
try {
|
||||
$p = $this->parentLookupService->getPrimaryParentForStudent($sid);
|
||||
if ($p) {
|
||||
$merged['parent_name'] = $p['parent_name'] ?? '';
|
||||
$merged['parent_email'] = $p['email'] ?? '';
|
||||
$merged['parent_phone'] = $p['phone'] ?? '';
|
||||
}
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
$mergedData[] = $merged;
|
||||
}
|
||||
|
||||
return [
|
||||
'students' => $mergedData,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user