Files
alrahma_sunday_school_api/app/Services/Attendance/TeacherAttendanceSubmissionService.php
root c891a82012
API CI/CD / Validate (composer + pint) (push) Successful in 3m10s
API CI/CD / Test (PHPUnit) (push) Failing after 5m2s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
fix teacher attendance
2026-07-07 21:51:51 -04:00

252 lines
9.1 KiB
PHP

<?php
namespace App\Services\Attendance;
use App\Models\AttendanceData;
use App\Models\AttendanceDay;
use App\Models\Student;
use App\Models\TeacherClass;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use RuntimeException;
class TeacherAttendanceSubmissionService
{
public function __construct(
protected AttendanceDay $attendanceDay,
protected AttendanceData $attendanceData,
protected TeacherClass $teacherClass,
protected Student $student,
protected AttendancePolicyService $attendancePolicyService,
protected StudentAttendanceWriterService $studentAttendanceWriterService,
protected AttendanceAutoPublishService $attendanceAutoPublishService,
) {}
public function submit(
Authenticatable $user,
array $data,
callable $detectExternalSubmission,
callable $currentSemester,
callable $currentSchoolYear
): array {
$rawSection = trim((string) $data['class_section_id']);
if ($rawSection === '') {
throw new RuntimeException('Missing or invalid class section.');
}
$sectionRow = DB::table('classSection')
->select('id', 'class_id', 'class_section_id')
->where('class_section_id', $rawSection)
->first();
if (! $sectionRow && ctype_digit($rawSection)) {
$sectionRow = DB::table('classSection')
->select('id', 'class_id', 'class_section_id')
->where('id', (int) $rawSection)
->first();
}
if (! $sectionRow) {
throw new RuntimeException('Invalid class section.');
}
$classSectionId = (int) $sectionRow->class_section_id;
$classSectionPk = (int) ($sectionRow->id ?? 0);
$alsoSectionId = ($classSectionPk > 0 && $classSectionPk !== $classSectionId) ? $classSectionPk : null;
$classId = (int) $sectionRow->class_id;
$attendanceData = $data['attendance'] ?? [];
$teachersData = $data['teachers'] ?? [];
if (empty($attendanceData) || ! is_array($attendanceData)) {
throw new RuntimeException('No student attendance data provided.');
}
if (empty($teachersData) || ! is_array($teachersData)) {
throw new RuntimeException('No teacher attendance data provided.');
}
$rawDate = $data['date'] ?? now()->toDateString();
$attendDate = Carbon::parse($rawDate)->toDateString();
$semester = (string) $currentSemester();
$schoolYear = (string) $currentSchoolYear();
$dayRow = AttendanceDay::query()
->where('class_section_id', $classSectionId)
->whereDate('date', $attendDate)
->where('semester', $semester)
->where('school_year', $schoolYear)
->first();
$currentUser = [
'id' => $user->id,
'roles' => method_exists($user, 'roles') ? $user->roles->pluck('name')->all() : [],
'permissions' => method_exists($user, 'permissions') ? $user->permissions->pluck('name')->all() : [],
];
$dayPolicyRow = $dayRow?->toArray() ?? [
'status' => 'draft',
'class_section_id' => $classSectionId,
'date' => $attendDate,
'semester' => $semester,
'school_year' => $schoolYear,
];
$isTeacher = $this->attendancePolicyService->isTeacher($currentUser['roles']);
$dayStatus = strtolower((string) ($dayPolicyRow['status'] ?? 'draft'));
if (! $this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)
&& ! ($isTeacher && $dayStatus === 'submitted')) {
throw new RuntimeException('Attendance is locked for your role.');
}
$existingRows = AttendanceData::query()
->where('class_section_id', $classSectionId)
->whereDate('date', $attendDate)
->where('semester', $semester)
->where('school_year', $schoolYear)
->get();
$existingByStudent = [];
foreach ($existingRows as $row) {
$existingByStudent[(int) $row->student_id] = $row->toArray();
}
$allowedStatuses = ['present', 'absent', 'late'];
$assignedTeachers = $this->teacherClass->assignedForSectionTerm(
$classSectionId,
$semester,
$schoolYear,
$alsoSectionId,
true,
);
if ($assignedTeachers === []) {
$assignedTeachers = $this->teacherClass->assignedForSectionTerm(
$classSectionId,
$semester,
$schoolYear,
$alsoSectionId,
false,
);
}
foreach ($assignedTeachers as $teacher) {
$teacherId = (int) $teacher['teacher_id'];
$status = strtolower((string) ($teachersData[$teacherId]['status'] ?? ''));
if (! in_array($status, $allowedStatuses, true)) {
throw new RuntimeException('Please fill teacher attendance for all assigned teachers.');
}
}
DB::transaction(function () use (
$user,
$assignedTeachers,
$teachersData,
$attendanceData,
$existingByStudent,
$classSectionId,
$classId,
$attendDate,
$semester,
$schoolYear,
$isTeacher,
$detectExternalSubmission
) {
$dayRow = AttendanceDay::query()->firstOrCreate(
[
'class_section_id' => $classSectionId,
'date' => $attendDate,
'semester' => $semester,
'school_year' => $schoolYear,
],
[
'status' => 'draft',
'created_at' => now(),
'updated_at' => now(),
]
);
foreach ($assignedTeachers as $teacher) {
$teacherId = (int) $teacher['teacher_id'];
$position = strtolower((string) ($teacher['position'] ?? 'main'));
$position = $position === 'ta' ? 'ta' : 'main';
if ($isTeacher && DB::table('staff_attendance')
->where('user_id', $teacherId)
->whereDate('date', $attendDate)
->where('semester', $semester)
->where('school_year', $schoolYear)
->exists()) {
continue;
}
$staffPayload = [
'role_name' => 'Teacher',
'status' => strtolower((string) ($teachersData[$teacherId]['status'] ?? 'present')),
'reason' => ($teachersData[$teacherId]['reason'] ?? null) ?: null,
'updated_at' => now(),
'created_at' => now(),
];
if (Schema::hasColumn('staff_attendance', 'class_section_id')) {
$staffPayload['class_section_id'] = $classSectionId;
}
if (Schema::hasColumn('staff_attendance', 'position')) {
$staffPayload['position'] = $position;
}
DB::table('staff_attendance')->updateOrInsert(
[
'user_id' => $teacherId,
'date' => $attendDate,
'semester' => $semester,
'school_year' => $schoolYear,
],
$staffPayload
);
}
foreach ($attendanceData as $row) {
$studentId = (int) ($row['student_id'] ?? 0);
$status = strtolower(trim((string) ($row['status'] ?? '')));
$reason = trim((string) ($row['reason'] ?? ''));
$existing = $existingByStudent[$studentId] ?? null;
if ($isTeacher && $existing) {
continue;
}
$this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([
'class_section_id' => $classSectionId,
'student_id' => $studentId,
'school_id' => $row['school_id'] ?? null,
'status' => $status,
'reason' => $reason,
'is_reported' => 'no',
'semester' => $semester,
'school_year' => $schoolYear,
'date' => $attendDate,
'class_id' => $classId,
'user_id' => $user->id,
]);
}
$autoPublishAt = $this->attendanceAutoPublishService->secondSundayAfterEndOfDay($attendDate);
$dayRow->update([
'status' => 'submitted',
'submitted_by' => $user->id,
'submitted_at' => now(),
'auto_publish_at' => $autoPublishAt,
'updated_at' => now(),
]);
});
return [
'ok' => true,
'message' => 'Attendance submitted successfully.',
];
}
}