Files
2026-03-08 16:33:24 -04:00

257 lines
9.6 KiB
PHP

<?php
namespace App\Services\AttendanceTracking;
use App\Models\AttendanceData;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Throwable;
class AttendancePendingViolationService
{
public function __construct(
protected AttendanceData $attendanceDataModel,
protected ViolationRuleEngineService $violationRuleEngine,
protected AttendanceViolationStudentResolverService $studentResolverService
) {
}
public function getPendingViolations(
string $defaultSchoolYear,
?string $schoolYearParam = null,
?string $semesterParam = null
): array {
$schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear;
$semester = filled($semesterParam) ? (string) $semesterParam : null;
$debugInfo = [
'school_year_param' => $schoolYear,
'semester_param' => $semester,
'class_students' => 0,
'student_ids' => 0,
'attendance_rows' => 0,
'filtered_rows' => 0,
'violations' => 0,
'active_weeks' => 0,
'abs_last5' => 0,
'late_last5' => 0,
];
$resolved = $this->studentResolverService->resolveForSchoolYear($schoolYear, $semester);
$schoolYear = (string) ($resolved['school_year'] ?? $schoolYear);
$semester = $resolved['semester'] ?? $semester;
$studentIds = $resolved['student_ids'] ?? [];
$studentCodes = $resolved['student_codes'] ?? [];
$students = $resolved['students'] ?? [];
$studentCodeToId = $resolved['student_code_to_id'] ?? [];
$existingIds = $resolved['existing_ids'] ?? [];
$resolverDebug = $resolved['debug'] ?? [];
$debugInfo['class_students'] = $resolverDebug['class_students'] ?? 0;
$debugInfo['student_ids'] = $resolverDebug['student_ids'] ?? 0;
if (empty($studentIds) && empty($studentCodes)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'error' => 'No student identifiers found for the current school year.',
'debug' => $debugInfo,
];
}
if (empty($students)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'error' => 'No students found matching attendance records.',
'debug' => $debugInfo,
];
}
$today = Carbon::today();
[$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear);
$start = Carbon::parse($syStart)->startOfDay();
$rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay();
$endEx = $rangeEnd->copy()->addDay();
$qb = $this->attendanceDataModel->query()
->select([
'id',
'student_id',
'class_id',
'class_section_id',
'date',
'status',
'reason',
'school_year',
'semester',
'is_reported',
'is_notified',
])
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(!empty($semester), fn ($q) => $q->where('semester', $semester))
->where(function ($q) use ($studentIds, $studentCodes) {
if (!empty($studentIds)) {
$q->whereIn('student_id', $studentIds);
}
if (!empty($studentCodes)) {
if (!empty($studentIds)) {
$q->orWhereIn('student_id', $studentCodes);
} else {
$q->whereIn('student_id', $studentCodes);
}
}
});
$this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable());
$qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))")
->whereRaw("LOWER(TRIM(status)) IN ('absent','late')")
->where('date', '>=', $start->format('Y-m-d'))
->where('date', '<', $endEx->format('Y-m-d'));
$termRows = $qb->orderByDesc('date')->get()->toArray();
if (empty($termRows)) {
$latestAttendanceTerm = DB::table($this->attendanceDataModel->getTable())
->select('school_year', 'semester', 'date')
->when(!empty($studentIds), fn ($q) => $q->whereIn('student_id', $studentIds))
->orderByDesc('date')
->first();
if ($latestAttendanceTerm) {
$schoolYear = !empty($latestAttendanceTerm->school_year)
? (string) $latestAttendanceTerm->school_year
: $schoolYear;
$semester = !empty($latestAttendanceTerm->semester)
? (string) $latestAttendanceTerm->semester
: $semester;
[$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: $defaultSchoolYear);
$start = Carbon::parse($syStart)->startOfDay();
$rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay();
$endEx = $rangeEnd->copy()->addDay();
$qb = $this->attendanceDataModel->query()
->select([
'id',
'student_id',
'class_id',
'class_section_id',
'date',
'status',
'reason',
'school_year',
'semester',
'is_reported',
'is_notified',
])
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->when(!empty($semester), fn ($q) => $q->where('semester', $semester))
->where(function ($q) use ($studentIds, $studentCodes) {
if (!empty($studentIds)) {
$q->whereIn('student_id', $studentIds);
}
if (!empty($studentCodes)) {
if (!empty($studentIds)) {
$q->orWhereIn('student_id', $studentCodes);
} else {
$q->whereIn('student_id', $studentCodes);
}
}
});
$this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable());
$qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))")
->whereRaw("LOWER(TRIM(status)) IN ('absent','late')")
->where('date', '>=', $start->format('Y-m-d'))
->where('date', '<', $endEx->format('Y-m-d'));
$termRows = $qb->orderByDesc('date')->get()->toArray();
}
if (empty($termRows)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
];
}
}
$attendanceRows = [];
foreach ($termRows as $row) {
$dStr = $row['date'] ?? null;
if (!$dStr) {
continue;
}
try {
$d = Carbon::parse($dStr);
} catch (Throwable) {
continue;
}
if ($d->lt($start) || !$d->lt($endEx)) {
continue;
}
$sid = $this->studentResolverService->ensureAttendanceStudentExists(
$students,
$studentCodeToId,
$existingIds,
$row['student_id'] ?? null
);
if ($sid === null || $sid <= 0) {
continue;
}
$row['student_id'] = $sid;
$row['status'] = strtolower(trim((string) ($row['status'] ?? '')));
$row['date'] = $d->format('Y-m-d');
$attendanceRows[] = $row;
}
if (empty($attendanceRows)) {
return [
'students' => [],
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
];
}
$debugInfo['attendance_rows'] = count($termRows);
$debugInfo['filtered_rows'] = count($attendanceRows);
$violations = $this->violationRuleEngine->computeViolations(
$students,
$attendanceRows,
$schoolYear,
null,
$attendanceRows
);
$ruleDebug = $this->violationRuleEngine->getDebugCompute();
$debugInfo['violations'] = count($violations);
$debugInfo['active_weeks'] = $ruleDebug['active_weeks'] ?? 0;
$debugInfo['by_student'] = $ruleDebug['by_student'] ?? 0;
$debugInfo['abs_last5'] = $ruleDebug['abs_last5'] ?? 0;
$debugInfo['late_last5'] = $ruleDebug['late_last5'] ?? 0;
return [
'students' => $violations,
'school_year' => $schoolYear,
'semester' => $semester,
'debug' => $debugInfo,
];
}
}