docs(billing): document invoice lifecycle and void-vs-cancel (PMS-580) #410

Merged
longjacksonle merged 1 commit from docs/PMS-580-invoice-lifecycle into main 2026-06-30 17:41:51 +02:00

PMS-580 (found in PMS-560 Invoices smoke)

The smoke test asked: once an invoice is Sent, it cannot be cancelled, only ... is this intended? Yes. This documents the lifecycle so the immutability is no longer a surprise.

What this adds

dev-docs/invoice-lifecycle.md, grounded in the code:

  • Statuses (models.rs InvoiceStatus, migrations/010_billing.sql): draft, pending, sent, paid, partially_paid, void, written_off. No cancelled by design.
  • is_frozen() (models.rs) = sent | paid | partially_paid | void | written_off; only draft / pending are editable.
  • update_invoice (service.rs) is the single mutation path and rejects any change (including the transition to void) once frozen. So voiding is a pre-send back-out, available only while draft / pending. A sent invoice cannot be edited, cancelled, or voided.
  • Sent / partially_paid: only Record Payment (separate record_payment path) applies. Correcting a sent invoice belongs to a credit-note flow that is not yet built.

AC3 decision (recorded in the doc)

No separate pre-send "cancel" control is needed: Void already backs out a draft / pending invoice and keeps the row for audit. Any relabelling (e.g. "Cancel" while still draft) is a separate UI-copy ticket, not a logic or status change.

Companion change

The UI clarity half of PMS-580 (an inline note on frozen invoices in the detail page) is a separate PR in mokosh-apps.

🤖 Generated with Claude Code

## PMS-580 (found in PMS-560 Invoices smoke) The smoke test asked: once an invoice is Sent, it cannot be cancelled, only ... is this intended? Yes. This documents the lifecycle so the immutability is no longer a surprise. ## What this adds `dev-docs/invoice-lifecycle.md`, grounded in the code: - Statuses (`models.rs` `InvoiceStatus`, `migrations/010_billing.sql`): draft, pending, sent, paid, partially_paid, void, written_off. No `cancelled` by design. - `is_frozen()` (`models.rs`) = sent | paid | partially_paid | void | written_off; only draft / pending are editable. - `update_invoice` (`service.rs`) is the single mutation path and rejects any change (including the transition to `void`) once frozen. So voiding is a pre-send back-out, available only while draft / pending. A sent invoice cannot be edited, cancelled, or voided. - Sent / partially_paid: only Record Payment (separate `record_payment` path) applies. Correcting a sent invoice belongs to a credit-note flow that is not yet built. ## AC3 decision (recorded in the doc) No separate pre-send "cancel" control is needed: Void already backs out a draft / pending invoice and keeps the row for audit. Any relabelling (e.g. "Cancel" while still draft) is a separate UI-copy ticket, not a logic or status change. ## Companion change The UI clarity half of PMS-580 (an inline note on frozen invoices in the detail page) is a separate PR in mokosh-apps. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(billing): document invoice lifecycle and void-vs-cancel (PMS-580)
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 46s
Check / fmt + clippy + build + tests (pull_request) Successful in 1m54s
Create release / Gate (release-branch merges only) (pull_request) Successful in 1s
Create release / Create release from merged PR (pull_request) Has been skipped
Integration / integration tests (pull_request) Successful in 10m15s
93021c97fc
A smoke test (PMS-560) asked whether it is intended that a sent invoice cannot be cancelled. It is. This adds dev-docs/invoice-lifecycle.md explaining the status model and the rationale, grounded in the code: InvoiceStatus / is_frozen in models.rs, the update_invoice frozen guard in service.rs, and the invoices.status CHECK in migrations/010_billing.sql.

Key points captured: there is no `cancelled` status by design; only draft/pending are editable and voidable; once frozen (sent and beyond) the invoice is immutable and the only action is Record Payment via the separate record_payment path; correcting a sent invoice belongs to a credit-note flow that is not yet built. Also records the AC3 decision: no separate pre-send "cancel" control is needed because Void already serves that role for draft/pending; any relabelling is a separate UI-copy ticket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch docs/PMS-580-invoice-lifecycle 2026-06-30 17:41:51 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!410
No description provided.