Files
alrahma_sunday_school_api/app/Services/Grading/GradingLockService.php
T
2026-06-08 23:30:22 -04:00

149 lines
4.6 KiB
PHP

<?php
namespace App\Services\Grading;
use App\Models\ClassSection;
use App\Models\GradingLock;
use App\Services\Grading\Policy\GradingPolicyResolver;
use App\Services\Grading\Validation\GradebookFinalizationValidator;
use RuntimeException;
class GradingLockService
{
public function __construct(
private ?GradingPolicyResolver $policyResolver = null,
private ?GradebookFinalizationValidator $finalizationValidator = null
) {
$this->policyResolver = $this->policyResolver ?? new GradingPolicyResolver;
$this->finalizationValidator = $this->finalizationValidator ?? new GradebookFinalizationValidator;
}
public function toggle(int $classSectionId, string $semester, string $schoolYear, ?int $userId): bool
{
if ($classSectionId <= 0 || $semester === '' || $schoolYear === '') {
throw new RuntimeException('Missing class section or term.');
}
$existing = GradingLock::getLock($classSectionId, $semester, $schoolYear);
if ($existing && $existing->is_locked) {
$existing->update([
'is_locked' => 0,
'locked_by' => null,
'locked_at' => null,
]);
return false;
}
$this->assertCanLock($classSectionId, $semester, $schoolYear);
if ($existing) {
$existing->update([
'is_locked' => 1,
'locked_by' => $userId ?: null,
'locked_at' => now(),
]);
} else {
GradingLock::query()->create([
'class_section_id' => $classSectionId,
'semester' => $semester,
'school_year' => $schoolYear,
'is_locked' => 1,
'locked_by' => $userId ?: null,
'locked_at' => now(),
]);
}
return true;
}
public function lockAll(string $semester, string $schoolYear, ?int $userId): int
{
if ($semester === '' || $schoolYear === '') {
throw new RuntimeException('Missing semester or school year.');
}
$sectionIds = ClassSection::query()
->select('class_section_id')
->groupBy('class_section_id')
->pluck('class_section_id')
->map(fn ($v) => (int) $v)
->filter(fn ($v) => $v > 0)
->values()
->all();
if (empty($sectionIds)) {
return 0;
}
$existingLocks = GradingLock::query()
->whereIn('class_section_id', $sectionIds)
->where('semester', $semester)
->where('school_year', $schoolYear)
->get();
$existingBySection = [];
foreach ($existingLocks as $row) {
$sid = (int) $row->class_section_id;
if ($sid > 0) {
$existingBySection[$sid] = $row;
}
}
$now = now();
$insertRows = [];
$count = 0;
foreach ($sectionIds as $sid) {
$this->assertCanLock($sid, $semester, $schoolYear);
if (! empty($existingBySection[$sid])) {
if ($existingBySection[$sid]->is_locked) {
continue;
}
$existingBySection[$sid]->update([
'is_locked' => 1,
'locked_by' => $userId ?: null,
'locked_at' => $now,
]);
$count++;
continue;
}
$insertRows[] = [
'class_section_id' => $sid,
'semester' => $semester,
'school_year' => $schoolYear,
'is_locked' => 1,
'locked_by' => $userId ?: null,
'locked_at' => $now,
'created_at' => $now,
'updated_at' => $now,
];
$count++;
}
if (! empty($insertRows)) {
GradingLock::query()->insert($insertRows);
}
return $count;
}
private function assertCanLock(int $classSectionId, string $semester, string $schoolYear): void
{
$mode = $this->policyResolver->calculationMode($classSectionId, $semester, $schoolYear);
if ($mode !== GradingPolicyResolver::STRONG_MODE) {
return;
}
$result = $this->finalizationValidator->validateClassSectionBeforeLock($classSectionId, $semester, $schoolYear);
if (! $result['valid']) {
$count = count($result['errors']);
throw new RuntimeException("Cannot lock class section {$classSectionId}; strong grading validation failed with {$count} error(s).");
}
}
}