148 lines
5.4 KiB
PHP
148 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Admin\AcademicClasses;
|
|
|
|
use App\Enums\AttendanceStatus;
|
|
use App\Models\Attendance;
|
|
use App\Models\CourseClass;
|
|
use App\Models\Student;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Support\Collection as BaseCollection;
|
|
|
|
class AttendanceService
|
|
{
|
|
/**
|
|
* Classes to show on the attendance overview: a dosen only sees classes
|
|
* they lecture, optionally narrowed down to one academic term.
|
|
*/
|
|
public function courseClassesForUser(User $user, ?int $academicTermId): Collection
|
|
{
|
|
return CourseClass::query()
|
|
->select(['course_classes.id', 'course_classes.course_id', 'course_classes.lecturer_id', 'course_classes.academic_term_id'])
|
|
->withCount('enrollments')
|
|
->with([
|
|
'course:id,code,name',
|
|
'academicTerm:id,academic_year,semester,start_date,end_date',
|
|
'attendances' => fn ($q) => $q->select('course_class_id', 'meeting_number')->distinct(),
|
|
])
|
|
->when($user->hasRole('dosen'), fn ($q) => $q->where('lecturer_id', $user->lecturer?->id))
|
|
->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
|
|
->orderBy('course_id')
|
|
->get()
|
|
->each(function (CourseClass $courseClass) {
|
|
$courseClass->setAttribute('meetings_count', $courseClass->attendances->count());
|
|
$courseClass->unsetRelation('attendances');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Every meeting recorded so far for a single class.
|
|
*/
|
|
public function sessionsForClass(CourseClass $courseClass): Collection
|
|
{
|
|
return Attendance::query()
|
|
->select(['meeting_number', 'date'])
|
|
->selectRaw('COUNT(*) as total_count')
|
|
->selectRaw("SUM(CASE WHEN status = 'present' THEN 1 ELSE 0 END) as present_count")
|
|
->where('course_class_id', $courseClass->id)
|
|
->groupBy('meeting_number', 'date')
|
|
->orderByDesc('meeting_number')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* The meeting number a new session for this class should default to.
|
|
*/
|
|
public function nextMeetingNumber(CourseClass $courseClass): int
|
|
{
|
|
return (Attendance::query()->where('course_class_id', $courseClass->id)->max('meeting_number') ?? 0) + 1;
|
|
}
|
|
|
|
public function session(CourseClass $courseClass, int $meetingNumber): array
|
|
{
|
|
$existing = Attendance::query()
|
|
->where('course_class_id', $courseClass->id)
|
|
->where('meeting_number', $meetingNumber)
|
|
->get()
|
|
->keyBy('student_id');
|
|
|
|
$roster = $courseClass->enrollments()
|
|
->with('student.user.profile')
|
|
->get()
|
|
->map(function ($enrollment) use ($existing) {
|
|
$attendance = $existing->get($enrollment->student_id);
|
|
|
|
return [
|
|
'student_id' => $enrollment->student_id,
|
|
'student' => $enrollment->student,
|
|
'status' => $attendance?->status?->value ?? AttendanceStatus::Absent->value,
|
|
];
|
|
})
|
|
->values();
|
|
|
|
return [
|
|
'roster' => $roster,
|
|
'date' => $existing->first()?->date?->format('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
public function saveSession(int $courseClassId, int $meetingNumber, string $date, array $records): void
|
|
{
|
|
foreach ($records as $record) {
|
|
Attendance::updateOrCreate(
|
|
[
|
|
'course_class_id' => $courseClassId,
|
|
'student_id' => $record['student_id'],
|
|
'meeting_number' => $meetingNumber,
|
|
],
|
|
[
|
|
'date' => $date,
|
|
'status' => $record['status'],
|
|
'recorded_at' => now(),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
public function deleteSession(int $courseClassId, int $meetingNumber): void
|
|
{
|
|
Attendance::query()
|
|
->where('course_class_id', $courseClassId)
|
|
->where('meeting_number', $meetingNumber)
|
|
->delete();
|
|
}
|
|
|
|
/**
|
|
* Per-class attendance summary for a student's own classes: every class
|
|
* they're enrolled in, with their recorded meetings and a present/total
|
|
* tally, even for classes with no attendance taken yet.
|
|
*/
|
|
public function forStudent(Student $student): BaseCollection
|
|
{
|
|
$enrollments = $student->enrollments()
|
|
->with([
|
|
'courseClass.course:id,code,name',
|
|
'courseClass.academicTerm:id,academic_year,semester,start_date,end_date',
|
|
])
|
|
->get();
|
|
|
|
$recordsByClass = Attendance::query()
|
|
->where('student_id', $student->id)
|
|
->orderBy('meeting_number')
|
|
->get(['course_class_id', 'meeting_number', 'date', 'status'])
|
|
->groupBy('course_class_id');
|
|
|
|
return $enrollments->map(function ($enrollment) use ($recordsByClass) {
|
|
$records = $recordsByClass->get($enrollment->course_class_id, new Collection);
|
|
|
|
return [
|
|
'course_class' => $enrollment->courseClass,
|
|
'records' => $records->values(),
|
|
'present_count' => $records->where('status', AttendanceStatus::Present)->count(),
|
|
'total_count' => $records->count(),
|
|
];
|
|
})->values();
|
|
}
|
|
}
|