Files
sgse-app/packages/backend/convex/funcionarios.ts
killer-cf d3d7744402 Refactor backend code style and improve user profile handling
- Standardize import formatting and indentation in auth and funcionarios
  modules
- Enhance getCurrentUser query to include photo URL retrieval from
  storage
- Add getCurrent funcionario query based on authenticated user
- Update controller logic to avoid redundant local state for profile
  photos
- Upgrade dependencies: convex 1.28.2, svelte 5.43.6, vite 7.2.2, rollup
  4.53.2, tailwindcss 4.1.17, and others
2025-11-11 16:01:18 -03:00

484 lines
15 KiB
TypeScript

import { v } from 'convex/values';
import { query, mutation } from './_generated/server';
import { internal } from './_generated/api';
import { simboloTipo } from './schema';
import { getCurrentUserFunction } from './auth';
// 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'))
);
export const getAll = query({
args: {},
handler: async (ctx) => {
// Autorização: listar funcionários
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'funcionarios',
acao: 'listar'
});
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 usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) throw new Error('Usuário não encontrado');
if (!usuarioAtual.funcionarioId) throw new Error('Usuário não é funcionário');
return await ctx.db.get(usuarioAtual.funcionarioId);
}
});
export const create = mutation({
args: {
// 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,
// 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.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()),
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;
}
});