fix(bookkeeping): resolve settlement account in all categorization flows and ship mis-booking audit (#1383)
Completes the #985/#986/#987 caller sweep: categorize-core, v1 batch-categorize, pending-operation edits and the MCP categorize path now resolve the settlement leg from the transaction's cash account instead of inheriting a hardcoded or stale account. Extends the correct_entry preview with currency, tax and dimension line metadata so staged corrections preserve full line fidelity. Adds a read-only audit query and a runbook for reviewing and correcting historical mis-bookings via staged storno with explicit approval; no automated bulk mutation. Fixes #1001 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e40dc64485
commit
8299ee9fb4
@@ -247,6 +247,43 @@ describe('bulkBookMatchedInboxItems: booking', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('books against the linked cash account instead of the 1930 mapping default', async () => {
|
||||
const supabase = queuedSupabase([
|
||||
{ data: { id: 'i1', matched_transaction_id: 'tx-1', created_journal_entry_id: null, created_supplier_invoice_id: null } },
|
||||
{
|
||||
data: {
|
||||
id: 'tx-1',
|
||||
date: '2026-06-01',
|
||||
amount: -700.28,
|
||||
currency: 'SEK',
|
||||
cash_account_id: 'cash-1',
|
||||
journal_entry_id: null,
|
||||
},
|
||||
},
|
||||
{ data: { entity_type: 'aktiebolag', fiscal_year_start_month: 1 } },
|
||||
{ data: { ledger_account: '1931' } },
|
||||
{ data: [{ id: 'fp-1' }] },
|
||||
{ error: null },
|
||||
{ data: [] },
|
||||
])
|
||||
|
||||
const { booked, skipped } = await bulkBookMatchedInboxItems(supabase, 'u1', 'c1', {
|
||||
item_ids: ['i1'],
|
||||
category: 'expense_software',
|
||||
})
|
||||
|
||||
expect(skipped).toEqual([])
|
||||
expect(booked).toHaveLength(1)
|
||||
expect(mockCreateJE).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'c1',
|
||||
'u1',
|
||||
expect.objectContaining({ id: 'tx-1', cash_account_id: 'cash-1' }),
|
||||
expect.objectContaining({ debit_account: '5420', credit_account: '1931' }),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards the shared dimensions bag onto every booked mapping result', async () => {
|
||||
// Fresh mapping object per call: the core mutates it in place, and a
|
||||
// shared fixture would leak dimensions across tests.
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import { eventBus } from '@/lib/events'
|
||||
import { buildMappingResultFromCategory } from '@/lib/bookkeeping/category-mapping'
|
||||
import { applySettlementAccount } from '@/lib/bookkeeping/mapping-engine'
|
||||
import { resolveSettlementAccount } from '@/lib/bookkeeping/settlement-account'
|
||||
import { createTransactionJournalEntry } from '@/lib/bookkeeping/transaction-entries'
|
||||
import { upsertCounterpartyTemplate } from '@/lib/bookkeeping/counterparty-templates'
|
||||
import { isBookkeepingError } from '@/lib/bookkeeping/errors'
|
||||
@@ -329,9 +331,16 @@ export async function categorizeMatchedTransaction(
|
||||
const entityType: EntityType = (settings?.entity_type as EntityType) || 'enskild_firma'
|
||||
const fiscalYearStartMonth = settings?.fiscal_year_start_month ?? 1
|
||||
|
||||
const mappingResult = buildMappingResultFromCategory(
|
||||
let mappingResult = buildMappingResultFromCategory(
|
||||
category, transaction as Transaction, isBusiness, entityType, vatTreatment, vatAmount
|
||||
)
|
||||
const settlementAccount = await resolveSettlementAccount(
|
||||
supabase,
|
||||
companyId,
|
||||
transaction.cash_account_id,
|
||||
log,
|
||||
)
|
||||
mappingResult = applySettlementAccount(mappingResult, settlementAccount)
|
||||
// Dimensions PR7: tag the business lines of the generated verifikat.
|
||||
if (dimensions && Object.keys(dimensions).length > 0) {
|
||||
mappingResult.dimensions = dimensions
|
||||
|
||||
Reference in New Issue
Block a user