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>
Whole-krona Bankgiro/Swish payments of öre-bearing invoices were stranded
as partially_paid forever (e.g. 11 231 paid on an 11 231,25 invoice left
0,25 kr open). Book the sub-krona residual to BAS 3740 (Öres- och
kronutjämning) and settle the invoice in full, on both the supplier- and
customer-invoice match flows.
New shared pure helpers buildSupplierPaymentClearingLines +
planSupplierPayment mirror the customer-side primitives; routing preview
and commit through the same builder also fixes two pre-existing
preview↔commit drifts (payment account + line descriptions). Öre
absorption is accrual-only — cash entries book the full invoice, so
absorbing there would hide a 1930 discrepancy.
Also improves supplier-invoice ↔ bank matching:
- Pass-3 date window now spans [invoice_date-5, due_date+5] instead of
due_date ±5, so early payments auto-match; an ambiguity guard demotes
non-unique amount matches to suggestions.
- New retroactive matcher (on supplier_invoice.registered/.approved)
surfaces the settling bank payment when the invoice is registered after
the payment was imported. Matches are written as suggestions for
one-click confirm-to-book, never silently auto-booked.
Tests: new unit tests for both pure helpers; extended matching, handler,
customer öre, and route suites. Full suite green (407 files / 5364 tests).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(invoices): reject overpayment on all invoice-match paths (audit C3)
The paid/remaining math was copy-pasted across three sites; the dashboard match-invoice route guarded against overpayment but the v1 public API route and the agent/MCP commitMatchTransactionInvoice had drifted WITHOUT it — silently accepting payment > remaining (recording paid_amount > total, over-crediting AR; cleanup needs storno, not edit).
- New lib/invoices/apply-invoice-payment.ts planInvoicePayment(): single source of the paid/remaining/status math + overpayment guard, via canonical roundOre (@/lib/money, guard rail #9). FX-agnostic — caller passes the invoice-currency amount.
- All three sites delegate; the guard runs BEFORE journal-entry creation so a rejected match never burns a voucher number. Dashboard behaviour unchanged (faithful extraction — its existing overpayment test still passes, the equivalence anchor). v1 returns MATCH_AMOUNT_EXCEEDS_REMAINING; commit returns the same registry message at 400.
- Removes 7 hand-rolled Math.round(x*100)/100 sites; antipattern guard ratchets 668 -> 661.
- Unit tests for the helper (overpayment rejection, half-öre tolerance, remaining_amount fallback).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* review: run overpayment guard before the storno (PR #647)
greptile: in commit.ts and the v1 route the conflicting-JE storno ran BEFORE the new guard, so a rejected overpayment would still reverse the transaction's prior JE and null its journal_entry_id — a side effect on a rejected match. Move planInvoicePayment above the storno so a rejection leaves the transaction fully untouched. (The dashboard route's pre-existing storno-before-guard ordering is FX-entangled and unchanged here; noted as a follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>