* 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>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { isPaymentConfirmationEligible } from '@/lib/invoices/payment-confirmation'
|
|
import { makeInvoice } from '@/tests/helpers'
|
|
|
|
describe('isPaymentConfirmationEligible', () => {
|
|
it('accepts a fully paid faktura', () => {
|
|
expect(isPaymentConfirmationEligible(makeInvoice({ status: 'paid' }))).toBe(true)
|
|
})
|
|
|
|
it.each(['draft', 'sent', 'overdue', 'partially_paid', 'cancelled', 'credited'] as const)(
|
|
'refuses status %s',
|
|
(status) => {
|
|
expect(isPaymentConfirmationEligible(makeInvoice({ status }))).toBe(false)
|
|
},
|
|
)
|
|
|
|
it('refuses credit notes even when marked paid', () => {
|
|
expect(
|
|
isPaymentConfirmationEligible(makeInvoice({ status: 'paid', credited_invoice_id: 'orig' })),
|
|
).toBe(false)
|
|
})
|
|
|
|
it.each(['proforma', 'delivery_note'] as const)('refuses document type %s', (document_type) => {
|
|
expect(isPaymentConfirmationEligible(makeInvoice({ status: 'paid', document_type }))).toBe(false)
|
|
})
|
|
|
|
it('treats a missing document_type as a regular invoice', () => {
|
|
expect(
|
|
isPaymentConfirmationEligible({ status: 'paid', credited_invoice_id: null, document_type: null }),
|
|
).toBe(true)
|
|
})
|
|
})
|