From e5abd28a9806ee8f4239a1db068876be82363975 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sun, 20 Sep 2026 12:59:05 +0200 Subject: bank: import SEB CSV and match against vouchers (schema v8) --- docs/PROTOCOL.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) (limited to 'docs/PROTOCOL.md') 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 -- cgit v1.3