fix financial and certificates
This commit is contained in:
@@ -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