Commit Graph
7 Commits
Author SHA1 Message Date
Jakob WennbergandClaude Sonnet 5 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>
2026-07-04 15:58:06 +02:00
MattssonandClaude Opus 4.8 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>
2026-06-08 15:25:27 +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
MattssonandClaude Opus 4.8 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>
2026-06-01 14:45:49 +02:00
Mattsson a1a816b4a5 Delete features (#218)
* Implement company and account deletion features

- Add event types for company and account deletion to CoreEvent.
- Enhance Supabase middleware to handle company context resolution and cookie management for archived companies.
- Create API routes for deleting accounts and companies, including necessary validations and event emissions.
- Implement tests for account and company deletion endpoints to ensure proper functionality and error handling.
- Add retention notice component to inform users about bookkeeping data retention during destructive actions.
- Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws.

* feat: enhance account deletion process and update user notifications

* Add service client for onboarding completion check and update escape hatch visibility

* Enhance invite flow and email handling for company members

* Refactor company context and RLS policies for active company isolation

- Update `switchCompany` to remove unnecessary revalidation as client handles navigation.
- Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships.
- Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility.
- Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership.
- Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization.
- Implement `CompanyTabSync` component for real-time active company enforcement across tabs.
- Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`.

* feat: implement viewer role enforcement for write permissions

- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.
2026-04-11 17:06:32 +02:00
Jakob WennbergandClaude Opus 4.6 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>
2026-04-11 13:02:50 +02:00
Mattsson 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
2026-04-09 16:22:51 +02:00