Files
accounted/supabase/migrations/20260605120000_transactions_original_description.sql
T
Mattsson 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

48 lines
3.0 KiB
SQL

-- Editable bank-transaction titles: preserve the original bank/PSD2 name.
--
-- The transactions.description column is a mutable working label on the
-- staging/inbox row. It is NOT räkenskapsinformation until the row is booked
-- into a verifikat (journal_entry_id IS NOT NULL); BFL immutability (5 kap 5§)
-- and the storno-only rule attach to the verifikat, not to pre-accounting
-- staging data. We are about to let users edit that label *before* booking.
--
-- Two reasons to keep the bank's original text (normalized once at ingest) in a
-- separate, immutable column (never written by the edit endpoint):
-- 1. Recoverability + provenance — an accidental edit is always reversible,
-- and the bank original stays auditable as the basis for "vad
-- affärshändelsen avser" once booked (BFL 5 kap 7§ / god redovisningssed).
-- The raw PSD2 JSON is already archived as a document, so this column is
-- defence-in-depth, not the sole record.
-- 2. Dedup safety — contentDedupKey() in lib/transactions/external-id.ts
-- bridges re-imports across sources (PSD2 re-sync ⇄ CSV overlap) using a
-- description prefix. If that bridge read the user-editable description, an
-- edited title could let a genuine re-import slip past as a duplicate.
-- Ingest now keys the bridge off original_description, which never drifts.
--
-- The edit gate (only unbooked + unmatched rows are editable) is enforced in
-- application code (app/api/transactions/[id] PATCH). transactions carry no
-- description-immutability DB trigger today and none is added here; the
-- editable predicate (journal_entry_id IS NULL AND invoice_id IS NULL AND
-- supplier_invoice_id IS NULL) already excludes every booked/matched row.
ALTER TABLE public.transactions
ADD COLUMN IF NOT EXISTS original_description text,
ADD COLUMN IF NOT EXISTS title_edited_at timestamptz;
COMMENT ON COLUMN public.transactions.original_description IS
'Bank/PSD2-provided description captured at ingest, normalized (empty/whitespace and the legacy "Unknown" sentinel map to the Swedish neutral "Okänd transaktion"). Never overwritten by user title edits; used as the dedup-bridge source and as the "restore original" value.';
COMMENT ON COLUMN public.transactions.title_edited_at IS
'Set when a user overrides the transaction title (description). NULL = title is still the bank original. Drives the "redigerad" tag and the restore affordance.';
-- Backfill: before this feature shipped, description always held the bank
-- original (every ingest path defaulted it from the source text), so the
-- current description IS the original for every legacy row. This gives each
-- existing row a recoverable original and means the dedup bridge's
-- `original_description ?? description` fallback is only ever exercised by rows
-- created in the brief window before this migration runs.
UPDATE public.transactions
SET original_description = description
WHERE original_description IS NULL;
NOTIFY pgrst, 'reload schema';