Files
alrahma_sunday_school_api/app/Services/AttendanceTracking/AttendanceViolationStudentResolverService.php
T
2026-03-08 16:33:24 -04:00

371 lines
12 KiB
PHP

<?php
namespace App\Services\AttendanceTracking;
use App\Models\AttendanceData;
use App\Models\Student;
use App\Models\StudentClass;
use Illuminate\Support\Facades\DB;
class AttendanceViolationStudentResolverService
{
public function __construct(
protected Student $studentModel,
protected StudentClass $studentClassModel,
protected AttendanceData $attendanceDataModel
) {
}
public function resolveForSchoolYear(string $schoolYear, ?string $semester = null): array
{
$debug = [
'class_students' => 0,
'student_ids' => 0,
];
$classStudentsQuery = $this->studentClassModel->query();
if (method_exists($this->studentClassModel, 'scopeActive')) {
$classStudentsQuery->active();
}
$classStudents = $classStudentsQuery
->where('school_year', $schoolYear)
->get()
->toArray();
$debug['class_students'] = count($classStudents);
if (empty($classStudents)) {
$latestTerm = DB::table('student_class')
->select('school_year', 'semester')
->orderByDesc('id')
->first();
if ($latestTerm && !empty($latestTerm->school_year)) {
$schoolYear = (string) $latestTerm->school_year;
if (!empty($latestTerm->semester)) {
$semester = (string) $latestTerm->semester;
}
$classStudentsQuery = $this->studentClassModel->query();
if (method_exists($this->studentClassModel, 'scopeActive')) {
$classStudentsQuery->active();
}
$classStudents = $classStudentsQuery
->where('school_year', $schoolYear)
->get()
->toArray();
$debug['class_students'] = count($classStudents);
}
}
[$studentIds, $studentCodes] = $this->extractStudentIdentifiers($classStudents);
if (empty($studentIds) && empty($studentCodes)) {
[$studentIds, $studentCodes] = $this->deriveIdentifiersFromAttendanceData($schoolYear);
}
[$studentIds, $studentCodes] = $this->filterInactiveIdentifiers($studentIds, $studentCodes);
$debug['student_ids'] = count($studentIds) + count($studentCodes);
if (empty($studentIds) && empty($studentCodes)) {
return [
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => [],
'student_codes' => [],
'students' => [],
'student_code_to_id'=> [],
'existing_ids' => [],
'debug' => $debug,
];
}
$students = $this->loadActiveStudents($studentIds, $studentCodes);
$students = $this->addPlaceholderStudentsForMissingCodes($students, $studentCodes);
$students = $this->ensureEntriesForAllIdentifiers($students, $studentIds, $studentCodes);
[$studentCodeToId, $existingIds] = $this->buildLookupMaps($students);
return [
'school_year' => $schoolYear,
'semester' => $semester,
'student_ids' => $studentIds,
'student_codes' => $studentCodes,
'students' => $students,
'student_code_to_id' => $studentCodeToId,
'existing_ids' => $existingIds,
'debug' => $debug,
];
}
public function ensureAttendanceStudentExists(
array &$students,
array &$studentCodeToId,
array &$existingIds,
mixed $rawStudentId
): ?int {
if (is_numeric($rawStudentId)) {
$sid = (int) $rawStudentId;
if ($sid <= 0) {
return null;
}
if (!isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'school_id' => (string) $sid,
'firstname' => (string) $sid,
'lastname' => '',
'parent_id' => 0,
];
$existingIds[$sid] = true;
}
return $sid;
}
if (is_string($rawStudentId)) {
$code = trim($rawStudentId);
if ($code === '') {
return null;
}
$sid = $studentCodeToId[$code] ?? null;
if ($sid === null) {
$sid = abs(crc32($code)) ?: random_int(3000000, 3999999);
$studentCodeToId[$code] = $sid;
if (!isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
];
$existingIds[$sid] = true;
}
}
return $sid;
}
return null;
}
protected function extractStudentIdentifiers(array $classStudents): array
{
$studentIds = [];
$studentCodes = [];
foreach ($classStudents as $row) {
$raw = $row['student_id'] ?? null;
if (is_numeric($raw)) {
$studentIds[] = (int) $raw;
} elseif (is_string($raw) && $raw !== '') {
$studentCodes[] = trim($raw);
}
}
$studentIds = array_values(array_unique(array_filter($studentIds, fn ($v) => $v > 0)));
$studentCodes = array_values(array_unique(array_filter($studentCodes, fn ($v) => $v !== '')));
return [$studentIds, $studentCodes];
}
protected function deriveIdentifiersFromAttendanceData(string $schoolYear): array
{
$studentIds = [];
$studentCodes = [];
$attendanceStudents = DB::table($this->attendanceDataModel->getTable())
->selectRaw('DISTINCT student_id')
->when(!empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear))
->orderBy('student_id')
->get();
foreach ($attendanceStudents as $r) {
$raw = $r->student_id ?? null;
if (is_numeric($raw)) {
$studentIds[] = (int) $raw;
} elseif (is_string($raw) && $raw !== '') {
$studentCodes[] = trim($raw);
}
}
$studentIds = array_values(array_unique(array_filter($studentIds, fn ($v) => $v > 0)));
$studentCodes = array_values(array_unique(array_filter($studentCodes, fn ($v) => $v !== '')));
return [$studentIds, $studentCodes];
}
protected function filterInactiveIdentifiers(array $studentIds, array $studentCodes): array
{
if (!empty($studentIds)) {
$inactiveRows = $this->studentModel->query()
->select('id')
->whereIn('id', $studentIds)
->where('is_active', 0)
->get()
->toArray();
$inactiveIdSet = array_flip(array_map(
static fn ($r) => (int) ($r['id'] ?? 0),
$inactiveRows
));
$studentIds = array_values(array_filter(
$studentIds,
static fn ($id) => !isset($inactiveIdSet[$id])
));
}
if (!empty($studentCodes)) {
$inactiveCodeRows = $this->studentModel->query()
->select('school_id')
->whereIn('school_id', $studentCodes)
->where('is_active', 0)
->get()
->toArray();
$inactiveCodeSet = array_flip(array_map(
static fn ($r) => (string) ($r['school_id'] ?? ''),
$inactiveCodeRows
));
$studentCodes = array_values(array_filter(
$studentCodes,
static fn ($code) => $code !== '' && !isset($inactiveCodeSet[$code])
));
}
return [$studentIds, $studentCodes];
}
protected function loadActiveStudents(array $studentIds, array $studentCodes): array
{
$students = [];
if (!empty($studentIds)) {
$students = $this->studentModel->query()
->whereIn('id', $studentIds)
->where('is_active', 1)
->get()
->toArray();
}
if (!empty($studentCodes)) {
$byCode = $this->studentModel->query()
->whereIn('school_id', $studentCodes)
->where('is_active', 1)
->get()
->toArray();
$seen = array_flip(array_map(fn ($s) => (int) ($s['id'] ?? 0), $students));
foreach ($byCode as $s) {
$sid = (int) ($s['id'] ?? 0);
if ($sid > 0 && !isset($seen[$sid])) {
$students[] = $s;
$seen[$sid] = true;
}
}
}
return $students;
}
protected function addPlaceholderStudentsForMissingCodes(array $students, array $studentCodes): array
{
$knownCodes = [];
foreach ($students as $s) {
$code = trim((string) ($s['school_id'] ?? ''));
if ($code !== '') {
$knownCodes[$code] = true;
}
}
foreach ($studentCodes as $code) {
if (!isset($knownCodes[$code])) {
$virtualId = abs(crc32($code)) ?: random_int(1000000, 1999999);
$students[] = [
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
}
}
return $students;
}
protected function ensureEntriesForAllIdentifiers(array $students, array $studentIds, array $studentCodes): array
{
[$studentCodeToId, $existingIds] = $this->buildLookupMaps($students);
foreach (array_merge($studentIds, $studentCodes) as $sidOrCode) {
if (is_numeric($sidOrCode)) {
$sid = (int) $sidOrCode;
if ($sid > 0 && !isset($existingIds[$sid])) {
$students[] = [
'id' => $sid,
'school_id' => (string) $sid,
'firstname' => (string) $sid,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
$existingIds[$sid] = true;
}
} else {
$code = (string) $sidOrCode;
if ($code !== '' && !isset($studentCodeToId[$code])) {
$virtualId = abs(crc32($code)) ?: random_int(2000000, 2999999);
$students[] = [
'id' => $virtualId,
'school_id' => $code,
'firstname' => $code,
'lastname' => '',
'parent_id' => 0,
'class_name' => '',
];
$studentCodeToId[$code] = $virtualId;
$existingIds[$virtualId] = true;
}
}
}
return $students;
}
protected function buildLookupMaps(array $students): array
{
$studentCodeToId = [];
foreach ($students as $stu) {
$code = trim((string) ($stu['student_code'] ?? $stu['school_id'] ?? ''));
if ($code !== '') {
$studentCodeToId[$code] = (int) ($stu['id'] ?? 0);
}
}
$existingIds = array_flip(array_map(
fn ($s) => (int) ($s['id'] ?? 0),
$students
));
return [$studentCodeToId, $existingIds];
}
}