e0dfc3ec82
API CI/CD / Validate (composer + pint) (push) Successful in 3m15s
API CI/CD / Test (PHPUnit) (push) Failing after 5m4s
API CI/CD / Build frontend assets (push) Successful in 1m3s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
297 lines
11 KiB
PHP
297 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Administrator;
|
|
|
|
use App\Models\AttendanceDay;
|
|
use App\Models\ScoreComment;
|
|
use App\Models\SemesterScore;
|
|
use App\Models\TeacherSubmissionNotificationHistory;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class TeacherSubmissionReportService
|
|
{
|
|
public function __construct(
|
|
protected AdministratorSharedService $shared,
|
|
protected TeacherSubmissionSupportService $support
|
|
) {}
|
|
|
|
public function report(array $filters = []): array
|
|
{
|
|
$semester = $this->shared->getSemester($filters['semester'] ?? null);
|
|
$schoolYear = $this->shared->getSchoolYear($filters['school_year'] ?? null);
|
|
try {
|
|
$teacherClassSupportsSemester = $this->shared->teacherClassSupportsSemester();
|
|
} catch (\Throwable) {
|
|
$teacherClassSupportsSemester = false;
|
|
}
|
|
|
|
$assignmentQuery = DB::table('teacher_class as tc')
|
|
->select([
|
|
'tc.class_section_id',
|
|
'cs.class_section_name',
|
|
'tc.teacher_id',
|
|
'u.firstname',
|
|
'u.lastname',
|
|
'tc.position',
|
|
])
|
|
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
|
|
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
|
|
->where('tc.school_year', $schoolYear)
|
|
->orderBy('cs.class_section_name');
|
|
|
|
if ($teacherClassSupportsSemester && $semester !== '') {
|
|
$assignmentQuery->where('tc.semester', $semester);
|
|
}
|
|
|
|
$assignmentRows = $assignmentQuery
|
|
->get()
|
|
->map(fn ($r) => (array) $r)
|
|
->all();
|
|
|
|
$teachersBySection = [];
|
|
foreach ($assignmentRows as $assignment) {
|
|
$sectionId = (int) ($assignment['class_section_id'] ?? 0);
|
|
if ($sectionId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$positionKey = strtolower(trim((string) ($assignment['position'] ?? '')));
|
|
$roleKey = $positionKey !== '' ? $positionKey : 'teacher';
|
|
|
|
$positionLabel = match ($roleKey) {
|
|
'ta' => 'TA',
|
|
'main' => 'Main',
|
|
default => $roleKey !== '' ? ucfirst($roleKey) : 'Teacher',
|
|
};
|
|
|
|
$teacherFullName = trim(($assignment['firstname'] ?? '').' '.($assignment['lastname'] ?? ''));
|
|
$teacherId = (int) ($assignment['teacher_id'] ?? 0);
|
|
|
|
if ($teacherFullName === '' || $teacherId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (! isset($teachersBySection[$sectionId])) {
|
|
$teachersBySection[$sectionId] = [
|
|
'class_section' => $assignment['class_section_name'] ?? "Section {$sectionId}",
|
|
'teachers' => [],
|
|
];
|
|
}
|
|
|
|
$teachersBySection[$sectionId]['teachers'][] = [
|
|
'id' => $teacherId,
|
|
'label' => "{$positionLabel}: {$teacherFullName}",
|
|
'role_key' => $roleKey,
|
|
];
|
|
}
|
|
|
|
$today = now()->format('Y-m-d');
|
|
$rows = [];
|
|
$totalStatuses = 0;
|
|
$missingItemCount = 0;
|
|
$allTeacherIds = [];
|
|
$allClassSectionIds = [];
|
|
|
|
foreach ($teachersBySection as $classSectionId => $section) {
|
|
$classSectionId = (int) $classSectionId;
|
|
if ($classSectionId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$studentIds = DB::table('student_class')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->pluck('student_id')
|
|
->map(fn ($id) => (int) $id)
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
$expected = count($studentIds);
|
|
|
|
$midtermStudents = [];
|
|
$participationStudents = [];
|
|
|
|
$scoreRecords = SemesterScore::query()
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->get();
|
|
|
|
foreach ($scoreRecords as $score) {
|
|
$sid = (int) ($score->student_id ?? 0);
|
|
if ($sid <= 0 || ($expected > 0 && ! in_array($sid, $studentIds, true))) {
|
|
continue;
|
|
}
|
|
|
|
if (trim((string) ($score->midterm_exam_score ?? '')) !== '') {
|
|
$midtermStudents[$sid] = true;
|
|
}
|
|
|
|
if (trim((string) ($score->participation_score ?? '')) !== '') {
|
|
$participationStudents[$sid] = true;
|
|
}
|
|
}
|
|
|
|
$midtermCommentStudents = [];
|
|
$ptapCommentStudents = [];
|
|
|
|
if (! empty($studentIds)) {
|
|
$comments = ScoreComment::query()
|
|
->select('student_id', 'score_type', 'comment')
|
|
->whereIn('student_id', $studentIds)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->whereIn('score_type', ['midterm', 'ptap'])
|
|
->get();
|
|
|
|
foreach ($comments as $comment) {
|
|
$sid = (int) ($comment->student_id ?? 0);
|
|
if ($sid <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$text = trim((string) ($comment->comment ?? ''));
|
|
if ($text === '') {
|
|
continue;
|
|
}
|
|
|
|
$type = strtolower(trim((string) ($comment->score_type ?? '')));
|
|
if ($type === 'midterm') {
|
|
$midtermCommentStudents[$sid] = true;
|
|
}
|
|
if ($type === 'ptap') {
|
|
$ptapCommentStudents[$sid] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$attendanceRow = AttendanceDay::query()
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->whereDate('date', $today)
|
|
->first();
|
|
|
|
$attendanceSubmitted = $attendanceRow
|
|
&& in_array(strtolower((string) ($attendanceRow->status ?? '')), ['submitted', 'published', 'finalized'], true);
|
|
|
|
$teacherList = $section['teachers'] ?? [];
|
|
usort(
|
|
$teacherList,
|
|
fn ($a, $b) => $this->support->teacherRolePriority($a['role_key'] ?? 'teacher')
|
|
<=> $this->support->teacherRolePriority($b['role_key'] ?? 'teacher')
|
|
);
|
|
|
|
foreach ($teacherList as $teacherEntry) {
|
|
if (! empty($teacherEntry['id'])) {
|
|
$allTeacherIds[] = $teacherEntry['id'];
|
|
}
|
|
}
|
|
$allClassSectionIds[] = $classSectionId;
|
|
|
|
$midtermScoreStatus = $this->support->submissionStatus(count($midtermStudents), $expected);
|
|
$midtermCommentStatus = $this->support->submissionStatus(count($midtermCommentStudents), $expected);
|
|
$participationStatus = $this->support->submissionStatus(count($participationStudents), $expected);
|
|
$ptapCommentStatus = $this->support->submissionStatus(count($ptapCommentStudents), $expected);
|
|
$attendanceStatus = $this->support->attendanceStatus($attendanceSubmitted, $expected);
|
|
|
|
$statusDetails = [
|
|
'midterm_score_status' => $midtermScoreStatus,
|
|
'midterm_comment_status' => $midtermCommentStatus,
|
|
'participation_status' => $participationStatus,
|
|
'ptap_comment_status' => $ptapCommentStatus,
|
|
'attendance_status' => $attendanceStatus,
|
|
];
|
|
|
|
$missingItemsForSection = $this->support->buildMissingItems($statusDetails);
|
|
$missingItemCount += count($missingItemsForSection);
|
|
$totalStatuses += count($statusDetails);
|
|
|
|
$rows[] = [
|
|
'class_section' => $section['class_section'] ?? "Section {$classSectionId}",
|
|
'class_section_id' => $classSectionId,
|
|
'teachers' => array_values($teacherList),
|
|
'midterm_score_status' => $midtermScoreStatus,
|
|
'midterm_comment_status' => $midtermCommentStatus,
|
|
'participation_status' => $participationStatus,
|
|
'ptap_comment_status' => $ptapCommentStatus,
|
|
'attendance_status' => $attendanceStatus,
|
|
'missing_items' => $missingItemsForSection,
|
|
'student_count' => $expected,
|
|
];
|
|
}
|
|
|
|
$historyMap = $this->notificationHistoryMap($allTeacherIds, $allClassSectionIds);
|
|
|
|
$summary = [
|
|
'total_items' => $totalStatuses,
|
|
'missing_items' => $missingItemCount,
|
|
'submitted_items' => max(0, $totalStatuses - $missingItemCount),
|
|
'submission_percentage' => $totalStatuses > 0
|
|
? (int) round((($totalStatuses - $missingItemCount) / $totalStatuses) * 100)
|
|
: 100,
|
|
];
|
|
|
|
return [
|
|
'rows' => $rows,
|
|
'semester' => $semester,
|
|
'schoolYear' => $schoolYear,
|
|
'currentSemester' => $this->shared->getSemester(),
|
|
'currentSchoolYear' => $this->shared->getSchoolYear(),
|
|
'notificationHistory' => $historyMap,
|
|
'summary' => $summary,
|
|
];
|
|
}
|
|
|
|
protected function notificationHistoryMap(array $allTeacherIds, array $allClassSectionIds): array
|
|
{
|
|
$historyMap = [];
|
|
$teacherIds = array_values(array_unique($allTeacherIds));
|
|
$classSectionIds = array_values(array_unique($allClassSectionIds));
|
|
|
|
if (empty($teacherIds) || empty($classSectionIds)) {
|
|
return $historyMap;
|
|
}
|
|
|
|
$historyRecords = TeacherSubmissionNotificationHistory::query()
|
|
->select('teacher_submission_notification_history.*', 'u.firstname', 'u.lastname')
|
|
->leftJoin('users as u', 'u.id', '=', 'teacher_submission_notification_history.admin_id')
|
|
->where('notification_category', 'teacher_submissions')
|
|
->whereIn('teacher_submission_notification_history.teacher_id', $teacherIds)
|
|
->whereIn('teacher_submission_notification_history.class_section_id', $classSectionIds)
|
|
->orderByDesc('sent_at')
|
|
->get();
|
|
|
|
foreach ($historyRecords as $record) {
|
|
$sectionId = (int) ($record->class_section_id ?? 0);
|
|
$teacherId = (int) ($record->teacher_id ?? 0);
|
|
|
|
if ($sectionId <= 0 || $teacherId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$adminName = trim(($record->firstname ?? '').' '.($record->lastname ?? ''));
|
|
if ($adminName === '') {
|
|
$adminName = 'Administrator';
|
|
}
|
|
|
|
$historyMap[$sectionId][$teacherId][] = [
|
|
'sent_at_text' => $record->sent_at ? Carbon::parse($record->sent_at)->format('M j, Y g:i A') : '',
|
|
'admin_name' => $adminName,
|
|
'status' => strtolower((string) ($record->status ?? 'sent')),
|
|
];
|
|
}
|
|
|
|
foreach ($historyMap as &$teachersHistory) {
|
|
foreach ($teachersHistory as &$entries) {
|
|
$entries = array_slice($entries, 0, 3);
|
|
}
|
|
}
|
|
|
|
return $historyMap;
|
|
}
|
|
}
|