fea5dfd1f912ec1111cec662cd8362f9aa55dfd9
38 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
70845edf69 |
feat(transactions): structured transaction_method instead of channel-in-the-name (#1459)
* feat(transactions): structured transaction_method instead of channel-in-the-name
Swedish bank feeds embed the payment channel in the description string
("Vercel Jul Överföring via internet", "ANTHROPIC* ... Kortköp/uttag"):
the PSD2 remittance array is joined into one string and the ISO 20022
type codes were dropped at insert. This promotes the channel to data:
- transactions.transaction_method (text + CHECK closed vocabulary: card,
transfer, bankgiro, plusgiro, swish, autogiro, e_invoice, international,
deposit, withdrawal, salary, fee, interest, adjustment) plus verbatim
bank_transaction_code / proprietary_bank_transaction_code evidence
columns (data_quality_master Appendix B "Layer-A capture").
- classifyTransactionMethod() in lib/transactions/transaction-method.ts:
explicit source method (Stripe txn.type) > trailing Swedish channel
phrase > ISO 20022 family/subfamily > proprietary-code keywords > MCC.
It also splits the clean display title off the description.
- Ingest stores the clean title as description and the full bank string
as original_description; dedup is untouched (external_id is date+öre,
the content bridge reads original_description and is prefix-based, and
a trailing strip leaves a prefix). Enable Banking passes the codes
through; the Stripe feed sets methods from its balance-txn types.
- Backfill migration classifies existing rows from the description text
(+ MCC and Stripe prefixes) and strips unedited titles; user-edited
titles are never rewritten.
- mapping-engine also matches original_description so user rules written
against the full bank text keep firing.
- UI: the inbox row shows the clean name; clicking it now folds out
"Betalsätt: Kortköp" etc. (sv/en), making every classified row
expandable.
A card purchase implies a physical receipt, a Bankgiro/e-invoice payment
implies a supplier invoice: downstream automations can now branch on the
rail instead of regexing display strings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(transactions): anchor counterparty-template identity on original_description
Audit follow-up to the phrase-strip change: counterparty template lookup
AND learning derived their key from merchant_name || description. With
the working title now stripped ("SPOTIFY AB Kortköp" -> "SPOTIFY AB"),
templates learned from the full bank string would only re-match via the
occurrence-gated single-token tier, and single-token counterparties with
fewer than 3 bookings would silently stop matching.
Both sides now read merchant_name || original_description || description:
the immutable bank original is identical across eras (and across user
renames), so every stored key and alias keeps matching exactly. Same
anchoring rationale as buildMerchantHistory in category-suggestions.
Existing tests that relied on the fixture's default original_description
now state it explicitly; two new regression tests pin the era stability
(lookup via alias on the full string, learning key derivation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(transactions): review follow-ups on method classification
- methodFromCodes: two-pass subfamily-then-family scan so a SALA/XBCT
refinement on the proprietary code beats a bare family match on the
ISO code, matching the documented precedence; pinned by a test.
- mapping-engine: regression tests for merchant/description patterns
that only match original_description, including the invalid-regex
substring fallback and the no-match default.
- Stripe: regression test for the SDK-unmodeled 'tax' balance-txn type
mapping to 'fee'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(transactions): scope method classification to feed rows + adjective guard
Multi-bank risk hardening before the backfill ships:
- Feed-row scope: classification and title stripping now require a real
import feed (import_source present, not manual/mcp), both at the
ingest boundary (USER_CREATED_IMPORT_SOURCES, now exported) and in
every backfill statement. User-authored titles like "Egen insättning"
on manual/MCP rows are never classified and never rewritten.
- Adjective guard (TS + SQL): a strip that would leave the title ending
in a possessive/scope adjective (egen/eget/privat/intern/extern ...)
is skipped, so "Egen insättning" stays whole even on bank-feed rows;
the method column still classifies (deposit).
- Unknown bank phrasings remain untouched by construction: an unmatched
phrase means no method and no rewrite, so the worst case for any bank
whose vocabulary we have not seen is the status quo.
Pinned by new unit + pg-real cases (user-created exclusion for
NULL/manual/mcp, adjective guard, feed defaults in the pg fixture).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(migrations): re-timestamp transaction_method migrations after rebase
Main gained migrations dated 20260729-20260730 (already applied to prod)
while this branch carried 20260728 versions, which would have applied
out-of-order on merge. The files have never reached prod, so renaming to
current timestamps is safe and removes any dependence on the integration's
out-of-order handling. All code/doc references updated; the pg test reads
the backfill by its new filename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(migrations): bump transaction_method versions past prod's max
Main's newest applied migration is 20260730090000 (future-leaning
timestamp), so the previous 202607300731xx rename still sorted before
prod's tail and risked a silent skip on merge-time apply. Versions are
now 20260730100000/20260730100100, strictly after everything applied to
prod. References updated; full migration stream replays clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(transactions): final review round: keyboard guard + bank_connection_id feed marker
- TransactionInboxCard: row-level Enter/Space handling now ignores events
bubbling from nested controls, so keyboard activation of Bokför / the
overflow menu is no longer cancelled by the (now much more common)
expandable row.
- Feed predicate parity with isImportedTransaction(): a live
bank_connection_id marks a feed row even when import_source is unset
(the oldest PSD2 rows predate that column), in both the ingest
classifier and every backfill statement: those legacy rows now get
classified instead of being skipped as user-created.
- pg fixture typing uses the TransactionMethod union.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(migrations): re-timestamp transaction_method migrations past prod's 20260807 tail
Prod max applied is 20260807170000 (verified by name via list_migrations);
the 20260730-stamped pair would sort before it. References in code,
tests, and DECISIONS.md updated to the new versions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(migrations): enforce, not assume, original_description preservation in the title strip
The strip UPDATE now fills a NULL original_description from the
pre-strip description in the same statement. Prod has zero such rows
(0/25,566 feed-scope rows, verified read-only), and 20260605120000's
backfill plus ingest make the NULL case unreachable on any DB that
replayed history, but the migration should not depend on that history
to avoid losing the only copy of a bank string.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: record the compliance-review triage of the backfill's booked-row title strip
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
|
||
|
|
108f348c84 |
refactor(motion): retime content entry, stop the double and one-frame animations (#1282)
The stagger is the standard content entry on every migrated page (convention 11), so its budget matters more than any single surface. It ran 500ms per item in 80ms steps, putting the 10th row at 1220ms against a 300ms UI budget, and it only defined delays for children 1-10: on any list longer than ten rows, children 11+ inherited delay 0 and arrived BEFORE the middle of the list. Now 300ms per item in 40ms steps with the delay capped at 360ms, so the tail lands at ~660ms instead of ~1220ms. The cap is on the delay, never on the animation: the `both` fill holds a child at opacity 0 until its delay elapses, so excluding rows 11+ from the animation would paint them while rows 1-10 were still invisible. Adds --ease-emphasized (the strong ease-out) rather than retiming --ease-out, which is unlayered in :root and therefore shadows Tailwind's own token: changing it would retime every ease-out utility in the app. Foldout rows in JournalEntryList, periodiseringar and TransactionInboxCard sit directly inside a staggered tbody, so expanding a verifikat fired the inherited slideUp (with an invisible pre-roll of up to 320ms) on top of the foldout's own transition. They opt out via data-no-stagger. Also: the confirmed match on Hem faded at full height and then vanished in one frame, jumping everything below it 52px; it now grid-collapses over 200ms with the gap inside the collapsing area. The assistant sheet slid nothing while the page panel animated 300ms to make room for it; it now arrives along the same edge on the same curve, gated to first mount so re-expanding a collapsed session stays instant. Chat history no longer replays 20 simultaneous 500ms page-entry slides on resume: only genuinely new messages animate. Payroll wizard segments transition their colour. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
53e343ee92 |
Bug/invalid imports (#1146)
* feat: add Accounted MCP namespace * fix(bookkeeping): stop flagging verifikat whose underlag lives on a referenced supplier invoice The missing-underlag surfaces only accepted a document directly linked to the entry, so payment verifikat for supplier invoices (doc on the registration entry per design) and entries whose doc was pinned to the bank transaction before matching were falsely flagged; opening the entry showed the referenced doc and cleared the warning client-side, and it came back on reload. - verifikat_without_documents + transactions_without_documents now treat an entry as covered when a supplier invoice referencing it (registration or payment FK, or a supplier_invoice_payments row) carries a document anchored to a journal entry (BFL 5 kap 7 paragraf hänvisning till underlag; anchoring required because the WORM deletion guards key on document_attachments.journal_entry_id) - match-supplier-invoice routes (dashboard + v1) propagate the transaction's pinned document onto the payment verifikat, mirroring the categorize route; migration backfills rows already written (open unlocked periods, company-guarded, never steals a linked doc) - /api/documents/counts, the transactions-page badges, the bulk "Inget underlag krävs" count and the push-notification scheduler share the same reference-aware predicate, so every surface agrees with the RPC - counts route validates journal_entry_ids as UUIDs (they are interpolated into a PostgREST or-filter) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): align table columns flush with page edges Collapse the checkbox gutter column to zero width and hang the hover-revealed checkbox/expand chevron in the page margins, drop the outer padding so DATUM sits flush left and STATUS flush right, and tuck the overflow-menu dots under the middle of the STATUS header. Applied to both the inbox and history tables so they stay identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(arsredovisning): tie anlaggningstillgangar note to booked depreciation The ARL 5:8 roll-forward note recomputed depreciation from its own day-based linear formula (365.25/12 month length, non-inclusive day count, linear only), drifting ~20 kr per year per asset from the ledger-driven resultat- and balansrakning and misstating non-linear methods entirely. Note figures now come from posted depreciation_schedules rows (the same source disposeAsset reverses), falling back to the engine's computeAnnualDepreciation when nothing is posted; pre-onboarding opening balances iterate prior years through the engine. Adds a note-vs-trial-balance tie-out warning (accounts 1000-1299, over 1 kr) surfaced before download. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(stripe): move connect and sync surface from settings to import page Stripe's transaction feed is a continuous import source in the same category as the PSD2 bank connection, so its connect/sync surface now lives on the import page as a source card (mode=stripe), gated "kommer snart" on hosted like before; self-hosted keeps the full panel. - Import page: Stripe card after Koppla bank, renders the existing StripeSettingsPanel via the settings-panel registry - OAuth callback and panel cleanup return to /import?mode=stripe - Settings > Betalningar retired: nav item removed, route redirects, PaymentsSettingsContent deleted, legacy ?tab=payments mapped - New import.stripe_* strings in sv+en; dead settings_nav.payments removed Crons and sync logic unchanged; payment-link settings stay in the invoicing section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(underlag): paginate missing-underlag cron and harden doc-surface queries Resolve PR review findings on bug/invalid-imports: - notification-scheduler: fetchAllRows on all 5 global reads; past 1000 rows the capped reads produced false "saknade underlag" notifications - bulk-missing: LOOKUP_CHUNK 300->150 so the twice-embedded .or() id list stays under the PostgREST URL limit - bulk-missing + transactions page: UUID-guard the .or()-interpolated id lists, matching documents/counts - match-supplier-invoice (dashboard + v1): log documentId/journalEntryId on the non-fatal doc-link warning - well-known/oauth-protected-resource: document the tool_namespace allow-list - messages/en: reword stripe_description - DECISIONS.md: record the asset ibAck tie-out and Tailwind !important calls Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tic): convert registrationDate from Unix seconds to millisecond epoch in lookup and profile tests --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be9d630347 |
feat(home): concept Hem with Att göra + Fortsätt (UI migration PR 11) (#1132)
* feat(home): concept scene 14: greeting + Att gora/Fortsatt panes Hem becomes the founder-approved two-panel layout: serif time-of-day greeting with date and company, the Att gora worklist restyled to the concept pane (eyebrow header, h-rows with count chips, hover chevrons) and a new Fortsatt pane listing in-progress work derived purely from draft state (lib/worklist/resume: journal drafts, invoice drafts/unsent, mid-lifecycle salary runs; deadline boost, cap 3, tested). A completed flow can never render as a resume row by construction: only draft-state rows are fetched. KPI tiles, revenue/expense cards and the deadline/tax widgets leave the page per dev_docs/last_session_resume.md section 8, which also prunes their fetches (journal-line YTD aggregation, unpaid totals, deadlines): the page got faster. Banners, checklist, build-assistant hero and the Skatteverket nudge survive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(home): serif pane titles for Att gora and Fortsatt Founder feedback: the uppercase eyebrow headers read as a stray font. Both pane titles are now the Hedvig display serif (text-lg) over the hairline, matching the page's heading language; the band headers inside Att gora keep their small uppercase form as grouping devices. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(home): Geist pane titles for Att gora and Fortsatt Founder call: the pane titles use the body sans (14px medium), not the display serif. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): Geist section headers + drop stale-transactions chip Founder feedback: pane/section headers (Att gora, Fortsatt, the reports groups Lopande/Bokslut/Skatt & moms etc) render in Geist sentence case instead of uppercase eyebrows or serif. The global h1-h3 display-font rule moves into @layer base so utility classes like font-sans can actually override it (unlayered element rules beat Tailwind's layered utilities: this was silently eating the override). Also removes the 'N aldre an 14 dagar' chip from the Bokfora transaktioner row and its stale-count plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): continuous nav crossfade + floating slide-over entrance The rail/full nav states now stay mounted and crossfade past each other (the inactive layer absolute, faded, nudged sideways, inert) while the aside width animates: the switch reads as one continuous motion instead of a DOM swap. The detail slide-over floats in from the right edge (slide-in-from-right-full, 300ms decelerating curve) per the concept, with a quicker ease-in exit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): make transitions and enter/exit animations actually run Two silent app-wide animation killers found while chasing 'the nav still is not smooth': 1. The codebase uses the shadcn animate-in/out vocabulary everywhere but no animate plugin was ever installed: Tailwind v4 silently dropped every such class, so popovers, dialogs, menus and the slide-over all appeared instantly. globals.css now defines the exact subset in use (accEnter/accExit keyframes + var-driven utilities), plugin-free, composing with duration/ease via --tw-duration/--tw-ease and collapsing under prefers-reduced-motion. Dialog drops its bracket-variant slide classes (zoom+fade carries the entrance). 2. The scrollbar auto-hide block's universal '* { transition: scrollbar-color ... }' was unlayered, and unlayered rules beat Tailwind's layered transition-* utilities regardless of specificity: every width/margin/color transition in the app was dead. The rule now lives in @layer base. Verified: the aside animates 248->64 over 300ms and the slide-over runs accEnter at 0.3s with the decelerating curve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): finish the rr-mask session-replay masking sweep Main's #1105 switched one amount cell from the no-op sensitive-field class to rr-mask (rrweb's built-in text-masking class). The reskinned tables introduced more sensitive-field cells; all 12 occurrences now use rr-mask so financial amounts are masked in session replays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ccf7ab81f |
feat(transactions): concept shell and row language (UI migration PR 5) (#1124)
* feat(transactions): concept shell for scene 10 (header, toolbar, footer)
- Header: title + Importera SplitButton (import guide / manual entry,
last-used mode persisted via ui_state.create_mode.transactions),
replacing the three-button row
- Toolbar in concept order: [Att bokföra/Alla seg with count chip]
[search] [Välj flera as quiet toggle] ... [source ContextPicker far
right merging the old in-list source dropdown (bank/Skatteverket)]
- Bank sync status + sync-now + Bankavstämning move to the concept's
footer status line together with the honest visible-row counter
("n att hantera")
- skv-reconnect banner and all dialogs/actions untouched
Row cards -> concept dry-table conversion follows separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept row language for the inbox cards
Rows read like the scene 10 table: date as the first tabular column,
description on one line, amount right (+income in sage), auto-detected
invoice matches as beige suggestion chips instead of primary pills,
tighter hairline rows at 13px. Every action unchanged: Bokför pill,
match/assistant/overflow menu, batch checkboxes, exit animations,
extraction status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): de-bloat rows and chrome to match the concept
- Rows carry only the essentials: date, description, amount, Bokför and
one overflow menu. The inline match and ask-assistant icon buttons
fold into the overflow menu (all actions still one click away)
- The "n nya banktransaktioner sen ditt senaste besök" banner is removed;
the footer sync line covers it
- The Skatteverket reconnect banner becomes the one-ochre-sentence
AttnLine with an inline action (convention 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(ui): extract dry-table primitives into components/ui/dry-table
The concept table styles (TH/TD/VTH/VTD classes, quiet links, RowFoldout)
move out of JournalEntryList so the transaction inbox can render the exact
same table language.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): Synka bank nu in the Importera menu
Extracts the per-connection sync/reconnect logic into a shared useBankSync()
hook (BankSyncNowButton keeps using it) and adds the concept's first menu row
to the Importera split button: sync all active connections, with the
last-synced age as the row description. Hidden without a connection or the
bank_sync capability; the footer button stays the gated conversion surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): per-account source picker with balances
The context chip becomes the concept's account chooser: always visible in
inbox mode, one row per enabled cash account (PSD2 balance annotation),
an Övriga bucket for bank rows without a registered account, and a
Skattekonto row annotated with the cached saldo. The trigger reads
'Alla källor · <sum>' where the sum covers SEK ledgers + skattekonto.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept dry-table for the inbox
The inbox list becomes the exact Bokforing table: borderless dry-table with
hover-revealed checkboxes and chevrons, one-line rows (date, description,
amount, quiet primary pill + overflow + foldout), and a RowFoldout expansion
carrying the row detail and the full action set as quiet links. Skattekonto
rows render as chip-marked table rows with inline actions. The 'Valj flera'
toggle and the floating batch bar are replaced by the concept bulkbar that
pops in above the table once a row is selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(i18n): singular form for bulkbar selection count
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): suggestion-first Bokfor flow (concept scene 10)
Bokfor on a row with a suggestion now goes straight to the compact
kontering confirm (QuickReview) showing the proposed verifikat; the full
template picker becomes the fallback and the 'Byt mall' path. Rows carry
the concept's 'Forslag: <mall>' chip (with D/K in the tooltip and the
foldout meta). The picker dialog is de-bloated: alternate paths (manuellt,
matcha, ignorera) collapse into quiet links. Modal scroll containers
(DialogContent, SlideOverBody) keep a visible scrollbar thumb: with the
app-wide auto-hide, a long dialog read as cut off at the fold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): drop suggestion UI and foldout action links
Founder direction: no booking suggestions in the list for now (logic comes
later), and the foldout duplicated the overflow menu: the three dots are
the clearer surface. The foldout keeps row detail only (FX conversion,
original bank name, 1930/1630 hint, extraction status) and rows without
any detail no longer expand. The Skattekonto picker row drops its saldo
annotation; the Alla kallor sum covers bank ledgers only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): dry-table for the Alla view + pill chips app-wide
The history list joins the inbox's table language: Datum, Beskrivning,
Belopp, Status columns with Bokford as muted text + Visa verifikat quiet
link (normal state) and Ej bokford as the beige exception chip with an
inline Bokfor pill; the business/private tabs become the house seg and the
KALLA header row becomes the standard chip-picker. Skattekonto rows get
the same treatment. SEK conversion moves to a tooltip (one-line rows).
Badge itself becomes the concept chip: pill radius (99px), 11.5px, quiet
padding, replacing the boxy rounded-md look the founder flagged on the
Underlag saknas badge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): audit round: viewer-disabled split options, touch fallback
Regression-audit fixes: SplitButton options support disabled+disabledTitle
so viewer-gated create paths render inert with the viewer tooltip instead
of silently no-opping (wired on Ny transaktion); the history list gets its
Kopplad till faktura indicator back; dead transactionsWithMatches memo
removed; and coarse-pointer devices now always show hover-revealed
controls (checkboxes, chevrons, quiet row actions) since touch has no
hover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
2da9c71eb3 |
UI badge cleanup + /chart-of-accounts route + loop skills (#850)
Bundles three separable concerns: - style(ui): badge audit + cleanup across 45 files — real-status chips use Badge variants (raw Tailwind colors dropped), non-status count/type/label chips demoted to muted text, clustered badges consolidated; 20 unused imports removed. - feat(bookkeeping): Kontoplan moved to a dedicated /chart-of-accounts route (nav + command palette wired); /bookkeeping shows the journal list only. - chore(skills): loop-* automation skills + design-scan workflow under .claude/. fix(reports): restored the destructive count badge on blocking errors in the periodisk sammanställning (EC Sales List) — a genuine status cue the audit had wrongly flattened; flagged by the PR reviewer and Swedish compliance bot, now clean. All CI green; compliance bots report no findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
60e33c4b51 |
Fix/cus fee 28 (#820)
* feat(invoices): add Plusgiro input to bank details settings Plusgiro was already persisted, validated by the API schema, rendered on the invoice PDF and toggleable via "Visa plusgiro" — but the settings UI had no field to enter the number, so plusgiro-only users could not fill it in. Add the input next to Bankgiro with Luhn validation and hyphen formatting, include it in the save payload (normalised on save so raw digits still match the dashed schema format), and add sv/en strings. Adds validatePlusgiroNumber/formatPlusgiroNumber helpers + tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(invoices): respect non-VAT-registered seller in PDF preview + portal tooltips Two user-reported bugs: - PDF preview (/api/invoices/preview-pdf) ignored company.vat_registered and fell back to the customer-driven 25% rate, so a non-momsregistrerad seller saw VAT in the review step even though the created invoice books none. Mirror the server-side write gate (build-invoice-write.ts): force 0% when vat_registered is false (delivery notes excepted). - InfoTooltip rendered TooltipContent without a Portal, so tooltips were clipped by the scrollable DialogContent (overflow-y-auto) in the send-invoice journal-entry review. Wrap in TooltipPrimitive.Portal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(transactions): book library mall from its literal lines, not a lossy fallback Booking a bank transaction with a user-created booking-template (mall) via the convertible "QuickReview" fast path reduced the template to a single category + one account_override, silently discarding the chosen debit/credit. A kundinbetalning mall (D 1930 / K 1510) booked as a generic cost (D 6991 / K 1930), or with a VAT line as D 1930 / K 1930 / K 2611 — and the result flipped with the direction inferred from the business/settlement line tags, so visually-identical templates produced different verifikationer. Route every library template through the journal-entry editor (applyTemplate -> /book), which posts the literal lines, regardless of convertibility. Add regression tests locking the contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bookkeeping): make the booking-time duplicate guard bypassable TRANSACTION_BOOK_POSSIBLE_DUPLICATE told users they could "book anyway" but the UI dead-ended on a toast with no way to do so. Add a shared DuplicateBookingDialog that surfaces the already-booked sibling and lets the user review it or book anyway (force bound to the reviewed candidate, which the server re-detects so a stale id cannot wave the guard away). - Wire the dialog into the /transactions categorize flow and the manual booking dialog (JournalEntryForm -> /api/transactions/[id]/book) - Bind the override to expected_duplicate_transaction_id OR expected_duplicate_journal_entry_id so ledger-only vouchers (paid invoice, salary run) can be confirmed too - Extend the guard to the pending-operations commit path and the MCP server - Tests for book/categorize routes, detection, and the commit guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bookkeeping): log duplicate-guard bypass to behandlingshistorik in the agent commit path The web /book and /categorize routes append a durable BankTransactionDuplicateDismissed event when a user books over a detected possible double-booking. The agent commit path (commitCategorizeTransaction, commitMarkInvoicePaid) skipped the guard silently on allow_duplicate=true, leaving no behandlingshistorik — an auditor could not reconstruct why the duplicate was allowed (BFNAR 2013:2 kap 8). When allow_duplicate=true, re-detect the candidate and append the dismissal event (BankTransactionDuplicateDismissed for the bank-line path, InvoiceDuplicatePaymentDismissed for mark-paid). Best-effort — a logging failure never blocks a legitimate booking. Payloads stay PII-safe (ids, amounts, dates only — no customer or merchant name). Also fix the misleading DuplicateBookingDialog JSDoc: the retry binds expected_duplicate_journal_entry_id, not candidate.transaction_id, so the systemdokumentation matches the actual control (BFL 7 kap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(mcp-server): stub booking-duplicate guard in receipt-matcher categorize tests The gnubok_categorize_transaction tool runs the booking-time duplicate guard before staging; its detection queries consumed the queued supabase mock results, so the staging assertions saw a thrown duplicate error instead of a staged op. Mock detectBookingDuplicate to "no duplicate" since these tests don't exercise that path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(transactions): use roundOre for duplicate-guard öre rounding Replace naive Math.round(x*100)/100 with roundOre() from @/lib/money in the booking-time duplicate guard (detection lib, commit executor, MCP categorize tool), satisfying the no-new-antipatterns ratchet guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sie-export): paginate journal entries and lines to prevent truncation * fix(bookkeeping): keep the Verifikat/Utkast toggle reachable on an empty list The journal entry list early-returned a pristine empty card whenever the visible list was empty and no filter was active, returning before the Verifikat/Utkast toggle rendered. This stranded users with only drafts (no posted entries) and users who emptied the drafts list, who then had to use the main menu to get back to posted entries. Narrow the early return to a genuinely empty ledger (committed view, no drafts, no filters); make the in-list empty placeholder context-aware (no drafts / no filter matches / no posted entries yet); resolve the draft count before clearing loading on an empty committed list to avoid a toggle flicker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(enable-banking): persist psu_type and reuse it on reconnect Reconnecting a bank connection re-derived psu_type from the company entity_type every time (aktiebolag -> 'business'), silently overriding the type the user actually authorized with. A connection that only signs as 'personal' — common for AB owners who use a personal Mobile BankID, notably at Handelsbanken — flipped back to 'business' on every consent renewal and failed at the bank's signing step. - Add nullable bank_connections.psu_type column (idempotent migration) - Persist psu_type on connect; on reconnect reuse the stored value (explicit client override still wins) - Let users switch account type (Företag/Privat) from the reconnect button - Tests for persistence, reuse, and override Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(import): set maxDuration=300 on bank-file execute to prevent timeout A full-year bank file (300+ rows) runs a sequential per-row ingest that takes ~85s of server time. The execute route set no maxDuration, so it inherited the platform default and was killed mid-run — the import "spins then aborts" for the user. Match the SIE import route and give it a 5-minute budget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(transactions): add assistant entry point on transaction rows The agent ("Lena") could only be reached from Dokumentinkorgen, and only once an underlag was matched to a transaction. Transaktioner is the most common starting point for booking, so users could not start a booking with the assistant from there at all. Add a per-row "Fråga [namn]" button on unbooked transaction rows that opens the existing transaction.categorization intent with the row's transaction_id. The intent already reads any linked underlag, so it works whether or not a receipt is attached. No new logic — only the missing entry point. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(invoices): enable Swish payment QR on invoices Flip SHOW_SWISH_ON_INVOICE on so the Swish row and payment QR render on the invoice PDF, and make the "Visa Swish" settings toggle live (it was hardcoded disabled). The preview-pdf route now builds the QR too, so it shows in forhandsvisning. Position the QR in the top-right of the payment box. No Swish API integration -- the QR is generated offline and prefills the customer Swish app; reconciliation stays via bank matching. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bookkeeping): scope verifikat list to current year, add storno action, clarify correction preview Three UI fixes from user feedback; no engine logic changed. - List defaults to the current räkenskapsår instead of all years. Voucher numbers run per fiscal year (one A42/year), so showing every year at once made them look like duplicates. New resolveCurrentPeriodId helper. - Add 'Återför (storno)' action on the entry detail page and list row, wiring the existing reverseEntry — a pure reversal (BFL 5 kap 5§) with no replacement, distinct from 'Rätta'. - Correction 'Effekt per konto' preview now labels a removed account 'tas bort' (vs a bare dash) and warns when the proposal is unbalanced; dialog explains the rows are the full new verifikat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(bank_connections): add psu_type column to persist chosen authorization type * feat(errors): add CannotReverseStornoError for handling reversal of storno or correction entries --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a4da2d62df |
feat: add ignore menu item to transaction inbox card (#758)
Imported bank transactions can now be hidden from the inbox via a new
"Ignorera transaktion" item in the ⋯ overflow menu. The backend
(POST/DELETE /api/transactions/[id]/ignore) and the is_ignored DB column
already existed; this wires up the missing UI affordance.
- TransactionInboxCard: add onIgnore prop + EyeOff menu item (imported
rows only — manually created rows use delete instead)
- page.tsx: pass onIgnore={handleIgnoreTransaction} to the card; add
handleBatchIgnore for the batch action bar's new "Ignorera" button
- sv.json / en.json: add ignore_btn translation key to tx_inbox_card
Signed-off-by: Jonas Flodén <jonas@floden.nu>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
36e3f6ceb0 |
Design critique: normalize daily-operator flows + UX improvements (#741)
* style(design): normalize daily-operator flows to locked design system Sweep the dashboard, transactions, reconciliation, invoicing and supplier flows for design-system violations (.claude/rules/design.md): - font-medium removed from Hedvig display headings/numerals - font-mono -> tabular-nums on monetary values (voucher ids stay mono) - raw Tailwind status colors -> Badge variants / muted-alert pattern - semantic colors removed from chrome backgrounds (deadline widgets, icon halos) - hand-rolled skeletons/empty-states -> Skeleton / EmptyState primitives - opacity-suffixed borders, the invisible warning-foreground count color, and shadow-sm/rounded-xl on non-overlay surfaces normalized The four files that also received UX changes carry their token fixes in the following commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ux): clearer dashboard CTA, match confidence, AI provenance, invoice actions Four high-impact UX fixes from the design critique (these files also carry their design-system token normalization): - Dashboard: render the next-best-action hero for every agent-built company, not only 'slim' nav density, so there is always one obvious next step instead of four equal-weight metric tiles. - Reconciliation: surface the match engine's 0-1 confidence as a graded strength badge (Stark / Trolig / Svag traff) in the shared verifikat picker rows and selected chip; drop the uninformative binary "Foreslagen traff" badge from the match dialog. - Supplier inbox: show AI-filled provenance per extracted field (a success dot that clears once the user verifies/edits the value) so misparsed amounts/dates get proofread before they post to an immutable verifikat. - Invoice detail: keep each status's primary action only in the header row; the sidebar "Status actions" card now holds secondary/reversible actions only (makulera, ta bort, skapa kreditnota, manual-send alternative), removing the duplicated CTAs and closing a viewer-permission gap on the old sidebar buttons. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ux): Tier-B medium design-critique fixes across daily-operator flows - Reconciliation: standardise the match-confirm verb on "Matcha" (was "Koppla" in MatchVoucherDialog) and replace the hand-rolled date <input>s in the bank reconciliation view with the Input primitive. - Supplier inbox: fold the two alternative bookings (Skapa leverantorsfaktura / Bokfor som verifikat) behind a single "Andra satt att bokfora" dropdown so the default path (Matcha mot transaktion) stays the lone primary action. - Duplicate-payment guard: demote the "Skapa ny verifikation anda" escape hatch to a ghost button so the safe "Koppla till befintlig" path dominates. - Onboarding: raise the "start fresh" escape hatch from a muted text link to a visible secondary button; normalise the checklist's off-scale spacing. - Supplier flows: finish the font-mono -> tabular-nums sweep on monetary values in the supplier-invoice detail / create / review surfaces (ids stay mono). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(deadlines): keep overdue rows visually distinct (destructive chrome is allowed) The Tier-A normalization stripped all semantic-color row tints from the deadline widgets, but design.md exempts --destructive ("only --destructive survives in chrome"). Restore a subtle bg-destructive/5 on OVERDUE rows so missed tax/AGI deadlines (-> skattetillagg) stay noticeable in a list scan; action-needed (warning) rows stay clean since warning is data-only. Surfaced by the Swedish compliance review on #741. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reconciliation): hide match-strength badge on already-matched verifikat Per the Swedish compliance review on #741: a green "Stark traff" confidence badge rendered alongside "Redan matchad" could visually nudge an accidental double-match of a posted verifikat (a BFL 5 kap audit-trail concern). Suppress the strength badge when linked_transaction_count > 0 so "Redan matchad" is the lone signal there; N:1 matching stays an explicit opt-in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0521c385d2 |
feat(transactions): underlag status badges + attach dialog; auto-expire stale pending ops (#712)
* feat(transactions): per-row underlag status + attach-document dialog
- New "Matcha mot underlag" dialog on /transactions (inbox pick or fresh
upload), the tx→doc mirror of the Documents view's matcher
- Per-row Underlag/Underlag saknas badges on booked history rows, driven
by computeJeUnderlagStatus — same posted-only, exemption-aware scope as
the worklist count so badge and count never disagree
- attach-document route + commit dispatcher now propagate the doc onto
the verifikation when the tx is already booked (BFL 5 kap 6 §), with a
409 guard for docs consumed by a different verifikation, idempotent
re-attach (no same-value rewrite under period lock), and an honest 409
when the period-lock trigger blocks the propagation
- Booking-dialog doc links also pin the doc to the transaction row
(first linked doc wins) via the link route's new transaction_id param
messages/{sv,en}.json also carries the strings for the pending-ops
expiry UI that lands in the next commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pending-operations): auto-expire stale staged operations after 30 days
- New daily cron (02:30 UTC, vercel.json + both docker crontabs) flips
>30-day-old pending ops to rejected with the dispatcher's
{ auto_rejected: true, reason: 'expired' } result_data shape — rows are
never deleted, the table is the audit trail
- /pending renders an "Utgick automatiskt" badge + detail line for these,
orders terminal tabs by resolved_at so a fresh expiry sweep isn't
buried, and adds a first-time-reviewer explainer
- Origin labels spell out where a proposal came from (AI chat, MCP key,
API, cron) instead of the raw actor_label
- agent_chat actor type added to PendingOperationActorType/AuditLogEntry
(DB CHECK already widened in 20260519090000) and to the agent filter
- ApprovalCard notes that ignoring a proposal is safe
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mcp): surface the client telemetry marker in connect instructions
Tag the connector URLs shown in ApiKeysPanel, the connect-claude doc and
the gnubok-mcp README with ?client=<surface> (claude-connector /
claude-code) and GNUBOK_CLIENT=claude-desktop for the npm bridge.
Telemetry-only — the server already reads the param/header; this just
lets us measure which Claude surface connected.
The claude mcp add copy blocks quote the URL: an unquoted ? in the query
string trips zsh globbing ("no matches found").
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: fix stale-closure badge flip + zod-validate link route body (PR #712)
- handleDocumentAttached read journal_entry_id off the render-time
transactions snapshot; if the list changed while the attach dialog was
open the optimistic badge flip was silently skipped. Read it off the
dialog's own subject (attachDocTx) instead.
- POST /api/documents/[id]/link now validates the body against the new
LinkDocumentSchema (uuid-strict, all four fields) instead of a bare
presence check on journal_entry_id — same canonical VALIDATION_ERROR
envelope. Test fixtures switched to real UUIDs accordingly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
64991eb3c9 |
Add/transaction deletion (#695)
* feat(salary): add remove-employee button to draft salary runs
The DELETE /api/salary/runs/{id}/employees/{employeeId} endpoint already
existed (draft-only, cascades to the employee's line items) but had no UI
trigger, so a mistakenly added employee could only be cleared by deleting
the whole draft. Add a trash-icon action column to the "Anställda" table,
gated on draft status + write permission to match the endpoint's guard,
with a confirm prompt and success/error toast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(settings): prevent horizontal overflow on mobile
The company settings invite form was a non-wrapping fixed-width flex row that overflowed narrow viewports, forcing the full-screen settings modal to scroll on the x-axis. Stack the form vertically on mobile (sm:flex-row at and above the sm breakpoint) and add the missing min-w-0 guard to the modal content pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(bookkeeping): move journal entry filters into a filter dialog
The ledger toolbar showed every filter inline (fiscal year, sort, series,
date range, missing-documents toggle), which felt cluttered. Keep only the
search field visible and move the rest into a "Filtrera" dialog with an
active-filter count badge.
- JournalEntryList now owns the fiscal-year scope, restored from the same
localStorage key FiscalYearSelector writes, so the page no longer renders
the selector separately.
- Filters apply live and the dialog stays open; "Rensa alla filter" clears them.
- Export STORAGE_KEY_PREFIX / ALL_YEARS_VALUE from FiscalYearSelector so the
list reuses the persisted selection without duplicating the key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(transactions): implement imported transaction guard for deletion
- Added a guard to prevent deletion of transactions that are imported via bank sync or file uploads.
- Introduced `isImportedTransaction` utility to determine if a transaction is user-created or imported.
- Updated DELETE endpoint to return a 409 status for attempts to delete imported transactions.
- Enhanced transaction history and inbox components to reflect the new deletion rules.
- Added tests for transaction origin determination and deletion behavior.
- Updated UI components to include a confirmation dialog for clearing journal entry forms.
- Localized new strings for clearing form functionality in English and Swedish.
* feat(transactions): enhance transaction deletion guard and improve fiscal year visibility
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c74b19df1b |
Accounted rebrand + swarm-skill cleanup + bank-reconciliation fixes (#643)
* feat(reconciliation): close the bank-feed loop on voucher links and re-tag mis-typed opening balances
Two related fixes to bank reconciliation correctness:
1. Auto-reconcile on voucher link. Linking an invoice or supplier invoice to
an existing voucher previously advanced only the invoice — the bank
transaction that paid it kept sitting in the Transactions inbox with a null
journal_entry_id. linkInvoiceToVoucher / linkSupplierInvoiceToVoucher now
call autoReconcileTransactionForLinkedVoucher (lib/reconciliation), which
links the bank transaction to the same verifikat when exactly one unbooked
line matches it. Best-effort and post-commit: a failure here never fails the
link. The result surfaces reconciledTransactionId; the inbox row leaves the
list and the UI shows link_success_tx_reconciled.
2. Re-tag mis-typed opening balances. getReconciliationStatus and the GL-line
matching RPCs identify a cash account's ingående balans solely by
journal_entries.source_type='opening_balance'. Companies migrated from other
systems often booked the bank IB as an ordinary voucher (source_type
'import' or 'manual'), so it was never excluded and surfaced as a phantom
reconciliation difference equal to the opening balance. Adds:
- migration mark_entry_as_opening_balance: a GUC-gated carve-out in the
immutability trigger plus a SECURITY DEFINER RPC that validates the entry
(balance-sheet lines only, dated on a fiscal-period boundary), flips the
source_type, and writes an audit row — no blanket data sweep.
- POST /api/reconciliation/bank/mark-opening-balance + MarkOpeningBalanceSchema.
- BankReconciliationView action to trigger it from the IB diff.
The gnubok_create_voucher executor now accepts a typed is_opening_balance flag
and derives source_type='opening_balance' only after validating class 1/2 lines
on the period start, so new IBs land correctly typed.
Covered by lib/reconciliation auto-reconcile tests, voucher-executors tests,
and a mark-entry-as-opening-balance pg-real test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: rebrand gnubok → Accounted and prune swarm agent skills
Product rebrand and skills housekeeping. No runtime behaviour change.
Rebrand: replace user-visible "gnubok" with "Accounted" across docs, READMEs,
in-code comments, doc-site content, MCP skill/resource prose, and the
gnubok-mcp package description. The MCP resource URI scheme is moved gnubok://
→ Accounted:// consistently across resource registrations, the event-type
comment, and the resource/skill tests. Deliberately preserved as stable
identifiers (NOT rebranded): the gnubok-company-id cookie, gnubok_sk_ / gnubok_inv_
token prefixes, the gnubok-mcp npm bridge name, and the AGI <gem:Programnamn>
value (kept 'gnubok' per its source comment — it is the software identifier sent
to Skatteverket and must not churn across visual rebrands).
Skills: remove the 27 swarm-* agent SKILL.md atoms (no longer used; already
absent from the agent_atom_registry in prod), refresh the remaining skill docs,
add the .claude/rules/ path-scoped rule set, and regenerate the
seed_agent_atom_bodies migration + .skill-body-manifest.json via
`npm run skills:generate` so the DB-backed skill bodies match the trimmed set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
b5c3c3ec04 |
fix(reconciliation): surface bank tx + match to existing verifikat, delete UX, DPA links (#623)
* fix(reconciliation): surface bank transactions via robust cash_account_id scoping The per-account reconciliation scoping silently returned zero transactions for companies whose rows were NULL or mis-assigned mid-backfill (e.g. Arcim: 138 transactions, 101 unbooked, yet Bankavstämning showed "0 kr" while the 1930 GL movement and a large difference still displayed). Two causes, both fixed: - scopeTransactionsToAccount used a fragile nested or(...,and(is.null,...)) PostgREST filter. Replace it with a flat, reliable `currency = X AND (cash_account_id = id OR cash_account_id IS NULL)` and share the one implementation with /api/transactions so the status card and the lists can never drift. - The original best-effort backfill only touched NULL rows and an earlier revision mis-assigned cash_account_id (the since-fixed min(uuid) bug), which migrations cannot self-correct. Add an idempotent repair migration that re-seeds the default 1930 account and re-derives cash_account_id (correcting non-NULL mis-assignments) for booked rows and single-account companies. Also localise manualLink's user-facing errors to Swedish. Adds unit coverage for the new filter shape and pg-real coverage for the repair (incl. the Arcim single-account reproduction). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(transactions): match a bank transaction to an existing verifikat Adds a "Matcha mot befintlig verifikation" action to the Transactions inbox so a bank line that is already booked elsewhere (a salary run, a Fortnox/manual voucher, an invoice paid from the invoice page) can be linked to that existing verifikat with no new bokföring — the capability previously lived only in Reports → Bankavstämning. - Extract the searchable MatchVerifikationPicker into a shared client component. - New MatchVoucherDialog: resolves the tx's cash account, fetches candidates ranked server-side by reconciliation confidence, links via /api/reconciliation/bank/link (so the link is undoable in Bankavstämning). - unmatched-entries route gains an optional transaction_id that ranks candidates (ranking stays server-side; the recon lib is not client-safe). - Inbox row's overflow (⋯) menu gains the new action. Also fixes the Bankavstämning view: editing the date no longer auto-reloads (applies on Filtrera / account change only) and Datum till defaults to today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(transactions): actionable Swedish errors when a bank tx cannot be deleted The delete route returned a hardcoded English 409 that getErrorMessage mapped to the misleading generic "En konflikt uppstod. Ladda om sidan...". Return structured bilingual envelopes instead: - TRANSACTION_DELETE_BOOKED (409) for a booked/linked row — steers the user to unlink in Bankavstämning or storna the voucher. - TRANSACTION_DELETE_HAS_AUDIT_TRAIL (409) for the real, common case where an unbooked row carries payment_match_log rows: the cascade hits the audit-immutability trigger (P0001), previously surfaced as a bare 500. Steers the user to match-to-voucher or ignore instead. Updates the DELETE test suite and adds the P0001 case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(settings): surface DPA and privacy policy links The /dpa page (personuppgiftsbiträdesavtal, GDPR Art. 28) was complete but linked from nowhere. Add a "Sekretess och avtal" card on Inställningar → Konto linking to /privacy and /dpa, and a reciprocal link to the DPA from the privacy policy's sub-processor section. (The DPA already links back to /privacy.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(review): address PR #623 feedback - unmatched-entries: when transaction_id is supplied but resolves to no row in the caller's company, return an empty candidate list instead of silently falling back to the full unranked list (Compliance Swarm V8.2.1, high). - MatchVoucherDialog: preserve a manually-picked voucher when the candidate list reloads (e.g. "Visa alla datum") instead of discarding it (Greptile P2). - DELETE /api/transactions/[id]: return the 404 as the structured { error: { code, message, message_en } } envelope like the handler's other errors, for a uniform contract (Greptile P2). Test updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(bookkeeping): allow editing notes on a committed journal entry Saving a note on a posted verifikation failed with "Committed entries are immutable": enforce_journal_entry_immutability() had no posted→posted path, so a notes-only UPDATE fell through to the final RAISE. `notes` is internal annotation metadata (not verifikation content under BFL 5 kap. / BFNAR 2013:2), so add a narrow carve-out that permits a notes-only change on a committed entry — verified with a whole-row to_jsonb() diff so any other field change still raises, and only when status is unchanged. Period-lock enforcement is unaffected. CREATE OR REPLACE in a new migration (same pattern as 20260428160000_fix_journal_entry_immutability_delete_bypass); the migration-017 protections are extended, never weakened. Covered by a pg-real test asserting a notes edit succeeds while amount/description/account edits still fail. (Already applied to production; committing the file + test for repo consistency.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sandbox): make pre-staged pending operations executor-complete The seeded pending_operations existed only as display previews — approving them failed because commit executors in lib/pending-operations/commit.ts validate required fields on "Godkänn". Seed a backing invoice_inbox_items row and fill the supplier-invoice and categorize params with every field the executors require (inbox_item_id, full items array; real uncategorized transaction_id + category), so the sandbox approval queue is actually approvable end to end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c6c86cded4 |
Mcp/template data feedback (#617)
* fix(booking-templates): scope template list to the active company GET /api/settings/booking-templates relied solely on the btl_select RLS policy, which is membership-wide (user_company_ids) and returns templates from every company the user belongs to. A user who owns multiple companies saw all their templates merged regardless of which company was active. Narrow the list in the API layer (mirroring counterparty-templates) to system + the active company + the active company's team. RLS stays the security backstop; this fixes the cross-company merge within a single user's own view (it was never a cross-tenant data leak). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(import): show proper message for duplicate bank file upload The bank file import page mis-parsed the structured error envelope ({ error: { code, message, details } }), so a BANK_FILE_DUPLICATE (409) fell through to the generic "Kunde inte läsa filen" fallback. The upload step also hardcoded that same string as the error heading, so duplicates were doubly misreported as parse failures. - Parse the structured envelope by error.code; surface error.message for all codes instead of rendering the error object. - Add a dedicated BANK_FILE_DUPLICATE message using the importedAt / importedCount details the route already returns. - Add an optional errorTitle prop to BankFileUploadStep (defaults to the previous text) and pass "Filen är redan importerad" for dupes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(tests): add comprehensive tests for recordateEntry, inbox-linking, and external-id handling - Implemented unit tests for recordateEntry in the bookkeeping module to validate various scenarios including date changes, non-posted entries, and fiscal period restrictions. - Created tests for inbox-linking status in pending operations to ensure correct handling of invoice inbox items and supplier invoices, addressing historical bugs related to status updates. - Added tests for external-id utilities to ensure consistent handling of monetary amounts and deduplication keys across different transaction sources. - Introduced new functions in external-id.ts for stable external ID generation and normalization of imported descriptions, enhancing transaction deduplication reliability. feat(migrations): add new database migrations for transaction handling - Created migration to exclude storno and correction vouchers from unmatched GL lines, ensuring accurate reconciliation. - Added a migration to preserve original bank transaction descriptions in a new immutable column, allowing for user edits while maintaining audit trails and deduplication integrity. * feat(migrations): add function to exclude storno/correction vouchers from unmatched GL lines * feat(transactions): enhance transaction handling with improved description normalization and preloaded original entries --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7eb8715417 |
feat(transactions): split-payment allocator — 1 tx → N invoices (#603)
* fix(category-mapping): use leaf BAS accounts instead of group codes 3900, 5800, 6200 are BAS gruppkonton (header codes) and shouldn't carry postings. Switched the default mappings to the matching leaf accounts: - income_other: 3900 -> 3999 (Övriga rörelseintäkter) - expense_travel: 5800 -> 5890 (Övriga resekostnader) - expense_telecom: 6200 -> 6230 (Datakommunikation) The fallback for income_other inside getCategoryAccountMapping was also hardcoded to '3900'; updated to '3999' for consistency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transactions): split-payment allocator — 1 tx → N invoices Closes one of the two flows that motivated PR #602's foundation: allocating a single bank transaction across multiple customer OR multiple supplier invoices, with one combined verifikat (samlingsverifikation per BFL 5 kap 6§ st 3). ## Backend (Phase 3a) - **PL/pgSQL RPC** match_batch_allocate (~400 lines): locks the tx + each target invoice with SELECT … FOR UPDATE in id order, validates status/currency/remaining/direction before any write, builds the combined verifikat via commit_journal_entry (atomically assigns voucher_number + flips draft→posted), inserts N rows in invoice_payments or supplier_invoice_payments pointing at the same JE, advances paid_amount/remaining_amount/status per invoice. Returns { ok, journal_entry_id, voucher_number, allocations: [...] } on success or { ok: false, code, details } on guard failure. Mixed customer+supplier kinds are rejected (v1 scope). - **Endpoint** POST /api/transactions/[id]/match-batch — thin wrapper around the RPC. Validates body via MatchBatchSchema (zod discriminatedUnion + superRefine to catch mixed-kinds at the schema layer). On RPC success, emits one invoice.match_confirmed or supplier_invoice.match_confirmed event per allocation so existing subscribers (reminders, automations, processing-history) keep working. Maps the structured RPC error envelope to errorResponseFromCode. - **16 new BATCH_* error codes** (sv+en): BATCH_TX_NOT_FOUND, BATCH_TX_ALREADY_BOOKED, BATCH_OVERSHOOT, BATCH_AMOUNT_EXCEEDS_TX, BATCH_MIXED_KINDS_UNSUPPORTED, BATCH_DIRECTION_MISMATCH, BATCH_CURRENCY_MISMATCH, BATCH_PERIOD_LOCKED, BATCH_RPC_FAILED, etc. ## UI (Phase 5a) - **MatchAllocationDialog** (components/transactions/) — direction- aware (positive tx → customer invoices, negative → supplier). Search + selectable list of open invoices. Per-row amount input with default = min(invoice.remaining, tx_remaining_budget). Live tally with green-check balanced state, red overshoot warning, gray leftover note. Confirm button disabled on overshoot. POSTs to /match-batch and on 200 triggers the same exit animation as single-tx match. - **Inbox row** gains a second outline icon button (Split icon) next to the existing 1:1 match button, gated by the same showInvoiceMatchButton predicate. Tooltip explains the direction- aware split. Opens MatchAllocationDialog. - **i18n** strings under tx_match_allocation namespace in sv.json and en.json (32 keys each). ## Tests - tests/pg/match-batch-allocate.pg.test.ts — 5 pg-real tests covering combined verifikat shape, overshoot guard, already-booked tx, direction mismatch, mixed-kinds rejection. - app/api/transactions/[id]/match-batch/__tests__/route.test.ts — 5 unit tests covering schema validation, mixed-kinds, happy path, structured-error mapping, raw-error → BATCH_RPC_FAILED. 63 unit tests pass across the touched paths. The RPC migration was already applied to remote in an earlier Phase 3a session (idempotent CREATE OR REPLACE FUNCTION; the next replay is a no-op). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(match-batch): PR #603 review round 1 + CI fixes Closes both CI failures and the three real review findings. ## CI fixes - **pg-real failure**: the RPC declared `v_journal_entry_id uuid := uuid_generate_v4()` which fails in the CI Postgres image (uuid-ossp extension is off). Switched to `gen_random_uuid()` — the codebase standard already used by supplier_invoices, invoice_inbox, etc. - **core-only failure**: my earlier BAS leaf-account commit (3900→3999, 5800→5890, 6200→6230) didn't update the matching `lib/bookkeeping/__tests__/category-mapping.test.ts` expectations, and `getDefaultAccountForCategory`'s fallback for `income_*` was still hardcoded to '3900'. Updated both. ## Review findings (greptile) - **P1 deadlock-stable locking** (`match_batch_allocate.sql:11`): the validation `FOR UPDATE` loop ran in caller-supplied array order. Two concurrent calls with overlapping invoice sets in opposite orders could deadlock and one would abort with `BATCH_RPC_FAILED`. Now all three loops (validate, build lines, advance invoices) iterate via `SELECT … FROM jsonb_array_elements(…) ORDER BY COALESCE(invoice_id, supplier_invoice_id)`, giving a stable global lock order regardless of how the caller ordered the JSON array. - **P1 duplicate-allocation detection** (`match_batch_allocate.sql:163`): the same invoice_id listed twice would pass the per-row overshoot guard (both iterations read the original `remaining_amount`) and the write loop would insert two `invoice_payments` rows for the same invoice. Added a `v_seen_ids text[]` check in the validation loop and a new `BATCH_DUPLICATE_ALLOCATION` error code (sv + en). The dialog already prevents this UI-side via `if (prev[candidate.id] return prev` — the RPC guard is the defense-in-depth layer. - **P2 zod `.positive()`** (`schemas.ts:544`): allocation amount was `nonNegativeAmount` (allowing 0), passing schema validation only to be rejected by the RPC with `BATCH_INVALID_AMOUNT`. Now `z.number().positive(…)` so 0-amount entries fail at the schema layer with a per-field path, cleaner 400. - **P2 strict `> 0` direction check** (`MatchAllocationDialog.tsx:82`): used `amount >= 0` to pick customer-side, but a zero-amount tx would load customer candidates only to hit `BATCH_TX_ZERO_AMOUNT` at submit time after the user has filled in allocations. Switched to `> 0` so 0-amount tx never reaches the dialog at all (it's rejected by the RPC immediately). The fourth Greptile comment (the schema P2 about amount validation) overlaps with the third; addressed in the same edit. ## Verification - 112 unit tests pass across touched paths - ESLint clean - New pg-real test `tests/pg/match-batch-allocate.pg.test.ts` covers the dedupe scenario (same supplier invoice listed twice with summing amounts that individually pass per-row overshoot) - RPC patch applied to remote via Supabase MCP Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(match-batch): PR #603 review round 2 — compliance hardening Addresses the actionable findings from compliance-swarm and Swedish-accounting-compliance reviews. Six small RPC changes + two TS-side guards, all bundled in one follow-up migration. ## Security - **(GDPR Art.5(1)(f) / ISO A.8.2) Caller verification**: SECURITY DEFINER bypasses RLS, and the prior RPC accepted any (p_user_id, p_company_id) pair from the route. Now the function rejects with new `BATCH_UNAUTHORIZED` (sv+en, HTTP 403) if `auth.uid()` is not a member of `p_company_id`. Pattern lifted from `harden_invoice_number_rpcs` (#20260510140000). - **(OWASP V4.2) Allocation cap**: `MatchBatchSchema.allocations` now carries `.max(100)` to prevent DoS via unbounded FOR UPDATE locks. ## Swedish accounting correctness - **source_type per direction**: was hardcoded to `'invoice_paid'` for both customer + supplier batches, mis-routing behandlingshistorik filters. Customer batches keep `'invoice_paid'`, supplier batches now write `'supplier_invoice_paid'`. - **Fiscal-period determinism**: `LIMIT 1` on the period lookup was non-deterministic on overlap (e.g. corrected broken year). Added `ORDER BY period_start DESC` so the most recent matching period wins. - **Tolerance harmonisation**: cross-allocation sum used `+0.01` tolerance while per-row used `+0.005`. Both now `+0.005` so a multi-row batch can't drift ~0.01 SEK while each row passes individually. - **`transactions.category` no longer overwritten**: was forced to `'income_services'` (→ BAS 3001 at 25% VAT) for any customer batch, misrepresenting reduced-rate / export / EU-service invoices. The category is only meaningful 1:1 with a single invoice; batches now leave it as-is, mirroring the supplier-side `ELSE category` branch. ## Tests - `tests/pg/match-batch-allocate.pg.test.ts` now wraps every RPC call in `withUserContext(userId)` so `auth.uid()` resolves to the seeded owner. Without this the new membership check would have failed all existing tests. - New pg-real test: `rejects with BATCH_UNAUTHORIZED when caller is not a member of the company` — outsider user gets explicit refusal. - New happy-path assertion: `source_type = 'supplier_invoice_paid'` on the combined verifikat for supplier batches. 15 unit tests pass on the touched paths. RPC patch applied to remote via Supabase MCP. Out-of-scope mcp-server changes still parked locally. Skipped findings (documented in PR comment thread): - V8.2.1 ownership pre-check at route layer (RPC enforces it) - V4.5 / Art.5(1)(b) narrower API response and event payload — typed contracts require the full shapes - V2.4 rate-limiting — system-level, applies to all match endpoints - A.8.28 client-side RLS reliance — documented architectural choice - Direction pre-check at API layer (RPC catches with cleaner code) - V16 + Art.32 + Art.5(1)(b) low-severity logging nits Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f53725b20a |
Agent v1 bundle: TIC v2 onboarding, in-app assistant gating, sidebar nav, MCP fixes (#584)
* fix(sie-import): accept tab as field separator (Bollbok exports) The SIE 4 spec allows either space or tab between fields, but splitSIELine() only treated space (0x20) as a separator. Bollbok exports tab-separated lines for every record except #RAR, which silently swallowed all #IB / #UB / #KONTO / #KTYP / #VER / #TRANS records — imports appeared empty even though the file was well-formed. Also adds a parser-side diagnostic that emits a warning when raw #IB or #VER lines are present in the input but parsing produced none. The previous silent failure is how this bug stayed hidden; the warning gives the import preview something visible to surface next time. Verified against two real reproducer files (Sean / Erik Hellqvist): erik h 2025.SE (UTF-8): 166 accounts, 66 IB, 4 UB, 11 RES, 95 vouchers, 198 TRANS. erik h 2026.SE (CP437): 166 accounts, 66 IB, 4 UB, 0 vouchers. Both now parse with zero warnings/errors. Tests: + 8 Bollbok-shape tab-separated fixtures (2025 + 2026 quoting variants). + 4 silent-failure diagnostic-warning tests. All 74 sie-parser tests pass; 155/155 in lib/import; 64/64 downstream callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sie-import): address PR #513 review — strip #KTYP quotes, suppress redundant aggregate warning Two non-blocking P2 findings from Greptile review on PR #513: 1. #KTYP handler stored fields[2] directly, so Bollbok 2026 exports (#KTYP\t1510\t"T") stored '"T"' with literal quotes instead of 'T'. Latent defect — accountType is unused downstream today, but my tab- separator fix made the quoted-value path reachable. Now routes through parseStringField so both Bollbok 2025 (unquoted T) and 2026 (quoted "T") land as 'T'. 2. The aggregate "kontrollera fältavskiljare och teckenkodning" warning fired alongside per-record 'error'-severity issues for malformed #IB / #VER records, producing a misleading hint when the parser had already pinpointed the structural problem. Now suppressed when an error-severity issue with the same tag already exists. Test coverage: + accountType asserted to be 'T' (not '"T"') in both 2025 + 2026 shapes. + VER aggregate-warning test now uses #VER lines without { } blocks (silent loss, no per-record error) — the canonical case the diagnostic is designed for. + New suppression test: bare #VER produces per-record errors AND the aggregate warning is absent. 75/75 sie-parser tests pass; 156/156 in lib/import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wip: agent chat + composer + memory + document extraction In-progress work on this branch beyond the SIE-import fixes: - Specialized accountant agent (composer + intents + chat loop) - Persistent agent_conversations/messages, agent_profiles, agent_memory - /chat surface + /onboarding/agent + /settings/agent-memory - document-extraction extension with status hooks - MCP server staging refactor + new skills (atoms, bank reconciliation, customer onboarding, kreditfaktura) - pending_operations rejection feedback (category + reason) + realtime - TIC company profile cached snapshot on companies - 17 migrations (all additive — see prior conversation analysis) Parked while branch waits for review/merge. Migrations are already applied to prod. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(tic): migrate company-data client from api-core v1 to Lens v2 Swaps the seven TIC company-data endpoints we call from the api-core paths (`/datasets/companies/{companyId}/...`, `/search/companies`) to the Lens equivalents (`/companies/{id}/...`, `/search-public/companies`). Hard cutover; proxy pattern preserved. Schema shifts handled inside the extension so consumers (TicWorkspace, Step2CompanyDetails) don't need changes: - `/companies/{id}/bank-accounts` now returns Bankgirot only — map to the existing `{ type, accountNumber, bic }` shape, drop terminated. - `/companies/{id}/industries` returns a discriminated array — filter to `companyIndustryCodeType === 'sni2007'` to preserve v1 behavior. - `/companies/{id}/phone-numbers` renamed the field to `phoneNumberFormatted` (fall back to `e164PhoneNumber`). - `/companies/{id}/documents` replaces `/financial-report-summaries`; filter `type === 'annualReport'` and read nested `financialReportMetadata` to rebuild the legacy summary shape. - `isCeased` is now a top-level boolean; `activityStatus` is an enum. Translate enum -> 'ceased' for the workspace's existing check. BankID identity flow (id.tic.io) is untouched — separate TIC product. Note: deploy gated on the TIC proxy being flipped to lens-api.tic.io with an `x-api-key` Lens key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tic): expose v2 onboarding & workspace data Adds six new Lens (v2) fetchers on top of the migration that already landed in this branch, surfacing the data through /lookup and /profile. New fetchers in lib/tic-client.ts: - getFiscalYears /companies/{id}/fiscal-years - getAccountingPeriods /companies/{id}/accounting-periods - getPayrolls /companies/{id}/payrolls - getSignatory /companies/{id}/signatory - getRepresentatives /companies/{id}/representatives - getCompanyStatus /companies/{id}/status /lookup gains a fiscalYear field (current fiscal-year configuration) so onboarding Step 2 can skip manual MM-DD entry. CompanyLookupResult extended with optional fiscalYear; consumers without it keep working. /profile gains five new sections on TICCompanyProfile: - fiscalYear + fiscalYearHistory current + deduped period list - signatory firmateckning descriptions - board + representatives board-composition summary + active officers (positionEnd in future) - payrolls payroll2 array newest-first, with deviation vs annual-report - statuses current+historical status entries with red/yellow/green/neutral color TicWorkspace renders the new data as four cards (Status, Fiscal year + Signatory, Board + Representatives, Payroll history) plus a Badge mapping for the traffic-light status color. Tests: 52 -> 60 passing. Added unit tests for the new fetchers' v2 paths, fiscal-year auto-fill in /lookup, and full v2 profile coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding,agent): lean on TIC v2 to skip Steps 1 & 3 and sharpen Opus Three small wins that unlock more of the v2 cutover. No new endpoints — the data was already in the snapshot, just not flowing where it should. Step 1 (entity_type) — deep-link path only: - /lookup now returns `legalEntityType` and `registrationDate` (added to CompanyLookupResult). - /onboarding/page.tsx does a server-side /lookup prefetch when ?org_number= is present (BankID picker path), maps "AB"/"EF" to the EntityType enum, and seeds Step 1's radio. Falls through silently for unsupported codes (HB, KB, …) and on TIC errors. - WelcomeOnboarding hydrates ticLookup state from the server prefetch so Step 2's debounced client fetch and Step 3's first-year inference both have data on first render — no flash. Step 3 (is_first_fiscal_year) — every path: - deriveFirstYearDefaults() parses ticLookup.registrationDate and returns { isFirstFiscalYear, firstYearStart } when registered <12 months ago. Step 3's initialData picks it up; the user only confirms the end date. - Settings value wins when present so existing users with a saved choice don't get overridden. Composer prompt: - redactTic allowlist was the bottleneck — it stripped beneficialOwners, signatory, board, representatives, payrolls, statuses, fiscalYear before Opus ever saw the JSON. Existing filterRedundantQuestions ownership logic was effectively dead because the data path was severed. Expanded allowlist to include those v2 sections; kept bankAccounts/ email/phone/fiscalYearHistory/financialReports out (token cost > signal). - SYSTEM_PROMPT now documents each v2 section and the rules Opus should apply: payroll signal switches from "registration.payroll" to "actual payrolls[] filings" (kills the false-positive swedish-payroll selection for newly registered employers); beneficialOwners[] becomes the authoritative ownership source (single owner → FMB modifier; multiple → multi-owner); statuses[] isCeased/red triggers an uncertainty_note. Tests: 4112 unchanged. Build: green. No schema or migration changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): onboarding polish + composer signal fixes from first-run feedback UX: - AgentOnboarding: drop the 10s "Hoppa över — fortsätt med standardval" escape hatch. The fallback path runs automatically on timeout; the manual skip just teased users into a degraded build. - ReviewCard step 2 title: "Stämma av detaljerna" → "Stäm av detaljerna" (imperative form matches the rest of the steps). - Drop em-dashes from user-visible Swedish strings in AgentOnboarding + ReviewCard (fallback labels, subtitles, placeholder, error message, final CTA). Em-dashes survive in code comments only. - "Fråga min revisor" → "Fråga min assistent" everywhere it surfaced: AgentTrigger, AgentSparkleButton, ReviewCard preview, ReviewCard fallback comment, general.help intent buttonLabel + prompt text. - AgentTrigger / AgentSparkleButton / EmptyState.AgentHelpLink / TransactionInboxCard ask-button all gated on identity.isVerified. Pre-onboarding users no longer see the floating FAB or per-page Sparkle buttons. AgentSheetProvider.identity gained an isVerified field; (dashboard)/layout.tsx selects agent_profiles.verified_at and passes it through. TIC verksamhetsbeskrivning: - tic/index.ts /profile: /companies/{id}/purposes returns every historical verksamhetsföremål filing. Picking [0] was returning the oldest "äga och förvalta" holding-company boilerplate for companies whose later filings narrowed the purpose ("tillhandahålla företagskrediter och finansiella teknologilösningar"). Sort the array by lastUpdatedAtUtc desc and take the most recent non-empty purpose. Composer banking signal: - loadBankingSummary now reads journal_entry_id alongside description/amount/date and returns per-counterparty `direction` ('in' | 'out' | 'mixed') and `has_unbooked` (any row not yet booked). Aggregate `unbooked_count` accompanies the rollup. - buildUserPrompt emits each counterparty as `Name: 12 345 kr (ut, OBOKFÖRD)` so Opus can tell income from cost on sight and tell which counterparties are still open questions. - SYSTEM_PROMPT now explicitly forbids verification questions about counterparties whose direction is unambiguous AND status is 'bokförd'. Should kill the regressions from the first agent build: * "Konsult, J 98 565 kr — intäkt eller kostnad?" when the amount is clearly negative. * "ALMI AB 493 000 kr — lån eller bidrag?" when the transaction is already categorized. Tests: 4112 unchanged. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent,ui): representation needs deltagare+syfte, drop duplicate doc icon Representation booking: - transaction-categorization prompt now requires the agent to capture participants (name + company) AND purpose before staging a representation categorization. SKV's representationsregler + ML 8 kap require the verifikation to document who attended and what the meeting was about; without that the avdrag is denied and the post should be booked as non-deductible / personalkostnad. - The agent confirms back in plain text (audit trail in the chat), writes the deltagare + syfte to gnubok_remember_fact (long-term), THEN stages. Saknas deltagare/syfte: explicitly tell the user the avdrag won't go through and offer the non-deductible alternative. - Known gap (followup, not this commit): the staged op's journal entry description doesn't yet carry the deltagare text. Until we add a `notes` field to gnubok_categorize_transaction, the audit trail lives in chat + agent_memory only. TransactionInboxCard duplicate attachment indicator: - Drop the FileCheck2 "open document" button from the trailing slot. TransactionAttachmentIndicator (Paperclip) next to the description already opens the underlag on click. Two icons doing the same thing was noise. Cleaned up the unused state (isOpeningDoc, hasAttachment, handleOpenAttachment) and dropped now-unused imports (FileCheck2, useToast). Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent,nav): notes on verifikation + redesigned sidebar Audit-trail notes for representation: - gnubok_categorize_transaction gains an optional `notes` string. Threaded through stagePendingOperation → commitCategorizeTransaction → createTransactionJournalEntry, which now appends notes to the entry's description (capped at 500 chars). The verifikation an external auditor reads now carries deltagare + syfte directly — not just chat history / agent_memory. - transaction-categorization prompt updated: representation flow now REQUIRES the agent to pass deltagare+syfte via the notes parameter. Without it the booking is non-deductible / personalkostnad per SKV. DashboardNav redesign: - Top section: flat, no header — Hem (/chat), Underlag (was Dokumentinkorg), Transaktioner, Granskning. Always visible; the inline badge on /pending shows the count when there are pending ops. - Mid section: four collapsible dropdowns (Försäljning, Inköp, Redovisning, Personal). Each auto-expands when the active route lives inside it. KPI moved from main to Redovisning. Extension nav items (TIC workspace, etc.) fold into Redovisning. - Bottom-left: new account popover (DropdownMenu, opens upward) holding CompanySwitcher, Inställningar, Hjälp, Support, Logga ut. Replaces the old top company-switcher card + the bottom Support/Logout block. - Mobile drawer mirrors the new structure: top items as flat list, same four dropdown groups, separate "Tillägg" section when extensions exist, "Mitt konto" section at the bottom. - i18n: invoice_inbox label renamed "Dokumentinkorg" → "Underlag" ("Documents" in en). New keys: mitt_konto, group_extensions. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nav): unhide Leverantörer under Inköp The /suppliers entry existed in navItems but was marked hidden — leftover from when the supplier list lived elsewhere in the IA. Removing the hidden flag puts Leverantörer in the Inköp dropdown alongside Leverantörsfakturor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nav): CompanySwitcher back to top-left, user account moves bottom-left The previous pass collapsed both concepts into the bottom popover. They mean different things: the company is the org context everything below operates against (top-of-sidebar, scannable); the user is the account-holder (bottom-of-sidebar, where settings/logout live). - (dashboard)/layout.tsx: fetch profiles.full_name alongside the existing identity queries; pass userName + userEmail into DashboardNav. - DashboardNav: restore CompanySwitcher at the top of the sidebar (pre-redesign placement). Bottom-left popover trigger now shows the signed-in user's name + single-letter initial (accountInitial helper falls back to email's first char, then "?"). Popover header carries full name + email; items unchanged (Inställningar, Hjälp, Support, Logga ut). CompanySwitcher removed from inside the popover — nested dropdowns were awkward and the top placement is where it belongs. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pending): trim the agent context strip The row-level AgentContextStrip on /pending was rendering the model name (eu.anthropic.claude-sonnet-4-6) and the full atoms array (horizontal/swedish-vat, vertical/konsult-it, …) inline, which made each row 60–80 chars of mostly-the-same metadata. Reviewers never scan that text; they scan amounts and decide approve/reject. Now the strip shows only the conversation deep-link (Konversation #<short id>) — the one piece that's actually useful for diving into context. Model + atoms remain available in agent_metadata for debugging surfaces; they're just not in the list view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): shared ground rules + paragraph breaks after tool calls Two regressions surfaced in real usage. Both are systemic. Shared agent ground rules: - /chat surface (general.help) was happily inventing four-digit BAS account numbers ("Debet 6212 - Molntjänster…", "Kredit 2614 - Ingående moms…") and proposing booking decisions on invoices it had never seen, with no follow-up questions about currency/scope/etc. - transaction-categorization had those rules baked into its prompt; general-help / bokslut-step / invoice-draft / supplier-invoice-review / verifikation-draft / vat-review never inherited them. - Extracted lib/agent/intents/shared-rules.ts with five cross-cutting rules: underlag first (check inbox + ask user to upload to Dokumentinkorgen when missing), ask follow-ups when ambiguous, never write four-digit BAS account numbers in chat (category names only), cite atoms / load skills (don't guess), check counterparty history before proposing. - Injected renderAgentGroundRules() into all six intents above. transaction-categorization left alone — it has more detailed inline rules tied to its specific underlag-flow. Paragraph break after tool calls: - text_delta from the model often resumes after a tool call without a leading newline ("kategoriseras." → gnubok_query_journal runs → "Inget historik hittades…" appended directly). Markdown rendered the concatenation as one paragraph. - AgentChat text_delta handler now inserts \n\n when (a) the buffer ends with text content, (b) the incoming delta starts with text content, (c) at least one tool call has run, and (d) the buffer doesn't already end with a blank line. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nav): default-open dropdown groups; closing is per-user Dropdowns started collapsed which meant first-time users had to open each group to discover what's inside. Inverted the state: default open, user can collapse, active route still forces a group open. - manualExpanded → manualCollapsed (semantics flip) - toggleGroup unchanged externally; flips the bit - isGroupExpanded returns !manualCollapsed[g] || hasActiveChild Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent): rate-safe v1→v2 TIC upgrade, counterparty defaults, profile settings Three pre-ship quality wins. Rate-limit-safe TIC v2 upgrade: - The /profile endpoint fans out to ~13 Lens calls; the account has a ~3000/mo ceiling. Force-refreshing every pre-v2 (v1) snapshot across the customer base would blow the budget. - ensureTicSnapshot gains an `upgradeV1` flag. A cached snapshot still inside the 7-day window is re-fetched only when (a) the caller passes upgradeV1 AND (b) the snapshot is v1-shaped (missing the v2-only `statuses` key). Gated to the two agent-onboarding call sites — a deliberate, once-per-company action and the only consumer of the v2 sections. Workspace + signup keep the natural 7-day staleness, so the v1→v2 migration is lazy and bounded to companies actually building an agent. Known-counterparty defaults (shared-rules): - Agent now proposes a sensible default for well-known counterparties instead of asking the same question monthly: Almi → lån, Tillväxtverket/ Vinnova/EU-stöd → bidrag, Skatteverket → skatt/avgift or återbäring, Bolagsverket → avgift, Försäkringskassan → ersättning, EF private withdrawal → eget uttag. Stated as an assumption the user can correct, not a hard rule — underlag/history still wins. Företagsprofil settings page: - New /settings/agent-profile (Företagsprofil / "Company profile"): view + edit the agent's company profile after onboarding — assistant name + avatar, the profile summary the agent reasons from, and a read-only chip view of loaded specialities (atoms). Backed by the existing GET/PATCH /api/agent/profile. - New GET /api/agent/atom-titles?ids= resolves atom slugs → human titles for the chips (registry is globally-readable reference data). - Added to SettingsSidebar; i18n keys agent_profile (sv "Företagsprofil" / en "Company profile"). Note: /chat already redirects unverified users to / (chat layout guard), and / renders WelcomeGate → /onboarding/agent. No redirect work needed. AgentSetupBanner.tsx is orphaned dead code (WelcomeGate superseded it). Tests: 4112. Build: green. Both new routes compile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(nav,agent): Hem=Översikt + separate Assistent button; memory dedup Nav restructure: - "Hem" now points to / (Översikt dashboard) again, not /chat. The agent chat gets its own top-level nav entry "Assistent" (Sparkles icon) → /chat. Mobile bottom nav mirrors this (Hem / Assistent / Transaktioner). - / restored to render DashboardContent (the Översikt) for built-agent users instead of redirecting to /chat. Users who haven't built their assistant yet still get WelcomeGate (the build-agent checklist); once verified, / shows the dashboard. Chat is reachable anytime via its nav entry. Restored main's dashboard data-fetch; added an agent_profiles verified_at probe to drive the WelcomeGate branch. - i18n: nav.assistant ("Assistent" / "Assistant"). agent_memory dedup (gnubok_remember_fact): - The agent re-remembers the same fact constantly (e.g. "Vercel = omvänd skattskyldighet" on every Vercel categorization), which would bloat agent_memory with paraphrases over months. - Before insert, compare the incoming fact against the 300 most-recent active memories by word-set Jaccard similarity (lowercased, punctuation- stripped, stopwords dropped). A near-duplicate (≥0.82) is treated as already-known: bump its relevance toward the new score + refresh updated_at instead of writing a new row. Embedding-free, zero added latency beyond one bounded SELECT. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent,nav): företagsprofil=Bolagsuppgifter, avatar nav icon, dedupe greeting Företagsprofil settings page (the right content this time): - Replaced the agent atoms/summary panel with CompanyProfileView — a read-only "Bolagsuppgifter" view of the cached TIC company snapshot (name, org-nr, form, address, F-skatt/Moms/Arbetsgivare, SNI, bank, verksamhet, employees, latest financials, status traffic-lights, fiscal year, firmateckning, företrädare). Server component reads the companies.tic_snapshot column directly — no extension import, stays inside the core-build boundary. - Route renamed /settings/agent-profile → /settings/company-profile. Removed the old AgentProfilePanel + the now-unused /api/agent/atom-titles endpoint. "Assistent" nav icon = the agent's chosen avatar: - DashboardNav reads agent identity from AgentSheetProvider and renders the onboarding-chosen avatar for the /chat ("Assistent") entry across desktop sidebar, mobile drawer, and mobile bottom nav. Falls back to the Sparkles glyph pre-onboarding (no avatar yet). Nav cleanup: - Dropped the beta badge from Underlag. - Filtered the TIC workspace (/e/general/tic, "Företagsprofil") out of the nav — the same Bolagsuppgifter now lives under Inställningar → Företagsprofil, so it shouldn't appear in two places. Doubled intake greeting fix: - /chat/intake fires an invoke with no conversation_id, then swaps the URL to /chat/[id] the instant the `conversation` event lands — which can beat the greeting being persisted. /chat/[id] then hydrated with 0 messages and, because the auto-fire guard keyed on (id && messages>0), fired a SECOND invoke on the same conversation → two greetings. Guard now keys on conversation-id presence alone: a set id means resume, never bootstrap. Closes the race. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): paragraph-break-after-tool split words mid-stream The earlier "insert \n\n when text resumes after a tool call" heuristic re-evaluated on EVERY text_delta (any delta not starting/ending with whitespace, once a tool had run). Streaming deltas arrive in sub-word chunks, so it injected breaks between fragments of the same word: "minnes\n\nno\n\nterna", "kund\n\nrep\n\nresentation". Replace the per-delta heuristic with a consume-once ref: - tool_use sets breakBeforeNextTextRef = true - the next text_delta consumes it: prepends \n\n exactly once (only when the buffer has content, doesn't already end in whitespace, and the delta doesn't start with whitespace), then clears the flag So the break fires once per tool→text resume, never mid-word. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): much shorter replies, representation headcount + VAT cap, dot separator Brevity (system-prompt Svarsformat — affects every reply): - Hard "korthet är regel nummer ett": aim for 2-4 sentences, lead with the answer/action, no warm-up ("Här är vad som gäller…"), don't derive VAT in prose, don't restate what the approval card shows, one question at a time. The agent was writing textbook-length essays. Representation rule now in shared-rules (so verifikation-draft, vat-review, etc. all get it — previously only transaction-categorization had it, which is why the verifikation flow guessed 25% VAT and skipped the cap): - Require ANTAL deltagare (headcount), not just one name — the moms deduction is per person (underlag cap 300 kr/person ex moms). - Use the receipt's ACTUAL VAT rate (usually 12% on food), never assume 25%. - Meal representation isn't income-tax deductible (post-2017); whole cost booked as non-deductible representation. Verifikation description separator: - createTransactionJournalEntry appended notes with an em-dash ("Utlägg Eatnam — Deltagare:…"), violating house style. Switched to a middle dot " · ". journal_entries has no separate notes column — the description IS the BFL verifikationstext / audit field, so deltagare + syfte correctly live there. Tests: 4112. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(settings): tidy Bolagsuppgifter — no status colours, clean firmateckning From first-look feedback on the Företagsprofil page: - Status: dropped the coloured traffic-light badges (red/yellow/green). Per the design system semantic colour is data-only, never chrome, so status now renders as plain label + date. Also filtered to dated entries only — Bolagsverket emits flags like "Har aldrig varit verksam" with no date that read as noise next to the real status. Ceased status gets muted destructive text (the one chrome colour the system keeps). - Firmateckning: the source text carries ">" list markers and crams several rules onto one line, and repeats "Firman tecknas av styrelsen" across rows. cleanSignatory() strips the markers, normalises whitespace, splits run-on "Firman tecknas …" clauses onto separate lines, and the render dedupes — so each rule reads as its own sentence. Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): inbox items expose all terminal links + processed flag The Eatnam receipt was booked against its bank transaction (so the inbox row had matched_transaction_id + created_journal_entry_id set), yet the agent reported it as loose/unmatched and a duplicate risk. Root cause: gnubok_list_inbox_items only selected and returned matched_supplier_id + created_supplier_invoice_id — the supplier-invoice path. The transaction-match and direct-journal-entry paths were invisible, so any receipt cleared via /transactions looked unprocessed. - list_inbox_items now selects + returns matched_transaction_id and created_journal_entry_id alongside the supplier fields, plus a derived `processed` boolean (true when ANY of the three terminal links is set). - New unprocessed_only=true input filters to items with no terminal link — the "what still needs handling" view that prevents the agent from flagging already-booked docs as duplicates. (Fetches a wider window then filters client-side so limit applies post-filter.) - Description updated to document the processed semantics, within the 280-char tool-description budget. The DB linkage itself already worked: /transactions attach-document sets matched_transaction_id, and commitCategorizeTransaction stamps created_journal_entry_id. This was purely a read/surface gap. Tests: 4112 (+ MCP description guard). Build: green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): repair stage-but-never-commit tools + consolidate tool surface - post_annual_depreciation AND reverse_entry were never in the pending_operations operation_type CHECK, so both staged then died with check_violation at INSERT. Add the CHECK migration, a commitPostAnnualDepreciation executor (reusing commitAnnualPostings), risk tier, and the PendingOperationType union member. - Salary tools de-risked: calculate_salary_run calls runSalaryCalculation() directly (no self-fetch/forged cookie); create_salary_run uses a transactional create-run helper with compensating delete; generate_agi actually generates + persists the declaration. - import_sie parses + validates at stage time with a content-rich preview (company, fiscal year, voucher/account counts, balance) instead of a blind byte count. - batch-match-invoices passed user.id where companyId was expected (silently matched zero). - VAT report+widget merged behind render_ui; gnubok_search_tools ranks by relevance; gnubok_feedback readOnlyHint corrected; tools/list instruction text fixed; income decision-tree + GL/query_journal cross-refs added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent): load skill atom bodies from the DB so they survive the build Skill bodies were read from disk at runtime (.claude/skills/**/SKILL.md); on Vercel the dynamic readFile path isn't traced into the lambda and on Docker .claude/ is excluded, so atoms loaded EMPTY in production — a despecialized agent. Inline the bodies into agent_atom_registry instead: - Migration adds body + mcp_exposed columns; a build-time generator (scripts/generate-skill-bodies.ts) emits a deterministic dollar-quoted seed migration with a content-hash manifest + --check CI guard. - Read sites (mcp-server atoms.ts, chat system-prompt.ts, composer prewarm) read body from the DB, with a dev-only disk fallback. mcp_exposed curates which atoms the MCP exposes (swarm-* never become atoms). - The seed script + generator share scripts/lib/atom-discovery.ts; estimated_tokens now reflects SKILL.md only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent): safe the in-app assistant — gating, FAB de-confliction, rate limit, friendly errors - Hide all agent entry points until verified_at: the Assistent nav tab (sidebar + mobile) and the agent-memory settings tab now match the floating FAB's gate. - FAB de-confliction: /kpi -> kpi.explain and /bookkeeping/year-end -> bokslut.step so the floating button opens the SAME assistant as the page button (no two-agents-on-one-page). - Generous per-user rate limit (30/min, 1000/day) on /api/agent/invoke, /onboarding/stream, /composer via a new agent_rate_counters table + check_and_increment_agent_quota RPC; fails open. Bounds runaway Bedrock spend without touching normal users. - Friendly errors: Bedrock 429/timeout/5xx normalized to Swedish (friendlyModelError) in run-turn + the invoke route; the chat client surfaces the server's friendly message instead of a raw HTTP status. - /chat/new validates ?intent= against the registry so bad deep-links fall back to general.help instead of rendering a broken-looking error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): keep /chat read-only — redirect categorization + swap the "categorize" suggestion for a VAT-report question general.help (the /chat assistant) is read-only, but it still gave per-transaction bokföringsförslag in prose and asked "godkänner du dessa?" — an analysis the user can't act on (no write tool, no per-tx underlag). Strengthen the prompt to redirect categorization/bokföring to the per-transaction flow (open the transaction -> "Fråga om denna transaktion", where the agent sees the underlag and stages a real ApprovalCard); a short overview is still allowed. Add a guard test locking in no-write-tools + the redirect language. Swap the /chat empty-state "Hjälp mig kategorisera" chip (which lured users into exactly this dead-end) for a VAT-report question the read-only assistant can actually answer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(pending): declutter the review queue rows + header Fold the conversation deep-link onto the actor label (drop the separate "Konversation #xxxx" strip and its icon), hide the quick-pick when there's only one operation type (it duplicated "Markera alla"), and drop the "(0)" from the disabled bulk-approve button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(vat): enhance VAT handling by integrating document validation and improving error messaging * feat(settings): add assistant knowledge surface + consolidate settings tabs Expose the agent's skill atoms (agent_atom_registry) in a read-only surface beside the existing memory view, and tighten the settings tab bar from 14 to 10 tabs. - New GET /api/agent/skills + AgentSkillsPanel: lists active, mcp_exposed atoms grouped by tier (Kärnkompetens / bransch / bolagssituation), flags which are active for the company from agent_profiles, and lazy-loads each SKILL.md body on expand. - New /settings/assistant tab with a Minne/Kompetens toggle (?view=skills); /settings/agent-memory and /settings/agent-skills redirect into it. - Merge Företagsprofil (TIC snapshot) into the Företag tab via CompanyProfileSection; /settings/company-profile redirects. - Merge Skatteverket-anslutningen into the Skatt tab — OAuth returnTo and the callback toast now target /settings/tax; /settings/skatteverket redirects. - Drop the Säkerhetsbackup tab (already under Importera/Exportera). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(inbox): keep booked underlag out of the unmatched queue + widen match window - categorize: after booking an inbox underlag onto a verifikat, backfill the inbox row's matched_transaction_id + created_journal_entry_id so it stops showing as unmatched (mirrors the /attach-document paperclip path). - TransactionMatchPicker: bias the candidate window forward (60d before → 180d after the invoice date) so late payments aren't dropped before scoring, and widen the ranking date tolerance to 120d so the true match floats to the top instead of collapsing to "Svag match". Fix "okatigoriserade" typo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wip: bundle in-progress branch work + agent onboarding chat optimizations Captures the uncommitted work-in-progress on this branch so it lives on the remote. Heterogeneous changeset — bundled as one commit since the work was already entangled across files. Headline change in this commit (from this session): - Remove the double interview in agent onboarding. Phase B's verification- question form stepper is gone — the Phase C chat (onboarding.intake) now owns the entire interview and reads the composer's verification_questions server-side as its question bank. - ReviewCard collapses from 3 steps to 2 (meet → review-and-confirm) with value-first ordering: profile + "vad jag kan hjälpa dig med" + facts + optional seed note. CTA reads "Möt {namn}" to signal the chat follows. - ChatIntakeStarter handoff subcopy updated to match reality (assistant greets first; user can leave anytime). - Stamp agent_profiles.intake_completed_at server-side in app/api/agent/invoke/route.ts on the first user-typed reply in any onboarding.intake conversation (idempotent IS NULL guard, best-effort). Closes the previously dead-write column and unlocks the opportunistic- follow-up hook the migration anticipated. Plus in-progress branch work being carried forward (not introduced here): agent runtime + intent prompts, composer + atom-discovery scripts, MCP server skills surface, onboarding flow components, dashboard/inbox tweaks, two new agent_atom_registry migrations, additional agent-chat tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent): drop inline "Fråga assistenten" affordances — rely on the FAB The bottom-right "Fråga {namn}" FAB (AgentTrigger) is already route-aware and picks the right intent per page, so duplicating it as inline page- header buttons and empty-state links is noise. Removed: - EmptyState `agentHelp` link ("Eller fråga {namn} hur du kommer igång") + the AgentHelpLink component + agent_default_name/agent_ask_link i18n keys + the agentHelp props on EmptyInvoices/EmptyCustomers/EmptyTransactions. - AgentSparkleButton on /bookkeeping (verifikation.draft) and /kpi (kpi.explain) page headers. The FAB stays — when verified, it appears on those routes and routes to the right intent automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): gate the last two ungated "Fråga assistenten" affordances Both surfaces previously called useAgentSheet directly without checking identity.isVerified, so they appeared pre-onboarding (everywhere else the FAB / sparkle buttons / /chat / Assistent nav are all gated on verified_at). - Settings page header: remove the "Fråga {namn}" pill entirely. The FAB covers /settings routes route-aware (settings.help) — no need for a duplicate inline trigger. - Invoice inbox transaction picker: hide the "Fråga assistenten" button when the agent isn't built. Done at the parent (InvoiceInboxWorkspace) by passing onAskAssistant only when identity.isVerified is true; the child renders the button only when the callback is present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tic,onboarding,agent): single-call TIC lookup + director-aware narrative voice - TIC: collapse the company lookup from 6 endpoint calls to 1 (search-public already exposes sniCodes, bank accounts, emails, phones, and registration flags). Derive fiscal-year MM-DD from mostRecentFinancialSummary; newly-registered companies fall through to the client's first-year defaults. - Onboarding: BankID picker no longer auto-provisions companies. Every pick routes through the wizard with orgnr (and entity_type via the CompanyRoles match) prefilled; F-skatt/VAT/address get confirmed in steps 2-4 instead of being auto-fetched. createCompanyFromOnboarding reuses CompanyLookupResult and adds a defensive top-level catch so server-action errors surface to the UI instead of being redacted. - Agent composer: loadUserDirectorship() checks BankID CompanyRoles for a director-like position (ceo/boardMember/chairman/externalSignatory, active) before the narrative uses second-person ownership voice ("Du driver…"); unknown users get neutral third-person voice so we never put ownership words in the user's mouth. Tests cover loadUserDirectorship, narrative voice, tic-fetch path, onboarding page, and updated TIC client + lookup/profile suites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tic): extend agent-onboarding TIC budget to 10s + backfill stranded org_numbers The 5s TIC fetch timeout aborted client-side before the upstream Lens fan-out (~13 calls) could complete, but the in-flight upstream calls still counted against quota — actions.ts already documents ~530 wasted calls from this in May. Same bug still applied to the agent-onboarding stream path. Adds an optional `timeoutMs` to `ensureTicSnapshot` so deliberate wait-screen callers (agent onboarding stream) can run with 10s while background/dev callers stay on the conservative 5s default. Page-level server fetch (page.tsx) intentionally stays at 5s to avoid blocking TTFB without a visible progress affordance. Backfill migration mirrors `company_settings.org_number` to `companies.org_number` for the 105 cases where it's safe (after dedup + conflict filtering). 56 of those are on active companies — unblocks duplicate guards, SIE/SRU exports, and TIC fallback chain. Zero TIC API calls — pure data move. Idempotent. Also sweeps a pre-existing SSRF guard on the stream route's origin derivation that was sitting unstaged in the working tree — it lives in the same diff hunks as the TIC budget change and couldn't be split cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wip: bundle in-progress branch work Sweep up uncommitted agent/MCP/RLS work-in-progress so the branch is fully backed up to origin. Not reviewed in detail — committed as-is to preserve working state alongside the TIC fixes in the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): tag the "Bygg din bokföringsassistent" CTA as Beta Adds a Beta badge next to the assistant-setup heading on the dashboard banner, dashboard inline card, and onboarding checklist row. Also drops the stale "Gratis i 30 dagar" subline from the dashboard card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(build,migrations): PendingOperationType salary ops + resolve migration version collisions PR #584 went red on three things: 1. core-only build / Vercel: `lib/pending-operations/commit.ts:2666` switched on 'create_salary_run' and 'generate_agi' but `PendingOperationType` was missing both literals. Add them to the union. 2. Supabase preview: migration version 20260526120000 collided with main's newly-merged 20260526120000_fix_replace_sie_import_hard_delete.sql. Bump the branch's pair to 20260526120050 / 20260526120051 — still ahead of 20260526120100_restvardeavskrivning so ordering is preserved. 3. 20260527170000 was used twice on this branch (_agent_rls_with_check + _journal_entry_no_doc_required). Bump the second to 20260527170100 so the pair stays orderable and Supabase doesn't choke on the duplicate schema_migrations PK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): reword comment so core-only guard stops flagging it The "Check no core imports from extensions" step greps for the literal \`from '@/extensions/\` across lib/, app/api/, components/. A comment in lib/agent/composer/tic-fetch.ts quoted the exact pattern verbatim to explain *why* the file does a self-fetch instead of importing the TIC extension directly — which the grep matched even though no actual import exists. Rewrite the line to keep the same meaning without the literal pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Emil <emilmattsson14@gmail.com> |
||
|
|
f8f49f8426 |
Inbox page-count gate + DataList/DropdownMenu primitives (#554)
* feat(inbox): skip AI extraction for multi-page PDFs (#553) Bedrock churns for minutes on multi-page PDFs (sales reports, bank statements, contracts) and returns nothing useful. Above 3 pages we now skip extraction entirely and mark the row with extraction_skipped=true; the document still lands in the inbox and can be attached or converted manually. Same gate applies to the /items/:id/attach path. Client can also opt out via skip_extraction=true (skip_reason=client_opt_out). The InvoiceInboxWorkspace renders an "Inte AI-tolkad" badge for skipped rows, distinct from the "Felaktig" failure state (status='error'). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(ui): introduce DataList + DropdownMenu primitives, roll out across list pages DataList replaces the per-row Card pattern across Granskning, Transactions, Invoices, Supplier invoices, and Pending. One bordered container with hairline rows matches the flat-with-hairlines aesthetic in CLAUDE.md — no shadows, no state-tinted borders, secondary token for selected/hover. DropdownMenu fills the gap for row-level action menus on TransactionInboxCard, TransactionHistoryList, and the page-level action menus on /transactions and /pending. Replaces ad-hoc Popover + buttons constructions. Migrates list pages and the transaction inbox/history components onto the new primitives. No behavior change beyond the visual unification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: add agent skills + gnubok domain skills, gitignore compliance reports .agents/skills/ + skills-lock.json + symlinks under .claude/skills/ check in the vercel-labs/agent-skills set pinned by the local skill manager (deploy-to-vercel, vercel-cli-with-tokens, react-best-practices, composition-patterns, react-native-skills, react-view-transitions, web-design-guidelines). Keeps the team on the same versions. .claude/skills/industry/ + .claude/skills/modifier/ are hand-authored vertical and entity-modifier skills for the specialized accountant agent — industries (konsult-it, e-handel, bygg-hantverk, reklambyra, saas-ai) and entity overlays (holding-ab, single-shareholder-ab-fmb, mixed- verksamhet). Project-owned content; lives in the repo by design. Also gitignores .compliance-reports/ — those are large generated SARIF/dossier artifacts from the compliance scanner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(transactions): unify inbox/history chrome, drop swipe flow The transactions page mixed two-tier filtering, a swipe-view detour, and per-row Card chrome that didn't carry its weight. This pass collapses those into a single editorial-list surface and removes the unused swipe path entirely. User-visible changes: - Mode toggle (Att bokföra / Alla transaktioner) moved from a Tabs row under the header into a dropdown to the right of a unified search bar. Search now persists when switching modes. - Removed the swipe categorization view ("Gå igenom alla") and its trigger button. The 800-line SwipeCategorizationView component is deleted; suggestion-fetching shrinks to what the template picker still consumes. - Inbox rows now show one primary action: invoice/supplier-invoice match shortcut when auto-detected, else "Bokför". A new visible Link2 icon button opens the customer or supplier invoice picker manually (chosen by amount sign). Delete becomes a plain trash button — no overflow menu since it only ever held one item. - Bulk action bar swaps "Markera som privat" for "Ta bort" with a single combined confirmation. - Built SupplierInvoicePicker mirroring InvoicePicker so expense transactions can be matched to supplier invoices from the inbox. Wired through /api/transactions/{id}/match-supplier-invoice. - Template picker dialog renamed to "Bokför transaktion"; "Bokför manuellt…" and "Matcha med faktura…" promoted from muted ghost buttons at the bottom to outline buttons at the top, above the template list. - Breathing room: row padding py-3 → py-4, primary text text-sm → text-base, amount text-base, button heights h-8 → h-9, trailing gap-2 → gap-3 (in the DataList primitive itself, so every list benefits slightly). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: align package-lock.json with merged package.json The merge resolution took origin/main's package-lock.json (which dropped pdf-lib) but kept our package.json (which still requires pdf-lib for the invoice-inbox extension's PDFDocument import). `npm ci` rejected the mismatch. Regenerate the lock from the merged package.json so both files agree. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: regenerate package-lock.json with npm@10 for CI compat Local npm@11 produced a lock that npm@10 (CI) rejected with "Missing: @swc/helpers@0.5.21". Regenerated with npm@10 --package-lock-only so CI can install. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(transactions): supplier-invoice status-leak guard + drop dead prop Two follow-ups from the merge-risk audit: - SupplierInvoicePicker now mirrors InvoicePicker's status-leak guard: if a supplier invoice is still 'approved'/'overdue' but already has a payment voucher attached (journal_entry_id on supplier_invoice_payments), hide it. Closes a UX race window between payment and status flip. Partially-paid invoices still pass through. - Drop the unused onMarkPrivate prop on TransactionInboxCard and the matching handleMarkPrivate wrapper in the parent. Both became dead when the swipe-categorisation flow was removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e4488a900b |
feat: add user locale preference to user_preferences table (#555)
* feat: add user locale preference to user_preferences table
- Introduced a new column 'locale' in the user_preferences table to store per-user UI language preferences.
- Added a CHECK constraint to ensure only supported locales ('sv', 'en') are allowed.
- Triggered a schema reload notification for the changes.
chore: declare CSS module support in TypeScript
- Added a declaration for CSS modules in globals.d.ts to enable TypeScript support for importing CSS files.
* feat: add Swish as an invoice payment method in company settings
|
||
|
|
980f29dae8 |
Bug/momsdeklaration skv (#449)
* fix(salary): show birthdate in masked personnummer, hide the 4-digit suffix Flip the personnummer display format from XXXXXXXX-NNNN to YYYYMMDD-XXXX so the sensitive 4-digit suffix is hidden while the (public) birthdate stays visible. Affects the employees list/detail, salary run, payslip PDF, payslip email, and the MCP server tools (list_employees, get_salary_run). Each call site now decrypts the stored personnummer before masking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(transactions): allow deleting unbooked transactions from "Alla transaktioner" The history list only let users delete via the inbox card; once a category or mall was picked but the verifikation hadn't been created, the row showed "Ej bokförd" with no way to remove it. The API already permits delete while journal_entry_id is null, so the gap was purely a missing UI affordance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(vat): populate ruta 20-24 for reverse charge + dishonest "Validera OK" Three connected issues caused Skatteverket to reject momsdeklarationer with FK004 even after our local "Validera"-knapp returned OK. 1. supplier-invoice-entries booked fiktiv moms (2614/2624/2634 + 2645/2647) on reverse-charge invoices but never the underlying basbelopp on 44xx/45xx. Ruta 30-32 filled up at SKV while ruta 20-24 stayed at 0 — SKV's FK004 ("silent netting prohibited", ML 13 kap kräver båda sidor). Fix: generateReverseChargeBasisLines in vat-entries.ts emits parallel 45xx/44xx debit + 4598 motkonto credit per rate group. Engine calls it from registration, cash, and credit-note paths. Skipped when the user booked the expense directly on a basis account to avoid double-counting. 4598 added to BAS reference (no migration needed; account_number is plain text on journal_entry_lines). 2. rutorToMomsuppgift rounded each ruta independently but computed summaMoms from the unrounded ruta49. SKV recomputes the sum from integer rutor on their side, so fractional öres caused ±1 SEK drift and SKV rejected with FK009. Fix: derive summaMoms from the already-rounded VAT-amount rutor. 3. "Validera"-knappen only confirmed SKV's internal arithmetic — a declaration with ruta 30-32 populated and ruta 20-24 empty validated fine until /utkast hit FK004. Users got a false green light. Fix: vat-declaration-checks.ts runs locally before the SKV call, blocks Validera/Spara when ERROR-level findings exist, and surfaces them in a separate "Lokala kontroller"-section. Success message reworded so SKV's OK is no longer presented as filing-ready. Tests: 4535/4536/4531/4425 lines + 4598 motkonto on EU/non-EU/byggtjänster RC, credit-note reversal, fractional-öres summaMoms, all four pre-flight codes (RC_BASIS_MISSING, RC_OUTPUT_MISSING, RC_INPUT_VAT_MISMATCH, SUMMA_MOMS_DRIFT). Backfill for already-posted entries follows in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: add skattekonto matching functionality - Enhance TransactionInboxCard to display a warning for potential 1930↔1630 transfers. - Implement match suggestions for skattekonto transactions in the backend. - Create SkattekontoMatchDialog component for linking skattekonto rows to existing journal entries. - Develop SkattekontoInboxCard component to handle skattekonto transactions in the inbox. - Introduce skattekonto-match utility functions for candidate matching and linking. - Update types to include match suggestions and enriched transaction responses. * refactor: reorganize skattekonto types and implement bank counterpart matching logic * docs: update CLAUDE.md to streamline integrations and clarify architecture details * refactor: enhance reverse charge logic to handle non-basis accounts and prevent double-counting --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
17c67fece0 |
Inbox UX overhaul + cross-currency supplier-invoice fixes (#444)
* feat(kpi): expense mix and top suppliers charts Replace the single monthly-trend chart with two additional compact visuals on /kpi: expense composition donut (BAS class 4-7) and top suppliers bar (supplier_invoices sum_sek over the fiscal period). KPIReport gains expenseComposition and topSuppliers fields, computed from the trial balance and supplier_invoices rows already fetched in the API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(nav): swap Deadlines sidebar slot for Dokumentinkorg Sidebar main-menu slot now points to the invoice-inbox extension. The /deadlines page stays accessible via dashboard widgets and direct links — only the prominent nav entry changes. Most users open gnubok to act on incoming documents, not to read tax deadlines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(supplier-invoices): cross-currency totals, FX residual, review SEK display Five fixes around foreign-currency supplier invoices: - Form layout: move Valuta / Växelkurs / Reverse charge from collapsed "Övrigt" into a visible row above the line-item table. Auto-fetch the Riksbanken rate when switching to a non-SEK currency; never clobber a user-typed rate; clear it when switching back to SEK. - Form submit: reset() the form on successful submit so the useUnsavedChanges hook detaches its beforeunload listener before the router.push, killing the "Are you sure you want to leave?" prompt that fired during Turbopack-mediated navigations. - BankTransactionPicker: drop the strict currency filter that hid every SEK transaction when the invoice was in EUR/USD. Cross-currency rows fall to the bottom with an "Annan valuta" hint instead of producing a meaningless numeric diff. - match-supplier-invoice route: when the bank transaction currency differs from the invoice currency, compute the FX diff against the AP-booked SEK and pass it to createSupplierInvoicePaymentEntry so 7960/3960 catches the residual instead of leaving a permanent stub on 2440. Fix also covers the "EUR transaction paying a SEK invoice" case that the first iteration missed. - Review dialog: buildJournalPreview now multiplies amounts by the exchange rate so the "Verifikation som bokförs" table shows the actual SEK numbers that hit the DB, not the EUR magnitudes labelled with no unit. Header gains an "(i SEK)" hint when foreign currency. Test coverage for the FX residual path covers SEK-SEK (no diff), SEK-into-EUR-invoice (loss), SEK-into-EUR-invoice (gain), foreign-tx- into-SEK-invoice, and the no-rate fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(inbox): rate limits, multi-file UX, onboarding, retry, supplier autolink Big workspace pass on /e/general/invoice-inbox. Highlights: Backend - New table inbox_rate_counters + RPC check_and_increment_inbox_quota. Postgres-backed (no Upstash dep) per-company limit: 30/min, 500/day. Applied at /upload, /inbound, and /items/:id/retry-extraction. - POST /items/:id/retry-extraction — re-runs the deterministic extractor on a stored document when the previous attempt errored. - POST /items/:id/match-supplier — links a freshly-created supplier back to the inbox item so the next action prefills correctly. - POST /api/transactions/create-from-document — creates an uncategorized manual transaction from an inbox item for the "I have a receipt, no bank transaction" case. The user categorizes through the normal flow. - /inbound caps email at 20 attachments/email; truncated count goes to processing_history as AttachmentsTruncated. Rate-limit drops emit RateLimitedDropped and return 200 so Resend doesn't retry. - attach-document side effect: when the document came from an inbox item, the inbox row's matched_transaction_id is updated so the UI can flip it to "Kopplad till transaktion" without a round-trip. New migration: re-introduces matched_transaction_id on invoice_inbox_items as a plain FK (the AI metadata that the previous migration stripped doesn't come back). Workspace UI - Onboarding card replaces the thin empty-state with a 3-step checkmark guide (Aktivera adress → Ladda upp → Matcha eller bokför). Auto-hides when all three steps are done; localStorage-backed dismiss. Beta badge + link to gnubok.se/priser. - Responsive layout: 3-pane at lg, 2-pane at md, master-detail toggle on phone (list xor detail with a back button). - Filter pills (Alla / Behöver åtgärd / Bearbetade / Fel) + search input above the list — client-side over the existing items list. - Multi-file upload queue with "Laddar X av N…" progress counter on the button. Sequential to avoid hammering pdfjs. Selection stays put during a batch (only single-file drops auto-jump the detail pane). - Bulk select + delete with sticky action bar. Items linked to a supplier invoice are skipped with a count toast. - Retry button in the FieldsRail error branch. - "Skapa transaktion från underlag" CTA in the match dialog when no unmatched bank transactions exist. Prefills date/amount/description from the extracted data; user picks the sign. - "Skapa leverantör" inline CTA when the extractor caught a supplier name with no match against existing suppliers. POSTs /api/suppliers with the extracted fields, then auto-links via /items/:id/match-supplier. - Matched-state CTA renamed to "Bokför transaktionen" with link to /transactions?highlight=<id> so the categorize panel auto-opens. Tests - lib/rate-limits/__tests__/inbox.test.ts — RPC wrapper happy/error/scope - app/api/transactions/create-from-document/__tests__/route.test.ts — auth, validation, 404/409/200/500, inbox-link failure tolerated - extensions/general/invoice-inbox/__tests__/retry-extraction.test.ts — auth, rate limit, 404, 409, 400 no-doc, success, extraction failure - attach-document tests extend coverage to the new inbox-link side effect (both success and best-effort failure paths) - inbound-webhook test mocks the rate-limit module so the queued-mock sequence in each existing test doesn't have to know about it CLAUDE.md gains a row for lib/rate-limits/ so the new helper is discoverable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transactions): paperclip indicator and highlight-row param Close the feedback loop after a user attaches a receipt to a transaction from the inbox: the row in /transactions now shows a paperclip icon when transaction.document_id is set, with a click handler that fetches a signed download URL and opens the document in a new tab. Works for both uncategorized and history views. When the inbox sends a user to /transactions?highlight=<id>, the page now scrolls that row into view and auto-opens the categorize panel if the transaction is still uncategorized. Behind a double-rAF so the row DOM exists when scrollIntoView fires. QuickReviewDialog no longer prompts to upload underlag when the transaction already has a doc attached (which it does after the inbox match flow). Shows "Underlag bifogat — Visa" instead, opening the existing doc in a new tab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pr-444): address review feedback (Greptile + compliance bots) Migration rules - New migration 20260512092423: adds updated_at trigger on inbox_rate_counters (CLAUDE.md rule 2) and explicit USING (false) RLS policies for the four DML verbs to make the SECURITY DEFINER-only intent explicit (rule 1). - New pg-real test inbox-rate-limit.pg.test.ts covering happy path, minute-cap rejection, day-cap rejection, per-company isolation, and the updated_at trigger firing. CLAUDE.md mandates *.pg.test.ts for every new RPC because mocks pass on broken PL/pgSQL. Bugs - Stale exchange rate on currency switch (Greptile P1) — userTouchedRateRef was scoped per session, not per currency. Switching EUR (with a hand-edited rate) → USD kept the EUR rate. Now tracks the last fetched currency in a ref and resets the touched flag on currency change while still honoring manual edits within a single currency. - topSuppliersResult.error silently swallowed (Greptile P2) — failed queries used to render an empty chart matching the no-data state. Logged now. - Currency from extracted_data not validated (GDPR Art.25(2), OWASP V4.5, Swedish compliance bot) — extracted PDF currency was inserted into transactions.currency without sanitisation. Allowlisted against the six supported ISO 4217 codes; coerce to SEK otherwise. - Idempotency gap on create-from-document (OWASP V2.3) — two concurrent POSTs with the same inbox_item_id could each pass the matched_transaction_id IS NULL read and insert duplicate transactions. UPDATE now includes .is('matched_transaction_id', null) as an optimistic-lock release and returns 409 with an orphan-transaction rollback when the predicate doesn't match. - FX residual on cash-method match path (Swedish compliance bot) — createSupplierInvoiceCashEntry has no exchange_rate_difference path, so a cross-currency match would silently leave a 1930 reconciliation gap. Added a guard that returns MATCH_SI_CASH_FX_UNSUPPORTED (400) before the JE is created. Users on cash method can switch to accrual or book the FX diff manually. Design system - gap-y-1.5 / gap-1.5 in KPIExpenseMixChart — replaced with gap-y-2 / gap-2 (CLAUDE.md design tokens; 2.5/1.5/5/hardcoded pixels are forbidden spacing values). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(migrations): rename to match applied versions The mcp__plugin_supabase_supabase__apply_migration tool stamps its own timestamp when it applies a migration to the live project, so the version recorded in supabase_migrations.schema_migrations differs from my local generation-time filenames. Renaming the local files so a production CD run sees the migrations as already-applied (matching versions) instead of trying to re-apply them — which would fail for the trigger/RLS migration (CREATE TRIGGER and CREATE POLICY don't support IF NOT EXISTS). Follows the pattern from d854efcd ("chore(migration): rename to match applied version"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(create-from-document): scope orphan rollback DELETE by company_id Defence in depth on the inbox-link race rollback. newTx.id is a fresh UUID from a company-scoped insert two statements above, so the existing single-key DELETE is already safe, but adding .eq('company_id', companyId) makes the cross-company invariant explicit on every write — addresses the OWASP ASVS V2.3 finding from the compliance swarm on PR #444. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(nav): mark Dokumentinkorg with Beta badge Same signal we use for Löner and Anställda — the inbox flow (AI extraction, supplier autolink, manual transaction creation) is in end-to-end customer testing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d0fbc2b616 |
refactor(ui): app-wide UI/UX consistency pass (#436)
* refactor(ui): app-wide UI/UX consistency pass
Net: +1,159 / −1,373 LOC across 77 files. No new features, no behavior
changes. Locks in a uniform design system across every dashboard surface.
What changed:
- **Foundation**: sidebar width 232→256px (md:w-64), spacing scale locked
(Tailwind 1/2/3/4/6/8/10/12; 2.5/5 forbidden), card padding p-6 default
(p-4 for compact metric cards), space-y-8 between page sections.
- **Tables unified**: all 33 thead blocks now share the Resultatrapport
pattern via shadcn Table primitive (text-[11px] font-medium uppercase
tracking-wider text-muted-foreground). Hand-rolled <table> instances
converted where they were data tables; form/edit grids kept distinct.
- **Status badges unified**: every status indicator routes through
shadcn <Badge variant>. Eliminated raw Tailwind colors
(bg-amber-100, bg-emerald-500/10, bg-blue-100, bg-purple-100, etc.)
in favor of the gnubok semantic palette (success=sage, warning=ochre,
destructive=terracotta).
- **Empty states unified**: list pages migrated from hand-rolled
"flex flex-col items-center py-12" divs to the EmptyState primitive.
- **Loading skeletons unified**: hand-rolled bg-muted rounded animate-pulse
divs replaced with shadcn <Skeleton> across 15 files.
- **Touch targets**: 6 back-buttons + edit-pencil + inbox delete bumped
from 24/32/36px to shadcn's 40px icon default. Added aria-labels on
9 icon-only navigation buttons.
- **Date formatting**: formatDate() for accounting data (ISO yyyy-MM-dd,
table-friendly) vs formatDateLong() for metadata (Swedish long form).
Raw {x.invoice_date} renderings routed through formatDate() in 18 sites.
- **Toast titles**: eliminated 33 generic "Fel" titles. Each toast title
now carries the action ("Kunde inte skapa lönekörning" etc.) with
description carrying the error detail.
- **Page-level cleanups**:
- Dashboard: dropped greeting hero + Snabbåtgärder/Att hantera nav
duplicates + Visa detaljer collapsible.
- Reports: 5-col mega-menu replaced with left-rail layout
(new ReportsNav component).
- Bookkeeping: fixed layout jump between Verifikationer/Ny verifikation
tabs (moved FiscalYearSelector inside journal tab).
- Bookkeeping: added voucher sort (A1 first / latest first) alongside
existing date sort. Required matching API param sort_by.
- KPI page: FiscalYearSelector instead of raw <select>; InfoTooltip
instead of inline info-button toggle; bigger numbers.
- Salary section: enum values translated to Swedish labels, mobile
table collapses to Anställd+Netto on <md, KPI typography aligned
with dashboard.
- Invoice forms: styled RequiredMark + aria-required, tabular-nums
on amount inputs.
- **CLAUDE.md**: new "Design System Tokens" subsection documents the
locked spacing scale, primitives table, typography rules, date helpers,
and forbidden patterns so future contributors don't drift.
Tests: 2,906 passing (unchanged). Lint: unchanged from main baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback (Greptile + compliance bot)
- **formatDate / formatDateLong timezone fix**: switch from new Date() to
parseISO. Bare yyyy-MM-dd strings are now parsed as local midnight rather
than UTC midnight, eliminating the off-by-one display in west-of-UTC
timezones flagged by Greptile.
- **DashboardContentProps cleanup**: removed unused firstName and settings
fields from the interface, and the corresponding fetch (profiles table)
+ computation in app/(dashboard)/page.tsx. The greeting was dropped in
the dashboard cleanup; these props were dead weight.
- **Voucher sort behavior documented**: extended the comment in the journal
entries API route to explain why voucher sort intentionally uses strict
fiscal_period_id filtering (BFL 5 kap 6–7 §§ — voucher numbers are
series-scoped within a fiscal year). The row-count delta between date
sort and voucher sort is now a documented design choice.
- **delete_last_voucher migration + draft-delete test included**: the UI
already shipped the "Radera utkast" path in the previous commit; this
pulls in the backing RPC migration that allows draft deletes (with the
full safety logic — drafts skip series/period checks since they have
voucher_number=0, posted entries go through the existing unchanged
path). This was originally meant for a separate PR but the UI shipped
half the feature without it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(migration): rename to match applied version
The function delete_last_voucher is already applied to the production DB
under version 20260509103736 (verified via pg_get_functiondef — exact
byte-for-byte match to file content). The previous file timestamp
20260509120000 would cause a fresh `supabase db push` to attempt re-applying
under a different version row. Renaming the file aligns local tracking
with what the database actually has.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address compliance bot findings (payroll label + VAT visibility)
- sick_karens label: drop "(första sjukdagen)" qualifier. Per sjuklönelagen
6 §, karensavdrag is a single calculated amount (20% of one week's
sjuklön) deducted from the first sick day's pay — not bounded to the
first day. The qualifier could mislead users when the first sick day
and return-to-work span a weekend. Swedish-payroll bot recommendation.
- Omvänd skattskyldighet badge: variant outline → warning. The reverse-
charge indicator is compliance-critical (ML 16 kap) — missing it leads
to incorrect input VAT deduction. Outline was too subtle; warning's
ochre fill matches its semantic weight.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fa7d4075cf |
Supp/invoice bfl errors (#390)
* feat(accounting): update accounting method validation and messaging for aktiebolag and enskild firma * Remove AI subsystem and related code - Deleted AI proposals and requests persistence logic from `lib/ai/proposals/persist.ts`. - Removed re-validation logic for proposals in `lib/ai/proposals/re-validate.ts`. - Cleaned up schemas related to AI flows in `lib/api/schemas.ts`. - Removed AI-related fields from bookkeeping engine in `lib/bookkeeping/engine.ts`. - Eliminated AI event types from `lib/events/types.ts`. - Updated tests to reflect the removal of AI-related functionality in `lib/extensions/__tests__/sectors.test.ts`. - Adjusted initialization logic in `lib/init.ts` to exclude AI proposal handler registration. - Cleaned up transaction ingestion logic in `lib/transactions/ingest.ts` to remove AI flow checks. - Updated helper functions in `tests/helpers.ts` to remove AI-related settings. - Removed AI-related types and interfaces from `types/index.ts`. - Added migration script to drop AI-related tables and settings from the database. * fix(migrations): ensure foreign key constraint is dropped before removing AI tables * feat(invoice-inbox): implement deterministic invoice field extraction and inbox provisioning - Added `extract-invoice-fields.ts` for extracting fields from PDF invoices using regex and pdfjs-dist, replacing the previous AI classifier. - Introduced `inbox-provisioning.ts` to manage company inbox addresses and rotation of inboxes using Supabase RPCs. - Created `resend-inbound.ts` for handling inbound email events and attachments via the Resend API. - Defined the extension manifest for the invoice inbox, specifying required environment variables and descriptions. - Migrated database schema to remove AI-related columns and tighten the status enum in `invoice_inbox_items`. * feat(invoice-inbox): remove AI-specific columns and tighten status enum * fix(skattekonto): remove manual entry creation reference from transaction input * fix(schemas): remove accounting method validation for aktiebolag in UpdateSettingsSchema |
||
|
|
bca00cc5bd |
fix: BankID fallback, bank import fixes, account deletion (#216)
* fix: BankID graceful fallback, bank import fixes, and account deletion - BankID: surface service_unavailable state in login/register with password fallback messaging; structured error codes in tic extension; poll failure counter in BankIdAuth avoids infinite retry when TIC API is down. - Transactions: allow deleting unbooked bank-synced and imported transactions (only posted entries remain protected); detect reconnect duplicates by also checking unbooked bank-synced rows in content-based dedup. - Enable Banking: key external_id by account iban/uid instead of connection id so reconnects don't create duplicates. - Bank import: detect SEB privatbanken CSV variant (Bokföringsdatum / Valutadatum headers) via regex. - Banking settings: replace full-screen sync loader with toast notifications. - delete_user_account: raise statement_timeout, pre-clear NO ACTION FK references, and disable audit/immutability triggers during CASCADE. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — ingest dedup scoping and migration EXCEPTION handler - ingest: split buildExistingTransactionMap into two maps. Booked rows (any source) remain consumed by any incoming raw transaction, but unbooked enable_banking slots are only consumed when the incoming raw transaction is also enable_banking. This preserves reconnect dedup while preventing false positives where a pending bank-synced row silently blocks a legitimately separate CSV row with the same date/amount. - delete_user_account: add EXCEPTION WHEN OTHERS handler that re-enables every legally required enforcement trigger before re-raising. Postgres transactional DDL already rolls back on abort, but the explicit guard makes the intent unambiguous and covers sub-transaction edge cases so enforcement triggers are never left disabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
bb336eba88 |
Fix/user feedback (#210)
* Add delete policies for provider consent tokens and provider OTC * Add trade name support for companies in settings and documents * Resolved currency selection issue * Enhance invoice line display with foreign currency support and update delivery date schema to allow empty values * Add currency display for journal entries and include currency metadata in transaction creation * Add trade_name column to company_settings for external display |
||
|
|
9dceb6285c |
fix: SIE import voucher range bugs + remove describe transaction (#161)
* fix: fix SIE import voucher range bugs from multi-tenant migration and remove describe transaction feature - Fix reserve_voucher_range RPC parameter mismatch (p_user_id → p_company_id) causing duplicate voucher errors - Fix create-accounts onConflict from user_id to company_id - Add reserve-then-adjust pattern: pre-reserve voucher range before batch insert, release unused range on partial failure - Add release_voucher_range DB function for safe rollback - Remove DescribeTransactionDialog, describe/batch-describe API routes, and related schemas (feature superseded) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — voucher range race condition and orphaned entries - Add p_reserved_highest upper-bound guard to release_voucher_range to prevent rolling back past numbers claimed by concurrent operations - Move highestInsertedVoucher tracking to after both headers AND lines succeed, preventing orphaned journal entries with no lines from being counted as "used" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
d9c95a7b59 |
feat: counterparty templates with multi-line patterns, batch matching, and settings cleanup (#118)
* feat: separate AR/AP/accounting into distinct nav groups (#92) Split the flat "Finans" sidebar group into three visually distinct sections — Försäljning (AR), Inköp (AP), and Redovisning — so users coming from Fortnox immediately find customer invoicing and supplier invoices as top-level concepts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: journal entry detail view, correction chain, and account name display - Add journal entry detail page at /bookkeeping/[id] with full entry view - Add correction chain API and component showing storno relationships - Add JournalEntryStatusBadge component for entry status display - Show debit/credit account names in template picker and review dialogs - Expand client-side BAS account name mapping with additional accounts - Show account codes on transaction inbox suggestion buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — N+1 query, duplicate name, nav dedup - Batch reverse-lookup into single query per BFS iteration (was N+1) - Differentiate account 2393 from 2893 in display names - Extract shared loop for desktop/mobile nav group rendering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: counterparty templates, Skatteverket extension, VAT form completeness, and UI cleanup - Add counterparty-based categorization templates (learned from user approvals and auto-ingestion) with fuzzy matching in the mapping engine - Add Skatteverket extension for direct VAT declaration submission via API - Complete VAT declaration form with all 30 SKV 4700 boxes (ruta 08, 35-42, 50, 60-62) - Fix ruta 49 formula to include import VAT (ruta 60+61+62) - Simplify dashboard UI: remove redundant icons from stat cards, customer cards, invoice list, supplier invoices; use Badge variants consistently - Add SkatteverketPanel component to reports page - Add categorization_templates and skatteverket_tokens migrations - Update tests and helpers for new types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback — VAT detection, migration timestamps, dedup - Fix detectVatTreatment to derive actual rate (12%/6%) from VAT line description instead of hardcoding standard_25 - Rename skatteverket_tokens migration to 20260324120001 to avoid duplicate timestamp with categorization_templates (fixes Supabase deployment failure) - Make refreshAccessToken accept previousRefreshCount param to enforce refresh limit contract at the type level - Fix rate limiter TOCTOU by claiming slot before await - Extract formatRedovisare/formatRedovisningsperiod to shared lib/skatteverket/format.ts — eliminates duplication between mappers.ts and SkatteverketPanel.tsx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: multi-line counterparty templates, batch matching, settings restructure Counterparty template engine: - Multi-line booking patterns (line_pattern JSONB) for complex entries with split VAT, tax accounts, and ratio-based allocation - Batch matching (findCounterpartyTemplatesBatch) — 1 DB query for all transactions instead of up to 3 per transaction - SIE voucher population (populateTemplatesFromSieVouchers) — extracts patterns from historical vouchers on import with dominance filtering - Source priority system (user_approved > sie_import > auto_learned) - Centralized counterparty: prefix helpers to prevent string fragility - Fix: re-approval path now updates line_pattern Transaction categorization: - /describe route returns counterparty_match in parallel with templates/AI - /categorize route accepts counterparty_template_id for direct booking - /suggest-categories uses batch matching, injects counterparty suggestions - transaction-entries supports all_lines_complete for multi-line patterns UI: - TemplatePicker shows "Tidigare motparter" section (no AI extension needed) - DescribeTransactionDialog shows counterparty match card with detail - QuickReviewDialog supports counterparty line patterns - JournalEntryPreview renders multi-line patterns with VAT/ratio math - Inline LinePatternEntry types replaced with shared import from @/types Settings restructure: - 8 tabs → 5: merged Säkerhet + Utseende + Kalender into Konto - Renamed "Motparter" → "Mallar" - CounterpartyTemplatesPanel: click-to-expand detail view with account lines, VAT, confidence, aliases, and delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — account_override guard, DELETE body parsing, stale test - Block account_override when counterparty_template_id is set (prevents corrupting stored template via override → upsert correction path) - Wrap DELETE request.json() in try-catch for malformed body (400 not 500) - Clean up stale 3-query mock enqueues in test for batch-based find Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
3050743013 |
Last feedback fixes (#116)
* Transaction updates. Email updates. Bookkeeping updates. * Added email confirmation box * Update components/invoices/SendInvoiceDialog.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update app/(dashboard)/invoices/[id]/page.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: restore deleted useEffect and remove stray JSX closing tag The SendInvoiceDialog useEffect body was accidentally removed in e32c9b3, leaving only the dependency array. Also removes a stray `/>` in the invoice detail page that broke the Turbopack build. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4d4a9a40c9 |
feat: AR/AP navigation separation and bookkeeping improvements (#109)
* feat: separate AR/AP/accounting into distinct nav groups (#92) Split the flat "Finans" sidebar group into three visually distinct sections — Försäljning (AR), Inköp (AP), and Redovisning — so users coming from Fortnox immediately find customer invoicing and supplier invoices as top-level concepts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: journal entry detail view, correction chain, and account name display - Add journal entry detail page at /bookkeeping/[id] with full entry view - Add correction chain API and component showing storno relationships - Add JournalEntryStatusBadge component for entry status display - Show debit/credit account names in template picker and review dialogs - Expand client-side BAS account name mapping with additional accounts - Show account codes on transaction inbox suggestion buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — N+1 query, duplicate name, nav dedup - Batch reverse-lookup into single query per BFS iteration (was N+1) - Differentiate account 2393 from 2893 in display names - Extract shared loop for desktop/mobile nav group rendering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a7be1aab17 |
feat: comprehensive UI design audit and normalization (#28)
* feat: import system improvements, INK2 fix, and Swedish text corrections - SIE parser: Windows-1252 and CP437 encoding detection and decoding - Bank file parser: add Nordea Business (Företag) CSV format - Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV - INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years - Dashboard: parallel Supabase queries, simplified dashboard page - Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures - Import wizard UI improvements across all steps - Migration: add 'bas_range' match type to sie_account_mappings constraint - Extensive new tests for SIE parser encoding and bank file parser Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: arcim migration wizard UX fixes, Sentry setup, and extension scaffolding Arcim migration wizard improvements: - Progress bar now excludes non-interactive steps (migrating/result) - Fix OAuth text to match target="_blank" behavior (new tab, not redirect) - Display month names instead of "Månad X" in preview - Fix Swedish typo "förifylla" in no-company-info message - Replace native checkboxes with shadcn Switch in options step - Add ConfirmationDialog before starting migration - Show progress percentage during migration - Add "Nästa steg" guidance and navigation links in result step - Add "Försök igen" button in error state (returns to options) - Add Bokio company ID help text (GUID from URL) - Add Fortnox integration add-on hint on connection failure Also includes: SIE import system improvements, INK2 fixes, Swedish text corrections, Sentry error tracking setup, and arcim-migration extension scaffolding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback - Fix OAuth error recovery blank page (restore provider from URL params) - Pass real userId to MigrationWizard instead of empty string - Remove ~50 debug console.log statements from sie-import.ts - Fix comment referencing account 3740 → 3741 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: comprehensive UI design audit and normalization Dashboard audit: - Fix muted-foreground contrast (4.31:1 → 5.08:1) for WCAG AA - Add prefers-reduced-motion media query for all animations - Replace border-l-2 accent anti-pattern with subtle full-border colors - Add aria-expanded to toggle buttons, role="status" to live counters - Fix touch targets on deadline buttons (28px → 36px) - Vary section spacing for rhythm (mb-12/mb-10/mb-8) - Remove unused imports and dead code Transactions audit + hardening: - Add pagination (200 per page) with "Ladda fler" button - Replace height animation with transform-only exit animation - Show batch progress in floating action bar during processing - Fix batch bar mobile overlap (bottom-20 on mobile) - Replace clickable badges with proper button elements - Add safe area padding to fullscreen swipe view - Add response.ok check to suggestion fetch - Add truncation to invoice number buttons Invoicing audit: - Remove border-l-4 accent pattern from invoice cards - Replace string concatenation with cn() utility Systemic sweep (34 files): - All page headings: font-bold → font-display font-medium (Fraunces) - All stat numbers: font-bold → font-display font-medium tabular-nums - All hard-coded blue/amber/emerald colors → design tokens - Remove all dark mode overrides (tokens handle automatically) - Tint pure white card background to 99% Design context added to CLAUDE.md with brand personality, aesthetic direction, and 5 design principles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: bookkeeping flow audit — design system, accessibility, UX - Replace raw <select> with shadcn Select component (JournalEntryForm) - Add confirmation dialog for account deletion (ChartOfAccountsManager) - Remove console.error from production code (JournalEntryList, JournalEntryForm) - Fix contradictory h-7/min-h-[44px] button sizing → h-10 (ChartOfAccountsManager) - Increase BAS catalog "Lägg till" touch target h-7 → h-9 - Improve loading state with spinner (JournalEntryList) - Improve empty state with icon, description, and guidance (JournalEntryList) - Add response.ok check on journal entry fetch - Add aria-expanded to entry expand buttons - Add tabular-nums to desktop debit/credit columns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: onboarding and empty state improvements Onboarding: - Replace font-serif with font-display (Fraunces) for brand consistency - Remove console.error calls from production code Empty states: - Fix broken /transactions/new link in EmptyTransactions (route doesn't exist) - Add actionHref fallback to EmptyCustomers when no onAction prop provided - Improve EmptyTransactions description copy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: clarify Swedish UX copy — terminology, errors, descriptions Terminology consistency: - "Försenad" → "Förfallen" for overdue invoices (customers/[id]) - "bokföringsorder" → actionable description in bookkeeping page - "verifikation har bifogats" → "underlag har bifogats" in doc warning - "Fortsätt ändå" → "Bokför utan underlag" (specific action) Error messages — replace generic "Fel" + "Något gick fel" with specific: - "Något gick fel vid bokföring" → "Transaktionen kunde inte bokföras" - "Något gick fel vid matchning" → "Transaktionen kunde inte matchas" - "Kunde inte hämta X" → "Kunde inte ladda X" + recovery hint - Add "Försök igen" guidance to all error toasts Page descriptions — replace redundant with actionable: - Invoices: "Skapa och hantera" → "Skicka, följ betalningar, skapa kreditnotor" - Bookkeeping: list of features → actionable description Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: design critique — dashboard affordance, reports description Dashboard: - Add ChevronRight indicator to clickable summary cards (Att få betalt, Koppla bank) to distinguish from static cards - Add cursor-pointer to linked cards Reports: - Replace feature list description with actionable guidance "Huvudbok, grundbok..." → "Generera skattedeklarationer..." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace generic "Fel" error toasts with specific messages Deadlines: 5 generic "Fel" → specific per-action titles (create, toggle, edit, delete, load) Expenses detail: 5 generic "Fel" → specific per-action titles (load, approve, pay, credit, delete) Expenses new: 3 generic "Fel" → instructional validation messages (supplier name, supplier selection, invoice number) Customers: 1 generic "Fel" → specific load error with recovery hint All error toasts now follow pattern: title = what failed, description = how to recover Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace all remaining generic "Fel" error toasts (37 instances) Systematic sweep across 12 dashboard pages replacing generic title: 'Fel' with context-specific error titles: - Load errors: "Kunde inte ladda [resurs]" - Action errors: "[Åtgärd] misslyckades" - Validation: "[Fält] saknas" Every error toast now tells the user what failed without needing to read the description. Recovery hints added where missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: import flow — normalize stat typography, remove console.warn - Replace font-bold with font-display font-medium on 13 stat numbers across SIEPreviewStep, BankFilePreviewStep, BankFileConfirmStep, ImportResultStep (missed by systemic sweep since these are in components/import/, not app/(dashboard)/) - Add tabular-nums to stat numbers displaying counts/currency - Remove console.warn in ArcimMigrationWorkspace Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: final cleanup — console statements, remaining font-bold stats Remove production console statements: - Step1EntityType: remove debug console.warn (dead code after onNext) - TransactionBookingDialog: remove console.error on doc link failure - JournalEntryAttachments: remove 3 console.error calls Normalize remaining font-bold stat displays: - SwipeCategorizationView: 3 instances (completion, amount displays) - NEDeclarationView: yearly result heading + value Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review feedback loadMoreTransactions: add inbox item enrichment matching fetchTransactions - Paginated transactions now fetch invoice_inbox_items in parallel - Fixes missing document indicator, template suggestions, and inbox match card for transactions loaded via "Ladda fler" fetchAllPages: add maxPages guard (default 500) to prevent infinite loop - If Arcim gateway returns hasMore:true indefinitely, the loop now exits after 500 pages instead of running forever Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
66a4027f1e |
feat: BAS data overhaul, currency revaluation, expenses, UI polish, and cleanup
- Update BAS account catalog with comprehensive SRU codes and K2 flags - Add currency revaluation service with tests and API route - Add expenses page and account deletion API - Enhance booking templates with new patterns and improved tests - Improve transaction categorization with template picker and description matching - Polish dashboard, onboarding, import, and transaction UIs - Refactor year-end service for multi-step closing - Move SRU generator to ne-bilaga, remove standalone SRU export - Remove unused dev docs, mock data, and extension hooks - Add invoice delivery note sequences migration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
03b569d708 |
refactor: consolidate extension system to general-only with manifest-driven architecture
- Remove all sector-specific extensions (construction, ecommerce, export, hotel, restaurant, tech) — only general-purpose extensions remain - Move NE-bilaga and SRU export from extensions to core reports (lib/reports/) - Move moms-box-mapping from extensions/export/shared to lib/vat/ - Replace per-extension API routes with catch-all dispatcher (app/api/extensions/ext/[...path]/route.ts) - Add manifest.json for each extension with metadata, env vars, and deps - Add api-routes.ts pattern for extension-defined API endpoints - Add code generation scripts (generate-extension-registry, create-extension) - Add extensions.config.json for opt-in extension loading - Add extensions.schema.json for config validation - Add email service interface with noop default (lib/email/service.ts) - Add CI workflow (core-build.yml) to verify core builds with zero extensions - Add migration 045: expand account_type CHECK for untaxed_reserves - Update CLAUDE.md with comprehensive extension system documentation - Update all report engines and bookkeeping services for new imports - Clean up extensions.schema.json to only list existing extensions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
39e407644d |
feat: unified document inbox, full BAS 2026, and document-transaction matching
- Expand BAS reference from ~180 to ~1,276 accounts (full BAS Kontoplan 2026) with K2 exclusion flags, per-class data files, and computed SRU codes - Evolve invoice inbox into unified document inbox handling invoices, receipts, and government letters with AI-powered classification (Claude Haiku Vision) - Add multi-pass document-to-transaction matching engine with greedy assignment for both supplier invoices (reference/amount/date/name) and receipts (weighted amount/merchant/date scoring) - Add supplier invoice matching in transaction ingest pipeline - Inject booking template suggestions into AI extraction prompts - Surface matched documents in swipe categorization UI with one-tap booking - Auto-activate missing BAS accounts during SIE import against full reference - Add K2 filter toggle in Chart of Accounts manager - Add receipt confirmation route with BFNAR representation fields - Add database migrations for K2 support and document matching columns - Remove obsolete extension migration scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
6956a757f3 |
feat: consolidate booking templates from 100 to 48 and improve codebase
Reduce booking template library to eliminate duplicate suggestions when users describe transactions. Templates with identical accounting treatment (same account + VAT) are merged, keywords consolidated, and the entire subscriptions group is eliminated. Also includes prior work on reports, extensions, and transaction improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
3e7fa45ed6 |
feat: transaction categorization UX improvements and description matching
Add journal entry preview, human-readable account names, auto-apply VAT, fallback template suggestions, example prompts, invoice match comparison, and batch result feedback. Also includes user-description-match extension, describe/batch-describe API routes, improved AI categorization with multi- suggestion support, and template embedding search. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
acb85edf4a |
feat: wire Zod validation into API routes, improve types and components
- Add 8 new Zod schemas (UpdateCustomer, UpdateSupplier, UpdateSupplierInvoice, UpdateAccount, BankUnlink, RunReconciliation, CorrectJournalEntry, EvaluateMappingRules) and wire validateBody() into 24 JSON-body API routes - Remove redundant manual validation checks replaced by Zod - Add comprehensive schema tests (222 tests) - Improve type definitions in types/index.ts with expanded interfaces - Refactor extension types (push-notifications, receipt-ocr) for cleaner imports - Update transaction components (BatchCategorySelector, SwipeCategorizationView, QuickReviewDialog, VatTreatmentSelect) and invoice inbox workspace - Add invoice-inbox utilities and type decoupling tests - Fix NE-bilaga, SRU export, and invoice PDF template type usage - Update CLAUDE.md with expanded architecture documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
44231d0000 |
feat: add transaction booking dialog with manual journal entry
Replace CategoryExpandedDialog with TransactionBookingDialog that embeds the JournalEntryForm for full manual booking. Add /api/transactions/[id]/book endpoint. Add VAT treatment selector to CategoryExpandedDialog. Add booking buttons to TransactionHistoryList and improve TransactionInboxCard actions. Fix animation timing in transactions page state updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
59d935f2cc | Invoice fix and new inbound invoice extension etc |