Fix/invoice delivery and payment accounts (#1116)

* fix: reconcile annual reports with final closing entries

* test: cover annual report depreciation and VAT balances

* Merge remote-tracking branch 'origin/main' into fix/usr-fdbck-ch

* fix: show exact invoice delivery details

* fix: use currency account in invoice emails

* fix: address invoice delivery review feedback

* fix: harden invoice delivery and payment accounts

* test: assert RLS-denied zero-row updates

* fix: close remaining invoice compliance gaps

* fix: harden invoice archive authorization

* fix: close invoice delivery review findings

* fix: verify delivery finalization results

* fix: cap combined invoice email recipients

* fix: close final invoice compliance findings

* fix: prevent stale payment account saves

* test: prove invoice delivery isolation

* fix: close invoice privacy review findings

* test: normalize delivery retention dates
This commit is contained in:
Mattsson
2026-07-23 09:54:02 +02:00
committed by GitHub
parent 321e684523
commit 466e55a015
83 changed files with 6619 additions and 671 deletions
@@ -431,6 +431,43 @@ describe('invoice email templates', () => {
expect(text).toMatch(/1[\s ]234,56 EUR/)
})
it('uses the matching EUR payment account in both email variants', () => {
const eurInvoice = makeInvoice({ invoice_number: '1042', currency: 'EUR', total: 1234.56 })
const multiCurrencyCompany = makeCompanySettings({
bank_name: 'Legacy SEK Bank',
clearing_number: '5037',
account_number: '1231231',
iban: 'SE0011111111111111111111',
bic: 'NDEASESS',
invoice_payment_accounts: {
EUR: {
bank_name: 'Mock ASPSP',
clearing_number: null,
account_number: null,
bankgiro: null,
plusgiro: null,
swish: null,
iban: 'SE4550000000058398257466',
bic: 'ESSESESS',
},
},
})
const data = { invoice: eurInvoice, customer: svCustomer, company: multiCurrencyCompany }
const html = generateInvoiceEmailHtml(data)
const text = generateInvoiceEmailText(data)
for (const rendered of [html, text]) {
expect(rendered).toContain('Mock ASPSP')
expect(rendered).toContain('SE4550000000058398257466')
expect(rendered).toContain('ESSESESS')
expect(rendered).not.toContain('Legacy SEK Bank')
expect(rendered).not.toContain('5037-1231231')
expect(rendered).not.toContain('SE0011111111111111111111')
expect(rendered).not.toContain('NDEASESS')
}
})
it('subtracts the ROT/RUT deduction so the email states what the customer owes', () => {
const rotInvoice = makeInvoice({ invoice_number: '1042', total: 1234.56, deduction_total: 500 })
const html = generateInvoiceEmailHtml({ invoice: rotInvoice, customer: svCustomer, company })
+5 -2
View File
@@ -1,6 +1,7 @@
import type { Invoice, Customer, CompanySettings, InvoiceDocumentType } from '@/types'
import { formatDate, getCompanyDisplayName, getCompanyPrimaryName } from '@/lib/utils'
import { getAmountToPay } from '@/lib/invoices/rounding'
import { companyWithInvoicePaymentAccount } from '@/lib/invoices/payment-accounts'
import { applyPlaceholders, escapeHtml, sanitizeSubjectLine, userTextToHtml } from './user-text'
type EmailLang = 'sv' | 'en'
@@ -197,7 +198,8 @@ function safeBrandingColor(value: string | null | undefined, fallback: string):
* Generate HTML email for sending an invoice
*/
export function generateInvoiceEmailHtml(data: InvoiceEmailData): string {
const { invoice, customer, company } = data
const { invoice, customer } = data
const company = companyWithInvoicePaymentAccount(data.company, invoice.currency)
const lang = resolveLang(customer)
const L = LABELS[lang]
@@ -353,7 +355,8 @@ export function generateInvoiceEmailHtml(data: InvoiceEmailData): string {
* Generate plain text email for sending an invoice
*/
export function generateInvoiceEmailText(data: InvoiceEmailData): string {
const { invoice, customer, company } = data
const { invoice, customer } = data
const company = companyWithInvoicePaymentAccount(data.company, invoice.currency)
const lang = resolveLang(customer)
const L = LABELS[lang]
+1
View File
@@ -9,6 +9,7 @@
export interface SendEmailOptions {
to: string | string[]
cc?: string | string[]
bcc?: string | string[]
subject: string
html: string
text?: string