Fix semester context, attendance rosters, and billing workflows
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 31s
Tests / PHPUnit (push) Failing after 55s

- load global semester helpers consistently and use date-based semester defaults
- fix grading and daily attendance duplicate student/section rows
- keep attendance violations scoped to the current semester by default
- update invoice, refund, discount, payment, and financial aid flows
- add configuration cleanup migrations for duplicate calendar/semester keys
- refresh parent registration/report-card and print request handling
- update related models, services, views, cron notes, and test coverage
This commit is contained in:
root
2026-08-16 17:41:11 -04:00
parent 36c7e3fc6d
commit 0ac3a8375e
99 changed files with 1598 additions and 814 deletions
+54
View File
@@ -27,6 +27,12 @@ class InvoiceIssuanceService
public function issueInvoice(IssueInvoiceCommand $command): InvoiceLedgerResult
{
$invoiceData = $command->invoiceData;
if (trim((string)($invoiceData['invoice_number'] ?? '')) === '') {
$invoiceData['invoice_number'] = $this->generateInvoiceNumber(
(string)($invoiceData['school_year'] ?? ''),
(int)($invoiceData['parent_id'] ?? 0)
);
}
$invoiceData['status'] = FinancialStatus::INVOICE_DRAFT;
$invoiceData['total_amount'] = $invoiceData['total_amount'] ?? number_format($command->tuitionAmount + $command->eventAmount, 2, '.', '');
$invoiceData['balance'] = $invoiceData['balance'] ?? $invoiceData['total_amount'];
@@ -78,6 +84,54 @@ class InvoiceIssuanceService
}
}
public function generateInvoiceNumber(string $schoolYear, ?int $parentId = null): string
{
$prefix = $this->invoiceNumberPrefix($schoolYear, $parentId);
do {
$invoiceNumber = $prefix . '-' . uniqid();
$exists = $this->db->table('invoices')
->where('invoice_number', $invoiceNumber)
->countAllResults() > 0;
} while ($exists);
return $invoiceNumber;
}
private function invoiceNumberPrefix(string $schoolYear, ?int $parentId = null): string
{
$year = date('y');
if (preg_match('/^(\d{4})-\d{4}$/', $schoolYear, $matches) === 1) {
$year = substr($matches[1], -2);
}
$parentSuffix = $this->parentSchoolIdSuffix((int)($parentId ?? 0));
if ($parentSuffix !== '') {
return 'INV-' . $year . $parentSuffix;
}
return 'INV-' . $year . str_pad((string) max(0, (int)($parentId ?? 0)), 5, '0', STR_PAD_LEFT);
}
private function parentSchoolIdSuffix(int $parentId): string
{
if ($parentId <= 0) {
return '';
}
$row = $this->db->table('users')
->select('school_id')
->where('id', $parentId)
->get()
->getRowArray();
$schoolId = trim((string)($row['school_id'] ?? ''));
if (preg_match('/^\d{2}(\d{5})$/', $schoolId, $matches) === 1) {
return $matches[1];
}
return '';
}
private function requireWrite($result, string $code, $model = null): void
{
if ($result === false || $result === null || $result === 0) {