fix parent pages and teachers and admin
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -179,10 +179,8 @@ class SchoolYearClosureService
|
||||
$promotionCounts = is_array($metadata['promotion_counts'] ?? null)
|
||||
? $metadata['promotion_counts']
|
||||
: [];
|
||||
$balanceCounts = is_array($metadata['balance_counts'] ?? null)
|
||||
? $metadata['balance_counts']
|
||||
: [];
|
||||
$transferTotals = $this->transferTotalsForClosedYear($year->name, $newYearName !== '' ? $newYearName : null);
|
||||
$invoiceBalanceTotals = $this->positiveInvoiceBalanceTotals($year->name);
|
||||
|
||||
$warnings = [];
|
||||
if (! $audit) {
|
||||
@@ -207,14 +205,13 @@ class SchoolYearClosureService
|
||||
'students_graduated' => (int) ($promotionCounts['graduate'] ?? 0),
|
||||
'students_transferred' => (int) ($promotionCounts['transfer'] ?? 0),
|
||||
'students_withdrawn' => (int) ($promotionCounts['withdraw'] ?? 0),
|
||||
'parents_with_unpaid_balances' => (int) ($balanceCounts['transferred_parents'] ?? $transferTotals['parents_with_unpaid_balances']),
|
||||
'total_unpaid_balance_transferred' => round((float) ($balanceCounts['transferred_amount'] ?? $transferTotals['total_unpaid_balance_transferred']), 2),
|
||||
'parents_with_credit_balances' => (int) ($balanceCounts['credit_parents'] ?? $transferTotals['parents_with_credit_balances']),
|
||||
'total_credit_carried_or_reported' => round((float) ($balanceCounts['credit_amount'] ?? $transferTotals['total_credit_carried_or_reported']), 2),
|
||||
'total_credit_transferred' => round((float) ($balanceCounts['credit_amount'] ?? $transferTotals['total_credit_carried_or_reported']), 2),
|
||||
'parents_with_unpaid_balances' => $invoiceBalanceTotals['parents_with_unpaid_balances'],
|
||||
'total_unpaid_balance_transferred' => $invoiceBalanceTotals['total_unpaid_balance'],
|
||||
'parents_with_credit_balances' => $transferTotals['parents_with_credit_balances'],
|
||||
'total_credit_carried_or_reported' => $transferTotals['total_credit_carried_or_reported'],
|
||||
'total_credit_transferred' => $transferTotals['total_credit_carried_or_reported'],
|
||||
'net_balance_impact' => round(
|
||||
(float) ($balanceCounts['transferred_amount'] ?? $transferTotals['total_unpaid_balance_transferred'])
|
||||
- (float) ($balanceCounts['credit_amount'] ?? $transferTotals['total_credit_carried_or_reported']),
|
||||
$invoiceBalanceTotals['total_unpaid_balance'] - $transferTotals['total_credit_carried_or_reported'],
|
||||
2
|
||||
),
|
||||
],
|
||||
@@ -458,7 +455,8 @@ class SchoolYearClosureService
|
||||
|
||||
return $this->buildParentBalanceRows(
|
||||
$year->name,
|
||||
$toSchoolYear ?: ($this->resolver->suggestNextName($year->name) ?? $year->name)
|
||||
$toSchoolYear ?: ($this->resolver->suggestNextName($year->name) ?? $year->name),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -680,6 +678,8 @@ class SchoolYearClosureService
|
||||
$action = 'graduate';
|
||||
} elseif (in_array($decisionText, ['retained', 'repeat', 'repeated', 'not promoted'], true) || ($score !== null && $score < 60)) {
|
||||
$action = 'repeat';
|
||||
} elseif ($this->isPassingSeventeenYearOld($student, $decisionText, $score)) {
|
||||
$action = 'graduate';
|
||||
} elseif (($progression['is_terminal'] ?? false) === true) {
|
||||
$action = 'graduate';
|
||||
} elseif ($decisionText !== '' || $score !== null) {
|
||||
@@ -694,6 +694,9 @@ class SchoolYearClosureService
|
||||
} elseif ($action === 'promote') {
|
||||
$targetLevelId = $progression['next_level_id'] ?? null;
|
||||
$targetLevelName = $progression['next_level_name'] ?? null;
|
||||
if ($this->isKindergartenAgeReadyForGradeOne($student, $currentClass, $section)) {
|
||||
[$targetLevelId, $targetLevelName] = $this->resolveGradeOneTarget();
|
||||
}
|
||||
if ($targetLevelId === null) {
|
||||
$action = 'hold';
|
||||
$warnings[] = 'No next grade mapping exists for this student.';
|
||||
@@ -739,11 +742,61 @@ class SchoolYearClosureService
|
||||
];
|
||||
}
|
||||
|
||||
private function buildParentBalanceRows(string $fromSchoolYear, string $toSchoolYear): array
|
||||
private function isPassingSeventeenYearOld(object $student, string $decisionText, ?float $score): bool
|
||||
{
|
||||
$invoices = $this->eligibleCarryoverInvoiceQuery($fromSchoolYear)
|
||||
->whereRaw('ABS(COALESCE(balance, 0)) > 0.01')
|
||||
->get(['id', 'parent_id', 'balance']);
|
||||
$age = (int) ($student->age ?? 0);
|
||||
if ($age !== 17) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($score !== null) {
|
||||
return $score >= 60;
|
||||
}
|
||||
|
||||
return str_contains($decisionText, 'promot')
|
||||
|| str_contains($decisionText, 'pass');
|
||||
}
|
||||
|
||||
private function isKindergartenAgeReadyForGradeOne(object $student, ?object $currentClass, ?object $section): bool
|
||||
{
|
||||
$age = (int) ($student->age ?? 0);
|
||||
if ($age + 1 < 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$className = strtolower(trim((string) ($currentClass->class_name ?? '')));
|
||||
$sectionName = strtolower(trim((string) ($section->class_section_name ?? '')));
|
||||
|
||||
return in_array($className, ['kg', 'kindergarten'], true)
|
||||
|| in_array($sectionName, ['kg', 'kindergarten'], true);
|
||||
}
|
||||
|
||||
private function resolveGradeOneTarget(): array
|
||||
{
|
||||
$target = DB::table('classes')
|
||||
->whereIn(DB::raw('LOWER(class_name)'), ['1', 'grade 1', 'class 1'])
|
||||
->orderByRaw("CASE WHEN class_name = '1' THEN 0 ELSE 1 END")
|
||||
->orderBy('id')
|
||||
->first();
|
||||
|
||||
return $target
|
||||
? [(int) $target->id, (string) $target->class_name]
|
||||
: [null, null];
|
||||
}
|
||||
|
||||
private function buildParentBalanceRows(string $fromSchoolYear, string $toSchoolYear, bool $includeCreditOnlyRows = true): array
|
||||
{
|
||||
$invoiceQuery = $includeCreditOnlyRows
|
||||
? $this->eligibleCarryoverInvoiceQuery($fromSchoolYear)
|
||||
: DB::table('invoices')->where('school_year', $fromSchoolYear);
|
||||
|
||||
if ($includeCreditOnlyRows) {
|
||||
$invoiceQuery->whereRaw('ABS(COALESCE(balance, 0)) > 0.01');
|
||||
} else {
|
||||
$invoiceQuery->whereRaw('COALESCE(balance, 0) > 0.01');
|
||||
}
|
||||
|
||||
$invoices = $invoiceQuery->get(['id', 'parent_id', 'balance']);
|
||||
|
||||
$balancesByParent = [];
|
||||
foreach ($invoices as $invoice) {
|
||||
@@ -801,6 +854,18 @@ class SchoolYearClosureService
|
||||
->first()
|
||||
: null;
|
||||
|
||||
if (! $includeCreditOnlyRows && $positive < 0.01) {
|
||||
if ($credit > 0.01) {
|
||||
$summary['parents_with_credit_balance']++;
|
||||
$summary['total_credit_balance'] += $credit;
|
||||
}
|
||||
if (abs($net) > 0.01) {
|
||||
$summary['parents_with_net_non_zero_balance']++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = [
|
||||
'parent_id' => $parentId,
|
||||
'parent_name' => $this->resolveParentName($parentId),
|
||||
@@ -808,9 +873,15 @@ class SchoolYearClosureService
|
||||
'unpaid_invoice_count' => count($balanceRow['positive_invoice_ids']),
|
||||
'positive_unpaid_balance' => $positive,
|
||||
'old_unpaid_balance' => $positive,
|
||||
'old_unpaid_amount' => $positive,
|
||||
'credit_balance' => $credit,
|
||||
'net_balance' => $net,
|
||||
'amount_to_transfer' => $positive,
|
||||
'old_school_year' => $fromSchoolYear,
|
||||
'new_school_year' => $toSchoolYear,
|
||||
'new_old_balance_invoice_id' => $existing?->new_invoice_id ?? $existing?->old_balance_invoice_id ?? null,
|
||||
'transfer_date' => optional($existing?->created_at)->toDateTimeString(),
|
||||
'created_by' => $existing?->created_by,
|
||||
'transfer_status' => $existing?->status ?? ($positive > 0.01 ? 'pending' : 'credit_reported'),
|
||||
'existing_transfer_id' => $existing?->id,
|
||||
'existing_transfer_conflict' => $existing !== null,
|
||||
@@ -845,6 +916,9 @@ class SchoolYearClosureService
|
||||
$summary['parents_with_non_zero_balance'] = $summary['parents_with_net_non_zero_balance'];
|
||||
$summary['total_old_unpaid_balance'] = $summary['total_positive_unpaid_balance'];
|
||||
$summary['net_balance_to_transfer'] = $summary['net_balance_impact'];
|
||||
$summary['parent_count'] = $summary['parents_with_positive_balance'];
|
||||
$summary['total_transferred'] = $summary['total_positive_unpaid_balance'];
|
||||
$summary['total_credit'] = $summary['total_credit_balance'];
|
||||
|
||||
return [
|
||||
'from_school_year' => $fromSchoolYear,
|
||||
@@ -858,7 +932,19 @@ class SchoolYearClosureService
|
||||
{
|
||||
return DB::table('invoices')
|
||||
->where('school_year', $schoolYear)
|
||||
->whereNotIn(DB::raw("LOWER(COALESCE(status, ''))"), ['void', 'voided', 'cancelled', 'canceled', 'draft', 'pending']);
|
||||
->whereNotIn(DB::raw("LOWER(COALESCE(status, ''))"), [
|
||||
'void',
|
||||
'voided',
|
||||
'cancelled',
|
||||
'canceled',
|
||||
'draft',
|
||||
'pending',
|
||||
'paid',
|
||||
'full',
|
||||
'full payment',
|
||||
'complete',
|
||||
'completed',
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveTargetSection(?int $targetLevelId, string $schoolYear): array
|
||||
@@ -1252,10 +1338,18 @@ class SchoolYearClosureService
|
||||
return $totals;
|
||||
}
|
||||
|
||||
$columns = ['amount'];
|
||||
if (Schema::hasColumn('parent_balance_transfers', 'source_summary_json')) {
|
||||
$columns[] = 'source_summary_json';
|
||||
}
|
||||
if (Schema::hasColumn('parent_balance_transfers', 'source_summary')) {
|
||||
$columns[] = 'source_summary';
|
||||
}
|
||||
|
||||
$rows = DB::table('parent_balance_transfers')
|
||||
->where('from_school_year', $fromSchoolYear)
|
||||
->when($toSchoolYear !== null && trim($toSchoolYear) !== '', fn ($query) => $query->where('to_school_year', $toSchoolYear))
|
||||
->get(['amount', 'source_summary_json', 'source_summary']);
|
||||
->get($columns);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$amount = round((float) ($row->amount ?? 0), 2);
|
||||
@@ -1283,6 +1377,21 @@ class SchoolYearClosureService
|
||||
return $totals;
|
||||
}
|
||||
|
||||
private function positiveInvoiceBalanceTotals(string $schoolYear): array
|
||||
{
|
||||
$rows = DB::table('invoices')
|
||||
->where('school_year', $schoolYear)
|
||||
->whereRaw('COALESCE(balance, 0) > 0.01')
|
||||
->select('parent_id', DB::raw('SUM(COALESCE(balance, 0)) as total_balance'))
|
||||
->groupBy('parent_id')
|
||||
->get();
|
||||
|
||||
return [
|
||||
'parents_with_unpaid_balances' => $rows->count(),
|
||||
'total_unpaid_balance' => round((float) $rows->sum('total_balance'), 2),
|
||||
];
|
||||
}
|
||||
|
||||
private function decodeTransferSourceSummary(object $row): array
|
||||
{
|
||||
$raw = $row->source_summary_json ?? $row->source_summary ?? null;
|
||||
|
||||
Reference in New Issue
Block a user