fix(invoices): accept USD/GBP payment accounts without an IBAN (#1649)
Payment accounts per currency required an IBAN for every non-SEK currency. USD (ABA routing number) and GBP (sort code) accounts have no IBAN, so a Wise US or UK receiving account could only be saved by pasting an IBAN from another currency, which then printed on the invoice and misrouted the payment. - InvoicePaymentAccount gains bank_code (routing number / sort code) and foreign_account_number; JSONB column, no migration. - Rule, shared by the Zod schema, the client validation and hasUsableInvoicePaymentAccount: a foreign account is usable with an IBAN, or, only for NON_IBAN_CURRENCIES (USD, GBP), with bank_code + foreign_account_number + BIC. EUR/NOK/DKK still require IBAN. - Settings: the two fields appear only for USD/GBP with the identifier named per currency (Routing number (ABA) / Sort code), a hint that IBAN may be left empty, and IBAN no longer marked required there. - Invoice PDF renders the routing row with the same per-currency label plus the foreign account number, in both sv and en. Reported via gnubok_feedback 2026-08-03. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
76b8d5c100
commit
798a76ed7a
@@ -1046,3 +1046,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
|
||||
[2026-08-17] 77xx nedskrivningar split per official BAS kopplingstabell in BOTH k2-mapper and ink2-engine (fältkod 7515: 7700-7739, 7750-7789, 7800-7899; 7516: 774x, 779x): agent feedback 2026-07-07 reported the K2 side; the INK2R side and the swedish-sru-filing reference table had the same whole-77xx-to-7516 error, verified against bas.se INK2_P1_intervall-240118.pdf before overriding the skill reference. NE-bilaga mappings deliberately untouched (NE has no separate omsättningstillgångar line).
|
||||
[2026-08-17] MCP feedback loop = local /loop-feedback-triage appending dev_docs/mcp_feedback_digest.md + small PRs, NOT a GitHub-issue digest or Resend email: closes the loops.md backlog item blocked since 07-09 on a "channel decision". Issues stay founder-authorised; the digest is the read surface. gnubok_feedback reply copy no longer promises weekly aggregation (it was never true); tool advertised in server instructions + agent briefing (feedback_channel), where it was previously discoverable only by scanning tools/list.
|
||||
[2026-08-17] Non-IBAN foreign payment accounts (USD/GBP): added generic bank_code + foreign_account_number to InvoicePaymentAccount (JSONB, no migration) instead of per-country fields (routing_number, sort_code, bsb); rule = IBAN OR (bank_code + foreign_account_number + BIC), only for NON_IBAN_CURRENCIES, label per currency. Chosen over a field per country: the Currency union only carries USD/GBP among non-IBAN systems, and one generic pair keeps the PDF/settings/schema surface small; extend NON_IBAN_CURRENCIES + bankCodeLabelKey when AUD/CAD land. Agent feedback 2026-08-03.
|
||||
|
||||
@@ -21,6 +21,9 @@ import { formatBankgiroNumber, validateBankgiroNumber, validatePlusgiroNumber }
|
||||
import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message'
|
||||
import {
|
||||
INVOICE_PAYMENT_ACCOUNT_CURRENCIES,
|
||||
bankCodeLabelKey,
|
||||
hasNonIbanForeignRouting,
|
||||
isNonIbanCurrency,
|
||||
legacySekInvoicePaymentAccount,
|
||||
normalizeInvoicePaymentAccount,
|
||||
} from '@/lib/invoices/payment-accounts'
|
||||
@@ -46,6 +49,8 @@ const EMPTY_ACCOUNT: InvoicePaymentAccount = {
|
||||
swish: null,
|
||||
iban: null,
|
||||
bic: null,
|
||||
bank_code: null,
|
||||
foreign_account_number: null,
|
||||
}
|
||||
|
||||
function initialAccounts(
|
||||
@@ -233,8 +238,25 @@ export function InvoicePaymentAccountsSettings({
|
||||
if (account.bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(account.bic)) {
|
||||
return t('validation_bic', { currency })
|
||||
}
|
||||
if (account.bank_code && !/^\d{2,3}(-?\d{2,3}){1,2}$|^\d{6,9}$/.test(account.bank_code)) {
|
||||
return t('validation_bank_code', { currency })
|
||||
}
|
||||
if (
|
||||
account.foreign_account_number
|
||||
&& !/^[A-Za-z0-9-]{4,34}$/.test(account.foreign_account_number)
|
||||
) {
|
||||
return t('validation_foreign_account_number', { currency })
|
||||
}
|
||||
// Foreign account: IBAN, or (non-IBAN banking system) bank code +
|
||||
// account number + BIC. Same rule as InvoicePaymentAccountsSchema.
|
||||
if (currency !== 'SEK' && !account.iban) {
|
||||
return t('validation_foreign_iban', { currency })
|
||||
if (isNonIbanCurrency(currency)) {
|
||||
if (!hasNonIbanForeignRouting(account)) {
|
||||
return t('validation_foreign_non_iban', { currency })
|
||||
}
|
||||
} else {
|
||||
return t('validation_foreign_iban', { currency })
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -457,9 +479,42 @@ export function InvoicePaymentAccountsSettings({
|
||||
className="max-w-40 flex-none tabular-nums"
|
||||
/>
|
||||
</SettingsRow>
|
||||
{isNonIbanCurrency(activeCurrency) && (
|
||||
<>
|
||||
<SettingsRow
|
||||
label={t(bankCodeLabelKey(activeCurrency))}
|
||||
htmlFor={`payment-bank-code-${activeCurrency}`}
|
||||
align="baseline"
|
||||
>
|
||||
<SettingsInput
|
||||
id={`payment-bank-code-${activeCurrency}`}
|
||||
inputMode="numeric"
|
||||
maxLength={11}
|
||||
value={value(activeAccount, 'bank_code')}
|
||||
onChange={(event) => updateField('bank_code', event.target.value.replace(/[^\d-]/g, ''))}
|
||||
placeholder={activeCurrency === 'USD' ? '021000021' : '12-34-56'}
|
||||
className="max-w-40 flex-none tabular-nums"
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
label={t('foreign_account_number_label')}
|
||||
htmlFor={`payment-foreign-account-${activeCurrency}`}
|
||||
align="baseline"
|
||||
>
|
||||
<SettingsInput
|
||||
id={`payment-foreign-account-${activeCurrency}`}
|
||||
maxLength={34}
|
||||
value={value(activeAccount, 'foreign_account_number')}
|
||||
onChange={(event) => updateField('foreign_account_number', event.target.value.replace(/\s/g, ''))}
|
||||
className="max-w-56 flex-none tabular-nums"
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRowNote>{t('non_iban_hint', { currency: activeCurrency })}</SettingsRowNote>
|
||||
</>
|
||||
)}
|
||||
<SettingsRow
|
||||
label={
|
||||
activeCurrency !== 'SEK'
|
||||
activeCurrency !== 'SEK' && !isNonIbanCurrency(activeCurrency)
|
||||
? `${t('iban_label')} ${t('required_suffix')}`
|
||||
: t('iban_label')
|
||||
}
|
||||
|
||||
@@ -1400,6 +1400,54 @@ describe('UpdateSettingsSchema', () => {
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a USD payment account with routing number + account number + BIC and no IBAN', () => {
|
||||
const result = UpdateSettingsSchema.safeParse({
|
||||
invoice_payment_accounts: {
|
||||
USD: {
|
||||
bank_name: 'Wise US Inc',
|
||||
bic: 'trwius35xxx',
|
||||
bank_code: '084 009 519',
|
||||
foreign_account_number: '9600 0012 3456 7890',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
const usd = result.data.invoice_payment_accounts?.USD
|
||||
expect(usd?.bic).toBe('TRWIUS35XXX')
|
||||
expect(usd?.bank_code).toBe('084009519')
|
||||
expect(usd?.foreign_account_number).toBe('9600001234567890')
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts a GBP payment account with a dashed sort code', () => {
|
||||
const result = UpdateSettingsSchema.safeParse({
|
||||
invoice_payment_accounts: {
|
||||
GBP: { bic: 'NWBKGB2L', bank_code: '60-16-13', foreign_account_number: '31926819' },
|
||||
},
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects a USD payment account with neither IBAN nor the full routing triple', () => {
|
||||
const result = UpdateSettingsSchema.safeParse({
|
||||
invoice_payment_accounts: {
|
||||
USD: { bank_code: '084009519', foreign_account_number: '9600001234567890' },
|
||||
},
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it('still requires an IBAN for an EUR payment account even with a routing triple', () => {
|
||||
const result = UpdateSettingsSchema.safeParse({
|
||||
invoice_payment_accounts: {
|
||||
EUR: { bic: 'DEUTDEFF', bank_code: '37040044', foreign_account_number: '0532013000' },
|
||||
},
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts null when clearing the legacy SEK bank account mirror', () => {
|
||||
const result = UpdateSettingsSchema.safeParse({
|
||||
bank_name: null,
|
||||
|
||||
+32
-8
@@ -20,8 +20,9 @@ import {
|
||||
deductionTypeForWorkType,
|
||||
normalizeHouseworkType,
|
||||
} from '@/lib/invoices/rot-rut-rules'
|
||||
import { NON_IBAN_CURRENCIES } from '@/lib/invoices/payment-accounts'
|
||||
import { PERSONAL_NUMBER_INPUT_RE } from '@/lib/customers/mask-personal-number'
|
||||
import type { AuditAction } from '@/types'
|
||||
import type { AuditAction, Currency } from '@/types'
|
||||
import type { BankFileFormatId } from '@/lib/import/bank-file/types'
|
||||
|
||||
// ============================================================
|
||||
@@ -1927,19 +1928,42 @@ const InvoicePaymentAccountSchema = z.object({
|
||||
.nullable()
|
||||
.optional()
|
||||
.or(z.literal('')),
|
||||
// Foreign non-IBAN routing (USD ABA routing number, GBP sort code): digits
|
||||
// with optional dashes, 6-9 digits after stripping (ABA = 9, sort code = 6).
|
||||
bank_code: z.string()
|
||||
.transform((value) => value.replace(/\s/g, ''))
|
||||
.pipe(z.string().regex(/^\d{2,3}(-?\d{2,3}){1,2}$|^\d{6,9}$/, 'Ogiltig bankkod'))
|
||||
.nullable()
|
||||
.optional()
|
||||
.or(z.literal('')),
|
||||
// Foreign account number: alphanumeric, distinct from the Swedish
|
||||
// clearing+account pair (account_number is digits-only 6-12).
|
||||
foreign_account_number: z.string()
|
||||
.transform((value) => value.replace(/\s/g, ''))
|
||||
.pipe(z.string().regex(/^[A-Za-z0-9-]{4,34}$/, 'Ogiltigt kontonummer'))
|
||||
.nullable()
|
||||
.optional()
|
||||
.or(z.literal('')),
|
||||
})
|
||||
|
||||
const InvoicePaymentAccountsSchema = z
|
||||
.partialRecord(CurrencySchema, InvoicePaymentAccountSchema)
|
||||
.superRefine((accounts, ctx) => {
|
||||
for (const [currency, account] of Object.entries(accounts)) {
|
||||
if (currency !== 'SEK' && account && !account.iban) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [currency, 'iban'],
|
||||
message: `IBAN krävs för betalningskonto i ${currency}`,
|
||||
})
|
||||
}
|
||||
if (currency === 'SEK' || !account) continue
|
||||
if (account.iban) continue
|
||||
// Non-IBAN banking systems (US, UK): bank code + account number + BIC
|
||||
// identifies the account. Requiring an IBAN there forced users to paste
|
||||
// one from another currency, which then printed on the invoice.
|
||||
const nonIban = NON_IBAN_CURRENCIES.includes(currency as Currency)
|
||||
if (nonIban && account.bank_code && account.foreign_account_number && account.bic) continue
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [currency, 'iban'],
|
||||
message: nonIban
|
||||
? `Ange IBAN eller bankkod, kontonummer och BIC/SWIFT för betalningskontot i ${currency}`
|
||||
: `IBAN krävs för betalningskonto i ${currency}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -81,6 +81,94 @@ describe('invoice payment accounts', () => {
|
||||
expect(hasUsableInvoicePaymentAccount(withoutIban, 'EUR')).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts a USD account identified by routing number + account number + BIC without an IBAN', () => {
|
||||
// gnubok_feedback 2026-08-03: a Wise US receiving account has no IBAN
|
||||
// (US banking does not use them); the only way past the old validation
|
||||
// was pasting an IBAN from another currency, which then printed on the
|
||||
// invoice and misrouted the payment.
|
||||
const usd = resolveInvoicePaymentAccount(company({
|
||||
invoice_payment_accounts: {
|
||||
USD: {
|
||||
bank_name: 'Wise US Inc',
|
||||
clearing_number: null,
|
||||
account_number: null,
|
||||
bankgiro: null,
|
||||
plusgiro: null,
|
||||
swish: null,
|
||||
iban: null,
|
||||
bic: 'TRWIUS35XXX',
|
||||
bank_code: '084009519',
|
||||
foreign_account_number: '9600001234567890',
|
||||
},
|
||||
},
|
||||
}), 'USD')
|
||||
|
||||
expect(hasUsableInvoicePaymentAccount(usd, 'USD')).toBe(true)
|
||||
expect(usd?.bank_code).toBe('084009519')
|
||||
expect(usd?.foreign_account_number).toBe('9600001234567890')
|
||||
|
||||
const rendered = companyWithInvoicePaymentAccount(company({
|
||||
invoice_payment_accounts: {
|
||||
USD: {
|
||||
bank_name: 'Wise US Inc',
|
||||
clearing_number: null,
|
||||
account_number: null,
|
||||
bankgiro: null,
|
||||
plusgiro: null,
|
||||
swish: null,
|
||||
iban: null,
|
||||
bic: 'TRWIUS35XXX',
|
||||
bank_code: '084009519',
|
||||
foreign_account_number: '9600001234567890',
|
||||
},
|
||||
},
|
||||
}), 'USD')
|
||||
expect(rendered.iban).toBeNull()
|
||||
expect(rendered.bank_code).toBe('084009519')
|
||||
expect(rendered.foreign_account_number).toBe('9600001234567890')
|
||||
})
|
||||
|
||||
it('still requires an IBAN for a non-IBAN-currency account that lacks the routing triple', () => {
|
||||
const partial = resolveInvoicePaymentAccount(company({
|
||||
invoice_payment_accounts: {
|
||||
GBP: {
|
||||
bank_name: 'UK bank',
|
||||
clearing_number: null,
|
||||
account_number: null,
|
||||
bankgiro: null,
|
||||
plusgiro: null,
|
||||
swish: null,
|
||||
iban: null,
|
||||
bic: null,
|
||||
bank_code: '12-34-56',
|
||||
foreign_account_number: '12345678',
|
||||
},
|
||||
},
|
||||
}), 'GBP')
|
||||
// Sort code + account number but no BIC: not payable from abroad.
|
||||
expect(hasUsableInvoicePaymentAccount(partial, 'GBP')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not accept the routing triple for an IBAN currency', () => {
|
||||
const eur = resolveInvoicePaymentAccount(company({
|
||||
invoice_payment_accounts: {
|
||||
EUR: {
|
||||
bank_name: 'EUR bank',
|
||||
clearing_number: null,
|
||||
account_number: null,
|
||||
bankgiro: null,
|
||||
plusgiro: null,
|
||||
swish: null,
|
||||
iban: null,
|
||||
bic: 'DEUTDEFF',
|
||||
bank_code: '37040044',
|
||||
foreign_account_number: '0532013000',
|
||||
},
|
||||
},
|
||||
}), 'EUR')
|
||||
expect(hasUsableInvoicePaymentAccount(eur, 'EUR')).toBe(false)
|
||||
})
|
||||
|
||||
it('blocks payable rendering in every currency without a usable account', () => {
|
||||
const emptySettings = company({
|
||||
clearing_number: null,
|
||||
|
||||
@@ -23,8 +23,37 @@ const PAYMENT_FIELDS: readonly (keyof InvoicePaymentAccount)[] = [
|
||||
'swish',
|
||||
'iban',
|
||||
'bic',
|
||||
'bank_code',
|
||||
'foreign_account_number',
|
||||
]
|
||||
|
||||
/**
|
||||
* Currencies whose domestic banking systems do not use IBAN. A payment
|
||||
* account in one of these may be identified by bank_code + account number +
|
||||
* BIC instead of an IBAN (USD: ABA routing number, GBP: sort code). Any
|
||||
* other non-SEK currency still requires IBAN. Extend here (and the
|
||||
* bank_code label map) when a non-IBAN currency is added to Currency.
|
||||
*/
|
||||
export const NON_IBAN_CURRENCIES: readonly Currency[] = ['USD', 'GBP']
|
||||
|
||||
export function isNonIbanCurrency(currency: Currency): boolean {
|
||||
return NON_IBAN_CURRENCIES.includes(currency)
|
||||
}
|
||||
|
||||
/** The routing identifier's name per currency, for labels and messages. */
|
||||
export function bankCodeLabelKey(currency: Currency): 'routing_number' | 'sort_code' | 'bank_code' {
|
||||
switch (currency) {
|
||||
case 'USD': return 'routing_number'
|
||||
case 'GBP': return 'sort_code'
|
||||
default: return 'bank_code'
|
||||
}
|
||||
}
|
||||
|
||||
/** True when a foreign account is fully identified WITHOUT an IBAN. */
|
||||
export function hasNonIbanForeignRouting(account: Partial<InvoicePaymentAccount> | null): boolean {
|
||||
return !!(account?.bank_code && account?.foreign_account_number && account?.bic)
|
||||
}
|
||||
|
||||
function clean(value: string | null | undefined): string | null {
|
||||
const trimmed = value?.trim()
|
||||
return trimmed ? trimmed : null
|
||||
@@ -42,6 +71,8 @@ export function legacySekInvoicePaymentAccount(
|
||||
swish: clean(company.swish),
|
||||
iban: clean(company.iban),
|
||||
bic: clean(company.bic),
|
||||
bank_code: null,
|
||||
foreign_account_number: null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +88,8 @@ export function normalizeInvoicePaymentAccount(
|
||||
swish: clean(account.swish),
|
||||
iban: clean(account.iban)?.replace(/\s/g, '').toUpperCase() ?? null,
|
||||
bic: clean(account.bic)?.replace(/\s/g, '').toUpperCase() ?? null,
|
||||
bank_code: clean(account.bank_code)?.replace(/\s/g, '') ?? null,
|
||||
foreign_account_number: clean(account.foreign_account_number)?.replace(/\s/g, '') ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +107,12 @@ export function hasUsableInvoicePaymentAccount(
|
||||
currency: Currency,
|
||||
): boolean {
|
||||
if (!account) return false
|
||||
if (currency !== 'SEK') return !!account.iban
|
||||
if (currency !== 'SEK') {
|
||||
// A foreign account is usable with an IBAN, or (for non-IBAN banking
|
||||
// systems) with a bank code + account number + BIC. Anything else would
|
||||
// print an invoice the customer cannot pay from.
|
||||
return !!account.iban || (isNonIbanCurrency(currency) && hasNonIbanForeignRouting(account))
|
||||
}
|
||||
return !!(
|
||||
account.iban
|
||||
|| account.bankgiro
|
||||
|
||||
@@ -91,6 +91,10 @@ const LABELS = {
|
||||
swish: 'Swish:',
|
||||
iban: 'IBAN:',
|
||||
bic: 'BIC/SWIFT:',
|
||||
routingNumber: 'Routing number (ABA):',
|
||||
sortCode: 'Sort code:',
|
||||
bankCode: 'Bankkod:',
|
||||
foreignAccount: 'Kontonummer:',
|
||||
ocr: 'OCR/Referens:',
|
||||
paymentReference: 'Betalningsreferens:',
|
||||
invoiceNumber: 'Fakturanummer:',
|
||||
@@ -159,6 +163,10 @@ const LABELS = {
|
||||
swish: 'Swish:',
|
||||
iban: 'IBAN:',
|
||||
bic: 'BIC/SWIFT:',
|
||||
routingNumber: 'Routing number (ABA):',
|
||||
sortCode: 'Sort code:',
|
||||
bankCode: 'Bank code:',
|
||||
foreignAccount: 'Account number:',
|
||||
ocr: 'Reference:',
|
||||
paymentReference: 'Payment reference:',
|
||||
invoiceNumber: 'Invoice number:',
|
||||
@@ -1120,6 +1128,26 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
|
||||
<Text style={styles.paymentValue}>{company.swish}</Text>
|
||||
</View>
|
||||
)}
|
||||
{/* Non-IBAN foreign routing (USD ABA / GBP sort code): the label
|
||||
names the identifier the customer's bank asks for. */}
|
||||
{company.bank_code && (
|
||||
<View style={styles.paymentRow}>
|
||||
<Text style={styles.paymentLabel}>
|
||||
{invoice.currency === 'USD'
|
||||
? L.routingNumber
|
||||
: invoice.currency === 'GBP'
|
||||
? L.sortCode
|
||||
: L.bankCode}
|
||||
</Text>
|
||||
<Text style={styles.paymentValue}>{company.bank_code}</Text>
|
||||
</View>
|
||||
)}
|
||||
{company.foreign_account_number && (
|
||||
<View style={styles.paymentRow}>
|
||||
<Text style={styles.paymentLabel}>{L.foreignAccount}</Text>
|
||||
<Text style={styles.paymentValue}>{company.foreign_account_number}</Text>
|
||||
</View>
|
||||
)}
|
||||
{company.iban && (
|
||||
<View style={styles.paymentRow}>
|
||||
<Text style={styles.paymentLabel}>{L.iban}</Text>
|
||||
|
||||
+9
-1
@@ -2161,7 +2161,7 @@
|
||||
},
|
||||
"settings_invoice_payment_accounts": {
|
||||
"heading": "Payment accounts by currency",
|
||||
"description": "The invoice automatically shows the account matching its currency. A foreign-currency account must have an IBAN before the invoice can be sent.",
|
||||
"description": "The invoice automatically shows the account matching its currency. A foreign-currency account must have an IBAN, or for USD/GBP a bank code, account number and BIC/SWIFT, before the invoice can be sent.",
|
||||
"currency_tabs_label": "Configured currencies",
|
||||
"add_currency_label": "Add currency",
|
||||
"add_currency_placeholder": "Select currency",
|
||||
@@ -2178,6 +2178,11 @@
|
||||
"swish_label": "Swish",
|
||||
"iban_label": "IBAN",
|
||||
"bic_label": "BIC/SWIFT",
|
||||
"routing_number": "Routing number (ABA)",
|
||||
"sort_code": "Sort code",
|
||||
"bank_code": "Bank code",
|
||||
"foreign_account_number_label": "Account number (foreign)",
|
||||
"non_iban_hint": "{currency} accounts often have no IBAN. Enter the bank code, account number and BIC/SWIFT instead; IBAN can be left empty.",
|
||||
"required_suffix": "(required)",
|
||||
"validation_title": "Check the payment account",
|
||||
"validation_clearing": "The clearing number for {currency} must contain 4 to 5 digits.",
|
||||
@@ -2188,6 +2193,9 @@
|
||||
"validation_iban": "The IBAN for {currency} is invalid.",
|
||||
"validation_bic": "The BIC/SWIFT for {currency} is invalid.",
|
||||
"validation_foreign_iban": "Enter an IBAN for the {currency} payment account.",
|
||||
"validation_foreign_non_iban": "Enter an IBAN, or a bank code, account number and BIC/SWIFT, for the {currency} payment account.",
|
||||
"validation_bank_code": "The bank code for {currency} is invalid.",
|
||||
"validation_foreign_account_number": "The account number for {currency} is invalid.",
|
||||
"conflict_title": "Payment accounts changed elsewhere",
|
||||
"conflict_description": "Reload the latest saved values before saving. Your unsaved edits will be discarded.",
|
||||
"reload_server_values": "Reload saved values",
|
||||
|
||||
+9
-1
@@ -2161,7 +2161,7 @@
|
||||
},
|
||||
"settings_invoice_payment_accounts": {
|
||||
"heading": "Betalningskonton per valuta",
|
||||
"description": "Fakturan visar automatiskt kontot som matchar fakturans valuta. Ett utländskt konto måste ha IBAN för att fakturan ska kunna skickas.",
|
||||
"description": "Fakturan visar automatiskt kontot som matchar fakturans valuta. Ett utländskt konto måste ha IBAN, eller för USD/GBP bankkod, kontonummer och BIC/SWIFT, för att fakturan ska kunna skickas.",
|
||||
"currency_tabs_label": "Konfigurerade valutor",
|
||||
"add_currency_label": "Lägg till valuta",
|
||||
"add_currency_placeholder": "Välj valuta",
|
||||
@@ -2178,6 +2178,11 @@
|
||||
"swish_label": "Swish",
|
||||
"iban_label": "IBAN",
|
||||
"bic_label": "BIC/SWIFT",
|
||||
"routing_number": "Routing number (ABA)",
|
||||
"sort_code": "Sort code",
|
||||
"bank_code": "Bankkod",
|
||||
"foreign_account_number_label": "Kontonummer (utländskt)",
|
||||
"non_iban_hint": "Konton i {currency} saknar ofta IBAN. Ange då bankkod, kontonummer och BIC/SWIFT i stället; IBAN kan lämnas tomt.",
|
||||
"required_suffix": "(obligatoriskt)",
|
||||
"validation_title": "Kontrollera betalningskontot",
|
||||
"validation_clearing": "Clearingnumret för {currency} måste vara 4 till 5 siffror.",
|
||||
@@ -2188,6 +2193,9 @@
|
||||
"validation_iban": "IBAN för {currency} är ogiltigt.",
|
||||
"validation_bic": "BIC/SWIFT för {currency} är ogiltigt.",
|
||||
"validation_foreign_iban": "Ange IBAN för betalningskontot i {currency}.",
|
||||
"validation_foreign_non_iban": "Ange IBAN, eller bankkod, kontonummer och BIC/SWIFT, för betalningskontot i {currency}.",
|
||||
"validation_bank_code": "Bankkoden för {currency} är ogiltig.",
|
||||
"validation_foreign_account_number": "Kontonumret för {currency} är ogiltigt.",
|
||||
"conflict_title": "Betalningskontona har ändrats någon annanstans",
|
||||
"conflict_description": "Läs in de senast sparade värdena innan du sparar. Dina osparade ändringar tas bort.",
|
||||
"reload_server_values": "Läs in sparade värden",
|
||||
|
||||
@@ -217,6 +217,14 @@ export interface InvoicePaymentAccount {
|
||||
swish: string | null
|
||||
iban: string | null
|
||||
bic: string | null
|
||||
/**
|
||||
* Foreign non-IBAN routing: ABA routing number (USD), sort code (GBP),
|
||||
* BSB (AUD) or a comparable national bank code. Only meaningful together
|
||||
* with foreign_account_number + bic on a non-SEK account.
|
||||
*/
|
||||
bank_code?: string | null
|
||||
/** Foreign account number for non-IBAN countries (US/UK/AU/CA style). */
|
||||
foreign_account_number?: string | null
|
||||
}
|
||||
|
||||
// Profile (extends auth.users)
|
||||
@@ -335,6 +343,10 @@ export interface CompanySettings {
|
||||
swish: string | null
|
||||
iban: string | null
|
||||
bic: string | null
|
||||
// Foreign non-IBAN routing, only ever populated on the render-time copy
|
||||
// produced by companyWithInvoicePaymentAccount (never a DB column).
|
||||
bank_code?: string | null
|
||||
foreign_account_number?: string | null
|
||||
// Invoice payment instructions keyed by the currency they can receive.
|
||||
// Legacy bank fields above remain the SEK fallback for older companies.
|
||||
invoice_payment_accounts?: Partial<Record<Currency, InvoicePaymentAccount>>
|
||||
|
||||
Reference in New Issue
Block a user