* refactor: update VAT handling logic for non-registered sellers and improve related comments
* chore: gate automated email flows behind 503 responses
Disables user-facing access to invoice payment reminders and salary
payslip email sending. Underlying lib code (reminder-processor,
PDF templates, notification_settings) is preserved for easy re-enable.
- Invoice reminders cron route returns 503; settings UI section removed.
- Payslip send route returns 503; original implementation kept as
_sendPayslipsImpl for future re-enable.
- Push notifications were already extension-disabled, no change needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: remove Recapt feedback widget
Strips the third-party Recapt SDK and its floating feedback bubble from
the app. The in-app contact form keeps working via the existing email
channel (/api/support/contact). Drops the Recapt entries from the CSP
and the subprocessor list in the privacy policy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: reject meaningless rättelser in correctEntry
Guard against zero-economic-effect corrections in the storno engine:
- Reject when proposed lines net to zero on every account (e.g. 1930
debit 100 / 1930 credit 100), which would erase the original posting
without representing any affärshändelse (BFL 5 kap. 5 §).
- Reject when proposed lines are an exact multiset match of the original
entry — a rättelse must actually change something.
New MeaninglessCorrectionError wired through bookkeepingErrorResponse
(HTTP 400) and the Swedish error translator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add date-range picker to resultat- and balansrapport
Adds optional from/to date filtering to the four operational financial
reports (resultatrapport, balansrapport, income-statement, balance-sheet)
so users can view a month, quarter, or custom range inside a fiscal year
without leaving the report. Defaults to YTD; "Hela året" preserves the
prior full-period behaviour (URL-identical, cache-stable).
- trial-balance engine accepts optional fromDate/toDate, rolling prior
in-period activity into IB and clamping period activity to the window
- 12 API routes accept and validate from_date/to_date query params
- ReportDateRange chip picker persists preset per company, only renders
on the four relevant tabs
- FiscalYearSelector now emits the period object so the range picker
has bounds without an extra fetch
- PDF/XLSX filenames reflect the chosen range
- Resultatrapport drops the prior-year column when narrowed (full-year
vs partial-year would mislead)
- 11 new tests (engine + parser); all existing report tests pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add support for marking journal entries as "no document required"
- Introduced a new sidecar table `journal_entry_no_doc_required` to track entries that do not require separate documentation (e.g., bank fees, interest).
- Implemented API routes for creating and deleting exemptions, including validation and authorization checks.
- Added a toggle component in the UI to allow users to mark entries as exempt, with an optional reason.
- Updated relevant tests to cover the new functionality, including RLS checks and cascading deletes.
- Enhanced existing schemas and types to accommodate the new `vat_amount` field for supplier invoice items.
* fix: address PR review findings on no-doc-required + VAT changes
- pg-real cascade test wraps DELETE in gnubok.allow_delete='true' txn so the
immutability trigger bypass fires (mirrors delete_last_voucher RPC).
- Clamp supplier-invoice item vat_amount to <= line_total * vat_rate via Zod
refinement (with 1-öre rounding tolerance) so the manual override can't
inflate the 2641 debit beyond the statutory ceiling.
- groupVatByRate falls back to line_total * rate when stored vat_amount is 0
with a positive rate, so legacy/import paths leaving the column at its
NOT NULL DEFAULT 0 don't silently understate ruta 48.
- ReportDateRange todayIso() and preset endpoints use local date components
instead of toISOString() (UTC) — fixes the midnight-to-02:00 off-by-one
that truncated a day from YTD / this-month / this-quarter for Swedish
users.
- NoDocRequiredToggle restores the previous reason on failed POST/DELETE so
the rolled-back toggle state stays consistent with the rendered reason.
- Document the company-scoped (not user-scoped) DELETE authorization policy
on the no-document-required route.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: restore document version supersession on posted entries
* refactor: update VAT handling logic to remove dependency on seller registration status
* feat: update VAT handling to respect seller registration status across invoice processing
* feat: add no-op migration placeholder for document supersession
* fix(dashboard): exclude credit notes from unpaid invoices widget
Credit notes (status='sent', negative total) were summed into the
"Att få betalt" widget, producing confusing negative totals like
"2 st, -38 625 kr". Filter them out via credited_invoice_id IS NULL,
matching the existing pattern in reminder-processor and the AR ledger.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(documents): harden PDF preview and upload validation
- JournalEntryAttachments: switch inline PDF preview from <iframe> to
<object type="application/pdf">. Mirrors the AttachmentPreviewSheet
fix from #572 — Chrome's frame pipeline intermittently surfaced
"Det här innehållet har blockerats" on iframes even with permissive
CSP. <object> invokes the PDF plugin directly. crbug.com/271452.
- /api/documents/:id/inline: resolve Content-Type via file extension
when mime_type is null or application/octet-stream. Legacy uploads
landed with empty File.type from some drag sources; combined with
the new X-Content-Type-Options: nosniff header on this route,
Chrome refused to render valid PDFs. Extension fallback covers
every legacy row without a DB backfill.
- /api/documents POST: surface DB-trigger period-lock errors as a
400 DOC_UPLOAD_PERIOD_LOCKED with a Swedish reason. Previously
every catch was bucketed into DOC_UPLOAD_STORAGE_FAILED (500 /
"Filen kunde inte sparas") which hid the real cause from users
attaching to verifikationer in closed/locked fiscal periods.
- document-service: add validateDocumentMagicBytes() that inspects
the first bytes for valid PDF/PNG/JPEG/WebP headers (PDF tolerates
a leading UTF-8 BOM). Wired into uploadDocument() and
createNewVersion() so every upload path is protected — UI, MCP,
and future email/webhook ingestion. Defends against agents that
send a base64-encoded text placeholder instead of real binary
bytes via the gnubok_upload_document MCP tool, which produced
tiny (15-561 byte) "PDFs" that failed to render in Chrome and
in external viewers.
Tests use a minimal valid PDF buffer (%PDF-1.4 … %%EOF).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(arsredovisning): emit ÅRL-required notes and FTE-weighted medelantal
Five compliance gaps fixed in the K2 and K3 noter builders:
- Anläggningstillgångar roll-forward per ÅRL 5:8 § — per-category IB
anskaffningsvärde, tillkommande, avgående, UB and accumulated
avskrivningar movement (was only emitting avskrivningstider).
- Långfristiga skulder förfallande efter mer än fem år per ÅRL 5:13 §.
- Ställda säkerheter and Eventualförpliktelser as separate notes per
ÅRL 5:14-15 § (K2 previously combined them).
- Koncernförhållanden per BFNAR 2016:10 kap. 19 / BFNAR 2012:1 kap. 8.
Replaces medelantal anställda — the old query filtered employees by an
is_active column that doesn't exist, so the note never emitted. Now
uses an FTE-weighted day-based average per ÅRL 5:20 §.
Six disclosure fields persist on arsredovisning_narratives as per-period
overrides; the UI extends the existing förvaltningsberättelse editor
with a "Lagstadgade upplysningar" subsection sharing the same Spara
button — no new pages, no settings changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(invoices): respect vat_registered=false and hide personnummer for B2C
- PDF address block no longer prints org_number for individual customers
(GDPR data minimization; ML 17 kap 24§ requires name + address only).
- Wire company_settings.vat_registered through the rule helpers, invoice
creation API, preview-pdf API, and the new-invoice form so a non-VAT-
registered seller cannot charge VAT (ML 1 kap. 1§). The PDF suppresses
the empty "Moms 0%" row and shows a dedicated "Företaget är inte
momsregistrerat" notice instead of the ML 3 kap. exempt notice.
- Engine unchanged: 'exempt' treatment already routes to 3004/3100 and
skips VAT lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(settings): remove approval rules from sidebar and routes
* fix(invoices): ensure vat_registered defaults to true for invoice previews and API
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add INK2 declaration improvements, invoice delivery date, and Swedish compliance skills
Expand INK2 engine with full INK2S/INK2R support and improved SRU generation.
Add delivery_date field to invoices and corresponding PDF/migration support.
Add Claude skills for Swedish asset accounting, invoice compliance, SIE import/export, SRU filing, and tax planning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review — map BAS 4500–4899, strip CRLF in SRU, document P3
- Map BAS accounts 4500–4599 (legoarbeten), 4700–4899 (diverse
varuinköpskostnader) to SRU 7512 so they are not silently dropped
from INK2R declarations
- Strip \r\n in sanitizeString to prevent CRLF injection in SRU fields
- Document P3 period suffix limitation for brutet räkenskapsår
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct BAS 4500-4599, 4700-4899 mapping from 7512 to 7511
Per the official BAS-to-SRU mapping, these account ranges are cost of
goods (legoarbeten, inkurans, svinn) and belong under 7511 (Råvaror
och förnödenheter), not 7512 (Handelsvaror). 7512 remains 4600-4699.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix calculateVat/calculateTotal to use proper monetary rounding. Add
getVatSummaryFromItems helper for deriving VAT labels from mixed-rate items.
Update invoice preview, review, and detail pages to show per-rate VAT
breakdown instead of a single aggregated rate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge the singular lib/invoice/ directory into the plural lib/invoices/
to align with the codebase convention (transactions/, extensions/, reports/, etc.).
Updates all import paths and CLAUDE.md architecture docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>