import { mutation, query } from './_generated/server'; import { v } from 'convex/values'; import { situacaoContrato } from './tables/contratos'; import { getCurrentUserFunction } from './auth'; import { internal } from './_generated/api'; export const listar = query({ args: { responsavelId: v.optional(v.id('funcionarios')), dataInicio: v.optional(v.string()), dataFim: v.optional(v.string()) }, handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'contratos', acao: 'listar' }); let q = ctx.db.query('contratos'); if (args.responsavelId) { q = q.withIndex('by_responsavel', (q) => q.eq('responsavelId', args.responsavelId!) ) as typeof q; } const contratos = await q.collect(); // Filtros em memória para datas (já que Convex não tem filtro de range nativo eficiente combinado com outros índices sem setup complexo) // Se o volume for muito grande, ideal seria criar índices específicos ou usar search. let resultado = contratos; if (args.dataInicio) { resultado = resultado.filter((c) => c.dataInicioVigencia >= args.dataInicio!); } if (args.dataFim) { resultado = resultado.filter((c) => c.dataFimVigencia <= args.dataFim!); } // Enriquecer com dados relacionados const contratosEnriquecidos = await Promise.all( resultado.map(async (c) => { const contratada = await ctx.db.get(c.contratadaId); const responsavel = await ctx.db.get(c.responsavelId); return { ...c, contratada, responsavel }; }) ); return contratosEnriquecidos; } }); export const obter = query({ args: { id: v.id('contratos') }, handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'contratos', acao: 'ver' }); const contrato = await ctx.db.get(args.id); if (!contrato) return null; const contratada = await ctx.db.get(contrato.contratadaId); const responsavel = await ctx.db.get(contrato.responsavelId); return { ...contrato, contratada, responsavel }; } }); export const criar = mutation({ args: { contratadaId: v.id('empresas'), objeto: v.string(), numeroNotaEmpenho: v.string(), responsavelId: v.id('funcionarios'), departamento: v.string(), situacao: situacaoContrato, numeroProcessoLicitatorio: v.string(), modalidade: v.string(), numeroContrato: v.string(), anoContrato: v.number(), dataInicioVigencia: v.string(), dataFimVigencia: v.string(), nomeFiscal: v.string(), valorTotal: v.string(), dataAditivoPrazo: v.optional(v.string()), diasAvisoVencimento: v.number() }, handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'contratos', acao: 'criar' }); const usuario = await getCurrentUserFunction(ctx); if (!usuario) throw new Error('Não autenticado'); const id = await ctx.db.insert('contratos', { ...args, criadoPor: usuario._id, criadoEm: Date.now() }); return id; } }); export const editar = mutation({ args: { id: v.id('contratos'), contratadaId: v.optional(v.id('empresas')), objeto: v.optional(v.string()), numeroNotaEmpenho: v.optional(v.string()), responsavelId: v.optional(v.id('funcionarios')), departamento: v.optional(v.string()), situacao: v.optional(situacaoContrato), numeroProcessoLicitatorio: v.optional(v.string()), modalidade: v.optional(v.string()), numeroContrato: v.optional(v.string()), anoContrato: v.optional(v.number()), dataInicioVigencia: v.optional(v.string()), dataFimVigencia: v.optional(v.string()), nomeFiscal: v.optional(v.string()), valorTotal: v.optional(v.string()), dataAditivoPrazo: v.optional(v.string()), diasAvisoVencimento: v.optional(v.number()) }, handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'contratos', acao: 'editar' }); const { id, ...campos } = args; await ctx.db.patch(id, { ...campos, atualizadoEm: Date.now() }); } }); export const excluir = mutation({ args: { id: v.id('contratos') }, handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'contratos', acao: 'excluir' }); await ctx.db.delete(args.id); } }); export const verificarVencimentos = query({ args: {}, handler: async (ctx) => { // Esta query pode ser usada por um componente de notificação ou cron job // Retorna contratos que estão próximos do vencimento baseados no diasAvisoVencimento const hoje = new Date(); const hojeStr = hoje.toISOString().split('T')[0]; // Buscar contratos ativos (em execução ou aguardando assinatura) const contratos = await ctx.db .query('contratos') .filter((q) => q.or( q.eq(q.field('situacao'), 'em_execucao'), q.eq(q.field('situacao'), 'aguardando_assinatura') ) ) .collect(); const proximosVencimento = contratos.filter((c) => { if (!c.dataFimVigencia) return false; const dataFim = new Date(c.dataFimVigencia); const dataAviso = new Date(dataFim); dataAviso.setDate(dataAviso.getDate() - c.diasAvisoVencimento); const dataAvisoStr = dataAviso.toISOString().split('T')[0]; // Se hoje for maior ou igual a data de aviso e menor que a data fim return hojeStr >= dataAvisoStr && hojeStr <= c.dataFimVigencia; }); return proximosVencimento; } });