7c3518a080
* feat: payment matching audit trail, partial payments, and match improvements - Add payment_match_log table (append-only audit trail per BFL 7:1) with immutability triggers and proper RLS - Add invoice_payments table for partial payment tracking, mirroring supplier_invoice_payments pattern - Add remaining_amount column + partially_paid status to invoices - Add match-log.ts service for recording match/unmatch state transitions - Improve match-invoice route: support partial payments, record audit log, emit payment.matched events, handle storno conflicts - Improve match-supplier-invoice route: audit logging, partial payment support - Update invoice-entries.ts for partial payment journal entries - Update transaction ingest to detect and auto-suggest invoice matches - Update bank-reconciliation to handle partial payment state - Add invoice_payments, payment_match_log types and helpers - Extend tests for match-invoice route and transaction ingest Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — payment matching correctness bugs - P1: Return 500 on non-23505 payment insert failures instead of silently continuing with corrupted invoice state (both routes) - P1: Fix foreign-currency partial payment matching — use proportional remaining SEK amount instead of full total_sek - P2: Check error when clearing journal_entry_id after storno - P2: Add updated_at column + trigger to invoice_payments table - P2: Make supplier_invoice_payments.user_id NOT NULL after backfill Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
55 lines
2.4 KiB
SQL
55 lines
2.4 KiB
SQL
-- Migration: payment_match_log
|
|
-- Append-only audit trail for all payment matching state transitions.
|
|
-- Required by BFL 7:1 — all match/unmatch events are räkenskapsinformation.
|
|
-- Do NOT add cleanup/DELETE jobs — 7-year retention is legally required.
|
|
-- Partition by created_at (yearly) when volume exceeds ~100k rows.
|
|
|
|
CREATE TABLE public.payment_match_log (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id uuid NOT NULL REFERENCES auth.users ON DELETE CASCADE,
|
|
transaction_id uuid NOT NULL REFERENCES public.transactions ON DELETE CASCADE,
|
|
invoice_id uuid REFERENCES public.invoices ON DELETE SET NULL,
|
|
supplier_invoice_id uuid REFERENCES public.supplier_invoices ON DELETE SET NULL,
|
|
action text NOT NULL CHECK (action IN (
|
|
'matched',
|
|
'unmatched',
|
|
'auto_suggested',
|
|
'suggestion_cleared',
|
|
'storno_conflict_resolved'
|
|
)),
|
|
match_confidence numeric,
|
|
match_method text,
|
|
previous_state jsonb,
|
|
new_state jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
-- Intentionally NO updated_at: append-only
|
|
);
|
|
|
|
ALTER TABLE public.payment_match_log ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users can read their own match log entries
|
|
CREATE POLICY "payment_match_log_select" ON public.payment_match_log
|
|
FOR SELECT USING (auth.uid() = user_id);
|
|
|
|
-- Users can insert their own match log entries
|
|
CREATE POLICY "payment_match_log_insert" ON public.payment_match_log
|
|
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
|
|
-- NO UPDATE or DELETE policies — immutability enforced by triggers below
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_payment_match_log_user_id ON public.payment_match_log (user_id);
|
|
CREATE INDEX idx_payment_match_log_transaction_id ON public.payment_match_log (transaction_id);
|
|
CREATE INDEX idx_payment_match_log_invoice_id ON public.payment_match_log (invoice_id);
|
|
CREATE INDEX idx_payment_match_log_supplier_invoice_id ON public.payment_match_log (supplier_invoice_id);
|
|
CREATE INDEX idx_payment_match_log_created_at ON public.payment_match_log (created_at);
|
|
|
|
-- Immutability triggers: reuse audit_log_immutable() from migration 014
|
|
CREATE TRIGGER payment_match_log_no_update
|
|
BEFORE UPDATE ON public.payment_match_log
|
|
FOR EACH ROW EXECUTE FUNCTION public.audit_log_immutable();
|
|
|
|
CREATE TRIGGER payment_match_log_no_delete
|
|
BEFORE DELETE ON public.payment_match_log
|
|
FOR EACH ROW EXECUTE FUNCTION public.audit_log_immutable();
|