fix installment for carry over balance parent account
This commit is contained in:
@@ -391,9 +391,14 @@ class PaymentController extends ResourceController
|
||||
if ($parent && !empty($parent['id'])) {
|
||||
$parentData = $parent;
|
||||
$parentId = (int) $parent['id'];
|
||||
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $manualPaySchoolYear);
|
||||
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.';
|
||||
$hasCarryForwardInvoice = $this->parentHasCarryForwardInvoice($parentId, $manualPaySchoolYear);
|
||||
$hasCurrentYearInstallmentOverride = $this->parentHasCurrentYearInstallmentOverride($parentId, $manualPaySchoolYear);
|
||||
$carryForwardPaymentRequired = $hasCarryForwardInvoice && ! $hasCurrentYearInstallmentOverride;
|
||||
if ($hasCarryForwardInvoice) {
|
||||
$carryForwardPaymentMessage = 'This parent has a carry-over balance record from a previous school year. Installments are not allowed without an admin exception.';
|
||||
if ($hasCurrentYearInstallmentOverride) {
|
||||
$carryForwardPaymentMessage = 'Carry-over invoices must be paid in full. Installments are allowed only for current-year balances by admin override.';
|
||||
}
|
||||
}
|
||||
|
||||
// Students
|
||||
@@ -433,6 +438,7 @@ class PaymentController extends ResourceController
|
||||
|
||||
// Always use the configured end date for installments
|
||||
$inv['due_ymd'] = $installmentEndYmd;
|
||||
$inv['is_carry_forward_invoice'] = $this->isCarryForwardInvoiceRow($inv) ? 1 : 0;
|
||||
|
||||
// Optional: keep a start marker if you ever need it elsewhere
|
||||
$issueYmd = '';
|
||||
@@ -956,7 +962,7 @@ class PaymentController extends ResourceController
|
||||
try {
|
||||
// Lock invoice & get context (also ensure totals are up-to-date before validation)
|
||||
$row = $this->db->query(
|
||||
'SELECT id, parent_id, invoice_number, total_amount, school_year FROM invoices WHERE id = ? FOR UPDATE',
|
||||
'SELECT id, parent_id, invoice_number, total_amount, school_year, semester, description FROM invoices WHERE id = ? FOR UPDATE',
|
||||
[$invoiceId]
|
||||
)->getRowArray();
|
||||
|
||||
@@ -972,7 +978,10 @@ class PaymentController extends ResourceController
|
||||
// Recompute invoice totals from tuition + events + additional charges
|
||||
$currentBalance = (float) $this->invoiceLedgerService->recalculateInvoice($invoiceId)['balance'];
|
||||
|
||||
$carryForwardPaymentRequired = $this->parentHasActiveCarryForwardBalance($parentId, $invYear);
|
||||
$hasCarryForwardInvoice = $this->parentHasCarryForwardInvoice($parentId, $invYear);
|
||||
$hasCurrentYearInstallmentOverride = $this->parentHasCurrentYearInstallmentOverride($parentId, $invYear);
|
||||
$carryForwardPaymentRequired = $hasCarryForwardInvoice
|
||||
&& ($this->isCarryForwardInvoiceRow($row) || ! $hasCurrentYearInstallmentOverride);
|
||||
if ($carryForwardPaymentRequired && $paymentType === 'installment') {
|
||||
$this->db->transRollback();
|
||||
$this->financialAttachmentService->discardStagedFile($stagedEvidence);
|
||||
@@ -1061,6 +1070,8 @@ class PaymentController extends ResourceController
|
||||
// Post-payment balance from snapshot
|
||||
$postBalance = (float) ($ledger['balance'] ?? max(0.0, round($initialPreBalance - $amount, 2)));
|
||||
|
||||
$this->syncEnrollmentFinanceAfterPayment($parentId, $invYear);
|
||||
|
||||
// Optional enrollment update
|
||||
$enrollmentupdated = $this->updateEnrollmentStatusIfPaid($invoiceId);
|
||||
if ($enrollmentupdated != 0) {
|
||||
@@ -1268,15 +1279,14 @@ class PaymentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
private function parentHasActiveCarryForwardBalance(int $parentId, ?string $schoolYear = null): bool
|
||||
private function parentHasCarryForwardInvoice(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);
|
||||
->where('parent_id', $parentId);
|
||||
|
||||
if ($schoolYear !== null && $schoolYear !== '') {
|
||||
$builder->where('school_year', $schoolYear);
|
||||
@@ -1290,6 +1300,7 @@ class PaymentController extends ResourceController
|
||||
$builder
|
||||
->orLike('description', 'carried over')
|
||||
->orLike('description', 'carry-forward')
|
||||
->orLike('description', 'carry over')
|
||||
->orLike('description', 'previous school year');
|
||||
}
|
||||
|
||||
@@ -1298,6 +1309,85 @@ class PaymentController extends ResourceController
|
||||
return $builder->countAllResults() > 0;
|
||||
}
|
||||
|
||||
private function parentHasCurrentYearInstallmentOverride(int $parentId, string $schoolYear): bool
|
||||
{
|
||||
if ($parentId <= 0 || $schoolYear === '' || ! $this->db->tableExists('enrollment_exceptions')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rows = $this->db->table('enrollment_exceptions')
|
||||
->select('bypassed_rule_codes_json')
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->whereIn('status', ['active', 'used'])
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$codes = json_decode((string) ($row['bypassed_rule_codes_json'] ?? ''), true);
|
||||
if (! is_array($codes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$codes = array_map(static fn ($code): string => strtoupper(trim((string) $code)), $codes);
|
||||
if (in_array('CURRENT_YEAR_INSTALLMENT_OVERRIDE', $codes, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isCarryForwardInvoiceRow(array $invoice): bool
|
||||
{
|
||||
$invoiceNumber = (string) ($invoice['invoice_number'] ?? '');
|
||||
if (str_starts_with($invoiceNumber, 'CF-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcasecmp((string) ($invoice['semester'] ?? ''), 'Opening Balance') === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$description = strtolower((string) ($invoice['description'] ?? ''));
|
||||
|
||||
return str_contains($description, 'carried over')
|
||||
|| str_contains($description, 'carry-forward')
|
||||
|| str_contains($description, 'carry over')
|
||||
|| str_contains($description, 'previous school year');
|
||||
}
|
||||
|
||||
private function syncEnrollmentFinanceAfterPayment(int $parentId, string $targetSchoolYear): void
|
||||
{
|
||||
$sourceSchoolYear = $this->previousSchoolYearName($targetSchoolYear);
|
||||
if ($parentId <= 0 || $sourceSchoolYear === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$studentIds = $this->studentModel->getStudentIdsByParentId($parentId);
|
||||
service('enrollmentTransition')->syncParentFinancialReviewFlags(
|
||||
$parentId,
|
||||
$sourceSchoolYear,
|
||||
$targetSchoolYear,
|
||||
array_values(array_map('intval', $studentIds ?? []))
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Enrollment finance sync after payment failed for parent {parent_id}, school year {school_year}: {error}', [
|
||||
'parent_id' => $parentId,
|
||||
'school_year' => $targetSchoolYear,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function previousSchoolYearName(string $schoolYear): ?string
|
||||
{
|
||||
return preg_match('/^(\d{4})-(\d{4})$/', trim($schoolYear), $matches)
|
||||
? ((int) $matches[1] - 1) . '-' . ((int) $matches[2] - 1)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 🔄 Helper: Recalculate invoice totals and status based on all payments for current school year
|
||||
|
||||
Reference in New Issue
Block a user