fix(supplier-invoices): freeze verifikat-critical fields once the registration entry is posted (#1249)

* fix(supplier-invoices): freeze verifikat-critical fields once the registration entry is posted

invoice_date becomes the registration verifikat's entry_date and
supplier_invoice_number goes into its description, but both stayed freely
writable through the shared UpdateSupplierInvoiceSchema. Editing either on a
booked invoice moved the invoice row while the posted entry kept its original
values: the two disagreed silently, nothing landed in
journal_entry_rattelse_log, and the change bypassed both sanctioned rättelse
paths (BFL 5 kap 5-7 §).

Adds findLockedVerifikatFields() next to the other supplier-invoice lifecycle
predicates and calls it from both writers (dashboard PUT and v1 PATCH, which
also covers the API-key/MCP path). Only a differing value is refused, so a
full-form resend of the stored value still succeeds, and due_date,
payment_reference and notes stay editable for the aged-invoice flow (#1206).

Fixes #1230

* fix(supplier-invoices): make the verifikat-field lock atomic with the write

Review follow-up on #1230: the lock check read the row a moment before the
update ran, so a registration entry posted in between let exactly the drift
the guard exists to prevent slip through.

When an update moves a verifikat-critical field on a row that read as
unbooked, the write is now pinned with `registration_journal_entry_id is
null`. A concurrent posting therefore matches zero rows: the dashboard route
returns its existing SI_EDIT_CONFLICT ("reload and try again", and the retry
hits the lock with the right message), and the v1 route re-reads to answer
with SI_EDIT_VERIFIKAT_LOCKED plus reason=race rather than a guess.

The pin is conditional on the update actually moving one of those fields, so
metadata-only edits and full-form resends of unchanged values on a booked
invoice keep working (#1206).
This commit is contained in:
Jakob Wennberg
2026-07-27 19:45:26 +02:00
committed by GitHub
parent de461c2cf8
commit 7c44cef66d
8 changed files with 472 additions and 8 deletions
+16
View File
@@ -1168,6 +1168,22 @@ const SUPPLIER_INVOICE: Record<string, StructuredErrorEntry> = {
message_en:
'Only unsettled supplier invoices can be edited. Paid, credited and reversed invoices are corrected with a credit note or a storno.',
},
SI_EDIT_VERIFIKAT_LOCKED: {
httpStatus: 400,
message_sv:
'Fakturadatum och fakturanummer står på det bokförda verifikatet och kan inte ändras här. ' +
'Rätta verifikatet (rättelse i öppen period, annars storno + ny bokföring) eller kreditera fakturan. ' +
'Förfallodatum, betalningsreferens och anteckningar går fortfarande att ändra.',
message_en:
'Invoice date and invoice number are part of the posted verifikat and cannot be changed here. ' +
'Correct the entry instead (inline rättelse in an open period, otherwise storno + re-book), or credit the invoice. ' +
'due_date, payment_reference and notes remain editable.',
remediation: {
description:
'Correct the registration verifikat through a sanctioned rättelse path, or credit the supplier invoice and register a corrected one.',
tool: 'gnubok_correct_entry',
},
},
SI_APPROVE_UPDATE_FAILED: {
httpStatus: 500,
message_sv: 'Kunde inte godkänna leverantörsfakturan.',