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>
This commit is contained in:
Mattsson
2026-07-24 15:03:50 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 51ca574ca4
commit 53e343ee92
71 changed files with 3085 additions and 452 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"mcpServers": {
"accounted": {
"type": "http",
"url": "https://app.gnubok.se/api/extensions/ext/mcp-server/mcp"
"url": "https://app.accounted.se/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted"
}
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
# Accounted plugin for Claude Code
The official plugin for [Accounted](https://app.gnubok.se), the open-source Swedish bookkeeping platform. Installing it gives Claude two things at once:
The official plugin for [Accounted](https://app.accounted.se), the open-source Swedish bookkeeping platform. Installing it gives Claude two things at once:
1. **The connection**: the Accounted MCP server (90+ bookkeeping tools, resources, and loadable skills) via OAuth. No API key needed.
2. **The flows**: seven short workflow skills that follow the Swedish bookkeeping rhythm. Each one grounds itself in your company's live data, loads the product's Swedish accounting knowledge when it needs it, and stages every write for your approval. Nothing is ever booked without you saying yes.
@@ -26,15 +26,15 @@ Then run `/mcp` and authenticate with Accounted (OAuth consent screen; read-only
| `/accounted:payroll` | Monthly salary run and AGI underlag |
| `/accounted:year-end` | Bokslut, readiness-gated |
The skills are deliberately thin: the deep procedural and regulatory content (month-end checklist, VAT rutor, payroll rules, bokslut law) lives server-side in Accounted and is loaded at need via `gnubok_load_skill`, so it is always in sync with the product and tailored to your company. `gnubok_list_skills` shows everything available.
The skills are deliberately thin: the deep procedural and regulatory content (month-end checklist, VAT rutor, payroll rules, bokslut law) lives server-side in Accounted and is loaded at need via `accounted_load_skill`, so it is always in sync with the product and tailored to your company. `accounted_list_skills` shows everything available.
## How writes work
Every write tool in Accounted stages a **pending operation** with a preview instead of booking directly. Claude shows you the preview; only `gnubok_approve_pending_operation`, after your explicit approval, books it. Period locks and Swedish accounting law (immutable vouchers, balanced entries, sequential voucher numbers) are enforced by the product itself.
Every write tool in Accounted stages a **pending operation** with a preview instead of booking directly. Claude shows you the preview; only `accounted_approve_pending_operation`, after your explicit approval, books it. Period locks and Swedish accounting law (immutable vouchers, balanced entries, sequential voucher numbers) are enforced by the product itself.
## Self-hosted
Point the MCP connection at your own instance instead: remove the bundled server and add your own with `claude mcp add --transport http accounted https://your-host/api/extensions/ext/mcp-server/mcp`, or use the [`gnubok-mcp`](https://www.npmjs.com/package/gnubok-mcp) stdio bridge with a `gnubok_sk_` API key.
Point the MCP connection at your own instance instead: remove the bundled server and add your own with `claude mcp add --transport http accounted "https://your-host/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted"`, or use the [`accounted-mcp`](https://www.npmjs.com/package/accounted-mcp) stdio bridge with your existing Accounted API key.
## Disclaimer
+4 -4
View File
@@ -10,13 +10,13 @@ Work through unbooked bank transactions and receipts and stage correct vouchers.
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing` (accounting method changes how income is booked: faktureringsmetoden credits 1510 via invoices, kontantmetoden books on payment).
2. Find what is unbooked: start from `Accounted://attention`, then discover the listing and categorization tools with `gnubok_search_tools` (for example "uncategorized transactions", "categorize", "upload receipt").
1. If not already done this session, call `accounted_get_agent_briefing` (accounting method changes how income is booked: faktureringsmetoden credits 1510 via invoices, kontantmetoden books on payment).
2. Find what is unbooked: start from `Accounted://attention`, then discover the listing and categorization tools with `accounted_search_tools` (for example "uncategorized transactions", "categorize", "upload receipt").
3. For each item, decide the posting in this order of authority:
1. Explicit mapping rules the company has configured.
2. Observed history in `Accounted://ledger/context`: how THIS company booked this counterparty before (dominant account, VAT treatment, frequency as evidence). Prefer these over textbook answers, but frequency is not permission to auto-post.
3. Only then general knowledge, and for VAT treatment always load it: `gnubok_load_skill("horizontal/swedish-vat")` for deductibility edge cases (representation caps, EU trade, reverse charge). Never answer Swedish VAT from memory.
4. Stage the categorizations, grouped by counterparty so the user can approve in coherent batches. Present each preview with account, VAT treatment, and why. Approve only what the user confirms, via `gnubok_approve_pending_operation`.
3. Only then general knowledge, and for VAT treatment always load it: `accounted_load_skill("horizontal/swedish-vat")` for deductibility edge cases (representation caps, EU trade, reverse charge). Never answer Swedish VAT from memory.
4. Stage the categorizations, grouped by counterparty so the user can approve in coherent batches. Present each preview with account, VAT treatment, and why. Approve only what the user confirms, via `accounted_approve_pending_operation`.
5. If a tool response carries `period_status` locked or closed, stop that item and explain; never work around a period lock.
6. Finish with a short summary in the user's language: booked, skipped and why, what remains, and the next relevant deadline.
+2 -2
View File
@@ -9,13 +9,13 @@ A read-only pass over the books that ends in a short prioritized list. This flow
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing`.
1. If not already done this session, call `accounted_get_agent_briefing`.
2. Read `Accounted://attention`, `Accounted://period/active`, and `Accounted://recent-activity`.
3. Assess, in this order:
- Unbooked backlog: how many items, how old is the oldest?
- Unreconciled bank transactions in the active period.
- Overdue customer invoices and unpaid supplier invoices.
- Unapproved pending operations waiting on the user (`gnubok_list_pending_operations`).
- Unapproved pending operations waiting on the user (`accounted_list_pending_operations`).
- Upcoming deadlines: moms, AGI, F-skatt, bokslut. Use dates from the product, not memorized ones.
- Period status: is a period that should be closed still open?
4. Present a short table in the user's language: finding, severity, and which flow fixes it (`/accounted:bookkeep`, `/accounted:month-close`, `/accounted:vat`, `/accounted:payroll`, `/accounted:year-end`).
+3 -3
View File
@@ -10,9 +10,9 @@ Run the monthly close as a checklist against live data. The authoritative checkl
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing` and read `Accounted://period/active` to confirm which period is being closed.
2. Load the checklist: `gnubok_load_skill("month-end-close")`. Follow it step by step with the company's real numbers.
3. Bank first: if unreconciled transactions exist in the period, load `gnubok_load_skill("bank-reconciliation")` and clear them before anything else. Unbooked items route through the `/accounted:bookkeep` flow.
1. If not already done this session, call `accounted_get_agent_briefing` and read `Accounted://period/active` to confirm which period is being closed.
2. Load the checklist: `accounted_load_skill("month-end-close")`. Follow it step by step with the company's real numbers.
3. Bank first: if unreconciled transactions exist in the period, load `accounted_load_skill("bank-reconciliation")` and clear them before anything else. Unbooked items route through the `/accounted:bookkeep` flow.
4. Work the remaining checklist items (accruals, recurring vouchers, control balances). Every correction is staged and individually approved by the user; use storno-style corrections through the product's tools, never edit posted entries.
5. If the month ends a VAT period, hand over to `/accounted:vat` rather than improvising the momsdeklaration inside this flow.
6. Lock or close the period only as the final step, only after the user explicitly confirms, as its own staged operation.
+3 -3
View File
@@ -10,10 +10,10 @@ Run the monthly salary cycle: verify the salary run, stage the bookings, prepare
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing`. If the company has no employees, say so and stop (an enskild firma owner takes eget uttag, not salary; offer to explain via the loaded skill).
2. Load the knowledge: `gnubok_load_skill("payroll-monthly")` (the monthly procedure) and `gnubok_load_skill("horizontal/swedish-payroll")` for rules (skatteavdrag, arbetsgivaravgifter and age reductions, formaner, karensavdrag, semesterloneskuld).
1. If not already done this session, call `accounted_get_agent_briefing`. If the company has no employees, say so and stop (an enskild firma owner takes eget uttag, not salary; offer to explain via the loaded skill).
2. Load the knowledge: `accounted_load_skill("payroll-monthly")` (the monthly procedure) and `accounted_load_skill("horizontal/swedish-payroll")` for rules (skatteavdrag, arbetsgivaravgifter and age reductions, formaner, karensavdrag, semesterloneskuld).
3. Verify the month's salary run in the product: does it exist, is it complete, are one-off items in (bonus, sick days, formaner, utlagg)? Ask the user about anything the data cannot show.
4. Stage the salary bookings from the run and present the preview: gross, skatteavdrag, arbetsgivaravgifter, net, per the 7xxx account mapping the product produces. The user approves via `gnubok_approve_pending_operation`.
4. Stage the salary bookings from the run and present the preview: gross, skatteavdrag, arbetsgivaravgifter, net, per the 7xxx account mapping the product produces. The user approves via `accounted_approve_pending_operation`.
5. Prepare the AGI underlag and state the filing deadline (from the product's deadline data, not memory). Filing with Skatteverket is the user's action unless the integration is connected.
6. Report in the user's language: booked, AGI status, deadline, anything pending.
+2 -2
View File
@@ -9,7 +9,7 @@ Verify the connection, learn who this company is, and surface what needs attenti
## Flow
1. Call `gnubok_get_agent_briefing`. This is the single source for company facts: entity type (aktiebolag or enskild firma), accounting method (faktureringsmetoden or kontantmetoden), VAT period, employees, and ledger context. Never assume these; the flows below behave differently depending on them.
1. Call `accounted_get_agent_briefing`. This is the single source for company facts: entity type (aktiebolag or enskild firma), accounting method (faktureringsmetoden or kontantmetoden), VAT period, employees, and ledger context. Never assume these; the flows below behave differently depending on them.
- If the call fails with an auth error, the MCP server is not connected yet: tell the user to run `/mcp` and authenticate with Accounted (OAuth consent screen; read-only scopes by default, write scopes are ticked explicitly). Self-hosted users: see the plugin README.
2. Read `Accounted://attention` and `Accounted://period/active`.
3. Present a short orientation in the user's language: company name and form, active fiscal period and its lock status, and the top 3 items needing attention.
@@ -20,7 +20,7 @@ Verify the connection, learn who this company is, and surface what needs attenti
- `/accounted:vat` - prepare the momsdeklaration
- `/accounted:payroll` - monthly salary run and AGI
- `/accounted:year-end` - bokslut
5. Mention that deeper, company-tailored guides exist on the server: `gnubok_list_skills` lists them (workflow guides plus Swedish regulatory skills, filtered to this company), and `gnubok_load_skill(slug)` loads any of them.
5. Mention that deeper, company-tailored guides exist on the server: `accounted_list_skills` lists them (workflow guides plus Swedish regulatory skills, filtered to this company), and `accounted_load_skill(slug)` loads any of them.
## Rules
+3 -3
View File
@@ -10,10 +10,10 @@ Prepare the momsdeklaration underlag from the ledger and reconcile it before any
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing`: it gives VAT registration status, period length (monthly, quarterly, yearly), and accounting method. If the company is not VAT registered, say so and stop.
2. Load the knowledge: `gnubok_load_skill("quarterly-vat-review")` (the review procedure, valid for any period length) and `gnubok_load_skill("horizontal/swedish-vat")` for ruta mapping and edge cases (reverse charge, EU trade, import VAT, representation).
1. If not already done this session, call `accounted_get_agent_briefing`: it gives VAT registration status, period length (monthly, quarterly, yearly), and accounting method. If the company is not VAT registered, say so and stop.
2. Load the knowledge: `accounted_load_skill("quarterly-vat-review")` (the review procedure, valid for any period length) and `accounted_load_skill("horizontal/swedish-vat")` for ruta mapping and edge cases (reverse charge, EU trade, import VAT, representation).
3. Verify the period is fully booked first: any unbooked transactions in the period make the return wrong. Route gaps through `/accounted:bookkeep` before continuing.
4. Generate the VAT report with the product's tools (discover via `gnubok_search_tools`, for example "vat report momsdeklaration") and reconcile: report rutor against the 26xx account balances, and against the previous period for anomalies.
4. Generate the VAT report with the product's tools (discover via `accounted_search_tools`, for example "vat report momsdeklaration") and reconcile: report rutor against the 26xx account balances, and against the previous period for anomalies.
5. Present a per-ruta summary in the user's language, flagging anything unusual with the ledger evidence behind it.
6. Booking the VAT settlement (redovisning mot 2650/1650) is a staged operation the user approves. Filing with Skatteverket is the user's action: prepare the underlag, state the deadline, and where the product's Skatteverket integration is connected, point at it.
+5 -5
View File
@@ -10,12 +10,12 @@ Run the bokslut as a readiness-gated, step-by-step flow. This is the highest-sta
## Flow
1. If not already done this session, call `gnubok_get_agent_briefing`: entity type decides the whole shape (aktiebolag: bolagsskatt and resultatdisposition; enskild firma: NE-bilaga, egenavgifter, rantefordelning).
2. Readiness first: call `gnubok_year_end_readiness` (read-only preflight). Every blocker it reports is fixed through the other flows (`/accounted:bookkeep`, `/accounted:month-close`, `/accounted:vat`) before continuing. Do not start closing entries on a year that is not ready.
3. Load the knowledge: `gnubok_load_skill("year-end-close")` (the procedure) and `gnubok_load_skill("horizontal/swedish-year-end-closing")` (the law and the account-level detail). When the user wants to optimize (periodiseringsfond, overavskrivningar), also load `gnubok_load_skill("horizontal/swedish-tax-planning")` and present options with trade-offs, not a single answer.
1. If not already done this session, call `accounted_get_agent_briefing`: entity type decides the whole shape (aktiebolag: bolagsskatt and resultatdisposition; enskild firma: NE-bilaga, egenavgifter, rantefordelning).
2. Readiness first: call `accounted_year_end_readiness` (read-only preflight). Every blocker it reports is fixed through the other flows (`/accounted:bookkeep`, `/accounted:month-close`, `/accounted:vat`) before continuing. Do not start closing entries on a year that is not ready.
3. Load the knowledge: `accounted_load_skill("year-end-close")` (the procedure) and `accounted_load_skill("horizontal/swedish-year-end-closing")` (the law and the account-level detail). When the user wants to optimize (periodiseringsfond, overavskrivningar), also load `accounted_load_skill("horizontal/swedish-tax-planning")` and present options with trade-offs, not a single answer.
4. Work the bokslutstransaktioner in the loaded order, one staged operation at a time, each approved by the user with the amounts and accounts visible.
5. The final close (`gnubok_run_year_end`) is high-risk: run it only after readiness is green again and the user has explicitly confirmed in this conversation.
6. Report what remains outside the ledger: arsredovisning and filing for AB (load `gnubok_load_skill("horizontal/swedish-financial-reporting")` if asked), INK2 or NE underlag, deadlines from the product's data.
5. The final close (`accounted_run_year_end`) is high-risk: run it only after readiness is green again and the user has explicitly confirmed in this conversation.
6. Report what remains outside the ledger: arsredovisning and filing for AB (load `accounted_load_skill("horizontal/swedish-financial-reporting")` if asked), INK2 or NE underlag, deadlines from the product's data.
## Rules