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
@@ -27,6 +27,7 @@
<th>Year</th>
<th>Status</th>
<th>Requested</th>
<th>Approved</th>
<th>Submitted</th>
<th></th>
</tr>
@@ -40,12 +41,13 @@
<td><?= esc($row['school_year'] ?? '') ?></td>
<td><?= esc($row['status'] ?? '') ?></td>
<td><?= $row['requested_amount'] !== null && $row['requested_amount'] !== '' ? '$' . number_format((float) $row['requested_amount'], 2) : '—' ?></td>
<td><?= $row['admin_amount'] !== null && $row['admin_amount'] !== '' ? '$' . number_format((float) $row['admin_amount'], 2) : '—' ?></td>
<td><?= esc($row['created_at'] ?? '') ?></td>
<td><a class="btn btn-sm btn-outline-primary" href="<?= site_url('administrator/financial-aid/' . (int) $row['id']) ?>">Review</a></td>
</tr>
<?php endforeach; ?>
<?php if (empty($requests)): ?>
<tr><td colspan="7" class="text-muted text-center">No financial aid requests found.</td></tr>
<tr><td colspan="8" class="text-muted text-center">No financial aid requests found.</td></tr>
<?php endif; ?>
</tbody>
</table>
+1 -1
View File
@@ -5,7 +5,7 @@
<h1>Apply Discount Voucher</h1>
<?= $this->include('partials/academic_filter') ?>
<form method="post" action="">
<form method="post" action="<?= site_url('discount/apply') ?>">
<?= csrf_field() ?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<div>
@@ -104,6 +104,18 @@
if (!list || !list.length) return '<em>No students enrolled.</em>';
return list.map(k => `${esc(k.name)} (Grade: ${esc(k.grade)})`).join('<br>');
}
function renderRefundCell(r) {
const details = Array.isArray(r.refund_details) ? r.refund_details : [];
const lines = details.map(d => {
const ts = d.date ? Date.parse(d.date) : NaN;
const date = Number.isNaN(ts) ? '-' : formatDateTime(ts);
const method = d.method ? esc(d.method) : '-';
const check = d.check_number ? `, Check # ${esc(d.check_number)}` : '';
return `<div class="text-muted small">${date} &middot; ${method}${check}</div>`;
}).join('');
return `<div class="fw-semibold">${fmtMoney(r.refund_amount)}</div>${lines}`;
}
function renderParentCell(r) {
const pid = parseInt(r.parent_id || 0, 10);
const name = esc(r.parent_name || '');
@@ -113,6 +125,21 @@
}
return name;
}
function renderInvoiceRow(r) {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
renderParentCell(r),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
renderRefundCell(r),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
}
async function generateInvoice(parentId) {
const body = new URLSearchParams();
@@ -160,20 +187,7 @@
const resp = await loadInvoices(selectedSchoolYear());
syncSchoolYearSelect(resp);
const data = (resp.invoices || []).map(r => {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
renderParentCell(r),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
fmtMoney(r.refund_amount),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
});
const data = (resp.invoices || []).map(renderInvoiceRow);
if ($.fn.DataTable.isDataTable($tbl)) {
const dti = $tbl.DataTable();
dti.clear();
@@ -190,20 +204,7 @@
const resp = await loadInvoices();
syncSchoolYearSelect(resp);
const data = (resp.invoices || []).map(r => {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
renderParentCell(r),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
fmtMoney(r.refund_amount),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
});
const data = (resp.invoices || []).map(renderInvoiceRow);
dt = $tbl.DataTable({
data,
@@ -232,20 +233,7 @@
await generateInvoice(btn.getAttribute('data-parent-id'));
// Refresh table minimally: reload data
const resp = await loadInvoices(selectedSchoolYear());
const data = (resp.invoices || []).map(r => {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
renderParentCell(r),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
fmtMoney(r.refund_amount),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
});
const data = (resp.invoices || []).map(renderInvoiceRow);
if ($.fn.DataTable.isDataTable($tbl)) {
const dti = $tbl.DataTable();
dti.clear();
@@ -265,20 +253,7 @@
this.disabled = true;
await generateInvoice(this.getAttribute('data-parent-id'));
const resp = await loadInvoices(selectedSchoolYear());
const data = (resp.invoices || []).map(r => {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
esc(r.parent_name || ''),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
fmtMoney(r.refund_amount),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
});
const data = (resp.invoices || []).map(renderInvoiceRow);
if ($.fn.DataTable.isDataTable($tbl)) {
const dti = $tbl.DataTable();
dti.clear();
@@ -307,20 +282,7 @@
$year.addEventListener('change', async () => {
try {
const resp = await loadInvoices($year.value);
const data = (resp.invoices || []).map(r => {
const ts = Date.parse(r.invoice_date || new Date().toISOString());
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
return [
esc(r.parent_name || ''),
renderStudents(r.enrolledKids || []),
genBtn,
fmtMoney(r.invoice_amount),
fmtMoney(r.refund_amount),
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
pdf,
];
});
const data = (resp.invoices || []).map(renderInvoiceRow);
if ($.fn.DataTable.isDataTable($tbl)) {
const dti = $tbl.DataTable();
dti.clear();
+1 -1
View File
@@ -233,7 +233,7 @@ html, body { overflow-x: hidden; }
} catch (\Throwable $e) {
$sy = (string)($cfg->getConfig('school_year') ?? '');
}
$sem = (string)($cfg->getConfig('semester') ?? '');
$sem = (string)(getSemester() ?? '');
$uid = (int)(session()->get('user_id') ?? 0);
if ($uid) {
$classOptions = $tcModel->getClassAssignmentsByUserId($uid, $sy, $sem);
+28 -19
View File
@@ -73,6 +73,21 @@
width: 100%;
}
.enrollment-withdraw-control {
align-items: center;
display: inline-flex;
gap: .5rem;
justify-content: flex-end;
min-width: max-content;
padding-left: 0;
white-space: nowrap;
}
.enrollment-withdraw-control .form-check-input {
flex: 0 0 auto;
margin-left: 0;
}
.enrollment-modal-footer {
gap: .5rem;
}
@@ -150,6 +165,18 @@
.enrollment-status-table td > * {
max-width: 58%;
}
.enrollment-status-table td[data-label="Withdraw"] {
align-items: center;
}
.enrollment-status-table td[data-label="Withdraw"] > * {
max-width: none;
}
.enrollment-status-table td[data-label="Withdraw"] .enrollment-withdraw-control {
margin-left: auto;
}
}
</style>
<?= $this->endSection() ?>
@@ -217,24 +244,6 @@ foreach (($students ?? []) as $student) {
</ul>
</div>
<?php if ($familyFinancialSummary !== []): ?>
<div class="border rounded p-3 mb-3 bg-light">
<div class="fw-semibold mb-2">Family Account Information</div>
<div class="row g-2">
<div class="col-md-4"><span class="text-muted">Previous-year carry-over balance:</span> <strong data-financial-value="carry_over_balance"><?= esc($money($familyFinancialSummary['carry_over_balance'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Registration fee:</span> <strong data-financial-value="registration_fee"><?= esc($money($familyFinancialSummary['registration_fee'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Tuition due now:</span> <strong data-financial-value="tuition_due_at_registration"><?= esc($money($familyFinancialSummary['tuition_due_at_registration'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Mandatory fees:</span> <strong data-financial-value="mandatory_fees"><?= esc($money($familyFinancialSummary['mandatory_fees'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Current-year account balance:</span> <strong data-financial-value="current_balance"><?= esc($money($familyFinancialSummary['current_balance'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Total currently due:</span> <strong data-financial-value="amount_due"><?= esc($money($familyFinancialSummary['amount_due'] ?? 0)) ?></strong></div>
</div>
<?php if (!empty($familyFinancialSummary['policy_message'])): ?>
<div class="small text-muted mt-2"><?= esc($familyFinancialSummary['policy_message']) ?></div>
<?php endif; ?>
<div class="small mt-2"><a href="<?= site_url('parent/financial-aid') ?>">Request financial aid</a></div>
</div>
<?php endif; ?>
<?php if (!empty($students)): ?>
<form action="<?= base_url('/parent/enroll_classes_handler') ?>" method="post" id="enrollmentFlowForm">
<?= csrf_field() ?>
@@ -297,7 +306,7 @@ foreach (($students ?? []) as $student) {
<td data-label="Status"><?= $statusBadge($student['enrollment_status'] ?? '') ?></td>
<td data-label="Withdraw">
<?php if (($student['enrollment_status'] ?? '') === 'enrolled'): ?>
<div class="form-check form-switch m-0">
<div class="form-check form-switch m-0 enrollment-withdraw-control">
<input class="form-check-input" type="checkbox" name="withdraw[]" value="<?= esc($student['id']) ?>" id="withdraw-<?= esc($student['id']) ?>" <?= !$isEditable ? 'disabled' : '' ?>>
<label class="form-check-label small" for="withdraw-<?= esc($student['id']) ?>">Request</label>
</div>
+76 -14
View File
@@ -6,6 +6,68 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
?>
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('styles') ?>
<style>
@media (max-width: 575.98px) {
.parent-register-stack-table {
border: 0;
}
.parent-register-stack-table thead {
display: none;
}
.parent-register-stack-table tbody,
.parent-register-stack-table tr,
.parent-register-stack-table td {
display: block;
width: 100%;
}
.parent-register-stack-table tr {
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: .85rem;
overflow: hidden;
}
.parent-register-stack-table td {
align-items: flex-start;
border-bottom: 1px solid #eef1f3;
display: flex;
gap: .75rem;
justify-content: space-between;
padding: .75rem;
text-align: right;
}
.parent-register-stack-table td:last-child {
border-bottom: 0;
}
.parent-register-stack-table td::before {
color: #6c757d;
content: attr(data-label);
flex: 0 0 42%;
font-size: .8rem;
font-weight: 700;
text-align: left;
}
.parent-register-stack-table td > * {
max-width: 58%;
}
.parent-register-stack-table td[data-label="Action"] {
align-items: center;
}
.parent-register-stack-table td[data-label="Action"] .btn {
white-space: nowrap;
}
}
</style>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container my-5">
<h3 class="text-center text-success mb-3" style="font-family: Arial, sans-serif;">Student Registration</h3>
@@ -67,7 +129,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
<?php endif; ?>
<?php if (!empty($existingKids)): ?>
<h5>Student Information</h5>
<table class="table table-bordered">
<table class="table table-bordered parent-register-stack-table">
<thead class="table-light">
<tr>
<th>School ID</th>
@@ -83,12 +145,12 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
<tbody>
<?php foreach ($existingKids as $kid): ?>
<tr>
<td><?= esc($kid['school_id']) ?></td>
<td><?= esc($kid['firstname']) ?></td>
<td><?= esc($kid['lastname']) ?></td>
<td><?= esc((new DateTime($kid['dob']))->format('m-d-Y')) ?></td>
<td><?= esc($kid['registration_grade']) ?></td>
<td>
<td data-label="School ID"><?= esc($kid['school_id']) ?></td>
<td data-label="First Name"><?= esc($kid['firstname']) ?></td>
<td data-label="Last Name"><?= esc($kid['lastname']) ?></td>
<td data-label="DOB"><?= esc((new DateTime($kid['dob']))->format('m-d-Y')) ?></td>
<td data-label="Grade"><?= esc($kid['registration_grade']) ?></td>
<td data-label="Medical Conditions">
<?php
$mc = is_array($kid['medical_conditions'] ?? null)
? $kid['medical_conditions']
@@ -99,7 +161,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
echo implode('<br>', array_map('esc', $mc));
?>
</td>
<td>
<td data-label="Allergies">
<?php
$al = is_array($kid['allergies'] ?? null)
? $kid['allergies']
@@ -109,7 +171,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
echo implode('<br>', array_map('esc', $al));
?>
</td>
<td class="text-center">
<td class="text-center" data-label="Action">
<?php if ($isEditable && !empty($kid['can_delete'])): ?>
<form action="<?= base_url('/parent/delete_student/' . $kid['id']) ?>"
method="post"
@@ -141,7 +203,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
<?php if (!empty($emergencies)): ?>
<h5 class="mt-4">Emergency Contacts</h5>
<table class="table table-bordered">
<table class="table table-bordered parent-register-stack-table">
<thead class="table-light">
<tr>
<th>First Name</th>
@@ -159,10 +221,10 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
$lastName = $nameParts[1] ?? '';
?>
<tr>
<td><?= esc($firstName) ?></td>
<td><?= esc($lastName) ?></td>
<td><?= esc($contact['cellphone']) ?></td>
<td><?= esc($contact['relation']) ?></td>
<td data-label="First Name"><?= esc($firstName) ?></td>
<td data-label="Last Name"><?= esc($lastName) ?></td>
<td data-label="Phone"><?= esc($contact['cellphone']) ?></td>
<td data-label="Relation"><?= esc($contact['relation']) ?></td>
</tr>
<?php include(APPPATH . 'Views/parent/edit_emergency_contact.php'); ?>
<?php endforeach; ?>
+83 -6
View File
@@ -1,4 +1,81 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('styles') ?>
<style>
@media (max-width: 575.98px) {
.parent-report-cards-table {
border: 0;
}
.parent-report-cards-table thead {
display: none;
}
.parent-report-cards-table tbody,
.parent-report-cards-table tr,
.parent-report-cards-table td {
display: block;
width: 100%;
}
.parent-report-cards-table tr {
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: .85rem;
overflow: hidden;
}
.parent-report-cards-table td {
align-items: flex-start;
border-bottom: 1px solid #eef1f3;
display: flex;
gap: .75rem;
justify-content: space-between;
padding: .75rem;
text-align: right;
}
.parent-report-cards-table td:last-child {
border-bottom: 0;
}
.parent-report-cards-table td::before {
color: #6c757d;
content: attr(data-label);
flex: 0 0 38%;
font-size: .8rem;
font-weight: 700;
text-align: left;
}
.parent-report-cards-table td > * {
max-width: 62%;
}
.parent-report-cards-table td[data-label="Action"] {
display: block;
text-align: left !important;
}
.parent-report-cards-table td[data-label="Action"]::before {
display: block;
margin-bottom: .5rem;
}
.parent-report-cards-table td[data-label="Action"] > * {
max-width: 100%;
}
.parent-report-cards-table td[data-label="Action"] form {
align-items: stretch !important;
display: flex !important;
flex-direction: column;
margin-left: 0 !important;
margin-top: .5rem;
width: 100%;
}
}
</style>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?php
$isEditable = (bool) ($isEditable ?? true);
@@ -30,7 +107,7 @@
<div class="alert alert-info">No students available for report cards.</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle">
<table class="table table-striped table-bordered align-middle parent-report-cards-table">
<thead class="table-light">
<tr>
<th>Student</th>
@@ -51,10 +128,10 @@
$hasReport = !empty(($reportAvailableMap ?? [])[$sid]);
?>
<tr>
<td><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></td>
<td><?= esc($student['class_section_name'] ?? 'N/A') ?></td>
<td><?= $viewedAt ? esc(local_datetime($viewedAt, 'm-d-Y H:i')) : 'Not viewed' ?></td>
<td>
<td data-label="Student"><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></td>
<td data-label="Class Section"><?= esc($student['class_section_name'] ?? 'N/A') ?></td>
<td data-label="Viewed"><?= $viewedAt ? esc(local_datetime($viewedAt, 'm-d-Y H:i')) : 'Not viewed' ?></td>
<td data-label="Signature">
<?php if ($signedAt): ?>
<?= esc($signedName ?: 'Signed') ?><br>
<small class="text-muted"><?= esc(local_datetime($signedAt, 'm-d-Y H:i')) ?></small>
@@ -62,7 +139,7 @@
<span class="text-muted">Not signed</span>
<?php endif; ?>
</td>
<td class="text-end">
<td class="text-end" data-label="Action">
<?php if ($hasReport): ?>
<a class="btn btn-sm btn-outline-primary" target="_blank" href="<?= base_url('parent/report-cards/view/' . $sid) ?>">View Report</a>
<?php else: ?>
+2 -2
View File
@@ -7,7 +7,7 @@
// - Reset link uses '?' to clear query string reliably.
// Ensure helper functions are available
if (function_exists('helper')) { @helper('GlobalConfigHelper'); }
if (function_exists('helper')) { @helper('global_config'); }
// Resolve defaults
$currentYear = function_exists('getSchoolYear') ? (string) (getSchoolYear() ?? '') : '';
@@ -16,7 +16,7 @@ if ($currentYear === '' || $currentSem === '') {
try {
$cfg = new \App\Models\ConfigurationModel();
if ($currentYear === '') $currentYear = (string) ($cfg->getConfig('school_year') ?? '');
if ($currentSem === '') $currentSem = (string) ($cfg->getConfig('semester') ?? '');
if ($currentSem === '') $currentSem = (string) (getSemester() ?? '');
} catch (\Throwable $e) { /* ignore */ }
}
+1 -1
View File
@@ -277,7 +277,7 @@ switch ($role) {
} catch (\Throwable $e) {
$year = $sess->get('school_year') ?? $configModel->getConfig('school_year');
}
$semester = $sess->get('semester') ?? $configModel->getConfig('semester');
$semester = $sess->get('semester') ?? getSemester();
$activeEventCount = count($eventModel->getActiveEvents($year, $semester) ?? []);
}
?>
+11
View File
@@ -175,6 +175,7 @@
aria-autocomplete="list"
aria-haspopup="listbox"
value="<?= esc($searchTermUsedInSearch ?? '') ?>">
<button class="btn btn-outline-secondary" type="button" id="manualPayClearSearch">Clear</button>
<button class="btn btn-primary" type="submit">Search</button>
</div>
<div id="manualPaySuggest" class="list-group manual-pay-suggest d-none" role="listbox"></div>
@@ -1001,9 +1002,11 @@
function initSearchSuggest() {
const input = _el('manualPaySearchInput');
const suggest = _el('manualPaySuggest');
const clear = _el('manualPayClearSearch');
if (!input || !suggest) return;
const suggestUrl = <?= json_encode(site_url('payment/manual_pay_suggest')) ?>;
const manualPayUrl = <?= json_encode(site_url('payment/manual_pay')) ?>;
let lastRequest = 0;
function hideSuggest() {
@@ -1089,6 +1092,14 @@
if (e.key === 'Escape') hideSuggest();
});
if (clear) {
clear.addEventListener('click', () => {
input.value = '';
hideSuggest();
window.location.href = manualPayUrl;
});
}
document.addEventListener('click', (e) => {
if (e.target === input || suggest.contains(e.target)) return;
hideSuggest();
+31 -1
View File
@@ -1,11 +1,27 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<?php $isSchoolYearReadonly = (bool) ($isSchoolYearReadonly ?? false); ?>
<?php
$isSchoolYearReadonly = (bool) ($isSchoolYearReadonly ?? false);
$successMessage = session()->getFlashdata('success');
$errorMessage = session()->getFlashdata('error');
?>
<div class="container-fluid py-5">
<div class="row">
<div class="col-md-12">
<h2>Print/Copy Requests</h2>
<?php if ($successMessage): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= esc($successMessage) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if ($errorMessage): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= esc($errorMessage) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if ($isSchoolYearReadonly): ?>
<div class="alert alert-warning">
This school year is read-only. Existing requests are visible, but changes are disabled.
@@ -46,6 +62,7 @@
<div class="tab-pane fade show active" id="tab-print" role="tabpanel" aria-labelledby="tab-print-tab">
<form action="<?= site_url('print-requests/create') ?>" method="post" enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="request_token" value="<?= esc($printRequestToken ?? '') ?>">
<?php if (session()->has('errors')): ?>
<div class="alert alert-danger">
<ul>
@@ -128,6 +145,7 @@
</div>
<form id="copyRequestForm" method="post" action="<?= site_url('print-requests/create-copy') ?>">
<?= csrf_field() ?>
<input type="hidden" name="request_token" value="<?= esc($copyRequestToken ?? '') ?>">
<div class="row g-3">
<div class="col-md-4">
<label for="copy_num_copies" class="form-label">Number of Copies</label>
@@ -353,8 +371,14 @@ document.addEventListener('DOMContentLoaded', function() {
const dateSelect = document.getElementById('required_by_date');
const timeSelect = document.getElementById('required_by_time');
const hiddenInput = document.getElementById('required_by');
const submitButton = createForm.querySelector('button[type="submit"]');
if (dateSelect.value && timeSelect.value) {
hiddenInput.value = dateSelect.value + 'T' + timeSelect.value;
if (submitButton) {
submitButton.disabled = true;
submitButton.dataset.originalText = submitButton.textContent;
submitButton.textContent = 'Submitting...';
}
} else {
if (!dateSelect.value) {
alert('Please select a date.');
@@ -483,6 +507,7 @@ if (copyForm) {
const dateSelect = document.getElementById('copy_required_by_date');
const timeSelect = document.getElementById('copy_required_by_time');
const hiddenInput = document.getElementById('copy_required_by');
const submitButton = copyForm.querySelector('button[type="submit"]');
if (!dateSelect || !dateSelect.value) {
alert('Please select a date for the copy request.');
e.preventDefault();
@@ -496,6 +521,11 @@ if (copyForm) {
if (hiddenInput) {
hiddenInput.value = dateSelect.value + 'T' + timeSelect.value;
}
if (submitButton) {
submitButton.disabled = true;
submitButton.dataset.originalText = submitButton.textContent;
submitButton.textContent = 'Submitting...';
}
});
}
</script>
-381
View File
@@ -1,384 +1,3 @@
Yesterday 11:12 PM
write sql querry to update school_year = 2025-2026
UPDATE your_table_name
SET school_year = '2025-2026'
WHERE your_condition;
To update every row:
UPDATE your_table_name
SET school_year = '2025-2026';
Use the second version only if you genuinely intend to rewrite the entire table, because databases are remarkably obedient when humans forget WHERE.
Today 12:49 AM
Screenshot 2026-07-14 at 12.49.07AM.png
Pasted code(6).php
File
remove the image content from the view
Removed the School year / Semester filter from the view by deleting:
<?= $this->include('partials/academic_filter') ?>
Pasted_code_6_without_academic_filter.php
Library
/
Pasted_code_6_without_academic_filter.php
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
// Role formatter for display + posting
$formatRole = function (?string $role): string {
$role = (string)$role;
$role = str_replace(['-', '_'], ' ', $role);
$role = preg_replace('/\s+/', ' ', trim($role));
if ($role === '') return '';
$out = [];
foreach (explode(' ', $role) as $w) {
if ($w === '') continue;
if (preg_match('/^[A-Za-z]{1,3}$/', $w)) {
$out[] = strtoupper($w); // TA, PTA, HR, KG...
} else {
$out[] = ucfirst(strtolower($w)); // Teacher, Assistant, Admin...
}
}
return implode(' ', $out);
};
?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<div class="text-center mx-auto mb-5 wow" data-wow-delay="0.1s" style="max-width: 600px;">
<br>
<h2 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Generate Staff Badges</h2>
</div>
<div class="d-flex flex-column flex-md-row justify-content-md-end align-items-md-center gap-2">
<!-- Submit button associates to form below -->
<button type="submit" class="btn btn-success mt-3" form="badgeForm">Generate Badges</button>
<br>
</div>
<!-- Staff selection -->
<form method="post" action="<?= base_url('badge') ?>" target="_blank" id="badgeForm">
<?= csrf_field() ?>
<table id="staffTable" class="table table-bordered table-striped align-middle w-100">
<thead class="table-dark">
<tr>
<th style="width: 70px;">#</th>
<th scope="col">Firstname</th>
<th scope="col">Lastname</th>
<th scope="col">Role</th>
<th scope="col">Teacher Class Section</th>
<th scope="col" style="width: 140px;">Badge Prints</th>
<th scope="col" style="width: 150px;">
<div class="form-check m-0">
<input class="form-check-input" type="checkbox" id="select-all">
<label class="form-check-label" for="select-all">Select All (page)</label>
</div>
</th>
</tr>
</thead>
<tbody>
<?php if (!empty($users)): ?>
<?php $order = 1; ?>
<?php foreach ($users as $user): ?>
<?php
// Pick a representative role (first of CSV or role_name/active_role)
$roleRaw = $user['active_role'] ?? $user['role_name'] ?? ($user['roles'] ?? '');
if (strpos((string)$roleRaw, ',') !== false) {
$parts = array_filter(array_map('trim', explode(',', (string)$roleRaw)));
$roleRaw = $parts[0] ?? '';
}
//$roleLabel = $roleRaw !== '' ? $formatRole($roleRaw) : '-';
$roleLabel = $user['role_name'] ?? ($user['roles'] ?? '-'); // both are pre-formatted by $formatRole
// Normalize user id key for checkbox
$uid = $user['user_id'] ?? $user['id'] ?? ($user['users.id'] ?? null);
// Class section name (optional)
$className = !empty($user['class_section_name']) ? (string)$user['class_section_name'] : '';
?>
<tr
data-user-id="<?= esc($uid ?? '') ?>"
data-role-label="<?= esc($roleLabel) ?>"
data-class-name="<?= esc($className) ?>">
<td style="text-align: center;"><?= esc($order++) ?></td>
<td><?= esc($user['firstname'] ?? '') ?></td>
<td><?= esc($user['lastname'] ?? '') ?></td>
<td><?= esc($roleLabel) ?></td>
<td><?= $className !== '' ? esc($className) : '-' ?></td>
<td><span class="badge bg-secondary prints-badge" data-user-id="<?= esc($uid ?? '') ?>">—</span></td>
<td>
<?php if ($uid !== null): ?>
<div class="form-check m-0">
<input type="checkbox" name="user_ids[]" value="<?= esc($uid) ?>" class="form-check-input user-checkbox" id="chk-<?= esc($uid) ?>">
<label class="form-check-label" for="chk-<?= esc($uid) ?>">Select</label>
</div>
<?php else: ?>
<span class="text-muted">N/A</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center text-muted">No staff found for the selected year.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</form>
<br>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track selected user IDs across paging/filtering/sorting
const selected = new Set();
const formEl = document.getElementById('badgeForm');
let clearTimerId = null; // pending auto-clear timer, if any
let hiddenContainer = document.getElementById('selectedHiddenInputs');
// Ensure hidden container exists inside the form (some earlier layouts had it outside)
if (!hiddenContainer || !formEl.contains(hiddenContainer)) {
hiddenContainer = document.createElement('div');
hiddenContainer.id = 'selectedHiddenInputs';
hiddenContainer.style.display = 'none';
formEl.appendChild(hiddenContainer);
}
const selectAll = document.getElementById('select-all');
const csrfName = <?= json_encode(csrf_token()) ?>;
const csrfInput = document.querySelector(`input[name='${csrfName}']`);
// Build a meta map (userId -> { role, className }) from the DOM BEFORE DataTables paginates
const rowMeta = {};
document.querySelectorAll('#staffTable tbody tr').forEach(tr => {
const id = tr.getAttribute('data-user-id');
if (!id) return;
rowMeta[id] = {
role: tr.getAttribute('data-role-label') || '',
className: tr.getAttribute('data-class-name') || ''
};
});
const table = $('#staffTable').DataTable({
stateSave: true,
pageLength: 100,
lengthMenu: [10, 25, 50, 100],
order: [
[1, 'asc'],
[2, 'asc']
], // Firstname, Lastname
columnDefs: [{
targets: 0,
searchable: false
}, // row #
{
targets: 6,
orderable: false,
searchable: false
} // checkbox column
]
});
// Helper: keep hidden inputs in sync so selections submit even if not on current page
function syncHiddenInputs() {
hiddenContainer.innerHTML = '';
selected.forEach(function(id) {
const meta = rowMeta[id] || {
role: '',
className: ''
};
// user_ids[]
const i1 = document.createElement('input');
i1.type = 'hidden';
i1.name = 'user_ids[]';
i1.value = id;
hiddenContainer.appendChild(i1);
// roles[ID]
const i2 = document.createElement('input');
i2.type = 'hidden';
i2.name = `roles[${id}]`;
i2.value = meta.role;
hiddenContainer.appendChild(i2);
// classes[ID]
const i3 = document.createElement('input');
i3.type = 'hidden';
i3.name = `classes[${id}]`;
i3.value = meta.className;
hiddenContainer.appendChild(i3);
});
}
// Helper: refresh checkboxes on current page based on Set
function syncPageCheckboxes() {
const pageNodes = table.rows({
page: 'current'
}).nodes().to$();
let allChecked = true;
let anyChecked = false;
pageNodes.each(function() {
const row = this;
const userId = row.getAttribute('data-user-id');
const cb = row.querySelector('.user-checkbox');
if (!cb || !userId) return;
cb.checked = selected.has(userId);
if (!cb.checked) allChecked = false;
if (cb.checked) anyChecked = true;
});
// Update header select-all for current page
selectAll.checked = allChecked && pageNodes.length > 0;
selectAll.indeterminate = !allChecked && anyChecked;
}
// Helper: clear all selections and reset the current page UI
function clearSelectionsNow() {
selected.clear();
// Uncheck visible checkboxes on current page
table.rows({ page: 'current' }).every(function () {
const row = this.node();
const cb = row.querySelector('.user-checkbox');
if (cb) cb.checked = false;
});
// Reset select-all and hidden inputs
selectAll.checked = false;
selectAll.indeterminate = false;
syncHiddenInputs();
syncPageCheckboxes();
}
// On draw (paging/sort/search), just refresh checkbox UI states
table.on('draw', function() {
syncPageCheckboxes();
});
// Delegate checkbox change handling once at the table level (works across redraws)
document.getElementById('staffTable').addEventListener('change', function (e) {
const target = e.target;
if (!target || !target.classList.contains('user-checkbox')) return;
const tr = target.closest('tr[data-user-id]');
const userId = tr ? tr.getAttribute('data-user-id') : null;
if (!userId) return;
if (target.checked) selected.add(userId); else selected.delete(userId);
syncHiddenInputs();
syncPageCheckboxes();
// If user interacts, cancel any pending auto-clear to avoid wiping new selections
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
});
// Initial sync on first load
table.draw(false);
// Header "Select All (page)" toggler
selectAll.addEventListener('change', function() {
const check = this.checked;
table.rows({
page: 'current'
}).every(function() {
const row = this.node();
const userId = row.getAttribute('data-user-id');
const cb = row.querySelector('.user-checkbox');
if (!cb || !userId) return;
cb.checked = check;
if (check) selected.add(userId);
else selected.delete(userId);
});
syncHiddenInputs();
syncPageCheckboxes();
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
});
// Before submit, ensure CSRF is fresh and hidden inputs include all selections
const form = document.getElementById('badgeForm');
async function refreshCsrf() {
try {
const params = new URLSearchParams();
if (!resp.ok) return;
const json = await resp.json();
if (json && json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
csrfInput.value = json.csrf_hash;
}
} catch (e) {
// No-op: if refresh fails we'll still attempt submit; server may accept existing token
}
}
form.addEventListener('submit', async function(e) {
e.preventDefault();
if (selected.size === 0) {
alert('Please select at least one staff member to generate badges.');
return;
}
syncHiddenInputs();
// CSRF is excluded for this endpoint now, but keeping refresh is safe
try { await refreshCsrf(); } catch (_) {}
// Native submit so browser handles PDF in a new tab
form.submit();
// Auto-clear selections 5 seconds after generating badges
clearTimerId = setTimeout(function() { clearSelectionsNow(); clearTimerId = null; }, 5000);
});
// --- Fetch and render print status ---
const selectedYear = <?= json_encode($selectedYear ?? '') ?>;
function fetchPrintStatus() {
const ids = Object.keys(rowMeta);
if (ids.length === 0) return;
const params = new URLSearchParams();
ids.forEach(id => params.append('user_ids[]', id));
if (selectedYear) params.append('school_year', selectedYear);
fetch('<?= base_url('api/printables/badges/status') ?>?' + params.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json'
}
})
.then(r => r.ok ? r.json() : Promise.reject())
.then(json => {
if (!json || !json.ok || !json.data) return;
const data = json.data;
Object.keys(rowMeta).forEach(id => {
const info = data[id];
const badge = document.querySelector(`.prints-badge[data-user-id="${id}"]`);
const tr = document.querySelector(`#staffTable tbody tr[data-user-id="${id}"]`);
if (!badge || !tr) return;
const count = info ? (info.count || 0) : 0;
badge.textContent = count > 0 ? `Printed ${count}` : '—';
badge.className = 'badge prints-badge ' + (count > 0 ? 'bg-success' : 'bg-secondary');
tr.classList.toggle('table-success', count > 0);
});
// Refresh CSRF for subsequent submissions (so no page reload is needed)
if (json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
csrfInput.value = json.csrf_hash;
}
})
.catch(() => {});
}
fetchPrintStatus();
window.addEventListener('focus', fetchPrintStatus);
});
</script>
<?= $this->endSection() ?>
Library
/
Pasted_code_6_without_academic_filter.php
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
+79 -80
View File
@@ -34,112 +34,104 @@
<?php endif; ?>
</form>
<div class="d-flex justify-content-end mb-2 gap-2 flex-wrap">
<form method="post" action="/refunds/recalculateOverpayments" class="d-flex gap-2">
<?= csrf_field() ?>
<button class="btn btn-outline-primary btn-sm" type="submit" title="Current school year">
Recalculate Overpayments (This Year)
</button>
</form>
<form method="post" action="/refunds/recalculateOverpayments" class="d-flex gap-2">
<?= csrf_field() ?>
<input type="text" name="invoice_number" class="form-control form-control-sm" placeholder="Invoice # (e.g., INV-...)" style="min-width: 260px;">
<button class="btn btn-outline-secondary btn-sm" type="submit" title="Recalc a specific invoice across any year">
Recalc Specific Invoice
</button>
</form>
</div>
<div class="table-responsive">
<table id="refundsTable" class="table table-bordered table-striped align-middle w-100">
<thead>
<tr>
<th>School&nbsp;ID</th>
<th>Parent</th>
<th>Request</th>
<th>Term</th>
<th>Invoice&nbsp;ID</th>
<th class="text-end">Refund Amount</th>
<th>Status</th>
<th>Invoice&nbsp;#</th>
<th>Requested</th>
<th>Approved</th>
<th>Approved By</th>
<th>Refunded</th>
<th>Method</th>
<th>Check #</th>
<th>Check File</th>
<th class="text-end">Paid Amount</th>
<th class="text-end">Source Available</th>
<th class="text-end">Parent Available</th>
<th>Status</th>
<th>Refund Details</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($refunds as $r): ?>
<?php
// Friendly badges
$req = strtolower((string)($r['request'] ?? ''));
$reqBadgeClass = [
'tuition' => 'primary',
'overpayment' => 'success',
'extra' => 'info',
'duplicate' => 'warning',
][$req] ?? 'secondary';
// Date formatting (keep raw if null)
$fmt = function($dt) {
if (empty($dt) || $dt === '0000-00-00 00:00:00') return '-';
// show date only; assumes DB is UTC
return local_date($dt, 'm-d-Y');
};
$statusRaw = (string)($r['status'] ?? '');
$statusKey = strtolower(str_replace(' ', '_', trim($statusRaw)));
if ($statusKey === 'requested') {
$statusKey = 'pending';
} elseif ($statusKey === 'partially_paid') {
$statusKey = 'partial';
}
$statusLabel = [
'pending' => 'Pending',
'approved' => 'Approved',
'rejected' => 'Rejected',
'partial' => 'Partial',
'paid' => 'Paid',
][$statusKey] ?? ($statusRaw !== '' ? $statusRaw : 'Pending');
$statusClass = [
'pending' => 'warning text-dark',
'approved' => 'primary',
'rejected' => 'danger',
'partial' => 'info text-dark',
'paid' => 'success',
][$statusKey] ?? 'secondary';
$refundAmount = (float)($r['refund_amount'] ?? 0);
$paidAmount = (float)($r['refund_paid_amount'] ?? 0);
$remainingAmount = max(0, $refundAmount - $paidAmount);
$hasSource = !empty($r['source_type']) && !empty($r['source_id']);
$canApprove = $statusKey === 'pending' && $refundAmount > 0 && $hasSource;
$canReject = $statusKey === 'pending';
$canRecord = in_array($statusKey, ['approved', 'partial'], true) && $remainingAmount > 0;
?>
<tr>
<td><?= esc($r['school_id']) ?></td>
<td><?= esc(($r['firstname'] ?? '').' '.($r['lastname'] ?? '')) ?></td>
<td><?= esc($r['invoice_number'] ?? '-') ?></td>
<td>
<?php if ($req): ?>
<span class="badge bg-<?= $reqBadgeClass ?> text-uppercase"><?= esc($req) ?></span>
<?php else: ?>
<span class="text-muted">-</span>
<div class="fw-semibold">$<?= esc(number_format($refundAmount, 2)) ?></div>
<div class="text-muted small"><?= esc($fmt($r['requested_at'] ?? null)) ?></div>
</td>
<td>
<div><span class="badge bg-<?= esc($statusClass) ?>"><?= esc($statusLabel) ?></span></div>
<?php if (!empty($r['approved_at']) && $r['approved_at'] !== '0000-00-00 00:00:00'): ?>
<div class="text-muted small mt-1">
<?= esc($fmt($r['approved_at'])) ?>
<?php if (!empty($r['approved_by_name']) && $r['approved_by_name'] !== '-'): ?>
by <?= esc($r['approved_by_name']) ?>
<?php endif; ?>
</div>
<?php endif; ?>
</td>
<td><?= esc(($r['school_year'] ?? '-'). ' / ' . ($r['semester'] ?? '-')) ?></td>
<td><?= esc($r['invoice_id'] ?? '-') ?></td>
<td class="text-end">$<?= esc(number_format((float)$r['refund_amount'], 2)) ?></td>
<td><?= esc($r['status']) ?></td>
<td><?= esc($fmt($r['requested_at'] ?? null)) ?></td>
<td><?= esc($fmt($r['approved_at'] ?? null)) ?></td>
<td><?= esc($r['approved_by_name'] ?? '-') ?></td>
<td><?= esc($fmt($r['refunded_at'] ?? null)) ?></td>
<td><?= esc($r['refund_method'] ?? '-') ?></td>
<td><?= esc($r['check_nbr'] ?? '-') ?></td>
<td>
<?php if (!empty($r['check_file'])): ?>
<a href="<?= base_url('refunds/file/' . (int) $r['id'] . '/inline') ?>" target="_blank">View</a>
<?php else: ?>
-
<?php endif; ?>
<div><span class="text-muted small">Check #:</span> <?= esc($r['check_nbr'] ?? '-') ?></div>
<div>
<span class="text-muted small">Check File:</span>
<?php if (!empty($r['check_file'])): ?>
<a href="<?= base_url('refunds/file/' . (int) $r['id'] . '/inline') ?>" target="_blank">View</a>
<?php else: ?>
-
<?php endif; ?>
</div>
<div><span class="text-muted small">Paid:</span> $<?= esc(number_format($paidAmount, 2)) ?></div>
</td>
<td class="text-end">$<?= esc(number_format((float)$r['refund_paid_amount'], 2)) ?></td>
<td class="text-end">
<?= $r['available_refundable_credit'] === null ? '-' : '$' . esc(number_format((float)$r['available_refundable_credit'], 2)) ?>
</td>
<td class="text-end">
<?= isset($r['parent_available_refundable_credit']) ? '$' . esc(number_format((float)$r['parent_available_refundable_credit'], 2)) : '-' ?>
</td>
<td class="d-flex gap-2">
<td>
<div class="d-flex gap-2 flex-wrap">
<button class="btn btn-success btn-sm"
onclick="handleRecordRefundClick(<?= (int)$r['id'] ?>, '<?= esc($r['status']) ?>', <?= (float)$r['refund_amount'] ?>)">
<?= $canRecord ? '' : 'disabled' ?>
onclick="handleRecordRefundClick(<?= (int)$r['id'] ?>, '<?= esc($statusLabel) ?>', <?= $remainingAmount ?>)">
Record
</button>
<button class="btn btn-primary btn-sm"
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Approved', <?= (float)$r['refund_amount'] ?>)">
<?= $canApprove ? '' : 'disabled' ?>
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Approved', <?= $refundAmount ?>)">
Approve
</button>
<button class="btn btn-danger btn-sm"
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Rejected', <?= (float)$r['refund_amount'] ?>)">
<?= $canReject ? '' : 'disabled' ?>
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Rejected', <?= max($refundAmount, 0.01) ?>)">
Reject
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
@@ -231,7 +223,7 @@ $(function () {
// DataTable
$('#refundsTable').DataTable({
pageLength: 25,
order: [[7, 'desc']], // order by Requested desc
order: [[2, 'desc']], // order by Requested desc
scrollX: true,
autoWidth: false,
});
@@ -277,21 +269,27 @@ $(function () {
});
// ---- Actions ----
function normalizeRefundStatus(status) {
return String(status || '').trim().toLowerCase().replace(/\s+/g, '_');
}
function handleRecordRefundClick(refundId, status, amount) {
if (!['Approved','Partial'].includes(status)) {
alert('⚠ Refund must be Approved (or Partial) before recording a payout.');
const normalized = normalizeRefundStatus(status);
if (!['approved','partial','partially_paid'].includes(normalized)) {
alert('Refund must be approved or partial before recording a payout.');
return;
}
if (!amount || parseFloat(amount) <= 0) {
alert('Refund amount not set.');
alert('Refund amount is not set.');
return;
}
showPaymentModal(refundId);
showPaymentModal(refundId, amount);
}
function handleStatusClick(refundId, status, amount) {
if (!amount || parseFloat(amount) <= 0) {
alert('⚠ Refund amount not set.');
const normalized = normalizeRefundStatus(status);
if (normalized !== 'rejected' && (!amount || parseFloat(amount) <= 0)) {
alert('Refund amount is not set.');
return;
}
showStatusModal(refundId, status);
@@ -308,7 +306,7 @@ function showStatusModal(refundId, status) {
$('#statusModal').modal('show');
}
function showPaymentModal(refundId) {
function showPaymentModal(refundId, amount) {
$('#statusForm').addClass('d-none');
$('#paymentForm').removeClass('d-none');
@@ -318,7 +316,8 @@ function showPaymentModal(refundId) {
? crypto.randomUUID()
: ('refund-' + refundId + '-' + Date.now() + '-' + Math.random().toString(16).slice(2))
);
$('#paidAmount').val('');
$('#paidAmount').val(Number(amount || 0).toFixed(2));
$('#paidAmount').attr('max', Number(amount || 0).toFixed(2));
$('#paymentMethod').val('').trigger('change');
$('#checkDetails').addClass('d-none');
$('#checkNumber').val('');
+1 -1
View File
@@ -71,7 +71,7 @@
} catch (\Throwable $e) {
$sy = $configModel->getConfig('school_year');
}
$sem = $configModel->getConfig('semester');
$sem = getSemester();
$classOptions = $teacherClassModel->getClassAssignmentsByUserId((int)$userId, (string)$sy, (string)$sem);
}
?>