Files
accounted/supabase/migrations/20260818141018_harden_company_migration_reset_eligibility.sql
Mattsson 3a1b842e4a feat: add safe owner-only migration reset (#1682)
* feat: add safe company migration reset

* fix: harden company reset eligibility

* fix: close company reset compliance gaps

* test: fix migration reset pg-real probes

* fix: preserve migration archive access

* docs: explain migration numbering continuity

* fix: block reset with VAT workflow state

* fix: block externally staged reset data

* fix: address migration reset review findings

* fix: clear stale migration archive estimate

* fix: retry migration archive estimates
2026-08-19 12:04:24 +02:00

102 lines
3.4 KiB
PL/PgSQL

-- Keep archive-and-replace resets before the first accounting record exists.
-- A replacement represents the same legal entity, so it must not start a
-- second voucher-number namespace after any draft, posting, or sequence state
-- has already been created on the retained source.
ALTER FUNCTION public.company_migration_reset_snapshot(uuid)
RENAME TO company_migration_reset_snapshot_before_20260818141018;
CREATE OR REPLACE FUNCTION public.company_migration_reset_snapshot(p_company_id uuid)
RETURNS jsonb
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_snapshot jsonb;
v_blockers jsonb;
v_journal_entries integer;
v_voucher_sequences integer;
v_authority_submissions integer;
v_direct_vat_submissions integer;
BEGIN
v_snapshot := public.company_migration_reset_snapshot_before_20260818141018(
p_company_id
);
IF v_snapshot ->> 'code' = 'COMPANY_RESET_NOT_FOUND' THEN
RETURN v_snapshot;
END IF;
SELECT count(*) INTO v_journal_entries
FROM public.journal_entries
WHERE company_id = p_company_id;
SELECT count(*) INTO v_voucher_sequences
FROM public.voucher_sequences
WHERE company_id = p_company_id;
SELECT COALESCE(sum((existing.blocker ->> 'count')::integer), 0)
INTO v_authority_submissions
FROM jsonb_array_elements(v_snapshot -> 'blockers') AS existing(blocker)
WHERE existing.blocker ->> 'code' = 'authority_submission_detected';
-- The VAT submission chain normally records a successful declaration lock
-- first. Count the direct submission endpoint as independent evidence too,
-- so a historical or interrupted audit chain still fails closed.
SELECT count(*) INTO v_direct_vat_submissions
FROM public.skatteverket_api_audit_log
WHERE company_id = p_company_id
AND outcome = 'ok'
AND endpoint = 'declaration/submit';
v_authority_submissions := v_authority_submissions + v_direct_vat_submissions;
SELECT COALESCE(jsonb_agg(existing.blocker ORDER BY existing.position), '[]'::jsonb)
INTO v_blockers
FROM jsonb_array_elements(v_snapshot -> 'blockers')
WITH ORDINALITY AS existing(blocker, position)
WHERE existing.blocker ->> 'code' NOT IN (
'non_import_committed_entries',
'authority_submission_detected'
);
IF v_journal_entries > 0 THEN
v_blockers := v_blockers || jsonb_build_array(jsonb_build_object(
'code', 'journal_entries_exist',
'count', v_journal_entries
));
END IF;
IF v_voucher_sequences > 0 THEN
v_blockers := v_blockers || jsonb_build_array(jsonb_build_object(
'code', 'voucher_sequence_state_exists',
'count', v_voucher_sequences
));
END IF;
IF v_authority_submissions > 0 THEN
v_blockers := v_blockers || jsonb_build_array(jsonb_build_object(
'code', 'authority_submission_detected',
'count', v_authority_submissions
));
END IF;
RETURN v_snapshot || jsonb_build_object(
'eligible', jsonb_array_length(v_blockers) = 0,
'blockers', v_blockers
);
END;
$$;
COMMENT ON FUNCTION public.company_migration_reset_snapshot(uuid) IS
'Internal fail-closed reset snapshot. Any journal entry or voucher sequence blocks archive-and-replace.';
REVOKE ALL ON FUNCTION public.company_migration_reset_snapshot_before_20260818141018(uuid)
FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.company_migration_reset_snapshot(uuid)
FROM PUBLIC, anon, authenticated;
NOTIFY pgrst, 'reload schema';