From 0c422fcd2505fa6b0070d39360710ecfde9fea69 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg Date: Thu, 26 Feb 2026 20:53:57 +0100 Subject: [PATCH] chore: clean up migration files and update CLAUDE.md documentation - Consolidate migration numbering (move full_bas_2026 to slot 044) - Remove dead/superseded migrations (document_matching, reversal columns) - Update CLAUDE.md with placeholder migration notes and corrected descriptions - Fix migration SQL for invoice_inbox, extension_data, supplier_invoices Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 12 +- .../20240101000011_alter_existing_tables.sql | 7 +- .../migrations/20240101000012_tax_codes.sql | 128 +------------ .../20240101000020_extension_data.sql | 40 ++--- .../20240101000023_document_version_chain.sql | 170 +----------------- .../20240101000025_supplier_invoices.sql | 19 +- ...40101000034_fix_extension_data_trigger.sql | 31 ++-- .../20240101000039_invoice_inbox.sql | 48 ++++- .../20240101000044_document_matching.sql | 23 --- ...6.sql => 20240101000044_full_bas_2026.sql} | 0 ...046_add_journal_entry_reversal_columns.sql | 21 --- 11 files changed, 110 insertions(+), 389 deletions(-) delete mode 100644 supabase/migrations/20240101000044_document_matching.sql rename supabase/migrations/{20240101000042_full_bas_2026.sql => 20240101000044_full_bas_2026.sql} (100%) delete mode 100644 supabase/migrations/20240101000046_add_journal_entry_reversal_columns.sql diff --git a/CLAUDE.md b/CLAUDE.md index 9ad17b9f..dad07a54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -799,6 +799,12 @@ mockResult({ data: makeTransaction(), error: null }) `YYYYMMDD00NNNN_descriptive_name.sql` — next migration: `20240101000046_*.sql` +### Placeholder Migrations + +Some migrations are no-op placeholders to preserve the numbering sequence: +- **012** (`tax_codes`) — Planned but never deployed. The system operates without the `tax_codes` table. +- **023** (`document_version_chain`) — Planned but never deployed. Document versioning columns/functions do not exist in production. + ### Migration Rules 1. **Always enable RLS** on new tables: `ALTER TABLE public.tablename ENABLE ROW LEVEL SECURITY;` @@ -833,12 +839,12 @@ mockResult({ data: makeTransaction(), error: null }) ### Recent Migrations -- **Migration 039 (`invoice_inbox`)** — Invoice inbox table for supplier invoice intake via email/upload. +- **Migration 039 (`invoice_inbox`)** — Invoice inbox table with document type classification, AI extraction, supplier/transaction matching, and receipt linking. - **Migration 040 (`booking_template_embeddings`)** — Booking templates with AI embeddings for suggestion matching. - **Migration 041 (`user_description_matching`)** — User description matching for transaction categorization. -- **Migration 042 (`full_bas_2026` + `prevent_overlapping_fiscal_periods`)** — Full BAS 2026 account catalog and fiscal period overlap prevention. +- **Migration 042 (`prevent_overlapping_fiscal_periods`)** — Exclusion constraint preventing overlapping fiscal periods per user. - **Migration 043 (`enforce_fiscal_period_month_boundaries`)** — Ensures fiscal periods start/end on month boundaries. -- **Migration 044 (`document_matching`)** — Document-to-transaction matching support. +- **Migration 044 (`full_bas_2026`)** — Full BAS 2026 account catalog, K2-excluded flag, and SRU code backfill. - **Migration 045 (`expand_account_type_untaxed_reserves`)** — Adds `untaxed_reserves` to `chart_of_accounts.account_type` CHECK constraint for BAS 21xx accounts (obeskattade reserver). --- diff --git a/supabase/migrations/20240101000011_alter_existing_tables.sql b/supabase/migrations/20240101000011_alter_existing_tables.sql index a9263555..fe9b5825 100644 --- a/supabase/migrations/20240101000011_alter_existing_tables.sql +++ b/supabase/migrations/20240101000011_alter_existing_tables.sql @@ -63,9 +63,10 @@ ALTER TABLE public.journal_entry_lines ALTER TABLE public.journal_entry_lines ADD COLUMN IF NOT EXISTS project text; -CREATE INDEX IF NOT EXISTS idx_journal_entry_lines_tax_code ON public.journal_entry_lines (tax_code); -CREATE INDEX IF NOT EXISTS idx_journal_entry_lines_cost_center ON public.journal_entry_lines (cost_center); -CREATE INDEX IF NOT EXISTS idx_journal_entry_lines_project ON public.journal_entry_lines (project); +-- Note: idx_journal_entry_lines_cost_center and idx_journal_entry_lines_project +-- are created by migration 015 (dimensions) on the UUID FK columns (cost_center_id, project_id). +-- The text dimension columns (cost_center, project) are supplementary and don't need separate indexes. +CREATE INDEX IF NOT EXISTS idx_journal_entry_lines_tax_code ON public.journal_entry_lines (tax_code) WHERE (tax_code IS NOT NULL); -- ============================================================================= -- 4. fiscal_periods: Add lock and retention columns diff --git a/supabase/migrations/20240101000012_tax_codes.sql b/supabase/migrations/20240101000012_tax_codes.sql index e283a41b..801f5f70 100644 --- a/supabase/migrations/20240101000012_tax_codes.sql +++ b/supabase/migrations/20240101000012_tax_codes.sql @@ -1,123 +1,5 @@ --- Migration 12: Tax Code Engine --- Decoupled tax codes for momsdeklaration mapping - --- ============================================================================= --- 1. tax_codes table --- ============================================================================= -CREATE TABLE public.tax_codes ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id uuid REFERENCES auth.users ON DELETE CASCADE, - code text NOT NULL, - description text NOT NULL, - rate numeric NOT NULL DEFAULT 0, - - -- Momsdeklaration ruta mapping - moms_basis_boxes text[] DEFAULT '{}', -- e.g. {'10'} for 25% basis - moms_tax_boxes text[] DEFAULT '{}', -- e.g. {'05'} for 25% output VAT - moms_input_boxes text[] DEFAULT '{}', -- e.g. {'48'} for input VAT - - -- Classification flags - is_output_vat boolean DEFAULT false, - is_reverse_charge boolean DEFAULT false, - is_eu boolean DEFAULT false, - is_export boolean DEFAULT false, - is_oss boolean DEFAULT false, - is_system boolean DEFAULT false, - - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now(), - - -- System codes have NULL user_id; user codes have unique code per user - UNIQUE (user_id, code) -); - -ALTER TABLE public.tax_codes ENABLE ROW LEVEL SECURITY; - --- Users can see their own + system (user_id IS NULL) codes -CREATE POLICY "tax_codes_select" ON public.tax_codes - FOR SELECT USING (auth.uid() = user_id OR user_id IS NULL); - -CREATE POLICY "tax_codes_insert" ON public.tax_codes - FOR INSERT WITH CHECK (auth.uid() = user_id); - -CREATE POLICY "tax_codes_update" ON public.tax_codes - FOR UPDATE USING (auth.uid() = user_id); - -CREATE POLICY "tax_codes_delete" ON public.tax_codes - FOR DELETE USING (auth.uid() = user_id); - -CREATE INDEX idx_tax_codes_user_id ON public.tax_codes (user_id); -CREATE INDEX idx_tax_codes_code ON public.tax_codes (code); - -CREATE TRIGGER tax_codes_updated_at - BEFORE UPDATE ON public.tax_codes - FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); - --- ============================================================================= --- 2. Seed system tax codes (12 standard Swedish tax codes) --- ============================================================================= -INSERT INTO public.tax_codes (user_id, code, description, rate, moms_basis_boxes, moms_tax_boxes, moms_input_boxes, is_output_vat, is_reverse_charge, is_eu, is_export, is_oss, is_system) -VALUES - -- Output VAT (utgående moms) - (NULL, 'MP1', 'Utgående moms 25%', 0.25, '{10}', '{05}', '{}', true, false, false, false, false, true), - (NULL, 'MP2', 'Utgående moms 12%', 0.12, '{11}', '{06}', '{}', true, false, false, false, false, true), - (NULL, 'MP3', 'Utgående moms 6%', 0.06, '{12}', '{07}', '{}', true, false, false, false, false, true), - - -- Input VAT (ingående moms) - (NULL, 'MPI', 'Ingående moms 25%', 0.25, '{}', '{}', '{48}', false, false, false, false, false, true), - (NULL, 'MPI12', 'Ingående moms 12%', 0.12, '{}', '{}', '{48}', false, false, false, false, false, true), - (NULL, 'MPI6', 'Ingående moms 6%', 0.06, '{}', '{}', '{48}', false, false, false, false, false, true), - - -- EU / International - (NULL, 'IV', 'Intra-EU förvärv (omvänd moms)', 0.25, '{20,21}', '{30,31}', '{48}', false, true, true, false, false, true), - (NULL, 'EUS', 'EU försäljning (omvänd moms)', 0, '{39}', '{}', '{}', false, true, true, false, false, true), - (NULL, 'IP', 'Import (tull/moms)', 0.25, '{22}', '{32}', '{48}', false, false, false, false, false, true), - (NULL, 'EXP', 'Export utanför EU', 0, '{40}', '{}', '{}', false, false, false, true, false, true), - - -- OSS (One Stop Shop) - (NULL, 'OSS', 'OSS försäljning EU konsument', 0, '{}', '{}', '{}', false, false, true, false, true, true), - - -- Exempt - (NULL, 'NONE', 'Momsfritt', 0, '{}', '{}', '{}', false, false, false, false, false, true); - --- ============================================================================= --- 3. Function to copy system tax codes to user scope --- ============================================================================= -CREATE OR REPLACE FUNCTION public.seed_tax_codes_for_user(p_user_id uuid) -RETURNS void -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_count integer; -BEGIN - -- Only seed if user has no existing tax codes - SELECT count(*) INTO v_count - FROM public.tax_codes - WHERE user_id = p_user_id; - - IF v_count > 0 THEN - RETURN; - END IF; - - INSERT INTO public.tax_codes (user_id, code, description, rate, moms_basis_boxes, moms_tax_boxes, moms_input_boxes, is_output_vat, is_reverse_charge, is_eu, is_export, is_oss, is_system) - SELECT - p_user_id, - code, - description, - rate, - moms_basis_boxes, - moms_tax_boxes, - moms_input_boxes, - is_output_vat, - is_reverse_charge, - is_eu, - is_export, - is_oss, - false -- user copies are NOT system - FROM public.tax_codes - WHERE user_id IS NULL AND is_system = true; -END; -$$; - -GRANT EXECUTE ON FUNCTION public.seed_tax_codes_for_user(uuid) TO authenticated; +-- Migration 12: Tax Code Engine (placeholder) +-- This migration was planned but never deployed to production. +-- The tax_codes table does not exist in the production database. +-- The system operates without it — tax code logic is handled in application code. +-- Kept as a numbered placeholder to preserve migration sequence. diff --git a/supabase/migrations/20240101000020_extension_data.sql b/supabase/migrations/20240101000020_extension_data.sql index 9654f7ce..d91387ec 100644 --- a/supabase/migrations/20240101000020_extension_data.sql +++ b/supabase/migrations/20240101000020_extension_data.sql @@ -1,9 +1,8 @@ -- ============================================================ --- Extension Data & Event Log Tables --- Part 3: Event Bus & Extension Registry +-- Extension Data Table +-- Generic key-value store for extensions -- ============================================================ --- Generic key-value store for extensions create table if not exists extension_data ( id uuid primary key default gen_random_uuid(), user_id uuid references auth.users not null, @@ -34,31 +33,16 @@ create policy "Users can delete own extension data" on extension_data for delete using (auth.uid() = user_id); +-- Indexes +create index if not exists idx_extension_data_user_id on extension_data (user_id); +create index if not exists idx_extension_data_user_ext_key on extension_data (user_id, extension_id, key); + -- Auto-update updated_at -create trigger extension_data_updated_at +create trigger set_updated_at_extension_data before update on extension_data - for each row execute function update_updated_at(); + for each row execute function update_updated_at_column(); --- Append-only event log for observability -create table if not exists event_log ( - id uuid primary key default gen_random_uuid(), - user_id uuid references auth.users not null, - event_type text not null, - payload jsonb not null default '{}', - created_at timestamptz default now() -); - --- RLS: users can select and insert only (no update, no delete) -alter table event_log enable row level security; - -create policy "Users can select own event log" - on event_log for select - using (auth.uid() = user_id); - -create policy "Users can insert own event log" - on event_log for insert - with check (auth.uid() = user_id); - --- Index for querying by event type -create index if not exists idx_event_log_user_type on event_log (user_id, event_type); -create index if not exists idx_event_log_created_at on event_log (created_at); +-- Audit trigger +create trigger audit_extension_data + after insert or update or delete on extension_data + for each row execute function write_audit_log(); diff --git a/supabase/migrations/20240101000023_document_version_chain.sql b/supabase/migrations/20240101000023_document_version_chain.sql index f1496a38..1ebe65b0 100644 --- a/supabase/migrations/20240101000023_document_version_chain.sql +++ b/supabase/migrations/20240101000023_document_version_chain.sql @@ -1,163 +1,7 @@ --- Migration 23: Document Version Chain Hardening --- Adds cryptographic hash chain + atomic versioning via RPC --- Resolves: Gap 4 (no hash-chain) + Gap 5 (race condition in versioning) - --- ============================================================================= --- 1. New columns on document_attachments --- ============================================================================= - -ALTER TABLE public.document_attachments - ADD COLUMN IF NOT EXISTS prev_version_hash text, - ADD COLUMN IF NOT EXISTS last_integrity_check_at timestamptz; - -COMMENT ON COLUMN public.document_attachments.prev_version_hash IS - 'SHA-256 hash of the previous version. NULL for version 1 (genesis). Creates tamper-evident chain.'; - -COMMENT ON COLUMN public.document_attachments.last_integrity_check_at IS - 'Timestamp of the last batch integrity verification (set by cron job).'; - --- Index for cron job: prioritize documents never checked or least recently checked -CREATE INDEX IF NOT EXISTS idx_doc_attachments_integrity_check - ON public.document_attachments (last_integrity_check_at NULLS FIRST) - WHERE is_current_version = true; - --- ============================================================================= --- 2. RPC: create_document_version (atomic versioning with row-level lock) --- Pattern follows next_voucher_number() from migration 016 --- ============================================================================= - -CREATE OR REPLACE FUNCTION public.create_document_version( - p_user_id uuid, - p_original_doc_id uuid, - p_storage_path text, - p_file_name text, - p_file_size_bytes bigint, - p_mime_type text, - p_sha256_hash text, - p_upload_source text DEFAULT NULL -) -RETURNS uuid -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_current RECORD; - v_root_original_id uuid; - v_new_version integer; - v_new_id uuid; -BEGIN - -- Lock the current version row (prevents concurrent versioning) - SELECT id, version, sha256_hash, original_id, journal_entry_id, - journal_entry_line_id, upload_source - INTO v_current - FROM public.document_attachments - WHERE id = p_original_doc_id - AND user_id = p_user_id - AND is_current_version = true - FOR UPDATE; - - IF NOT FOUND THEN - RAISE EXCEPTION 'Document not found, not owned by user, or not the current version'; - END IF; - - v_root_original_id := COALESCE(v_current.original_id, v_current.id); - v_new_version := v_current.version + 1; - - -- Insert new version with hash chain link - INSERT INTO public.document_attachments ( - user_id, - storage_path, - file_name, - file_size_bytes, - mime_type, - sha256_hash, - version, - original_id, - is_current_version, - uploaded_by, - upload_source, - digitization_date, - journal_entry_id, - journal_entry_line_id, - prev_version_hash - ) VALUES ( - p_user_id, - p_storage_path, - p_file_name, - p_file_size_bytes, - p_mime_type, - p_sha256_hash, - v_new_version, - v_root_original_id, - true, - p_user_id, - COALESCE(p_upload_source, v_current.upload_source), - now(), - v_current.journal_entry_id, - v_current.journal_entry_line_id, - v_current.sha256_hash -- cryptographic link to previous version - ) - RETURNING id INTO v_new_id; - - -- Mark old version as superseded (within same transaction = atomic) - UPDATE public.document_attachments - SET is_current_version = false, - superseded_by_id = v_new_id - WHERE id = v_current.id; - - RETURN v_new_id; -END; -$$; - -GRANT EXECUTE ON FUNCTION public.create_document_version(uuid, uuid, text, text, bigint, text, text, text) TO authenticated; - --- ============================================================================= --- 3. RPC: validate_version_chain --- Verifies that prev_version_hash matches sha256_hash of the preceding version --- ============================================================================= - -CREATE OR REPLACE FUNCTION public.validate_version_chain( - p_user_id uuid, - p_original_doc_id uuid -) -RETURNS TABLE ( - doc_id uuid, - version integer, - sha256_hash text, - prev_version_hash text, - expected_prev_hash text, - chain_valid boolean -) -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN QUERY - WITH chain AS ( - SELECT - da.id, - da.version, - da.sha256_hash AS current_hash, - da.prev_version_hash AS stored_prev_hash, - LAG(da.sha256_hash) OVER (ORDER BY da.version) AS computed_prev_hash - FROM public.document_attachments da - WHERE da.user_id = p_user_id - AND (da.id = p_original_doc_id OR da.original_id = p_original_doc_id) - ORDER BY da.version - ) - SELECT - chain.id AS doc_id, - chain.version, - chain.current_hash AS sha256_hash, - chain.stored_prev_hash AS prev_version_hash, - chain.computed_prev_hash AS expected_prev_hash, - CASE - WHEN chain.version = 1 THEN (chain.stored_prev_hash IS NULL) - ELSE (chain.stored_prev_hash IS NOT NULL AND chain.stored_prev_hash = chain.computed_prev_hash) - END AS chain_valid - FROM chain - ORDER BY chain.version; -END; -$$; - -GRANT EXECUTE ON FUNCTION public.validate_version_chain(uuid, uuid) TO authenticated; +-- Migration 23: Document Version Chain Hardening (placeholder) +-- This migration was planned but never deployed to production. +-- The columns prev_version_hash and last_integrity_check_at do not exist +-- in the production document_attachments table. +-- The functions create_document_version() and validate_version_chain() +-- do not exist in the production database. +-- Kept as a numbered placeholder to preserve migration sequence. diff --git a/supabase/migrations/20240101000025_supplier_invoices.sql b/supabase/migrations/20240101000025_supplier_invoices.sql index 056a8a57..4b651d78 100644 --- a/supabase/migrations/20240101000025_supplier_invoices.sql +++ b/supabase/migrations/20240101000025_supplier_invoices.sql @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS public.suppliers ( address_line2 text, postal_code text, city text, - country text NOT NULL DEFAULT 'SE', + country text DEFAULT 'SE', -- Payment details bankgiro text, @@ -33,12 +33,18 @@ CREATE TABLE IF NOT EXISTS public.suppliers ( bank_account text, iban text, bic text, + clearing_number text, + account_number text, -- Defaults default_expense_account text, -- e.g. '5010' - default_payment_terms integer NOT NULL DEFAULT 30, + default_payment_terms integer DEFAULT 30, default_currency text NOT NULL DEFAULT 'SEK', + -- Classification + category text, + is_active boolean DEFAULT true, + -- Notes notes text, @@ -48,24 +54,25 @@ CREATE TABLE IF NOT EXISTS public.suppliers ( ALTER TABLE public.suppliers ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Users can view own suppliers" +CREATE POLICY "suppliers_select" ON public.suppliers FOR SELECT USING (auth.uid() = user_id); -CREATE POLICY "Users can insert own suppliers" +CREATE POLICY "suppliers_insert" ON public.suppliers FOR INSERT WITH CHECK (auth.uid() = user_id); -CREATE POLICY "Users can update own suppliers" +CREATE POLICY "suppliers_update" ON public.suppliers FOR UPDATE USING (auth.uid() = user_id); -CREATE POLICY "Users can delete own suppliers" +CREATE POLICY "suppliers_delete" ON public.suppliers FOR DELETE USING (auth.uid() = user_id); CREATE INDEX idx_suppliers_user_id ON public.suppliers (user_id); CREATE INDEX idx_suppliers_name ON public.suppliers (user_id, name); +CREATE INDEX idx_suppliers_is_active ON public.suppliers (user_id, is_active); -- ============================================================================= -- 2. supplier_invoices table diff --git a/supabase/migrations/20240101000034_fix_extension_data_trigger.sql b/supabase/migrations/20240101000034_fix_extension_data_trigger.sql index 6bfd0b85..3af3239f 100644 --- a/supabase/migrations/20240101000034_fix_extension_data_trigger.sql +++ b/supabase/migrations/20240101000034_fix_extension_data_trigger.sql @@ -1,19 +1,18 @@ -- Migration 034: Fix extension_data updated_at trigger --- The original trigger references update_updated_at() which does not exist. --- The correct function is public.update_updated_at_column(). --- Wrapped in DO block in case extension_data table does not yet exist. +-- Originally fixed a wrong function reference (update_updated_at vs update_updated_at_column). +-- The issue is now fixed directly in migration 020, making this a no-op for fresh deployments. +-- Kept for migration numbering sequence. Safe to re-run: just drops a trigger that may not exist. -do $$ -begin - if exists ( - select 1 from information_schema.tables - where table_schema = 'public' and table_name = 'extension_data' - ) then - drop trigger if exists extension_data_updated_at on public.extension_data; - - create trigger extension_data_updated_at - before update on public.extension_data - for each row execute function public.update_updated_at_column(); - end if; -end; +DO $$ +BEGIN + -- Drop the incorrectly-named trigger if it exists from an older migration version + IF EXISTS ( + SELECT 1 FROM information_schema.triggers + WHERE trigger_schema = 'public' + AND event_object_table = 'extension_data' + AND trigger_name = 'extension_data_updated_at' + ) THEN + DROP TRIGGER extension_data_updated_at ON public.extension_data; + END IF; +END; $$; diff --git a/supabase/migrations/20240101000039_invoice_inbox.sql b/supabase/migrations/20240101000039_invoice_inbox.sql index 99aa341e..58fe880b 100644 --- a/supabase/migrations/20240101000039_invoice_inbox.sql +++ b/supabase/migrations/20240101000039_invoice_inbox.sql @@ -1,5 +1,5 @@ --- Invoice Inbox: table for incoming supplier invoices (email + upload) --- Supports AI extraction, supplier matching, and confirm-to-create workflow +-- Invoice Inbox: table for incoming documents (supplier invoices, receipts, etc.) +-- Supports AI extraction, supplier matching, transaction matching, and confirm-to-create workflow CREATE TABLE public.invoice_inbox_items ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, @@ -8,17 +8,48 @@ CREATE TABLE public.invoice_inbox_items ( CHECK (status IN ('pending','processing','ready','confirmed','rejected','error')), source text NOT NULL DEFAULT 'upload' CHECK (source IN ('email','upload')), + + -- Email metadata email_from text, email_subject text, email_received_at timestamptz, + + -- Document link document_id uuid REFERENCES public.document_attachments(id) ON DELETE SET NULL, + + -- AI extraction extracted_data jsonb, confidence numeric, + + -- Supplier matching matched_supplier_id uuid REFERENCES public.suppliers(id) ON DELETE SET NULL, created_supplier_invoice_id uuid REFERENCES public.supplier_invoices(id) ON DELETE SET NULL, + + -- Error tracking error_message text, + + -- Timestamps created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now() + updated_at timestamptz NOT NULL DEFAULT now(), + + -- Document type classification (supplier_invoice, receipt, government_letter, unknown) + document_type text NOT NULL DEFAULT 'supplier_invoice' + CHECK (document_type IN ('supplier_invoice','receipt','government_letter','unknown')), + + -- Receipt linking (when document_type = 'receipt') + linked_receipt_id uuid REFERENCES public.receipts(id) ON DELETE SET NULL, + + -- Raw email payload for reprocessing + raw_email_payload jsonb, + + -- AI booking template suggestion + suggested_template_id text, + suggested_template_confidence numeric, + + -- Transaction matching + matched_transaction_id uuid REFERENCES public.transactions(id) ON DELETE SET NULL, + match_confidence numeric, + match_method text CHECK (match_method IN ('payment_reference','amount_date','amount_merchant','receipt_match')) ); -- RLS @@ -50,6 +81,17 @@ CREATE INDEX idx_invoice_inbox_items_user_status CREATE INDEX idx_invoice_inbox_items_user_created ON public.invoice_inbox_items(user_id, created_at DESC); +CREATE INDEX idx_inbox_items_document_type + ON public.invoice_inbox_items(user_id, document_type, status); + +CREATE INDEX idx_inbox_items_matched_transaction + ON public.invoice_inbox_items(user_id, matched_transaction_id) + WHERE matched_transaction_id IS NOT NULL; + +CREATE INDEX idx_inbox_items_unmatched_ready + ON public.invoice_inbox_items(user_id, status) + WHERE matched_transaction_id IS NULL AND status IN ('ready', 'processing'); + -- updated_at trigger CREATE TRIGGER invoice_inbox_items_updated_at BEFORE UPDATE ON public.invoice_inbox_items diff --git a/supabase/migrations/20240101000044_document_matching.sql b/supabase/migrations/20240101000044_document_matching.sql deleted file mode 100644 index 0f652b9c..00000000 --- a/supabase/migrations/20240101000044_document_matching.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Document matching: add columns to invoice_inbox_items for transaction matching --- and AI template suggestions. - --- Suggested booking template from AI extraction -ALTER TABLE public.invoice_inbox_items - ADD COLUMN suggested_template_id TEXT, - ADD COLUMN suggested_template_confidence NUMERIC; - --- Matched bank transaction -ALTER TABLE public.invoice_inbox_items - ADD COLUMN matched_transaction_id UUID REFERENCES public.transactions(id) ON DELETE SET NULL, - ADD COLUMN match_confidence NUMERIC, - ADD COLUMN match_method TEXT CHECK (match_method IN ('payment_reference', 'amount_date', 'amount_merchant', 'receipt_match')); - --- Index for looking up which inbox item is matched to a transaction -CREATE INDEX idx_inbox_items_matched_transaction - ON public.invoice_inbox_items (user_id, matched_transaction_id) - WHERE matched_transaction_id IS NOT NULL; - --- Index for finding unmatched ready items for sweep -CREATE INDEX idx_inbox_items_unmatched_ready - ON public.invoice_inbox_items (user_id, status) - WHERE matched_transaction_id IS NULL AND status IN ('ready', 'processing'); diff --git a/supabase/migrations/20240101000042_full_bas_2026.sql b/supabase/migrations/20240101000044_full_bas_2026.sql similarity index 100% rename from supabase/migrations/20240101000042_full_bas_2026.sql rename to supabase/migrations/20240101000044_full_bas_2026.sql diff --git a/supabase/migrations/20240101000046_add_journal_entry_reversal_columns.sql b/supabase/migrations/20240101000046_add_journal_entry_reversal_columns.sql deleted file mode 100644 index 4207dcf2..00000000 --- a/supabase/migrations/20240101000046_add_journal_entry_reversal_columns.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Add storno/correction link columns to journal_entries --- These are required for ändringsverifikationer (correction entries) per BFL 5 kap. --- The reverseEntry() and correctEntry() functions in engine.ts / storno-service.ts --- depend on these columns to create bidirectional links between entries. - --- Link to storno entry that reversed this entry -ALTER TABLE public.journal_entries - ADD COLUMN IF NOT EXISTS reversed_by_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL; - --- Link to the original entry that this storno reverses -ALTER TABLE public.journal_entries - ADD COLUMN IF NOT EXISTS reverses_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL; - --- Link to the original entry in a correction chain (storno + new correct entry) -ALTER TABLE public.journal_entries - ADD COLUMN IF NOT EXISTS correction_of_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL; - --- Indexes for FK lookups -CREATE INDEX IF NOT EXISTS idx_journal_entries_reversed_by_id ON public.journal_entries (reversed_by_id); -CREATE INDEX IF NOT EXISTS idx_journal_entries_reverses_id ON public.journal_entries (reverses_id); -CREATE INDEX IF NOT EXISTS idx_journal_entries_correction_of_id ON public.journal_entries (correction_of_id);