This commit is contained in:
@@ -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'] ?? []);
|
||||
$latestBatch = service('schoolYearClosing')->latestBatch($id);
|
||||
|
||||
return view('school_years/closing_preview', [
|
||||
'preview' => $preview,
|
||||
'promotionTable' => $promotionTable,
|
||||
'latestBatch' => service('schoolYearClosing')->latestBatch($id),
|
||||
'latestBatch' => $latestBatch,
|
||||
'missingCarryForwardInvoices' => $this->missingCarryForwardInvoiceCount($latestBatch),
|
||||
'schoolYears' => (new SchoolYearModel())->orderBy('name', 'DESC')->findAll(),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
@@ -26,6 +28,23 @@ class SchoolYearClosingController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function previewByName(string $name)
|
||||
{
|
||||
try {
|
||||
$year = (new SchoolYearModel())
|
||||
->where('name', rawurldecode($name))
|
||||
->first();
|
||||
|
||||
if ($year === null) {
|
||||
return redirect()->to('/administrator/school-years')->with('error', 'School year was not found.');
|
||||
}
|
||||
|
||||
return $this->preview((int) $year['id']);
|
||||
} catch (Throwable $e) {
|
||||
return redirect()->to('/administrator/school-years')->with('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function start(int $id)
|
||||
{
|
||||
try {
|
||||
@@ -76,6 +95,26 @@ class SchoolYearClosingController extends BaseController
|
||||
return is_numeric($value) && (int) $value > 0 ? (int) $value : null;
|
||||
}
|
||||
|
||||
private function missingCarryForwardInvoiceCount(?array $batch): int
|
||||
{
|
||||
if ($batch === null || ! in_array((string) ($batch['status'] ?? ''), ['executed', 'completed'], true)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
if (! $db->tableExists('school_year_closing_items')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $db->table('school_year_closing_items')
|
||||
->where('closing_batch_id', (int) $batch['id'])
|
||||
->groupStart()
|
||||
->where('target_invoice_id', null)
|
||||
->orWhere('target_invoice_id', 0)
|
||||
->groupEnd()
|
||||
->countAllResults();
|
||||
}
|
||||
|
||||
private function promotionTablePayload(array $rows): array
|
||||
{
|
||||
$allowedSorts = ['student', 'school_id', 'class', 'year_score', 'decision', 'source', 'queue', 'target', 'status'];
|
||||
|
||||
@@ -860,8 +860,13 @@ class LandingPageController extends BaseController
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// Fetch latest invoice balance
|
||||
$paymentBalance = $this->invoiceModel->getLatestInvoiceTotalAmount($parentId);
|
||||
$paymentRow = $this->db->table('invoices')
|
||||
->select('COALESCE(SUM(balance), 0) AS account_balance')
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->get()
|
||||
->getRowArray();
|
||||
$paymentBalance = (float) ($paymentRow['account_balance'] ?? 0);
|
||||
|
||||
// Pass data to the view, including the deadlines
|
||||
return view('/landing_page/parent_dashboard', [
|
||||
|
||||
@@ -297,6 +297,8 @@ class PaymentController extends ResourceController
|
||||
$payments = [];
|
||||
$invoices = [];
|
||||
$pager = null;
|
||||
$carryForwardPaymentRequired = false;
|
||||
$carryForwardPaymentMessage = '';
|
||||
|
||||
// Read the search term (email or phone)
|
||||
$searchTerm = trim((string) $this->request->getGet('search_term'));
|
||||
@@ -351,6 +353,10 @@ class PaymentController extends ResourceController
|
||||
if ($parent && !empty($parent['id'])) {
|
||||
$parentData = $parent;
|
||||
$parentId = (int) $parent['id'];
|
||||
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $this->schoolYear);
|
||||
if ($carryForwardPaymentRequired) {
|
||||
$carryForwardPaymentMessage = 'This parent has a balance carried over from a previous school year. Manual payments must be paid in full; installments are not allowed.';
|
||||
}
|
||||
|
||||
// Students
|
||||
$students = $this->studentModel
|
||||
@@ -461,6 +467,8 @@ class PaymentController extends ResourceController
|
||||
'searchTermUsedInSearch' => $searchTerm,
|
||||
'todayYmd' => utc_now(),
|
||||
'installmentEndYmd' => $installmentEndYmd, // SELECT will use this in data-end-date
|
||||
'carryForwardPaymentRequired' => $carryForwardPaymentRequired,
|
||||
'carryForwardPaymentMessage' => $carryForwardPaymentMessage,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -952,6 +960,15 @@ class PaymentController extends ResourceController
|
||||
// Recompute invoice totals from tuition + events + additional charges
|
||||
$currentBalance = (float) $this->invoiceLedgerService->recalculateInvoice($invoiceId)['balance'];
|
||||
|
||||
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $invYear);
|
||||
if ($carryForwardPaymentRequired && $paymentType === 'installment') {
|
||||
$this->db->transRollback();
|
||||
return redirect()->back()->withInput()->with(
|
||||
'error',
|
||||
'This parent has a balance carried over from a previous school year. Installments are not allowed; payment must be made in full.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($amount > $currentBalance + 0.00001) {
|
||||
$this->db->transRollback();
|
||||
return redirect()->back()->withInput()->with(
|
||||
@@ -960,6 +977,14 @@ class PaymentController extends ResourceController
|
||||
);
|
||||
}
|
||||
|
||||
if ($carryForwardPaymentRequired && (float)round($amount, 2) !== (float)round($currentBalance, 2)) {
|
||||
$this->db->transRollback();
|
||||
return redirect()->back()->withInput()->with(
|
||||
'error',
|
||||
'This parent has a balance carried over from a previous school year. Payment must equal the full remaining balance (' . number_format($currentBalance, 2) . ').'
|
||||
);
|
||||
}
|
||||
|
||||
if ($paymentMethod === 'card' && (float)round($amount, 2) !== (float)round($currentBalance, 2)) {
|
||||
$this->db->transRollback();
|
||||
return redirect()->back()->withInput()->with(
|
||||
@@ -1158,6 +1183,36 @@ class PaymentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
private function parentHasActiveCarryForwardBalance(int $parentId, ?string $schoolYear = null): bool
|
||||
{
|
||||
if ($parentId <= 0 || ! $this->db->tableExists('invoices')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('invoices')
|
||||
->where('parent_id', $parentId)
|
||||
->where('balance >', 0);
|
||||
|
||||
if ($schoolYear !== null && $schoolYear !== '') {
|
||||
$builder->where('school_year', $schoolYear);
|
||||
}
|
||||
|
||||
$builder->groupStart()
|
||||
->like('invoice_number', 'CF-', 'after')
|
||||
->orWhere('semester', 'Opening Balance');
|
||||
|
||||
if ($this->db->fieldExists('description', 'invoices')) {
|
||||
$builder
|
||||
->orLike('description', 'carried over')
|
||||
->orLike('description', 'carry-forward')
|
||||
->orLike('description', 'previous school year');
|
||||
}
|
||||
|
||||
$builder->groupEnd();
|
||||
|
||||
return $builder->countAllResults() > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 🔄 Helper: Recalculate invoice totals and status based on all payments for current school year
|
||||
|
||||
Reference in New Issue
Block a user