ec27228a8e
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
/**
|
|
* Centralised predicate for "is this bank transaction anchored to a
|
|
* verifikat?": single source of truth that readers across the inbox,
|
|
* history list, and MCP filters use to decide whether a tx is unbooked
|
|
* (needs categorisation) vs already attached to a journal entry.
|
|
*
|
|
* Three storage locations to consider, all of which can independently
|
|
* make a tx "booked":
|
|
*
|
|
* 1. transactions.journal_entry_id: the 1:1 case (single tx → single
|
|
* verifikat via categorisation, match-invoice, or match-supplier-invoice).
|
|
*
|
|
* 2. invoice_payments / supplier_invoice_payments: the multi-allocation
|
|
* case (PR #603's match_batch_allocate). One tx with multiple payment
|
|
* rows pointing at the same combined verifikat; the row in transactions
|
|
* itself has journal_entry_id = NULL because no single invoice ID
|
|
* captures the full picture.
|
|
*
|
|
* 3. transaction_voucher_links: the N-tx-to-1-JE case (the bulk-book
|
|
* flow). Same combined verifikat, multiple bank lines, each tx's row
|
|
* in transactions has journal_entry_id = NULL for N>1.
|
|
*
|
|
* If a reader only checks `tx.journal_entry_id`, every multi-tx and
|
|
* multi-allocation case falsely shows as "unbooked" and would re-surface
|
|
* in the inbox or hide the "Open verifikat" affordance. Use this helper
|
|
* to avoid that.
|
|
*
|
|
* The Postgres mirror is `public.is_transaction_booked(uuid)`
|
|
* (migration 20260529120000_transaction_voucher_links.sql): same
|
|
* predicate, three storage locations, in SQL.
|
|
*/
|
|
|
|
interface TxLike {
|
|
id: string
|
|
journal_entry_id: string | null
|
|
}
|
|
|
|
interface PaymentLike {
|
|
transaction_id: string | null
|
|
}
|
|
|
|
interface VoucherLinkLike {
|
|
transaction_id: string
|
|
}
|
|
|
|
/**
|
|
* @param tx - the bank transaction row (must include `journal_entry_id`)
|
|
* @param payments - rows from invoice_payments AND supplier_invoice_payments
|
|
* filtered to ones whose transaction_id might equal tx.id.
|
|
* May be empty if the reader didn't fetch them.
|
|
* @param voucherLinks - rows from transaction_voucher_links filtered to ones
|
|
* whose transaction_id might equal tx.id. May be empty.
|
|
*/
|
|
export function isTransactionBooked(
|
|
tx: TxLike,
|
|
payments: PaymentLike[] = [],
|
|
voucherLinks: VoucherLinkLike[] = [],
|
|
): boolean {
|
|
if (tx.journal_entry_id != null) return true
|
|
if (payments.some((p) => p.transaction_id === tx.id)) return true
|
|
if (voucherLinks.some((v) => v.transaction_id === tx.id)) return true
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Resolve the "primary" journal_entry_id to link to from the UI when a
|
|
* tx has multiple anchoring rows. Order of precedence:
|
|
*
|
|
* 1. tx.journal_entry_id (the 1:1 case, always the right answer)
|
|
* 2. First voucher-link row (multi-tx bulk-book points all txs at one JE)
|
|
* 3. First payment row (multi-allocation puts each invoice on its own
|
|
* payment row but they all share the combined verifikat)
|
|
*
|
|
* Returns null if none of the three are present, in which case the tx
|
|
* is not booked at all.
|
|
*/
|
|
export function getPrimaryJournalEntryId(
|
|
tx: TxLike,
|
|
payments: { transaction_id: string | null; journal_entry_id: string | null }[] = [],
|
|
voucherLinks: { transaction_id: string; journal_entry_id: string }[] = [],
|
|
): string | null {
|
|
if (tx.journal_entry_id != null) return tx.journal_entry_id
|
|
const link = voucherLinks.find((v) => v.transaction_id === tx.id)
|
|
if (link) return link.journal_entry_id
|
|
const payment = payments.find((p) => p.transaction_id === tx.id && p.journal_entry_id != null)
|
|
return payment?.journal_entry_id ?? null
|
|
}
|