Commit Graph
7 Commits
Author SHA1 Message Date
Jakob WennbergandClaude Opus 4.8 ff01640f60 feat(reports,settings): report library + focused report routes, settings modal (#629)
* feat(reports,settings): report library + focused report routes, settings modal

Reports
- Replace the monolithic /reports tab-switcher with a calm, grouped report
  library landing (ReportLibrary + RecentReportsShelf) driven by a new
  lib/reports/catalog.ts.
- Each report opens a focused /reports/[slug] route (FocusedReport) with a
  shared fiscal-year selector, optional date-range, and URL-based account
  drill-down into the general ledger.
- Extract every report view into components/reports/views, add a reusable
  ReportExportMenu, and remove the old ReportsNav.

Settings
- Add an intercepting @settingsModal parallel route so in-app navigation to
  /settings opens as a modal over the current page; hard loads still resolve
  to the full page.
- Share one SettingsShell (rail + content) between page and modal, extract each
  section into components/settings/sections/*Content, add a settings hotkey and
  command-palette entry, and remove the old SettingsSidebar.

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

* fix(reports,settings): remove dead salary-journal entry, fix border token

Addresses PR review feedback (#629):

- Remove the unreachable `salary-journal` report from the catalog. It had
  needsEmployees + no route + no FocusedView handler and `hasEmployees` was
  never plumbed through, so it never appeared in the library and a direct
  /reports/salary-journal URL rendered a blank frame. The report was never on
  the old page and has no view component; the API + generator stay in place for
  a proper follow-up. Drops its two now-unused i18n keys.

- Replace opacity-suffixed `border-border/8` section dividers with full-opacity
  `border-border` across the extracted settings section components, per the
  design system (no opacity-suffixed border tokens on surfaces).

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

* ci: re-trigger checks (pg-real hit a Docker Hub registry timeout)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:50:50 +02:00
Jakob WennbergandClaude Opus 4.8 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>
2026-06-01 21:08:06 +02:00
Mattsson a9aff5a120 Bug/template and sandbox (#589)
* Enhance booking template functionality and add sandbox extraction checks

* Implement Recapt integration for feedback submission and user identification

* Add Recapt identification component and bank sync status chip; update crontab entries

* Update .gitignore to ignore the entire scripts directory

* Refactor Recapt integration: add loader component, update privacy policy, and enhance bank sync status messages

* Fix .gitignore to correctly ignore the scripts directory
2026-05-28 15:43:48 +02:00
MattssonandClaude Opus 4.7 a9b43ebeb7 Bug/vat selection warning (#583)
* 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>
2026-05-28 01:56:09 +02:00
Mattsson 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
2026-05-21 21:33:22 +02:00
Mattsson 81e9dd224e Add/csv import options (#420)
* feat(import): add customer and supplier parsing functionality

- Implemented customer file parsing in `lib/import/customers/parser.ts` with support for Excel and CSV formats.
- Created types for detected customer columns and parsed customer rows in `lib/import/customers/types.ts`.
- Added tests for customer classification logic in `lib/import/shared/__tests__/classify.test.ts`.
- Developed classification functions for customers and suppliers in `lib/import/shared/classify.ts`.
- Introduced shared column utility functions in `lib/import/shared/column-utils.ts`.
- Implemented supplier file parsing in `lib/import/suppliers/parser.ts` with validation for various fields.
- Created types for detected supplier columns and parsed supplier rows in `lib/import/suppliers/types.ts`.
- Added tests for supplier column detection and parsing in `lib/import/suppliers/__tests__/column-detector.test.ts` and `lib/import/suppliers/__tests__/parser.test.ts`.

* fix(labels): update 'Svenskt företag' to 'Svenskt företag eller organisation' for clarity

* feat(import): refactor encoding handling for Swedish files and add tests for character preservation

* feat(recapt): implement clearRecaptIdentity function and integrate into logout flow

* feat(bookkeeping): implement copy functionality and next voucher sequence retrieval

* feat(import): enhance customer and supplier import functionality with normalization and event handling
2026-05-08 15:42:06 +02:00
Jakob WennbergandClaude Opus 4.6 d0b3f21bde feat: remove AI extensions, restructure settings, and add atomic voucher commits (#157)
Remove AI-dependent extensions (ai-chat, ai-categorization, receipt-ocr,
invoice-inbox) and their infrastructure (lib/ai/*, ai-consent, LangChain/
Anthropic/OpenAI deps) to simplify core and reduce bundle size.

Restructure monolithic settings page into dedicated sub-pages (company,
bookkeeping, invoicing, tax, banking, api, account, team, templates) with
shared layout and sidebar navigation.

Add atomic commit_journal_entry RPC so voucher number increment and status
update happen in a single transaction — prevents burned numbers on constraint
failures. Add continuity check report and voucher gap explanation tracking.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:08:00 +02:00