add tests batch 20

This commit is contained in:
root
2026-06-09 01:03:53 -04:00
parent 95efb9652e
commit 6be4875c5e
1502 changed files with 13797 additions and 11313 deletions
@@ -5,7 +5,6 @@ namespace App\Services\Promotions;
use App\Models\ClassSection;
use App\Models\LevelProgression;
use App\Models\SchoolClass;
use App\Services\School\AccountEventService;
use Illuminate\Support\Collection;
/**
@@ -13,7 +12,7 @@ use Illuminate\Support\Collection;
* `level_progressions` table (plan section 10).
*
* Falls back to `class_id + 1` only if no mapping is found, matching
* the legacy behaviour in {@see AccountEventService}.
* the legacy behaviour in {@see \App\Services\School\AccountEventService}.
*/
class LevelProgressionService
{
@@ -24,15 +23,15 @@ class LevelProgressionService
public const DEFAULT_MAP = [
['current' => 'KG1', 'next' => 'KG2', 'order' => 10, 'terminal' => false],
['current' => 'KG2', 'next' => 'Grade 1', 'order' => 20, 'terminal' => false],
['current' => 'Grade 1', 'next' => 'Grade 2', 'order' => 30, 'terminal' => false],
['current' => 'Grade 2', 'next' => 'Grade 3', 'order' => 40, 'terminal' => false],
['current' => 'Grade 3', 'next' => 'Grade 4', 'order' => 50, 'terminal' => false],
['current' => 'Grade 4', 'next' => 'Grade 5', 'order' => 60, 'terminal' => false],
['current' => 'Grade 5', 'next' => 'Grade 6', 'order' => 70, 'terminal' => false],
['current' => 'Grade 6', 'next' => 'Grade 7', 'order' => 80, 'terminal' => false],
['current' => 'Grade 7', 'next' => 'Grade 8', 'order' => 90, 'terminal' => false],
['current' => 'Grade 8', 'next' => 'Grade 9', 'order' => 100, 'terminal' => false],
['current' => 'Grade 9', 'next' => 'Youth', 'order' => 110, 'terminal' => false],
['current' => 'Grade 1','next' => 'Grade 2', 'order' => 30, 'terminal' => false],
['current' => 'Grade 2','next' => 'Grade 3', 'order' => 40, 'terminal' => false],
['current' => 'Grade 3','next' => 'Grade 4', 'order' => 50, 'terminal' => false],
['current' => 'Grade 4','next' => 'Grade 5', 'order' => 60, 'terminal' => false],
['current' => 'Grade 5','next' => 'Grade 6', 'order' => 70, 'terminal' => false],
['current' => 'Grade 6','next' => 'Grade 7', 'order' => 80, 'terminal' => false],
['current' => 'Grade 7','next' => 'Grade 8', 'order' => 90, 'terminal' => false],
['current' => 'Grade 8','next' => 'Grade 9', 'order' => 100, 'terminal' => false],
['current' => 'Grade 9','next' => 'Youth', 'order' => 110, 'terminal' => false],
['current' => 'Youth', 'next' => null, 'order' => 120, 'terminal' => true],
];
@@ -78,7 +77,7 @@ class LevelProgressionService
}
$current = SchoolClass::query()->find($currentClassId);
if (! $current) {
if (!$current) {
return null;
}
$currentName = (string) ($current->class_name ?? '');
@@ -119,10 +118,9 @@ class LevelProgressionService
return null;
}
$classId = ClassSection::getClassId($classSectionId);
if (! $classId) {
if (!$classId) {
return null;
}
return $this->resolveByCurrentClassId((int) $classId);
}
@@ -183,7 +181,6 @@ class LevelProgressionService
if ($existing) {
$existing->fill($data);
$existing->save();
return $existing;
}
@@ -45,7 +45,6 @@ class BalancedSectionPlacementService
return $scoreB <=> $scoreA;
}
$name = strcmp((string) ($a['student_name'] ?? ''), (string) ($b['student_name'] ?? ''));
return $name !== 0 ? $name : ((int) $a['student_id'] <=> (int) $b['student_id']);
});
}
@@ -80,7 +79,6 @@ class BalancedSectionPlacementService
for ($i = 1; $i <= $sectionCount; $i++) {
$sizes[$i] = $base + ($i <= $remainder ? 1 : 0);
}
return $sizes;
}
@@ -100,7 +98,6 @@ class BalancedSectionPlacementService
if ($cmp !== 0) {
return $cmp;
}
return $a['section_index'] <=> $b['section_index'];
});
@@ -7,7 +7,9 @@ use Illuminate\Support\Facades\DB;
class PlacementPoolBuilder
{
public function __construct(private ScoreBandClassifier $bands) {}
public function __construct(private ScoreBandClassifier $bands)
{
}
public function build(string $fromSchoolYear, string $toSchoolYear, ?int $toGradeLevelId = null): array
{
@@ -36,39 +38,34 @@ class PlacementPoolBuilder
$studentId = (int) $row->student_id;
if ((int) ($row->is_active ?? 1) === 0) {
$exceptions['withdrawn_or_inactive_student'][] = $studentId;
continue;
}
if ($this->alreadyPlaced($studentId, $toSchoolYear)) {
$exceptions['duplicate_target_year_placement'][] = $studentId;
continue;
}
if (! $this->isPromotedDecision((string) ($row->decision ?? ''))) {
if (!$this->isPromotedDecision((string) ($row->decision ?? ''))) {
$bucket = $row->decision === null ? 'missing_student_decision' : 'decision_conflicts_with_enrollment_request';
$exceptions[$bucket][] = $studentId;
continue;
}
$score = $row->year_score !== null ? (float) $row->year_score : ($row->final_average !== null ? (float) $row->final_average : null);
if ($score === null) {
$exceptions['missing_student_decision'][] = $studentId;
continue;
}
try {
$band = $this->bands->fromScore($score, true);
} catch (\RuntimeException) {
$exceptions['failed_retained'][] = $studentId;
continue;
}
$pool[] = [
'student_id' => $studentId,
'student_type' => 'returning',
'student_name' => trim((string) $row->lastname.', '.(string) $row->firstname),
'student_name' => trim((string) $row->lastname . ', ' . (string) $row->firstname),
'source_decision_id' => $row->decision_id ? (int) $row->decision_id : null,
'source_enrollment_id' => $row->enrollment_id ? (int) $row->enrollment_id : null,
'final_score' => $score,
@@ -101,19 +98,17 @@ class PlacementPoolBuilder
$studentId = (int) $row->student_id;
if ((int) ($row->is_active ?? 1) === 0) {
$exceptions['withdrawn_or_inactive_student'][] = $studentId;
continue;
}
if ($this->alreadyPlaced($studentId, $toSchoolYear)) {
$exceptions['duplicate_target_year_placement'][] = $studentId;
continue;
}
$pool[] = [
'student_id' => $studentId,
'student_type' => 'new',
'student_name' => trim((string) $row->lastname.', '.(string) $row->firstname),
'student_name' => trim((string) $row->lastname . ', ' . (string) $row->firstname),
'source_decision_id' => null,
'source_enrollment_id' => (int) $row->enrollment_id,
'final_score' => null,
@@ -182,7 +177,6 @@ class PlacementPoolBuilder
private function isPromotedDecision(string $decision): bool
{
$normalized = strtolower(trim($decision));
return in_array($normalized, ['passed', 'pass', 'promoted', 'promote', 'eligible_to_continue'], true);
}
}
@@ -9,7 +9,6 @@ use RuntimeException;
class PromotionSectionCapacityService
{
public const CONFIG_KEY = 'promotion.section_capacity';
public const DEFAULT_CAPACITY = 20;
public function capacity(): array
@@ -24,7 +23,7 @@ class PromotionSectionCapacityService
'fallback' => self::DEFAULT_CAPACITY,
]);
$value = self::DEFAULT_CAPACITY;
} elseif (! ctype_digit((string) $raw)) {
} elseif (!ctype_digit((string) $raw)) {
throw new RuntimeException('promotion.section_capacity must be a positive integer.');
} else {
$value = (int) $raw;
@@ -14,7 +14,8 @@ class SectionPlacementPreviewService
private PromotionSectionCapacityService $capacityService,
private PlacementPoolBuilder $poolBuilder,
private BalancedSectionPlacementService $placementService
) {}
) {
}
public function createDraft(
string $fromSchoolYear,
@@ -118,12 +119,12 @@ class SectionPlacementPreviewService
->where('is_event_only', 0)
->exists();
if ($duplicate) {
throw new RuntimeException('Student '.$studentId.' already has a target-year class placement.');
throw new RuntimeException('Student ' . $studentId . ' already has a target-year class placement.');
}
$sectionId = $sectionMap[(int) $student->planned_section_index] ?? null;
if ($sectionId === null) {
throw new RuntimeException('Missing target section for planned section '.$student->planned_section_index.'.');
throw new RuntimeException('Missing target section for planned section ' . $student->planned_section_index . '.');
}
DB::table('student_class')->insert([
@@ -132,7 +133,7 @@ class SectionPlacementPreviewService
'is_event_only' => 0,
'semester' => $semester,
'school_year' => $batch->to_school_year,
'description' => 'Finalized from section placement batch #'.$batch->getKey(),
'description' => 'Finalized from section placement batch #' . $batch->getKey(),
'created_at' => now(),
'updated_at' => now(),
'updated_by' => $userId,
@@ -153,7 +154,7 @@ class SectionPlacementPreviewService
private function ensureSections(SectionPlacementBatch $batch, string $semester): array
{
if (! $batch->to_grade_level_id) {
if (!$batch->to_grade_level_id) {
throw new RuntimeException('to_grade_level_id is required to finalize placement sections.');
}
@@ -175,7 +176,6 @@ class SectionPlacementPreviewService
if ($existing) {
$map[$i] = (int) $existing;
continue;
}
@@ -198,8 +198,7 @@ class SectionPlacementPreviewService
private function sectionName(int $classId, string $suffix, string $schoolYear): string
{
$className = DB::table('classes')->where('id', $classId)->value('class_name');
$base = $className ? (string) $className : ('Class '.$classId);
return $base.'-'.$suffix.' '.$schoolYear;
$base = $className ? (string) $className : ('Class ' . $classId);
return $base . '-' . $suffix . ' ' . $schoolYear;
}
}
@@ -24,12 +24,13 @@ class PromotionEligibilityService
private LevelProgressionService $progression,
private PromotionStatusService $statusService,
private PromotionAuditService $audit
) {}
) {
}
/**
* Evaluate eligibility for a single student.
*
* @param int|null $userId actor id for audit
* @param int|null $userId actor id for audit
*/
public function evaluateStudent(
int $studentId,
@@ -37,7 +38,7 @@ class PromotionEligibilityService
?int $userId = null
): ?StudentPromotionRecord {
$student = Student::query()->find($studentId);
if (! $student) {
if (!$student) {
return null;
}
@@ -82,7 +83,7 @@ class PromotionEligibilityService
$summary = $this->emptySummary();
foreach ($studentIds as $studentId) {
$student = Student::query()->find($studentId);
if (! $student) {
if (!$student) {
continue;
}
try {
@@ -131,7 +132,7 @@ class PromotionEligibilityService
$summary = $this->emptySummary();
foreach ($studentIds as $studentId) {
$student = Student::query()->find($studentId);
if (! $student) {
if (!$student) {
continue;
}
try {
@@ -213,7 +214,7 @@ class PromotionEligibilityService
->first();
$isNew = $existing === null;
$record = $existing ?: new StudentPromotionRecord;
$record = $existing ?: new StudentPromotionRecord();
$record->fill([
'student_id' => $studentId,
'parent_id' => (int) ($student->parent_id ?? 0) ?: null,
@@ -287,7 +288,7 @@ class PromotionEligibilityService
->orderByDesc('id')
->first();
if (! $decision) {
if (!$decision) {
return [
'passed' => null,
'final_average' => null,
@@ -331,14 +332,14 @@ class PromotionEligibilityService
return [
'passed' => null,
'final_average' => $score,
'notes' => 'student_decisions has unrecognized decision: '.$rawDecision,
'notes' => 'student_decisions has unrecognized decision: ' . $rawDecision,
'has_data' => false,
];
}
private function resolveStatusFromEvaluation(?bool $passed, bool $terminal, bool $hasData): string
{
if (! $hasData) {
if (!$hasData) {
return StudentPromotionRecord::STATUS_ON_HOLD;
}
if ($passed === false) {
@@ -347,7 +348,6 @@ class PromotionEligibilityService
if ($terminal) {
return StudentPromotionRecord::STATUS_GRADUATED;
}
return StudentPromotionRecord::STATUS_AWAITING_PARENT;
}
@@ -380,17 +380,15 @@ class PromotionEligibilityService
private function resolveCurrentSchoolYear(): ?string
{
$year = (string) (Configuration::getConfigValueByKey('school_year') ?? '');
return $year !== '' ? $year : null;
}
private function nextSchoolYear(string $current): ?string
{
if (! preg_match('/^(\d{4})-(\d{4})$/', $current, $m)) {
if (!preg_match('/^(\d{4})-(\d{4})$/', $current, $m)) {
return null;
}
return ((int) $m[1] + 1).'-'.((int) $m[2] + 1);
return ((int) $m[1] + 1) . '-' . ((int) $m[2] + 1);
}
private function passThreshold(): float
@@ -399,7 +397,6 @@ class PromotionEligibilityService
if ($configured !== null && is_numeric($configured)) {
return (float) $configured;
}
return self::DEFAULT_PASS_THRESHOLD;
}
}
@@ -28,7 +28,8 @@ class PromotionEnrollmentService
public function __construct(
private PromotionStatusService $statusService,
private PromotionAuditService $audit
) {}
) {
}
/**
* Lists actionable promotion records for a parent (the parent
@@ -53,9 +54,9 @@ class PromotionEnrollmentService
$studentIds = $records->pluck('student_id')->unique()->all();
/** @var EloquentCollection<int,Student> $studentsCollection */
$studentsCollection = ! empty($studentIds)
$studentsCollection = !empty($studentIds)
? Student::query()->whereIn('id', $studentIds)->get()
: new EloquentCollection;
: new EloquentCollection();
$studentsById = $studentsCollection->keyBy('id');
$payload = $records->map(function (StudentPromotionRecord $record) use ($studentsById) {
@@ -79,7 +80,7 @@ class PromotionEnrollmentService
$this->assertActionable($record);
return DB::transaction(function () use ($record, $parentId, $userId) {
if (! $record->enrollment_started_at) {
if (!$record->enrollment_started_at) {
$record->enrollment_started_at = now();
}
$record->enrollment_status = StudentPromotionRecord::ENROLLMENT_IN_PROGRESS;
@@ -116,7 +117,7 @@ class PromotionEnrollmentService
$sanitized = [];
foreach ($steps as $field => $value) {
if (! in_array($field, self::ALLOWED_STEPS, true)) {
if (!in_array($field, self::ALLOWED_STEPS, true)) {
continue;
}
$sanitized[$field] = (bool) $value;
@@ -138,7 +139,7 @@ class PromotionEnrollmentService
return $record;
}
if (! $record->enrollment_started_at) {
if (!$record->enrollment_started_at) {
$record->enrollment_started_at = now();
}
$record->enrollment_status = StudentPromotionRecord::ENROLLMENT_IN_PROGRESS;
@@ -184,7 +185,7 @@ class PromotionEnrollmentService
$this->assertParentOwns($record, $parentId);
$this->assertActionable($record);
if (! $record->enrollmentChecklistComplete()) {
if (!$record->enrollmentChecklistComplete()) {
throw new RuntimeException('Enrollment checklist is incomplete.');
}
@@ -293,12 +294,10 @@ class PromotionEnrollmentService
if ($existing) {
$existing->fill($payload);
$existing->save();
return (int) $existing->getKey();
}
$created = Enrollment::query()->create($payload);
return $created ? (int) $created->getKey() : null;
}
@@ -308,7 +307,7 @@ class PromotionEnrollmentService
'promotion_id' => (int) $record->getKey(),
'student_id' => (int) $record->student_id,
'student_name' => $student
? trim((string) $student->firstname.' '.(string) $student->lastname)
? trim((string) $student->firstname . ' ' . (string) $student->lastname)
: null,
'current_level' => $record->current_level_name,
'promoted_level' => $record->promoted_level_name,
@@ -344,13 +343,12 @@ class PromotionEnrollmentService
true
);
});
if (! $actionable) {
if (!$actionable) {
return null;
}
$level = $actionable->promoted_level_name ?: 'the next level';
$deadline = $actionable->enrollment_deadline?->toDateString();
$deadlineText = $deadline ? 'by '.$deadline : 'as soon as possible';
$deadlineText = $deadline ? 'by ' . $deadline : 'as soon as possible';
return sprintf(
'Your child is eligible for promotion to %s. Please complete enrollment for the new school year %s.',
$level,
@@ -374,7 +372,7 @@ class PromotionEnrollmentService
private function assertActionable(StudentPromotionRecord $record): void
{
$actionable = StudentPromotionRecord::parentActionableStatuses();
if (! in_array($record->promotion_status, $actionable, true)) {
if (!in_array($record->promotion_status, $actionable, true)) {
throw new RuntimeException(sprintf(
'Promotion record is not actionable in status "%s".',
$record->promotion_status
@@ -385,10 +383,9 @@ class PromotionEnrollmentService
private function guessNextSchoolYear(): ?string
{
$current = (string) (Configuration::getConfigValueByKey('school_year') ?? '');
if (! preg_match('/^(\d{4})-(\d{4})$/', $current, $m)) {
if (!preg_match('/^(\d{4})-(\d{4})$/', $current, $m)) {
return null;
}
return ((int) $m[1] + 1).'-'.((int) $m[2] + 1);
return ((int) $m[1] + 1) . '-' . ((int) $m[2] + 1);
}
}
@@ -42,6 +42,7 @@ class PromotionQueryService
* parent_action_required?: bool,
* per_page?: int|null,
* } $filters
*
* @return array{
* data: array<int,array<string,mixed>>,
* total: int,
@@ -67,12 +68,12 @@ class PromotionQueryService
$studentIds = $records->pluck('student_id')->unique()->values()->all();
$parentIds = $records->pluck('parent_id')->filter()->unique()->values()->all();
$studentsById = ! empty($studentIds)
$studentsById = !empty($studentIds)
? Student::query()->whereIn('id', $studentIds)->get()->keyBy('id')
: new EloquentCollection;
$parentsById = ! empty($parentIds)
: new EloquentCollection();
$parentsById = !empty($parentIds)
? User::query()->whereIn('id', $parentIds)->get()->keyBy('id')
: new EloquentCollection;
: new EloquentCollection();
$rows = $records->map(function (StudentPromotionRecord $record) use ($studentsById, $parentsById) {
return $this->presentAdminRow(
@@ -116,7 +117,6 @@ class PromotionQueryService
foreach ($rows as $row) {
$counts[(string) $row->promotion_status] = (int) $row->total;
}
return $counts;
}
@@ -133,34 +133,34 @@ class PromotionQueryService
}
/**
* @param Builder<StudentPromotionRecord> $query
* @param Builder<StudentPromotionRecord> $query
*/
private function applyFilters(Builder $query, array $filters): void
{
if (! empty($filters['status'])) {
if (!empty($filters['status'])) {
$statuses = is_array($filters['status']) ? $filters['status'] : [$filters['status']];
$statuses = array_values(array_filter($statuses, static fn ($s) => is_string($s) && $s !== ''));
if (! empty($statuses)) {
if (!empty($statuses)) {
$query->whereIn('promotion_status', $statuses);
}
}
if (! empty($filters['parent_action_required'])) {
if (!empty($filters['parent_action_required'])) {
$query->whereIn('promotion_status', StudentPromotionRecord::parentActionableStatuses());
}
if (! empty($filters['next_school_year'])) {
if (!empty($filters['next_school_year'])) {
$query->where('next_school_year', $filters['next_school_year']);
}
if (! empty($filters['current_school_year'])) {
if (!empty($filters['current_school_year'])) {
$query->where('current_school_year', $filters['current_school_year']);
}
if (! empty($filters['parent_id'])) {
if (!empty($filters['parent_id'])) {
$query->where('parent_id', (int) $filters['parent_id']);
}
if (! empty($filters['student_id'])) {
if (!empty($filters['student_id'])) {
$query->where('student_id', (int) $filters['student_id']);
}
if (! empty($filters['search'])) {
$search = '%'.strtolower((string) $filters['search']).'%';
if (!empty($filters['search'])) {
$search = '%' . strtolower((string) $filters['search']) . '%';
$query->whereIn('student_id', function ($sub) use ($search) {
$sub->select('id')
->from('students')
@@ -182,12 +182,12 @@ class PromotionQueryService
'promotion_id' => (int) $record->getKey(),
'student_id' => (int) $record->student_id,
'student_name' => $student
? trim((string) $student->firstname.' '.(string) $student->lastname)
? trim((string) $student->firstname . ' ' . (string) $student->lastname)
: null,
'school_id' => $student->school_id ?? null,
'parent_id' => $record->parent_id ? (int) $record->parent_id : null,
'parent_name' => $parent
? trim((string) $parent->firstname.' '.(string) $parent->lastname)
? trim((string) $parent->firstname . ' ' . (string) $parent->lastname)
: null,
'parent_email' => $parent->email ?? null,
'current_school_year' => $record->current_school_year,
@@ -19,7 +19,7 @@ use Illuminate\Support\Facades\Log;
*/
class PromotionReminderService
{
/** @var (callable(int, string, string, array):void)|null */
/** @var (callable(int $userId, string $title, string $body, array $channels):void)|null */
private $dispatcher;
public function __construct(
@@ -40,7 +40,7 @@ class PromotionReminderService
?string $message = null,
array $channels = ['in_app', 'email']
): PromotionReminderLog {
if (! in_array($reminderType, PromotionReminderLog::ALLOWED_TYPES, true)) {
if (!in_array($reminderType, PromotionReminderLog::ALLOWED_TYPES, true)) {
$reminderType = PromotionReminderLog::TYPE_MANUAL;
}
@@ -53,7 +53,7 @@ class PromotionReminderService
'%s is eligible for promotion to %s.%s Please complete enrollment as soon as possible.',
$studentName ?: 'Your child',
$level,
$deadline ? ' Deadline: '.$deadline.'.' : ''
$deadline ? ' Deadline: ' . $deadline . '.' : ''
);
$finalSubject = $subject ?: $defaultSubject;
@@ -83,7 +83,7 @@ class PromotionReminderService
$this->dispatch((int) $record->parent_id, $finalSubject, $finalMessage, $channels);
}
if (! $record->parent_notified_at) {
if (!$record->parent_notified_at) {
$record->parent_notified_at = now();
$record->save();
$this->audit->logParentNotified($record, $userId, $reminderType);
@@ -118,7 +118,6 @@ class PromotionReminderService
$type = $this->resolveReminderType($record, $now);
if ($type === null) {
$skipped++;
continue;
}
$alreadySent = PromotionReminderLog::query()
@@ -127,7 +126,6 @@ class PromotionReminderService
->exists();
if ($alreadySent) {
$skipped++;
continue;
}
try {
@@ -179,24 +177,22 @@ class PromotionReminderService
if ($elapsedSeconds * 2 >= $totalSeconds) {
return PromotionReminderLog::TYPE_HALFWAY;
}
return PromotionReminderLog::TYPE_FIRST;
}
private function studentName(StudentPromotionRecord $record): ?string
{
$student = Student::query()->find($record->student_id);
if (! $student) {
if (!$student) {
return null;
}
$name = trim((string) ($student->firstname ?? '').' '.(string) ($student->lastname ?? ''));
$name = trim((string) ($student->firstname ?? '') . ' ' . (string) ($student->lastname ?? ''));
return $name !== '' ? $name : null;
}
private function dispatch(int $userId, string $subject, string $message, array $channels): void
{
if (! $this->dispatcher) {
if (!$this->dispatcher) {
return;
}
try {
@@ -15,7 +15,9 @@ use RuntimeException;
*/
class PromotionStatusService
{
public function __construct(private PromotionAuditService $audit) {}
public function __construct(private PromotionAuditService $audit)
{
}
/**
* Map of allowed transitions from current → set of next statuses.
@@ -78,11 +80,10 @@ class PromotionStatusService
if ($from === $to) {
return true;
}
if (! in_array($to, StudentPromotionRecord::ALL_STATUSES, true)) {
if (!in_array($to, StudentPromotionRecord::ALL_STATUSES, true)) {
return false;
}
$allowed = self::ALLOWED_TRANSITIONS[$from] ?? [];
return in_array($to, $allowed, true);
}
@@ -97,7 +98,7 @@ class PromotionStatusService
?int $userId = null,
?string $notes = null
): StudentPromotionRecord {
if (! in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
if (!in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
throw new InvalidArgumentException(sprintf('Unknown promotion status "%s".', $newStatus));
}
@@ -105,7 +106,7 @@ class PromotionStatusService
if ($oldStatus === $newStatus) {
return $record;
}
if (! $this->canTransition($oldStatus, $newStatus)) {
if (!$this->canTransition($oldStatus, $newStatus)) {
throw new RuntimeException(sprintf(
'Transition from "%s" to "%s" is not allowed.',
$oldStatus,
@@ -134,7 +135,7 @@ class PromotionStatusService
?int $userId = null,
?string $notes = null
): StudentPromotionRecord {
if (! in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
if (!in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
throw new InvalidArgumentException(sprintf('Unknown promotion status "%s".', $newStatus));
}