feat(bank): expose bank-reported balance (booked + available) in UI, reconciliation, MCP and v1 API (#2118)
* feat(bank): expose bank-reported balance (booked + available) in UI, reconciliation, MCP and v1 API
The PSD2 sync has fetched the bank's reported balance for years but the
data was stranded (F7): the Bank-page source picker read a cash_accounts
column no sync ever updated (frozen at connect time), reconciliation
hard-coded external_balance to null for bank accounts, and neither MCP
nor the v1 API exposed any balance at all, so the only path to a current
bank balance was logging into the bank.
- getAccountBalance now returns booked + available from the same
quota-limited BALANCES response (previously all but one type discarded)
- every sync (manual + cron) mirrors balance, available_balance and
balance_updated_at into cash_accounts, fixing the stale picker
- new cash_accounts.available_balance column (additive migration)
- reconciliation bank kind: external_balance = bank-reported balance,
plus bank_reported_* fields and fetch timestamp in the bank block;
difference math stays movement-based and untouched
- reconciliation view shows "Saldo enligt banken ... hamtat {date}"
- MCP gnubok_list_cash_accounts returns the three balance fields; the
cash_today prompt now reports the bank's figure instead of teaching
agents to answer with the bookkept 19xx balance
- new GET /api/v1/companies/{companyId}/cash-accounts endpoint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ewu46quXgh9LSr9UwYusxm
* fix(bank): keep external_balance null for bank sign-offs; never fabricate a zero balance; guard the mirror against stale writers
Post-review fixes from the skeptic pass + CodeRabbit on PR #2118:
- external_balance stays null for the bank reconciliation kind: sign-off
persists it into account_reconciliations and bokslutsbilagor computes
closing - external from that row, so a today-balance stored on a
balansdag sign-off printed a phantom warning-red differens in the
year-end appendix. The bank-reported figure lives only in the
timestamped bank_reported_* pair in the bank block, and only when its
fetch timestamp exists (a balance of unknown age is suppressed).
- AccountOverview no longer falls back to today's date when the balance
timestamp is missing; the line is omitted instead.
- getAccountBalance returns null on an empty BALANCES response instead
of fabricating amount 0 with a fresh timestamp; sync keeps the
previous stored value.
- updateBalancesFromSync only writes over an older-or-missing
balance_updated_at, so an older sync run finishing later cannot move
the mirrored balance backwards.
- The inline initial backfill (picker save) now mirrors fetched
balances into cash_accounts too (accounts_data is deliberately not
re-written there).
- cash_today MCP prompt mentions the gnubok_call_tool bridge for hosts
that only see the default catalog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ewu46quXgh9LSr9UwYusxm
* fix(bank): express the stale-writer guard as two literal predicates for the schema guard
The .or() with a template literal pushed the no-phantom-columns
unresolvable-expression count over its ceiling. Same semantics, two
updates: one for rows with an older timestamp, one for rows with none.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ewu46quXgh9LSr9UwYusxm
* fix(bank): rank interimBooked (ITBD) as a booked balance type before the generic fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ewu46quXgh9LSr9UwYusxm
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a57a8d968b
commit
cd40127f0e
@@ -8,7 +8,7 @@ description: >-
|
||||
transactions and reconciliation, payroll (lön), VAT/moms and financial
|
||||
reports, SIE import/export, documents, webhooks. Covers auth with
|
||||
gnubok_sk_ API keys, conventions (dry-run, idempotency, cursor
|
||||
pagination, scopes), and all 142 endpoints.
|
||||
pagination, scopes), and all 143 endpoints.
|
||||
---
|
||||
|
||||
<!-- GENERATED FILE, do not edit. Source: lib/api/v1 registry + scripts/api-skill/overlays. Regenerate with `npm run apiskill:generate`. -->
|
||||
@@ -142,7 +142,7 @@ call can undo it, e.g. invoice credit).
|
||||
|
||||
## Endpoint index
|
||||
|
||||
API version `2026-05-12`, 142 operations. Paths are shown without
|
||||
API version `2026-05-12`, 143 operations. Paths are shown without
|
||||
their `/api/v1` prefix (full base URL: `https://app.gnubok.se/api/v1`).
|
||||
|
||||
### Core (5)
|
||||
@@ -255,11 +255,12 @@ POST /companies/{companyId}/documents/{id}/link : Link a document to a journal e
|
||||
POST /companies/{companyId}/inbox-items/{id}/stamp : Mark an inbox item as consumed by a journal entry [scope:documents:write risk:low idempotent]
|
||||
```
|
||||
|
||||
### Banking (24)
|
||||
### Banking (25)
|
||||
|
||||
Full detail: [references/banking.md](references/banking.md)
|
||||
|
||||
```text
|
||||
GET /companies/{companyId}/cash-accounts : List bank/cash accounts with the bank-reported balance [scope:transactions:read risk:low idempotent]
|
||||
POST /companies/{companyId}/imports/bank : Import a bank-file (CSV / XML / CAMT053) [scope:transactions:write risk:medium idempotent]
|
||||
POST /companies/{companyId}/imports/sie : Import a SIE4 file [scope:bookkeeping:write risk:high idempotent]
|
||||
GET /companies/{companyId}/reconciliation/accounts : List the accounts that can be reconciled, with status per account [scope:reconciliation:read risk:low idempotent]
|
||||
|
||||
@@ -2,11 +2,75 @@
|
||||
|
||||
# Banking endpoints
|
||||
|
||||
Bank transactions (ingest, categorize, match against invoices), bank reconciliation runs, and file imports (SIE, bank statements).
|
||||
Bank transactions (ingest, categorize, match against invoices), cash accounts with the bank-reported balance, bank reconciliation runs, and file imports (SIE, bank statements).
|
||||
|
||||
Conventions (auth, envelope, pagination, dry-run, idempotency, standard errors)
|
||||
are in SKILL.md and are not repeated per endpoint.
|
||||
|
||||
### `GET /api/v1/companies/{companyId}/cash-accounts`
|
||||
|
||||
**List bank/cash accounts with the bank-reported balance.**
|
||||
`scope:transactions:read · risk:low · idempotent`
|
||||
|
||||
Returns the company's cash accounts (bank accounts, kassa) with their BAS ledger mapping and, for PSD2-connected accounts, the balance the bank itself reported at the last sync: balance (booked), available_balance, and balance_updated_at (when it was fetched). Pass ?enabled_only=true to return only accounts that sync.
|
||||
|
||||
**Use when:** You need the current bank balance per account (e.g. a covering decision before a payment run), or cash_account_id values to filter transaction listings.
|
||||
**Do not use for:** The bookkept 19xx balance: use the trial-balance or balance-sheet reports. The two legitimately differ (pending bookings, timing).
|
||||
|
||||
**Pitfalls:**
|
||||
- balance/available_balance are what the BANK reported, refreshed at most every 12h (PSD2 quota): check balance_updated_at before treating them as current.
|
||||
- balance is null for manual and SIE-imported accounts, and for PSD2 accounts that have not completed a sync since connecting.
|
||||
- available_balance is null when the bank reports no available balance type; that does not mean 0.
|
||||
|
||||
| Parameter | In | Type | Required | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `companyId` | path | `string` | yes | |
|
||||
|
||||
Response `200`:
|
||||
```ts
|
||||
{
|
||||
data: {
|
||||
cash_accounts: { cash_account_id: string, ledger_account: string, name: string, currency: string, iban: string, is_primary: boolean, enabled: boolean, source: "enable_banking" | "manual" | "sie_import", balance: number, available_balance: number, balance_updated_at: string }[]
|
||||
},
|
||||
meta: {
|
||||
request_id: string,
|
||||
api_version: string,
|
||||
next_cursor?: string,
|
||||
audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string },
|
||||
partial_expansions?: string[]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response `200`:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cash_accounts": [
|
||||
{
|
||||
"cash_account_id": "ca_…",
|
||||
"ledger_account": "1930",
|
||||
"name": "Företagskonto",
|
||||
"currency": "SEK",
|
||||
"iban": "SE4550000000058398257466",
|
||||
"is_primary": true,
|
||||
"enabled": true,
|
||||
"source": "enable_banking",
|
||||
"balance": 125430.5,
|
||||
"available_balance": 123930.5,
|
||||
"balance_updated_at": "2026-09-01T05:12:44.000Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"request_id": "req_…",
|
||||
"api_version": "2026-05-12"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/companies/{companyId}/imports/bank`
|
||||
|
||||
**Import a bank-file (CSV / XML / CAMT053).**
|
||||
|
||||
Reference in New Issue
Block a user