Prod has three foreign keys between journal_entries and fiscal_periods, so
PostgREST answers PGRST201 to any embed of that pair that does not name the
relationship. pickAnchorEntry() destructured only data, so the error was
dropped and the helper returned null on every call since it shipped on
2026-07-27: supplier-invoice underlag has never once anchored in production.
Users see "Underlag saknas" on a verifikat that plainly shows the invoice PDF.
Names the constraint, matching the already-merged sibling fix in
lib/transactions/inbox-underlag.ts (6a40b3c0e), and handles the error instead
of dropping it.
Adds scripts/checks/ambiguous-embed.mjs to the ratchet guard, because neither
test layer can see this class: a mocked Supabase client never resolves a
relationship, and pg-real bypasses PostgREST entirely. The check derives the
ambiguous table pairs by parsing supabase/migrations, so a migration adding a
second foreign key between two tables arms the guard on the same commit; the
derived list reproduces prod's pg_constraint output exactly. It accepts both
PostgREST hint forms (constraint name and FK column name, both in use here) and
parses aliased embeds, which is the shape the real bug took.
Claude-Session: https://claude.ai/code/session_016ifKg6Ec67A39oxfGPU1yc
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two user reports (Anders, 2026-08-25 + 2026-08-29):
1. The seeded standardmallar "Inkop EU-varor/-tjanster, omvand moms 25%"
booked the cost on 4010/6540, which no momsdeklaration ruta reads, so the
fiktiv moms filled ruta 30/48 while ruta 20/21 (inkopsvarde) stayed 0;
Skatteverket rejects that (FK004, ML 13 kap). The packs now book directly
on the basis accounts 4515/4535 (ACCOUNT_RUTA -> ruta 20/21); the
transaction-picker path already skips its own basis emission for basis
debit accounts, so no double counting. Regression test pins every
reverse-charge pack to a 44xx/45xx business debit. Prod rows update via
the existing pack sync cron (upsert on pack_slug).
2. A kontantmetod payment verifikat stayed "Underlag saknas" although the
invoice PDF was attached and eligible on every static condition: the
inline anchorSupplierInvoiceDocument silently did nothing (prod case
2026-08-28, verified in audit_log: no document_attachments update between
the payment booking and the user's manual re-upload). The helper now
verifies the guarded update actually matched a row instead of claiming
success on zero rows, logs its silent bail branches, and a new daily cron
(/api/documents/reanchor/cron) re-runs the anchor for any floating
retained document with a posted verifikat, replacing the pattern of
one-off repair migrations (20260727180000, 20260824150000). The sweep
names the FK in its embed and is idempotent; locked/closed periods are
skipped as before.
Claude-Session: https://claude.ai/code/session_01Jj6Rg1ViyFRej55gbxLVgj
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A verifikat booked from a supplier invoice showed the invoice PDF when opened
while the list kept warning "Underlag saknas" on the same row. Both surfaces
behaved as written: every missing-underlag surface only accepts a referenced
supplier-invoice document when it is ANCHORED to a journal entry (only anchored
docs sit behind block_document_deletion), while the verifikat view's reference
resolver displayed the document regardless.
The document was floating because delete_last_voucher clears journal_entry_id
on everything attached to the voucher it tears down (the FK is ON DELETE
RESTRICT, so it must). Deleting a rättelse the invoice PDF had been relinked
onto therefore orphaned it while the payment verifikat stayed posted, and
nothing ever anchored it again: the warning was unresolvable by design.
Same class one surface over: v1 mark-paid never linked the document at all,
dashboard mark-paid only did so for the cash entry, and both
match-supplier-invoice routes propagated the transaction's document but not the
invoice's own. Four of the five affected prod rows come from those paths, not
from a deleted voucher.
- lib/core/documents/supplier-invoice-underlag.ts: anchor a floating document
to the invoice's own posted verifikat (registration, then payment, then
partial payments; open unlocked periods only). Never moves an anchored doc,
never throws.
- Called after delete_last_voucher and from all four payment paths.
- getJournalEntryUnderlagReferences withholds an unanchored document so the
verifikat view and the warning can no longer contradict each other.
- Migration 20260727180000 backfills the rows already in this state.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>