Fix semester context, attendance rosters, and billing workflows
- 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:
@@ -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} · ${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();
|
||||
|
||||
Reference in New Issue
Block a user