Files
alrahma_sunday_school_api/app/Services/Attendance/TeacherAttendanceSubmissionService.php
T

221 lines
8.0 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 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;
$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,
];
if (!$this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)) {
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);
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.');
}
}
$isTeacher = $this->attendancePolicyService->isTeacher($currentUser['roles']);
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';
DB::table('staff_attendance')->updateOrInsert(
[
'user_id' => $teacherId,
'date' => $attendDate,
'semester' => $semester,
'school_year' => $schoolYear,
],
[
'role_name' => 'Teacher',
'class_section_id' => $classSectionId,
'position' => $position,
'status' => strtolower((string)($teachersData[$teacherId]['status'] ?? 'present')),
'reason' => ($teachersData[$teacherId]['reason'] ?? null) ?: null,
'updated_at' => now(),
'created_at' => now(),
]
);
}
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) {
$lockInfo = $detectExternalSubmission($existing);
if (($lockInfo['locked'] ?? false) === true) {
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.',
];
}
}