/* Slim Uiltjies — Invoice & Statement rendering Builds the on-brand invoice / statement HTML that matches the original receipt. */ (function () { 'use strict'; const months = ['January','February','March','April','May','June','July','August','September','October','November','December']; function fmtCurrency(n, currency = 'R') { const v = Number(n) || 0; return `${currency}${v.toLocaleString('en-ZA', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`; } function fmtDate(d) { if (!d) return ''; const [y, m, day] = String(d).split('-').map(Number); return `${day} ${months[m - 1]} ${y}`; } function formatPeriod(p) { if (!p) return ''; const [y, m] = String(p).split('-').map(Number); return `${months[m - 1]} ${y}`; } function statusBadge(status) { const map = { paid: { cls: 'invoice-status-paid', label: 'PAID' }, unpaid: { cls: 'invoice-status-unpaid', label: 'UNPAID' }, partial: { cls: 'invoice-status-partial', label: 'PARTIAL' }, }; const s = map[status] || map.unpaid; return `${s.label}`; } function paymentBlock(payments) { if (!payments || payments.length === 0) return ''; const rows = payments.map((p) => `
${fmtDate(p.date)} · ${(p.method || '').toUpperCase()} ${fmtCurrency(p.amount)}
`).join(''); const total = payments.reduce((s, p) => s + Number(p.amount || 0), 0); return `
Payments received
${rows}
Total received ${fmtCurrency(total)}
`; } function renderInvoiceHTML({ invoice, parent, child, payments, business, type = 'invoice' }) { const items = (invoice.items || []).map((it) => ` ${escape(it.description)} ${it.quantity || 1} ${fmtCurrency(it.unitPrice, business.currency)} ${fmtCurrency(it.total, business.currency)} `).join(''); const dueText = invoice.dueDate ? `Due ${fmtDate(invoice.dueDate)}` : ''; const logoHtml = business.logoDataUrl ? `` : ``; return `
${logoHtml}

${escape(business.name)}

"${escape(business.tagline || 'A safe place to learn, grow and shine!')}"
${escape(invoice.number || 'INVOICE')}
Issued: ${fmtDate(invoice.issueDate)}
${dueText ? `
${escape(dueText)}
` : ''} ${statusBadge(invoice.status)}

${type === 'statement' ? 'STATEMENT OF PAYMENTS RECEIVED' : 'INVOICE'}

Parent / Guardian
${escape(parent.name)}
${parent.phone ? `
${escape(parent.phone)}
` : ''} ${parent.email ? `
${escape(parent.email)}
` : ''} ${parent.address ? `
${escape(parent.address)}
` : ''}
Child & Period
${escape(child.name)}
${child.plan === 'full' ? 'Full Day' : (child.plan === 'half' ? 'Half Day' : escape(child.plan))}
${formatPeriod(invoice.period)}
${paymentBlock(payments)} ${items}
Description Qty Unit Total
Subtotal ${fmtCurrency(invoice.subtotal, business.currency)}
${invoice.paid > 0 ? `
Paid − ${fmtCurrency(invoice.paid, business.currency)}
` : ''}
${invoice.balance > 0 ? 'Balance Due' : 'Total Paid'} ${fmtCurrency(invoice.balance > 0 ? invoice.balance : invoice.total, business.currency)}
${invoice.notes ? `
Notes
${escape(invoice.notes)}
` : ''} ${business.bankName || business.accountNumber ? `
Banking Details
${business.bankName ? `
Bank: ${escape(business.bankName)}
` : ''} ${business.accountHolder ? `
Account holder: ${escape(business.accountHolder)}
` : ''} ${business.accountNumber ? `
Account no: ${escape(business.accountNumber)}
` : ''} ${business.branchCode ? `
Branch code: ${escape(business.branchCode)}
` : ''} ${business.referenceNote ? `
${escape(business.referenceNote)}
` : ''}
` : ''}
`; } // Statement of payments received — modeled on the original receipt function renderStatementHTML({ parent, children, payments, business, periodStart, periodEnd }) { // Aggregate payments by child + period for display const rows = []; let total = 0; const childMap = {}; children.forEach((c) => { childMap[c.id] = c; }); const sorted = payments.slice().sort((a, b) => (a.date || '').localeCompare(b.date || '')); for (const p of sorted) { const child = childMap[p.childName ? -1 : (p.invoiceChildId || 0)]; // we'll fall back below // We need the child for each payment — we'll resolve by looking up the invoice rows.push(p); total += Number(p.amount) || 0; } const rowsHtml = rows.map((p) => ` ${fmtDate(p.date)} ${escape(p.childName || '')} ${escape((p.method || '').toUpperCase())} ${fmtCurrency(p.amount, business.currency)} ${escape(p.receivedBy || '')} `).join(''); return `

${escape(business.name)}

"${escape(business.tagline || 'A safe place to learn, grow and shine!')}"
Statement Period
${formatPeriod(periodStart)} — ${formatPeriod(periodEnd)}

STATEMENT OF THE PROOFS OF
PAYMENTS RECEIVED

Parent / Guardian
${escape(parent.name)}
${parent.phone ? `
${escape(parent.phone)}
` : ''} ${parent.email ? `
${escape(parent.email)}
` : ''}
Children
${children.map((c) => `
${escape(c.name)}
`).join('')}
${rowsHtml || ''}
Date Child Method Amount Received by
No payments in this period
TOTAL RECEIVED: ${fmtCurrency(total, business.currency)}
`; } function escape(s) { return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } window.SUInvoice = { renderInvoiceHTML, renderStatementHTML, fmtCurrency, fmtDate, formatPeriod, escape, }; })();