Fix/mcp and req (#753)

* feat(api): test-mode API keys force dry-run on the v1 REST API

A key created with mode='test' (prefix gnubok_sk_test_) binds to the real
company, but the v1 wrapper forces dry_run on every write so nothing is
persisted or sent. Mutations on endpoints that can't be simulated
(dryRunSupported=false or unregistered) are refused with 403
TEST_KEY_WRITE_BLOCKED — fail-closed. Reads pass through unchanged and every
test-key response carries X-Gnubok-Mode: test. Live keys are unaffected
(mode defaults to 'live').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(invoices): company default "Vår referens" + per-line sales-account override

Add company_settings.default_our_reference (settings form, schema, type); the
invoice editor pre-fills our_reference from it on new invoices only, never
overwriting an edited draft. Separately, add an optional per-line
försäljningskonto (class-3) override in the editor — left blank, the engine
still derives the revenue account from the VAT rate, and reverse-charge/export
lines ignore the override.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(invoices): render a Swish payment QR on invoice PDFs

Build the Swish "Type C" QR payload offline (no Swish API call) and embed it as
a PNG in the invoice PDF payment box when Swish display is enabled, the invoice
is in SEK, and the amount is positive. Also surface the invoice number in the
payment box. Wired through every PDF render path: send, mark-sent and pdf
routes (both legacy and v1), the recurring-schedule sender, and the staged-send
commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(bookkeeping): draft exclusion + correction-chain collapse on verifikationslista

Extend list_fiscal_period_entries_with_related with two opt-in params:
p_exclude_draft (keep drafts off the committed list — they get their own
surface) and p_collapse_corrections (render a correction group as the single
live correction, hiding the mechanical storno and the reversed original).
Both default false; nothing is deleted, every voucher keeps its number, and a
"show all" toggle exposes the full chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(reports): link multi-year SIE periods so resultatrapport shows the prior year

SIE import now sets fiscal_periods.previous_period_id in both directions when
creating a period, so multi-year files chain correctly regardless of #RAR order.
A backfill migration repairs periods imported before this (idempotent; only
touches NULL links on first-of-month periods). generateResultatrapport falls
back to the date-adjacent prior period when the chain is still null, so the
comparison column works for legacy data too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(articles): hide the VAT field for non-momsregistrerade companies

The article form reads company_settings.vat_registered and, when false, hides
the moms field and forces vat_rate to 0 on submit — mirroring the invoice
editor so a non-VAT-registered company never sets a rate it can't charge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(import): allow file-based imports in the sandbox

Bank-file, CSV/Excel and SIE imports run entirely on uploaded data with no
external service, so they're now reachable in the sandbox. Only the API-backed
options that need live third-party credentials (PSD2 bank connection, provider
migration) stay disabled. Updates the sandbox notice copy to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(bookkeeping): add edit draft functionality for journal entries

* feat(database): add default "Vår referens" column to company_settings for invoicing

* fix(tests): set SHOW_SWISH_ON_INVOICE to false in PDF template mocks

* @
fix(payments): use roundOre for Swish amount formatting

Replace naive Math.round(x*100)/100 with roundOre from @/lib/money to
satisfy the antipattern guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-06-18 11:49:33 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2d6ddeafc5
commit 241959513b
54 changed files with 1634 additions and 120 deletions
+25 -2
View File
@@ -1228,6 +1228,9 @@
"swish_error": "Invalid Swish number (business number 123XXXXXXX or mobile number 07XXXXXXXX)"
},
"settings_invoice_form": {
"default_our_reference_label": "Default \"Our reference\"",
"default_our_reference_placeholder": "E.g. your name",
"default_our_reference_help": "Pre-filled on new invoices. Editable per invoice.",
"heading": "Invoice settings",
"prefix_label": "Invoice prefix",
"prefix_placeholder": "e.g. F-",
@@ -1238,6 +1241,7 @@
"default_notes_help": "Suggested automatically for new invoices."
},
"settings_pdf_print": {
"coming_soon": "Coming soon",
"heading": "Print & PDF",
"toast_save_failed": "Could not save",
"ore_rounding_label": "Öre rounding",
@@ -1249,7 +1253,7 @@
"show_plusgiro_label": "Show plusgiro",
"show_plusgiro_help": "Show plusgiro number on invoice printout",
"show_swish_label": "Show Swish",
"show_swish_help": "Show Swish number on invoice printout",
"show_swish_help": "Show swish number and qr code on invoice",
"show_logo_label": "Show logo",
"show_logo_help": "Show uploaded logo in the invoice header",
"show_company_name_label": "Show company name on invoice",
@@ -1477,6 +1481,12 @@
"toast_create_failed": "Could not create key",
"toast_revoked": "Key revoked",
"toast_revoke_failed": "Could not revoke key",
"mode_label": "Environment",
"mode_live": "Live",
"mode_test": "Test",
"mode_live_help": "Live keys operate on your real company and its books.",
"mode_test_help": "Test keys simulate every call (forced dry-run) — you see exactly what would happen, but nothing is saved or sent.",
"badge_test": "Test",
"revoke_dialog_title": "Revoke API key",
"revoke_dialog_description": "\"{name}\" will be permanently revoked. Any clients using the key will stop working immediately.",
"revoke_confirm": "Revoke",
@@ -2134,6 +2144,10 @@
"cancel": "Cancel"
},
"invoice_editor": {
"row_menu_set_account": "Set sales account",
"row_menu_remove_account": "Remove sales account",
"revenue_account_label": "Sales account",
"revenue_account_hint": "Leave blank to derive the account from the VAT rate. Ignored for reverse charge and export.",
"ore_rounding_label": "Öre rounding",
"ore_rounding_help": "Round the invoice total to whole kronor",
"back": "Back",
@@ -2988,6 +3002,9 @@
"send_failed_fallback": "Please try again."
},
"journal_list": {
"mode_vouchers": "Vouchers",
"mode_drafts": "Drafts",
"show_correction_chain": "Show storno & corrected entries",
"loading": "Loading journal entries...",
"empty_title": "No journal entries",
"empty_description": "Journal entries are created automatically from invoicing and transaction posting, or manually via the \"New entry\" tab.",
@@ -3155,6 +3172,7 @@
"current": "Current"
},
"journal_detail": {
"edit_draft": "Edit",
"back": "Back to bookkeeping",
"loading": "Loading journal entry...",
"error_not_found": "Journal entry not found",
@@ -3219,6 +3237,10 @@
"delete_dialog_entry_body": "The journal entry and its lines are removed. Linked transactions and invoices keep their data but are marked as unposted. Documents (receipts, files) are kept but unlinked."
},
"journal_form": {
"save_edit": "Save changes",
"toast_updated_title": "Draft updated",
"toast_updated_description": "Your changes to the draft were saved.",
"toast_update_failed": "Could not save changes",
"card_title": "New journal entry",
"fiscal_year": "Fiscal year",
"fiscal_year_placeholder": "Select period",
@@ -3602,6 +3624,7 @@
"import_psd2_active_warning_body": "Transactions sync automatically each night. File imports are only needed for older history or when PSD2 isn't working — otherwise duplicates may occur."
},
"bookkeeping": {
"edit_draft_dialog_title": "Edit draft",
"title": "Bookkeeping",
"year_end": "Year-end (Årsbokslut)",
"tab_journal": "Journal entries",
@@ -4169,7 +4192,7 @@
"export_subtitle": "Download your bookkeeping as a SIE file or back it up to Google Drive",
"tab_import": "Import",
"tab_export": "Export",
"sandbox_disabled": "Import is not available in the sandbox. Create an account to import data.",
"sandbox_disabled": "Bank connections and migration from other systems require a real account and are disabled in the sandbox. File-based imports (bank files, CSV/Excel and SIE) work as usual.",
"back_to_choices": "Back to choices",
"psd2_title": "Connect bank",
"psd2_recommended": "Recommended",
+25 -2
View File
@@ -1228,6 +1228,9 @@
"swish_error": "Ogiltigt Swish-nummer (företagsnummer 123XXXXXXX eller mobilnummer 07XXXXXXXX)"
},
"settings_invoice_form": {
"default_our_reference_label": "Standard för Vår referens",
"default_our_reference_placeholder": "T.ex. ditt namn",
"default_our_reference_help": "Förifylls automatiskt på nya fakturor. Kan ändras per faktura.",
"heading": "Fakturainställningar",
"prefix_label": "Fakturaprefix",
"prefix_placeholder": "t.ex. F-",
@@ -1238,6 +1241,7 @@
"default_notes_help": "Föreslås automatiskt vid ny faktura."
},
"settings_pdf_print": {
"coming_soon": "Kommer snart",
"heading": "Utskrift & PDF",
"toast_save_failed": "Kunde inte spara",
"ore_rounding_label": "Öresavrundning",
@@ -1249,7 +1253,7 @@
"show_plusgiro_label": "Visa plusgiro",
"show_plusgiro_help": "Visa plusgironummer på fakturautskrift",
"show_swish_label": "Visa Swish",
"show_swish_help": "Visa Swish-nummer på fakturautskrift",
"show_swish_help": "Visa swish nummer och qr kod på faktura",
"show_logo_label": "Visa logga",
"show_logo_help": "Visa uppladdad logga i fakturahuvudet",
"show_company_name_label": "Visa företagsnamn i faktura",
@@ -1477,6 +1481,12 @@
"toast_create_failed": "Kunde inte skapa nyckel",
"toast_revoked": "Nyckel återkallad",
"toast_revoke_failed": "Kunde inte återkalla nyckel",
"mode_label": "Miljö",
"mode_live": "Live",
"mode_test": "Test",
"mode_live_help": "Live-nycklar arbetar mot ditt riktiga företag och dess bokföring.",
"mode_test_help": "Testnycklar simulerar varje anrop (tvingad dry-run) — du ser exakt vad som skulle hända, men inget sparas eller skickas.",
"badge_test": "Test",
"revoke_dialog_title": "Återkalla API-nyckel",
"revoke_dialog_description": "\"{name}\" återkallas permanent. Alla klienter som använder nyckeln slutar fungera omedelbart.",
"revoke_confirm": "Återkalla",
@@ -2134,6 +2144,10 @@
"cancel": "Avbryt"
},
"invoice_editor": {
"row_menu_set_account": "Ange försäljningskonto",
"row_menu_remove_account": "Ta bort försäljningskonto",
"revenue_account_label": "Försäljningskonto",
"revenue_account_hint": "Lämna tomt för att härleda kontot från momssatsen. Ignoreras för omvänd skattskyldighet och export.",
"ore_rounding_label": "Öresavrundning",
"ore_rounding_help": "Avrunda fakturatotal till hel krona",
"back": "Tillbaka",
@@ -2988,6 +3002,9 @@
"send_failed_fallback": "Försök igen."
},
"journal_list": {
"mode_vouchers": "Verifikat",
"mode_drafts": "Utkast",
"show_correction_chain": "Visa storno- och rättade poster",
"loading": "Laddar verifikationer...",
"empty_title": "Inga verifikationer",
"empty_description": "Verifikationer skapas automatiskt vid fakturering och transaktionsbokföring, eller manuellt via fliken \"Ny verifikation\".",
@@ -3155,6 +3172,7 @@
"current": "Aktuell"
},
"journal_detail": {
"edit_draft": "Redigera",
"back": "Tillbaka till bokföring",
"loading": "Laddar verifikation...",
"error_not_found": "Verifikation hittades inte",
@@ -3219,6 +3237,10 @@
"delete_dialog_entry_body": "Verifikatet och dess kontorader tas bort. Kopplade transaktioner och fakturor behåller sina uppgifter men markeras som ej bokförda. Underlag (kvitton, dokument) behålls men avlänkas."
},
"journal_form": {
"save_edit": "Spara ändringar",
"toast_updated_title": "Utkast uppdaterat",
"toast_updated_description": "Ändringarna i utkastet har sparats.",
"toast_update_failed": "Kunde inte spara ändringarna",
"card_title": "Ny verifikation",
"fiscal_year": "Räkenskapsår",
"fiscal_year_placeholder": "Välj period",
@@ -3602,6 +3624,7 @@
"import_psd2_active_warning_body": "Transaktioner synkas automatiskt varje natt. Filimport behövs bara för äldre historik eller om PSD2 inte fungerar — annars kan dubbletter uppstå."
},
"bookkeeping": {
"edit_draft_dialog_title": "Redigera utkast",
"title": "Bokföring",
"year_end": "Årsbokslut",
"tab_journal": "Verifikationer",
@@ -4169,7 +4192,7 @@
"export_subtitle": "Ladda ner bokföringen som SIE-fil eller säkerhetskopia till Google Drive",
"tab_import": "Importera",
"tab_export": "Exportera",
"sandbox_disabled": "Import är inte tillgängligt i sandlådemiljön. Skapa ett konto för att importera data.",
"sandbox_disabled": "Bankkoppling och migrering från andra system kräver ett riktigt konto och är avstängda i sandlådan. Filbaserad import (bankfiler, CSV/Excel och SIE) fungerar som vanligt.",
"back_to_choices": "Tillbaka till val",
"psd2_title": "Koppla bank",
"psd2_recommended": "Rekommenderat",