aboutsummaryrefslogtreecommitdiff
path: root/docs/PROTOCOL.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/PROTOCOL.md')
-rw-r--r--docs/PROTOCOL.md114
1 files changed, 84 insertions, 30 deletions
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 76197d9..75ae963 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -374,26 +374,36 @@ resolved rows in a dry run.
| Command | Args | Notes |
|---|---|---|
| `settings.get` | — | effective org settings (defaults included); secret values replaced by `_set` flags |
-| `settings.set` | `key`, `value?` | known keys: `default_series`, `attachment_dir`, `bank_account`, `invoice_receivable_account`, `invoice_revenue_account`, `invoice_bankgiro`, `invoice_our_ref`, `smtp_host`, `smtp_port`, `smtp_user`, `smtp_from`, `smtp_reply_to`, `smtp_security`, `smtp_password` |
+| `settings.set` | `key`, `value?` | known keys: `default_series`, `series_voucher`, `series_invoice`, `series_payroll`, `series_bokslut`, `series_ib`, `attachment_dir`, `bank_account`, `invoice_receivable_account`, `invoice_revenue_account`, `invoice_bankgiro`, `invoice_our_ref`, `document_header_color`, `smtp_host`, `smtp_port`, `smtp_user`, `smtp_from`, `smtp_reply_to`, `smtp_security`, `smtp_password` |
-`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 accepted for
+`series_voucher`, `series_invoice`, `series_payroll`, `series_bokslut` and
+`series_ib` (1–8 characters, e.g. `A`, `V-`, `Å`) are the number series each
+feature posts into: manual vouchers, `invoice.issue`, payroll runs and
+payments, `bokslut.post` and ingående balans. They default to `A`, `F`, `L`,
+`Å` and `IB`; `series_voucher` also falls back to the legacy
+`default_series` (1–8 characters) when unset, and new templates default to
+it. Reports and SIE count both the configured `series_ib` and the
+historical `IB` series as ingående balans. `attachment_dir` (a path, up to
+255 characters) is accepted for
compatibility, but the TUI file browser now remembers the directory of the
last picked attachment client-side. `bank_account` (digits
only, up to 10 characters, default `1930`) is the account `bank.import` uses
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
+characters. `document_header_color` (`#rrggbb`, default `#314c59`) is the
+background colour of the generated documents' header bars (invoice and
+lönebesked); the invoice prints the org name on it. Verification ids are the
+concatenation of series and number
(`V-8`), and series are free-form: only an unbroken numbering per series is
required.
`smtp_host` (up to 255 characters, no control characters), `smtp_user` (up to
-255), `smtp_from` and `smtp_reply_to` (up to 254), `smtp_port` (digits,
-1–65535) and `smtp_security` (`starttls`, `tls` or `plain`, default
-`starttls` when unset) configure the outgoing mail used when invoices are
-sent.
+255), `smtp_from` and `smtp_reply_to` (up to 254) must be e-mail addresses
+(one `@`, no spaces), `smtp_port` (digits, 1–65535) and `smtp_security`
+(`starttls`, `tls` or `plain`, default `starttls` when unset) configure the
+outgoing mail used when invoices are sent. The sender's display name is the
+organization name; `smtp_from` is the address.
`smtp_password` is a secret setting. `settings.set` encrypts the value with
AES-256-GCM under the key in the `BOKFD_SECRET_KEY` environment variable (32
@@ -441,7 +451,17 @@ linked to that voucher and each item carries that `voucher_id`.
| `sru.export` | `fiscal_year`, `adjustments?`, `submitter?`, `assisted?`, `audited?`, `ignore_unmapped?` | `INFO.SRU` + `BLANKETTER.SRU` (ISO-8859-1, base64) |
All reports are pure reads, respect locks, and return JSON rows. Amounts are
-öre. `report.general_ledger` (huvudbok) returns account blocks:
+öre. IB (ingående balans) for a balance account (asset, liability, equity) is
+all earlier history, including earlier years' `IB` vouchers, plus this
+year's `IB` vouchers; a P&L account (revenue, expense) restarts at zero at
+every fiscal-year start, so its IB is only this year's `IB` vouchers and,
+with a narrowed `from`, the year's movements before it.
+`report.income_statement` (and therefore `sru.export` and the TUI
+resultatrapport) ignores the source system's `"Stäng ..."` closing vouchers
+in SIE-imported years (`source:"sie_import"`), where the P&L accounts are
+closed straight to 2099 and would otherwise net to zero; the TUI
+årsredovisning uses the same rule. `report.general_ledger` (huvudbok)
+returns account blocks:
`{"fiscal_year","from","to","last_voucher":{...},"accounts":[{"account",
"name","ib_ore","debit_ore","credit_ore","ub_ore","rows":[{"series",
"number","date","description","row_description","debit_ore","credit_ore",
@@ -472,7 +492,10 @@ like the blankett; box 48 positive as filed) and returns `{"org_nr",
bytes are ISO-8859-1, so write them verbatim to a `.xml` file. Both cover
the whole fiscal year; the ledger's
period can be narrowed with `from`/`to`. `report.vat` returns `{"from","to","boxes":[{"box":"05","label":"...","amount_ore":...}],"note"}`.
-Rules sharing a box are summed into a single entry. `box 49` is the sum of
+Rules sharing a box are summed into a single entry. The momsomföring
+itself (any voucher with a row on 2650) and SIE-imported `"Stäng ..."` year
+closings are left out, so a period that includes its own VAT settlement
+still reports the underlying boxes. `box 49` is the sum of
the moms boxes (`10`,`11`,`12`,`30`,`31`,`32`,`48`,`60`,`61`,`62`), so box 48
is signed like the blankett (ingående moms negative); underlag boxes do not
change what is payable.
@@ -509,7 +532,8 @@ support `dry_run`, which validates without writing.
| `sie.import` | `content_base64` or `path`, `dry_run?` | one file per call; creates missing accounts and posts #VER as `source:"sie_import"`; only into an empty org fiscal year; `#IB` becomes an `IB` voucher when the year has no earlier history, otherwise the earlier vouchers carry the balances |
SIE 4 files are written in CP437 with PC8 format, `#SIETYP 4`, `#FNR`, `#ORGNR`,
-`#KONTO`, `#IB`, `#UB`, `#RES`, `#VER`, `#TRANS`. Import is the migration path
+`#KONTO`, `#IB`, `#UB`, `#RES`, `#VER`, `#TRANS`. `#IB`/`#UB` are written for
+balance accounts and `#RES` for P&L accounts, with IB as in §7.6. Import is the migration path
from Fortnox/Visma/BL and must be dry-run first; it reports exactly what would
be created.
@@ -585,10 +609,11 @@ removes one link and is a `NOT_FOUND` when it does not exist. Both mutate
| `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.get` | `id` | header, `rows[]`, `document_id`, `voucher_id`, `paid_date`, `payment_voucher_id`, `last_sent_at`, `last_sent_to` |
+| `invoice.list` | `customer_id?`, `status?` (`issued`/`credited`), `limit?` | `items[]`, newest first, with `paid_date` |
| `invoice.pdf` | `id` | stored PDF as `content_base64` |
| `invoice.send` | `id`, `to?` | `id`, `sent_to`, `at`; `dry_run` returns `to`, `subject` |
+| `invoice.pay` | `id`, `voucher_id`, `dry_run?` | `id`, `number`, `paid_date`, `payment_voucher_id`, `voucher_series`, `voucher_number` |
The draft object is the argument set shared by `invoice.preview` and
`invoice.issue`:
@@ -611,6 +636,13 @@ 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`.
+A row with `"text": true` is a free-text line: only `description` is used,
+it has no quantity, unit, price or VAT, contributes nothing to the totals
+and only prints its description in the document. Every draft still needs at
+least one priced row (`INVALID_ARGS` otherwise, "invoice total must be
+greater than zero"). `invoice.get` and `invoice.list` return rows with
+`is_text`.
+
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
@@ -629,7 +661,9 @@ is the idempotency key).
`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
+(`JVBERi0` after decoding is the PDF magic). The document's header bar
+prints the organization `name` and is coloured by `document_header_color`
+(default `#314c59`; invalid stored values fall back to it). When the setting
`invoice_bankgiro` is present it is printed in the document's Bankgiro field;
`invoice_our_ref` (up to 64 characters) prefills the invoice form's "Vår
referens".
@@ -647,6 +681,15 @@ updated and the `invoice.send` audit entry stores `{id,to,subject}` only.
`dry_run` validates configuration, recipient and stored document and returns
the recipient and subject without sending or updating anything.
+`invoice.pay` links a payment voucher (created by the client, normally from
+the TUI's **Kvittera betalning** action, which prefills debit `bank_account`
+and credit `invoice_receivable_account` in the ordinary voucher form) and
+stamps `paid_date` with the voucher's date. The voucher must credit the
+`invoice_receivable_account` (default `1510`) with exactly the invoice
+total, else `INVALID_ARGS`; an already paid invoice and a `credited` one are
+rejected (`CONFLICT` and `INVALID_ARGS`). `dry_run` validates without
+writing, and the command is audited.
+
### 7.11 Anställda (employees)
The employee register. `personal_no` is checked for shape (10 or 12 digits,
@@ -732,7 +775,8 @@ given date (default: today) and sets the run's `status` to `paid` with
`payment_voucher_id`; paying twice is a `CONFLICT`.
`payroll.payslip` renders one A4 lönebesked for an employee line of a posted
-run: the employer header and footer, the employee name, the masked
+run: the employer name in the header bar (coloured by
+`document_header_color`), the employer footer, the employee name, the masked
personnummer (`********-1234`, or `********` when the key is unavailable),
period, pay date, tax table and column, then Bruttolön, the negative
Preliminärskatt, a rule and Nettolön, and the note `Arbetsgivaravgifter
@@ -857,6 +901,7 @@ Args: `name:type(values)[!][=default]`, `!` = required.
| `invoice.list` | viewer | yes | no | no | `customer_id:int`, `status:enum(issued\|credited)`, `limit:int=200` |
| `invoice.pdf` | viewer | yes | no | no | `id:int!` |
| `invoice.send` | bookkeeper | yes | yes | yes | `id:int!`, `to:string` |
+| `invoice.pay` | bookkeeper | yes | yes | yes | `id:int!`, `voucher_id:int!` |
| `employee.list` | viewer | yes | no | no | `active_only:bool` |
| `employee.get` | viewer | yes | no | no | `id:int!` |
| `employee.create` | bookkeeper | yes | yes | yes | `name:string!`, `personal_no:string!`, `address:string`, `postal_code:string`, `city:string`, `bank_account:string`, `email:string`, `salary_account:string`, `monthly_salary_ore:int=0`, `tax_table:int=30`, `tax_column:int=1` |
@@ -902,7 +947,10 @@ commands. Implemented screens (0.1.0-dev):
- **Mallar** — list, create and edit templates in the same form style as
vouchers (Tab, dynamic rows, F7 clear row, F5 validate, F9 save); archive
keeps the template but hides it from the list.
-- **Underlag** — inbox of unlinked attachments; `a` uploads a file.
+- **Underlag** — inbox of unlinked attachments; `a` uploads a file and
+ Enter opens **Granska** (text in a pager, other files in the desktop
+ viewer) or **Ladda ned…**; the voucher detail's underlag list (`f`) works
+ the same way.
- **Bankavstämning** — imported bank transactions (`bank.import`) matched
against vouchers on the bank account, with suggestions; Enter matches the
suggested voucher (or picks another), `u` unmatches, `a` imports a SEB CSV.
@@ -912,16 +960,21 @@ commands. Implemented screens (0.1.0-dev):
returns to the list. A failed auto-match keeps the posted voucher and
shows the server error.
- **Fakturor** — invoice list (`invoice.list`, newest first) with number,
- date, customer, total and status (`utfärdad`/`krediterad`). Ctrl+N opens
- the form, Enter the detail. The form has the customer picker, invoice/due
- (due defaults from the customer's payment days) and delivery dates, er/var
- referens and rows (beskrivning, antal, enhet, à-pris, moms, anm); `F5`
- previews the real PDF (`invoice.preview`, nothing stored, no number
- consumed), `Ctrl+Enter` issues (`invoice.issue`) and then asks
- "Skicka faktura <nr> till <e-post>?". The detail shows header and rows;
- `p` fetches the stored PDF (`invoice.pdf`) and `s` sends it
- (`invoice.send`). In the list, `n` sets the next invoice number
- (`invoice.sequence_get`/`sequence_set`, owner-only).
+ date, customer, total and status (`utfärdad`/`krediterad`/`betald
+ <datum>`). Ctrl+N opens the form, Enter the detail. The form has the
+ customer picker, invoice/due (due defaults from the customer's payment
+ days) and delivery dates, er/var referens and rows (beskrivning, antal,
+ enhet, à-pris, moms, anm); a row with only beskrivning is a free-text line
+ (`text` rows, no amount). `F5` previews the real PDF (`invoice.preview`,
+ nothing stored, no number consumed), `F9` issues (`invoice.issue`) and
+ then asks "Skicka faktura <nr> till <e-post>?". The detail shows header
+ and rows and offers `p = visa PDF` (`invoice.pdf`), `s = skicka`
+ (`invoice.send`), `u = duplicera` (a new draft with the same rows and
+ today's dates) and, on unpaid invoices, `b = kvittera betalning`: a
+ prefilled payment voucher (debit `bank_account`, credit
+ `invoice_receivable_account`) is opened in the ordinary voucher form and,
+ once posted, linked with `invoice.pay`. In the list, `n` sets the next
+ invoice number (`invoice.sequence_get`/`sequence_set`, owner-only).
- **Kunder** — the customer register (name, address, postal code, city, VAT
number, e-mail, your reference, payment days, notes). Ctrl+N creates,
Enter edits (F5 validates with a dry run, Ctrl+Enter saves), `d`
@@ -963,10 +1016,11 @@ commands. Implemented screens (0.1.0-dev):
- **Bolaget** — the dashboard's hub for the org's master data:
**Företagsuppgifter** (name, org number, VAT number, address, e-mail,
phone, moms period, framework, fiscal-year start month), editable in
- place by owners, others see it read-only; **Fakturauppgifter**
- (standardserie, fordringskonto, intäktskonto, bankgiro, vår referens) and
+ place by owners, others see it read-only; **Fakturauppgifter** (nästa
+ fakturanummer (owner), fordringskonto, intäktskonto, bankgiro, vår
+ referens); **Verifikationsserier** (the per-feature series); and
**E-post (SMTP)** (host, port, user, sender, reply-to, security,
- password), both `settings.set` and open to bookkeepers;
+ password), the last three `settings.set` and open to bookkeepers;
**Styrelseledamöter**; and the registers **Anställda**, **Kunder** and
**Momsregler**.
- **System** — the hub with **Skattetabeller** and **Revision**. The file