Files
root 02ba2fe6b6
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
fix parent pages and teachers and admin
2026-07-09 13:56:22 -04:00

235 lines
6.4 KiB
PHP

<?php
namespace App\Services\ClassProgress;
use App\Models\Configuration;
use App\Models\SubjectCurriculum;
use App\Services\SemesterRangeService;
use Illuminate\Database\Eloquent\Builder;
class ClassProgressMetaService
{
public function subjectSections(): array
{
return (array) config('progress.subject_sections', []);
}
public function statusOptions(): array
{
return (array) config('progress.status_options', []);
}
/**
* Fallback: next N Sundays from today (legacy `buildUpcomingSundayOptions`).
*
* @return list<string>
*/
public function sundayOptions(int $count = 12): array
{
return $this->buildUpcomingSundayOptions($count);
}
/**
* legacy-aligned Sundays within semester/school-year range when configuration allows.
*
* @return list<string>
*/
public function sundayOptionsAlignedWithCi(int $count = 12): array
{
$range = $this->resolveProgressDateRange();
if ($range === null) {
return $this->buildUpcomingSundayOptions($count);
}
[$rangeStart, $rangeEnd] = $range;
try {
$start = new \DateTime($rangeStart);
$end = new \DateTime($rangeEnd);
} catch (\Exception $e) {
return $this->buildUpcomingSundayOptions($count);
}
if ($end < $start) {
[$start, $end] = [$end, $start];
}
$weekday = (int) $start->format('w');
if ($weekday !== 0) {
$start->modify('next sunday');
}
$options = [];
while ($start <= $end) {
$options[] = $start->format('Y-m-d');
$start->modify('+7 days');
}
return $options !== [] ? $options : $this->buildUpcomingSundayOptions($count);
}
/** legacy `pickDefaultWeekStart`. */
public function pickDefaultWeekStart(array $options): string
{
if ($options === []) {
return '';
}
$today = date('Y-m-d');
$default = '';
foreach ($options as $option) {
if ($option <= $today) {
$default = $option;
continue;
}
break;
}
return $default !== '' ? $default : $options[0];
}
/**
* @return list<string>
*/
private function buildUpcomingSundayOptions(int $count): array
{
$start = new \DateTime('today');
$weekday = (int) $start->format('w');
if ($weekday !== 0) {
$start->modify('next sunday');
}
$options = [];
for ($i = 0; $i < $count; $i++) {
$options[] = $start->format('Y-m-d');
$start->modify('+7 days');
}
return $options;
}
/**
* legacy `resolveProgressDateRange`.
*
* @return array{0:string,1:string}|null
*/
private function resolveProgressDateRange(): ?array
{
$schoolYear = (string) (Configuration::getConfig('school_year') ?? '');
if ($schoolYear === '') {
return null;
}
/** @var SemesterRangeService $svc */
$svc = app(SemesterRangeService::class);
$semester = $svc->normalizeSemester((string) (Configuration::getConfig('semester') ?? ''));
if ($semester === '') {
$semester = $svc->getSemesterForDate();
}
if ($semester !== '') {
$range = $svc->getSemesterRange($schoolYear, $semester);
if ($range !== null) {
return $range;
}
}
return $svc->getSchoolYearRange($schoolYear);
}
public function curriculumOptions(?int $classId, ?string $schoolYear = null): array
{
if (! $classId) {
return [];
}
$options = [];
foreach ($this->subjectSections() as $slug => $section) {
$subjects = $this->subjectLookupValues((string) $slug, is_array($section) ? $section : []);
$options[$slug] = SubjectCurriculum::query()
->forClass((int) $classId)
->where(function (Builder $query) use ($subjects) {
$query->whereIn('subject', $subjects);
})
->forSchoolYear($schoolYear)
->ordered()
->get()
->all();
}
return $options;
}
public function unitOptions(?int $classId, ?string $schoolYear = null): array
{
$curriculum = $this->curriculumOptions($classId, $schoolYear);
$units = [];
foreach ($curriculum as $slug => $rows) {
$seen = [];
$units[$slug] = [];
foreach ($rows as $row) {
$unitNumber = $row->unit_number ?? null;
$unitTitle = trim((string) ($row->unit_title ?? ''));
if ($unitNumber === null && $unitTitle === '') {
continue;
}
$key = ((string) $unitNumber).'|'.$unitTitle;
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$units[$slug][] = [
'unit_number' => $unitNumber !== null ? (int) $unitNumber : null,
'unit_title' => $unitTitle,
'label' => $this->formatUnitLabel($unitNumber !== null ? (int) $unitNumber : null, $unitTitle),
];
}
}
return $units;
}
private function formatUnitLabel(?int $unitNumber, string $unitTitle): string
{
if ($unitNumber !== null && $unitTitle !== '') {
return 'Unit '.$unitNumber.' - '.$unitTitle;
}
if ($unitNumber !== null) {
return 'Unit '.$unitNumber;
}
return $unitTitle;
}
/**
* Curriculum rows have existed with both short slugs (`quran`) and display
* subjects (`Quran/Arabic`). Query all configured aliases but keep the API
* response keyed by slug for the frontend.
*
* @param array<string, mixed> $section
* @return list<string>
*/
private function subjectLookupValues(string $slug, array $section): array
{
$values = [$slug];
foreach (['db_subject', 'label'] as $key) {
$value = trim((string) ($section[$key] ?? ''));
if ($value !== '') {
$values[] = $value;
}
}
return array_values(array_unique($values));
}
}