fix(bookkeeping): clear the period IB link when stornoing an opening balance (#1022)
* fix(bookkeeping): clear the period IB link when stornoing an opening balance Reversing a period's opening-balance verifikat left fiscal_periods.opening_balance_entry_id pointing at the reversed entry, and nothing reads that pointer's status. The storno was a no-op where it mattered: - getOpeningBalances() reads the linked entry's lines with no status filter, so the Balansrapport kept showing the cancelled IB. - Year-end blocks while the pointer is non-null and tells the user to "reverse it before re-running year-end": advice the storno could never satisfy. delete_last_voucher and the opening-balance/correct route both refuse an already-reversed entry, so there was no in-app way out. reverseEntry now drops the link, mirroring the bank-transaction unlink directly above it. getOpeningBalances falls through to the duplicate-safe compute_prior_opening_balances RPC, and year-end can re-book the IB. This also closes the documented residual edge in opening-balance/correct (storno succeeded, relink failed) and makes runYearEnd's rollback comment true. Two statements, not one: enforce_opening_balance_immutability rejects a pointer change while opening_balances_set is still true. Covered by a pg-real test, since a mocked client happily accepts the single-statement version that the real trigger rejects. Found via support: a user could not close 2025 because bogus 2026 opening balances from a SIE import would not go away. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: record the storno/IB-link decision in DECISIONS.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b6332e9ff4
commit
a558c75678
@@ -865,6 +865,48 @@ export async function reverseEntry(
|
||||
log.error('failed to unlink transactions from reversed entry', unlinkError, { entryId })
|
||||
}
|
||||
|
||||
// Same hazard one table over: a period whose opening_balance_entry_id still
|
||||
// points at the entry we just reversed. getOpeningBalances() reads the linked
|
||||
// entry's lines directly with no status filter, so the Balansrapport would go
|
||||
// on showing a cancelled IB, and year-end refuses to run while the link is
|
||||
// non-null ("Next fiscal period already has opening balance entry posted;
|
||||
// reverse it before re-running year-end"): advice the storno itself could
|
||||
// never satisfy, leaving no in-app way out. Clearing the link falls
|
||||
// getOpeningBalances through to the duplicate-safe
|
||||
// compute_prior_opening_balances RPC, and lets year-end re-book the IB.
|
||||
//
|
||||
// Two statements, not one: enforce_opening_balance_immutability rejects any
|
||||
// UPDATE that changes opening_balance_entry_id while OLD.opening_balances_set
|
||||
// is still true, so the flag must fall first (same order, and same reason, as
|
||||
// the replace_period_opening_balance_link RPC). Both are scoped to this
|
||||
// entryId, so a period already pointing elsewhere is untouched and callers
|
||||
// that storno an old IB then relink a fresh one (opening-balance/correct)
|
||||
// still win: they relink after this returns.
|
||||
if (original.source_type === 'opening_balance') {
|
||||
const { error: obFlagError } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.update({ opening_balances_set: false })
|
||||
.eq('company_id', companyId)
|
||||
.eq('opening_balance_entry_id', entryId)
|
||||
|
||||
if (obFlagError) {
|
||||
log.error('failed to clear opening_balances_set on reversed IB period', obFlagError, {
|
||||
entryId,
|
||||
})
|
||||
} else {
|
||||
const { error: obUnlinkError } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.update({ opening_balance_entry_id: null })
|
||||
.eq('company_id', companyId)
|
||||
.eq('opening_balance_entry_id', entryId)
|
||||
if (obUnlinkError) {
|
||||
log.error('failed to unlink reversed opening balance entry from period', obUnlinkError, {
|
||||
entryId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this was a payment entry, sync the linked invoice/supplier-invoice status.
|
||||
// Helper is shared with the DELETE journal entry route so both code paths leave
|
||||
// the invoice in a consistent state (BFL 5 kap 5§ requires GL reversal; this
|
||||
|
||||
Reference in New Issue
Block a user