# Phase 08: Billing Engine + Invoice PDF **Status:** Complete **Completed:** 2026-03-16 ## Goal Automated monthly invoice generation, PDF export, mark-as-paid workflow, and overdue escalation. ## What was built ### Backend **`billing/invoice_pdf.py`** (new service) - `generate_invoice_pdf(invoice_id)` — renders Jinja2 HTML template → WeasyPrint PDF - Stores at `/app/data/invoices/{invoice_id}.pdf` - Sets `Invoice.pdf_path` in DB **`templates/invoice.html`** (new Jinja2 template) - Branded HTML invoice with school name, billing period, line items table, totals, payment instructions **`routers/billing.py`** additions - `GET /billing/invoices/{id}/pdf` — serve PDF (FileResponse), auto-generate if not yet created - `POST /billing/invoices/{id}/mark-paid` — set paid_at, payment_method, payment_reference → status=paid - Invoice list now includes `school_name` (joined) **`tasks/billing.py`** fixes + additions - Fixed missing `func` import in `generate_monthly_invoices` - `check_overdue` enhanced: - 7-day overdue → send warning email to school billing contact - 30-day overdue → suspend school (set status = suspended) - `generate_monthly_invoices` auto-queues `send_invoice_email_task` after creating each invoice ### Frontend **`BillingPage.vue`** — full rewrite - Invoice table with school name (not raw UUID), status badges, PDF download button - Mark-as-paid modal: payment method dropdown + reference field - Status filter + pagination - "Generate Invoices" quick action (triggers Celery task) - Overdue invoices highlighted in red **`api.ts`** additions - `downloadInvoicePdf(id)` — opens PDF in new tab - `markInvoicePaid(id, data)` — PATCH to mark-paid endpoint - `triggerGenerateInvoices()` — POST to trigger monthly invoice generation