summaryrefslogtreecommitdiff
path: root/docs/PROTOCOL.md
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 12:59:05 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 12:59:05 +0200
commite5abd28a9806ee8f4239a1db068876be82363975 (patch)
tree2af68cbd748e49f7547226c06b233240a2f252c5 /docs/PROTOCOL.md
parent9ee8cb18b1017dce11e7c8c9b7fafad301cb766e (diff)
downloadbokf-e5abd28a9806ee8f4239a1db068876be82363975.tar.gz
bokf-e5abd28a9806ee8f4239a1db068876be82363975.zip
bank: import SEB CSV and match against vouchers (schema v8)
Diffstat (limited to 'docs/PROTOCOL.md')
-rw-r--r--docs/PROTOCOL.md54
1 files changed, 52 insertions, 2 deletions
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 6f26463..d7e70be 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -108,6 +108,7 @@ Scopes on a token can narrow but never widen the user's role.
|---|---|---|---|---|
| Read: vouchers, reports, audit, accounts | ● | ● | ● | ● |
| `voucher.post`, `voucher.correct`, `attachment.put` | | ● | ● | |
+| `bank.import`, `bank.match`, `bank.unmatch` | | ● | ● | |
| `sie.import`, `account.create`, `account.update` | | ● | ● | |
| `period.lock`, `fiscal_year.open/close/reopen`, `org.update` | | | ● | |
| `org.member_*`, `token.create` for others | | | ● | |
@@ -370,12 +371,14 @@ 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` |
+| `settings.set` | `key`, `value` | known keys: `default_series`, `attachment_dir`, `bank_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. Verification ids are the concatenation of series and number
+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
(`V-8`), and series are free-form: only an unbroken numbering per series is
required.
@@ -496,6 +499,53 @@ be created.
backup; `full:true` includes attachment hashes. The voucher chain is verified
per org in posting order (SCHEMA.md §7.1); the audit chain globally.
+### 7.9 Bank reconciliation (statement import)
+
+Phase 1 is mechanical reconciliation only: statements are imported as
+read-only evidence and already-booked vouchers are matched against them.
+**It never books anything** — no voucher is created or changed by these
+commands. Suggestions are advisory; a human (or an agent) must post any
+missing voucher with `voucher.post` and then match it.
+
+| Command | Args | Result |
+|---|---|---|
+| `bank.import` | `format`, `content_base64` or `path`, `account?` | `format`, `account`, `total`, `imported`, `duplicates`, `first_date`, `last_date`; `dry_run?` |
+| `bank.list` | `status?`, `from?`, `to?`, `account?`, `limit` | `items[{id,account,booked_at,value_date,text,type,amount_ore,balance_ore,matches[],suggestions[]}]`, `summary{unmatched,matched,unmatched_amount_ore}` |
+| `bank.match` | `transaction_id`, `voucher_id` | `transaction_id`, `voucher_id`, `difference_ore` |
+| `bank.unmatch` | `transaction_id`, `voucher_id` | `transaction_id`, `voucher_id`, `unmatched:true` |
+
+`bank.import` takes one SEB CSV export (`format:"seb"`): UTF-8 with an
+optional BOM, `;`-separated, the header exactly
+`Bokförd;Valutadatum;Text;Typ;Insättningar;Uttag;Bokfört saldo`. Quoted fields
+use `""` for an embedded quote; amounts use decimal comma and may group
+thousands with spaces or `.`; the `Insättningar` and `Uttag` columns are
+mutually exclusive and exactly one must be non-empty (a withdrawal becomes a
+negative amount); the balance may be empty. Files larger than 64 MiB are
+rejected with `TOO_LARGE`, an unknown account with `ACCOUNT_NOT_FOUND`, a
+header mismatch or malformed row (reported with its line number) with
+`INVALID_ARGS`. Each row is hashed over its canonical field encoding incl. the
+account, so re-importing the same export only reports duplicates
+(`imported:0`). `account` defaults to the `bank_account` setting, else `1930`.
+The result's `first_date`/`last_date` span every row in the file.
+
+`bank.list` items carry their `matches` (`voucher_id`, `series`, `number`,
+`date`, `bank_amount_ore` = the voucher's signed movement on the transaction's
+account) and, for unmatched transactions, up to three advisory `suggestions`
+(`voucher_id`, `series`, `number`, `date`, `amount_ore`): posted vouchers that
+touch the account, whose movement on it equals the transaction amount exactly,
+are dated within ±5 days and are not yet matched to any transaction.
+`summary` always counts all transactions for the org (optionally narrowed by
+`account`), ignoring `status`, `from` and `to`.
+
+`bank.match` links one transaction to one voucher. The voucher must post to
+the transaction's account; matching the same pair twice is a `CONFLICT`.
+Several vouchers may match one transaction (partial matching) and
+`difference_ore` is the transaction amount minus the summed bank legs after
+the insert, so `0` means the transaction is fully reconciled. `bank.unmatch`
+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`.
+
## 8. The TUI is just a client
`bokftui` logs in over the same socket, picks an org and issues the same