Invoices, Estimates & Recurring Invoices
BottleCRM ships a lightweight billing layer covering one-off invoices, customer-facing estimates (quotes), and scheduled recurring invoices. All three share the same line-item model and PDF renderer.
Documents
| Type | Purpose | Public URL |
|---|---|---|
| Estimate | Pre-sale quote — customer can review and accept | yes |
| Invoice | Bill for delivered work or goods | yes |
| Recurring invoice | A schedule that generates invoices automatically | no |
Estimates and invoices each get a tokenised public URL so a customer can view (and accept / pay) without a login.
Line items
Each document is a list of line items:
- Item name and description
- Quantity, unit, unit price
- Tax rate (per line — useful for mixed tax jurisdictions)
- Discount (% or flat)
Totals (subtotal, tax, discount, grand total) are recomputed server-side on every save — never trust client-computed totals.
Statuses
draft → sent → (paid | overdue | void)
The transition rules:
- draft → sent stamps
sent_atand emails the customer's primary contact. - sent → paid records
paid_atand an optional payment reference. - sent → overdue is automatic when the due date passes without payment.
- void is irreversible — voided documents stay visible for the audit trail but can't be re-sent.
Recurring invoices
A recurring invoice is a template plus a cadence:
- Frequency: daily / weekly / monthly / yearly
- Start date and optional end date
- Day-of-month or day-of-week anchor
- Auto-send toggle
A Celery beat task scans active recurring schedules every hour and materialises invoices that are due. The generated invoice inherits the template's line items, contact, and (optionally) custom field values.
Converting an estimate to an invoice
POST /api/estimates/<id>/convert/ clones the estimate's line items into a new invoice in draft state, links the two for traceability, and marks the estimate as accepted. Custom field values carry over for matching keys.
Public actions
Customers can:
- View an estimate or invoice at its tokenised public URL.
- Accept an estimate — fires the
estimate.acceptedevent. - Pay an invoice — if a payment gateway is connected (Stripe out of the box; others via webhook).
See also
- Custom fields — PO number, tax exemption ID, payment terms.
- Webhooks —
invoice.sent,invoice.paid,estimate.accepted.