fix(company): migration reset no longer blocks on existing vouchers, sequences, or invoices (#1977)

* fix(company): migration reset no longer blocks on existing vouchers, sequences, or invoices

The 2026-08-18 eligibility rule stopped the archive-and-replace reset before
the first journal entry, voucher sequence, or invoice. The reset deletes
nothing: the source stays a write-closed, downloadable retention container,
and the unchecked Radera foretag path already produced the same outcome
without any of those guards, so the blockers only led owners into a dead
end (Carrierstories, 2026-08-26). External-state blockers are unchanged.

Closes #1916

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(decisions): move the migration-reset entry to the end of the log

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-27 14:35:19 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent dfed55cb6c
commit c0ecb34a2b
8 changed files with 231 additions and 131 deletions
@@ -0,0 +1,71 @@
-- Allow archive-and-replace migration resets after accounting records exist.
--
-- The 2026-08-18 eligibility rule stopped self-service before the first
-- journal entry, voucher sequence, or invoice on the retained source. The rule
-- protected voucher-namespace continuity inside the same legal entity, but the
-- reset never deletes anything: the source stays a write-closed retention
-- container holding every voucher, sequence, invoice, and document, and the
-- replacement owner can download it through the retained-source archive. The
-- unchecked "Radera företag" path already produced the same outcome (archived
-- source plus a fresh company with the same org number) with none of that
-- protection, so the blockers only pushed owners toward the weaker route.
-- Observed live 2026-08-26: an owner who imported two fiscal years in the
-- wrong order was shown "Självservice är blockerad" with no way forward.
--
-- Journal-entry, voucher-sequence, and invoice counts stay in the snapshot as
-- retained-data information. Every external-state blocker is unchanged: locked
-- or closed periods, authority submissions and staging state, live bank
-- connections, running imports, active integrations, background work, the
-- 30-day window, and sandbox companies.
ALTER FUNCTION public.company_migration_reset_snapshot(uuid)
RENAME TO company_migration_reset_snapshot_before_20260826150000;
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;
BEGIN
v_snapshot := public.company_migration_reset_snapshot_before_20260826150000(
p_company_id
);
IF v_snapshot ->> 'code' = 'COMPANY_RESET_NOT_FOUND' THEN
RETURN v_snapshot;
END IF;
-- Accounting records are retained, not disposed of, so their presence is
-- reported through counts and must not decide eligibility.
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 (
'journal_entries_exist',
'non_import_committed_entries',
'voucher_sequence_state_exists',
'invoice_records_exist'
);
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. Journal entries, voucher sequences, and invoices are retained data, not blockers; lock, filing, sync, import, integration, and worker state still block.';
REVOKE ALL ON FUNCTION public.company_migration_reset_snapshot_before_20260826150000(uuid)
FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.company_migration_reset_snapshot(uuid)
FROM PUBLIC, anon, authenticated;
NOTIFY pgrst, 'reload schema';