Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions docs/modules/invoices.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,26 @@ credits more tax than the invoice charged.
Every route is in `core/api/fragments/invoice.yaml` and the registered
handles are in `core/internal/invoice/handler.go`. The route census
(`core/api/ROUTES.txt`) lists each one under the `internal/invoice`
package (the route census has a `package` column, not a module
column). Three of the routes below are registered under other
packages: the email and print routes are `internal/document`, the
invoice's payment list is `internal/payment`.
module (the route census has a `module` column whose value is the
registering package's directory relative to the Go module root).
Two of the routes below are registered under other modules: the
email route is `internal/document` and the credit memo refund route
is `internal/payment`. The print routes (below) are `internal/document`.

| Method | Path | One line |
|---|---|---|
| GET | `/api/v1/invoices` | Cursor list of invoices, newest first; status, customer_id, job_id, ship_to_id, order_id, overdue filter. |
| GET | `/api/v1/invoices/{id}` | One invoice with its lines and revision as ETag. |
| POST | `/api/v1/invoices/{id}/transitions` | Void an invoice. |
| POST | `/api/v1/invoices/{id}/write-offs` | Write off part or all of an open invoice as bad debt (`internal/invoice`); roles `admin`, `owner`, `finance`; body `{amount_cents, reason}` (1 to 500 chars) and revision; posts DR `5040` / CR `1020` and a `WRITE_OFF` subledger row; a write off that takes the open amount to zero closes the invoice as `written_off`, else it stays `partial`. |
| POST | `/api/v1/invoices/{id}/email` | Email invoice to customer (queued, answers 202, notification is log only; owned by `internal/document`). |
| GET | `/api/v1/invoices/{id}/payments` | List an invoice's payments (the payment module's read). |
| GET | `/api/v1/invoices/{id}/payments` | List an invoice's applications (payments, credit memos, early pay discounts and write offs, oldest first, reversals included). |
| GET | `/api/v1/credit-memos` | Cursor list of credit memos, newest first; status, customer_id, invoice_id, job_id filter. |
| POST | `/api/v1/credit-memos` | Create a draft credit memo. |
| GET | `/api/v1/credit-memos/{id}` | One credit memo with its lines and revision as ETag. |
| PUT | `/api/v1/credit-memos/{id}` | Replace a draft credit memo's header and lines, on the client's revision. |
| POST | `/api/v1/credit-memos/{id}/applications` | Apply a credit memo's open credit to invoices of the same customer and currency (`internal/invoice`); body `{applications: [{invoice_id, amount_cents}], revision}`; no ledger entry (both sides are in `1020`) and no subledger row. |
| POST | `/api/v1/credit-memos/{id}/refunds` | Pay a credit memo's open credit out (`internal/payment`); roles `admin`, `owner`, `finance`; body `{amount_cents, reason, method: card\|check\|ach\|other, payment_id?}`; a card refund names the card payment it goes back to and goes through the gateway before the transaction; posts DR `1020` / CR `1010` and a `REFUND` subledger row. |
| POST | `/api/v1/credit-memos/{id}/transitions` | Post or void a credit memo. |

The print routes at `/api/v1/documents/print/invoice/{id}` and
Expand Down Expand Up @@ -163,9 +167,14 @@ The wire vocabulary is lowercase. The transitions are:
| Entity | From | To | Event |
|---|---|---|---|
| Invoice | `unpaid` with no payments and no applied memos and no live memos and not a counter sale | `void` | `invoice.voided` (reason required) |
| Invoice | `unpaid`, `partial` | `written_off` | `invoice.written_off` (reason required) when the write off takes the open amount to zero, or when an application takes the open amount to zero on an invoice with a live write off; `invoice.partial` when it leaves the invoice still open |
| Invoice | `unpaid`, `partial` | `paid` | `invoice.paid` when an application (payment or credit memo) takes the open amount to zero and the invoice has no live write off (with one, it closes as `written_off` and writes `invoice.written_off`); `invoice.partial` when it leaves the invoice still open |
| Invoice | `partial`, `paid`, `written_off` | `unpaid` or `partial` | `invoice.reopened` when an application is reversed |
| Credit memo | `draft` | `open` | `credit_memo.posted` (mints the number) |
| Credit memo | `draft` | `void` | `credit_memo.voided` (reason required) |
| Credit memo | `open` | `void` | `credit_memo.voided` (reason required) |
| Credit memo | `open`, `partial` | `partial` | `credit_memo.partial` when an application or refund leaves credit open; a refund writes `credit_memo.refunded` first, then the status event |
| Credit memo | `open`, `partial` | `applied` | `credit_memo.applied` when an application takes the credit to zero; a refund that closes the memo writes `credit_memo.refunded` first, then the status event |

