* 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>
22 lines
970 B
TypeScript
22 lines
970 B
TypeScript
import type { Invoice, InvoiceDocumentType } from '@/types'
|
|
|
|
/**
|
|
* Which invoices may be handed to the customer as a betalningsbekräftelse
|
|
* (#1693): a real faktura whose status is `paid`. Credit notes settle against
|
|
* their original and proformas are not payment requests, so neither has a
|
|
* "betald" state worth confirming. `partially_paid` is excluded on purpose:
|
|
* the customer still owes money, and a document stamped BETALD would say
|
|
* otherwise.
|
|
*
|
|
* Shared by the PDF route (`?variant=paid`), the send-confirmation route and
|
|
* the detail page, so the three can never disagree on who gets the button.
|
|
*/
|
|
export function isPaymentConfirmationEligible(
|
|
invoice: Pick<Invoice, 'status' | 'credited_invoice_id'> & { document_type?: InvoiceDocumentType | null },
|
|
): boolean {
|
|
if (invoice.credited_invoice_id) return false
|
|
const docType = invoice.document_type || 'invoice'
|
|
if (docType !== 'invoice') return false
|
|
return invoice.status === 'paid'
|
|
}
|