update the new school year model

This commit is contained in:
root
2026-06-07 20:01:58 -04:00
parent 6866aedf42
commit 68a5c9edca
19 changed files with 472 additions and 32 deletions
+16 -6
View File
@@ -17,7 +17,7 @@ class BadgeScanService
/**
* @return array{recognized: bool, message: string, user_id?: int, display_name?: string}
*/
public function processScan(string $badgeScan): array
public function processScan(string $badgeScan, ?string $schoolYear = null, ?string $semester = null): array
{
$tag = trim($badgeScan);
if ($tag === '') {
@@ -51,8 +51,8 @@ class BadgeScanService
'user_id' => $user->id,
'card_id' => $tag,
'scan_time' => now(),
'school_year' => Configuration::getConfigValueByKey('school_year'),
'semester' => Configuration::getConfigValueByKey('semester'),
'school_year' => $schoolYear ?: Configuration::getConfigValueByKey('school_year'),
'semester' => $semester ?: Configuration::getConfigValueByKey('semester'),
]);
return [
@@ -68,13 +68,13 @@ class BadgeScanService
*
* @return list<array<string, mixed>>
*/
public function scanLogRows(): array
public function scanLogRows(?string $schoolYear = null, ?string $semester = null): array
{
if (! Schema::hasTable('scan_log')) {
return [];
}
return DB::table('scan_log')
$query = DB::table('scan_log')
->select([
'scan_log.id',
'scan_log.user_id',
@@ -88,7 +88,17 @@ class BadgeScanService
'students.lastname as student_lastname',
])
->leftJoin('users', 'users.id', '=', 'scan_log.user_id')
->leftJoin('students', 'students.rfid_tag', '=', 'scan_log.card_id')
->leftJoin('students', 'students.rfid_tag', '=', 'scan_log.card_id');
if ($schoolYear !== null && trim($schoolYear) !== '') {
$query->where('scan_log.school_year', trim($schoolYear));
}
if ($semester !== null && trim($semester) !== '') {
$query->where('scan_log.semester', trim($semester));
}
return $query
->orderByDesc('scan_log.scan_time')
->limit(self::LOG_LIMIT)
->get()
@@ -138,7 +138,7 @@ class ClassProgressMetaService
return $svc->getSchoolYearRange($schoolYear);
}
public function curriculumOptions(?int $classId): array
public function curriculumOptions(?int $classId, ?string $schoolYear = null): array
{
if (!$classId) {
return [];
@@ -146,7 +146,7 @@ class ClassProgressMetaService
$options = [];
foreach ($this->subjectSections() as $slug => $section) {
$options[$slug] = SubjectCurriculum::getOptionsForClass((int) $classId, (string) $slug);
$options[$slug] = SubjectCurriculum::getOptionsForClass((int) $classId, (string) $slug, $schoolYear);
}
return $options;
@@ -6,6 +6,8 @@ use App\Models\ClassProgressAttachment;
use App\Models\ClassProgressReport;
use App\Models\TeacherClass;
use App\Models\User;
use App\Services\SchoolYears\SchoolYearContextService;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@@ -16,6 +18,7 @@ class ClassProgressMutationService
private ClassProgressRuleService $rules,
private ClassProgressAttachmentService $attachments,
private ClassProgressQueryService $queries,
private SchoolYearContextService $schoolYears,
) {}
public function createReports(User $user, array $payload, array $filesBySubject): Collection
@@ -25,6 +28,9 @@ class ClassProgressMutationService
$this->assertTeacherAssignment($user, $classSectionId);
$schoolYear = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null);
$semester = $this->schoolYears->currentSemester($payload['semester'] ?? null);
$weekStart = (string) ($payload['week_start'] ?? '');
$weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null);
@@ -51,7 +57,7 @@ class ClassProgressMutationService
throw new \InvalidArgumentException('CONFIRM_OVERWRITE');
}
$reports = DB::transaction(function () use ($user, $payload, $subjectSections, $classSectionId, $weekStart, $weekEnd, $filesBySubject, $confirmOverwrite, $existingIds) {
$reports = DB::transaction(function () use ($user, $payload, $subjectSections, $classSectionId, $schoolYear, $semester, $weekStart, $weekEnd, $filesBySubject, $confirmOverwrite, $existingIds) {
if ($existingIds !== [] && $confirmOverwrite) {
ClassProgressAttachment::query()->whereIn('report_id', $existingIds)->delete();
ClassProgressReport::query()->whereIn('id', $existingIds)->delete();
@@ -70,7 +76,7 @@ class ClassProgressMutationService
$chapterValues = (array) ($payload["chapter_{$slug}"] ?? []);
$unitTitle = $this->rules->buildUnitChapterSummary($unitValues, $chapterValues);
$report = ClassProgressReport::query()->create([
$data = [
'teacher_id' => $user->id,
'class_section_id' => $classSectionId,
'week_start' => $weekStart,
@@ -82,7 +88,16 @@ class ClassProgressMutationService
'status' => $this->rules->defaultStatus(),
'support_needed' => $payload['support_needed'] ?? null,
'flags_json' => $this->rules->normalizeFlags($payload['flags'] ?? null),
]);
];
if (Schema::hasColumn('class_progress_reports', 'school_year')) {
$data['school_year'] = $schoolYear !== '' ? $schoolYear : null;
}
if (Schema::hasColumn('class_progress_reports', 'semester')) {
$data['semester'] = $semester !== '' ? $semester : null;
}
$report = ClassProgressReport::query()->create($data);
$attachments = $filesBySubject[$slug] ?? [];
$stored = $this->attachments->storeAttachments($report->id, $attachments);
@@ -121,6 +136,9 @@ class ClassProgressMutationService
$this->assertTeacherAssignment($user, $classSectionId);
$schoolYear = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null);
$semester = $this->schoolYears->currentSemester($payload['semester'] ?? null);
$weekStart = (string) ($payload['week_start'] ?? '');
$weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null);
@@ -172,6 +190,8 @@ class ClassProgressMutationService
$classSectionId,
$weekStart,
$weekEnd,
$schoolYear,
$semester,
$filesBySubject,
$allowedIds,
$originalWeekStart,
@@ -226,6 +246,13 @@ class ClassProgressMutationService
$data['flags_json'] = $this->rules->normalizeFlags($payload['flags'] ?? null);
}
if (Schema::hasColumn('class_progress_reports', 'school_year')) {
$data['school_year'] = $schoolYear !== '' ? $schoolYear : null;
}
if (Schema::hasColumn('class_progress_reports', 'semester')) {
$data['semester'] = $semester !== '' ? $semester : null;
}
if ($existing) {
$existing->fill($data);
$existing->save();
@@ -7,10 +7,21 @@ use App\Models\ClassProgressReport;
use App\Models\Configuration;
use App\Models\TeacherClass;
use App\Models\User;
use App\Services\SchoolYears\SchoolYearContextService;
use Illuminate\Support\Facades\Schema;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class ClassProgressQueryService
{
private SchoolYearContextService $schoolYears;
public function __construct($schoolYears = null)
{
$this->schoolYears = $schoolYears instanceof SchoolYearContextService
? $schoolYears
: app(SchoolYearContextService::class);
}
public function listReports(User $user, array $filters): LengthAwarePaginator
{
$query = ClassProgressReport::query()
@@ -51,6 +62,14 @@ class ClassProgressQueryService
$query->where('status', (string) $filters['status']);
}
if (!empty($filters['school_year']) && Schema::hasColumn('class_progress_reports', 'school_year')) {
$query->where('school_year', (string) $filters['school_year']);
}
if (!empty($filters['semester']) && Schema::hasColumn('class_progress_reports', 'semester')) {
$query->where('semester', (string) $filters['semester']);
}
if (!empty($filters['week_start'])) {
$query->whereDate('week_start', '>=', $filters['week_start']);
}
@@ -132,6 +151,11 @@ class ClassProgressQueryService
return TeacherClass::getClassAssignmentsByUserId($user->id, $schoolYear, $semester);
}
public function meta(?string $schoolYear = null, ?string $semester = null): array
{
return $this->schoolYears->options($schoolYear, $semester);
}
private function statusLabel(?string $status): string
{
$options = (array) config('progress.status_options', []);
@@ -0,0 +1,191 @@
<?php
namespace App\Services\SchoolYears;
use App\Models\Configuration;
use App\Models\SchoolYear;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class SchoolYearContextService
{
public function __construct(private SchoolYearResolver $resolver)
{
}
public function currentSchoolYear(?string $requested = null): string
{
$requested = trim((string) $requested);
if ($requested !== '') {
return $requested;
}
$current = trim((string) (Configuration::getConfig('school_year') ?? ''));
if ($current !== '') {
return $current;
}
if (Schema::hasTable('school_years')) {
$year = SchoolYear::query()->where('is_current', true)->orderByDesc('id')->first();
if ($year && trim((string) $year->name) !== '') {
return (string) $year->name;
}
}
return '';
}
public function currentSemester(?string $requested = null): string
{
$requested = trim((string) $requested);
if ($requested !== '') {
return $requested;
}
return trim((string) (Configuration::getConfig('semester') ?? ''));
}
public function options(?string $selectedSchoolYear = null, ?string $selectedSemester = null): array
{
if (Schema::hasTable('school_years')) {
$this->resolver->ensureCurrentTracked();
}
$currentSchoolYear = $this->currentSchoolYear($selectedSchoolYear);
$currentSemester = $this->currentSemester($selectedSemester);
$schoolYears = $this->schoolYears($currentSchoolYear);
$semesters = $this->semesters($currentSemester);
return [
'school_year' => $currentSchoolYear !== '' ? $currentSchoolYear : null,
'current_school_year' => $currentSchoolYear !== '' ? $currentSchoolYear : null,
'semester' => $currentSemester !== '' ? $currentSemester : null,
'current_semester' => $currentSemester !== '' ? $currentSemester : null,
'school_years' => $schoolYears,
'semesters' => $semesters,
];
}
public function schoolYears(?string $include = null): array
{
$years = [];
if (Schema::hasTable('school_years')) {
$years = SchoolYear::query()
->orderByDesc('start_date')
->orderByDesc('id')
->get()
->map(fn (SchoolYear $year) => [
'id' => (int) $year->id,
'name' => (string) $year->name,
'label' => (string) $year->name,
'start_date' => $year->start_date ? $year->start_date->format('Y-m-d') : null,
'end_date' => $year->end_date ? $year->end_date->format('Y-m-d') : null,
'status' => (string) $year->status,
'is_current' => (bool) $year->is_current,
'is_editable' => $year->status === SchoolYear::STATUS_ACTIVE,
])
->all();
}
$seen = [];
foreach ($years as $row) {
$seen[$row['name']] = true;
}
foreach ($this->legacySchoolYearNames() as $name) {
if (!isset($seen[$name])) {
$years[] = [
'id' => null,
'name' => $name,
'label' => $name,
'start_date' => null,
'end_date' => null,
'status' => 'legacy',
'is_current' => false,
'is_editable' => true,
];
$seen[$name] = true;
}
}
$include = trim((string) $include);
if ($include !== '' && !isset($seen[$include])) {
array_unshift($years, [
'id' => null,
'name' => $include,
'label' => $include,
'start_date' => null,
'end_date' => null,
'status' => 'selected',
'is_current' => true,
'is_editable' => true,
]);
}
return $years;
}
public function semesters(?string $include = null): array
{
$names = ['Fall', 'Spring'];
$current = trim((string) $include);
if ($current !== '' && !in_array($current, $names, true)) {
array_unshift($names, $current);
}
return array_map(fn (string $name) => [
'name' => $name,
'label' => $name,
'is_current' => $current !== '' && strcasecmp($name, $current) === 0,
], $names);
}
private function legacySchoolYearNames(): array
{
$tables = [
'students', 'parents', 'users', 'staff', 'student_class', 'teacher_class',
'events', 'calendar_events', 'scan_log', 'exam_drafts', 'inventory_items',
'inventory_movements', 'certificate_records', 'semester_scores', 'final_scores',
'student_decisions', 'below_sixty_decisions', 'whatsapp_group_links',
];
$names = [];
foreach ($tables as $table) {
if (!Schema::hasTable($table) || !Schema::hasColumn($table, 'school_year')) {
continue;
}
try {
$rows = DB::table($table)
->whereNotNull('school_year')
->where('school_year', '!=', '')
->distinct()
->orderByDesc('school_year')
->limit(20)
->pluck('school_year')
->all();
} catch (\Throwable $e) {
continue;
}
foreach ($rows as $row) {
$name = trim((string) $row);
if ($name !== '') {
$names[$name] = true;
}
}
}
$configured = trim((string) (Configuration::getConfig('school_year') ?? ''));
if ($configured !== '') {
$names[$configured] = true;
}
$out = array_keys($names);
rsort($out, SORT_NATURAL);
return $out;
}
}
@@ -4,25 +4,46 @@ namespace App\Services\Subjects;
use App\Models\SchoolClass;
use App\Models\SubjectCurriculum;
use App\Services\SchoolYears\SchoolYearContextService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class SubjectCurriculumService
{
private SchoolYearContextService $schoolYears;
public const SUBJECT_OPTIONS = [
'islamic' => 'Islamic Studies',
'quran' => 'Quran/Arabic',
];
public function list(): array
public function __construct(?SchoolYearContextService $schoolYears = null)
{
$this->schoolYears = $schoolYears ?: app(SchoolYearContextService::class);
}
public function list(?string $schoolYear = null): array
{
$classes = SchoolClass::query()
->orderBy('class_name')
->get()
->toArray();
$entries = DB::table('subject_curriculum_items as sci')
$resolvedSchoolYear = $this->schoolYears->currentSchoolYear($schoolYear);
$entriesQuery = DB::table('subject_curriculum_items as sci')
->leftJoin('classes', 'classes.id', '=', 'sci.class_id')
->select('sci.*', 'classes.class_name')
->select('sci.*', 'classes.class_name');
if ($resolvedSchoolYear !== '' && Schema::hasColumn('subject_curriculum_items', 'school_year')) {
$entriesQuery->where(function ($query) use ($resolvedSchoolYear) {
$query->where('sci.school_year', $resolvedSchoolYear)
->orWhereNull('sci.school_year')
->orWhere('sci.school_year', '');
});
}
$entries = $entriesQuery
->orderBy('classes.class_name')
->orderBy('sci.subject')
->orderBy('sci.unit_number')
@@ -35,18 +56,25 @@ class SubjectCurriculumService
'classes' => $classes,
'entries' => $entries,
'subject_labels' => self::SUBJECT_OPTIONS,
'meta' => $this->schoolYears->options($resolvedSchoolYear),
];
}
public function store(array $payload): SubjectCurriculum
{
return SubjectCurriculum::query()->create([
$data = [
'class_id' => (int) $payload['class_id'],
'subject' => (string) $payload['subject'],
'unit_number' => $payload['unit_number'] !== null ? (int) $payload['unit_number'] : null,
'unit_title' => $payload['unit_title'] !== '' ? $payload['unit_title'] : null,
'chapter_name' => (string) $payload['chapter_name'],
]);
];
if (Schema::hasColumn('subject_curriculum_items', 'school_year')) {
$data['school_year'] = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null) ?: null;
}
return SubjectCurriculum::query()->create($data);
}
public function update(int $id, array $payload): ?SubjectCurriculum
@@ -56,13 +84,19 @@ class SubjectCurriculumService
return null;
}
$entry->update([
$data = [
'class_id' => (int) $payload['class_id'],
'subject' => (string) $payload['subject'],
'unit_number' => $payload['unit_number'] !== null ? (int) $payload['unit_number'] : null,
'unit_title' => $payload['unit_title'] !== '' ? $payload['unit_title'] : null,
'chapter_name' => (string) $payload['chapter_name'],
]);
];
if (Schema::hasColumn('subject_curriculum_items', 'school_year')) {
$data['school_year'] = $this->schoolYears->currentSchoolYear($payload['school_year'] ?? null) ?: null;
}
$entry->update($data);
return $entry;
}