- Invoices drafted 14 days before each licence's invoice date and emailed to the alert email for approval; approved ones go out on their date from 08:00 SA time. Drafts can be edited, recalculated, sent early, put back to draft or voided; one-off invoices too. - Annual tier review from the 12-month event-income band; approving a tier change emails a notice of licence change, applied when the invoice goes out. Notices can also be sent by hand. - Founding churches (35% off as its own line), payments with receipts, statements for any dates. - Every billing email is styled HTML with a PDF (invoice / tax invoice, receipt, statement, licence change) and logged per customer. - Settings -> Billing: business and bank details, VAT (off: plain invoices; on: VAT-inclusive tax invoices), timing and numbering. - Migration 20261103111645_invoicing; pdfkit 0.17.2; tests/billing.test.js; scripts/sample-billing-pdfs.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
34 lines
3.2 KiB
JavaScript
34 lines
3.2 KiB
JavaScript
/**
|
||
* Writes sample billing PDFs (invoice, receipt, statement, licence change) with made-up data, to
|
||
* check the layout after changing utils/billing/pdf.js: `node scripts/sample-billing-pdfs.js [dir]`.
|
||
* Needs no database.
|
||
*/
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { DEFAULTS } = require('../src/utils/settings');
|
||
const pdf = require('../src/utils/billing/pdf');
|
||
const { draftInvoice, invoiceState, statement } = require('../src/utils/billing/compute');
|
||
|
||
const out = path.resolve(process.argv[2] || 'billing-samples');
|
||
fs.mkdirSync(out, { recursive: true });
|
||
const settings = { ...DEFAULTS, bank_name: 'Sample Bank', bank_account_name: 'Cross Code', bank_account_number: '1234 567 890', bank_branch_code: '250655', bank_account_type: 'Cheque' };
|
||
const customer = { id: 'c1', name: 'Cedarwood Community Church', contactName: 'Thandi Nkosi', billingAddress: '12 Oak Street, Meyerton, 1961', email: 'office@cedarwood.example', founding: true };
|
||
const install = { adminContact: { email: 'admin@cedarwood.example' }, stats: { income: { eventIncome12m: 'R250 001–R400 000' } } };
|
||
const licence = { id: 'l1', plan: 'Sprout', billingCycle: 'monthly', billingStart: new Date('2026-01-01'), nextInvoiceDate: new Date('2027-01-01'), reviewDate: new Date('2027-01-01'), customMonthlyCents: null };
|
||
|
||
(async () => {
|
||
const { invoice: draft } = draftInvoice({ licence, customer, install, settings });
|
||
const invoice = { ...draft, id: 'i1', number: 'INV-2027-0001', status: 'sent', notes: null };
|
||
const payment = { id: 'p1', receiptNumber: 'REC-2027-0001', amountCents: 30000, paidOn: new Date('2027-01-05'), method: 'eft', reference: 'INV-2027-0001' };
|
||
const state = invoiceState({ ...invoice, payments: [payment] }, '2027-01-06');
|
||
fs.writeFileSync(path.join(out, 'invoice.pdf'), await pdf.invoicePdf({ invoice, customer, settings, state }));
|
||
fs.writeFileSync(path.join(out, 'invoice-draft.pdf'), await pdf.invoicePdf({ invoice: { ...invoice, status: 'draft' }, customer, settings, state: invoiceState({ ...invoice, payments: [] }, '2027-01-06') }));
|
||
fs.writeFileSync(path.join(out, 'receipt.pdf'), await pdf.receiptPdf({ payment, customer, invoice, state, settings, recipientEmail: invoice.recipientEmail }));
|
||
const older = { ...invoice, number: 'INV-2026-0012', issueDate: new Date('2026-12-01'), dueDate: new Date('2026-12-08'), periodStart: new Date('2026-12-01'), periodEnd: new Date('2026-12-31'), totalCents: 36000, payments: [] };
|
||
const st = statement({ invoices: [older, { ...invoice, payments: [payment] }], payments: [{ ...payment, invoice }], from: '2026-12-15', to: '2027-01-31' });
|
||
fs.writeFileSync(path.join(out, 'statement.pdf'), await pdf.statementPdf({ statement: st, customer, settings, recipientEmail: invoice.recipientEmail }));
|
||
const change = { number: 'LC-2026-0001', fromPlan: 'Sprout', toPlan: 'Grow', fromMonthlyCents: 55000, toMonthlyCents: 85000, effectiveDate: new Date('2027-01-01'), incomeBand: 'R250 001–R400 000', createdAt: new Date('2026-12-18') };
|
||
fs.writeFileSync(path.join(out, 'licence-change.pdf'), await pdf.licenceChangePdf({ change, customer, settings, founding: true, recipientEmail: invoice.recipientEmail, cycle: 'monthly' }));
|
||
console.log(`samples in ${out}`);
|
||
})();
|