AVP-78 Feature to check if parents looked at report card

This commit is contained in:
root
2026-02-27 01:21:39 -05:00
parent fe7b99581d
commit 3cc5546733
37 changed files with 743 additions and 107 deletions
+149 -28
View File
@@ -8,6 +8,7 @@ use App\Models\ClassSectionModel;
use App\Models\CalendarModel;
use App\Models\ConfigurationModel;
use App\Models\StudentClassModel;
use App\Models\SubjectCurriculumModel;
use App\Services\SemesterRangeService;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\ResponseInterface;
@@ -21,6 +22,7 @@ class AdminProgressController extends BaseController
protected CalendarModel $calendarModel;
protected ConfigurationModel $configModel;
protected SemesterRangeService $semesterRangeService;
protected SubjectCurriculumModel $curriculumModel;
public function __construct()
{
@@ -32,6 +34,7 @@ class AdminProgressController extends BaseController
$this->calendarModel = new CalendarModel();
$this->configModel = new ConfigurationModel();
$this->semesterRangeService = new SemesterRangeService($this->configModel);
$this->curriculumModel = new SubjectCurriculumModel();
}
public function index()
@@ -107,6 +110,7 @@ class AdminProgressController extends BaseController
$filterEnd
);
$sectionStats = $this->buildSectionSubmissionStats($rows, $activeDatesSet, $expectedDays);
$sectionSubjectCounts = $this->buildSectionSubjectCounts($rows);
return view('admin/class_progress_list', [
'reportGroupsBySection' => $reportGroupsBySection,
@@ -115,6 +119,7 @@ class AdminProgressController extends BaseController
'statusOptions' => ClassProgressController::STATUS_OPTIONS,
'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
'sectionStats' => $sectionStats,
'sectionSubjectCounts' => $sectionSubjectCounts,
'expectedDays' => $expectedDays,
]);
}
@@ -381,50 +386,166 @@ class AdminProgressController extends BaseController
protected function buildSectionSubmissionStats(array $rows, array $activeDatesSet, int $expectedDays): array
{
$requiredSubjects = [];
foreach (ClassProgressController::SUBJECT_SECTIONS as $section) {
$requiredSubjects[] = $section['db_subject'] ?? $section['label'] ?? '';
}
$requiredSubjects = array_values(array_filter($requiredSubjects));
$submittedBySection = [];
foreach ($rows as $row) {
$sectionId = (int) ($row['class_section_id'] ?? 0);
$weekStart = (string) ($row['week_start'] ?? '');
$subject = (string) ($row['subject'] ?? '');
if ($sectionId === 0 || $weekStart === '' || $subject === '') {
if ($sectionId === 0 || $weekStart === '') {
continue;
}
if (! empty($activeDatesSet) && empty($activeDatesSet[$weekStart])) {
continue;
}
if (! in_array($subject, $requiredSubjects, true)) {
continue;
}
$submittedBySection[$sectionId][$weekStart][$subject] = true;
$submittedBySection[$sectionId][$weekStart] = true;
}
$stats = [];
foreach ($submittedBySection as $sectionId => $weeks) {
$submitted = 0;
foreach ($weeks as $subjects) {
$all = true;
foreach ($requiredSubjects as $subject) {
if (empty($subjects[$subject])) {
$all = false;
break;
}
}
if ($all) {
$submitted++;
}
}
$submitted = count($weeks);
$stats[$sectionId] = $this->buildSectionStat($submitted, $expectedDays);
}
return $stats;
}
protected function buildSectionSubjectCounts(array $rows): array
{
$allowedSubjects = [];
foreach (ClassProgressController::SUBJECT_SECTIONS as $section) {
$allowedSubjects[] = $section['db_subject'] ?? $section['label'] ?? '';
}
$allowedSubjects = array_values(array_filter($allowedSubjects));
$counts = [];
$latestWeekBySection = [];
$sectionClassMap = [];
foreach ($rows as $row) {
$sectionId = (int) ($row['class_section_id'] ?? 0);
$subject = (string) ($row['subject'] ?? '');
$weekStart = (string) ($row['week_start'] ?? '');
if ($sectionId === 0 || $subject === '' || $weekStart === '') {
continue;
}
if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
continue;
}
if (! isset($sectionClassMap[$sectionId])) {
$sectionClassMap[$sectionId] = $this->classSectionModel->getClassId($sectionId);
}
if (
! isset($latestWeekBySection[$sectionId])
|| $weekStart > $latestWeekBySection[$sectionId]
) {
$latestWeekBySection[$sectionId] = $weekStart;
}
}
$curriculumChapters = $this->buildCurriculumChapterMap(array_values(array_filter($sectionClassMap)));
foreach ($rows as $row) {
$sectionId = (int) ($row['class_section_id'] ?? 0);
$subject = (string) ($row['subject'] ?? '');
$weekStart = (string) ($row['week_start'] ?? '');
if ($sectionId === 0 || $subject === '' || $weekStart === '') {
continue;
}
if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
continue;
}
if (empty($latestWeekBySection[$sectionId]) || $weekStart !== $latestWeekBySection[$sectionId]) {
continue;
}
$subjectSlug = $this->resolveSubjectSlug($subject);
$classId = $sectionClassMap[$sectionId] ?? null;
$chapterSet = [];
if ($classId && $subjectSlug && ! empty($curriculumChapters[$classId][$subjectSlug])) {
$chapterSet = $curriculumChapters[$classId][$subjectSlug];
}
$counts[$sectionId] = ($counts[$sectionId] ?? 0) + $this->countChapterSegments(
(string) ($row['unit_title'] ?? ''),
$chapterSet
);
}
return $counts;
}
protected function buildCurriculumChapterMap(array $classIds): array
{
$classIds = array_values(array_filter(array_map('intval', $classIds)));
if (empty($classIds)) {
return [];
}
$rows = $this->curriculumModel
->whereIn('class_id', $classIds)
->findAll();
$map = [];
foreach ($rows as $row) {
$classId = (int) ($row['class_id'] ?? 0);
$subject = (string) ($row['subject'] ?? '');
$chapter = trim((string) ($row['chapter_name'] ?? ''));
if ($classId === 0 || $subject === '' || $chapter === '') {
continue;
}
$map[$classId][$subject][$chapter] = true;
}
return $map;
}
protected function resolveSubjectSlug(string $subject): ?string
{
foreach (ClassProgressController::SUBJECT_SECTIONS as $slug => $section) {
$dbSubject = (string) ($section['db_subject'] ?? '');
$label = (string) ($section['label'] ?? '');
if ($subject === $dbSubject || $subject === $label) {
return $slug;
}
}
return null;
}
protected function countChapterSegments(string $unitTitle, array $chapterSet): int
{
$unitTitle = trim($unitTitle);
if ($unitTitle === '') {
return 1;
}
$parts = array_filter(array_map('trim', explode(';', $unitTitle)), static fn ($part) => $part !== '');
if (! $parts) {
return 1;
}
$count = 0;
$seen = [];
foreach ($parts as $part) {
$chapter = $this->extractChapterFromSegment($part);
$key = $chapter !== '' ? $chapter : $part;
if (! empty($chapterSet) && $chapter !== '' && empty($chapterSet[$chapter])) {
$key = $part;
}
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$count++;
}
return $count > 0 ? $count : 1;
}
protected function extractChapterFromSegment(string $segment): string
{
$segment = trim($segment);
if ($segment === '') {
return '';
}
$pos = strrpos($segment, '/');
if ($pos === false) {
return $segment;
}
return trim(substr($segment, $pos + 1));
}
protected function buildSectionStat(int $submitted, int $expectedDays): array
{
$percent = $expectedDays > 0 ? round(($submitted * 100) / $expectedDays, 1) : 0.0;