-- ============================================================ -- SUPERVISOR — Fase 1 -- Aplicar no Supabase SQL Editor (em ordem) -- ============================================================ -- ──────────────────────────────────────────────────────────── -- 1. tenants.kind → adiciona 'supervisor' -- ──────────────────────────────────────────────────────────── ALTER TABLE public.tenants DROP CONSTRAINT IF EXISTS tenants_kind_check; ALTER TABLE public.tenants ADD CONSTRAINT tenants_kind_check CHECK (kind = ANY (ARRAY[ 'therapist', 'clinic_coworking', 'clinic_reception', 'clinic_full', 'clinic', 'saas', 'supervisor' -- ← novo ])); -- ──────────────────────────────────────────────────────────── -- 2. plans.target → adiciona 'supervisor' -- ──────────────────────────────────────────────────────────── ALTER TABLE public.plans DROP CONSTRAINT IF EXISTS plans_target_check; ALTER TABLE public.plans ADD CONSTRAINT plans_target_check CHECK (target = ANY (ARRAY[ 'patient', 'therapist', 'clinic', 'supervisor' -- ← novo ])); -- ──────────────────────────────────────────────────────────── -- 3. plans.max_supervisees — limite de supervisionados -- ──────────────────────────────────────────────────────────── ALTER TABLE public.plans ADD COLUMN IF NOT EXISTS max_supervisees integer DEFAULT NULL; COMMENT ON COLUMN public.plans.max_supervisees IS 'Limite de terapeutas que podem ser supervisionados. Apenas para planos target=supervisor. NULL = sem limite.'; -- ──────────────────────────────────────────────────────────── -- 4. Planos supervisor_free e supervisor_pro -- ──────────────────────────────────────────────────────────── INSERT INTO public.plans (key, name, description, target, is_active, max_supervisees) VALUES ( 'supervisor_free', 'Supervisor Free', 'Plano gratuito de supervisão. Até 3 terapeutas supervisionados.', 'supervisor', true, 3 ), ( 'supervisor_pro', 'Supervisor PRO', 'Plano profissional de supervisão. Até 20 terapeutas supervisionados.', 'supervisor', true, 20 ) ON CONFLICT (key) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, target = EXCLUDED.target, is_active = EXCLUDED.is_active, max_supervisees = EXCLUDED.max_supervisees; -- ──────────────────────────────────────────────────────────── -- 5. Features de supervisor -- ──────────────────────────────────────────────────────────── INSERT INTO public.features (key, name, descricao) VALUES ( 'supervisor.access', 'Acesso à Supervisão', 'Acesso básico ao espaço de supervisão (sala, lista de supervisionados).' ), ( 'supervisor.invite', 'Convidar Supervisionados', 'Permite convidar terapeutas para participar da sala de supervisão.' ), ( 'supervisor.sessions', 'Sessões de Supervisão', 'Agendamento e registro de sessões de supervisão.' ), ( 'supervisor.reports', 'Relatórios de Supervisão', 'Relatórios avançados de progresso e evolução dos supervisionados.' ) ON CONFLICT (key) DO UPDATE SET name = EXCLUDED.name, descricao = EXCLUDED.descricao; -- ──────────────────────────────────────────────────────────── -- 6. plan_features — vincula features aos planos supervisor -- ──────────────────────────────────────────────────────────── DO $$ DECLARE v_free_id uuid; v_pro_id uuid; v_f_access uuid; v_f_invite uuid; v_f_sessions uuid; v_f_reports uuid; BEGIN SELECT id INTO v_free_id FROM public.plans WHERE key = 'supervisor_free'; SELECT id INTO v_pro_id FROM public.plans WHERE key = 'supervisor_pro'; SELECT id INTO v_f_access FROM public.features WHERE key = 'supervisor.access'; SELECT id INTO v_f_invite FROM public.features WHERE key = 'supervisor.invite'; SELECT id INTO v_f_sessions FROM public.features WHERE key = 'supervisor.sessions'; SELECT id INTO v_f_reports FROM public.features WHERE key = 'supervisor.reports'; -- supervisor_free: access + invite (limitado por max_supervisees=3) INSERT INTO public.plan_features (plan_id, feature_id) VALUES (v_free_id, v_f_access), (v_free_id, v_f_invite) ON CONFLICT DO NOTHING; -- supervisor_pro: tudo INSERT INTO public.plan_features (plan_id, feature_id) VALUES (v_pro_id, v_f_access), (v_pro_id, v_f_invite), (v_pro_id, v_f_sessions), (v_pro_id, v_f_reports) ON CONFLICT DO NOTHING; END; $$; -- ──────────────────────────────────────────────────────────── -- 7. activate_subscription_from_intent — suporte a supervisor -- Supervisor = pessoal (user_id), sem tenant_id (igual therapist) -- ──────────────────────────────────────────────────────────── CREATE OR REPLACE FUNCTION public.activate_subscription_from_intent(p_intent_id uuid) RETURNS public.subscriptions LANGUAGE plpgsql SECURITY DEFINER AS $$ declare v_intent record; v_sub public.subscriptions; v_days int; v_user_id uuid; v_plan_id uuid; v_target text; begin -- lê pela VIEW unificada select * into v_intent from public.subscription_intents where id = p_intent_id; if not found then raise exception 'Intent não encontrado: %', p_intent_id; end if; if v_intent.status <> 'paid' then raise exception 'Intent precisa estar paid para ativar assinatura'; end if; -- resolve target e plan_id via plans.key select p.id, p.target into v_plan_id, v_target from public.plans p where p.key = v_intent.plan_key limit 1; if v_plan_id is null then raise exception 'Plano não encontrado em plans.key = %', v_intent.plan_key; end if; v_target := lower(coalesce(v_target, '')); -- ✅ supervisor adicionado if v_target not in ('clinic', 'therapist', 'supervisor') then raise exception 'Target inválido em plans.target: %', v_target; end if; -- regra por target if v_target = 'clinic' then if v_intent.tenant_id is null then raise exception 'Intent sem tenant_id'; end if; else -- therapist ou supervisor: vinculado ao user v_user_id := v_intent.user_id; if v_user_id is null then v_user_id := v_intent.created_by_user_id; end if; end if; if v_target in ('therapist', 'supervisor') and v_user_id is null then raise exception 'Não foi possível determinar user_id para assinatura %.', v_target; end if; -- cancela assinatura ativa anterior if v_target = 'clinic' then update public.subscriptions set status = 'cancelled', cancelled_at = now() where tenant_id = v_intent.tenant_id and plan_id = v_plan_id and status = 'active'; else -- therapist ou supervisor update public.subscriptions set status = 'cancelled', cancelled_at = now() where user_id = v_user_id and plan_id = v_plan_id and status = 'active' and tenant_id is null; end if; -- duração do plano (30 dias para mensal) v_days := case when lower(coalesce(v_intent.interval, 'month')) = 'year' then 365 else 30 end; -- cria nova assinatura insert into public.subscriptions ( user_id, plan_id, status, started_at, expires_at, cancelled_at, activated_at, tenant_id, plan_key, interval, source, created_at, updated_at ) values ( case when v_target = 'clinic' then null else v_user_id end, v_plan_id, 'active', now(), now() + make_interval(days => v_days), null, now(), case when v_target = 'clinic' then v_intent.tenant_id else null end, v_intent.plan_key, v_intent.interval, 'manual', now(), now() ) returning * into v_sub; -- grava vínculo intent → subscription if v_target = 'clinic' then update public.subscription_intents_tenant set subscription_id = v_sub.id where id = p_intent_id; else update public.subscription_intents_personal set subscription_id = v_sub.id where id = p_intent_id; end if; return v_sub; end; $$; -- ──────────────────────────────────────────────────────────── -- 8. subscriptions_validate_scope — suporte a supervisor -- ──────────────────────────────────────────────────────────── CREATE OR REPLACE FUNCTION public.subscriptions_validate_scope() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_target text; BEGIN SELECT lower(p.target) INTO v_target FROM public.plans p WHERE p.id = NEW.plan_id; IF v_target IS NULL THEN RAISE EXCEPTION 'Plano inválido (target nulo).'; END IF; IF v_target = 'clinic' THEN IF NEW.tenant_id IS NULL THEN RAISE EXCEPTION 'Assinatura clinic exige tenant_id.'; END IF; IF NEW.user_id IS NOT NULL THEN RAISE EXCEPTION 'Assinatura clinic não pode ter user_id (XOR).'; END IF; ELSIF v_target IN ('therapist', 'supervisor') THEN -- supervisor é pessoal como therapist IF NEW.tenant_id IS NOT NULL THEN RAISE EXCEPTION 'Assinatura % não deve ter tenant_id.', v_target; END IF; IF NEW.user_id IS NULL THEN RAISE EXCEPTION 'Assinatura % exige user_id.', v_target; END IF; ELSIF v_target = 'patient' THEN IF NEW.tenant_id IS NOT NULL THEN RAISE EXCEPTION 'Assinatura patient não deve ter tenant_id.'; END IF; IF NEW.user_id IS NULL THEN RAISE EXCEPTION 'Assinatura patient exige user_id.'; END IF; ELSE RAISE EXCEPTION 'Target de plano inválido: %', v_target; END IF; RETURN NEW; END; $$; -- ──────────────────────────────────────────────────────────── -- 9. subscription_intents_view_insert — suporte a supervisor -- supervisor é roteado como therapist (tabela personal) -- ──────────────────────────────────────────────────────────── CREATE OR REPLACE FUNCTION public.subscription_intents_view_insert() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$ declare v_target text; v_plan_id uuid; begin select p.id, p.target into v_plan_id, v_target from public.plans p where p.key = new.plan_key; if v_plan_id is null then raise exception 'Plano inválido: plan_key=%', new.plan_key; end if; if lower(v_target) = 'clinic' then if new.tenant_id is null then raise exception 'Intenção clinic exige tenant_id.'; end if; insert into public.subscription_intents_tenant ( id, tenant_id, created_by_user_id, email, plan_id, plan_key, interval, amount_cents, currency, status, source, notes, created_at, paid_at ) values ( coalesce(new.id, gen_random_uuid()), new.tenant_id, new.created_by_user_id, new.email, v_plan_id, new.plan_key, coalesce(new.interval,'month'), new.amount_cents, coalesce(new.currency,'BRL'), coalesce(new.status,'pending'), coalesce(new.source,'manual'), new.notes, coalesce(new.created_at, now()), new.paid_at ); new.plan_target := 'clinic'; return new; end if; -- therapist ou supervisor → tabela personal if lower(v_target) in ('therapist', 'supervisor') then insert into public.subscription_intents_personal ( id, user_id, created_by_user_id, email, plan_id, plan_key, interval, amount_cents, currency, status, source, notes, created_at, paid_at ) values ( coalesce(new.id, gen_random_uuid()), new.user_id, new.created_by_user_id, new.email, v_plan_id, new.plan_key, coalesce(new.interval,'month'), new.amount_cents, coalesce(new.currency,'BRL'), coalesce(new.status,'pending'), coalesce(new.source,'manual'), new.notes, coalesce(new.created_at, now()), new.paid_at ); new.plan_target := lower(v_target); -- 'therapist' ou 'supervisor' return new; end if; raise exception 'Target de plano não suportado: %', v_target; end; $$; -- ──────────────────────────────────────────────────────────── -- FIM — verificação rápida -- ──────────────────────────────────────────────────────────── SELECT key, name, target, max_supervisees FROM public.plans WHERE target = 'supervisor' ORDER BY key;