245 lines
8.8 KiB
PHP
245 lines
8.8 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,
|
|
];
|
|
|
|
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,
|
|
$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.');
|
|
}
|
|
}
|
|
|
|
$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';
|
|
|
|
$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) {
|
|
$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.',
|
|
];
|
|
}
|
|
}
|