Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
Lägger docs/production-readiness.md och siax/production-gaps.json enligt SIAX Master Definition of Done. Underlag: git-, kod- och live-evidens 2026-11-11. Nivån är evidensbaserad, inte ett leveransgodkännande.
* fix(bookkeeping): negative item rows book on the opposite side, never as negative amounts
A supplier-invoice item with a negative line_total (an öresavrundning row on
3740, a rabatt row) was copied straight into debit_amount, producing a line
like "3740 debit -0.25". The entry balances arithmetically, so no trigger
fired, but the verifikat page renders only positive amounts: the row showed
empty and the visible debits (20 056,25) disagreed with the summa (20 056,00).
Prod holds 14 such lines: 12 supplier registrations in 3 companies, 1
customer invoice (3004 credit -0.50), 1 storno mirroring a bad original.
Why it occurred: the "one non-negative side per line" invariant lived
nowhere. Zod allows negative items (they are legitimate), the engine only
checked balance, and journal_entry_lines had no CHECK. Any producer that
aggregates user rows could repeat it.
What was removed or simplified: no new state. The privately-paid supplier
path already flipped negative buckets to credit; that rule is now one
helper (lib/bookkeeping/line-side.ts) shared by the supplier registration,
cash-method and privately-paid generators and by the customer-invoice
per-rate generator. The credit-note generator stops swapping sides and
takes |net|, since its inputs now arrive on the correct side.
Why this and not the proposed fix: patching only the supplier generator
leaves MCP, templates and future producers free to repeat the class, and
rejecting negative items at input would break real rabatt/avrundning rows.
So the sign is fixed at three levels: producers flip the side, the engine
refuses negative amounts before any write (JOURNAL_LINE_NEGATIVE_AMOUNT,
Swedish message), and a NOT VALID CHECK on journal_entry_lines rejects new
rows regardless of the writer. reverseEntry swaps on the net so a legacy
negative line stornos into a well-formed line before the data repair runs.
The 14 existing prod lines are repaired by a separate founder-approved SQL
(flip to the opposite column, net unchanged); VALIDATE CONSTRAINT follows
in a later migration once prod reports zero offending rows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMJvTFitzKQYuv7ABUVFj9
* fix(bookkeeping): anchor foreign-currency 1510/1930 on the net of the revenue lines; flip salary buckets by side
Skeptic findings on ab119d6ed:
1. A non-SEK customer invoice with a negative row (rabatt, avrundning on a
separate revenue account) now lands that row on the debit side, but the
1510 (accrual) and 1930 (kontantmetod) anchors summed only credit_amount,
so the entry was overstated by the row and threw "Verifikationen
balanserar inte". Both anchors now use credit - debit. EUR test added for
both paths.
2. Salary: arbetsgivaravgifter, semesteravsättning, pension and SLP buckets
copied bucket.amount into debit_amount and the aggregated liability into
credit_amount. A negative month (unpaid leave beyond gross) produced
7510 D -628,40, which the engine now refuses. Buckets and liabilities go
through debitNatural/creditNatural so a negative month books 7510 K /
2731 D. Test added.
3. replaceOpeningBalanceEntry, the third engine write path, now runs the
same non-negative guard as createDraftEntry and updateDraftEntry.
4. The credit-note comment claimed |net| is side-correct for every original;
it is not for originals with a negative row (pre-existing, callers negate
items with -Math.abs). Comment now states the actual behaviour and the
known gap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(bookkeeping): map JOURNAL_LINE_NEGATIVE_AMOUNT to a structured 400; supplier anchors flip side when the invoice nets below zero
CodeRabbit on #2439:
- JournalLineNegativeAmountError was not registered in isBookkeepingError /
bookkeepingErrorResponse, so the journal-entry routes would have returned
a generic 500 instead of the structured 400 with code and details. Added,
with a test.
- The three supplier balance anchors (2440 on registration, the payment
account under kontantmetoden, the liability account for privately paid
invoices) were fixed-credit lines. An invoice whose rows net below zero
(a leverantörskreditfaktura keyed in as an invoice) produced a negative
credit there, which the engine now refuses. The anchors go through
creditNatural so such an invoice books 2440 D, as a supplier credit note
would. Tests for all three paths.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
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>
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(mcp): update workflow descriptions for transaction categorization and approval processes
* feat: implement account validation for transaction categorization to handle inactive accounts
* fix(tests): stub findMissingAccountsMock to ensure no missing accounts during batch-categorize tests
* fix(errors): ensure deterministic sorting of account numbers in AccountsNotInChartError
- Introduced new error classes for better error categorization:
- JournalEntryNotBalancedError
- FiscalPeriodNotFoundError
- EntryDateOutsideFiscalPeriodError
- JournalEntryNotFoundError
- CannotReverseNonPostedError
- CannotCorrectNonPostedError
- EntryAlreadyReversedError
- CurrencyRevaluationAlreadyExistsError
- InvalidMappingResultError
- BookkeepingDatabaseError
- Updated existing functions in engine.ts and transaction-entries.ts to throw specific errors instead of generic ones.
- Enhanced error response handling in get-error-message.ts to provide localized messages for new error types.
- Added unit tests for new error classes and error handling functions to ensure correctness and coverage.