Cost tracking and invoicing are the same story told from two ends — what a project actually spent, and what the client actually gets billed and pays. Built as two connected apps so neither one drifts from the other.
Every cost carries a verified, versioned paper trail, and every invoice is validated before it can go out wrong — with nothing ever hard-deleted.
Spending nobody could verify
Versioned, hashed, budget-tracked
Invoices validated before they go out
Costs needed consistent categories across very different project types, with real supporting evidence that could survive an audit and budgets that warned before they were blown. On the billing side, invoices needed to stay searchable and filterable as volume grew, track partial payments without ever letting a client overpay, and keep every attached file pointed at its latest version — not a stale copy from three edits ago.
A hierarchical cost-category tree with versioned, SHA-256-hashed cost files (13 document types) and threshold-based budget alerts, paired with an invoicing system that auto-populates the client from the project, validates every date and amount relationship, and won't let a payment exceed what's owed. Both apps share the same virtual-folder file storage — invoices and payments even get their own auto-created sub-folders — and a signal keeps every file reference in both apps pointed at the latest uploaded version automatically.
A hierarchical CostCategory tree tags every cost by discipline — the same structure works whether the project is a bridge or an office fit-out.
A ProjectCost entry records amount, supplier, and tax detail. If quantity × unit price doesn't match the entered amount, the form either auto-corrects small rounding gaps or rejects it outright — it doesn't just trust whatever number was typed in.
Receipts, invoices, and POs attach as versioned, SHA-256-hashed files across 13 recognized document types, optionally requiring approval before they count as verified.
A ProjectCostBudget compares allocated against actual spend per category, firing configurable warning (80%) and critical (95%) threshold alerts.
An invoice auto-populates its client from the selected project, and validates that the due date isn't before the invoice date before it'll save.
Partial payments are tracked individually — the form rejects any payment that would push the total received past the invoice amount, and requires a payment method whenever an initial payment is recorded.
A dedicated filter service handles quick filters (draft, overdue, this month), date and amount ranges, and even boolean filters like "has files" or "has payments" — plus full-text search across the invoice number, project, client, and e-invoice reference in one pass.
When someone uploads a new version of an attached file, a signal walks every InvoiceFile and PaymentFile reference pointing at the old version and repoints it at the new one — nobody has to remember to update a link by hand.
Status — auto-computed on save, not set by hand
An AJAX endpoint mirrors the exact same rules as the form itself — required fields, date logic, amount checks — so a user finds out about a problem before they submit, not after.
Export, permissions, validation, audit logging, and cross-app integration each live in their own dedicated mixin — no single view class tries to do all five jobs at once.
Payments, invoice files, and payment files all soft-delete with a restore path — the payment cancel endpoint's own docstring calls it "cancel/archive instead of deleting." A mistake is reversible, not catastrophic.
A cost without a receipt is a guess, and an invoice that doesn't match what was actually spent is a problem waiting to surface at the worst time. Keeping both sides validated, versioned, and connected to the same file system means the numbers a CEO sees on a Tuesday are the same numbers an auditor would find eighteen months later.