Files
accounted/supabase/migrations/20260402100200_atomic_commit_entry.sql
Jakob Wennberg 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

55 lines
1.9 KiB
PL/PgSQL

-- Atomic journal entry commit: assigns voucher number and posts entry in one transaction.
-- Prevents burned voucher numbers from failed commits (the counter increment rolls back
-- if the status update or balance trigger fails).
--
-- Only used for the draft→posted transition (commitEntry). reverseEntry and storno
-- use a different flow (INSERT with voucher number) and still call next_voucher_number.
CREATE OR REPLACE FUNCTION public.commit_journal_entry(
p_company_id uuid,
p_entry_id uuid
)
RETURNS TABLE (voucher_number integer)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_next integer;
v_fiscal_period_id uuid;
v_series text;
BEGIN
-- Fetch and lock the draft entry
SELECT je.fiscal_period_id, COALESCE(je.voucher_series, 'A')
INTO v_fiscal_period_id, v_series
FROM public.journal_entries je
WHERE je.id = p_entry_id
AND je.company_id = p_company_id
AND je.status = 'draft'
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION 'Draft journal entry not found: %', p_entry_id;
END IF;
-- Increment voucher sequence (atomic via INSERT ON CONFLICT)
INSERT INTO public.voucher_sequences (company_id, user_id, fiscal_period_id, voucher_series, last_number)
VALUES (p_company_id, auth.uid(), v_fiscal_period_id, v_series, 1)
ON CONFLICT (company_id, fiscal_period_id, voucher_series)
DO UPDATE SET
last_number = public.voucher_sequences.last_number + 1,
updated_at = now()
RETURNING last_number INTO v_next;
-- Update entry to posted with the assigned voucher number.
-- If the balance trigger (check_balance_on_post) rejects the UPDATE,
-- the entire transaction rolls back — including the sequence increment.
-- No burned number, no gap.
UPDATE public.journal_entries
SET voucher_number = v_next,
status = 'posted'
WHERE id = p_entry_id
AND company_id = p_company_id;
RETURN QUERY SELECT v_next;
END;
$$;