* fix(customers): make country ISO-2 everywhere and check it against the customer type (#2025, #2028)
customers.country and suppliers.country were read as ISO codes by the
periodisk sammanstallning (SKV 5740), Peppol and the provider importers but
written as English names by the customer form and the v1 API, so a correct
German customer produced GERMANY811234567 in the SKV file plus two false
warnings, and an EU customer saved with land Sverige got reverse charge with
nothing objecting until after the invoice was sent.
- lib/vat/country-codes.ts: one helper that normalises codes and the
Swedish/English names the writers used to store, the country-vs-type
rule (swedish_business = SE, eu_business = EU member other than SE that
matches the VAT prefix, non_eu_business = outside the EU), and the
reverse-charge country gate.
- Writers: customer form and supplier form get a country select; internal
REST, v1 REST, bulk-create, MCP create/update, CSV/Excel import and the
provider migration mapper normalise to a code and refuse unknown text;
the consistency rule is a form error and an API 400
(CUSTOMER_COUNTRY_MISMATCH on update). An omitted country is SE for
Swedish types, derived from the VAT prefix for eu_business, required
for non_eu_business.
- vat-rules.ts: getVatRules and friends take the country as a third
argument and grant reverse charge only for an EU country other than SE;
every invoice/sales-order/MCP call site passes customer.country.
- periodisk sammanstallning reads legacy names through the same helper.
- Migration 20260903170000: normalize_country_code() SQL twin, country_raw
rollback column on both tables, backfill of every non-code row; unknown
text is left as-is. pg-real test for the function.
Closes#2025, closes#2028
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5EmmndLyDCmY5NHYAvYkE
* fix(customers): keep reverse charge for defaulted-SE EU rows, gate the country rule on the fields it reads, fix build
Skeptic and CI findings on #2241, one pass:
- Migration step 4: eu_business rows whose country was null or only the old
writer default (SE) while the VAT number names another EU member take the
country from the prefix. The pre-2026-09 rules granted reverse charge on
type + VIES validation alone, so these rows invoiced at 0% and would have
flipped to 25% on the next invoice. country_raw = '' marks a null origin;
rollback uses nullif(country_raw, '').
- countryPermitsReverseCharge refuses SE only: a VIES-validated number
outweighs a non-EU address (Swiss company registered in DE, Monaco with a
FR number, Northern Ireland XI).
- checkCountryConsistency: an eu_business outside the EU VAT area is
accepted when the VAT prefix is an EU-trade registration (incl. XI);
Monaco maps to the FR prefix.
- Internal PATCH, MCP update and the commit executor judge the country rule
only when customer_type, country or vat_number is part of the update, so
a contradictory legacy row can still change its email (v1 already did).
- Webshop-order customers get the order's billing country; spreadsheet
import derives a missing country from the type and flags contradictions
(parser row error + execute schema refine).
- Build: v1 [id] route typed the existing row through a narrowed alias
(never) and passed messageSv/messageEn the v1 error context lacks; the
self-billed customer projection lacked country.
- Checks: regenerated skills/accounted-api (customer example country SE).
- New parity test holds the migration's SQL name table to the TS table.
- DECISIONS.md: correct migration version and the revised rule.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5EmmndLyDCmY5NHYAvYkE
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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>
* feat(import): add customer and supplier parsing functionality
- Implemented customer file parsing in `lib/import/customers/parser.ts` with support for Excel and CSV formats.
- Created types for detected customer columns and parsed customer rows in `lib/import/customers/types.ts`.
- Added tests for customer classification logic in `lib/import/shared/__tests__/classify.test.ts`.
- Developed classification functions for customers and suppliers in `lib/import/shared/classify.ts`.
- Introduced shared column utility functions in `lib/import/shared/column-utils.ts`.
- Implemented supplier file parsing in `lib/import/suppliers/parser.ts` with validation for various fields.
- Created types for detected supplier columns and parsed supplier rows in `lib/import/suppliers/types.ts`.
- Added tests for supplier column detection and parsing in `lib/import/suppliers/__tests__/column-detector.test.ts` and `lib/import/suppliers/__tests__/parser.test.ts`.
* fix(labels): update 'Svenskt företag' to 'Svenskt företag eller organisation' for clarity
* feat(import): refactor encoding handling for Swedish files and add tests for character preservation
* feat(recapt): implement clearRecaptIdentity function and integrate into logout flow
* feat(bookkeeping): implement copy functionality and next voucher sequence retrieval
* feat(import): enhance customer and supplier import functionality with normalization and event handling