import { Infer, v } from 'convex/values'; import { query, mutation } from './_generated/server'; import { internal } from './_generated/api'; import { getCurrentUserFunction } from './auth'; import { simboloTipo } from './tables/funcionarios'; // Validadores para campos opcionais const sexoValidator = v.optional( v.union(v.literal('masculino'), v.literal('feminino'), v.literal('outro')) ); const estadoCivilValidator = v.optional( v.union( v.literal('solteiro'), v.literal('casado'), v.literal('divorciado'), v.literal('viuvo'), v.literal('uniao_estavel') ) ); const grauInstrucaoValidator = v.optional( v.union( v.literal('fundamental'), v.literal('medio'), v.literal('superior'), v.literal('pos_graduacao'), v.literal('mestrado'), v.literal('doutorado') ) ); const grupoSanguineoValidator = v.optional( v.union(v.literal('A'), v.literal('B'), v.literal('AB'), v.literal('O')) ); const fatorRHValidator = v.optional(v.union(v.literal('positivo'), v.literal('negativo'))); const aposentadoValidator = v.optional( v.union(v.literal('nao'), v.literal('funape_ipsep'), v.literal('inss')) ); const regimeTrabalhoValidator = v.optional( v.union( v.literal('clt'), v.literal('estatutario_pe'), v.literal('estatutario_federal'), v.literal('estatutario_municipal') ) ); export const getAll = query({ args: {}, handler: async (ctx) => { // Verificar autenticação primeiro const usuario = await getCurrentUserFunction(ctx); if (!usuario) { // Retornar array vazio quando não autenticado return []; } // Autorização: listar funcionários try { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'listar' }); } catch { // Se não tiver permissão, retornar array vazio return []; } const funcionarios = await ctx.db.query('funcionarios').collect(); // Retornar apenas os campos necessários para listagem return funcionarios.map((f) => ({ _id: f._id, nome: f.nome, matricula: f.matricula, cpf: f.cpf, rg: f.rg, nascimento: f.nascimento, email: f.email, telefone: f.telefone, endereco: f.endereco, cep: f.cep, cidade: f.cidade, uf: f.uf, simboloId: f.simboloId, simboloTipo: f.simboloTipo, admissaoData: f.admissaoData, desligamentoData: f.desligamentoData, descricaoCargo: f.descricaoCargo })); } }); export const getById = query({ args: { id: v.id('funcionarios') }, // Tipo inferido automaticamente pelo Convex handler: async (ctx, args) => { // Autorização: ver funcionário await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'ver' }); return await ctx.db.get(args.id); } }); export const getCurrent = query({ args: {}, handler: async (ctx) => { const user = await getCurrentUserFunction(ctx); if (!user) { throw new Error('Usuário não encontrado'); } if (!user.funcionarioId) { throw new Error('Funcionário não encontrado'); } const funcionario = await ctx.db.get(user.funcionarioId); if (!funcionario) { throw new Error('Funcionário não encontrado'); } return funcionario; } }); const createArgs = v.object({ // Campos obrigatórios nome: v.string(), matricula: v.optional(v.string()), simboloId: v.id('simbolos'), nascimento: v.string(), rg: v.string(), cpf: v.string(), endereco: v.string(), cep: v.string(), cidade: v.string(), uf: v.string(), telefone: v.string(), email: v.string(), admissaoData: v.optional(v.string()), desligamentoData: v.optional(v.string()), simboloTipo: simboloTipo, // Dados Pessoais Adicionais nomePai: v.optional(v.string()), nomeMae: v.optional(v.string()), naturalidade: v.optional(v.string()), naturalidadeUF: v.optional(v.string()), sexo: sexoValidator, estadoCivil: estadoCivilValidator, nacionalidade: v.optional(v.string()), // Documentos Pessoais rgOrgaoExpedidor: v.optional(v.string()), rgDataEmissao: v.optional(v.string()), carteiraProfissionalNumero: v.optional(v.string()), carteiraProfissionalSerie: v.optional(v.string()), carteiraProfissionalDataEmissao: v.optional(v.string()), reservistaNumero: v.optional(v.string()), reservistaSerie: v.optional(v.string()), tituloEleitorNumero: v.optional(v.string()), tituloEleitorZona: v.optional(v.string()), tituloEleitorSecao: v.optional(v.string()), pisNumero: v.optional(v.string()), // Formação e Saúde grauInstrucao: grauInstrucaoValidator, formacao: v.optional(v.string()), formacaoRegistro: v.optional(v.string()), grupoSanguineo: grupoSanguineoValidator, fatorRH: fatorRHValidator, // Cargo e Vínculo descricaoCargo: v.optional(v.string()), nomeacaoPortaria: v.optional(v.string()), nomeacaoData: v.optional(v.string()), nomeacaoDOE: v.optional(v.string()), pertenceOrgaoPublico: v.optional(v.boolean()), orgaoOrigem: v.optional(v.string()), aposentado: aposentadoValidator, regimeTrabalho: regimeTrabalhoValidator, // Dados Bancários contaBradescoNumero: v.optional(v.string()), contaBradescoDV: v.optional(v.string()), contaBradescoAgencia: v.optional(v.string()), // Documentos Anexos (Storage IDs) certidaoAntecedentesPF: v.optional(v.id('_storage')), certidaoAntecedentesJFPE: v.optional(v.id('_storage')), certidaoAntecedentesSDS: v.optional(v.id('_storage')), certidaoAntecedentesTJPE: v.optional(v.id('_storage')), certidaoImprobidade: v.optional(v.id('_storage')), rgFrente: v.optional(v.id('_storage')), rgVerso: v.optional(v.id('_storage')), cpfFrente: v.optional(v.id('_storage')), cpfVerso: v.optional(v.id('_storage')), situacaoCadastralCPF: v.optional(v.id('_storage')), tituloEleitorFrente: v.optional(v.id('_storage')), tituloEleitorVerso: v.optional(v.id('_storage')), comprovanteVotacao: v.optional(v.id('_storage')), carteiraProfissionalFrente: v.optional(v.id('_storage')), carteiraProfissionalVerso: v.optional(v.id('_storage')), comprovantePIS: v.optional(v.id('_storage')), certidaoRegistroCivil: v.optional(v.id('_storage')), certidaoNascimentoDependentes: v.optional(v.id('_storage')), cpfDependentes: v.optional(v.id('_storage')), reservistaDoc: v.optional(v.id('_storage')), comprovanteEscolaridade: v.optional(v.id('_storage')), comprovanteResidencia: v.optional(v.id('_storage')), comprovanteContaBradesco: v.optional(v.id('_storage')), // Declarações (Storage IDs) declaracaoAcumulacaoCargo: v.optional(v.id('_storage')), declaracaoDependentesIR: v.optional(v.id('_storage')), declaracaoIdoneidade: v.optional(v.id('_storage')), termoNepotismo: v.optional(v.id('_storage')), termoOpcaoRemuneracao: v.optional(v.id('_storage')), // Dependentes (opcional) dependentes: v.optional( v.array( v.object({ parentesco: v.union( v.literal('filho'), v.literal('filha'), v.literal('conjuge'), v.literal('outro') ), nome: v.string(), cpf: v.string(), nascimento: v.string(), documentoId: v.optional(v.id('_storage')), salarioFamilia: v.optional(v.boolean()), impostoRenda: v.optional(v.boolean()) }) ) ) }); export type CreateArgs = Infer; export const create = mutation({ args: createArgs, returns: v.id('funcionarios'), handler: async (ctx, args) => { // Autorização: criar await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'criar' }); // Unicidade: CPF const cpfExists = await ctx.db .query('funcionarios') .withIndex('by_cpf', (q) => q.eq('cpf', args.cpf)) .unique(); if (cpfExists) { throw new Error('CPF já cadastrado'); } // Unicidade: Matrícula (apenas se fornecida) if (args.matricula) { const matriculaExists = await ctx.db .query('funcionarios') .withIndex('by_matricula', (q) => q.eq('matricula', args.matricula)) .unique(); if (matriculaExists) { throw new Error( 'Já existe um funcionário com esta matrícula. Por favor, use outra ou deixe em branco.' ); } } const novoFuncionarioId = await ctx.db.insert('funcionarios', args); return novoFuncionarioId; } }); export const update = mutation({ args: { id: v.id('funcionarios'), // Campos obrigatórios nome: v.string(), matricula: v.optional(v.string()), simboloId: v.id('simbolos'), nascimento: v.string(), rg: v.string(), cpf: v.string(), endereco: v.string(), cep: v.string(), cidade: v.string(), uf: v.string(), telefone: v.string(), email: v.string(), admissaoData: v.optional(v.string()), desligamentoData: v.optional(v.string()), simboloTipo: simboloTipo, // Dados Pessoais Adicionais nomePai: v.optional(v.string()), nomeMae: v.optional(v.string()), naturalidade: v.optional(v.string()), naturalidadeUF: v.optional(v.string()), sexo: sexoValidator, estadoCivil: estadoCivilValidator, nacionalidade: v.optional(v.string()), // Documentos Pessoais rgOrgaoExpedidor: v.optional(v.string()), rgDataEmissao: v.optional(v.string()), carteiraProfissionalNumero: v.optional(v.string()), carteiraProfissionalSerie: v.optional(v.string()), carteiraProfissionalDataEmissao: v.optional(v.string()), reservistaNumero: v.optional(v.string()), reservistaSerie: v.optional(v.string()), tituloEleitorNumero: v.optional(v.string()), tituloEleitorZona: v.optional(v.string()), tituloEleitorSecao: v.optional(v.string()), pisNumero: v.optional(v.string()), // Formação e Saúde grauInstrucao: grauInstrucaoValidator, formacao: v.optional(v.string()), formacaoRegistro: v.optional(v.string()), grupoSanguineo: grupoSanguineoValidator, fatorRH: fatorRHValidator, // Cargo e Vínculo descricaoCargo: v.optional(v.string()), nomeacaoPortaria: v.optional(v.string()), nomeacaoData: v.optional(v.string()), nomeacaoDOE: v.optional(v.string()), regimeTrabalho: regimeTrabalhoValidator, pertenceOrgaoPublico: v.optional(v.boolean()), orgaoOrigem: v.optional(v.string()), aposentado: aposentadoValidator, // Dados Bancários contaBradescoNumero: v.optional(v.string()), contaBradescoDV: v.optional(v.string()), contaBradescoAgencia: v.optional(v.string()), // Documentos Anexos (Storage IDs) certidaoAntecedentesPF: v.optional(v.id('_storage')), certidaoAntecedentesJFPE: v.optional(v.id('_storage')), certidaoAntecedentesSDS: v.optional(v.id('_storage')), certidaoAntecedentesTJPE: v.optional(v.id('_storage')), certidaoImprobidade: v.optional(v.id('_storage')), rgFrente: v.optional(v.id('_storage')), rgVerso: v.optional(v.id('_storage')), cpfFrente: v.optional(v.id('_storage')), cpfVerso: v.optional(v.id('_storage')), situacaoCadastralCPF: v.optional(v.id('_storage')), tituloEleitorFrente: v.optional(v.id('_storage')), tituloEleitorVerso: v.optional(v.id('_storage')), comprovanteVotacao: v.optional(v.id('_storage')), carteiraProfissionalFrente: v.optional(v.id('_storage')), carteiraProfissionalVerso: v.optional(v.id('_storage')), comprovantePIS: v.optional(v.id('_storage')), certidaoRegistroCivil: v.optional(v.id('_storage')), certidaoNascimentoDependentes: v.optional(v.id('_storage')), cpfDependentes: v.optional(v.id('_storage')), reservistaDoc: v.optional(v.id('_storage')), comprovanteEscolaridade: v.optional(v.id('_storage')), comprovanteResidencia: v.optional(v.id('_storage')), comprovanteContaBradesco: v.optional(v.id('_storage')), // Declarações (Storage IDs) declaracaoAcumulacaoCargo: v.optional(v.id('_storage')), declaracaoDependentesIR: v.optional(v.id('_storage')), declaracaoIdoneidade: v.optional(v.id('_storage')), termoNepotismo: v.optional(v.id('_storage')), termoOpcaoRemuneracao: v.optional(v.id('_storage')), // Dependentes (opcional) dependentes: v.optional( v.array( v.object({ parentesco: v.union( v.literal('filho'), v.literal('filha'), v.literal('conjuge'), v.literal('outro') ), nome: v.string(), cpf: v.string(), nascimento: v.string(), documentoId: v.optional(v.id('_storage')), salarioFamilia: v.optional(v.boolean()), impostoRenda: v.optional(v.boolean()) }) ) ) }, returns: v.null(), handler: async (ctx, args) => { // Autorização: editar await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'editar' }); // Unicidade: CPF (excluindo o próprio registro) const cpfExists = await ctx.db .query('funcionarios') .withIndex('by_cpf', (q) => q.eq('cpf', args.cpf)) .unique(); if (cpfExists && cpfExists._id !== args.id) { throw new Error('CPF já cadastrado'); } // Unicidade: Matrícula (apenas se fornecida, excluindo o próprio registro) if (args.matricula) { const matriculaExists = await ctx.db .query('funcionarios') .withIndex('by_matricula', (q) => q.eq('matricula', args.matricula)) .unique(); if (matriculaExists && matriculaExists._id !== args.id) { throw new Error( 'Já existe um funcionário com esta matrícula. Por favor, use outra ou deixe em branco.' ); } } const usuarioExists = await ctx.db .query('usuarios') .withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', args.id)) .unique(); if (usuarioExists && usuarioExists.email !== args.email) { await ctx.db.patch(usuarioExists._id, { email: args.email }); } const { id, ...updateData } = args; await ctx.db.patch(id, updateData); return null; } }); export const remove = mutation({ args: { id: v.id('funcionarios') }, returns: v.null(), handler: async (ctx, args) => { // Autorização: excluir await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'excluir' }); // TODO: Talvez queiramos também remover os arquivos do storage await ctx.db.delete(args.id); return null; } }); // Query para obter ficha completa para impressão export const getFichaCompleta = query({ args: { id: v.id('funcionarios') }, // Tipo inferido automaticamente pelo Convex handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'funcionarios', acao: 'ver' }); const funcionario = await ctx.db.get(args.id); if (!funcionario) { return null; } // Buscar informações do símbolo const simbolo = await ctx.db.get(funcionario.simboloId); // Buscar cursos do funcionário const cursos = await ctx.db .query('cursos') .withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.id)) .collect(); // Buscar URLs dos certificados const cursosComUrls = await Promise.all( cursos.map(async (curso) => { let certificadoUrl = null; if (curso.certificadoId) { certificadoUrl = await ctx.storage.getUrl(curso.certificadoId); } return { ...curso, certificadoUrl }; }) ); return { ...funcionario, simbolo: simbolo ? { nome: simbolo.nome, descricao: simbolo.descricao, tipo: simbolo.tipo, vencValor: simbolo.vencValor, repValor: simbolo.repValor, valor: simbolo.valor } : null, cursos: cursosComUrls }; } }); // Mutation: Configurar gestor (apenas para TI_MASTER) export const configurarGestor = mutation({ args: { funcionarioId: v.id('funcionarios'), gestorId: v.optional(v.id('usuarios')) }, returns: v.null(), handler: async (ctx, args) => { await ctx.db.patch(args.funcionarioId, { gestorId: args.gestorId }); return null; } });