assertTeacherAssignment($user, $classSectionId); $weekStart = (string) ($payload['week_start'] ?? ''); $weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null); if (!$this->rules->isWeekEndValid($weekStart, $weekEnd)) { throw new \InvalidArgumentException('Week end must be the same as or after the week start.'); } if (!$this->rules->hasIslamicUnitSelection($payload)) { throw new \InvalidArgumentException('Please select at least one Islamic Studies unit.'); } $confirmOverwrite = filter_var($payload['confirm_overwrite'] ?? false, FILTER_VALIDATE_BOOLEAN); $existingIds = ClassProgressReport::query() ->where('class_section_id', $classSectionId) ->whereDate('week_start', $weekStart) ->where('teacher_id', $user->id) ->pluck('id') ->map(fn ($id) => (int) $id) ->filter() ->values() ->all(); if ($existingIds !== [] && !$confirmOverwrite) { throw new \InvalidArgumentException('CONFIRM_OVERWRITE'); } $reports = DB::transaction(function () use ($user, $payload, $subjectSections, $classSectionId, $weekStart, $weekEnd, $filesBySubject, $confirmOverwrite, $existingIds) { if ($existingIds !== [] && $confirmOverwrite) { ClassProgressAttachment::query()->whereIn('report_id', $existingIds)->delete(); ClassProgressReport::query()->whereIn('id', $existingIds)->delete(); } $created = collect(); foreach ($subjectSections as $slug => $section) { $covered = trim((string) ($payload["covered_{$slug}"] ?? '')); if ($covered === '') { continue; } $homework = trim((string) ($payload["homework_{$slug}"] ?? '')); $unitValues = (array) ($payload["unit_{$slug}"] ?? []); $chapterValues = (array) ($payload["chapter_{$slug}"] ?? []); $unitTitle = $this->rules->buildUnitChapterSummary($unitValues, $chapterValues); $report = ClassProgressReport::query()->create([ 'teacher_id' => $user->id, 'class_section_id' => $classSectionId, 'week_start' => $weekStart, 'week_end' => $weekEnd, 'subject' => $section['db_subject'] ?? $section['label'] ?? $slug, 'unit_title' => $unitTitle, 'covered' => $covered, 'homework' => $homework !== '' ? $homework : null, 'status' => $this->rules->defaultStatus(), 'support_needed' => $payload['support_needed'] ?? null, 'flags_json' => $this->rules->normalizeFlags($payload['flags'] ?? null), ]); $attachments = $filesBySubject[$slug] ?? []; $stored = $this->attachments->storeAttachments($report->id, $attachments); if ($stored !== []) { $report->update(['attachment_path' => $stored[0]['file_path'] ?? null]); } $created->push($report); } if ($created->isEmpty()) { throw new \InvalidArgumentException('Please provide progress for at least one subject.'); } return $created; }); Log::info('Class progress reports created.', [ 'teacher_id' => $user->id, 'class_section_id' => $classSectionId, 'count' => $reports->count(), ]); return $reports; } /** * CI `ClassProgressController::update` — sync all subjects for the weekly batch keyed by {@see $anchor}. * * @return Collection */ public function syncWeeklyFromTeacherForm(User $user, ClassProgressReport $anchor, array $payload, array $filesBySubject): Collection { $subjectSections = (array) config('progress.subject_sections', []); $classSectionId = (int) ($payload['class_section_id'] ?? $anchor->class_section_id); $this->assertTeacherAssignment($user, $classSectionId); $weekStart = (string) ($payload['week_start'] ?? ''); $weekEnd = $this->rules->ensureWeekEnd($weekStart, $payload['week_end'] ?? null); if (! $this->rules->isWeekEndValid($weekStart, $weekEnd)) { throw new \InvalidArgumentException('Week end must be the same as or after the week start.'); } if (! $this->rules->hasIslamicUnitSelection($payload)) { throw new \InvalidArgumentException('Please select at least one Islamic Studies unit.'); } $originalWeekStart = $anchor->week_start instanceof \Carbon\CarbonInterface ? $anchor->week_start->format('Y-m-d') : (string) $anchor->week_start; $confirmOverwrite = filter_var($payload['confirm_overwrite'] ?? false, FILTER_VALIDATE_BOOLEAN); if ($weekStart !== '' && $weekStart !== $originalWeekStart) { $conflicts = ClassProgressReport::query() ->where('class_section_id', $classSectionId) ->whereDate('week_start', $weekStart) ->where('teacher_id', $user->id) ->pluck('id') ->map(fn ($id) => (int) $id) ->filter() ->values() ->all(); if ($conflicts !== [] && ! $confirmOverwrite) { throw new \InvalidArgumentException('CONFIRM_OVERWRITE'); } if ($conflicts !== [] && $confirmOverwrite) { ClassProgressAttachment::query()->whereIn('report_id', $conflicts)->delete(); ClassProgressReport::query()->whereIn('id', $conflicts)->delete(); } } $allowedIds = $this->queries->resolveAssignedTeacherIds($classSectionId); if ($allowedIds === []) { $allowedIds = [(int) $user->id]; } return DB::transaction(function () use ( $user, $anchor, $payload, $subjectSections, $classSectionId, $weekStart, $weekEnd, $filesBySubject, $allowedIds, $originalWeekStart, ): Collection { $weeklyBatch = ClassProgressReport::query() ->whereIn('teacher_id', $allowedIds) ->where('class_section_id', $classSectionId) ->whereDate('week_start', $originalWeekStart) ->orderBy('subject') ->get(); $reportMap = []; foreach ($weeklyBatch as $rep) { $subject = (string) ($rep->subject ?? ''); if ($subject !== '') { $reportMap[$subject] = $rep; } } $updated = collect(); $flagsPresent = array_key_exists('flags', $payload); foreach ($subjectSections as $slug => $section) { $covered = trim((string) ($payload["covered_{$slug}"] ?? '')); if ($covered === '') { continue; } $homework = trim((string) ($payload["homework_{$slug}"] ?? '')); $unitValues = (array) ($payload["unit_{$slug}"] ?? []); $chapterValues = (array) ($payload["chapter_{$slug}"] ?? []); $unitTitle = $this->rules->buildUnitChapterSummary($unitValues, $chapterValues); $subjectName = $section['db_subject'] ?? $section['label'] ?? $slug; $existing = $reportMap[$subjectName] ?? null; if ($unitTitle === null && $existing) { $unitTitle = $existing->unit_title; } $data = [ 'class_section_id' => $classSectionId, 'week_start' => $weekStart, 'week_end' => $weekEnd, 'subject' => $subjectName, 'unit_title' => $unitTitle, 'covered' => $covered, 'homework' => $homework !== '' ? $homework : null, ]; if ($flagsPresent) { $data['flags_json'] = $this->rules->normalizeFlags($payload['flags'] ?? null); } if ($existing) { $existing->fill($data); $existing->save(); $saved = $existing->fresh(); } else { $data['teacher_id'] = $user->id; $data['status'] = $this->rules->defaultStatus(); $saved = ClassProgressReport::query()->create($data); } if (! $saved) { continue; } $updated->push($saved); $attachments = $filesBySubject[$slug] ?? []; $stored = $this->attachments->storeAttachments((int) $saved->id, $attachments); if ($stored !== [] && ! $saved->attachment_path) { $saved->update(['attachment_path' => $stored[0]['file_path'] ?? null]); } } if ($updated->isEmpty()) { throw new \InvalidArgumentException('Please provide progress for at least one subject.'); } Log::info('Class progress weekly sync completed.', [ 'teacher_id' => $user->id, 'anchor_id' => $anchor->id, 'count' => $updated->count(), ]); return $updated; }); } public function updateReport(ClassProgressReport $report, array $payload, array $attachments): ClassProgressReport { return DB::transaction(function () use ($report, $payload, $attachments) { $weekStart = $payload['week_start'] ?? $report->week_start?->format('Y-m-d'); $weekEnd = $this->rules->ensureWeekEnd((string) $weekStart, $payload['week_end'] ?? null); if ($weekStart && $weekEnd && !$this->rules->isWeekEndValid((string) $weekStart, (string) $weekEnd)) { throw new \InvalidArgumentException('Week end must be the same as or after the week start.'); } $data = []; if ($weekStart) { $data['week_start'] = $weekStart; } if ($weekEnd) { $data['week_end'] = $weekEnd; } if (array_key_exists('covered', $payload)) { $data['covered'] = $payload['covered']; } if (array_key_exists('homework', $payload)) { $data['homework'] = $payload['homework']; } if (array_key_exists('unit_title', $payload)) { $data['unit_title'] = $payload['unit_title']; } if (array_key_exists('status', $payload)) { $data['status'] = $payload['status']; } if (array_key_exists('support_needed', $payload)) { $data['support_needed'] = $payload['support_needed']; } if (array_key_exists('flags', $payload)) { $data['flags_json'] = $this->rules->normalizeFlags($payload['flags']); } if ($data !== []) { $report->fill($data); $report->save(); } if ($attachments !== []) { $stored = $this->attachments->storeAttachments($report->id, $attachments); if ($stored !== []) { $report->update(['attachment_path' => $stored[0]['file_path'] ?? $report->attachment_path]); } } return $report->fresh(['classSection', 'attachments']); }); } public function deleteReport(ClassProgressReport $report): void { DB::transaction(function () use ($report) { $report->attachments()->delete(); $report->delete(); }); } private function assertTeacherAssignment(User $user, int $classSectionId): void { if ($classSectionId <= 0) { throw new \InvalidArgumentException('No class assignment found for this report.'); } if ($this->isAdmin($user)) { return; } $assigned = TeacherClass::query() ->where('teacher_id', $user->id) ->where('class_section_id', $classSectionId) ->exists(); if (!$assigned) { throw new \InvalidArgumentException('No class assignment found for this report.'); } } private function isAdmin(User $user): bool { $roles = $user->roles() ->pluck('roles.name') ->map(fn ($name) => strtolower((string) $name)) ->toArray(); foreach (['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'] as $role) { if (in_array($role, $roles, true)) { return true; } } return false; } }