aboutsummaryrefslogtreecommitdiff
path: root/docs/PROTOCOL.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/PROTOCOL.md')
-rw-r--r--docs/PROTOCOL.md66
1 files changed, 64 insertions, 2 deletions
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 3f8aaff..e02d2f7 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -371,14 +371,17 @@ resolved rows in a dry run.
| Command | Args | Notes |
|---|---|---|
| `settings.get` | — | effective org settings (defaults included) |
-| `settings.set` | `key`, `value` | known keys: `default_series`, `attachment_dir`, `bank_account` |
+| `settings.set` | `key`, `value` | known keys: `default_series`, `attachment_dir`, `bank_account`, `invoice_receivable_account`, `invoice_revenue_account` |
`default_series` (1–8 characters, e.g. `A`, `V-`, `A `) is used when
`voucher.post` carries no `series` and as the default series for new
templates. `attachment_dir` (a path, up to 255 characters) is the folder the
TUI file browser opens in when attaching underlag. `bank_account` (digits
only, up to 10 characters, default `1930`) is the account `bank.import` uses
-when the request carries no `account`. Verification ids are the concatenation of series and number
+when the request carries no `account`. `invoice_receivable_account` (default
+`1510`) and `invoice_revenue_account` (default `3001`) are the receivable and
+default revenue account of invoice postings, digits only, up to 10
+characters. Verification ids are the concatenation of series and number
(`V-8`), and series are free-form: only an unbroken numbering per series is
required.
@@ -546,6 +549,65 @@ removes one link and is a `NOT_FOUND` when it does not exist. Both mutate
`bank_matches` only and are audited (`bank.match`, `bank.unmatch`);
`bank.import` is audited as `bank.import`.
+### 7.10 Invoicing (fakturering)
+
+| Command | Args | Result |
+|---|---|---|
+| `customer.list` | `active_only?` | `items[]` ordered by name |
+| `customer.get` | `id` | one customer |
+| `customer.create` | `name`; `address`, `postal_code`, `city`, `country`, `vat_nr`, `email`, `your_ref`, `notes`, `payment_days?` | the customer |
+| `customer.update` | `id` plus any field (merged) | the effective customer |
+| `customer.archive` | `id`, `active` | `id`, `active` |
+| `invoice.sequence_get` | — | `next_number` (1 when no row) |
+| `invoice.sequence_set` | `next_number` (owner) | `next_number` |
+| `invoice.preview` | draft (below) | `content_base64`, `number`, `ocr`, `net_ore`, `vat_ore`, `total_ore` |
+| `invoice.issue` | draft, `dry_run?` | `id`, `number`, `ocr`, `document_id`, `voucher_id`, totals |
+| `invoice.get` | `id` | header, `rows[]`, `document_id`, `voucher_id`, `last_sent_at`, `last_sent_to` |
+| `invoice.list` | `customer_id?`, `status?` (`issued`/`credited`), `limit?` | `items[]`, newest first |
+| `invoice.pdf` | `id` | stored PDF as `content_base64` |
+
+The draft object is the argument set shared by `invoice.preview` and
+`invoice.issue`:
+
+```json
+{"customer_id":2,"invoice_date":"2026-09-20","due_date":"2026-10-20",
+ "delivery_date":"2026-09-20","your_ref":"Lars","our_ref":"Anders",
+ "notes":"", "rows":[{"article_no":"","description":"Utvecklingsarbete",
+ "quantity":"61","unit":"tim","unit_price_ore":120000,"note":"",
+ "vat_code":"25","account":""}]}
+```
+
+`quantity` is a decimal string with at most three decimals (`61`, `61,5`,
+`0,25`); it must be greater than zero. `amount_ore =
+(quantity_milli * unit_price_ore + 500) / 1000` (round half up), and
+`unit_price_ore` must be a non-negative integer. `vat_code` is one of `25`,
+`12`, `6`, `0`, `rc`, `eu` (default `25`). An empty `account` uses the
+setting `invoice_revenue_account` (default `3001`); unknown or inactive
+accounts are `ACCOUNT_NOT_FOUND`/`ACCOUNT_INACTIVE`. The customer must exist
+and be active (`NOT_FOUND`). A draft whose rows do not fit the single page is
+rejected with `TOO_LARGE`.
+
+Numbering is a per-org, global series: `invoice_sequence.next_number` starts
+at 1, is set by the owner and is incremented by exactly one per issued
+invoice. The OCR reference is the number followed by its MOD10 (Luhn) check
+digit: `OCR = <number><check>`. `invoice.preview` renders the document with
+the next number but consumes nothing; `invoice.issue` takes the number,
+renders the PDF, stores it as an immutable `application/pdf` attachment
+(named `Faktura <number> <customer>.pdf`), posts the voucher and links
+invoice, document and voucher in one transaction. The voucher debits
+`invoice_receivable_account` (default `1510`) with the total and credits
+`2610`/`2620`/`2630` with the VAT per rate plus each row's revenue account
+with its net; its source is `invoice`. The renderer's totals equal the
+voucher rows exactly. `invoice.issue` ignores `client_ref` (the number series
+is the idempotency key).
+
+`invoice.preview` is a read. `customer.create/update/archive`,
+`invoice.sequence_set` and `invoice.issue` are audited; `invoice.issue`
+supports `dry_run`, which validates and renders but takes no number and
+writes nothing. `invoice.pdf` returns the stored document as base64
+(`JVBERi0` after decoding is the PDF magic). When the setting
+`invoice_bankgiro` is present it is printed in the document's Bankgiro field.
+
## 8. The TUI is just a client
`bokftui` logs in over the same socket, picks an org and issues the same