import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; /** * Migração: Converte estrutura antiga de gestores individuais para times * * Esta função cria automaticamente times baseados nos gestores existentes * e adiciona os funcionários subordinados aos respectivos times. * * Execute uma vez via dashboard do Convex: * Settings > Functions > Internal > migrarParaTimes > executar */ export const executar = internalMutation({ args: {}, returns: v.object({ timesCreated: v.number(), funcionariosAtribuidos: v.number(), erros: v.array(v.string()), }), handler: async (ctx) => { const erros: string[] = []; let timesCreated = 0; let funcionariosAtribuidos = 0; try { // 1. Buscar todos os funcionários que têm gestor definido const funcionariosComGestor = await ctx.db .query("funcionarios") .filter((q) => q.neq(q.field("gestorId"), undefined)) .collect(); if (funcionariosComGestor.length === 0) { return { timesCreated: 0, funcionariosAtribuidos: 0, erros: ["Nenhum funcionário com gestor configurado encontrado"], }; } // 2. Agrupar funcionários por gestor const gestoresMap = new Map[]>(); for (const funcionario of funcionariosComGestor) { if (!funcionario.gestorId) continue; const gestorId = funcionario.gestorId; if (!gestoresMap.has(gestorId)) { gestoresMap.set(gestorId, []); } gestoresMap.get(gestorId)!.push(funcionario); } // 3. Para cada gestor, criar um time for (const [gestorId, subordinados] of gestoresMap.entries()) { try { const gestor = await ctx.db.get(gestorId); if (!gestor) { erros.push(`Gestor ${gestorId} não encontrado`); continue; } // Verificar se já existe time para este gestor const timeExistente = await ctx.db .query("times") .withIndex("by_gestor", (q) => q.eq("gestorId", gestorId)) .filter((q) => q.eq(q.field("ativo"), true)) .first(); let timeId; if (timeExistente) { timeId = timeExistente._id; } else { // Criar novo time timeId = await ctx.db.insert("times", { nome: `Equipe ${gestor.nome}`, descricao: `Time gerenciado por ${gestor.nome} (migração automática)`, gestorId: gestorId, ativo: true, cor: "#3B82F6", }); timesCreated++; } // Adicionar membros ao time for (const funcionario of subordinados) { try { // Verificar se já está em algum time const membroExistente = await ctx.db .query("timesMembros") .withIndex("by_funcionario", (q) => q.eq("funcionarioId", funcionario._id)) .filter((q) => q.eq(q.field("ativo"), true)) .first(); if (!membroExistente) { await ctx.db.insert("timesMembros", { timeId: timeId, funcionarioId: funcionario._id, dataEntrada: Date.now(), ativo: true, }); funcionariosAtribuidos++; } } catch (e) { erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`); } } } catch (e: any) { erros.push(`Erro ao processar gestor ${gestorId}: ${e.message}`); } } return { timesCreated, funcionariosAtribuidos, erros, }; } catch (e: any) { erros.push(`Erro geral na migração: ${e.message}`); return { timesCreated, funcionariosAtribuidos, erros, }; } }, }); /** * Função auxiliar para limpar times inativos antigos */ export const limparTimesInativos = internalMutation({ args: { diasInativos: v.optional(v.number()), }, returns: v.number(), handler: async (ctx, args) => { const diasLimite = args.diasInativos || 30; const dataLimite = Date.now() - (diasLimite * 24 * 60 * 60 * 1000); const timesInativos = await ctx.db .query("times") .filter((q) => q.eq(q.field("ativo"), false)) .collect(); let removidos = 0; for (const time of timesInativos) { if (time._creationTime < dataLimite) { // Remover membros inativos do time const membrosInativos = await ctx.db .query("timesMembros") .withIndex("by_time", (q) => q.eq("timeId", time._id)) .filter((q) => q.eq(q.field("ativo"), false)) .collect(); for (const membro of membrosInativos) { await ctx.db.delete(membro._id); } // Remover o time await ctx.db.delete(time._id); removidos++; } } return removidos; }, });