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
+34 -9
View File
@@ -86,7 +86,7 @@ class ParentController extends BaseController
$this->schoolStartDate = $this->configModel->getConfig('fall_semester_start');
$this->withdrawalDeadline = $this->configModel->getConfig('refund_deadline');
$this->schoolYear = $this->configModel->getConfig('school_year');
$this->semester = $this->configModel->getConfig('semester');
$this->semester = getSemester();
$this->dateAgeReference = $this->configModel->getConfig('date_age_reference');
$this->maxChilds = (int) $this->configModel->getConfig('max_kids') ?? 0;
$this->maxEmergency = (int) $this->configModel->getConfig('max_emergency') ?? 0;
@@ -548,7 +548,7 @@ class ParentController extends BaseController
if ($existingEnrollment['is_withdrawn'] == 1) {
// Reactivate the enrollment if the student was previously withdrawn
$this->enrollmentModel->where('id', $existingEnrollment['id'])->update($update);
$this->enrollmentModel->update((int) $existingEnrollment['id'], $update);
log_message('info', "{$studentData[$studentId]['firstname']} {$studentData[$studentId]['lastname']} (ID $studentId) has been re-enrolled in enrollment ID {$existingEnrollment['id']}.");
// Apply promotion-based class placement for the upcoming year
$this->applyPromotionAssignment((int)$studentId, $selectedYear, $isReturningReEnrollment);
@@ -557,7 +557,7 @@ class ParentController extends BaseController
if ($currentStatus === 'enrolled') {
$update['admission_status'] = 'accepted';
}
$this->enrollmentModel->where('id', $existingEnrollment['id'])->update($update);
$this->enrollmentModel->update((int) $existingEnrollment['id'], $update);
log_message('info', "{$studentData[$studentId]['firstname']} {$studentData[$studentId]['lastname']} (ID $studentId) is already actively enrolled.");
$this->applyPromotionAssignment((int)$studentId, $selectedYear, $isReturningReEnrollment);
}
@@ -642,7 +642,7 @@ class ParentController extends BaseController
if ($enrollment !== null) {
// Update enrollment as withdrawn
$this->enrollmentModel->where('id', $enrollment['id'])->update([
$this->enrollmentModel->update((int) $enrollment['id'], [
'withdrawal_date' => local_date(utc_now(), 'Y-m-d'),
'enrollment_status' => 'withdraw under review', // Withdrawal needs review
'updated_at' => utc_now()
@@ -660,6 +660,12 @@ class ParentController extends BaseController
if ($invoice !== null) {
$invoiceId = $invoice['id'];
$studentsForRefund = $this->enrollmentModel
->where('parent_id', $parentId)
->where('school_year', $this->schoolYear)
->findAll();
$refundAmount = $refundService->calculateRefund($studentsForRefund, (int) $parentId);
$refundCents = max(0, (int) round($refundAmount * 100));
$refundTable = $this->db->table('refunds');
@@ -667,21 +673,29 @@ class ParentController extends BaseController
->where('parent_id', $parentId)
->where('invoice_id', $invoiceId)
->where('school_year', $this->schoolYear)
->whereIn('status', ['Pending', 'Approved', 'Partial', 'pending', 'requested', 'approved', 'partial', 'partially_paid'])
->get()
->getRow();
if ($existingRefund) {
// Only update the fields that should change
$updateData = [
'refund_amount' => $refundAmount,
'requested_amount_cents' => $refundCents,
'currency' => 'USD',
'reason' => 'Withdrawal under review for student ID ' . $studentId,
'note' => null,
'request' => 'tuition',
'source_type' => 'tuition_withdrawal',
'source_id' => (int) $invoiceId,
'status' => 'Pending',
'updated_by' => session()->get('user_id'), // optionally track updates
// Add other fields if *and only if* they must be changed
];
$refundTable
->where('id', $existingRefund->id)
->update($updateData);
->update($this->filterPayloadByTableColumns($updateData, 'refunds'));
log_message('info', "Refund record updated for invoice ID {$invoiceId}, student ID {$studentId}.");
} else {
@@ -689,16 +703,22 @@ class ParentController extends BaseController
$insertData = [
'parent_id' => $parentId,
'invoice_id' => $invoiceId,
'refund_amount' => $refundAmount,
'requested_amount_cents' => $refundCents,
'approved_amount_cents' => null,
'currency' => 'USD',
'requested_at' => utc_now(),
'school_year' => $this->schoolYear,
'status' => 'Pending review',
'status' => 'Pending',
'reason' => 'Withdrawal under review for student ID ' . $studentId,
'request' => 'new',
'request' => 'tuition',
'source_type' => 'tuition_withdrawal',
'source_id' => (int) $invoiceId,
'semester' => $this->semester,
'refund_paid_amount' => 0.0,
];
$refundTable->insert($insertData);
$refundTable->insert($this->filterPayloadByTableColumns($insertData, 'refunds'));
log_message('info', "Refund record created for invoice ID {$invoiceId}, student ID {$studentId}.");
}
@@ -893,9 +913,14 @@ class ParentController extends BaseController
}
private function filterEnrollmentPayloadByColumns(array $payload): array
{
return $this->filterPayloadByTableColumns($payload, 'enrollments');
}
private function filterPayloadByTableColumns(array $payload, string $table): array
{
foreach (array_keys($payload) as $column) {
if (! $this->db->fieldExists($column, 'enrollments')) {
if (! $this->db->fieldExists($column, $table)) {
unset($payload[$column]);
}
}