@@ -14,11 +14,13 @@ class SchoolYearClosingController extends BaseController
|
||||
$targetId = $this->normalizeInt($this->request->getGet('target_school_year_id'));
|
||||
$preview = service('schoolYearClosing')->preview($id, $targetId);
|
||||
$promotionTable = $this->promotionTablePayload($preview['promotion']['rows'] ?? []);
|
||||
$carryForwardTable = $this->carryForwardTablePayload($preview['carry_forward'] ?? []);
|
||||
$latestBatch = service('schoolYearClosing')->latestBatch($id);
|
||||
|
||||
return view('school_years/closing_preview', [
|
||||
'preview' => $preview,
|
||||
'promotionTable' => $promotionTable,
|
||||
'carryForwardTable' => $carryForwardTable,
|
||||
'latestBatch' => $latestBatch,
|
||||
'missingCarryForwardInvoices' => $this->missingCarryForwardInvoiceCount($latestBatch),
|
||||
'schoolYears' => (new SchoolYearModel())->orderBy('name', 'DESC')->findAll(),
|
||||
@@ -118,13 +120,10 @@ class SchoolYearClosingController extends BaseController
|
||||
private function promotionTablePayload(array $rows): array
|
||||
{
|
||||
$allowedSorts = ['student', 'school_id', 'class', 'year_score', 'decision', 'source', 'queue', 'target', 'status'];
|
||||
$sort = (string) ($this->request->getGet('sort') ?? 'class');
|
||||
$sort = in_array($sort, $allowedSorts, true) ? $sort : 'class';
|
||||
$order = strtolower((string) ($this->request->getGet('order') ?? 'asc')) === 'desc' ? 'desc' : 'asc';
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$perPage = (int) ($this->request->getGet('per_page') ?? 25);
|
||||
$sort = 'class';
|
||||
$order = 'asc';
|
||||
$perPage = 25;
|
||||
$allowedPerPage = [10, 25, 50, 100];
|
||||
$perPage = in_array($perPage, $allowedPerPage, true) ? $perPage : 25;
|
||||
|
||||
usort($rows, function (array $a, array $b) use ($sort, $order): int {
|
||||
$comparison = $this->comparePromotionRows($a, $b, $sort);
|
||||
@@ -142,21 +141,18 @@ class SchoolYearClosingController extends BaseController
|
||||
});
|
||||
|
||||
$total = count($rows);
|
||||
$pageCount = max(1, (int) ceil($total / $perPage));
|
||||
$page = min($page, $pageCount);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
return [
|
||||
'rows' => array_slice($rows, $offset, $perPage),
|
||||
'rows' => $rows,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
'page' => $page,
|
||||
'page' => 1,
|
||||
'perPage' => $perPage,
|
||||
'total' => $total,
|
||||
'pageCount' => $pageCount,
|
||||
'pageCount' => 1,
|
||||
'allowedPerPage' => $allowedPerPage,
|
||||
'from' => $total === 0 ? 0 : $offset + 1,
|
||||
'to' => min($offset + $perPage, $total),
|
||||
'from' => $total === 0 ? 0 : 1,
|
||||
'to' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -198,6 +194,58 @@ class SchoolYearClosingController extends BaseController
|
||||
};
|
||||
}
|
||||
|
||||
private function carryForwardTablePayload(array $rows): array
|
||||
{
|
||||
$allowedSorts = ['family', 'parent', 'source_balance', 'credits', 'adjustments', 'net'];
|
||||
$sort = (string) ($this->request->getGet('cf_sort') ?? 'family');
|
||||
$sort = in_array($sort, $allowedSorts, true) ? $sort : 'family';
|
||||
$order = strtolower((string) ($this->request->getGet('cf_order') ?? 'asc')) === 'desc' ? 'desc' : 'asc';
|
||||
|
||||
usort($rows, function (array $a, array $b) use ($sort, $order): int {
|
||||
$comparison = $this->compareCarryForwardRows($a, $b, $sort);
|
||||
if ($comparison === 0 && $sort !== 'family') {
|
||||
$comparison = $this->compareCarryForwardRows($a, $b, 'family');
|
||||
}
|
||||
if ($comparison === 0) {
|
||||
$comparison = ((int) ($a['family_id'] ?? 0)) <=> ((int) ($b['family_id'] ?? 0));
|
||||
}
|
||||
|
||||
return $order === 'desc' ? -$comparison : $comparison;
|
||||
});
|
||||
|
||||
return [
|
||||
'rows' => $rows,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
];
|
||||
}
|
||||
|
||||
private function compareCarryForwardRows(array $a, array $b, string $sort): int
|
||||
{
|
||||
$numericSorts = ['source_balance', 'credits', 'adjustments', 'net'];
|
||||
$aValue = $this->carryForwardSortValue($a, $sort);
|
||||
$bValue = $this->carryForwardSortValue($b, $sort);
|
||||
|
||||
if (in_array($sort, $numericSorts, true)) {
|
||||
return (float) $aValue <=> (float) $bValue;
|
||||
}
|
||||
|
||||
return strnatcasecmp((string) $aValue, (string) $bValue);
|
||||
}
|
||||
|
||||
private function carryForwardSortValue(array $row, string $sort): mixed
|
||||
{
|
||||
return match ($sort) {
|
||||
'family' => (string) ($row['family'] ?? ''),
|
||||
'parent' => trim((string) ($row['parent'] ?? '') . ' ' . (string) ($row['parent_email'] ?? '')),
|
||||
'source_balance' => (float) ($row['source_balance'] ?? 0),
|
||||
'credits' => (float) ($row['credit_amount'] ?? 0),
|
||||
'adjustments' => (float) ($row['adjustment_amount'] ?? 0),
|
||||
'net' => (float) ($row['carry_forward_amount'] ?? 0),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
private function userId(): ?int
|
||||
{
|
||||
$id = session('user_id') ?? session('id');
|
||||
|
||||
@@ -31,6 +31,7 @@ use App\Models\GradingLockModel;
|
||||
use App\Models\BelowSixtyDecisionModel;
|
||||
use App\Models\StudentDecisionModel;
|
||||
use App\Services\NavbarService;
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
|
||||
//use App\Models\ScoreModel;
|
||||
|
||||
@@ -3399,13 +3400,16 @@ public function allDecisions()
|
||||
's.firstname',
|
||||
's.lastname',
|
||||
's.gender',
|
||||
's.dob',
|
||||
'ss.class_section_id',
|
||||
'cs.class_section_name',
|
||||
'c.class_name',
|
||||
'LOWER(TRIM(ss.semester)) AS sem_key',
|
||||
'ss.semester_score',
|
||||
])
|
||||
->join('students s', 's.id = ss.student_id', 'inner')
|
||||
->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left')
|
||||
->join('classes c', 'c.id = cs.class_id', 'left')
|
||||
->where('s.is_active', 1)
|
||||
->where('ss.school_year', $schoolYear)
|
||||
->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring'])
|
||||
@@ -3432,7 +3436,9 @@ public function allDecisions()
|
||||
'firstname' => $sr['firstname'] ?? '',
|
||||
'lastname' => $sr['lastname'] ?? '',
|
||||
'gender' => $sr['gender'] ?? '',
|
||||
'dob' => $sr['dob'] ?? '',
|
||||
'class_section_id' => (int)($sr['class_section_id'] ?? 0),
|
||||
'class_name' => $sr['class_name'] ?? '',
|
||||
'class_section_name' => $sr['class_section_name'] ?? '',
|
||||
'fall_score' => null,
|
||||
'spring_score' => null,
|
||||
@@ -3513,18 +3519,30 @@ public function allDecisions()
|
||||
$notes = '';
|
||||
}
|
||||
|
||||
$currentClassSectionName = trim((string)($info['class_section_name'] ?? ''));
|
||||
if ($currentClassSectionName === '' && isset($savedMap[$sid])) {
|
||||
$currentClassSectionName = trim((string)($savedMap[$sid]['class_section_name'] ?? ''));
|
||||
}
|
||||
$currentClassName = trim((string)($info['class_name'] ?? ''));
|
||||
if ($currentClassName === '') {
|
||||
$currentClassName = $this->classOnlyLabel($currentClassSectionName);
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'student_id' => $sid,
|
||||
'school_id' => $info['school_id'],
|
||||
'firstname' => $info['firstname'],
|
||||
'lastname' => $info['lastname'],
|
||||
'gender' => $info['gender'] ?? '',
|
||||
'dob' => $info['dob'] ?? '',
|
||||
'class_section_id' => (int)($info['class_section_id'] ?? 0),
|
||||
'class_section_name' => $info['class_section_name'],
|
||||
'class_name' => $currentClassName,
|
||||
'class_section_name' => $currentClassSectionName,
|
||||
'fall_score' => $fall,
|
||||
'spring_score' => $spring,
|
||||
'year_score' => $yearScore,
|
||||
'decision' => $decision,
|
||||
'next_year_placement' => $this->nextYearPlacementLabel($decision, $currentClassName, $currentClassSectionName, (string)($info['dob'] ?? ''), $schoolYear),
|
||||
'source' => $source,
|
||||
'notes' => $notes,
|
||||
'saved' => isset($savedMap[$sid]),
|
||||
@@ -3741,6 +3759,76 @@ public function generateAllDecisions()
|
||||
->with('status', "Decisions generated for {$savedCount} students.");
|
||||
}
|
||||
|
||||
private function nextYearPlacementLabel(
|
||||
?string $decision,
|
||||
string $currentClassName,
|
||||
string $currentClassSectionName,
|
||||
string $dob,
|
||||
string $schoolYear
|
||||
): string
|
||||
{
|
||||
$normalizedDecision = DeliberationDecision::normalize($decision);
|
||||
$classLabel = $this->classOnlyLabel($currentClassName !== '' ? $currentClassName : $currentClassSectionName);
|
||||
|
||||
if ($this->isKgClass($classLabel)) {
|
||||
$kgPlacement = $this->kgPlacementByComingSeptember($dob, $schoolYear);
|
||||
if ($kgPlacement !== '') {
|
||||
return $kgPlacement;
|
||||
}
|
||||
}
|
||||
|
||||
if ($normalizedDecision === DeliberationDecision::REPEAT_CLASS) {
|
||||
return $classLabel;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function classOnlyLabel(string $className): string
|
||||
{
|
||||
return trim((string) preg_replace('/-.+$/', '', $className));
|
||||
}
|
||||
|
||||
private function isKgClass(string $className): bool
|
||||
{
|
||||
$value = strtoupper(trim($className));
|
||||
|
||||
return preg_match('/(^|[^A-Z0-9])KG([^A-Z0-9]|$)/', $value) === 1 || str_contains($value, 'KINDERGARTEN');
|
||||
}
|
||||
|
||||
private function kgPlacementByComingSeptember(string $dob, string $schoolYear): string
|
||||
{
|
||||
$dob = trim($dob);
|
||||
if ($dob === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$birthDate = new \DateTimeImmutable($dob);
|
||||
$cutoff = $this->comingSeptemberFirstCutoff($schoolYear);
|
||||
} catch (\Throwable) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($birthDate > $cutoff) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $birthDate->diff($cutoff)->y >= 6 ? '1' : 'KG';
|
||||
}
|
||||
|
||||
private function comingSeptemberFirstCutoff(string $schoolYear): \DateTimeImmutable
|
||||
{
|
||||
if (preg_match('/^(\d{4})-\d{4}$/', $schoolYear, $matches) === 1) {
|
||||
return new \DateTimeImmutable(((int) $matches[1] + 1) . '-09-01');
|
||||
}
|
||||
|
||||
$today = new \DateTimeImmutable('today');
|
||||
$cutoff = new \DateTimeImmutable($today->format('Y') . '-09-01');
|
||||
|
||||
return $today <= $cutoff ? $cutoff : $cutoff->modify('+1 year');
|
||||
}
|
||||
|
||||
private function calculateTrophyThreshold(array $scores, float $percentile = 75.0): array
|
||||
{
|
||||
$scores = array_values(array_filter(
|
||||
|
||||
Reference in New Issue
Block a user