Files
accounted/lib/company/connection-iban.ts
T
MattssonandClaude Fable 5 d0640e0968 fix(settings): clarify bankgiro source on Foretag tab, offer IBAN prefill from bank connection (#1695)
* fix(settings): stop registry bank data masquerading as a setting, offer IBAN from bank connection

User report: the Foretag tab shows a bankgiro from the Bolagsverket snapshot,
which reads as a configured setting while the field payment files and
invoices actually use (Fakturering) was empty.

- Note on the Foretag Bankuppgifter row: data is from Bolagsverket; the
  editable fields live under Installningar -> Fakturering.
- One-click IBAN prefill on the SEK payment account, sourced from the
  connected bank accounts (cash_accounts.iban). Deterministic: only offered
  when every connected account agrees on a single IBAN.
- Delete dead BankDetailsForm.tsx (unmounted since the settings
  restructure); its bank fields are edited via InvoicePaymentAccountsSettings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(settings): only suggest IBAN from enabled, still-connected SEK accounts

Skeptic refutation on the initial PR state: cash_accounts keeps rows after
disconnect (bank_connection_id nulled) and the connect picker mirrors
deselected accounts with enabled=false, so an unfiltered read could offer a
closed or third-party IBAN as the invoice payee / pain.001 sender. Filter on
enabled=true, currency=SEK and a non-null bank_connection_id, matching the
enable-banking session-sharing invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 17:18:51 +02:00

37 lines
1.6 KiB
TypeScript

/**
* Pick the IBAN to suggest for the SEK invoice payment account from the
* company's connected bank accounts (cash_accounts.iban). Like the
* Bolagsverket bankgiro suggestion, this is a suggestion only: the user
* still confirms and saves the value.
*
* The caller must pre-filter rows to accounts a live connection vouches for
* (enabled, non-null bank_connection_id, SEK): cash_accounts also holds rows
* for disconnected and picker-deselected accounts whose IBANs must never be
* offered as the company's own. This helper only enforces determinism: the
* IBAN is returned when every remaining row agrees on a single one. A
* company with accounts at two banks gets no suggestion rather than a guess
* at which one receives customer payments.
*
* Rows come from an unvalidated query result, so every level is checked
* defensively. Returns the compact uppercase IBAN, or null.
*/
export function uniqueConnectionIban(rows: unknown): string | null {
if (!Array.isArray(rows)) return null
const seen = new Set<string>()
for (const row of rows) {
if (!row || typeof row !== 'object') continue
const iban = (row as { iban?: unknown }).iban
if (typeof iban !== 'string') continue
const compact = iban.replace(/\s/g, '').toUpperCase()
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(compact)) continue
seen.add(compact)
if (seen.size > 1) return null
}
return seen.size === 1 ? [...seen][0] : null
}
/** Display form: groups of four, "SE12 3456 ...". Storage stays compact. */
export function formatIbanGroups(iban: string): string {
return iban.replace(/(.{4})/g, '$1 ').trim()
}