fix enrollment, invoice, payment and financila aid
This commit is contained in:
@@ -173,6 +173,76 @@ class InvoiceLedgerService
|
||||
return $this->recalculateInvoice($invoiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh frozen tuition invoice lines to match the current enrollment/class assignment state.
|
||||
* Event and additional charge lines are left unchanged.
|
||||
*/
|
||||
public function syncTuitionLines(int $invoiceId): bool
|
||||
{
|
||||
$invoice = $this->loadInvoice($invoiceId);
|
||||
if ($invoice === null || $this->isCarryForwardInvoice($invoice) || ! $this->invoiceLinesAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->invoiceHasLines($invoiceId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentTuitionCents = $this->toCents($this->calculateTuitionTotal($invoice));
|
||||
$frozen = $this->calculateFrozenLineTotals($invoiceId);
|
||||
$frozenTuitionCents = (int) ($frozen['tuition_cents'] ?? 0);
|
||||
|
||||
if ($currentTuitionCents === $frozenTuitionCents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = utc_now();
|
||||
$this->invoiceLineModel()
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('voided_at IS NULL', null, false)
|
||||
->groupStart()
|
||||
->where('line_type', 'tuition')
|
||||
->orWhere('source_type', 'tuition_calculation')
|
||||
->groupEnd()
|
||||
->set([
|
||||
'voided_at' => $now,
|
||||
'updated_at' => $now,
|
||||
])
|
||||
->update();
|
||||
|
||||
if ($currentTuitionCents === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$metadata = [
|
||||
'parent_id' => (int) ($invoice['parent_id'] ?? 0),
|
||||
'school_year' => (string) ($invoice['school_year'] ?? ''),
|
||||
'semester' => (string) ($invoice['semester'] ?? ''),
|
||||
'synced_at' => $now,
|
||||
];
|
||||
|
||||
$lineId = $this->invoiceLineModel()->insert(
|
||||
$this->buildInvoiceLineRow(
|
||||
$invoiceId,
|
||||
'tuition',
|
||||
'tuition_calculation',
|
||||
null,
|
||||
'Tuition charges',
|
||||
$currentTuitionCents,
|
||||
1,
|
||||
$this->getCalculationVersion(),
|
||||
$metadata,
|
||||
$now
|
||||
)
|
||||
);
|
||||
|
||||
if (! $lineId) {
|
||||
throw new FinancialPersistenceException('INVOICE_TUITION_SYNC_FAILED', $this->invoiceLineModel()->errors());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getValidPaymentTotalCents(int $invoiceId): int
|
||||
{
|
||||
return $this->toCents($this->calculateValidPayments($invoiceId));
|
||||
@@ -428,9 +498,129 @@ class InvoiceLedgerService
|
||||
|
||||
return str_contains($description, 'carried over')
|
||||
|| str_contains($description, 'carry-forward')
|
||||
|| str_contains($description, 'carry over')
|
||||
|| str_contains($description, 'previous school year');
|
||||
}
|
||||
|
||||
public function invoiceIsCarryForward(array $invoice): bool
|
||||
{
|
||||
return $this->isCarryForwardInvoice($invoice);
|
||||
}
|
||||
|
||||
public function carryForwardDisplayDescription(array $invoice): string
|
||||
{
|
||||
$sourceYear = $this->carryForwardSourceSchoolYear($invoice);
|
||||
if ($sourceYear !== '') {
|
||||
return 'Carry over balance from last year ' . $sourceYear;
|
||||
}
|
||||
|
||||
return 'Carry over balance from last year';
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a single carry-over line on an opening-balance invoice. No student names are used.
|
||||
*/
|
||||
public function issueCarryForwardInvoiceLine(int $invoiceId, float $amount, string $description): int
|
||||
{
|
||||
if ($invoiceId <= 0 || ! $this->invoiceLinesAvailable()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$invoice = $this->loadInvoice($invoiceId);
|
||||
if ($invoice === null || ! $this->isCarryForwardInvoice($invoice)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$amountCents = $this->toCents($amount);
|
||||
if ($amountCents === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$description = trim($description);
|
||||
if ($description === '') {
|
||||
$description = $this->carryForwardDisplayDescription($invoice);
|
||||
}
|
||||
|
||||
$existingLine = $this->invoiceLineModel()
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('source_type', 'carry_forward_opening_balance')
|
||||
->where('voided_at IS NULL', null, false)
|
||||
->first();
|
||||
|
||||
$now = utc_now();
|
||||
|
||||
$this->invoiceLineModel()
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('voided_at IS NULL', null, false)
|
||||
->groupStart()
|
||||
->where('line_type', 'tuition')
|
||||
->orWhere('source_type', 'tuition_calculation')
|
||||
->groupEnd()
|
||||
->set([
|
||||
'voided_at' => $now,
|
||||
'updated_at' => $now,
|
||||
])
|
||||
->update();
|
||||
|
||||
if ($existingLine !== null) {
|
||||
$this->invoiceLineModel()->update((int) $existingLine['id'], [
|
||||
'description' => $description,
|
||||
'unit_amount_cents' => $amountCents,
|
||||
'line_amount_cents' => $amountCents,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
return (int) $existingLine['id'];
|
||||
}
|
||||
|
||||
$lineId = $this->invoiceLineModel()->insert(
|
||||
$this->buildInvoiceLineRow(
|
||||
$invoiceId,
|
||||
'additional_charge',
|
||||
'carry_forward_opening_balance',
|
||||
$invoiceId,
|
||||
$description,
|
||||
$amountCents,
|
||||
0,
|
||||
$this->getCalculationVersion(),
|
||||
['carry_forward' => true],
|
||||
$now
|
||||
)
|
||||
);
|
||||
|
||||
if (! $lineId) {
|
||||
throw new FinancialPersistenceException('INVOICE_CARRY_FORWARD_LINE_FAILED', $this->invoiceLineModel()->errors());
|
||||
}
|
||||
|
||||
return (int) $lineId;
|
||||
}
|
||||
|
||||
private function carryForwardSourceSchoolYear(array $invoice): string
|
||||
{
|
||||
$description = (string) ($invoice['description'] ?? '');
|
||||
if (preg_match('/last year\s+(\d{4}-\d{4})/i', $description, $matches) === 1) {
|
||||
return $matches[1];
|
||||
}
|
||||
if (preg_match('/previous school year\s+(\d{4}-\d{4})/i', $description, $matches) === 1) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
$invoiceNumber = (string) ($invoice['invoice_number'] ?? '');
|
||||
if (preg_match('/^CF-(\d{8})-/i', $invoiceNumber, $matches) === 1) {
|
||||
$compact = $matches[1];
|
||||
if (strlen($compact) === 8) {
|
||||
return substr($compact, 0, 4) . '-' . substr($compact, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
$targetYear = trim((string) ($invoice['school_year'] ?? ''));
|
||||
if (preg_match('/^(\d{4})-(\d{4})$/', $targetYear, $matches) === 1) {
|
||||
return ((int) $matches[1] - 1) . '-' . ((int) $matches[2] - 1);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function calculateTuitionTotal(array $invoice): float
|
||||
{
|
||||
$parentId = (int) ($invoice['parent_id'] ?? 0);
|
||||
|
||||
Reference in New Issue
Block a user