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_at and emails the customer's primary contact.
  • sent → paid records paid_at and 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.accepted event.
  • 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.
  • Webhooksinvoice.sent, invoice.paid, estimate.accepted.