Files
accounted/lib/invoices/__tests__/pdf-filename.test.ts
T
6b9338f17e feat(invoices): Betald-stämpel i faktura-PDF + betalningsbekräftelse till kund (#1700)
* feat(invoices): Betald-stämpel i faktura-PDF + betalningsbekräftelse till kund

Closes #1693. A paid faktura re-renders with a BETALD banner (paid date
and amount) and "Betalt: X" followed by "Att betala: 0"; partially_paid
gets the Betalt / Att betala (remaining) rows without a banner. Credit
notes and proformas are unchanged. Labels in sv and en.

The paid copy is its own document, a betalningsbekräftelse, never the
archived original: GET /api/invoices/[id]/pdf?variant=paid refuses
anything but status paid (409 INVOICE_PAYMENT_CONFIRMATION_NOT_PAID),
names the file Betalningsbekraftelse-<nr>.pdf and never reads or
replaces the delivery archive. invoice-pdf-source gains the
'payment_confirmation' re-render reason so the UI caveats it like any
re-render. POST /api/invoices/[id]/send-payment-confirmation emails the
paid PDF with a dedicated subject/body through the existing email
service and recipient routing, without touching status, sent_at,
journal entries or invoice_deliveries (no kind column there; logged via
the route logger instead).

Detail page: the two actions sit inside the Betald card (download paid
copy, send confirmation with an up-front confirm dialog), not in the
header row. No migrations.

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

* fix(invoices): one-line hint for the betalningsbekräftelse actions

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

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 20:17:18 +02:00

85 lines
3.0 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { invoicePdfFilename, paymentConfirmationPdfFilename } from '../pdf-filename'
describe('invoicePdfFilename', () => {
it('includes company, customer, document type, number, and invoice date', () => {
expect(invoicePdfFilename({
companyName: 'Oppy',
customerName: 'Kund AB',
invoiceNumber: '2621',
invoiceDate: '2026-07-21',
})).toBe('Oppy x Kund AB Faktura nr 2621 20260721.pdf')
})
it('uses the correct label for credit notes and other document types', () => {
const base = {
companyName: 'Oppy',
customerName: 'Kund AB',
invoiceNumber: '42',
invoiceDate: '2026-07-21',
}
expect(invoicePdfFilename({ ...base, isCreditNote: true }))
.toContain('Kreditfaktura nr 42')
expect(invoicePdfFilename({ ...base, documentType: 'proforma' }))
.toContain('Proformafaktura nr 42')
expect(invoicePdfFilename({ ...base, documentType: 'delivery_note' }))
.toContain('Följesedel nr 42')
})
it('keeps drafts identifiable without inventing an invoice number', () => {
expect(invoicePdfFilename({
companyName: 'Oppy',
customerName: 'Kund AB',
invoiceId: 'bbbbbbbb-1111-2222-3333-cccccccccccc',
invoiceDate: '2026-07-21',
})).toBe('Oppy x Kund AB Faktura utkast-bbbbbbbb 20260721.pdf')
})
it('removes characters that are unsafe in cross-platform filenames', () => {
expect(invoicePdfFilename({
companyName: 'Oppy / Sverige',
customerName: 'Kund: "Nord" * AB',
invoiceNumber: '../26/21',
invoiceDate: '2026-07-21',
})).toBe('Oppy Sverige x Kund Nord AB Faktura nr .. 26 21 20260721.pdf')
})
it('falls back when company and customer names are empty', () => {
expect(invoicePdfFilename({
companyName: ' ',
customerName: null,
invoiceNumber: '2621',
invoiceDate: '2026-07-21',
})).toBe('Företag x Kund Faktura nr 2621 20260721.pdf')
})
it('keeps multibyte filenames within common filesystem byte limits', () => {
const filename = invoicePdfFilename({
companyName: '🚀'.repeat(60),
customerName: '漢'.repeat(60),
invoiceNumber: '2621',
invoiceDate: '2026-07-21',
})
expect(Buffer.byteLength(filename, 'utf8')).toBeLessThanOrEqual(255)
expect(filename).toMatch(/Faktura nr 2621 20260721\.pdf$/)
})
})
// #1693: the paid copy is named as a betalningsbekräftelse, never as the
// invoice, so the file cannot be mistaken for the one that was sent.
describe('paymentConfirmationPdfFilename', () => {
it('names the file after the invoice number', () => {
expect(paymentConfirmationPdfFilename('2621')).toBe('Betalningsbekraftelse-2621.pdf')
})
it('sanitizes unsafe characters and spaces in the number', () => {
expect(paymentConfirmationPdfFilename('F 2026/0042')).toBe('Betalningsbekraftelse-F-2026-0042.pdf')
})
it('falls back when the number is missing', () => {
expect(paymentConfirmationPdfFilename(null)).toBe('Betalningsbekraftelse-okand.pdf')
})
})