Fixes#1168. articles.currency exists in the DB and REST API, but the
staged-operation schemas and the two MCP tools had no currency param,
so agent-created articles were always SEK and an agent asked to create
an EUR-priced article could not.
- CreateArticleParamsSchema/UpdateArticleParamsSchema accept an
optional ISO 4217 code (normalized to upper case; empty/null =
unset). The currencies-table FK stays the allow-list: a 23503 on the
currency FK maps to a clear 400 instead of a raw 500.
- commitCreateArticle inserts currency ?? 'SEK'; the sparse update
executor passes it through only when staged.
- gnubok_create_article / gnubok_update_article expose the param.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- invoice/article posting-account overrides accept active class 1-3 accounts;
class 1-2 (balance-sheet) accounts are rejected on VAT-bearing lines so the
ruta 05 tax base always books to a 3xxx account
- shared posting-account regex across server schemas, pending-operation
re-validation, and client forms
- share-capital settings (aktiekapital/antal_aktier) feed the annual-report
note; kvotvarde derived per ABL 1 kap 6 $; all-or-nothing pair constraint
- signed per-rate VAT breakdown on credit-note PDFs; U+2212 to ASCII hyphen
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp): kontoplan account tools + verifikat notes exposure
Two gaps reported by an MCP-driven user: no account management in the
API, and verifikat notes invisible to agents (they exist in the product
but MCP could neither read nor write them).
- add staged gnubok_create_account / gnubok_update_account (BAS 2026
prefill for catalog numbers; rename/VAT-default/SRU/activate via
update; both LOW risk reference data)
- add staged gnubok_set_voucher_note (notes-only annotation, legal on
posted entries per the 20260608120000 trigger carve-out) and return
entry_notes from gnubok_query_journal
- new pending_operations types create_account / update_account /
set_voucher_note (CHECK migration + validate companion, applied to
staging)
- tools/list payload ceiling 54K -> 56K (documented; wire contract,
descriptions trimmed first)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skatteverket): unstick BankID connect flow and stale connection views
- respond to the OAuth callback immediately and run the post-connect
refresh after the response (next/server after()): users no longer
stare at Skatteverket's consumed consent page for up to 40s
- open the consent flow in a full tab instead of a 600x750 popup that
hid the approve button below the fold
- disable connect buttons while the OAuth tab is open (parallel flows
overwrote oauth_state + the PKCE verifier) and recover via a
closed-tab watcher plus a delayed status refetch
- persist MISSING_SCOPE token health from the post-connect sync and
show an actionable "approve all permissions" notice
- refetch connection state on tab visibility (settings connect panel,
enable-banking panel, /skattekonto) so a connect completed in another
tab or after a mobile app-switch shows up without a manual reload;
fix /skattekonto never clearing its not-connected state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(article-form): add article number field with validation to ArticleForm
* feat(account): enforce account type consistency with BAS class and add validation
---------
Co-authored-by: Claude Fable 5 <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(dimensions): PR6 retro-tagging — audited retag carve-out, workbench, staged MCP tool
Tier-2 retro-tagging (founder decision №1, approved 2026-07-02): posted
entries in OPEN periods can have their dimension tags changed through ONE
audited path — everything about the verifikat itself stays immutable.
Carve-out (migration 20260702170000): the line-immutability trigger gains a
single narrow branch — while the transaction-local GUC set by the RPC is
active, an UPDATE of a posted line is admitted iff every non-dimension
column is unchanged, enforced by a whole-row to_jsonb diff (any future
column is protected by construction; mirrors cost_center/project are in the
changeable set because they are derived views of dimensions['1']/['6']).
Precedent: mark_entry_as_opening_balance (20260613120000).
retag_line_dimensions RPC: tenant guard (20260619130100 pattern), writer
gate (viewers rejected), posted-only, open period + company lock date
enforced, every code validated against the ACTIVE registry, immutable
dimension_retag_log row (before/after/actor/reason, INSERT-only via its own
trigger, no FKs so the trail survives hard-deletes) written BEFORE the
carve-out UPDATE. Idempotent no-op without a log row. Untag ({}) supported.
Legal position per the plan: dimensions are internredovisning metadata, not
BFL 5 kap 7§ verifikat content — this is strictly more conservative than
Fortnox/Visma (dimension-only diffs, open periods only, immutable log,
storno past locks — Tier 3 has no exceptions).
Mandatory pg suite (11 tests): GUC-less updates still blocked; amounts/
description can never change even under the GUC (transaction-local);
closed/locked/lock-date, role, registry, draft and cross-tenant rejections;
log immutability; gnubok.allow_delete bulk path unaffected.
UX (all writes through the ONE RPC): pencil on posted-voucher lines in
bookkeeping/[id] ("Påverkar endast internredovisningen, inte verifikatet")
+ retag-history card; BulkTagWorkbench at /dimensions/tagging (filters,
shift-select, merge vs "Ersätt tagg" replace mode, reversal-pair warning
with "Inkludera motverifikat" auto-selection, per-line failure display).
MCP: gnubok_tag_journal_lines (bookkeeping:write) — filter block resolved
via resolve-don't-select, ≤500 lines, staged via pending_operations (new
op type migration 20260702171000, medium risk tier, shared Zod validation
boundary between staging and commit; executor loops the RPC per line with
partial-success aggregation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dimensions): address #867 review — SQLSTATE classification, blocking storno confirm, documented divergence
- Retag route classifies RPC errors by SQLSTATE instead of message-regex:
P0001 (every rule violation in the RPC) → 409 verbatim, 42501 (tenant
guard) → 403, anything else → logged 500 with a generic message. No more
substring sniffing.
- The workbench's storno-pair warning escalates to a BLOCKING confirmation
naming the unselected counter-vouchers before apply (Srf U 14 gross
reporting — one-legged retags silently skew project P&L; the banner alone
was advisory).
- The empty-bag divergence is now documented on both schemas as intentional:
the direct dialog/workbench path allows {} (human untags phantom codes,
logged with reason), the MCP staged path rejects it (agents never
bulk-clear history).
Triage notes: the log's missing FKs are the point (behandlingshistorik must
survive undo_sie_import hard-deletes — a cascade would erase the trail);
SIE exports are generated fresh on demand, never cached, so post-retag
exports carry the new object lists automatically; date-scoped registry
values are deliberately not enforced at retag because entry creation does
not enforce them either — enforcing in one path only would be incoherent
(both belong to the PR10 rules engine).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Phase 3 of dev_docs/dimensions_implementation_plan.md. Companies with
dimensions_enabled=false see zero change; existing free-text API writers keep
working (validation is toggle-governed).
Engine (soft validation):
- validateEntryDimensions() in dimension-resolver: zero queries for untagged
entries; toggle off → passthrough; toggle on → one settings fetch + two
registry queries, rejects unknown dims/codes and archived values with
Swedish per-code messages (DimensionValidationError, 400, details.issues).
Wired into createDraftEntry + updateDraftEntry before any insert; reversal/
storno paths untouched (verbatim copies). Fails open on transient registry
errors — soft validation must never block bookkeeping.
MCP (agent write path):
- New tools: gnubok_list_dimensions, gnubok_list_dimension_values (fuse.js
fuzzy), gnubok_create_dimension_value (STAGED via pending_operations —
agents never silently mint reporting values; new op type + CHECK migration
+ executor with duplicate-idempotency).
- create_voucher/correct_entry: per-line dimensions bag + default_dimensions,
resolve-don't-select server-side (code OR natural-language name; exact →
fuzzy ≤0.30 with ≥0.15 runner-up margin; non-exact resolutions echoed with
confidence; ambiguous → ranked candidates, no auto-create).
- gnubok_get_agent_briefing gains a dimensions block (enabled, dims, top
values) — omitted when registry empty.
- TOOL_SCOPE_MAP entries; risk tier low for staged value creation.
UI:
- JournalEntryForm (manual voucher + TransactionBookingDialog embed): header
"+ Kostnadsställe/Projekt" progressive disclosure (gäller alla rader with
documented inheritance rule) + per-row tag popover + compact KS·PR badges;
gated on dimensions_enabled.
- Voucher detail: display-only dimension badges with registry-name resolution.
- EditDraftEntryDialog carries line dimensions so editing a draft no longer
strips tags.
categorize/bulk_book dims deferred to PR7 (needs the bulk_book RPC migration).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: simplify COMING_SOON_PROVIDERS to include only bjornlunden and briox
* feat: add supplier creation functionality and related operations
* feat: reorder and enhance OAuth scopes in Visma integration
* feat: implement create supplier functionality with validation and risk tier management