Files
accounted/extensions/general/mcp-server/skills/bank-reconciliation.ts
T
Jakob WennbergandClaude Sonnet 5 ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
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>
2026-07-04 15:58:06 +02:00

143 lines
8.4 KiB
TypeScript

import type { Skill } from './types'
const body = `# Bank Reconciliation: Accounted
Reconcile the company's bank statements against the bookkeeping ledger so that the cash position in the books matches the bank. Run at month-end and before VAT close.
## When to use
- "Stäm av banken"
- "Reconcile the bank account"
- "Why doesn't my 1930 balance match the bank?"
- Mid-month spot-check before tax filings
## Choosing the right matching/linking tool
Accounted has several reconciliation tools because the *right* one depends on what
you already have in hand. Decide along two axes (**what you're connecting** and
**whether a verifikat already exists**) then pick:
| You have… | …and the entry is | Use |
|---|---|---|
| One bank tx + one customer invoice | not yet booked | \`gnubok_match_transaction_to_invoice\` |
| One bank receipt covering many invoices (customer **or** supplier) | not yet booked | \`gnubok_match_batch_allocate\` (samlingsbetalning, BFL 5 kap 6§) |
| A whole period of unmatched income to clear | not yet booked | \`gnubok_auto_match_period\` (run \`dry_run=true\` first) |
| One bank tx whose affärshändelse you already posted manually | already booked | \`gnubok_link_transaction_to_journal_entry\` |
| A customer invoice you know is paid, payment already in a verifikat | already booked | \`gnubok_find_voucher_candidates_for_invoice\` → \`gnubok_link_invoice_to_voucher\` |
| A supplier invoice paid, payment already in a verifikat | already booked | \`gnubok_find_voucher_candidates_for_supplier_invoice\` → \`gnubok_link_supplier_invoice_to_voucher\` |
| A receipt/document to file against a tx (no new bokföring) | n/a | \`gnubok_attach_document_to_transaction\` |
| Income you're sure is paid but with no bank line to point at | not yet booked | \`gnubok_mark_invoice_as_paid\` |
Rule of thumb: **match_\*** creates the payment bokföring; **link_\*** attaches to
bokföring that already exists (no new verifikat). Every one of these stages a
pending operation: the user approves before anything posts.
### Kontantmetoden vs faktureringsmetoden
The settlement posting differs by the company's \`accounting_method\` (read it from
\`gnubok_get_agent_briefing\`: \`accrual\` = faktureringsmetoden, \`cash\` =
kontantmetoden; null defaults to accrual):
- **Faktureringsmetoden (accrual):** revenue was booked at invoice time against
kundfordran **1510**. Payment **credits 1510** and debits the bank (19xx). The
link/match tools settle 1510.
- **Kontantmetoden (cash):** no receivable was raised at invoice time. Payment
**debits 19xx** and books the revenue + moms now.
You usually don't pass the method explicitly: the tools resolve it from company
settings, but knowing it lets you sanity-check the staged preview's accounts
before approving (1510 movement under accrual; revenue/moms under cash).
## Workflow
### Step 1: Pull the reconciliation status
\`gnubok_get_reconciliation_status\` returns the current state per bank account:
matched count, unmatched count, latest bank balance, and ledger balance for the
asset account (typically 1930). If \`unmatched_count = 0\` and the balances agree,
the account is reconciled: skip to Step 5.
### Step 2: Categorize the uncategorized side
Unmatched usually means *bank rows without journal entries* (incoming PSD2
transactions) AND *journal rows without bank lines* (manually-posted entries
the agent might already have created via \`gnubok_create_voucher\`).
For the bank-side gap:
1. \`gnubok_list_uncategorized_transactions(limit=20)\`. Page through if needed.
2. For each, decide: is this an income payment for a known invoice, or an expense?
3. **Income** that matches an open invoice: \`gnubok_match_transaction_to_invoice\`: keeps AR clean.
4. **Income** without a matching invoice (refund, deposit, owner contribution): \`gnubok_categorize_transaction\` with the appropriate category.
5. **Expense**: \`gnubok_suggest_categories\` first (uses counterparty templates + history): accept the top suggestion if confidence is high. Otherwise pick from the category list manually.
6. **Owner draw / private withdrawal** (EF only): category \`private\` posts to 2013.
Each call stages a pending operation: the user approves in the web app.
### Step 3: Resolve duplicates / dead entries
If the bank shows a transaction that was already booked (e.g. manually entered via \`gnubok_create_voucher\`):
- \`gnubok_match_transaction_to_invoice\` won't help here.
- The cleanest path: reverse the manual entry via \`gnubok_reverse_journal_entry\` (storno) and then categorize the bank transaction normally so the trail is "bank → ledger" rather than "ledger → bank → orphan".
If the ledger shows a phantom entry with no bank counterpart (a payment that
was never actually sent), it must be reversed: \`gnubok_reverse_journal_entry\`.
### Step 4: Re-check status
\`gnubok_get_reconciliation_status\` should now report \`unmatched_count = 0\`
and matching balances. If the balances still disagree:
- A historical opening balance might be wrong. Compare \`gnubok_get_trial_balance\` for the period start against the bank's opening statement.
- An entry might sit in a closed period that wasn't fully reconciled before locking. Check \`gnubok_list_fiscal_periods\` and walk backwards.
- FX accounts (non-SEK) need revaluation before period close: \`gnubok_run_currency_revaluation\`.
### Step 5: Document the reconciliation
For each fiscal period, Accounted stores the reconciliation state automatically.
For a printed audit trail (BFL 8 kap), generate the supplier ledger and AR
ledger after reconciliation:
- \`gnubok_get_ar_ledger\`: open customer balances should match unpaid invoices
- \`gnubok_get_supplier_ledger\`: open supplier balances should match unpaid leverantörsfakturor
## Critical rules
- **Never delete a bank transaction**, even if it looks wrong. The PSD2 feed is the source of truth. If the bank made a mistake, the bank reverses it via a new transaction.
- **Never edit a posted entry to "make it match"**: use \`gnubok_correct_entry\` or \`gnubok_reverse_journal_entry\`. BFL 5 kap 5 § forbids in-place edits.
- **Reconcile BEFORE locking the period.** Lock-period sealed-off audit can't be re-opened cheaply (it requires \`gnubok_unlock_period\` + storno + relock).
- **FX accounts must be revalued** at period close. Pre-revaluation reconciliation can show a phantom mismatch that disappears after revaluation: don't chase it.
## Common errors
- *"Balances disagree by exactly N"*: usually a single rounding entry at year-end (öresavrundning, 3741/7741) wasn't booked. Verify with \`gnubok_get_general_ledger\` filtered to the rounding accounts.
- *"Unmatched count is 0 but balances disagree"*: opening balance issue. Check the previous period's UB matches this period's IB via \`gnubok_get_trial_balance(period=prev)\` vs \`gnubok_get_trial_balance(period=current, opening=true)\`.
- *"Same transaction shows twice"*: either two PSD2 feeds (manual + Enable Banking) imported the same row, or the user manually created a voucher AND the bank imported the row. Reverse the duplicate via \`gnubok_reverse_journal_entry\`.
## Tools
- \`gnubok_get_reconciliation_status\` (single source of truth: call first and last)
- \`gnubok_list_uncategorized_transactions\`
- \`gnubok_suggest_categories\`
- \`gnubok_categorize_transaction\`
- \`gnubok_match_transaction_to_invoice\`, \`gnubok_match_batch_allocate\` (one receipt → many invoices)
- \`gnubok_auto_match_period\` (bulk matcher with confidence thresholds: use for big backlogs)
- \`gnubok_link_transaction_to_journal_entry\`, \`gnubok_link_invoice_to_voucher\`, \`gnubok_link_supplier_invoice_to_voucher\` (attach to an existing verifikat: no new bokföring)
- \`gnubok_find_voucher_candidates_for_invoice\`, \`gnubok_find_voucher_candidates_for_supplier_invoice\` (read-only: run before the link_\* tools)
- \`gnubok_attach_document_to_transaction\` (file a receipt against a tx)
- \`gnubok_reverse_journal_entry\` (storno)
- \`gnubok_run_currency_revaluation\` (FX accounts only)
- \`gnubok_get_trial_balance\`, \`gnubok_get_ar_ledger\`, \`gnubok_get_supplier_ledger\` (verification)
`
export const bankReconciliationSkill: Skill = {
slug: 'bank-reconciliation',
name: 'Bank Reconciliation',
summary: 'Stämma av banken: pick the right match/link tool, categorize PSD2 rows, handle kontant- vs faktureringsmetoden, resolve duplicates, verify with ledger reports.',
tags: ['monthly', 'reconciliation', 'bank', 'verification', 'matching'],
body,
tier: 'workflow',
applicability: { entity_type: 'both' },
}