Files
myeasycms-v2/apps/web/supabase/migrations/20260416000006_event_member_link.sql
T. Zehetbauer 5c5aaabae5
Some checks failed
Workflow / ʦ TypeScript (pull_request) Failing after 5m57s
Workflow / ⚫️ Test (pull_request) Has been skipped
refactor: remove obsolete member management API module
2026-04-03 14:08:31 +02:00

156 lines
5.1 KiB
PL/PgSQL

-- =====================================================
-- Event-Member Linkage
--
-- Problem: event_registrations links to members by email
-- only. If a member changes their email, event history is
-- lost. transfer_member matches by email — fragile.
--
-- Fix: Add member_id FK to event_registrations, backfill
-- from email matches, update transfer_member.
-- =====================================================
-- Add member_id FK column
ALTER TABLE public.event_registrations
ADD COLUMN IF NOT EXISTS member_id uuid
REFERENCES public.members(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS ix_event_registrations_member
ON public.event_registrations(member_id)
WHERE member_id IS NOT NULL;
-- Backfill: match existing registrations to members by email within the same account
UPDATE public.event_registrations er
SET member_id = m.id
FROM public.events e
JOIN public.members m ON m.account_id = e.account_id
AND lower(m.email) = lower(er.email)
AND m.email IS NOT NULL AND m.email != ''
AND m.status IN ('active', 'inactive', 'pending')
WHERE e.id = er.event_id
AND er.member_id IS NULL
AND er.email IS NOT NULL AND er.email != '';
-- Update transfer_member to count active events via member_id instead of email
CREATE OR REPLACE FUNCTION public.transfer_member(
p_member_id uuid,
p_target_account_id uuid,
p_reason text DEFAULT NULL,
p_keep_sepa boolean DEFAULT false
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_source_account_id uuid;
v_source_name varchar;
v_target_name varchar;
v_active_courses bigint;
v_active_events bigint;
v_cleared_data jsonb;
v_transfer_id uuid;
v_member record;
BEGIN
-- Get current member state
SELECT * INTO v_member FROM public.members WHERE id = p_member_id;
IF v_member IS NULL THEN
RAISE EXCEPTION 'Member not found';
END IF;
v_source_account_id := v_member.account_id;
-- Verify target account exists
IF NOT EXISTS (SELECT 1 FROM public.accounts WHERE id = p_target_account_id) THEN
RAISE EXCEPTION 'Target account not found';
END IF;
-- Ensure caller has access to source account
IF NOT public.has_role_on_account_or_ancestor(v_source_account_id) THEN
RAISE EXCEPTION 'Access denied to source account';
END IF;
-- Same account? No-op
IF v_source_account_id = p_target_account_id THEN
RAISE EXCEPTION 'Cannot transfer member to the same account';
END IF;
-- Ensure both accounts share a common ancestor
IF NOT EXISTS (
SELECT 1
FROM public.get_account_ancestors(v_source_account_id) sa
JOIN public.get_account_ancestors(p_target_account_id) ta ON sa = ta
) THEN
RAISE EXCEPTION 'Source and target accounts do not share a common ancestor (Verband)';
END IF;
-- Get org names for the transfer note
SELECT name INTO v_source_name FROM public.accounts WHERE id = v_source_account_id;
SELECT name INTO v_target_name FROM public.accounts WHERE id = p_target_account_id;
-- Count active relationships (informational, for the log)
SELECT count(*) INTO v_active_courses
FROM public.course_participants cp
JOIN public.courses c ON c.id = cp.course_id
WHERE cp.member_id = p_member_id AND cp.status = 'enrolled';
-- Use member_id for event lookups instead of fragile email matching
SELECT count(*) INTO v_active_events
FROM public.event_registrations er
JOIN public.events e ON e.id = er.event_id
WHERE er.member_id = p_member_id
AND er.status IN ('confirmed', 'pending')
AND e.event_date >= current_date;
-- Perform the transfer
UPDATE public.members
SET
account_id = p_target_account_id,
-- Clear org-specific admin data
dues_category_id = NULL,
member_number = NULL,
-- Clear primary_mandate_id FK (mandate needs re-confirmation in new org)
primary_mandate_id = NULL,
-- Legacy inline SEPA fields (deprecated, kept for backward compat)
sepa_mandate_id = CASE WHEN p_keep_sepa THEN sepa_mandate_id ELSE NULL END,
sepa_mandate_date = CASE WHEN p_keep_sepa THEN sepa_mandate_date ELSE NULL END,
sepa_mandate_status = 'pending',
-- Append transfer note
notes = COALESCE(notes, '') ||
E'\n[Transfer ' || to_char(now(), 'YYYY-MM-DD') || '] ' ||
v_source_name || '' || v_target_name ||
COALESCE(' | Grund: ' || p_reason, ''),
is_transferred = true
WHERE id = p_member_id;
-- Reset SEPA mandate(s) in the mandates table
UPDATE public.sepa_mandates
SET status = 'pending'
WHERE member_id = p_member_id
AND status = 'active';
-- Build cleared data snapshot for the transfer log
v_cleared_data := jsonb_build_object(
'member_number', v_member.member_number,
'dues_category_id', v_member.dues_category_id,
'active_courses', v_active_courses,
'active_events', v_active_events
);
-- Create transfer log entry
INSERT INTO public.member_transfers (
member_id, source_account_id, target_account_id, transferred_by, reason, cleared_data
) VALUES (
p_member_id,
v_source_account_id,
p_target_account_id,
COALESCE(nullif(current_setting('app.current_user_id', true), '')::uuid, auth.uid()),
p_reason,
v_cleared_data
)
RETURNING id INTO v_transfer_id;
RETURN v_transfer_id;
END;
$$;