`invoice.voided` is refused with `409 has_applications` when the
invoice has any payment or any applied credit memo, with
Expand All @@ -182,6 +191,30 @@ credit memo's `draft` to `open` post; the `If-Match` header carries
the same number, and the idempotency key is on every mutating
request.

`invoice.written_off` (ADR 0005 section 9.4) is refused with
`409 exceeds_open_amount` when the write off is larger than the
invoice's open amount, with `409 invalid_state_transition` when the
invoice is not `unpaid` or `partial`, and with `409 period_closed`
when the branch's accounting period is closed; the service enforces
the finance role (`requireFinance` at
`core/internal/invoice/service_ar.go` line 89).
`credit_memo.applied` and `credit_memo.partial` (the credit memo's
application call) are refused with `409 exceeds_open_credit` when the
lines together ask for more than the memo's open credit, and with
`409 invalid_state_transition` when the memo is not `open` or `partial`,
`409 exceeds_open_amount` when an invoice is asked for more than its
open amount, `409 invoice_void` when an invoice is void,
`409 customer_mismatch` when an invoice belongs to another customer,
and `409 currency_mismatch` when the invoice's currency differs.
`credit_memo.refunded` (the credit memo's refund call,
`core/internal/payment/service_card.go` `RefundCredit`) is refused with
`409 invalid_state_transition` when the memo is not `open` or `partial`,
with `409 exceeds_open_credit` when the refund is larger than the open
credit, and with `409 customer_mismatch` or `409 currency_mismatch` on a
card refund when the card payment and the credit memo disagree; the
service enforces the finance role (`who.finance` at
`core/internal/payment/service_card.go` line 192).

A credit memo names an invoice, and the credit memo credits tax at
that invoice's rate, `round_half_away(taxable_cents x rate)`, but never
more than the invoice's `tax_cents` less the tax earlier credit memos
Expand All @@ -193,7 +226,15 @@ applies to the provider's answer.

The events are the constants in `core/internal/invoice/model.go`
(`EventInvoiceCreated`, `EventInvoiceVoided`, `EventCreditCreated`,
`EventCreditUpdated`, `EventCreditPosted`, `EventCreditVoided`).
`EventCreditUpdated`, `EventCreditPosted`, `EventCreditVoided`) and
in `core/internal/account/model.go` for the writes the AR core
performs (`EventInvoicePartial`, `EventInvoicePaid`,
`EventInvoiceWrittenOff`, `EventInvoiceReopened`,
`EventCreditPartial`, `EventCreditApplied`, `EventCreditReopened`;
`credit_memo.refunded` is the one the payment module writes itself
at `core/internal/payment/service_card.go` line 263). The events
arrive last in the transaction, after the AR core's effects
(`recordEffects` at `core/internal/invoice/service_cm.go` line 136).

## Posting and the subledger

Expand Down
1 change: 1 addition & 0 deletions docs/modules/orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ consults before it bills.
| `currency` | ISO 4217 | The customer's effective currency. |
| `lines` | array of `OrderLine` | The priced lines, in position order. |
| `invoice_ids` | array of UUID | The invoices that have been issued from this order. |
| `deposit_unapplied_cents` | integer | The unapplied cash of the posted payments taken against this order (deposits), applied to its invoices as they are billed; `sum(pm.amount_unapplied)` over posted payments of this order, read at read time (`core/internal/order/repository.go` `summaryColumns`). |
| `created_at`, `updated_at` | timestamp | RFC 3339 UTC. |

`OrderLine` carries the shared sales line shape of ADR 0005 section 2
Expand Down
Loading