feat(invoices): diagonal UTKAST/DRAFT watermark on draft PDFs instead of the top-margin banner (#2441)
* feat(invoices): mark draft PDFs with a diagonal UTKAST/DRAFT watermark instead of a banner Why the problem occurred: the draft marking was a boxed yellow banner in the page's top margin. It stayed out of the flow (#2369) but still read as UI chrome pasted on a document, and carried a two-line legal sentence that nobody reads on a preview. What was removed: the banner block, its three styles and the four legal sentences (sv+en). The draft state is now one word, bold, rotated -35deg at 14% opacity, centred on every page, the way a stamp marks paper. The download dialog (#2399) already explains why a draft is not a valid invoice before the file exists, so the PDF does not repeat it. Why this shape: rotation and opacity sit on a padded wrapper View so the word turns about its own centre and the Text keeps a plain type style. The overlay is absolutely positioned over the page box and `fixed`, so the document underneath previews pixel-identical to the final print; a test asserts the first row sits at the same y as on a sent invoice. BETALD and MAKULERAD banners are unchanged (separate concern). Fixes #2437 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D9wvsGnvu5tHGqYnJnjnaB * fix(invoices): darken the draft watermark so it survives a greyscale print Skeptic refutation: #6b7280 at 0.14 composites to about 92% brightness on white, which a monochrome print or greyscale scan drops, and a numbered draft otherwise prints the FAKTURA title, its number and an OCR like an issued invoice. Now #4b5563 at 0.3 (about 79% brightness), with a test pinning the composited grey between 70% and 85% so neither extreme can creep back in. Refs #2437 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(invoices): paint the draft watermark last so opaque boxes cannot cover it Skeptic refutation (correctness and regression, independently): the overlay was the first child of the Page. react-pdf paints children in document order and `fixed` does not hoist, so the customer box and the full-width payment section (opaque #f5f5f5 / #f8f9fa) painted over the word. On a two-page draft the last page, the one with totals, bankgiro and OCR, lost the word entirely. The overlay is now the last child of the Page, behind a single isDraftMarked flag that also keeps the cancelled > draft > paid banner precedence. A new test inflates the rendered PDF content streams and asserts the UTKAST glyph run comes after the last rectangle fill on every page, so the element tree alone can no longer pass while the paint order is wrong. Refs #2437 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(invoices): note the English DRAFT label in the download-decision comment CodeRabbit on #2441: the comment said every draft is stamped UTKAST; an English document says DRAFT. Refs #2437 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
2d49a81508
commit
32721b9f61
@@ -609,8 +609,8 @@ describe('POST /api/invoices/[id]/mark-sent: PDF archival', () => {
|
||||
expect(status).toBe(200)
|
||||
// The in-memory invoice still reads 'draft' after the DB status flip
|
||||
// (it's never re-fetched). We must override it before render or
|
||||
// pdf-template.tsx prints the "UTKAST: inte en giltig faktura" banner
|
||||
// on the archived underlag.
|
||||
// pdf-template.tsx prints the "UTKAST" watermark on the archived
|
||||
// underlag.
|
||||
expect(vi.mocked(InvoicePDF)).toHaveBeenCalledTimes(1)
|
||||
const renderArgs = vi.mocked(InvoicePDF).mock.calls[0][0]
|
||||
expect(renderArgs.invoice.status).toBe('sent')
|
||||
|
||||
@@ -986,7 +986,7 @@ describe('POST /api/invoices/[id]/send', () => {
|
||||
// Final render: invoice already has an invoice_number on the fixture, so
|
||||
// preflight is skipped and InvoicePDF is called exactly once. The status
|
||||
// passed in must be 'sent': otherwise pdf-template.tsx renders the
|
||||
// "UTKAST: inte en giltig faktura" banner on the customer's PDF.
|
||||
// "UTKAST" watermark on the customer's PDF.
|
||||
expect(vi.mocked(InvoicePDF)).toHaveBeenCalledTimes(1)
|
||||
const renderArgs = vi.mocked(InvoicePDF).mock.calls[0][0]
|
||||
expect(renderArgs.invoice.status).toBe('sent')
|
||||
|
||||
@@ -363,7 +363,7 @@ export const POST = withRouteContext(
|
||||
// the email and later archived as underlag. Override status to 'sent' on
|
||||
// the in-memory copy: the DB flip happens after email delivery (line
|
||||
// ~185), but if we render with the stale 'draft' status the customer
|
||||
// receives a PDF stamped "UTKAST: inte en giltig faktura".
|
||||
// receives a PDF stamped "UTKAST".
|
||||
const renderableInvoice = { ...(invoice as Invoice), status: 'sent' as const }
|
||||
const { branding, company: renderCompany } = await prepareInvoicePdfRender(
|
||||
company as CompanySettings,
|
||||
|
||||
@@ -807,7 +807,7 @@ describe('POST /api/v1/companies/:companyId/invoices/:id/send', () => {
|
||||
// a preflight render runs first with the F-PREVIEW placeholder. The final
|
||||
// render is the second call: its invoice must carry status: 'sent' and
|
||||
// the freshly-assigned invoice_number, otherwise the customer's PDF is
|
||||
// stamped "UTKAST: inte en giltig faktura".
|
||||
// stamped "UTKAST".
|
||||
const calls = vi.mocked(InvoicePDF).mock.calls
|
||||
expect(calls.length).toBeGreaterThanOrEqual(2)
|
||||
const finalRenderArgs = calls[calls.length - 1][0]
|
||||
|
||||
@@ -561,7 +561,7 @@ export const POST = withApiV1<{ params: Promise<{ companyId: string; id: string
|
||||
// Also override `status` to 'sent' on the in-memory copy. The actual DB
|
||||
// flip happens at step 9a (after email delivery), but if we render with
|
||||
// the stale 'draft' status the customer receives a PDF stamped
|
||||
// "UTKAST: inte en giltig faktura".
|
||||
// "UTKAST".
|
||||
const renderableInvoice: Invoice = {
|
||||
...(typed as Invoice),
|
||||
invoice_number: finalInvoiceNumber,
|
||||
|
||||
Reference in New Issue
Block a user