Files
RDO/supabase/migrations/20241202000002_create_functions_and_triggers.sql
2026-02-20 07:25:32 -03:00

406 lines
13 KiB
PL/PgSQL

-- ========================================
-- MIGRATION: Functions and Triggers (FIXED)
-- Data: 2024-12-02
-- Descrição: Funções auxiliares e triggers automáticos
-- ========================================
-- ========================================
-- 1. FUNÇÃO PARA ATUALIZAR UPDATED_AT
-- ========================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Aplicar trigger em todas as tabelas relevantes
CREATE TRIGGER update_organizacoes_updated_at BEFORE UPDATE ON public.organizacoes
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_usuarios_updated_at BEFORE UPDATE ON public.usuarios
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_organizacao_usuarios_updated_at BEFORE UPDATE ON public.organizacao_usuarios
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_obras_updated_at BEFORE UPDATE ON public.obras
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_rdos_updated_at BEFORE UPDATE ON public.rdos
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_tarefas_updated_at BEFORE UPDATE ON public.tarefas
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ========================================
-- 2. FUNÇÃO PARA CRIAR USUÁRIO AUTOMATICAMENTE
-- ========================================
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
DECLARE
v_org_id UUID;
v_nome TEXT;
v_email TEXT;
BEGIN
-- Extrair dados do metadata
v_nome := COALESCE(NEW.raw_user_meta_data->>'nome', NEW.email);
v_email := NEW.email;
v_org_id := (NEW.raw_user_meta_data->>'organizacao_id')::UUID;
-- Se não tem org_id no metadata, não criar o perfil ainda
-- (será criado quando aceitar um convite)
IF v_org_id IS NULL THEN
RETURN NEW;
END IF;
-- Criar perfil de usuário
INSERT INTO public.usuarios (id, organizacao_id, nome, email)
VALUES (NEW.id, v_org_id, v_nome, v_email)
ON CONFLICT (id) DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Trigger para criar usuário automaticamente
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
-- ========================================
-- 3. FUNÇÃO PARA AUTO-INCREMENTAR NÚMERO DO RDO
-- ========================================
CREATE OR REPLACE FUNCTION public.set_rdo_numero()
RETURNS TRIGGER AS $$
DECLARE
v_max_numero INTEGER;
BEGIN
-- Se já tem número, não fazer nada
IF NEW.numero IS NOT NULL THEN
RETURN NEW;
END IF;
-- Buscar o maior número para esta obra
SELECT COALESCE(MAX(numero), 0) INTO v_max_numero
FROM public.rdos
WHERE obra_id = NEW.obra_id;
-- Atribuir próximo número
NEW.numero := v_max_numero + 1;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_rdo_numero_trigger
BEFORE INSERT ON public.rdos
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_numero();
-- ========================================
-- 4. FUNÇÃO PARA PROPAGAR ORGANIZACAO_ID
-- ========================================
-- Quando criar um RDO, copiar organizacao_id da obra
CREATE OR REPLACE FUNCTION public.set_rdo_organizacao_id()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.organizacao_id IS NULL THEN
SELECT organizacao_id INTO NEW.organizacao_id
FROM public.obras
WHERE id = NEW.obra_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_rdo_organizacao_id_trigger
BEFORE INSERT ON public.rdos
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_organizacao_id();
-- Propagar para tabelas relacionadas ao RDO
CREATE OR REPLACE FUNCTION public.set_rdo_child_organizacao_id()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.organizacao_id IS NULL THEN
SELECT organizacao_id INTO NEW.organizacao_id
FROM public.rdos
WHERE id = NEW.rdo_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_rdo_atividades_org_trigger
BEFORE INSERT ON public.rdo_atividades
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_mao_obra_org_trigger
BEFORE INSERT ON public.rdo_mao_obra
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_equipamentos_org_trigger
BEFORE INSERT ON public.rdo_equipamentos
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_ocorrencias_org_trigger
BEFORE INSERT ON public.rdo_ocorrencias
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_anexos_org_trigger
BEFORE INSERT ON public.rdo_anexos
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_inspecoes_org_trigger
BEFORE INSERT ON public.rdo_inspecoes_solda
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
CREATE TRIGGER set_rdo_verificacoes_org_trigger
BEFORE INSERT ON public.rdo_verificacoes_torque
FOR EACH ROW EXECUTE FUNCTION public.set_rdo_child_organizacao_id();
-- Propagar para tarefas
CREATE OR REPLACE FUNCTION public.set_tarefa_organizacao_id()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.organizacao_id IS NULL THEN
SELECT organizacao_id INTO NEW.organizacao_id
FROM public.obras
WHERE id = NEW.obra_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_tarefa_organizacao_id_trigger
BEFORE INSERT ON public.tarefas
FOR EACH ROW EXECUTE FUNCTION public.set_tarefa_organizacao_id();
-- Propagar para task_logs
CREATE OR REPLACE FUNCTION public.set_task_log_organizacao_id()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.organizacao_id IS NULL THEN
SELECT organizacao_id INTO NEW.organizacao_id
FROM public.tarefas
WHERE id = NEW.task_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_task_log_organizacao_id_trigger
BEFORE INSERT ON public.task_logs
FOR EACH ROW EXECUTE FUNCTION public.set_task_log_organizacao_id();
-- ========================================
-- 5. FUNÇÃO PARA ATUALIZAR MÉTRICAS
-- ========================================
CREATE OR REPLACE FUNCTION public.atualizar_metricas_organizacao()
RETURNS TRIGGER AS $$
DECLARE
v_org_id UUID;
v_mes_ref DATE;
BEGIN
-- Determinar organizacao_id baseado na operação
IF TG_OP = 'DELETE' THEN
v_org_id := OLD.organizacao_id;
ELSE
v_org_id := NEW.organizacao_id;
END IF;
v_mes_ref := DATE_TRUNC('month', CURRENT_DATE);
-- Inserir ou atualizar métricas
INSERT INTO public.organizacao_metricas (
organizacao_id,
mes_referencia,
total_usuarios,
total_obras,
total_rdos,
limite_usuarios,
limite_obras,
limite_rdos_mes,
limite_storage_mb
)
SELECT
v_org_id,
v_mes_ref,
(SELECT COUNT(*) FROM public.usuarios WHERE organizacao_id = v_org_id AND ativo = true),
(SELECT COUNT(*) FROM public.obras WHERE organizacao_id = v_org_id),
(SELECT COUNT(*) FROM public.rdos WHERE organizacao_id = v_org_id AND DATE_TRUNC('month', created_at) = v_mes_ref),
o.max_usuarios,
o.max_obras,
o.max_rdos_mes,
o.max_storage_mb
FROM public.organizacoes o
WHERE o.id = v_org_id
ON CONFLICT (organizacao_id, mes_referencia)
DO UPDATE SET
total_usuarios = EXCLUDED.total_usuarios,
total_obras = EXCLUDED.total_obras,
total_rdos = EXCLUDED.total_rdos,
limite_usuarios = EXCLUDED.limite_usuarios,
limite_obras = EXCLUDED.limite_obras,
limite_rdos_mes = EXCLUDED.limite_rdos_mes,
limite_storage_mb = EXCLUDED.limite_storage_mb,
updated_at = NOW();
IF TG_OP = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Triggers para atualizar métricas
CREATE TRIGGER atualizar_metricas_usuarios
AFTER INSERT OR UPDATE OR DELETE ON public.usuarios
FOR EACH ROW EXECUTE FUNCTION public.atualizar_metricas_organizacao();
CREATE TRIGGER atualizar_metricas_obras
AFTER INSERT OR DELETE ON public.obras
FOR EACH ROW EXECUTE FUNCTION public.atualizar_metricas_organizacao();
CREATE TRIGGER atualizar_metricas_rdos
AFTER INSERT OR DELETE ON public.rdos
FOR EACH ROW EXECUTE FUNCTION public.atualizar_metricas_organizacao();
-- ========================================
-- 6. FUNÇÃO PARA VERIFICAR LIMITES DO PLANO
-- ========================================
CREATE OR REPLACE FUNCTION public.verificar_limite_usuarios()
RETURNS TRIGGER AS $$
DECLARE
v_total_usuarios INTEGER;
v_limite INTEGER;
BEGIN
-- Contar usuários ativos da organização
SELECT COUNT(*) INTO v_total_usuarios
FROM public.usuarios
WHERE organizacao_id = NEW.organizacao_id AND ativo = true;
-- Buscar limite do plano
SELECT max_usuarios INTO v_limite
FROM public.organizacoes
WHERE id = NEW.organizacao_id;
-- Verificar se excedeu o limite
IF v_total_usuarios >= v_limite THEN
RAISE EXCEPTION 'Limite de usuários atingido para esta organização. Plano atual permite % usuários.', v_limite;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER verificar_limite_usuarios_trigger
BEFORE INSERT ON public.usuarios
FOR EACH ROW EXECUTE FUNCTION public.verificar_limite_usuarios();
CREATE OR REPLACE FUNCTION public.verificar_limite_obras()
RETURNS TRIGGER AS $$
DECLARE
v_total_obras INTEGER;
v_limite INTEGER;
BEGIN
-- Contar obras da organização
SELECT COUNT(*) INTO v_total_obras
FROM public.obras
WHERE organizacao_id = NEW.organizacao_id;
-- Buscar limite do plano
SELECT max_obras INTO v_limite
FROM public.organizacoes
WHERE id = NEW.organizacao_id;
-- Verificar se excedeu o limite
IF v_total_obras >= v_limite THEN
RAISE EXCEPTION 'Limite de obras atingido para esta organização. Plano atual permite % obras.', v_limite;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER verificar_limite_obras_trigger
BEFORE INSERT ON public.obras
FOR EACH ROW EXECUTE FUNCTION public.verificar_limite_obras();
-- ========================================
-- 7. FUNÇÕES AUXILIARES PARA QUERIES
-- ========================================
-- Função para obter role do usuário em uma organização
CREATE OR REPLACE FUNCTION public.get_user_role(p_user_id UUID, p_org_id UUID)
RETURNS TEXT AS $$
BEGIN
RETURN (
SELECT role
FROM public.organizacao_usuarios
WHERE usuario_id = p_user_id
AND organizacao_id = p_org_id
AND ativo = true
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Função para verificar se usuário tem permissão
CREATE OR REPLACE FUNCTION public.user_has_permission(
p_user_id UUID,
p_org_id UUID,
p_permissao TEXT
)
RETURNS BOOLEAN AS $$
DECLARE
v_role TEXT;
BEGIN
v_role := public.get_user_role(p_user_id, p_org_id);
-- Lógica de permissões baseada em role
RETURN CASE
WHEN v_role = 'owner' THEN true
WHEN v_role = 'admin' THEN true
WHEN v_role = 'engenheiro' AND p_permissao IN ('criar_rdo', 'aprovar_rdo', 'criar_obra', 'editar_obra') THEN true
WHEN v_role = 'mestre_obra' AND p_permissao IN ('criar_rdo', 'editar_rdo') THEN true
WHEN v_role = 'usuario' AND p_permissao IN ('visualizar_rdo', 'visualizar_obra') THEN true
ELSE false
END;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Função para obter organizacao_id do usuário atual
CREATE OR REPLACE FUNCTION public.get_current_user_org_id()
RETURNS UUID AS $$
BEGIN
RETURN (
SELECT organizacao_id
FROM public.usuarios
WHERE id = auth.uid()
LIMIT 1
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- ========================================
-- COMENTÁRIOS
-- ========================================
COMMENT ON FUNCTION update_updated_at_column() IS 'Atualiza automaticamente o campo updated_at';
COMMENT ON FUNCTION handle_new_user() IS 'Cria perfil de usuário automaticamente após registro no auth';
COMMENT ON FUNCTION set_rdo_numero() IS 'Define automaticamente o número sequencial do RDO por obra';
COMMENT ON FUNCTION atualizar_metricas_organizacao() IS 'Atualiza métricas de uso da organização';
COMMENT ON FUNCTION verificar_limite_usuarios() IS 'Verifica se organização não excedeu limite de usuários do plano';
COMMENT ON FUNCTION verificar_limite_obras() IS 'Verifica se organização não excedeu limite de obras do plano';
COMMENT ON FUNCTION get_user_role(UUID, UUID) IS 'Retorna o role do usuário em uma organização';
COMMENT ON FUNCTION user_has_permission(UUID, UUID, TEXT) IS 'Verifica se usuário tem uma permissão específica';
COMMENT ON FUNCTION get_current_user_org_id() IS 'Retorna o organizacao_id do usuário autenticado';