import { v } from 'convex/values'; import { mutation, query } from './_generated/server'; import { getCurrentUserFunction } from './auth'; import type { Id } from './_generated/dataModel'; /** * Lista todas as associações de endereços para um funcionário */ export const listarAssociacoesFuncionario = query({ args: { funcionarioId: v.id('funcionarios'), incluirInativos: v.optional(v.boolean()), }, handler: async (ctx, args) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } let associacoes; if (args.incluirInativos) { associacoes = await ctx.db .query('funcionarioEnderecosMarcacao') .withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId)) .collect(); } else { associacoes = await ctx.db .query('funcionarioEnderecosMarcacao') .withIndex('by_funcionario_ativo', (q) => q.eq('funcionarioId', args.funcionarioId).eq('ativo', true) ) .collect(); } // Buscar dados completos dos endereços const associacoesCompletas = await Promise.all( associacoes.map(async (associacao) => { const endereco = await ctx.db.get(associacao.enderecoMarcacaoId); if (!endereco) return null; // Usar raio personalizado se disponível, senão usar o raio padrão do endereço const raioMetros = associacao.raioMetrosPersonalizado ?? endereco.raioMetros; return { ...associacao, raioMetros, // Raio usado (personalizado ou padrão) endereco: { _id: endereco._id, nome: endereco.nome, descricao: endereco.descricao, latitude: endereco.latitude, longitude: endereco.longitude, endereco: endereco.endereco, cep: endereco.cep, cidade: endereco.cidade, estado: endereco.estado, pais: endereco.pais, raioMetros: endereco.raioMetros, // Raio padrão do endereço tipo: endereco.tipo, ativo: endereco.ativo, }, }; }) ); return associacoesCompletas.filter( (item): item is NonNullable => item !== null ); }, }); /** * Associa um endereço a um funcionário */ export const associarEnderecoFuncionario = mutation({ args: { funcionarioId: v.id('funcionarios'), enderecoMarcacaoId: v.id('enderecosMarcacao'), raioMetrosPersonalizado: v.optional(v.number()), dataInicio: v.optional(v.string()), // YYYY-MM-DD dataFim: v.optional(v.string()), // YYYY-MM-DD }, handler: async (ctx, args) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } // TODO: Verificar permissões (apenas TI ou admin) // Validar que o funcionário existe const funcionario = await ctx.db.get(args.funcionarioId); if (!funcionario) { throw new Error('Funcionário não encontrado'); } // Validar que o endereço existe e está ativo const endereco = await ctx.db.get(args.enderecoMarcacaoId); if (!endereco) { throw new Error('Endereço não encontrado'); } if (!endereco.ativo) { throw new Error('Endereço não está ativo'); } // Validar raio personalizado se fornecido if (args.raioMetrosPersonalizado !== undefined) { if (args.raioMetrosPersonalizado < 0 || args.raioMetrosPersonalizado > 50000) { throw new Error('Raio deve estar entre 0 e 50000 metros'); } } // Validar datas se fornecidas if (args.dataInicio && args.dataFim) { if (args.dataInicio > args.dataFim) { throw new Error('Data de início deve ser anterior à data de fim'); } } // Verificar se já existe uma associação ativa const associacaoExistente = await ctx.db .query('funcionarioEnderecosMarcacao') .withIndex('by_funcionario_ativo', (q) => q.eq('funcionarioId', args.funcionarioId).eq('ativo', true) ) .filter((q) => q.eq(q.field('enderecoMarcacaoId'), args.enderecoMarcacaoId)) .first(); if (associacaoExistente) { // Atualizar associação existente await ctx.db.patch(associacaoExistente._id, { raioMetrosPersonalizado: args.raioMetrosPersonalizado, dataInicio: args.dataInicio, dataFim: args.dataFim, ativo: true, }); return { associacaoId: associacaoExistente._id, atualizado: true }; } // Criar nova associação const associacaoId = await ctx.db.insert('funcionarioEnderecosMarcacao', { funcionarioId: args.funcionarioId, enderecoMarcacaoId: args.enderecoMarcacaoId, raioMetrosPersonalizado: args.raioMetrosPersonalizado, dataInicio: args.dataInicio, dataFim: args.dataFim, ativo: true, criadoPor: usuario._id as Id<'usuarios'>, criadoEm: Date.now(), }); return { associacaoId, atualizado: false }; }, }); /** * Atualiza uma associação de endereço com funcionário */ export const atualizarAssociacao = mutation({ args: { associacaoId: v.id('funcionarioEnderecosMarcacao'), raioMetrosPersonalizado: v.optional(v.number()), dataInicio: v.optional(v.string()), dataFim: v.optional(v.string()), ativo: v.optional(v.boolean()), }, handler: async (ctx, args) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } // TODO: Verificar permissões (apenas TI ou admin) const associacao = await ctx.db.get(args.associacaoId); if (!associacao) { throw new Error('Associação não encontrada'); } const atualizacoes: { raioMetrosPersonalizado?: number; dataInicio?: string; dataFim?: string; ativo?: boolean; } = {}; if (args.raioMetrosPersonalizado !== undefined) { if (args.raioMetrosPersonalizado < 0 || args.raioMetrosPersonalizado > 50000) { throw new Error('Raio deve estar entre 0 e 50000 metros'); } atualizacoes.raioMetrosPersonalizado = args.raioMetrosPersonalizado; } if (args.dataInicio !== undefined) { atualizacoes.dataInicio = args.dataInicio || undefined; } if (args.dataFim !== undefined) { atualizacoes.dataFim = args.dataFim || undefined; } if (args.dataInicio && args.dataFim) { if (args.dataInicio > args.dataFim) { throw new Error('Data de início deve ser anterior à data de fim'); } } if (args.ativo !== undefined) { atualizacoes.ativo = args.ativo; } await ctx.db.patch(args.associacaoId, atualizacoes); return { sucesso: true }; }, }); /** * Remove uma associação de endereço com funcionário (desativa) */ export const removerAssociacao = mutation({ args: { associacaoId: v.id('funcionarioEnderecosMarcacao'), }, handler: async (ctx, args) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } // TODO: Verificar permissões (apenas TI ou admin) const associacao = await ctx.db.get(args.associacaoId); if (!associacao) { throw new Error('Associação não encontrada'); } // Desativar ao invés de deletar await ctx.db.patch(args.associacaoId, { ativo: false, }); return { sucesso: true }; }, });