fix(salary): make pain.001 betalfil generatable (company IBAN + BIC) (#950)

The ISO 20022 pain.001 salary payment file could never be generated: the
route required company_settings.iban/bic, but no settings screen wrote
those columns, so every request returned 400. The specific reason was
also swallowed by getErrorMessage (isSwedishUserMessage did not know
"krävs"/"saknar"), surfacing only the generic "Förfrågan innehåller
ogiltiga uppgifter" (issue #945).

- Add IBAN + BIC inputs to Settings > Fakturering > Bankuppgifter. BIC
  auto-derives from the clearing number / bank already entered, so in
  practice only the IBAN is typed. Validated client- and server-side.
- Route requires the company IBAN (canonical debtor form every Swedish
  bank accepts) and derives the BIC, with clear actionable errors.
- Employees are unchanged: domestic clearing + account (BBAN), which is
  what Swedish payroll collects. Only the company (debtor) uses IBAN.
- getErrorMessage recognizes "krävs"/"saknar" so payment-file reasons
  surface instead of the generic 400.

Fixes #945

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-09 12:35:52 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent bacc5914af
commit c1ea0d9bf2
12 changed files with 236 additions and 24 deletions
@@ -5,6 +5,8 @@ import {
isValidAccount,
validateEmployeeBankAccount,
lookupBankByClearing,
lookupBicByClearing,
lookupBicByBankName,
} from '@/lib/salary/payment/bank-account'
describe('normalizeBankNumber', () => {
@@ -100,3 +102,37 @@ describe('lookupBankByClearing', () => {
expect(lookupBankByClearing('')).toBeNull()
})
})
describe('lookupBicByClearing', () => {
it('maps clearing numbers to the bank BIC', () => {
expect(lookupBicByClearing('5000')).toBe('ESSESESS') // SEB
expect(lookupBicByClearing('6789')).toBe('HANDSESS') // Handelsbanken
expect(lookupBicByClearing('7123')).toBe('SWEDSESS') // Swedbank
expect(lookupBicByClearing('3000')).toBe('NDEASESS') // Nordea
expect(lookupBicByClearing('1234')).toBe('DABASESX') // Danske Bank
})
it('maps a 5-digit Swedbank clearing via its 8xxx prefix', () => {
expect(lookupBicByClearing('83279')).toBe('SWEDSESS')
})
it('returns null for unknown ranges rather than guessing a BIC', () => {
expect(lookupBicByClearing('9999')).toBeNull()
expect(lookupBicByClearing('123')).toBeNull()
expect(lookupBicByClearing('')).toBeNull()
})
})
describe('lookupBicByBankName', () => {
it('resolves banks outside the clearing table by name', () => {
expect(lookupBicByBankName('Länsförsäkringar')).toBe('ELLFSESS')
expect(lookupBicByBankName('Skandiabanken')).toBe('SKIASESS')
})
it('matches on a normalized substring', () => {
expect(lookupBicByBankName('Danske Bank Sverige')).toBe('DABASESX')
expect(lookupBicByBankName(' SEB ')).toBe('ESSESESS')
})
it('returns null for unknown or empty names', () => {
expect(lookupBicByBankName('Min Lokala Bank')).toBeNull()
expect(lookupBicByBankName('')).toBeNull()
expect(lookupBicByBankName(null)).toBeNull()
})
})