fix enrollment and email send to parent
This commit is contained in:
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -514,6 +514,8 @@ class DiscountController extends BaseController
|
|||||||
$affected = count($rowsToUpdate);
|
$affected = count($rowsToUpdate);
|
||||||
$db->transCommit();
|
$db->transCommit();
|
||||||
|
|
||||||
|
$this->triggerStudentEnrolledNotification($parentId, $rowsToUpdate);
|
||||||
|
|
||||||
log_message(
|
log_message(
|
||||||
'info',
|
'info',
|
||||||
'Enrollment status -> enrolled for {n} row(s). parent={p}, year={y}, sem={s}, ids=[{ids}]',
|
'Enrollment status -> enrolled for {n} row(s). parent={p}, year={y}, sem={s}, ids=[{ids}]',
|
||||||
@@ -534,10 +536,60 @@ class DiscountController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function triggerStudentEnrolledNotification(int $parentId, array $enrollmentRows): void
|
||||||
|
{
|
||||||
|
if ($parentId <= 0 || $enrollmentRows === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parent = $this->userModel->find($parentId) ?? [];
|
||||||
|
$studentIds = array_values(array_unique(array_filter(
|
||||||
|
array_map(static fn (array $row): int => (int) ($row['student_id'] ?? 0), $enrollmentRows),
|
||||||
|
static fn (int $studentId): bool => $studentId > 0
|
||||||
|
)));
|
||||||
|
|
||||||
|
if ($studentIds === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$students = [];
|
||||||
|
foreach ($this->db->table('students')->whereIn('id', $studentIds)->get()->getResultArray() as $student) {
|
||||||
|
$studentId = (int) ($student['id'] ?? 0);
|
||||||
|
if ($studentId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$students[] = [
|
||||||
|
'student_id' => $studentId,
|
||||||
|
'name' => trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')) ?: 'Student #' . $studentId,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($students === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Events::trigger('studentEnrolled', [
|
||||||
|
'user_id' => $parent['id'] ?? $parentId,
|
||||||
|
'email' => $parent['email'] ?? null,
|
||||||
|
'firstname' => $parent['firstname'] ?? '',
|
||||||
|
'lastname' => $parent['lastname'] ?? '',
|
||||||
|
'school_year' => (string) $this->schoolYear,
|
||||||
|
'portalLink' => base_url('/login'),
|
||||||
|
], $students);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'Enrollment confirmation notification after discount failed for parent {parent_id}: {error}', [
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function listVouchers()
|
public function listVouchers()
|
||||||
{
|
{
|
||||||
$this->schoolYear = $this->activeSchoolYearName();
|
$this->schoolYear = $this->currentSchoolYearName();
|
||||||
|
|
||||||
$vouchers = $this->voucherModel
|
$vouchers = $this->voucherModel
|
||||||
->where('school_year', $this->schoolYear)
|
->where('school_year', $this->schoolYear)
|
||||||
@@ -684,6 +736,19 @@ class DiscountController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function currentSchoolYearName(?string $fallback = null): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return service('schoolYearContext')->resolve($this->request)->yearName();
|
||||||
|
} catch (\Throwable) {
|
||||||
|
if ($fallback !== null && $fallback !== '') {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) ($this->configModel->getConfig('school_year') ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🔄 Helper: Current invoice balance (school-year scoped) = total - payments - discounts - refundsPaid
|
* 🔄 Helper: Current invoice balance (school-year scoped) = total - payments - discounts - refundsPaid
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2010,8 +2010,14 @@ class ParentController extends BaseController
|
|||||||
private function enrollmentTuitionDue(array $students): float
|
private function enrollmentTuitionDue(array $students): float
|
||||||
{
|
{
|
||||||
$tuitionStudents = [];
|
$tuitionStudents = [];
|
||||||
|
$existingTuitionStudentCount = 0;
|
||||||
|
|
||||||
foreach ($students as $student) {
|
foreach ($students as $student) {
|
||||||
|
if ($this->countsTowardCurrentYearTuition($student)) {
|
||||||
|
$existingTuitionStudentCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||||||
if (
|
if (
|
||||||
($evaluation['can_enroll'] ?? false) !== true
|
($evaluation['can_enroll'] ?? false) !== true
|
||||||
@@ -2034,9 +2040,29 @@ class ParentController extends BaseController
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($existingTuitionStudentCount > 0) {
|
||||||
|
$additionalStudentFee = (float) ($this->configModel->getConfig('second_student_fee') ?? 280);
|
||||||
|
|
||||||
|
return round(count($tuitionStudents) * $additionalStudentFee, 2);
|
||||||
|
}
|
||||||
|
|
||||||
return (new FeeCalculationService())->calculateEnrollmentTuition($tuitionStudents);
|
return (new FeeCalculationService())->calculateEnrollmentTuition($tuitionStudents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function countsTowardCurrentYearTuition(array $student): bool
|
||||||
|
{
|
||||||
|
$status = strtolower(trim((string) ($student['enrollment_status'] ?? '')));
|
||||||
|
if (in_array($status, ['withdrawn', 'withdraw under review', 'refund pending', 'denied', 'not enrolled'], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($status, ['admission under review', 'review & decision', 'payment pending', 'enrolled', 'waitlist'], true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtolower(trim((string) ($student['admission_status'] ?? ''))) === 'accepted';
|
||||||
|
}
|
||||||
|
|
||||||
private function enrollmentFeeSchedule(string $selectedYear): array
|
private function enrollmentFeeSchedule(string $selectedYear): array
|
||||||
{
|
{
|
||||||
$schoolYearConfig = $this->schoolYearConfig($selectedYear);
|
$schoolYearConfig = $this->schoolYearConfig($selectedYear);
|
||||||
@@ -2673,7 +2699,6 @@ class ParentController extends BaseController
|
|||||||
$this->db->transStart();
|
$this->db->transStart();
|
||||||
|
|
||||||
$studentAdded = false;
|
$studentAdded = false;
|
||||||
$registeredStudentIds = [];
|
|
||||||
|
|
||||||
// Gather all POST data for last year flags
|
// Gather all POST data for last year flags
|
||||||
$rawPost = $this->request->getPost();
|
$rawPost = $this->request->getPost();
|
||||||
@@ -2700,9 +2725,6 @@ class ParentController extends BaseController
|
|||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$studentAdded = true;
|
$studentAdded = true;
|
||||||
if (is_numeric($result) && (int) $result > 0) {
|
|
||||||
$registeredStudentIds[] = (int) $result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2724,13 +2746,8 @@ class ParentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($studentAdded) {
|
if ($studentAdded) {
|
||||||
$enrollmentQuery = ['start' => 2];
|
return redirect()->to(base_url('/parent/enroll_classes?' . http_build_query(['start' => 1])))
|
||||||
if ($registeredStudentIds !== []) {
|
->with('success', 'Registration successful! Continue enrollment by selecting all students you want to enroll.');
|
||||||
$enrollmentQuery['students'] = implode(',', array_values(array_unique($registeredStudentIds)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->to(base_url('/parent/enroll_classes?' . http_build_query($enrollmentQuery)))
|
|
||||||
->with('success', 'Registration successful! Continue enrollment by confirming your home address and phone number.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to(base_url('/parent/child_register'))->with('success', 'Registration successful!');
|
return redirect()->to(base_url('/parent/child_register'))->with('success', 'Registration successful!');
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ class PaymentController extends ResourceController
|
|||||||
|
|
||||||
// Read the search term (email or phone)
|
// Read the search term (email or phone)
|
||||||
$searchTerm = trim((string) $this->request->getGet('search_term'));
|
$searchTerm = trim((string) $this->request->getGet('search_term'));
|
||||||
$manualPaySchoolYear = $this->activeSchoolYearName();
|
$manualPaySchoolYear = $this->currentSchoolYearName();
|
||||||
|
|
||||||
// --- Installment end date comes ONLY from config ---
|
// --- Installment end date comes ONLY from config ---
|
||||||
$installmentDateRaw = (string) ($this->installmentDate ?? '');
|
$installmentDateRaw = (string) ($this->installmentDate ?? '');
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use App\Models\StudentSectionDistributionDraftModel;
|
|||||||
use App\Models\StudentAllergyModel;
|
use App\Models\StudentAllergyModel;
|
||||||
use App\Models\StudentMedicalConditionModel;
|
use App\Models\StudentMedicalConditionModel;
|
||||||
use App\Support\Enrollment\DeliberationDecision;
|
use App\Support\Enrollment\DeliberationDecision;
|
||||||
|
use CodeIgniter\Events\Events;
|
||||||
use CodeIgniter\Database\Exceptions\DataException;
|
use CodeIgniter\Database\Exceptions\DataException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@@ -129,11 +130,21 @@ class StudentController extends BaseController
|
|||||||
$existing = $this->studentClassModel
|
$existing = $this->studentClassModel
|
||||||
->where('student_id', $studentId)
|
->where('student_id', $studentId)
|
||||||
->where('school_year', (string)$this->schoolYear)
|
->where('school_year', (string)$this->schoolYear)
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
$existingBySection = [];
|
$existingBySection = [];
|
||||||
|
$scPk = 'id';
|
||||||
foreach ($existing as $row) {
|
foreach ($existing as $row) {
|
||||||
$existingBySection[(int)($row['class_section_id'] ?? 0)] = $row;
|
$rowEventFlag = (int)($row['is_event_only'] ?? 0);
|
||||||
|
if ($rowEventFlag !== $isEventOnly) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionKey = (int)($row['class_section_id'] ?? 0);
|
||||||
|
if ($sectionKey > 0 && !isset($existingBySection[$sectionKey])) {
|
||||||
|
$existingBySection[$sectionKey] = $row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$displayNames = [];
|
$displayNames = [];
|
||||||
@@ -184,8 +195,6 @@ class StudentController extends BaseController
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Upsert student_class entries per selection
|
// Upsert student_class entries per selection
|
||||||
$scPk = $this->studentClassModel->primaryKey ?? 'id';
|
|
||||||
|
|
||||||
foreach ($classSectionIds as $cid) {
|
foreach ($classSectionIds as $cid) {
|
||||||
$eventFlag = $isEventOnly ? 1 : 0;
|
$eventFlag = $isEventOnly ? 1 : 0;
|
||||||
if (isset($existingBySection[$cid])) {
|
if (isset($existingBySection[$cid])) {
|
||||||
@@ -215,6 +224,7 @@ class StudentController extends BaseController
|
|||||||
$attnStats = [];
|
$attnStats = [];
|
||||||
$scoreStats = [];
|
$scoreStats = [];
|
||||||
$invoiceParentId = 0;
|
$invoiceParentId = 0;
|
||||||
|
$paymentPendingNotification = null;
|
||||||
if (!$isEventOnly) {
|
if (!$isEventOnly) {
|
||||||
// Update enrollment for current term (if exists)
|
// Update enrollment for current term (if exists)
|
||||||
$enroll = \Config\Services::enrollmentStatus(false)
|
$enroll = \Config\Services::enrollmentStatus(false)
|
||||||
@@ -258,6 +268,16 @@ class StudentController extends BaseController
|
|||||||
if ((int) ($result['id'] ?? 0) <= 0) {
|
if ((int) ($result['id'] ?? 0) <= 0) {
|
||||||
throw new \RuntimeException('Failed to update enrollment.');
|
throw new \RuntimeException('Failed to update enrollment.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$oldEnrollmentStatus = strtolower(trim(str_replace("\xc2\xa0", ' ', (string) ($result['old_status'] ?? $enroll['enrollment_status'] ?? ''))));
|
||||||
|
$oldEnrollmentStatus = preg_replace('/\s+/', ' ', $oldEnrollmentStatus) ?? $oldEnrollmentStatus;
|
||||||
|
if (in_array($oldEnrollmentStatus, ['admission under review', 'review & decision'], true)) {
|
||||||
|
$paymentPendingNotification = [
|
||||||
|
'parent_id' => $invoiceParentId,
|
||||||
|
'student_id' => $studentId,
|
||||||
|
'student_name' => trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')) ?: 'Student #' . $studentId,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔄 Re-tag attendance rows for this student in the current term (section + class)
|
// 🔄 Re-tag attendance rows for this student in the current term (section + class)
|
||||||
@@ -301,6 +321,10 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($paymentPendingNotification !== null) {
|
||||||
|
$this->triggerPaymentPendingNotification($paymentPendingNotification);
|
||||||
|
}
|
||||||
|
|
||||||
$resp = [
|
$resp = [
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'student_id' => $studentId,
|
'student_id' => $studentId,
|
||||||
@@ -323,6 +347,51 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function triggerPaymentPendingNotification(array $notification): void
|
||||||
|
{
|
||||||
|
$parentId = (int) ($notification['parent_id'] ?? 0);
|
||||||
|
$studentId = (int) ($notification['student_id'] ?? 0);
|
||||||
|
if ($parentId <= 0 || $studentId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parent = $this->userModel->find($parentId) ?? [];
|
||||||
|
$invoice = $this->db->table('invoices')
|
||||||
|
->where('parent_id', $parentId)
|
||||||
|
->where('school_year', (string) $this->schoolYear)
|
||||||
|
->orderBy('created_at', 'DESC')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->limit(1)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
$parentData = [
|
||||||
|
'user_id' => $parent['id'] ?? $parentId,
|
||||||
|
'email' => $parent['email'] ?? null,
|
||||||
|
'firstname' => $parent['firstname'] ?? '',
|
||||||
|
'lastname' => $parent['lastname'] ?? '',
|
||||||
|
'school_year' => (string) $this->schoolYear,
|
||||||
|
'portalLink' => base_url('/login'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($invoice) {
|
||||||
|
$parentData['amount'] = (float) ($invoice['balance'] ?? $invoice['amount_due'] ?? $invoice['total_amount'] ?? 0);
|
||||||
|
$parentData['due_date'] = $invoice['due_date'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Events::trigger('paymentPending', $parentData, [[
|
||||||
|
'student_id' => $studentId,
|
||||||
|
'name' => (string) ($notification['student_name'] ?? ('Student #' . $studentId)),
|
||||||
|
]]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'Payment pending notification after class assignment failed for parent {parent_id}: {error}', [
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a student-class assignment for the current term.
|
* Remove a student-class assignment for the current term.
|
||||||
*/
|
*/
|
||||||
@@ -1116,6 +1185,7 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
|
$activeEnrollmentStatuses = $this->distributionActiveEnrollmentStatuses();
|
||||||
|
|
||||||
if ($this->db->tableExists('student_class')) {
|
if ($this->db->tableExists('student_class')) {
|
||||||
$builder = $this->db->table('student_class sc')
|
$builder = $this->db->table('student_class sc')
|
||||||
@@ -1141,16 +1211,16 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->db->tableExists('enrollments')) {
|
if ($this->db->tableExists('enrollments')) {
|
||||||
|
$classIdExpression = $this->distributionEnrollmentClassIdExpression();
|
||||||
|
$sectionIdExpression = $this->distributionEnrollmentSectionIdExpression();
|
||||||
$builder = $this->db->table('enrollments e')
|
$builder = $this->db->table('enrollments e')
|
||||||
->select('0 AS promotion_queue_id, e.student_id, e.school_year AS school_year_from, cs.class_id AS source_class_id, cs.class_section_name AS source_class_name, students.firstname, students.lastname, students.gender, students.age, students.dob, students.registration_grade', false)
|
->select('0 AS promotion_queue_id, e.student_id, e.school_year AS school_year_from, ' . $classIdExpression . ' AS source_class_id, cs.class_section_name AS source_class_name, students.firstname, students.lastname, students.gender, students.age, students.dob, students.registration_grade', false)
|
||||||
->join('students', 'students.id = e.student_id', 'inner')
|
->join('students', 'students.id = e.student_id', 'inner')
|
||||||
->join('classSection cs', 'cs.class_section_id = e.class_section_id AND cs.school_year = ' . $this->db->escape($year), 'left', false)
|
->join('classSection cs', 'cs.class_section_id = ' . $sectionIdExpression . ' AND cs.school_year = ' . $this->db->escape($year), 'left', false)
|
||||||
->where('e.school_year', $year)
|
->where('e.school_year', $year)
|
||||||
->whereIn('e.enrollment_status', ['admission under review', 'payment pending', 'enrolled'])
|
->whereIn('e.enrollment_status', $activeEnrollmentStatuses);
|
||||||
->groupStart()
|
|
||||||
->where('e.is_withdrawn', 0)
|
$this->applyEnrollmentNotWithdrawnFilter($builder);
|
||||||
->orWhere('e.is_withdrawn', null)
|
|
||||||
->groupEnd();
|
|
||||||
|
|
||||||
if ($this->db->fieldExists('is_active', 'students')) {
|
if ($this->db->fieldExists('is_active', 'students')) {
|
||||||
$builder->where('students.is_active', 1);
|
$builder->where('students.is_active', 1);
|
||||||
@@ -1272,21 +1342,22 @@ class StudentController extends BaseController
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$classIdExpression = $this->distributionEnrollmentClassIdExpression();
|
||||||
|
$sectionIdExpression = $this->distributionEnrollmentSectionIdExpression();
|
||||||
|
|
||||||
$builder = $this->db->table('enrollments e')
|
$builder = $this->db->table('enrollments e')
|
||||||
->select('0 AS promotion_queue_id, e.student_id, e.school_year AS school_year_from, cs.class_id AS source_class_id, cs.class_section_name AS source_class_name, students.firstname, students.lastname, students.gender, students.age, students.dob, students.registration_grade', false)
|
->select('0 AS promotion_queue_id, e.student_id, e.school_year AS school_year_from, ' . $classIdExpression . ' AS source_class_id, cs.class_section_name AS source_class_name, students.firstname, students.lastname, students.gender, students.age, students.dob, students.registration_grade', false)
|
||||||
->join('students', 'students.id = e.student_id', 'inner')
|
->join('students', 'students.id = e.student_id', 'inner')
|
||||||
->join('classSection cs', 'cs.class_section_id = e.class_section_id AND cs.school_year = ' . $this->db->escape($year), 'left', false)
|
->join('classSection cs', 'cs.class_section_id = ' . $sectionIdExpression . ' AND cs.school_year = ' . $this->db->escape($year), 'left', false)
|
||||||
->where('e.school_year', $year)
|
->where('e.school_year', $year)
|
||||||
->whereIn('e.enrollment_status', ['admission under review', 'payment pending', 'enrolled'])
|
->whereIn('e.enrollment_status', $this->distributionActiveEnrollmentStatuses())
|
||||||
->groupStart()
|
|
||||||
->where('e.is_withdrawn', 0)
|
|
||||||
->orWhere('e.is_withdrawn', null)
|
|
||||||
->groupEnd()
|
|
||||||
->groupStart()
|
->groupStart()
|
||||||
->where('cs.class_id', $classId)
|
->where('cs.class_id', $classId)
|
||||||
->orWhereIn('UPPER(students.registration_grade)', ['KG', 'K', 'KINDERGARTEN'])
|
->orWhereIn('UPPER(students.registration_grade)', ['KG', 'K', 'KINDERGARTEN'])
|
||||||
->groupEnd();
|
->groupEnd();
|
||||||
|
|
||||||
|
$this->applyEnrollmentNotWithdrawnFilter($builder);
|
||||||
|
|
||||||
if ($this->db->fieldExists('is_active', 'students')) {
|
if ($this->db->fieldExists('is_active', 'students')) {
|
||||||
$builder->where('students.is_active', 1);
|
$builder->where('students.is_active', 1);
|
||||||
}
|
}
|
||||||
@@ -1459,19 +1530,17 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->db->tableExists('enrollments')) {
|
if ($this->db->tableExists('enrollments')) {
|
||||||
$rows = $this->db->table('enrollments e')
|
$sectionIdExpression = $this->distributionEnrollmentSectionIdExpression();
|
||||||
|
$builder = $this->db->table('enrollments e')
|
||||||
->select('cs.class_section_name')
|
->select('cs.class_section_name')
|
||||||
->join('classSection cs', 'cs.class_section_id = e.class_section_id AND cs.school_year = ' . $this->db->escape($previousYear), 'left', false)
|
->join('classSection cs', 'cs.class_section_id = ' . $sectionIdExpression . ' AND cs.school_year = ' . $this->db->escape($previousYear), 'left', false)
|
||||||
->where('e.student_id', $studentId)
|
->where('e.student_id', $studentId)
|
||||||
->where('e.school_year', $previousYear)
|
->where('e.school_year', $previousYear)
|
||||||
->where('e.class_section_id IS NOT NULL', null, false)
|
->where($sectionIdExpression . ' IS NOT NULL', null, false)
|
||||||
->whereIn('e.enrollment_status', ['admission under review', 'payment pending', 'enrolled'])
|
->whereIn('e.enrollment_status', $this->distributionActiveEnrollmentStatuses());
|
||||||
->groupStart()
|
|
||||||
->where('e.is_withdrawn', 0)
|
$this->applyEnrollmentNotWithdrawnFilter($builder);
|
||||||
->orWhere('e.is_withdrawn', null)
|
$rows = $builder->get()->getResultArray();
|
||||||
->groupEnd()
|
|
||||||
->get()
|
|
||||||
->getResultArray();
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$baseName = $this->baseClassNameForDistribution((string)($row['class_section_name'] ?? ''));
|
$baseName = $this->baseClassNameForDistribution((string)($row['class_section_name'] ?? ''));
|
||||||
@@ -1901,20 +1970,18 @@ class StudentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (empty($names) && $this->db->tableExists('enrollments')) {
|
if (empty($names) && $this->db->tableExists('enrollments')) {
|
||||||
$rows = $this->db->table('enrollments e')
|
$sectionIdExpression = $this->distributionEnrollmentSectionIdExpression();
|
||||||
|
$builder = $this->db->table('enrollments e')
|
||||||
->select('cs.class_section_name')
|
->select('cs.class_section_name')
|
||||||
->join('classSection cs', 'cs.class_section_id = e.class_section_id AND cs.school_year = ' . $this->db->escape($previousYear), 'left', false)
|
->join('classSection cs', 'cs.class_section_id = ' . $sectionIdExpression . ' AND cs.school_year = ' . $this->db->escape($previousYear), 'left', false)
|
||||||
->where('e.student_id', $studentId)
|
->where('e.student_id', $studentId)
|
||||||
->where('e.school_year', $previousYear)
|
->where('e.school_year', $previousYear)
|
||||||
->where('e.class_section_id IS NOT NULL', null, false)
|
->where($sectionIdExpression . ' IS NOT NULL', null, false)
|
||||||
->whereIn('e.enrollment_status', ['payment pending', 'enrolled'])
|
->whereIn('e.enrollment_status', $this->distributionActiveEnrollmentStatuses())
|
||||||
->groupStart()
|
->orderBy('cs.class_section_name', 'ASC');
|
||||||
->where('e.is_withdrawn', 0)
|
|
||||||
->orWhere('e.is_withdrawn', null)
|
$this->applyEnrollmentNotWithdrawnFilter($builder);
|
||||||
->groupEnd()
|
$rows = $builder->get()->getResultArray();
|
||||||
->orderBy('cs.class_section_name', 'ASC')
|
|
||||||
->get()
|
|
||||||
->getResultArray();
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$name = trim((string)($row['class_section_name'] ?? ''));
|
$name = trim((string)($row['class_section_name'] ?? ''));
|
||||||
if ($name !== '') {
|
if ($name !== '') {
|
||||||
@@ -2399,6 +2466,56 @@ class StudentController extends BaseController
|
|||||||
return $this->distributionBaseClassIdCache[$cacheKey];
|
return $this->distributionBaseClassIdCache[$cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function distributionActiveEnrollmentStatuses(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'admission under review',
|
||||||
|
'review & decision',
|
||||||
|
'payment pending',
|
||||||
|
'enrolled',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyEnrollmentNotWithdrawnFilter($builder): void
|
||||||
|
{
|
||||||
|
if (! $this->db->fieldExists('is_withdrawn', 'enrollments')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->groupStart()
|
||||||
|
->where('e.is_withdrawn', 0)
|
||||||
|
->orWhere('e.is_withdrawn', null)
|
||||||
|
->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function distributionEnrollmentClassIdExpression(): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach (['assigned_grade_id', 'source_grade_id'] as $field) {
|
||||||
|
if ($this->db->fieldExists($field, 'enrollments')) {
|
||||||
|
$parts[] = 'e.' . $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$parts[] = 'cs.class_id';
|
||||||
|
|
||||||
|
return count($parts) === 1 ? $parts[0] : 'COALESCE(' . implode(', ', $parts) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function distributionEnrollmentSectionIdExpression(): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach (['assigned_class_section_id', 'class_section_id', 'source_class_section_id'] as $field) {
|
||||||
|
if ($this->db->fieldExists($field, 'enrollments')) {
|
||||||
|
$parts[] = 'e.' . $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($parts)) {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($parts) === 1 ? $parts[0] : 'COALESCE(' . implode(', ', $parts) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
private function pendingDistributionDraftClassIds(string $year): array
|
private function pendingDistributionDraftClassIds(string $year): array
|
||||||
{
|
{
|
||||||
if ($year === '' || ! $this->db->tableExists('student_section_distribution_drafts')) {
|
if ($year === '' || ! $this->db->tableExists('student_section_distribution_drafts')) {
|
||||||
|
|||||||
@@ -901,6 +901,19 @@ private function applyDistributionDraftToStudentClass(int $studentId, string $ye
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$enrollment = $this->db->table('enrollments')
|
||||||
|
->select('class_section_id')
|
||||||
|
->where('student_id', $studentId)
|
||||||
|
->where('school_year', $year)
|
||||||
|
->orderBy('updated_at', 'DESC')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->limit(1)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
if ((int)($enrollment['class_section_id'] ?? 0) > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$targetSectionId = (int)($draft['class_section_id'] ?? 0);
|
$targetSectionId = (int)($draft['class_section_id'] ?? 0);
|
||||||
if ($targetSectionId <= 0) {
|
if ($targetSectionId <= 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ final class EnrollmentEligibility
|
|||||||
public const DEFERRED_MESSAGE = 'Re-enrollment cannot currently be completed because the final deliberation decision is deferred. Please contact the school administration for the next required step.';
|
public const DEFERRED_MESSAGE = 'Re-enrollment cannot currently be completed because the final deliberation decision is deferred. Please contact the school administration for the next required step.';
|
||||||
public const KG_MISSING_DECISION_ELIGIBLE_MESSAGE = 'KG students may complete registration now. Their new-year grade placement will be based on the school age-placement rule.';
|
public const KG_MISSING_DECISION_ELIGIBLE_MESSAGE = 'KG students may complete registration now. Their new-year grade placement will be based on the school age-placement rule.';
|
||||||
public const MISSING_DECISION_MESSAGE = 'Re-enrollment cannot currently be completed because no final deliberation decision is recorded for the student. Registration will become available after the school records a final decision.';
|
public const MISSING_DECISION_MESSAGE = 'Re-enrollment cannot currently be completed because no final deliberation decision is recorded for the student. Registration will become available after the school records a final decision.';
|
||||||
public const ADULT_STUDENT_MESSAGE = 'This student will be 18 years old or older on September 1 of the selected school year. A parent or guardian cannot complete registration for this student. Please contact school administration.';
|
public const ADULT_STUDENT_MESSAGE = 'This student will be 18 years old or older on September 1 of the selected school year. This student can no longer enroll with the school';
|
||||||
public const ADULT_STUDENT_PARENT_PORTAL_MESSAGE = self::ADULT_STUDENT_MESSAGE;
|
public const ADULT_STUDENT_PARENT_PORTAL_MESSAGE = self::ADULT_STUDENT_MESSAGE;
|
||||||
public const WITHDRAWN_PORTAL_MESSAGE = 'This student is currently marked as Withdrawn and cannot be enrolled at this time. Please contact school administration.';
|
public const WITHDRAWN_PORTAL_MESSAGE = 'This student is currently marked as Withdrawn and cannot be enrolled at this time. Please contact school administration.';
|
||||||
public const SIBLING_PORTAL_MESSAGE = 'Enrollment cannot continue because the family record requires administrative review. Please contact school administration.';
|
public const SIBLING_PORTAL_MESSAGE = 'Enrollment cannot continue because the family record requires administrative review. Please contact school administration.';
|
||||||
|
|||||||
@@ -279,7 +279,20 @@ $hasSettledEnrollmentStatus = static function (?string $status, ?string $admissi
|
|||||||
};
|
};
|
||||||
$enrollableCount = 0;
|
$enrollableCount = 0;
|
||||||
$withdrawableCount = 0;
|
$withdrawableCount = 0;
|
||||||
|
$currentYearTuitionStudentCount = 0;
|
||||||
foreach (($students ?? []) as $student) {
|
foreach (($students ?? []) as $student) {
|
||||||
|
$studentStatus = strtolower(trim((string) ($student['enrollment_status'] ?? '')));
|
||||||
|
$studentAdmissionStatus = strtolower(trim((string) ($student['admission_status'] ?? '')));
|
||||||
|
if (
|
||||||
|
! in_array($studentStatus, ['withdrawn', 'withdraw under review', 'refund pending', 'denied', 'not enrolled'], true)
|
||||||
|
&& (
|
||||||
|
in_array($studentStatus, ['admission under review', 'review & decision', 'payment pending', 'enrolled', 'waitlist'], true)
|
||||||
|
|| $studentAdmissionStatus === 'accepted'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$currentYearTuitionStudentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||||||
if (
|
if (
|
||||||
(($evaluation['can_enroll'] ?? false) === true || ($evaluation['parent_enrollment_allowed'] ?? false) === true)
|
(($evaluation['can_enroll'] ?? false) === true || ($evaluation['parent_enrollment_allowed'] ?? false) === true)
|
||||||
@@ -942,6 +955,7 @@ $studentCount = count($students ?? []);
|
|||||||
'carryOverBalance' => (float) ($familyFinancialSummary['carry_forward_balance'] ?? $familyFinancialSummary['carry_over_balance'] ?? 0),
|
'carryOverBalance' => (float) ($familyFinancialSummary['carry_forward_balance'] ?? $familyFinancialSummary['carry_over_balance'] ?? 0),
|
||||||
'currentBalance' => (float) ($familyFinancialSummary['current_year_balance'] ?? $familyFinancialSummary['current_balance'] ?? 0),
|
'currentBalance' => (float) ($familyFinancialSummary['current_year_balance'] ?? $familyFinancialSummary['current_balance'] ?? 0),
|
||||||
'amountDue' => (float) ($familyFinancialSummary['total_enrollment_due'] ?? $familyFinancialSummary['amount_due'] ?? 0),
|
'amountDue' => (float) ($familyFinancialSummary['total_enrollment_due'] ?? $familyFinancialSummary['amount_due'] ?? 0),
|
||||||
|
'currentYearTuitionStudentCount' => (int) ($currentYearTuitionStudentCount ?? 0),
|
||||||
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||||||
let currentStep = 0;
|
let currentStep = 0;
|
||||||
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
|
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
|
||||||
@@ -975,7 +989,8 @@ $studentCount = count($students ?? []);
|
|||||||
|
|
||||||
function calculateSelectedTuition() {
|
function calculateSelectedTuition() {
|
||||||
return selectedEnrollInputs().reduce((total, input, index) => {
|
return selectedEnrollInputs().reduce((total, input, index) => {
|
||||||
return total + (index === 0 ? Number(feeSchedule.firstStudentFee || 0) : Number(feeSchedule.secondStudentFee || 0));
|
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
|
||||||
|
return total + (familyPosition === 0 ? Number(feeSchedule.firstStudentFee || 0) : Number(feeSchedule.secondStudentFee || 0));
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,8 +1052,8 @@ $studentCount = count($students ?? []);
|
|||||||
}
|
}
|
||||||
|
|
||||||
feeReviewList.innerHTML = cards.map((student, index) => {
|
feeReviewList.innerHTML = cards.map((student, index) => {
|
||||||
const tuitionFee = index === 0 ? feeSchedule.firstStudentFee : feeSchedule.secondStudentFee;
|
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
|
||||||
const tier = index === 0 ? 'First student tuition tier' : 'Additional student tuition tier';
|
const tuitionFee = familyPosition === 0 ? feeSchedule.firstStudentFee : feeSchedule.secondStudentFee;
|
||||||
const lines = [
|
const lines = [
|
||||||
['Student', student.name],
|
['Student', student.name],
|
||||||
['School ID', student.schoolId || 'N/A'],
|
['School ID', student.schoolId || 'N/A'],
|
||||||
@@ -1717,11 +1732,11 @@ $studentCount = count($students ?? []);
|
|||||||
});
|
});
|
||||||
|
|
||||||
refreshEligibility().then(() => {
|
refreshEligibility().then(() => {
|
||||||
if (enrollmentStartStep >= 2 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
if (enrollmentStartStep >= 1 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
||||||
openEnrollmentAtStep(enrollmentStartStep - 1);
|
openEnrollmentAtStep(enrollmentStartStep - 1);
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
if (enrollmentStartStep >= 2 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
if (enrollmentStartStep >= 1 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
||||||
openEnrollmentAtStep(enrollmentStartStep - 1);
|
openEnrollmentAtStep(enrollmentStartStep - 1);
|
||||||
}
|
}
|
||||||
// Keep server-rendered eligibility if refresh fails.
|
// Keep server-rendered eligibility if refresh fails.
|
||||||
|
|||||||
@@ -101,20 +101,13 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php
|
<?php
|
||||||
$hasUnenrolled = false;
|
$hasUnenrolled = false;
|
||||||
$unenrolledStudentIds = [];
|
|
||||||
foreach ($existingKids as $kid) {
|
foreach ($existingKids as $kid) {
|
||||||
if ($kid['enrollment'] == 0) {
|
if ($kid['enrollment'] == 0) {
|
||||||
$hasUnenrolled = true;
|
$hasUnenrolled = true;
|
||||||
$studentId = (int) ($kid['id'] ?? 0);
|
|
||||||
if ($studentId > 0) {
|
|
||||||
$unenrolledStudentIds[] = $studentId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
usort($unenrolledStudentIds, static fn (int $a, int $b): int => $b <=> $a);
|
|
||||||
$enrollmentUrl = base_url('/parent/enroll_classes?' . http_build_query([
|
$enrollmentUrl = base_url('/parent/enroll_classes?' . http_build_query([
|
||||||
'start' => 2,
|
'start' => 1,
|
||||||
'students' => implode(',', $unenrolledStudentIds),
|
|
||||||
]));
|
]));
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
'administrator/sections/auto-distribute',
|
'administrator/sections/auto-distribute',
|
||||||
'admin/enrollment/new-students',
|
'admin/enrollment/new-students',
|
||||||
'payment/unpaid-parents',
|
'payment/unpaid-parents',
|
||||||
'discounts/list',
|
|
||||||
'expenses/index',
|
'expenses/index',
|
||||||
'reimbursements/index',
|
'reimbursements/index',
|
||||||
];
|
];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user