fix(bookkeeping): let a rättelseverifikation be stornoed; unblock aged supplier-invoice deletion (#1204)

* fix(bookkeeping): let a rättelseverifikation be stornoed; unblock aged supplier-invoice deletion

A user who corrected a booking (storno + rättelse) and then discovered the
affärshändelse was already booked by another verifikat had no sanctioned way
out: reverseEntry refused source_type 'correction' alongside 'storno', and
correctEntry rightly rejects a zeroing rättelse (BFL 5 kap 5 §). The same
guard also broke uncategorize-after-rättelse, since bank transactions are
relinked to the correction entry.

- reverseEntry now blocks only 'storno' (storno-of-a-storno keeps the chain
  ambiguity problem); a correction entry is a regular live verifikat and can
  be stornoed, with correction_of_id keeping the chain traceable.
- CANNOT_REVERSE_STORNO copy narrowed to stornos + remediation hint.
- Supplier-invoice DELETE now allows unbooked, unpaid invoices in
  registered/approved/overdue: the daily overdue cron flipped unbooked
  invoices past due_date into a state where deletion was blocked forever.
  Orphan-safety checks (registration JE, payments, accrual schedule) are what
  actually protect the books. UI shows the delete button accordingly.
- LinkVoucherPicker showed customer-side copy (kundfordran/1510) in
  supplier-invoice mode; supplier mode now explains the 2440-debit
  requirement, including why a direct-cost verifikat cannot be linked.

Support case 2026-07-26 (marcus@).

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

* fix(supplier-invoices): review fixes: fail-closed orphan lookups, hide delete when payments loaded

- The payment and accrual-schedule lookups in DELETE now fail closed: a
  lookup error returns 500 instead of reading as "nothing linked" and
  letting the delete proceed unverified.
- The delete button also requires the loaded payment list to be empty,
  matching the server predicate.

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

* docs: authorize 'approved' in supplier-invoice delete allow-list (compliance-swarm V2.3)

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-26 12:49:10 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 968161b42b
commit 1270b6daeb
12 changed files with 291 additions and 68 deletions
+10 -5
View File
@@ -733,11 +733,16 @@ export async function reverseEntry(
throw new CannotReverseNonPostedError(original.status)
}
// A storno or correction entry must never itself be reversed: a
// storno-of-a-storno makes the original verifikat's cancellation chain
// ambiguous (BFL 5 kap 5§). The UI hides "Återför" for these source types;
// this is the server-side backstop against a direct API call.
if (original.source_type === 'storno' || original.source_type === 'correction') {
// A storno entry must never itself be reversed: a storno-of-a-storno makes
// the original verifikat's cancellation chain ambiguous (BFL 5 kap 5§). A
// correction entry, by contrast, is a regular live verifikation and must
// stay reversible: it can be a duplicate (the affärshändelse already booked
// by another verifikat) or plain wrong, and blocking it left users with no
// sanctioned way out (support case 2026-07-26). Its correction_of_id link
// keeps the chain traceable either way; the original it corrected stays
// 'reversed'. The UI hides "Återför" for stornos; this is the server-side
// backstop against a direct API call.
if (original.source_type === 'storno') {
throw new CannotReverseStornoError(original.source_type)
}