feat(webshop): generate orderunderlag and attach it to the verifikat at booking (#1899)

* feat(webshop): generate orderunderlag PDF and attach it to the verifikat at booking

Booked webshop orders only carried the VAT split; the verifikat showed no
product lines, customer or payment method although the sync already stores
all of it in webshop_orders.line_items (#1881).

- lib/webshop-orders/order-underlag.tsx: pure model builder + react-pdf
  template (order lines, customer, payment method, per-rate VAT summary,
  SEK conversion facts) + archiveWebshopOrderUnderlag, which renders and
  archives the PDF on the committed verifikat through uploadDocument
  (upload_source system, extraction none), mirroring archiveIssuedInvoicePdf.
  Never throws: the booking is immutable by then.
- book route: archive after commitEntry; response gains underlag_archived.
  FX-retry now also syncs the in-memory row so the underlag shows the
  resolved SEK facts.
- webshop_order added to NEEDS_DOC_SOURCE_TYPES and (new migration
  20260825140000) to the verifikat_without_documents needs-doc list, so a
  failed attach or a historical booking surfaces on the saknar-underlag
  worklist. transactions_without_documents is deliberately unchanged.
- tests: underlag model/render/archive unit tests, book-route archive and
  failure-isolation cases, pg test extended (per-source-type probe now
  covers webshop_order; explicit flagged/silenced pair).

Fixes #1881

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

* chore(migrations): move webshop needs-doc migration after main's 20260825150000

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

* test(webshop): add manually_booked fields to the underlag order fixture

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

* fix(webshop): skeptic findings on the orderunderlag (#1881)

Two refutations from the skeptic pass on PR #1899, both fixed:

1. Correctness: sv-SE Intl emits U+2212 MINUS SIGN for negatives, which
   Helvetica/WinAnsi PDF fonts drop silently, so refund and discount
   amounts on the archived underlag rendered as POSITIVE. formatAmount now
   replaces U+2212 with an ASCII hyphen (same guard as formatPdfCurrency),
   is exported, and is pinned by a regression test.

2. Regression: NEEDS_DOC_SOURCE_TYPES had two hardcoded copies that missed
   webshop_order, so flagged rows rendered without the "Underlag saknas"
   chip, waiver toggle, or batch-exempt selection, and the weekly
   missing-underlag push cron disagreed with the badge. The constant now
   lives in dependency-free lib/worklist/types.ts (client-safe), is
   re-exported from categories.ts, and both JournalEntryList.tsx and
   push-notifications/notification-scheduler.ts consume it instead of
   their own copies.

Also: "Bokfört i SEK" reworded to "Motsvarande i SEK" (compliance skeptic
observation: the dialog's lines are user-editable, so the underlag must
state the order's conversion, not claim a booking fact).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-25 15:15:33 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent c6f2bebab9
commit 5fc0be9ed7
11 changed files with 1150 additions and 31 deletions
+4 -14
View File
@@ -16,21 +16,11 @@ import {
} from '@/lib/invoices/matchable-statuses'
import type { SuggestedMatch } from './types'
const log = createLogger('worklist')
// Canonical home is lib/worklist/types.ts (dependency-free, client-safe);
// re-exported here so existing server-side imports keep working.
export { NEEDS_DOC_SOURCE_TYPES } from './types'
/**
* Journal-entry source types that require underlag (BFL 5 kap 7§). Source
* types representing system-generated entries (VAT settlement, year-end,
* currency revaluation, …) are exempt by omission.
*/
export const NEEDS_DOC_SOURCE_TYPES = [
'manual',
'bank_transaction',
'supplier_invoice_registered',
'supplier_invoice_paid',
'supplier_invoice_cash_payment',
'import',
] as const
const log = createLogger('worklist')
/**
* Upper bound on the unconsumed-inbox scan in countInboxDocuments. An inbox
+24
View File
@@ -117,3 +117,27 @@ export interface SuggestedMatch {
counterparty_name: string | null
candidate_total: number | null
}
/**
* Journal-entry source types that require underlag (BFL 5 kap 7 §). Source
* types representing system-generated entries (VAT settlement, year-end,
* currency revaluation, ...) are exempt by omission.
*
* Single source of truth for EVERY TS surface (worklist counts, journal-list
* chip/waiver UI, no-doc-required batch route, push notifications); the SQL
* mirror lives in the verifikat_without_documents RPC, pinned by
* tests/pg/document-surfaces-unification.pg.test.ts. Lives here (not in
* categories.ts) because this module is dependency-free and safe to import
* from client components.
*/
export const NEEDS_DOC_SOURCE_TYPES = [
'manual',
'bank_transaction',
'supplier_invoice_registered',
'supplier_invoice_paid',
'supplier_invoice_cash_payment',
'import',
// Webshop order bookings rest on the generated orderunderlag (#1881); an
// entry whose underlag failed to attach must surface here.
'webshop_order',
] as const