Invoice Lifecycle
Every invoice follows a strict state machine from creation to payment. Status transitions are enforced by the backend — invalid transitions are rejected with a 409 Conflict error.
State Diagram
Status Definitions
| Status | Editable | Description |
|---|---|---|
draft | Yes | Invoice created, all fields editable. Line items can be added, modified, or removed. |
needs_review | No | Finalized with PDF generated. Awaiting manager approval. |
approved | No | Approved by manager. Ready to be sent to client. |
declined | No | Declined by manager with reason. Can be returned to draft for rework. |
sent | No | Sent to client via Didox (UZS) or email (international). |
accepted | No | Client accepted the invoice. |
rejected | No | Client rejected the invoice. Can be returned to draft. |
paid | No | Payment received. Terminal state. |
Allowed Transitions
var AllowedTransitions = map[string][]string{
"draft": {"needs_review"},
"needs_review": {"approved", "declined"},
"declined": {"draft"},
"approved": {"draft", "sent"},
"sent": {"accepted", "rejected"},
"rejected": {"draft"},
"accepted": {"paid"},
}
Transition Details
Generate → Draft
The invoice is created by pre-filling data from ClickHouse (worklogs, client info) and saved to PostgreSQL with status = 'draft'. Line items have source = 'auto' and is_auto_generated = true.
Finalize: Draft → Needs Review
- Validate all required fields are present
- Render the HTML invoice template (Russian or English)
- Send HTML to Gotenberg (
convert.prxm.uz) for PDF conversion - Store PDF binary in the
pdf_datacolumn - Record the
html_template_hashfor change detection - Transition status to
needs_review - Log a
status_changedevent
Approve / Decline: Needs Review → Approved / Declined
- Approve: Sets
approved_atandapproved_by. (Phase 2 adds Telegram notification.) - Decline: Sets
declined_at,declined_by, anddecline_reason.
Regenerate: Declined / Rejected / Approved → Draft
Re-fetches fresh worklog data from ClickHouse, recalculates financials, and resets the invoice to draft. Previous edits are preserved in invoice_edit_history. When regenerating from approved, clears approved_at, approved_by, and html_override.
Send: Approved → Sent
- Local (UZS): Sent via Didox.uz e-invoicing (Phase 4)
- International: Sent as PDF email attachment (Phase 4)
- Sets
sent_attimestamp
Accept / Reject: Sent → Accepted / Rejected
- Accept: Logs a
status_changedevent. - Reject: Sets
rejected_at,rejected_by, andreject_reason. Reason is required (enforced by frontend RejectModal). Rejected invoices can be regenerated back to draft.
Mark Paid: Accepted → Paid
Terminal state. Sets paid_at timestamp. Triggers PlanFact sync (Phase 3).
Event Tracking
Every status transition is logged in the invoice_events table:
{
"event_type": "status_changed",
"from_status": "draft",
"to_status": "needs_review",
"actor": "[email protected]",
"metadata": { "pdf_generated": true }
}
Event types include: created, status_changed, regenerated, line_items_updated, invoice_updated.