Commit Graph
2 Commits
Author SHA1 Message Date
MattssonandClaude Fable 5 cd40127f0e 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>
2026-09-01 16:16:29 +02:00
Jonas FlodénandClaude Sonnet 4.6 1cd8863958 fix(transactions): resolve bank account from cash_account_id in booking dialog (#769)
* feat(transactions): expose cash_account_id in list API response

Add cash_account_id to the transactions list API select so that components
can resolve the bank account from the transaction instead of hardcoding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* feat(cash-accounts): extract resolveAccount to shared utility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* refactor(transactions): use shared resolveAccount in MatchVoucherDialog

Replace the local resolveAccount function with the shared utility from
lib/cash-accounts/resolve-account, reducing code duplication and improving
maintainability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* fix(transactions): resolve bank account from cash_account_id in booking dialog

Replaces the hardcoded '1930' bank leg in TransactionBookingDialog with
the actual ledger_account of the transaction's cash account. Companies
with multiple bank accounts (e.g. 1930 + 1940) now get the correct
account pre-filled in both the blank and template-based booking flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* fix(transactions): cancel stale cash-account fetch on dialog re-open

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* fix(transactions): prevent form remount discarding edits during bank account fetch

Hold JournalEntryForm render until the /api/cash-accounts fetch resolves by
changing bankAccount state to string | null (null = pending). This prevents the
form from mounting with key '…-1930', then immediately remounting with the
correct account key and losing any user edits made in the sub-100ms window.
Also adds r.ok guard before parsing and sets '1930' as explicit catch fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

* fix(transactions): cancel stale cash-account fetch in MatchVoucherDialog

Pass a signal object into loadCandidates and return a cleanup from the useEffect
so a stale in-flight fetch (from a previous transaction) cannot call
setAccountNumber/setAccountFallback/setGlLines/setSelected after the dialog
re-opens for a different transaction. Also adds r.ok check before parsing
/api/cash-accounts response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jonas Flodén <jonas@floden.nu>

---------

Signed-off-by: Jonas Flodén <jonas@floden.nu>
2026-06-29 23:08:04 +02:00