Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
11
database-novo/fixes/fix_addon_credits_fk.sql
Normal file
11
database-novo/fixes/fix_addon_credits_fk.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- ============================================================
|
||||
-- Fix: addon_credits e addon_transactions tenant_id FK
|
||||
-- Corrige FK que apontava para auth.users → agora aponta para public.tenants
|
||||
-- Agência PSI — 2026-03-22
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE public.addon_credits DROP CONSTRAINT IF EXISTS addon_credits_tenant_id_fkey;
|
||||
ALTER TABLE public.addon_credits ADD CONSTRAINT addon_credits_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);
|
||||
|
||||
ALTER TABLE public.addon_transactions DROP CONSTRAINT IF EXISTS addon_transactions_tenant_id_fkey;
|
||||
ALTER TABLE public.addon_transactions ADD CONSTRAINT addon_transactions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);
|
||||
83
database-novo/fixes/fix_addon_rls_saas_admin.sql
Normal file
83
database-novo/fixes/fix_addon_rls_saas_admin.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
-- ============================================================
|
||||
-- Fix: RLS addon_credits e addon_transactions
|
||||
-- 1. SaaS Admin: acesso total
|
||||
-- 2. Tenant members: SELECT nos seus créditos/transações
|
||||
-- Agência PSI — 2026-03-22
|
||||
-- ============================================================
|
||||
|
||||
-- ── addon_products: admin pode tudo (CRUD) ────────────────────
|
||||
DROP POLICY IF EXISTS "addon_products_admin_all" ON public.addon_products;
|
||||
CREATE POLICY "addon_products_admin_all"
|
||||
ON public.addon_products FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- ── addon_credits: admin pode ver todos ───────────────────────
|
||||
DROP POLICY IF EXISTS "addon_credits_admin_select" ON public.addon_credits;
|
||||
CREATE POLICY "addon_credits_admin_select"
|
||||
ON public.addon_credits FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- ── addon_credits: admin pode inserir/atualizar ───────────────
|
||||
DROP POLICY IF EXISTS "addon_credits_admin_write" ON public.addon_credits;
|
||||
CREATE POLICY "addon_credits_admin_write"
|
||||
ON public.addon_credits FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- ── addon_transactions: admin pode ver todas ──────────────────
|
||||
DROP POLICY IF EXISTS "addon_transactions_admin_select" ON public.addon_transactions;
|
||||
CREATE POLICY "addon_transactions_admin_select"
|
||||
ON public.addon_transactions FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- ── addon_transactions: admin pode inserir ────────────────────
|
||||
DROP POLICY IF EXISTS "addon_transactions_admin_insert" ON public.addon_transactions;
|
||||
CREATE POLICY "addon_transactions_admin_insert"
|
||||
ON public.addon_transactions FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- ══════════════════════════════════════════════════════════════
|
||||
-- Corrige policies de tenant members (SELECT)
|
||||
-- A policy original usava tenant_id = auth.uid(), mas o auth.uid()
|
||||
-- é o user_id, não o tenant_id. Usa is_tenant_member() em vez disso.
|
||||
-- ══════════════════════════════════════════════════════════════
|
||||
|
||||
-- addon_credits: membro do tenant vê os créditos do seu tenant
|
||||
DROP POLICY IF EXISTS "addon_credits_select_own" ON public.addon_credits;
|
||||
CREATE POLICY "addon_credits_select_own"
|
||||
ON public.addon_credits FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
public.is_tenant_member(tenant_id)
|
||||
OR owner_id = auth.uid()
|
||||
);
|
||||
|
||||
-- addon_transactions: membro do tenant vê as transações do seu tenant
|
||||
DROP POLICY IF EXISTS "addon_transactions_select_own" ON public.addon_transactions;
|
||||
CREATE POLICY "addon_transactions_select_own"
|
||||
ON public.addon_transactions FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
public.is_tenant_member(tenant_id)
|
||||
OR owner_id = auth.uid()
|
||||
);
|
||||
179
database-novo/fixes/fix_encoding_accents.sql
Normal file
179
database-novo/fixes/fix_encoding_accents.sql
Normal file
@@ -0,0 +1,179 @@
|
||||
-- =============================================================================
|
||||
-- FIX: Corrige acentuação perdida (caracteres ?? no banco)
|
||||
-- =============================================================================
|
||||
-- Causa: Seeds aplicados originalmente sem encoding UTF-8 correto.
|
||||
-- Os ?? são bytes literais 0x3F (ASCII ?) onde deveria haver UTF-8.
|
||||
-- Este fix faz UPDATE direto nos valores conhecidos.
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
SET client_encoding TO 'UTF8';
|
||||
|
||||
-- ============================================================
|
||||
-- 1. PROFILES — full_name
|
||||
-- ============================================================
|
||||
UPDATE profiles SET full_name = 'Clínica Espaço Psi' WHERE id = 'aaaaaaaa-0003-0003-0003-000000000003' AND full_name != 'Clínica Espaço Psi';
|
||||
UPDATE profiles SET full_name = 'Clínica Mente Sã' WHERE id = 'aaaaaaaa-0004-0004-0004-000000000004' AND full_name != 'Clínica Mente Sã';
|
||||
UPDATE profiles SET full_name = 'Clínica Bem Estar' WHERE id = 'aaaaaaaa-0005-0005-0005-000000000005' AND full_name != 'Clínica Bem Estar';
|
||||
UPDATE profiles SET full_name = 'Gabriela Secretária' WHERE id = 'aaaaaaaa-0011-0011-0011-000000000011' AND full_name != 'Gabriela Secretária';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. TENANTS — name
|
||||
-- ============================================================
|
||||
UPDATE tenants SET name = 'Clínica Espaço Psi' WHERE id = 'bbbbbbbb-0003-0003-0003-000000000003';
|
||||
UPDATE tenants SET name = 'Clínica Mente Sã' WHERE id = 'bbbbbbbb-0004-0004-0004-000000000004';
|
||||
UPDATE tenants SET name = 'Clínica Bem Estar' WHERE id = 'bbbbbbbb-0005-0005-0005-000000000005';
|
||||
|
||||
-- ============================================================
|
||||
-- 3. DETERMINED_COMMITMENTS — name
|
||||
-- ============================================================
|
||||
UPDATE determined_commitments SET name = 'Sessão' WHERE native_key = 'session';
|
||||
UPDATE determined_commitments SET name = 'Supervisão' WHERE native_key = 'supervision';
|
||||
UPDATE determined_commitments SET name = 'Análise Pessoal' WHERE native_key = 'analysis';
|
||||
|
||||
-- ============================================================
|
||||
-- 4. PLANS — name, description
|
||||
-- ============================================================
|
||||
UPDATE plans SET name = 'THERAPIST PRO', description = 'Plano profissional para terapeutas' WHERE key = 'therapist_pro' AND description LIKE '%??%';
|
||||
UPDATE plans SET name = 'CLINIC PRO', description = 'Plano profissional para clínicas' WHERE key = 'clinic_pro' AND description LIKE '%??%';
|
||||
UPDATE plans SET name = 'THERAPIST FREE', description = 'Plano gratuito para terapeutas' WHERE key = 'therapist_free' AND description LIKE '%??%';
|
||||
UPDATE plans SET name = 'CLINIC FREE', description = 'Plano gratuito para clínicas' WHERE key = 'clinic_free' AND description LIKE '%??%';
|
||||
|
||||
-- ============================================================
|
||||
-- 5. FEATURES — name, description
|
||||
-- ============================================================
|
||||
UPDATE features SET name = 'Agenda - Visualizar', description = 'Visualização da agenda' WHERE key = 'agenda.view';
|
||||
UPDATE features SET name = 'Agenda - Gerenciar', description = 'Gerenciamento completo da agenda' WHERE key = 'agenda.manage';
|
||||
UPDATE features SET name = 'Pacientes', description = 'Módulo de pacientes' WHERE key = 'patients';
|
||||
UPDATE features SET name = 'Pacientes - Visualizar', description = 'Visualização de pacientes' WHERE key = 'patients.view';
|
||||
UPDATE features SET name = 'Pacientes - Gerenciar', description = 'Gerenciamento completo de pacientes' WHERE key = 'patients.manage';
|
||||
UPDATE features SET name = 'Agendamento Online', description = 'Sistema de agendamento online' WHERE key = 'online_scheduling';
|
||||
UPDATE features SET name = 'Agendamento Online - Gerenciar', description = 'Gerenciamento do agendamento online' WHERE key = 'online_scheduling.manage';
|
||||
UPDATE features SET name = 'Agendamento Online - Público', description = 'Página pública do agendador' WHERE key = 'online_scheduling.public';
|
||||
UPDATE features SET name = 'Lembretes', description = 'Sistema de lembretes automáticos' WHERE key = 'reminders';
|
||||
UPDATE features SET name = 'Relatórios Básicos', description = 'Relatórios básicos' WHERE key = 'reports_basic';
|
||||
UPDATE features SET name = 'Relatórios Avançados', description = 'Relatórios avançados com exportação' WHERE key = 'reports_advanced';
|
||||
UPDATE features SET name = 'Secretária', description = 'Funcionalidade de secretária' WHERE key = 'secretary';
|
||||
UPDATE features SET name = 'Recepção Compartilhada', description = 'Recepção compartilhada entre terapeutas' WHERE key = 'shared_reception';
|
||||
UPDATE features SET name = 'Salas', description = 'Gerenciamento de salas' WHERE key = 'rooms';
|
||||
UPDATE features SET name = 'Intake Público', description = 'Formulário de intake público' WHERE key = 'intake_public';
|
||||
UPDATE features SET name = 'Intakes PRO', description = 'Funcionalidades avançadas de intake' WHERE key = 'intakes_pro';
|
||||
UPDATE features SET name = 'Branding Personalizado', description = 'Personalização de marca' WHERE key = 'custom_branding';
|
||||
UPDATE features SET name = 'Acesso API', description = 'Acesso via API' WHERE key = 'api_access';
|
||||
UPDATE features SET name = 'Log de Auditoria', description = 'Log de auditoria completo' WHERE key = 'audit_log';
|
||||
UPDATE features SET name = 'Lembrete SMS', description = 'Lembretes via SMS' WHERE key = 'sms_reminder';
|
||||
UPDATE features SET name = 'Calendário da Clínica', description = 'Visão consolidada do calendário' WHERE key = 'clinic_calendar';
|
||||
UPDATE features SET name = 'Relatórios Avançados (Clínica)', description = 'Relatórios avançados da clínica' WHERE key = 'advanced_reports';
|
||||
UPDATE features SET name = 'Supervisor - Acesso', description = 'Acesso ao módulo de supervisão' WHERE key = 'supervisor.access';
|
||||
UPDATE features SET name = 'Supervisor - Convidar', description = 'Convidar supervisionados' WHERE key = 'supervisor.invite';
|
||||
UPDATE features SET name = 'Supervisor - Sessões', description = 'Gerenciar sessões de supervisão' WHERE key = 'supervisor.sessions';
|
||||
UPDATE features SET name = 'Supervisor - Relatórios', description = 'Relatórios de supervisão' WHERE key = 'supervisor.reports';
|
||||
|
||||
-- ============================================================
|
||||
-- 6. EMAIL_TEMPLATES_GLOBAL — subject, body_html, body_text
|
||||
-- ============================================================
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Lembrete: sua sessão amanhã às {{session_time}}',
|
||||
body_text = 'Olá {{patient_name}}, lembrete da sua sessão amanhã às {{session_time}} com {{therapist_name}}.'
|
||||
WHERE key = 'session.reminder';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Sessão confirmada — {{session_date}} às {{session_time}}',
|
||||
body_text = 'Sua sessão com {{therapist_name}} em {{session_date}} às {{session_time}} foi confirmada.'
|
||||
WHERE key = 'session.confirmation';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Sessão cancelada — {{session_date}}',
|
||||
body_text = 'A sessão de {{session_date}} às {{session_time}} com {{therapist_name}} foi cancelada.'
|
||||
WHERE key = 'session.cancellation';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Sessão reagendada — novo horário: {{session_date}} às {{session_time}}',
|
||||
body_text = 'Sua sessão foi reagendada para {{session_date}} às {{session_time}} com {{therapist_name}}.'
|
||||
WHERE key = 'session.rescheduled';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Recebemos seu cadastro — {{patient_name}}',
|
||||
body_text = 'Olá {{patient_name}}, recebemos seu formulário de cadastro. Entraremos em contato em breve.'
|
||||
WHERE key = 'intake.received';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Cadastro aprovado — Bem-vindo(a)!',
|
||||
body_text = 'Olá {{patient_name}}, seu cadastro foi aprovado. Você já pode acessar a plataforma.'
|
||||
WHERE key = 'intake.approved';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Cadastro não aprovado',
|
||||
body_text = 'Olá {{patient_name}}, infelizmente seu cadastro não foi aprovado no momento.'
|
||||
WHERE key = 'intake.rejected';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Solicitação aceita — {{session_date}} às {{session_time}}',
|
||||
body_text = 'Sua solicitação de agendamento para {{session_date}} às {{session_time}} foi aceita.'
|
||||
WHERE key = 'scheduler.request_accepted';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Solicitação não disponível',
|
||||
body_text = 'Infelizmente o horário solicitado não está disponível. Por favor, escolha outro horário.'
|
||||
WHERE key = 'scheduler.request_rejected';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Bem-vindo(a) à AgenciaPsi!',
|
||||
body_text = 'Olá {{user_name}}, sua conta foi criada com sucesso. Acesse a plataforma para começar.'
|
||||
WHERE key = 'system.welcome';
|
||||
|
||||
UPDATE email_templates_global SET
|
||||
subject = 'Redefinição de senha — AgenciaPsi',
|
||||
body_text = 'Clique no link abaixo para redefinir sua senha: {{reset_link}}'
|
||||
WHERE key = 'system.password_reset';
|
||||
|
||||
-- ============================================================
|
||||
-- 7. LOGIN_CAROUSEL_SLIDES — title, description
|
||||
-- ============================================================
|
||||
UPDATE login_carousel_slides SET
|
||||
title = '<strong>Gestão clínica simplificada</strong>',
|
||||
body = 'Gerencie agenda, pacientes e financeiro em um só lugar. Simples, rápido e seguro.'
|
||||
WHERE ordem = 1;
|
||||
|
||||
UPDATE login_carousel_slides SET
|
||||
title = '<strong>Múltiplos profissionais, uma só plataforma</strong>',
|
||||
body = 'Ideal para clínicas com vários terapeutas. Cada profissional com sua agenda e seus pacientes.'
|
||||
WHERE ordem = 2;
|
||||
|
||||
UPDATE login_carousel_slides SET
|
||||
title = '<strong>Seguro, privado e sempre disponível</strong>',
|
||||
body = 'Seus dados protegidos com criptografia. Acesse de qualquer lugar, a qualquer hora.'
|
||||
WHERE ordem = 3;
|
||||
|
||||
-- ============================================================
|
||||
-- 8. PATIENT_GROUPS (default groups) — name
|
||||
-- ============================================================
|
||||
UPDATE patient_groups SET nome = 'Crianças' WHERE nome LIKE 'Crian%' AND is_system = true;
|
||||
UPDATE patient_groups SET nome = 'Adolescentes' WHERE nome LIKE 'Adolescen%' AND is_system = true;
|
||||
UPDATE patient_groups SET nome = 'Idosos' WHERE nome LIKE 'Idoso%' AND is_system = true;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. AUTH.USERS — raw_user_meta_data (name field)
|
||||
-- ============================================================
|
||||
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Espaço Psi"') WHERE id = 'aaaaaaaa-0003-0003-0003-000000000003';
|
||||
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Mente Sã"') WHERE id = 'aaaaaaaa-0004-0004-0004-000000000004';
|
||||
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Bem Estar"') WHERE id = 'aaaaaaaa-0005-0005-0005-000000000005';
|
||||
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Gabriela Secretária"') WHERE id = 'aaaaaaaa-0011-0011-0011-000000000011';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
broken_count int;
|
||||
BEGIN
|
||||
SELECT count(*) INTO broken_count
|
||||
FROM profiles WHERE full_name LIKE '%??%';
|
||||
|
||||
IF broken_count = 0 THEN
|
||||
RAISE NOTICE 'fix_encoding_accents: Todos os acentos corrigidos com sucesso.';
|
||||
ELSE
|
||||
RAISE WARNING 'fix_encoding_accents: Ainda restam % registros com ?? em profiles.full_name', broken_count;
|
||||
END IF;
|
||||
END $$;
|
||||
220
database-novo/fixes/fix_missing_subscriptions.sql
Normal file
220
database-novo/fixes/fix_missing_subscriptions.sql
Normal file
@@ -0,0 +1,220 @@
|
||||
-- =============================================================================
|
||||
-- FIX: Atribuir plano free a usuários/tenants sem assinatura ativa
|
||||
-- =============================================================================
|
||||
-- Execute no SQL Editor do Supabase (service_role)
|
||||
-- Idempotente: só insere onde não existe assinatura ativa.
|
||||
--
|
||||
-- Regras:
|
||||
-- • tenant kind = 'therapist' → therapist_free (por user_id do admin)
|
||||
-- • tenant kind IN (clinic_*) → clinic_free (por tenant_id)
|
||||
-- • profiles.account_type = 'patient' / portal_user → patient_free (por user_id)
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
-- DIAGNÓSTICO — mostra o estado atual antes de corrigir
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
BEGIN
|
||||
RAISE NOTICE '=== DIAGNÓSTICO DE ASSINATURAS ===';
|
||||
RAISE NOTICE '';
|
||||
|
||||
-- Terapeutas sem plano
|
||||
RAISE NOTICE '--- Terapeutas SEM assinatura ativa ---';
|
||||
FOR r IN
|
||||
SELECT
|
||||
tm.user_id,
|
||||
p.full_name,
|
||||
t.id AS tenant_id,
|
||||
t.name AS tenant_name
|
||||
FROM public.tenant_members tm
|
||||
JOIN public.tenants t ON t.id = tm.tenant_id
|
||||
JOIN public.profiles p ON p.id = tm.user_id
|
||||
WHERE t.kind = 'therapist'
|
||||
AND tm.role = 'tenant_admin'
|
||||
AND tm.status = 'active'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.user_id = tm.user_id
|
||||
AND s.status = 'active'
|
||||
)
|
||||
LOOP
|
||||
RAISE NOTICE ' FALTANDO: % (%) — tenant %', r.full_name, r.user_id, r.tenant_id;
|
||||
END LOOP;
|
||||
|
||||
-- Clínicas sem plano
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '--- Clínicas SEM assinatura ativa ---';
|
||||
FOR r IN
|
||||
SELECT t.id, t.name, t.kind
|
||||
FROM public.tenants t
|
||||
WHERE t.kind IN ('clinic_coworking', 'clinic_reception', 'clinic_full', 'clinic')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.tenant_id = t.id
|
||||
AND s.status = 'active'
|
||||
)
|
||||
LOOP
|
||||
RAISE NOTICE ' FALTANDO: % (%) — kind %', r.name, r.id, r.kind;
|
||||
END LOOP;
|
||||
|
||||
-- Pacientes sem plano
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '--- Pacientes SEM assinatura ativa ---';
|
||||
FOR r IN
|
||||
SELECT p.id, p.full_name
|
||||
FROM public.profiles p
|
||||
WHERE p.account_type = 'patient'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.user_id = p.id
|
||||
AND s.status = 'active'
|
||||
)
|
||||
LOOP
|
||||
RAISE NOTICE ' FALTANDO: % (%)', r.full_name, r.id;
|
||||
END LOOP;
|
||||
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '=== FIM DO DIAGNÓSTICO — aplicando correções... ===';
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
-- CORREÇÃO 1: Terapeutas sem assinatura → therapist_free
|
||||
-- Escopo: user_id do tenant_admin do tenant kind='therapist'
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
INSERT INTO public.subscriptions (
|
||||
user_id, plan_id, plan_key, status, interval,
|
||||
current_period_start, current_period_end,
|
||||
source, started_at, activated_at
|
||||
)
|
||||
SELECT
|
||||
tm.user_id,
|
||||
p.id,
|
||||
p.key,
|
||||
'active',
|
||||
'month',
|
||||
now(),
|
||||
now() + interval '30 days',
|
||||
'fix_seed',
|
||||
now(),
|
||||
now()
|
||||
FROM public.tenant_members tm
|
||||
JOIN public.tenants t ON t.id = tm.tenant_id
|
||||
JOIN public.plans p ON p.key = 'therapist_free'
|
||||
WHERE t.kind = 'therapist'
|
||||
AND tm.role = 'tenant_admin'
|
||||
AND tm.status = 'active'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.user_id = tm.user_id
|
||||
AND s.status = 'active'
|
||||
);
|
||||
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
-- CORREÇÃO 2: Clínicas sem assinatura → clinic_free
|
||||
-- Escopo: tenant_id
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
INSERT INTO public.subscriptions (
|
||||
tenant_id, plan_id, plan_key, status, interval,
|
||||
current_period_start, current_period_end,
|
||||
source, started_at, activated_at
|
||||
)
|
||||
SELECT
|
||||
t.id,
|
||||
p.id,
|
||||
p.key,
|
||||
'active',
|
||||
'month',
|
||||
now(),
|
||||
now() + interval '30 days',
|
||||
'fix_seed',
|
||||
now(),
|
||||
now()
|
||||
FROM public.tenants t
|
||||
JOIN public.plans p ON p.key = 'clinic_free'
|
||||
WHERE t.kind IN ('clinic_coworking', 'clinic_reception', 'clinic_full', 'clinic')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.tenant_id = t.id
|
||||
AND s.status = 'active'
|
||||
);
|
||||
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
-- CORREÇÃO 3: Pacientes sem assinatura → patient_free
|
||||
-- Escopo: user_id
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
INSERT INTO public.subscriptions (
|
||||
user_id, plan_id, plan_key, status, interval,
|
||||
current_period_start, current_period_end,
|
||||
source, started_at, activated_at
|
||||
)
|
||||
SELECT
|
||||
pr.id,
|
||||
p.id,
|
||||
p.key,
|
||||
'active',
|
||||
'month',
|
||||
now(),
|
||||
now() + interval '30 days',
|
||||
'fix_seed',
|
||||
now(),
|
||||
now()
|
||||
FROM public.profiles pr
|
||||
JOIN public.plans p ON p.key = 'patient_free'
|
||||
WHERE pr.account_type = 'patient'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM public.subscriptions s
|
||||
WHERE s.user_id = pr.id
|
||||
AND s.status = 'active'
|
||||
);
|
||||
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
-- CONFIRMAÇÃO — mostra o que foi inserido (source = 'fix_seed')
|
||||
-- ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
total INT := 0;
|
||||
BEGIN
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '=== ASSINATURAS CRIADAS NESTA EXECUÇÃO ===';
|
||||
|
||||
FOR r IN
|
||||
SELECT
|
||||
s.plan_key,
|
||||
COALESCE(pr.full_name, t.name) AS nome,
|
||||
COALESCE(s.user_id::text, s.tenant_id::text) AS owner_id
|
||||
FROM public.subscriptions s
|
||||
LEFT JOIN public.profiles pr ON pr.id = s.user_id
|
||||
LEFT JOIN public.tenants t ON t.id = s.tenant_id
|
||||
WHERE s.source = 'fix_seed'
|
||||
AND s.started_at >= now() - interval '5 seconds'
|
||||
ORDER BY s.plan_key, nome
|
||||
LOOP
|
||||
RAISE NOTICE ' ✅ % → % (%)', r.plan_key, r.nome, r.owner_id;
|
||||
total := total + 1;
|
||||
END LOOP;
|
||||
|
||||
IF total = 0 THEN
|
||||
RAISE NOTICE ' (nenhuma nova assinatura criada — todos já tinham plano ativo)';
|
||||
ELSE
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE ' Total: % assinatura(s) criada(s).', total;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
45
database-novo/fixes/fix_notification_templates_rls_admin.sql
Normal file
45
database-novo/fixes/fix_notification_templates_rls_admin.sql
Normal file
@@ -0,0 +1,45 @@
|
||||
-- ============================================================
|
||||
-- Fix: RLS notification_templates — acesso SaaS Admin
|
||||
-- Admin precisa criar/editar/excluir templates globais (tenant_id IS NULL)
|
||||
-- Agência PSI — 2026-03-22
|
||||
-- ============================================================
|
||||
|
||||
-- SaaS Admin: acesso total (SELECT + INSERT + UPDATE + DELETE)
|
||||
DROP POLICY IF EXISTS "notif_templates_admin_all" ON public.notification_templates;
|
||||
CREATE POLICY "notif_templates_admin_all"
|
||||
ON public.notification_templates FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- Tenant member: pode ler os globais + os do seu tenant
|
||||
DROP POLICY IF EXISTS "notif_templates_read_global" ON public.notification_templates;
|
||||
CREATE POLICY "notif_templates_read_global"
|
||||
ON public.notification_templates FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
deleted_at IS NULL
|
||||
AND (
|
||||
(tenant_id IS NULL AND is_default = true)
|
||||
OR owner_id = auth.uid()
|
||||
OR public.is_tenant_member(tenant_id)
|
||||
)
|
||||
);
|
||||
|
||||
-- Tenant member: pode inserir/atualizar templates do seu tenant
|
||||
DROP POLICY IF EXISTS "notif_templates_write_owner" ON public.notification_templates;
|
||||
CREATE POLICY "notif_templates_write_owner"
|
||||
ON public.notification_templates FOR ALL
|
||||
TO authenticated
|
||||
USING (
|
||||
owner_id = auth.uid()
|
||||
OR public.is_tenant_member(tenant_id)
|
||||
)
|
||||
WITH CHECK (
|
||||
owner_id = auth.uid()
|
||||
OR public.is_tenant_member(tenant_id)
|
||||
);
|
||||
37
database-novo/fixes/fix_seed_patient_groups.sql
Normal file
37
database-novo/fixes/fix_seed_patient_groups.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- ============================================================
|
||||
-- Fix: cria função seed_default_patient_groups
|
||||
-- Colunas reais: nome, cor, descricao, tenant_id (NOT NULL)
|
||||
-- ============================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.seed_default_patient_groups(p_tenant_id uuid)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_owner_id uuid;
|
||||
BEGIN
|
||||
-- busca o owner (tenant_admin) do tenant
|
||||
SELECT user_id INTO v_owner_id
|
||||
FROM public.tenant_members
|
||||
WHERE tenant_id = p_tenant_id
|
||||
AND role = 'tenant_admin'
|
||||
AND status = 'active'
|
||||
LIMIT 1;
|
||||
|
||||
IF v_owner_id IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.patient_groups (owner_id, nome, cor, is_system, tenant_id)
|
||||
VALUES
|
||||
(v_owner_id, 'Crianças', '#60a5fa', true, p_tenant_id),
|
||||
(v_owner_id, 'Adolescentes', '#a78bfa', true, p_tenant_id),
|
||||
(v_owner_id, 'Idosos', '#34d399', true, p_tenant_id)
|
||||
ON CONFLICT (owner_id, nome) DO NOTHING;
|
||||
END;
|
||||
$$;
|
||||
|
||||
GRANT EXECUTE ON FUNCTION public.seed_default_patient_groups(uuid)
|
||||
TO postgres, anon, authenticated, service_role;
|
||||
50
database-novo/fixes/fix_subscriptions_validate_scope.sql
Normal file
50
database-novo/fixes/fix_subscriptions_validate_scope.sql
Normal file
@@ -0,0 +1,50 @@
|
||||
-- Fix: subscriptions_validate_scope — adiciona suporte a target='patient'
|
||||
|
||||
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 = 'therapist' THEN
|
||||
IF NEW.tenant_id IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'Assinatura therapist não deve ter tenant_id.';
|
||||
END IF;
|
||||
IF NEW.user_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Assinatura therapist exige user_id.';
|
||||
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;
|
||||
$$;
|
||||
|
||||
ALTER FUNCTION public.subscriptions_validate_scope() OWNER TO supabase_admin;
|
||||
78
database-novo/fixes/fix_template_keys_match_populate.sql
Normal file
78
database-novo/fixes/fix_template_keys_match_populate.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- ============================================================
|
||||
-- Fix: Template keys devem casar com o que populate_notification_queue gera
|
||||
-- Agência PSI — 2026-03-22
|
||||
-- ============================================================
|
||||
-- O populate gera: 'session.' || REPLACE(event_type, '_sessao', '') || '.' || channel
|
||||
-- Ex: event_type='lembrete_sessao' → 'session.lembrete.whatsapp'
|
||||
--
|
||||
-- Os seeds usavam nomes em inglês (session.reminder.whatsapp).
|
||||
-- Este fix renomeia para casar com o populate.
|
||||
-- ============================================================
|
||||
|
||||
-- ── 1. Renomeia templates existentes ──────────────────────────
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.lembrete.whatsapp'
|
||||
WHERE key = 'session.reminder.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.lembrete_2h.whatsapp'
|
||||
WHERE key = 'session.reminder_2h.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.confirmacao.whatsapp'
|
||||
WHERE key = 'session.confirmation.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.cancelamento.whatsapp'
|
||||
WHERE key = 'session.cancellation.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.reagendamento.whatsapp'
|
||||
WHERE key = 'session.reschedule.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'cobranca.pendente.whatsapp'
|
||||
WHERE key = 'billing.pending.whatsapp';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'sistema.boas_vindas.whatsapp'
|
||||
WHERE key = 'system.welcome.whatsapp';
|
||||
|
||||
-- ── SMS templates (mesmo padrão) ──────────────────────────────
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.lembrete.sms'
|
||||
WHERE key = 'session.reminder.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.lembrete_2h.sms'
|
||||
WHERE key = 'session.reminder_2h.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.confirmacao.sms'
|
||||
WHERE key = 'session.confirmation.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.cancelamento.sms'
|
||||
WHERE key = 'session.cancellation.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'session.reagendamento.sms'
|
||||
WHERE key = 'session.reschedule.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'cobranca.pendente.sms'
|
||||
WHERE key = 'billing.pending.sms';
|
||||
|
||||
UPDATE public.notification_templates
|
||||
SET key = 'sistema.boas_vindas.sms'
|
||||
WHERE key = 'system.welcome.sms';
|
||||
|
||||
|
||||
-- ── 2. Verifica resultado ─────────────────────────────────────
|
||||
|
||||
SELECT key, channel, domain, event_type, is_default
|
||||
FROM notification_templates
|
||||
WHERE deleted_at IS NULL
|
||||
ORDER BY channel, key;
|
||||
Reference in New Issue
Block a user