Files
accounted/supabase/migrations/20260402075153_fix_reserve_voucher_range.sql
T
Jakob WennbergandClaude Opus 4.6 9dceb6285c fix: SIE import voucher range bugs + remove describe transaction (#161)
* fix: fix SIE import voucher range bugs from multi-tenant migration and remove describe transaction feature

- Fix reserve_voucher_range RPC parameter mismatch (p_user_id → p_company_id) causing duplicate voucher errors
- Fix create-accounts onConflict from user_id to company_id
- Add reserve-then-adjust pattern: pre-reserve voucher range before batch insert, release unused range on partial failure
- Add release_voucher_range DB function for safe rollback
- Remove DescribeTransactionDialog, describe/batch-describe API routes, and related schemas (feature superseded)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Greptile review — voucher range race condition and orphaned entries

- Add p_reserved_highest upper-bound guard to release_voucher_range to prevent
  rolling back past numbers claimed by concurrent operations
- Move highestInsertedVoucher tracking to after both headers AND lines succeed,
  preventing orphaned journal entries with no lines from being counted as "used"

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-02 10:48:13 +02:00

70 lines
2.7 KiB
PL/PgSQL

-- Fix reserve_voucher_range: p_user_id was not updated to p_company_id
-- during the multi-tenant migration (20260330130000). The parameter mismatch
-- caused SIE imports to silently fail to update the voucher sequence,
-- leading to duplicate voucher number errors on subsequent operations.
--
-- Also adds release_voucher_range for rolling back burned numbers on partial
-- import failure (reserve-then-adjust pattern).
-- =============================================================================
-- 1. Fix reserve_voucher_range: p_user_id -> p_company_id
-- =============================================================================
DROP FUNCTION IF EXISTS public.reserve_voucher_range(uuid, uuid, text, integer);
CREATE OR REPLACE FUNCTION public.reserve_voucher_range(
p_company_id uuid,
p_fiscal_period_id uuid,
p_series text,
p_highest_used integer
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
INSERT INTO public.voucher_sequences (company_id, user_id, fiscal_period_id, voucher_series, last_number)
VALUES (p_company_id, auth.uid(), p_fiscal_period_id, p_series, p_highest_used)
ON CONFLICT (company_id, fiscal_period_id, voucher_series)
DO UPDATE SET
last_number = GREATEST(public.voucher_sequences.last_number, EXCLUDED.last_number),
updated_at = now();
END;
$$;
GRANT EXECUTE ON FUNCTION public.reserve_voucher_range(uuid, uuid, text, integer) TO authenticated;
-- =============================================================================
-- 2. release_voucher_range: roll back sequence on partial import failure
-- Only decreases last_number (never increases), preventing race conditions
-- with concurrent operations that may have legitimately advanced the sequence.
-- =============================================================================
CREATE OR REPLACE FUNCTION public.release_voucher_range(
p_company_id uuid,
p_fiscal_period_id uuid,
p_series text,
p_actual_last integer,
p_reserved_highest integer -- the ceiling this import originally reserved
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
-- Only release within the range this import originally reserved.
-- The upper-bound guard (last_number <= p_reserved_highest) prevents rolling
-- back past numbers that a concurrent operation has legitimately claimed.
UPDATE public.voucher_sequences
SET last_number = p_actual_last,
updated_at = now()
WHERE company_id = p_company_id
AND fiscal_period_id = p_fiscal_period_id
AND voucher_series = p_series
AND last_number > p_actual_last
AND last_number <= p_reserved_highest;
END;
$$;
GRANT EXECUTE ON FUNCTION public.release_voucher_range(uuid, uuid, text, integer, integer) TO authenticated;