275 lines
9.3 KiB
PHP
275 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Attendance;
|
|
|
|
use App\Models\AttendanceData;
|
|
use App\Models\AttendanceDay;
|
|
use App\Models\ClassSection;
|
|
use App\Models\Configuration;
|
|
use App\Models\UserRole;
|
|
use Illuminate\Contracts\Auth\Authenticatable;
|
|
use Illuminate\Support\Facades\DB;
|
|
use RuntimeException;
|
|
|
|
class AttendanceService
|
|
{
|
|
public function __construct(
|
|
protected Configuration $configuration,
|
|
protected AttendanceData $attendanceData,
|
|
protected AttendanceDay $attendanceDay,
|
|
protected ClassSection $classSection,
|
|
protected UserRole $userRole,
|
|
protected AttendancePolicyService $attendancePolicyService,
|
|
protected StudentAttendanceWriterService $studentAttendanceWriterService,
|
|
protected TeacherAttendanceSubmissionService $teacherAttendanceSubmissionService,
|
|
protected AttendanceRecordSyncService $attendanceRecordSyncService,
|
|
) {}
|
|
|
|
public function currentSemester(): string
|
|
{
|
|
return (string)($this->configuration->getConfig('semester') ?? 'Fall');
|
|
}
|
|
|
|
public function currentSchoolYear(): string
|
|
{
|
|
return (string)($this->configuration->getConfig('school_year') ?? now()->year . '-' . (now()->year + 1));
|
|
}
|
|
|
|
public function updateAttendanceManagement(Authenticatable $user, array $data): array
|
|
{
|
|
$classSectionId = (int)$data['class_section_id'];
|
|
$studentId = (int)$data['student_id'];
|
|
$status = strtolower((string)$data['status']);
|
|
$date = (string)$data['date'];
|
|
$reason = $data['reason'] ?? null;
|
|
$classId = $data['class_id'] ?? null;
|
|
$semester = (string)($data['semester'] ?? $this->currentSemester());
|
|
$schoolYear = (string)($data['school_year'] ?? $this->currentSchoolYear());
|
|
|
|
$dayRow = AttendanceDay::query()
|
|
->where('class_section_id', $classSectionId)
|
|
->whereDate('date', $date)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->first();
|
|
|
|
$dayPolicyRow = $dayRow?->toArray() ?? [
|
|
'status' => 'draft',
|
|
'class_section_id' => $classSectionId,
|
|
'date' => $date,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
];
|
|
|
|
$currentUser = [
|
|
'id' => $user->id,
|
|
'roles' => method_exists($user, 'roles') ? $user->roles->pluck('name')->all() : [],
|
|
'permissions' => method_exists($user, 'permissions') ? $user->permissions->pluck('name')->all() : [],
|
|
];
|
|
|
|
if (!$this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)) {
|
|
throw new RuntimeException('Attendance is locked for your role.');
|
|
}
|
|
|
|
$reportedRaw = strtolower((string)($data['is_reported'] ?? ''));
|
|
$isReported = $status === 'present'
|
|
? 'no'
|
|
: (in_array($reportedRaw, ['1', 'yes', 'y', 'true', 'on'], true) ? 'yes' : 'no');
|
|
|
|
DB::transaction(function () use (
|
|
$user,
|
|
$classSectionId,
|
|
$studentId,
|
|
$status,
|
|
$date,
|
|
$reason,
|
|
$classId,
|
|
$semester,
|
|
$schoolYear,
|
|
$isReported
|
|
) {
|
|
$this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([
|
|
'class_section_id' => $classSectionId,
|
|
'student_id' => $studentId,
|
|
'school_id' => null,
|
|
'status' => $status,
|
|
'reason' => $reason,
|
|
'is_reported' => $isReported,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
'date' => $date,
|
|
'class_id' => (int)$classId,
|
|
'user_id' => $user->id,
|
|
]);
|
|
|
|
AttendanceDay::query()->firstOrCreate(
|
|
[
|
|
'class_section_id' => $classSectionId,
|
|
'date' => $date,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
],
|
|
[
|
|
'status' => 'draft',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
});
|
|
|
|
return [
|
|
'ok' => true,
|
|
'message' => 'Attendance updated successfully.',
|
|
];
|
|
}
|
|
|
|
public function submitTeacherAttendance(Authenticatable $user, array $data): array
|
|
{
|
|
return $this->teacherAttendanceSubmissionService->submit(
|
|
$user,
|
|
$data,
|
|
fn (?array $row) => $this->detectExternalSubmission($row),
|
|
fn () => $this->currentSemester(),
|
|
fn () => $this->currentSchoolYear(),
|
|
);
|
|
}
|
|
|
|
public function adminAddEntry(Authenticatable $user, array $data): array
|
|
{
|
|
$classSectionId = (int)$data['class_section_id'];
|
|
$studentId = (int)$data['student_id'];
|
|
$status = strtolower((string)$data['status']);
|
|
$date = (string)($data['date'] ?? now()->toDateString());
|
|
$reason = $data['reason'] ?? null;
|
|
$isReported = in_array(strtolower((string)($data['is_reported'] ?? 'no')), ['yes', 'no'], true)
|
|
? strtolower((string)($data['is_reported'] ?? 'no'))
|
|
: 'no';
|
|
|
|
$section = $this->classSection
|
|
->query()
|
|
->select('class_id', 'id', 'class_section_id')
|
|
->where('id', $classSectionId)
|
|
->orWhere('class_section_id', $classSectionId)
|
|
->first();
|
|
|
|
$resolvedClassId = (int)($section->class_id ?? 0);
|
|
|
|
if ($resolvedClassId <= 0) {
|
|
throw new RuntimeException('Cannot resolve class_id for this section.');
|
|
}
|
|
|
|
DB::transaction(function () use (
|
|
$user,
|
|
$classSectionId,
|
|
$studentId,
|
|
$status,
|
|
$date,
|
|
$reason,
|
|
$resolvedClassId,
|
|
$isReported
|
|
) {
|
|
AttendanceDay::query()->firstOrCreate(
|
|
[
|
|
'class_section_id' => $classSectionId,
|
|
'date' => $date,
|
|
'semester' => $this->currentSemester(),
|
|
'school_year' => $this->currentSchoolYear(),
|
|
],
|
|
[
|
|
'status' => 'draft',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
|
|
$this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([
|
|
'class_section_id' => $classSectionId,
|
|
'student_id' => $studentId,
|
|
'school_id' => $data['school_id'] ?? null,
|
|
'status' => $status,
|
|
'reason' => $reason,
|
|
'is_reported' => $isReported,
|
|
'semester' => $this->currentSemester(),
|
|
'school_year' => $this->currentSchoolYear(),
|
|
'date' => $date,
|
|
'class_id' => $resolvedClassId,
|
|
'user_id' => $user->id,
|
|
]);
|
|
});
|
|
|
|
return [
|
|
'ok' => true,
|
|
'message' => 'Entry saved successfully.',
|
|
];
|
|
}
|
|
|
|
public function normalizeAttendanceEntries(array $entries): array
|
|
{
|
|
foreach ($entries as &$row) {
|
|
$row['status'] = $this->normalizeStatus($row['status'] ?? '');
|
|
$reportedRaw = $row['is_reported'] ?? ($row['reported'] ?? '');
|
|
$row['is_reported'] = $this->normalizeReportedFlag($reportedRaw);
|
|
}
|
|
return $entries;
|
|
}
|
|
|
|
public function normalizeStatus(?string $status): string
|
|
{
|
|
$status = strtolower(trim((string)$status));
|
|
return in_array($status, ['present', 'absent', 'late'], true) ? $status : $status;
|
|
}
|
|
|
|
public function normalizeReportedFlag(mixed $flag): string
|
|
{
|
|
$v = strtolower(trim((string)$flag));
|
|
return in_array($v, ['yes', '1', 'true', 'y', 'on'], true) ? 'yes' : 'no';
|
|
}
|
|
|
|
public function detectExternalSubmission(?array $row): array
|
|
{
|
|
if (empty($row) || !is_array($row)) {
|
|
return ['locked' => false, 'source' => null];
|
|
}
|
|
|
|
$reportedRaw = strtolower((string)($row['is_reported'] ?? ''));
|
|
$reason = strtolower((string)($row['reason'] ?? ''));
|
|
|
|
$isParent = in_array($reportedRaw, ['yes', '1', 'true', 'y'], true)
|
|
|| str_contains($reason, 'parent-reported')
|
|
|| str_contains($reason, 'parent reported');
|
|
|
|
if ($isParent) {
|
|
return ['locked' => true, 'source' => 'parent'];
|
|
}
|
|
|
|
$modifierId = (int)($row['modified_by'] ?? 0);
|
|
if ($modifierId > 0 && $this->isAdminLikeUserId($modifierId)) {
|
|
return ['locked' => true, 'source' => 'admin'];
|
|
}
|
|
|
|
return ['locked' => false, 'source' => null];
|
|
}
|
|
|
|
public function isAdminLikeUserId(int $userId): bool
|
|
{
|
|
if ($userId <= 0) {
|
|
return false;
|
|
}
|
|
|
|
$roles = $this->userRole->getRolesByUserId($userId);
|
|
if (empty($roles)) {
|
|
return false;
|
|
}
|
|
|
|
$excluded = ['parent', 'guest', 'teacher', 'teacher_assistant', 'assistant_teacher', 'ta'];
|
|
|
|
foreach ($roles as $role) {
|
|
$name = strtolower(str_replace([' ', '-'], '_', (string)($role['role_name'] ?? '')));
|
|
if ($name !== '' && !in_array($name, $excluded, true)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} |