feat(reconciliation): propose the explaining voucher set for a bank row before offering Bokför (#2359)
* feat(reconciliation): propose the explaining voucher set for a bank row before offering Bokför (#2293) The bridge table said "ej matchad" and steered to Bokför when a Bankgirot aggregate was already booked as two or three unlinked vouchers. The booking doors have refused that double booking since #2300 and #2346 with detectExplainingVoucherSet; the view never ran it. - duplicate-payment-detection: split the set detector into fetch and pure steps and add detectExplainingVoucherSets, the batch form (one ledger scan, one anchor lookup, per-row verdict identical to the single detector; a voucher explains at most one row per call). ExplainingVoucher now also carries voucher_series and voucher_number. - reconciliation/covering-set-candidate (new): maps sets to proposals (0.95 same date, 0.85 within seven days), SEK accounts only, fails open. - items: open bank rows nothing explains 1:1 are searched before they land in unmatched_external; a hit lands in proposed with proposal.vouchers. - schemas: ReconciliationProposal.vouchers (optional, set proposals only). - AccountOverview: "= A57 + A58" with the legs' amounts, one Koppla that posts every voucher as a 1:N pair to the existing links route. - i18n: reconciliation.proposal_set_title and proposal_set_same_day (sv, en). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvMaHcTnwAfxzgYD1fGYX1 * chore(skill): regenerate accounted-api for ReconciliationProposal.vouchers (#2293) The set proposal field added to the reconciliation items response shape flows into the generated agent skill; regenerated with `npm run apiskill:generate`, which changes one line of references/banking.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvMaHcTnwAfxzgYD1fGYX1 --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
Jakob Wennberg
parent
fcda4a75dd
commit
6776cb4fc6
@@ -2,10 +2,12 @@ import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import { roundOre } from '@/lib/money'
|
||||
import { fetchJunctionLinkedTxIds, fetchUnlinkedGLLines, scopeTransactionsToAccount } from './bank-reconciliation'
|
||||
import { getSkattekontoReconciliationStatus } from './skattekonto-reconciliation'
|
||||
import { proposeCoveringSets } from './covering-set-candidate'
|
||||
import {
|
||||
parseAccountKey,
|
||||
type ReconciliationItem,
|
||||
type ReconciliationItemBucket,
|
||||
type ReconciliationProposal,
|
||||
} from './schemas'
|
||||
|
||||
/**
|
||||
@@ -160,13 +162,30 @@ export async function listAccountItems(
|
||||
companyId,
|
||||
rows.filter((tx) => !tx.journal_entry_id && !tx.is_ignored).map((tx) => tx.id),
|
||||
)
|
||||
// Rows nothing explains 1:1 are searched for a set of unlinked verifikat
|
||||
// summing exactly to them (#2293) before they are offered as unmatched:
|
||||
// "Bokför" is the door only when the ledger has nothing for the row.
|
||||
const coveringSets = buckets.some((b) => b === 'proposed' || b === 'unmatched_external')
|
||||
? await proposeCoveringSets(
|
||||
supabase,
|
||||
companyId,
|
||||
account,
|
||||
rows
|
||||
.filter(
|
||||
(tx) =>
|
||||
!tx.is_ignored && !tx.journal_entry_id && !junctionLinked.has(tx.id) && !tx.potential_journal_entry_id,
|
||||
)
|
||||
.map((tx) => ({ id: tx.id, date: tx.date, amount: Number(tx.amount), currency: tx.currency })),
|
||||
)
|
||||
: new Map<string, ReconciliationProposal>()
|
||||
{
|
||||
for (const tx of rows) {
|
||||
const coveringSet = coveringSets.get(tx.id) ?? null
|
||||
const bucket: ReconciliationItemBucket = tx.is_ignored
|
||||
? 'ignored'
|
||||
: tx.journal_entry_id || junctionLinked.has(tx.id)
|
||||
? 'matched'
|
||||
: tx.potential_journal_entry_id
|
||||
: tx.potential_journal_entry_id || coveringSet
|
||||
? 'proposed'
|
||||
: 'unmatched_external'
|
||||
if (!buckets.includes(bucket)) continue
|
||||
@@ -191,7 +210,7 @@ export async function listAccountItems(
|
||||
confidence: Number(tx.potential_match_confidence ?? 0.75),
|
||||
reasons: [tx.potential_match_method ?? 'föreslagen av matcharen'],
|
||||
}
|
||||
: null,
|
||||
: coveringSet,
|
||||
actions:
|
||||
bucket === 'matched'
|
||||
? ['unmatch']
|
||||
|
||||
Reference in New Issue
Block a user