fix financial and certificates
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Promotions\Placement;
|
||||
|
||||
class BalancedSectionPlacementService
|
||||
{
|
||||
public const ALGORITHM = 'score_band_balanced_v1';
|
||||
|
||||
public function assign(array $students, int $capacity): array
|
||||
{
|
||||
$total = count($students);
|
||||
if ($total === 0) {
|
||||
return [
|
||||
'section_count' => 0,
|
||||
'target_sizes' => [],
|
||||
'sections' => [],
|
||||
'assignments' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$sectionCount = $total <= $capacity ? 1 : (int) ceil($total / $capacity);
|
||||
$targetSizes = $this->targetSizes($total, $sectionCount);
|
||||
$sections = [];
|
||||
for ($i = 1; $i <= $sectionCount; $i++) {
|
||||
$sections[$i] = [
|
||||
'section_index' => $i,
|
||||
'target_size' => $targetSizes[$i],
|
||||
'total' => 0,
|
||||
'bands' => ['A' => 0, 'B' => 0, 'C' => 0, 'D' => 0],
|
||||
'students' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$grouped = ['A' => [], 'B' => [], 'C' => [], 'D' => []];
|
||||
foreach ($students as $student) {
|
||||
$band = $student['score_band'] ?? 'D';
|
||||
$grouped[$band][] = $student;
|
||||
}
|
||||
|
||||
foreach (array_keys($grouped) as $band) {
|
||||
usort($grouped[$band], function (array $a, array $b): int {
|
||||
$scoreA = $a['final_score'] ?? $a['placement_score'] ?? -1;
|
||||
$scoreB = $b['final_score'] ?? $b['placement_score'] ?? -1;
|
||||
if ($scoreA !== $scoreB) {
|
||||
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']);
|
||||
});
|
||||
}
|
||||
|
||||
$assignments = [];
|
||||
$order = 1;
|
||||
foreach (['A', 'B', 'C', 'D'] as $band) {
|
||||
foreach ($grouped[$band] as $student) {
|
||||
$sectionIndex = $this->selectSection($sections, $band);
|
||||
$sections[$sectionIndex]['total']++;
|
||||
$sections[$sectionIndex]['bands'][$band]++;
|
||||
$sections[$sectionIndex]['students'][] = (int) $student['student_id'];
|
||||
$student['planned_section_index'] = $sectionIndex;
|
||||
$student['assignment_order'] = $order++;
|
||||
$assignments[] = $student;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'section_count' => $sectionCount,
|
||||
'target_sizes' => $targetSizes,
|
||||
'sections' => array_values($sections),
|
||||
'assignments' => $assignments,
|
||||
];
|
||||
}
|
||||
|
||||
private function targetSizes(int $total, int $sectionCount): array
|
||||
{
|
||||
$base = intdiv($total, $sectionCount);
|
||||
$remainder = $total % $sectionCount;
|
||||
$sizes = [];
|
||||
for ($i = 1; $i <= $sectionCount; $i++) {
|
||||
$sizes[$i] = $base + ($i <= $remainder ? 1 : 0);
|
||||
}
|
||||
return $sizes;
|
||||
}
|
||||
|
||||
private function selectSection(array $sections, string $band): int
|
||||
{
|
||||
$eligible = array_filter($sections, fn ($s) => $s['total'] < $s['target_size']);
|
||||
if (empty($eligible)) {
|
||||
$eligible = $sections;
|
||||
}
|
||||
|
||||
uasort($eligible, function (array $a, array $b) use ($band): int {
|
||||
$cmp = $a['total'] <=> $b['total'];
|
||||
if ($cmp !== 0) {
|
||||
return $cmp;
|
||||
}
|
||||
$cmp = ($a['bands'][$band] ?? 0) <=> ($b['bands'][$band] ?? 0);
|
||||
if ($cmp !== 0) {
|
||||
return $cmp;
|
||||
}
|
||||
return $a['section_index'] <=> $b['section_index'];
|
||||
});
|
||||
|
||||
return (int) array_key_first($eligible);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Promotions\Placement;
|
||||
|
||||
use App\Models\StudentPromotionRecord;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PlacementPoolBuilder
|
||||
{
|
||||
public function __construct(private ScoreBandClassifier $bands)
|
||||
{
|
||||
}
|
||||
|
||||
public function build(string $fromSchoolYear, string $toSchoolYear, ?int $toGradeLevelId = null): array
|
||||
{
|
||||
$pool = [];
|
||||
$exceptions = $this->exceptionBuckets($fromSchoolYear, $toSchoolYear);
|
||||
|
||||
$returning = StudentPromotionRecord::query()
|
||||
->from('student_promotion_records as r')
|
||||
->join('students as s', 's.id', '=', 'r.student_id')
|
||||
->leftJoin('student_decisions as d', function ($join) use ($fromSchoolYear) {
|
||||
$join->on('d.student_id', '=', 'r.student_id')
|
||||
->where('d.school_year', '=', $fromSchoolYear);
|
||||
})
|
||||
->where('r.current_school_year', $fromSchoolYear)
|
||||
->where('r.next_school_year', $toSchoolYear)
|
||||
->where('r.enrollment_status', StudentPromotionRecord::ENROLLMENT_COMPLETED)
|
||||
->where('r.promotion_status', StudentPromotionRecord::STATUS_PROMOTED_AND_ENROLLED)
|
||||
->when($toGradeLevelId !== null, fn ($q) => $q->where('r.promoted_level_id', $toGradeLevelId))
|
||||
->selectRaw('r.*, s.firstname, s.lastname, s.is_active, d.id as decision_id, d.decision, d.year_score')
|
||||
->orderBy('s.lastname')
|
||||
->orderBy('s.firstname')
|
||||
->orderBy('s.id')
|
||||
->get();
|
||||
|
||||
foreach ($returning as $row) {
|
||||
$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 ?? ''))) {
|
||||
$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),
|
||||
'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,
|
||||
'placement_score' => null,
|
||||
'score_band' => $band,
|
||||
'placement_band_reason' => 'returning_student_final_score',
|
||||
];
|
||||
}
|
||||
|
||||
$newStudents = DB::table('enrollments as e')
|
||||
->join('students as s', 's.id', '=', 'e.student_id')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'e.class_section_id')
|
||||
->where('e.school_year', $toSchoolYear)
|
||||
->where('s.is_new', 1)
|
||||
->where('e.admission_status', 'accepted')
|
||||
->whereIn('e.enrollment_status', ['enrolled', 'payment pending'])
|
||||
->when($toGradeLevelId !== null, function ($q) use ($toGradeLevelId) {
|
||||
$q->where(function ($inner) use ($toGradeLevelId) {
|
||||
$inner->whereNull('e.class_section_id')
|
||||
->orWhere('cs.class_id', $toGradeLevelId);
|
||||
});
|
||||
})
|
||||
->selectRaw('e.id as enrollment_id, e.student_id, s.firstname, s.lastname, s.is_active')
|
||||
->orderBy('s.lastname')
|
||||
->orderBy('s.firstname')
|
||||
->orderBy('s.id')
|
||||
->get();
|
||||
|
||||
foreach ($newStudents as $row) {
|
||||
$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),
|
||||
'source_decision_id' => null,
|
||||
'source_enrollment_id' => (int) $row->enrollment_id,
|
||||
'final_score' => null,
|
||||
'placement_score' => null,
|
||||
'score_band' => 'D',
|
||||
'placement_band_reason' => 'new_student_default_band',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'pool' => $pool,
|
||||
'exceptions' => array_map(fn ($ids) => array_values(array_unique($ids)), $exceptions),
|
||||
];
|
||||
}
|
||||
|
||||
private function exceptionBuckets(string $fromSchoolYear, string $toSchoolYear): array
|
||||
{
|
||||
$buckets = [
|
||||
'passed_but_parent_enrollment_missing' => [],
|
||||
'passed_and_parent_enrollment_completed' => [],
|
||||
'failed_retained' => [],
|
||||
'missing_student_decision' => [],
|
||||
'pending_student_decision' => [],
|
||||
'decision_conflicts_with_enrollment_request' => [],
|
||||
'withdrawn_or_inactive_student' => [],
|
||||
'duplicate_target_year_placement' => [],
|
||||
];
|
||||
|
||||
$rows = DB::table('student_decisions as d')
|
||||
->leftJoin('student_promotion_records as r', function ($join) use ($toSchoolYear) {
|
||||
$join->on('r.student_id', '=', 'd.student_id')
|
||||
->where('r.next_school_year', '=', $toSchoolYear);
|
||||
})
|
||||
->where('d.school_year', $fromSchoolYear)
|
||||
->select('d.student_id', 'd.decision', 'r.enrollment_status')
|
||||
->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decision = strtolower(trim((string) $row->decision));
|
||||
$studentId = (int) $row->student_id;
|
||||
if ($this->isPromotedDecision($decision)) {
|
||||
if ($row->enrollment_status === StudentPromotionRecord::ENROLLMENT_COMPLETED) {
|
||||
$buckets['passed_and_parent_enrollment_completed'][] = $studentId;
|
||||
} else {
|
||||
$buckets['passed_but_parent_enrollment_missing'][] = $studentId;
|
||||
}
|
||||
} elseif (in_array($decision, ['failed', 'fail', 'retained', 'repeat', 'repeated_level'], true)) {
|
||||
$buckets['failed_retained'][] = $studentId;
|
||||
} elseif ($decision === '' || $decision === 'pending') {
|
||||
$buckets['pending_student_decision'][] = $studentId;
|
||||
}
|
||||
}
|
||||
|
||||
return $buckets;
|
||||
}
|
||||
|
||||
private function alreadyPlaced(int $studentId, string $schoolYear): bool
|
||||
{
|
||||
return DB::table('student_class')
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('is_event_only', 0)
|
||||
->exists();
|
||||
}
|
||||
|
||||
private function isPromotedDecision(string $decision): bool
|
||||
{
|
||||
$normalized = strtolower(trim($decision));
|
||||
return in_array($normalized, ['passed', 'pass', 'promoted', 'promote', 'eligible_to_continue'], true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Promotions\Placement;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class PromotionSectionCapacityService
|
||||
{
|
||||
public const CONFIG_KEY = 'promotion.section_capacity';
|
||||
public const DEFAULT_CAPACITY = 20;
|
||||
|
||||
public function capacity(): array
|
||||
{
|
||||
$raw = Configuration::getConfigValueByKey(self::CONFIG_KEY);
|
||||
$usedDefault = false;
|
||||
|
||||
if ($raw === null || trim((string) $raw) === '') {
|
||||
$usedDefault = true;
|
||||
Log::warning('Missing promotion section capacity configuration; using fallback.', [
|
||||
'config_key' => self::CONFIG_KEY,
|
||||
'fallback' => self::DEFAULT_CAPACITY,
|
||||
]);
|
||||
$value = self::DEFAULT_CAPACITY;
|
||||
} elseif (!ctype_digit((string) $raw)) {
|
||||
throw new RuntimeException('promotion.section_capacity must be a positive integer.');
|
||||
} else {
|
||||
$value = (int) $raw;
|
||||
}
|
||||
|
||||
if ($value <= 0) {
|
||||
throw new RuntimeException('promotion.section_capacity must be greater than zero.');
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => self::CONFIG_KEY,
|
||||
'value' => $value,
|
||||
'used_default' => $usedDefault,
|
||||
'warnings' => $usedDefault ? ['promotion.section_capacity missing; fallback capacity 20 used.'] : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Promotions\Placement;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ScoreBandClassifier
|
||||
{
|
||||
public function fromScore(?float $score, bool $allowNull = false): ?string
|
||||
{
|
||||
if ($score === null) {
|
||||
return $allowNull ? null : 'D';
|
||||
}
|
||||
if ($score >= 90.0 && $score <= 100.0) {
|
||||
return 'A';
|
||||
}
|
||||
if ($score >= 80.0) {
|
||||
return 'B';
|
||||
}
|
||||
if ($score >= 70.0) {
|
||||
return 'C';
|
||||
}
|
||||
if ($score >= 60.0) {
|
||||
return 'D';
|
||||
}
|
||||
|
||||
throw new RuntimeException('Scores below 60 are not eligible for automatic placement.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Promotions\Placement;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\SectionPlacementBatch;
|
||||
use App\Models\SectionPlacementBatchStudent;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
class SectionPlacementPreviewService
|
||||
{
|
||||
public function __construct(
|
||||
private PromotionSectionCapacityService $capacityService,
|
||||
private PlacementPoolBuilder $poolBuilder,
|
||||
private BalancedSectionPlacementService $placementService
|
||||
) {
|
||||
}
|
||||
|
||||
public function createDraft(
|
||||
string $fromSchoolYear,
|
||||
string $toSchoolYear,
|
||||
?int $fromGradeLevelId = null,
|
||||
?int $toGradeLevelId = null,
|
||||
?int $createdBy = null
|
||||
): SectionPlacementBatch {
|
||||
$capacityInfo = $this->capacityService->capacity();
|
||||
$poolResult = $this->poolBuilder->build($fromSchoolYear, $toSchoolYear, $toGradeLevelId);
|
||||
$placement = $this->placementService->assign($poolResult['pool'], (int) $capacityInfo['value']);
|
||||
|
||||
$snapshot = [
|
||||
'capacity' => $capacityInfo,
|
||||
'target_section_sizes' => $placement['target_sizes'],
|
||||
'sections' => $placement['sections'],
|
||||
'excluded_students' => $poolResult['exceptions'],
|
||||
'warnings' => $capacityInfo['warnings'],
|
||||
'new_students_defaulted_to_d_band' => array_values(array_map(
|
||||
fn ($s) => $s['student_id'],
|
||||
array_filter($placement['assignments'], fn ($s) => ($s['placement_band_reason'] ?? null) === 'new_student_default_band')
|
||||
)),
|
||||
];
|
||||
|
||||
return DB::transaction(function () use ($fromSchoolYear, $toSchoolYear, $fromGradeLevelId, $toGradeLevelId, $createdBy, $capacityInfo, $placement, $snapshot) {
|
||||
$batch = SectionPlacementBatch::query()->create([
|
||||
'from_school_year' => $fromSchoolYear,
|
||||
'to_school_year' => $toSchoolYear,
|
||||
'from_grade_level_id' => $fromGradeLevelId,
|
||||
'to_grade_level_id' => $toGradeLevelId,
|
||||
'section_capacity_used' => (int) $capacityInfo['value'],
|
||||
'total_students' => count($placement['assignments']),
|
||||
'section_count' => (int) $placement['section_count'],
|
||||
'algorithm' => BalancedSectionPlacementService::ALGORITHM,
|
||||
'configuration_snapshot_json' => json_encode([
|
||||
PromotionSectionCapacityService::CONFIG_KEY => (int) $capacityInfo['value'],
|
||||
'placement.algorithm' => BalancedSectionPlacementService::ALGORITHM,
|
||||
]),
|
||||
'preview_snapshot_json' => json_encode($snapshot),
|
||||
'created_by' => $createdBy,
|
||||
'status' => SectionPlacementBatch::STATUS_DRAFT,
|
||||
]);
|
||||
|
||||
foreach ($placement['assignments'] as $assignment) {
|
||||
SectionPlacementBatchStudent::query()->create([
|
||||
'batch_id' => $batch->getKey(),
|
||||
'student_id' => $assignment['student_id'],
|
||||
'student_type' => $assignment['student_type'],
|
||||
'source_decision_id' => $assignment['source_decision_id'],
|
||||
'source_enrollment_id' => $assignment['source_enrollment_id'],
|
||||
'final_score' => $assignment['final_score'],
|
||||
'placement_score' => $assignment['placement_score'],
|
||||
'score_band' => $assignment['score_band'],
|
||||
'placement_band_reason' => $assignment['placement_band_reason'],
|
||||
'planned_section_index' => $assignment['planned_section_index'],
|
||||
'assignment_order' => $assignment['assignment_order'],
|
||||
'was_override' => false,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $batch->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public function previewPayload(SectionPlacementBatch $batch): array
|
||||
{
|
||||
return [
|
||||
'batch' => $batch->toArray(),
|
||||
'preview' => $batch->preview_snapshot_json ? json_decode((string) $batch->preview_snapshot_json, true) : null,
|
||||
'students' => $batch->students()->orderBy('planned_section_index')->orderBy('assignment_order')->get()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
public function finalize(int $batchId, ?int $userId = null): SectionPlacementBatch
|
||||
{
|
||||
$batch = SectionPlacementBatch::query()->findOrFail($batchId);
|
||||
if ($batch->status !== SectionPlacementBatch::STATUS_DRAFT) {
|
||||
throw new RuntimeException('Only draft placement batches can be finalized.');
|
||||
}
|
||||
if ((int) $batch->total_students !== $batch->students()->count()) {
|
||||
throw new RuntimeException('Draft batch student count does not match batch total.');
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($batch, $userId) {
|
||||
$semester = Configuration::getConfigValueByKey('semester') ?: 'Fall';
|
||||
$sectionMap = $this->ensureSections($batch, $semester);
|
||||
|
||||
$seen = [];
|
||||
$students = $batch->students()->orderBy('assignment_order')->get();
|
||||
foreach ($students as $student) {
|
||||
$studentId = (int) $student->student_id;
|
||||
if (isset($seen[$studentId])) {
|
||||
throw new RuntimeException('Student appears more than once in placement batch.');
|
||||
}
|
||||
$seen[$studentId] = true;
|
||||
|
||||
$duplicate = DB::table('student_class')
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $batch->to_school_year)
|
||||
->where('is_event_only', 0)
|
||||
->exists();
|
||||
if ($duplicate) {
|
||||
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 . '.');
|
||||
}
|
||||
|
||||
DB::table('student_class')->insert([
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $sectionId,
|
||||
'is_event_only' => 0,
|
||||
'semester' => $semester,
|
||||
'school_year' => $batch->to_school_year,
|
||||
'description' => 'Finalized from section placement batch #' . $batch->getKey(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
$student->assigned_section_id = $sectionId;
|
||||
$student->save();
|
||||
}
|
||||
|
||||
$batch->status = SectionPlacementBatch::STATUS_FINALIZED;
|
||||
$batch->finalized_by = $userId;
|
||||
$batch->finalized_at = now();
|
||||
$batch->save();
|
||||
|
||||
return $batch->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private function ensureSections(SectionPlacementBatch $batch, string $semester): array
|
||||
{
|
||||
if (!$batch->to_grade_level_id) {
|
||||
throw new RuntimeException('to_grade_level_id is required to finalize placement sections.');
|
||||
}
|
||||
|
||||
$map = [];
|
||||
$classId = (int) $batch->to_grade_level_id;
|
||||
$sectionCount = (int) $batch->section_count;
|
||||
$maxSectionId = (int) DB::table('classSection')->max('class_section_id');
|
||||
$letters = range('A', 'Z');
|
||||
|
||||
for ($i = 1; $i <= $sectionCount; $i++) {
|
||||
$name = $this->sectionName($classId, $letters[$i - 1] ?? (string) $i, $batch->to_school_year);
|
||||
$existing = DB::table('classSection')
|
||||
->where('class_id', $classId)
|
||||
->where('class_section_name', $name)
|
||||
->where(function ($q) use ($batch) {
|
||||
$q->where('school_year', $batch->to_school_year)->orWhereNull('school_year');
|
||||
})
|
||||
->value('class_section_id');
|
||||
|
||||
if ($existing) {
|
||||
$map[$i] = (int) $existing;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sectionId = ++$maxSectionId;
|
||||
DB::table('classSection')->insert([
|
||||
'class_id' => $classId,
|
||||
'class_section_id' => $sectionId,
|
||||
'class_section_name' => $name,
|
||||
'semester' => $semester,
|
||||
'school_year' => $batch->to_school_year,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$map[$i] = $sectionId;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user