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

415 lines
15 KiB
PHP

<?php
namespace App\Services\Attendance;
use App\Models\Configuration;
use App\Models\StaffAttendance;
use App\Models\TeacherClass;
use App\Models\ClassSection;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;
class StaffAttendanceService
{
public function __construct(
protected Configuration $configuration,
protected StaffAttendance $staffAttendance,
protected TeacherClass $teacherClass,
protected ClassSection $classSection,
protected SemesterRangeService $semesterRangeService,
) {}
public function monthData(?string $semester, ?string $schoolYear): array
{
$semester = $semester ?: (string)$this->configuration->getConfig('semester');
$schoolYear = $schoolYear ?: (string)$this->configuration->getConfig('school_year');
[$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($schoolYear);
$semesterNorm = $this->semesterRangeService->normalizeSemester($semester);
if ($semesterNorm !== '') {
$semesterRange = $this->semesterRangeService->getSemesterRange($schoolYear, $semesterNorm);
if ($semesterRange) {
[$rangeStart, $rangeEnd] = $semesterRange;
}
}
$days = $this->semesterRangeService->buildSundayList($rangeStart, $rangeEnd);
$assignedBySection = $semesterNorm !== ''
? $this->teacherClass->assignedBySectionForTerm($semesterNorm, $schoolYear)
: $this->teacherClass->assignedBySectionForTerm($semester, $schoolYear);
$sectionCodes = array_keys($assignedBySection);
$labels = [];
if (!empty($sectionCodes)) {
$rows = $this->classSection->query()
->select(['class_section_id', 'class_section_name'])
->whereIn('class_section_id', $sectionCodes)
->get()
->toArray();
foreach ($rows as $row) {
$labels[(int)$row['class_section_id']] = trim((string)$row['class_section_name']) ?: 'Section #' . (int)$row['class_section_id'];
}
}
$teacherIds = [];
foreach ($assignedBySection as $rows) {
foreach ($rows as $row) {
$teacherIds[(int)$row['teacher_id']] = true;
}
}
$teacherIds = array_keys($teacherIds);
$statusByTeacher = [];
if (!empty($teacherIds) && !empty($days)) {
$query = DB::table('staff_attendance')
->select('user_id', 'date', 'status', 'reason')
->where('school_year', $schoolYear)
->whereIn('user_id', $teacherIds)
->whereIn('date', $days);
if ($semesterNorm !== '') {
$query->where('semester', $semesterNorm);
}
$rows = $query->get();
foreach ($rows as $row) {
$statusByTeacher[(int)$row->user_id][(string)$row->date] = [
'status' => strtolower((string)$row->status),
'reason' => $row->reason,
];
}
}
$noSchoolRows = DB::table('calendar_events')
->select('date')
->where('school_year', $schoolYear)
->where('no_school', 1)
->where('date', '>=', $rangeStart)
->where('date', '<=', $rangeEnd)
->get();
$noSchoolDays = [];
foreach ($noSchoolRows as $row) {
$noSchoolDays[] = (string)$row->date;
}
$sections = [];
foreach ($sectionCodes as $code) {
$teachersPayload = [];
foreach (($assignedBySection[$code] ?? []) as $row) {
$teacherId = (int)$row['teacher_id'];
$name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? '')) ?: ('User#' . $teacherId);
$position = strtolower((string)($row['position'] ?? 'main'));
$position = in_array($position, ['main', 'ta'], true) ? $position : 'main';
$cells = [];
$totals = ['p' => 0, 'a' => 0, 'l' => 0];
foreach ($days as $date) {
$cell = $statusByTeacher[$teacherId][$date] ?? null;
if (!$cell || empty($cell['status'])) {
continue;
}
$cells[$date] = $cell;
if ($cell['status'] === 'present') {
$totals['p']++;
} elseif ($cell['status'] === 'absent') {
$totals['a']++;
} elseif ($cell['status'] === 'late') {
$totals['l']++;
}
}
$teachersPayload[] = [
'teacher_id' => $teacherId,
'name' => $name,
'position' => $position,
'cells' => $cells,
'totals' => $totals,
];
}
$sections[] = [
'section_code' => (int)$code,
'label' => $labels[$code] ?? ('Section #' . (int)$code),
'teachers' => $teachersPayload,
];
}
return [
'filters' => [
'semester' => $semester,
'school_year' => $schoolYear,
'range_start' => $rangeStart,
'range_end' => $rangeEnd,
],
'sundays' => $days,
'noSchoolDays' => $noSchoolDays,
'sections' => $sections,
'admins' => [],
];
}
public function adminsAttendanceData(?string $date, ?string $semester, ?string $schoolYear): array
{
$date = $date ?: now()->toDateString();
$semester = $semester ?: (string)$this->configuration->getConfig('semester');
$schoolYear = $schoolYear ?: (string)$this->configuration->getConfig('school_year');
$allStaff = $this->staffAttendance->staffWithStatusForDay($date, $semester, $schoolYear, []);
$userIds = [];
foreach ($allStaff as $row) {
$uid = (int)($row['user_id'] ?? 0);
if ($uid > 0) {
$userIds[$uid] = true;
}
}
$userIds = array_keys($userIds);
$rolesRows = DB::table('user_roles as ur')
->select('ur.user_id', 'r.id as role_id', 'r.name as role_name', 'r.slug as role_slug', 'r.priority')
->join('roles as r', 'r.id', '=', 'ur.role_id')
->where('r.is_active', 1)
->whereIn('ur.user_id', $userIds)
->get();
$rolesByUser = [];
foreach ($rolesRows as $row) {
$rolesByUser[(int)$row->user_id][] = (array)$row;
}
foreach ($rolesByUser as &$list) {
usort($list, fn($a, $b) => ((int)($a['priority'] ?? 100)) <=> ((int)($b['priority'] ?? 100)));
}
$excludeSlugs = ['parent', 'guest', 'teacher', 'teacher_assistant', 'ta', 'assistant_teacher'];
$staffByUser = [];
foreach ($allStaff as $row) {
$uid = (int)($row['user_id'] ?? 0);
if ($uid > 0 && !isset($staffByUser[$uid])) {
$staffByUser[$uid] = $row;
}
}
$adminsGrid = [];
foreach ($userIds as $uid) {
$roles = $rolesByUser[$uid] ?? [];
$picked = null;
foreach ($roles as $role) {
$slug = strtolower((string)($role['role_slug'] ?? $role['role_name'] ?? ''));
if (!in_array($slug, $excludeSlugs, true)) {
$picked = $role;
break;
}
}
if (!$picked) {
continue;
}
$base = $staffByUser[$uid] ?? ['user_id' => $uid];
$base['role_name'] = $picked['role_name'] ?? 'Admin';
$base['role_slug'] = $picked['role_slug'] ?? null;
$adminsGrid[] = $base;
}
usort($adminsGrid, function ($a, $b) {
$an = trim(($a['firstname'] ?? '') . ' ' . ($a['lastname'] ?? ''));
$bn = trim(($b['firstname'] ?? '') . ' ' . ($b['lastname'] ?? ''));
return strcasecmp($an, $bn);
});
return [
'semester' => $semester,
'school_year' => $schoolYear,
'date' => $date,
'admins_grid' => $adminsGrid,
];
}
public function saveAdmins(Authenticatable $user, array $data): array
{
$date = (string)$data['date'];
$semester = (string)$data['semester'];
$schoolYear = (string)$data['school_year'];
$admins = (array)($data['admins'] ?? $data['staff'] ?? []);
$allowedStatuses = ['present', 'absent', 'late'];
$excludedRoleSlugs = ['parent', 'guest', 'teacher', 'teacher_assistant'];
DB::transaction(function () use ($admins, $date, $semester, $schoolYear, $allowedStatuses, $excludedRoleSlugs) {
foreach ($admins as $uid => $row) {
$uid = (int)$uid;
$status = strtolower(trim((string)($row['status'] ?? '')));
$reason = trim((string)($row['reason'] ?? ''));
$roleName = (string)($row['role_name'] ?? '');
$roleSlug = strtolower(str_replace(['-', ' '], '_', (string)($row['role_slug'] ?? $roleName)));
if (in_array($roleSlug, $excludedRoleSlugs, true)) {
continue;
}
if ($status === '') {
DB::table('staff_attendance')
->where('user_id', $uid)
->whereDate('date', $date)
->where('semester', $semester)
->where('school_year', $schoolYear)
->delete();
continue;
}
if (!in_array($status, $allowedStatuses, true)) {
continue;
}
DB::table('staff_attendance')->updateOrInsert(
[
'user_id' => $uid,
'date' => $date,
'semester' => $semester,
'school_year' => $schoolYear,
],
[
'role_name' => $roleName !== '' ? $roleName : 'Admin',
'status' => $status,
'reason' => $reason !== '' ? $reason : null,
'updated_at' => now(),
'created_at' => now(),
]
);
}
});
return [
'ok' => true,
'message' => 'Admins attendance saved successfully.',
];
}
public function saveCell(Authenticatable $user, array $data): array
{
$date = (string)$data['date'];
$status = (string)($data['status'] ?? '');
$targetId = (int)$data['user_id'];
$roleName = trim((string)($data['role_name'] ?? ''));
$semester = (string)$data['semester'];
$schoolYear = (string)$data['school_year'];
$reason = trim((string)($data['reason'] ?? ''));
if ($status === '') {
DB::table('staff_attendance')
->where('user_id', $targetId)
->whereDate('date', $date)
->where('semester', $semester)
->where('school_year', $schoolYear)
->delete();
return ['ok' => true];
}
DB::table('staff_attendance')->updateOrInsert(
[
'user_id' => $targetId,
'date' => $date,
'semester' => $semester,
'school_year' => $schoolYear,
],
[
'role_name' => $roleName !== '' ? $roleName : 'Admin',
'status' => $status,
'reason' => $reason !== '' ? $reason : null,
'updated_at' => now(),
'created_at' => now(),
]
);
return ['ok' => true];
}
public function monthCsv(?string $semester, ?string $schoolYear, ?string $scope): StreamedResponse
{
$semester = $semester ?: (string)$this->configuration->getConfig('semester');
$schoolYear = $schoolYear ?: (string)$this->configuration->getConfig('school_year');
$scope = strtolower((string)$scope);
[$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($schoolYear);
$semesterNorm = $this->semesterRangeService->normalizeSemester($semester);
if ($semesterNorm !== '') {
$semesterRange = $this->semesterRangeService->getSemesterRange($schoolYear, $semesterNorm);
if ($semesterRange) {
[$rangeStart, $rangeEnd] = $semesterRange;
}
}
$daysList = $this->semesterRangeService->buildSundayList($rangeStart, $rangeEnd);
$filename = ($scope === 'admins' ? 'admin_attendance_' : 'teacher_attendance_')
. $semester . '_' . str_replace('/', '-', $schoolYear) . '.csv';
return response()->streamDownload(function () use ($semester, $schoolYear, $scope, $daysList, $semesterNorm, $rangeStart, $rangeEnd) {
$out = fopen('php://output', 'w');
if ($scope === 'admins') {
fputcsv($out, ['User ID', 'Date', 'Status', 'Reason']);
$query = DB::table('staff_attendance')
->select('user_id', 'date', 'status', 'reason')
->where('school_year', $schoolYear)
->where('date', '>=', $rangeStart)
->where('date', '<=', $rangeEnd);
if ($semesterNorm !== '') {
$query->where('semester', $semesterNorm);
}
foreach ($query->orderBy('date')->get() as $row) {
fputcsv($out, [
$row->user_id,
$row->date,
strtoupper(substr((string)$row->status, 0, 1)),
$row->reason,
]);
}
fclose($out);
return;
}
fputcsv($out, ['Teacher ID', 'Date', 'Status', 'Reason']);
$query = DB::table('staff_attendance')
->select('user_id', 'date', 'status', 'reason')
->where('school_year', $schoolYear)
->where('date', '>=', $rangeStart)
->where('date', '<=', $rangeEnd);
if ($semesterNorm !== '') {
$query->where('semester', $semesterNorm);
}
foreach ($query->orderBy('date')->get() as $row) {
fputcsv($out, [
$row->user_id,
$row->date,
strtoupper(substr((string)$row->status, 0, 1)),
$row->reason,
]);
}
fclose($out);
}, $filename);
}
}