feat: integrate jsPDF and jsPDF-autotable for document generation; enhance employee management with print functionality and improved data handling in employee forms

This commit is contained in:
2025-10-27 23:36:04 -03:00
parent 3a1956f83b
commit 81e6eb4a42
26 changed files with 6405 additions and 935 deletions

View File

@@ -16,6 +16,7 @@ import type * as betterAuth__generated_server from "../betterAuth/_generated/ser
import type * as betterAuth_adapter from "../betterAuth/adapter.js";
import type * as betterAuth_auth from "../betterAuth/auth.js";
import type * as dashboard from "../dashboard.js";
import type * as documentos from "../documentos.js";
import type * as funcionarios from "../funcionarios.js";
import type * as healthCheck from "../healthCheck.js";
import type * as http from "../http.js";
@@ -52,6 +53,7 @@ declare const fullApi: ApiFromModules<{
"betterAuth/adapter": typeof betterAuth_adapter;
"betterAuth/auth": typeof betterAuth_auth;
dashboard: typeof dashboard;
documentos: typeof documentos;
funcionarios: typeof funcionarios;
healthCheck: typeof healthCheck;
http: typeof http;

View File

@@ -0,0 +1,138 @@
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
// Mutation para fazer upload de arquivo e obter o storage ID
export const generateUploadUrl = mutation({
args: {},
returns: v.string(),
handler: async (ctx) => {
return await ctx.storage.generateUploadUrl();
},
});
// Mutation para atualizar um campo de documento do funcionário
export const updateDocumento = mutation({
args: {
funcionarioId: v.id("funcionarios"),
campo: v.string(),
storageId: v.union(v.id("_storage"), v.null()),
},
returns: v.null(),
handler: async (ctx, args) => {
const funcionario = await ctx.db.get(args.funcionarioId);
if (!funcionario) {
throw new Error("Funcionário não encontrado");
}
// Atualizar o campo específico do documento
await ctx.db.patch(args.funcionarioId, {
[args.campo]: args.storageId,
} as any);
return null;
},
});
// Query para obter URLs de todos os documentos de um funcionário
export const getDocumentosUrls = query({
args: { funcionarioId: v.id("funcionarios") },
returns: v.object({
certidaoAntecedentesPF: v.union(v.string(), v.null()),
certidaoAntecedentesJFPE: v.union(v.string(), v.null()),
certidaoAntecedentesSDS: v.union(v.string(), v.null()),
certidaoAntecedentesTJPE: v.union(v.string(), v.null()),
certidaoImprobidade: v.union(v.string(), v.null()),
rgFrente: v.union(v.string(), v.null()),
rgVerso: v.union(v.string(), v.null()),
cpfFrente: v.union(v.string(), v.null()),
cpfVerso: v.union(v.string(), v.null()),
situacaoCadastralCPF: v.union(v.string(), v.null()),
tituloEleitorFrente: v.union(v.string(), v.null()),
tituloEleitorVerso: v.union(v.string(), v.null()),
comprovanteVotacao: v.union(v.string(), v.null()),
carteiraProfissionalFrente: v.union(v.string(), v.null()),
carteiraProfissionalVerso: v.union(v.string(), v.null()),
comprovantePIS: v.union(v.string(), v.null()),
certidaoRegistroCivil: v.union(v.string(), v.null()),
certidaoNascimentoDependentes: v.union(v.string(), v.null()),
cpfDependentes: v.union(v.string(), v.null()),
reservistaDoc: v.union(v.string(), v.null()),
comprovanteEscolaridade: v.union(v.string(), v.null()),
comprovanteResidencia: v.union(v.string(), v.null()),
comprovanteContaBradesco: v.union(v.string(), v.null()),
declaracaoAcumulacaoCargo: v.union(v.string(), v.null()),
declaracaoDependentesIR: v.union(v.string(), v.null()),
declaracaoIdoneidade: v.union(v.string(), v.null()),
termoNepotismo: v.union(v.string(), v.null()),
termoOpcaoRemuneracao: v.union(v.string(), v.null()),
}),
handler: async (ctx, args) => {
const funcionario = await ctx.db.get(args.funcionarioId);
if (!funcionario) {
throw new Error("Funcionário não encontrado");
}
// Gerar URLs para todos os documentos
const urls: Record<string, string | null> = {};
const campos = [
"certidaoAntecedentesPF",
"certidaoAntecedentesJFPE",
"certidaoAntecedentesSDS",
"certidaoAntecedentesTJPE",
"certidaoImprobidade",
"rgFrente",
"rgVerso",
"cpfFrente",
"cpfVerso",
"situacaoCadastralCPF",
"tituloEleitorFrente",
"tituloEleitorVerso",
"comprovanteVotacao",
"carteiraProfissionalFrente",
"carteiraProfissionalVerso",
"comprovantePIS",
"certidaoRegistroCivil",
"certidaoNascimentoDependentes",
"cpfDependentes",
"reservistaDoc",
"comprovanteEscolaridade",
"comprovanteResidencia",
"comprovanteContaBradesco",
"declaracaoAcumulacaoCargo",
"declaracaoDependentesIR",
"declaracaoIdoneidade",
"termoNepotismo",
"termoOpcaoRemuneracao",
];
for (const campo of campos) {
const storageId = (funcionario as any)[campo];
if (storageId) {
urls[campo] = await ctx.storage.getUrl(storageId);
} else {
urls[campo] = null;
}
}
return urls as any;
},
});
// Query para obter metadados de um documento
export const getDocumentoMetadata = query({
args: { storageId: v.id("_storage") },
returns: v.union(
v.object({
_id: v.id("_storage"),
_creationTime: v.number(),
contentType: v.optional(v.string()),
sha256: v.string(),
size: v.number(),
}),
v.null()
),
handler: async (ctx, args) => {
return await ctx.db.system.get(args.storageId);
},
});

View File

@@ -2,58 +2,43 @@ import { v } from "convex/values";
import { query, mutation } from "./_generated/server";
import { simboloTipo } from "./schema";
// 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: {},
returns: v.array(
v.object({
_id: v.id("funcionarios"),
_creationTime: v.number(),
nome: v.string(),
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(),
matricula: v.string(),
admissaoData: v.optional(v.string()),
desligamentoData: v.optional(v.string()),
simboloId: v.id("simbolos"),
simboloTipo: simboloTipo,
})
),
handler: async (ctx) => {
return await ctx.db.query("funcionarios").collect();
const funcionarios = await ctx.db.query("funcionarios").collect();
// Retornar apenas os campos necessários para listagem
return funcionarios.map((f: any) => ({
_id: f._id,
_creationTime: f._creationTime,
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,
}));
},
});
export const getById = query({
args: { id: v.id("funcionarios") },
returns: v.union(
v.object({
_id: v.id("funcionarios"),
_creationTime: v.number(),
nome: v.string(),
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(),
matricula: v.string(),
admissaoData: v.optional(v.string()),
desligamentoData: v.optional(v.string()),
simboloId: v.id("simbolos"),
simboloTipo: simboloTipo,
}),
v.null()
),
handler: async (ctx, args) => {
return await ctx.db.get(args.id);
},
@@ -61,6 +46,7 @@ export const getById = query({
export const create = mutation({
args: {
// Campos obrigatórios
nome: v.string(),
matricula: v.string(),
simboloId: v.id("simbolos"),
@@ -76,6 +62,81 @@ export const create = mutation({
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")),
},
returns: v.id("funcionarios"),
handler: async (ctx, args) => {
@@ -97,23 +158,7 @@ export const create = mutation({
throw new Error("Matrícula já cadastrada");
}
const novoFuncionarioId = await ctx.db.insert("funcionarios", {
nome: args.nome,
nascimento: args.nascimento,
rg: args.rg,
cpf: args.cpf,
endereco: args.endereco,
cep: args.cep,
cidade: args.cidade,
uf: args.uf,
telefone: args.telefone,
email: args.email,
matricula: args.matricula,
admissaoData: args.admissaoData,
desligamentoData: args.desligamentoData,
simboloId: args.simboloId,
simboloTipo: args.simboloTipo,
});
const novoFuncionarioId = await ctx.db.insert("funcionarios", args as any);
return novoFuncionarioId;
},
});
@@ -121,6 +166,7 @@ export const create = mutation({
export const update = mutation({
args: {
id: v.id("funcionarios"),
// Campos obrigatórios
nome: v.string(),
matricula: v.string(),
simboloId: v.id("simbolos"),
@@ -136,6 +182,81 @@ export const update = mutation({
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")),
},
returns: v.null(),
handler: async (ctx, args) => {
@@ -157,23 +278,8 @@ export const update = mutation({
throw new Error("Matrícula já cadastrada");
}
await ctx.db.patch(args.id, {
nome: args.nome,
nascimento: args.nascimento,
rg: args.rg,
cpf: args.cpf,
endereco: args.endereco,
cep: args.cep,
cidade: args.cidade,
uf: args.uf,
telefone: args.telefone,
email: args.email,
matricula: args.matricula,
admissaoData: args.admissaoData,
desligamentoData: args.desligamentoData,
simboloId: args.simboloId,
simboloTipo: args.simboloTipo,
});
const { id, ...updateData } = args;
await ctx.db.patch(id, updateData as any);
return null;
},
});
@@ -182,7 +288,31 @@ export const remove = mutation({
args: { id: v.id("funcionarios") },
returns: v.null(),
handler: async (ctx, args) => {
// 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") },
handler: async (ctx, args) => {
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);
return {
...funcionario,
simbolo: simbolo ? {
nome: simbolo.nome,
descricao: simbolo.descricao,
valor: simbolo.valor,
} : null,
};
},
});

View File

@@ -16,6 +16,7 @@ export default defineSchema({
completed: v.boolean(),
}),
funcionarios: defineTable({
// Campos obrigatórios existentes
nome: v.string(),
nascimento: v.string(),
rg: v.string(),
@@ -31,6 +32,110 @@ export default defineSchema({
desligamentoData: v.optional(v.string()),
simboloId: v.id("simbolos"),
simboloTipo: simboloTipo,
// Dados Pessoais Adicionais (opcionais)
nomePai: v.optional(v.string()),
nomeMae: v.optional(v.string()),
naturalidade: v.optional(v.string()),
naturalidadeUF: v.optional(v.string()),
sexo: v.optional(v.union(
v.literal("masculino"),
v.literal("feminino"),
v.literal("outro")
)),
estadoCivil: v.optional(v.union(
v.literal("solteiro"),
v.literal("casado"),
v.literal("divorciado"),
v.literal("viuvo"),
v.literal("uniao_estavel")
)),
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: v.optional(v.union(
v.literal("fundamental"),
v.literal("medio"),
v.literal("superior"),
v.literal("pos_graduacao"),
v.literal("mestrado"),
v.literal("doutorado")
)),
formacao: v.optional(v.string()),
formacaoRegistro: v.optional(v.string()),
grupoSanguineo: v.optional(v.union(
v.literal("A"),
v.literal("B"),
v.literal("AB"),
v.literal("O")
)),
fatorRH: v.optional(v.union(
v.literal("positivo"),
v.literal("negativo")
)),
// 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: v.optional(v.union(
v.literal("nao"),
v.literal("funape_ipsep"),
v.literal("inss")
)),
// 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")),
})
.index("by_matricula", ["matricula"])
.index("by_nome", ["nome"])