- Updated the document URL fetching logic in the +page.svelte file to use a new query method, enhancing the retrieval process. - Added a new query in atestadosLicencas.ts to obtain stored document URLs, improving authentication checks and error handling. - Streamlined the user experience by ensuring URLs are fetched correctly and opened in a new tab when available.
1065 lines
30 KiB
TypeScript
1065 lines
30 KiB
TypeScript
import { v } from "convex/values";
|
|
import { mutation, query } from "./_generated/server";
|
|
import { Id, Doc } from "./_generated/dataModel";
|
|
import type { QueryCtx, MutationCtx } from "./_generated/server";
|
|
import { registrarAtividade } from "./logsAtividades";
|
|
|
|
// ========== HELPERS ==========
|
|
|
|
/**
|
|
* Helper function para obter usuário autenticado
|
|
*/
|
|
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
|
const identity = await ctx.auth.getUserIdentity();
|
|
let usuarioAtual = null;
|
|
|
|
if (identity && identity.email) {
|
|
usuarioAtual = await ctx.db
|
|
.query("usuarios")
|
|
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
|
.first();
|
|
}
|
|
|
|
if (!usuarioAtual) {
|
|
const sessaoAtiva = await ctx.db
|
|
.query("sessoes")
|
|
.filter((q) => q.eq(q.field("ativo"), true))
|
|
.order("desc")
|
|
.first();
|
|
|
|
if (sessaoAtiva) {
|
|
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
|
|
}
|
|
}
|
|
|
|
return usuarioAtual;
|
|
}
|
|
|
|
/**
|
|
* Helper para calcular dias entre duas datas
|
|
*/
|
|
function calcularDias(dataInicio: string, dataFim: string): number {
|
|
const inicio = new Date(dataInicio);
|
|
const fim = new Date(dataFim);
|
|
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
|
return diffDays;
|
|
}
|
|
|
|
// ========== QUERIES ==========
|
|
|
|
/**
|
|
* Listar todos os atestados e licenças com detalhes do funcionário
|
|
*/
|
|
export const listarTodos = query({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
try {
|
|
const [atestados, licencas] = await Promise.all([
|
|
ctx.db.query("atestados").collect(),
|
|
ctx.db.query("licencas").collect(),
|
|
]);
|
|
|
|
const atestadosComDetalhes = await Promise.all(
|
|
atestados.map(async (a) => {
|
|
try {
|
|
const funcionario = await ctx.db.get(a.funcionarioId);
|
|
const criadoPor = await ctx.db.get(a.criadoPor);
|
|
return {
|
|
...a,
|
|
funcionario,
|
|
criadoPorNome: criadoPor?.nome || "Sistema",
|
|
dias: calcularDias(a.dataInicio, a.dataFim),
|
|
status: new Date(a.dataFim) >= new Date() ? "ativo" : "finalizado",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao buscar detalhes do atestado:", error);
|
|
return {
|
|
...a,
|
|
funcionario: null,
|
|
criadoPorNome: "Sistema",
|
|
dias: calcularDias(a.dataInicio, a.dataFim),
|
|
status: new Date(a.dataFim) >= new Date() ? "ativo" : "finalizado",
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
const licencasComDetalhes = await Promise.all(
|
|
licencas.map(async (l) => {
|
|
try {
|
|
const funcionario = await ctx.db.get(l.funcionarioId);
|
|
const criadoPor = await ctx.db.get(l.criadoPor);
|
|
const licencaOriginal = l.licencaOriginalId
|
|
? await ctx.db.get(l.licencaOriginalId)
|
|
: null;
|
|
return {
|
|
...l,
|
|
funcionario,
|
|
criadoPorNome: criadoPor?.nome || "Sistema",
|
|
licencaOriginal,
|
|
dias: calcularDias(l.dataInicio, l.dataFim),
|
|
status: new Date(l.dataFim) >= new Date() ? "ativo" : "finalizado",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao buscar detalhes da licença:", error);
|
|
return {
|
|
...l,
|
|
funcionario: null,
|
|
criadoPorNome: "Sistema",
|
|
licencaOriginal: null,
|
|
dias: calcularDias(l.dataInicio, l.dataFim),
|
|
status: new Date(l.dataFim) >= new Date() ? "ativo" : "finalizado",
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
return {
|
|
atestados: atestadosComDetalhes.sort(
|
|
(a, b) => b._creationTime - a._creationTime
|
|
),
|
|
licencas: licencasComDetalhes.sort(
|
|
(a, b) => b._creationTime - a._creationTime
|
|
),
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro em listarTodos:", error);
|
|
return {
|
|
atestados: [],
|
|
licencas: [],
|
|
};
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Listar por funcionário específico
|
|
*/
|
|
export const listarPorFuncionario = query({
|
|
args: { funcionarioId: v.id("funcionarios") },
|
|
handler: async (ctx, args) => {
|
|
const [atestados, licencas] = await Promise.all([
|
|
ctx.db
|
|
.query("atestados")
|
|
.withIndex("by_funcionario", (q) =>
|
|
q.eq("funcionarioId", args.funcionarioId)
|
|
)
|
|
.collect(),
|
|
ctx.db
|
|
.query("licencas")
|
|
.withIndex("by_funcionario", (q) =>
|
|
q.eq("funcionarioId", args.funcionarioId)
|
|
)
|
|
.collect(),
|
|
]);
|
|
|
|
return {
|
|
atestados: atestados.sort((a, b) => b._creationTime - a._creationTime),
|
|
licencas: licencas.sort((a, b) => b._creationTime - a._creationTime),
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Listar por período
|
|
*/
|
|
export const listarPorPeriodo = query({
|
|
args: {
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const dataInicioObj = new Date(args.dataInicio);
|
|
const dataFimObj = new Date(args.dataFim);
|
|
|
|
const atestados = await ctx.db.query("atestados").collect();
|
|
const licencas = await ctx.db.query("licencas").collect();
|
|
|
|
const atestadosFiltrados = atestados.filter((a) => {
|
|
const inicio = new Date(a.dataInicio);
|
|
const fim = new Date(a.dataFim);
|
|
return (
|
|
(inicio >= dataInicioObj && inicio <= dataFimObj) ||
|
|
(fim >= dataInicioObj && fim <= dataFimObj) ||
|
|
(inicio <= dataInicioObj && fim >= dataFimObj)
|
|
);
|
|
});
|
|
|
|
const licencasFiltradas = licencas.filter((l) => {
|
|
const inicio = new Date(l.dataInicio);
|
|
const fim = new Date(l.dataFim);
|
|
return (
|
|
(inicio >= dataInicioObj && inicio <= dataFimObj) ||
|
|
(fim >= dataInicioObj && fim <= dataFimObj) ||
|
|
(inicio <= dataInicioObj && fim >= dataFimObj)
|
|
);
|
|
});
|
|
|
|
return {
|
|
atestados: atestadosFiltrados,
|
|
licencas: licencasFiltradas,
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Obter dados para gráficos
|
|
*/
|
|
export const obterDadosGraficos = query({
|
|
args: {
|
|
periodo: v.optional(v.number()), // dias (padrão: 30)
|
|
},
|
|
handler: async (ctx, args) => {
|
|
try {
|
|
const dias = args.periodo || 30;
|
|
const dataLimite = Date.now() - dias * 24 * 60 * 60 * 1000;
|
|
|
|
const [atestados, licencas] = await Promise.all([
|
|
ctx.db.query("atestados").collect(),
|
|
ctx.db.query("licencas").collect(),
|
|
]);
|
|
|
|
// Filtrar por período
|
|
const atestadosFiltrados = atestados.filter(
|
|
(a) => new Date(a.criadoEm) >= new Date(dataLimite)
|
|
);
|
|
const licencasFiltradas = licencas.filter(
|
|
(l) => new Date(l.criadoEm) >= new Date(dataLimite)
|
|
);
|
|
|
|
// 1. Total de dias por tipo (para gráfico de barras)
|
|
const totalDiasPorTipo: Record<string, number> = {
|
|
atestado_medico: 0,
|
|
declaracao_comparecimento: 0,
|
|
maternidade: 0,
|
|
paternidade: 0,
|
|
ferias: 0,
|
|
};
|
|
|
|
atestadosFiltrados.forEach((a) => {
|
|
const dias = calcularDias(a.dataInicio, a.dataFim);
|
|
if (a.tipo === "atestado_medico") {
|
|
totalDiasPorTipo.atestado_medico += dias;
|
|
} else {
|
|
totalDiasPorTipo.declaracao_comparecimento += dias;
|
|
}
|
|
});
|
|
|
|
licencasFiltradas.forEach((l) => {
|
|
const dias = calcularDias(l.dataInicio, l.dataFim);
|
|
if (l.tipo === "maternidade") {
|
|
totalDiasPorTipo.maternidade += dias;
|
|
} else {
|
|
totalDiasPorTipo.paternidade += dias;
|
|
}
|
|
});
|
|
|
|
// Buscar férias do período
|
|
try {
|
|
const solicitacoesFerias = await ctx.db
|
|
.query("solicitacoesFerias")
|
|
.filter((q) =>
|
|
q.or(
|
|
q.eq(q.field("status"), "aprovado"),
|
|
q.eq(q.field("status"), "data_ajustada_aprovada")
|
|
)
|
|
)
|
|
.collect();
|
|
|
|
solicitacoesFerias.forEach((s) => {
|
|
if (s.periodos && Array.isArray(s.periodos)) {
|
|
s.periodos.forEach((p: { dataInicio: string; dataFim: string }) => {
|
|
const dias = calcularDias(p.dataInicio, p.dataFim);
|
|
totalDiasPorTipo.ferias += dias;
|
|
});
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Erro ao buscar férias para gráfico:", error);
|
|
}
|
|
|
|
// 2. Tendências mensais (últimos 6 meses)
|
|
const meses: Record<
|
|
string,
|
|
{
|
|
atestado_medico: number;
|
|
declaracao_comparecimento: number;
|
|
maternidade: number;
|
|
paternidade: number;
|
|
ferias: number;
|
|
}
|
|
> = {};
|
|
|
|
const hoje = new Date();
|
|
for (let i = 5; i >= 0; i--) {
|
|
const mesData = new Date(hoje.getFullYear(), hoje.getMonth() - i, 1);
|
|
const mesKey = mesData.toLocaleDateString("pt-BR", {
|
|
month: "short",
|
|
year: "numeric",
|
|
});
|
|
meses[mesKey] = {
|
|
atestado_medico: 0,
|
|
declaracao_comparecimento: 0,
|
|
maternidade: 0,
|
|
paternidade: 0,
|
|
ferias: 0,
|
|
};
|
|
}
|
|
|
|
// Processar atestados para tendências mensais (usar todos, não apenas filtrados)
|
|
atestados.forEach((item) => {
|
|
try {
|
|
const mesData = new Date(item.criadoEm);
|
|
const mesKey = mesData.toLocaleDateString("pt-BR", {
|
|
month: "short",
|
|
year: "numeric",
|
|
});
|
|
|
|
if (meses[mesKey]) {
|
|
const dias = calcularDias(item.dataInicio, item.dataFim);
|
|
if (item.tipo === "atestado_medico") {
|
|
meses[mesKey].atestado_medico += dias;
|
|
} else if (item.tipo === "declaracao_comparecimento") {
|
|
meses[mesKey].declaracao_comparecimento += dias;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao processar atestado para tendências:", error);
|
|
}
|
|
});
|
|
|
|
// Processar licenças para tendências mensais (usar todas, não apenas filtradas)
|
|
licencas.forEach((item) => {
|
|
try {
|
|
const mesData = new Date(item.criadoEm);
|
|
const mesKey = mesData.toLocaleDateString("pt-BR", {
|
|
month: "short",
|
|
year: "numeric",
|
|
});
|
|
|
|
if (meses[mesKey]) {
|
|
const dias = calcularDias(item.dataInicio, item.dataFim);
|
|
if (item.tipo === "maternidade") {
|
|
meses[mesKey].maternidade += dias;
|
|
} else if (item.tipo === "paternidade") {
|
|
meses[mesKey].paternidade += dias;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao processar licença para tendências:", error);
|
|
}
|
|
});
|
|
|
|
// 3. Funcionários atualmente afastados
|
|
const hojeStr = new Date().toISOString().split("T")[0];
|
|
const funcionariosAfastados: Array<{
|
|
funcionarioId: Id<"funcionarios">;
|
|
funcionarioNome: string;
|
|
tipo: string;
|
|
dataInicio: string;
|
|
dataFim: string;
|
|
}> = [];
|
|
|
|
// Processar atestados (verificar funcionários atualmente afastados)
|
|
atestadosFiltrados.forEach((item) => {
|
|
try {
|
|
const inicio = new Date(item.dataInicio);
|
|
const fim = new Date(item.dataFim);
|
|
const hoje = new Date(hojeStr);
|
|
|
|
if (hoje >= inicio && hoje <= fim) {
|
|
funcionariosAfastados.push({
|
|
funcionarioId: item.funcionarioId,
|
|
funcionarioNome: "Carregando...",
|
|
tipo: item.tipo,
|
|
dataInicio: item.dataInicio,
|
|
dataFim: item.dataFim,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao processar atestado:", error);
|
|
}
|
|
});
|
|
|
|
// Processar licenças (verificar funcionários atualmente afastados)
|
|
licencasFiltradas.forEach((item) => {
|
|
try {
|
|
const inicio = new Date(item.dataInicio);
|
|
const fim = new Date(item.dataFim);
|
|
const hoje = new Date(hojeStr);
|
|
|
|
if (hoje >= inicio && hoje <= fim) {
|
|
funcionariosAfastados.push({
|
|
funcionarioId: item.funcionarioId,
|
|
funcionarioNome: "Carregando...",
|
|
tipo: item.tipo,
|
|
dataInicio: item.dataInicio,
|
|
dataFim: item.dataFim,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao processar licença:", error);
|
|
}
|
|
});
|
|
|
|
// Buscar nomes dos funcionários
|
|
const funcionariosAfastadosComNomes = await Promise.all(
|
|
funcionariosAfastados.map(async (item) => {
|
|
try {
|
|
const funcionario = await ctx.db.get(item.funcionarioId);
|
|
return {
|
|
...item,
|
|
funcionarioNome: funcionario?.nome || "Desconhecido",
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro ao buscar funcionário:", error);
|
|
return {
|
|
...item,
|
|
funcionarioNome: "Desconhecido",
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
return {
|
|
totalDiasPorTipo: [
|
|
{ tipo: "Atestado Médico", dias: totalDiasPorTipo.atestado_medico },
|
|
{
|
|
tipo: "Declaração",
|
|
dias: totalDiasPorTipo.declaracao_comparecimento,
|
|
},
|
|
{ tipo: "Licença Maternidade", dias: totalDiasPorTipo.maternidade },
|
|
{ tipo: "Licença Paternidade", dias: totalDiasPorTipo.paternidade },
|
|
{ tipo: "Férias", dias: totalDiasPorTipo.ferias },
|
|
],
|
|
tendenciasMensais: Object.entries(meses).map(([mes, dados]) => ({
|
|
mes,
|
|
...dados,
|
|
})),
|
|
funcionariosAfastados: funcionariosAfastadosComNomes,
|
|
};
|
|
} catch (error) {
|
|
console.error("Erro em obterDadosGraficos:", error);
|
|
// Retornar dados vazios em caso de erro para não quebrar a página
|
|
return {
|
|
totalDiasPorTipo: [
|
|
{ tipo: "Atestado Médico", dias: 0 },
|
|
{ tipo: "Declaração", dias: 0 },
|
|
{ tipo: "Licença Maternidade", dias: 0 },
|
|
{ tipo: "Licença Paternidade", dias: 0 },
|
|
{ tipo: "Férias", dias: 0 },
|
|
],
|
|
tendenciasMensais: [],
|
|
funcionariosAfastados: [],
|
|
};
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Obter estatísticas para dashboard
|
|
*/
|
|
export const obterEstatisticas = query({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
const hoje = new Date();
|
|
hoje.setHours(0, 0, 0, 0);
|
|
const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1);
|
|
const fimMes = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0);
|
|
|
|
const [atestados, licencas] = await Promise.all([
|
|
ctx.db.query("atestados").collect(),
|
|
ctx.db.query("licencas").collect(),
|
|
]);
|
|
|
|
// Atestados ativos
|
|
const atestadosAtivos = atestados.filter(
|
|
(a) => new Date(a.dataFim) >= hoje
|
|
);
|
|
|
|
// Licenças ativas
|
|
const licencasAtivas = licencas.filter(
|
|
(l) => new Date(l.dataFim) >= hoje
|
|
);
|
|
|
|
// Funcionários afastados hoje
|
|
const funcionariosAfastadosHoje = new Set<string>();
|
|
[...atestados, ...licencas].forEach((item) => {
|
|
const inicio = new Date(item.dataInicio);
|
|
const fim = new Date(item.dataFim);
|
|
if (hoje >= inicio && hoje <= fim) {
|
|
funcionariosAfastadosHoje.add(item.funcionarioId);
|
|
}
|
|
});
|
|
|
|
// Total de dias no mês
|
|
let totalDiasMes = 0;
|
|
[...atestados, ...licencas].forEach((item) => {
|
|
const inicio = new Date(item.dataInicio);
|
|
const fim = new Date(item.dataFim);
|
|
if (
|
|
(inicio >= inicioMes && inicio <= fimMes) ||
|
|
(fim >= inicioMes && fim <= fimMes) ||
|
|
(inicio <= inicioMes && fim >= fimMes)
|
|
) {
|
|
const dias = calcularDias(item.dataInicio, item.dataFim);
|
|
totalDiasMes += dias;
|
|
}
|
|
});
|
|
|
|
return {
|
|
totalAtestadosAtivos: atestadosAtivos.length,
|
|
totalLicencasAtivas: licencasAtivas.length,
|
|
funcionariosAfastadosHoje: funcionariosAfastadosHoje.size,
|
|
totalDiasAfastamentoMes: totalDiasMes,
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Obter eventos formatados para calendário
|
|
*/
|
|
export const obterEventosCalendario = query({
|
|
args: {
|
|
dataInicio: v.optional(v.string()),
|
|
dataFim: v.optional(v.string()),
|
|
tipoFiltro: v.optional(
|
|
v.union(
|
|
v.literal("todos"),
|
|
v.literal("atestado_medico"),
|
|
v.literal("declaracao_comparecimento"),
|
|
v.literal("maternidade"),
|
|
v.literal("paternidade"),
|
|
v.literal("ferias")
|
|
)
|
|
),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const eventos: Array<{
|
|
id: string;
|
|
title: string;
|
|
start: string;
|
|
end: string;
|
|
color: string;
|
|
tipo: string;
|
|
funcionarioNome: string;
|
|
funcionarioId: string;
|
|
}> = [];
|
|
|
|
try {
|
|
// Buscar atestados
|
|
if (
|
|
!args.tipoFiltro ||
|
|
args.tipoFiltro === "todos" ||
|
|
args.tipoFiltro === "atestado_medico" ||
|
|
args.tipoFiltro === "declaracao_comparecimento"
|
|
) {
|
|
try {
|
|
const atestados = await ctx.db.query("atestados").collect();
|
|
for (const atestado of atestados) {
|
|
try {
|
|
if (
|
|
args.tipoFiltro &&
|
|
args.tipoFiltro !== "todos" &&
|
|
atestado.tipo !== args.tipoFiltro
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const funcionario = await ctx.db.get(atestado.funcionarioId);
|
|
if (!funcionario) continue;
|
|
|
|
if (!atestado.dataInicio || !atestado.dataFim) continue;
|
|
|
|
const cor =
|
|
atestado.tipo === "atestado_medico"
|
|
? "#ef4444"
|
|
: "#f97316"; // vermelho ou laranja
|
|
|
|
eventos.push({
|
|
id: `atestado-${atestado._id}`,
|
|
title: `${funcionario.nome} - ${
|
|
atestado.tipo === "atestado_medico"
|
|
? "Atestado Médico"
|
|
: "Declaração"
|
|
}`,
|
|
start: atestado.dataInicio,
|
|
end: atestado.dataFim,
|
|
color: cor,
|
|
tipo: atestado.tipo,
|
|
funcionarioNome: funcionario.nome,
|
|
funcionarioId: funcionario._id,
|
|
});
|
|
} catch (error) {
|
|
console.error(`Erro ao processar atestado ${atestado._id}:`, error);
|
|
continue;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar atestados:", error);
|
|
}
|
|
}
|
|
|
|
// Buscar licenças
|
|
if (
|
|
!args.tipoFiltro ||
|
|
args.tipoFiltro === "todos" ||
|
|
args.tipoFiltro === "maternidade" ||
|
|
args.tipoFiltro === "paternidade"
|
|
) {
|
|
try {
|
|
const licencas = await ctx.db.query("licencas").collect();
|
|
for (const licenca of licencas) {
|
|
try {
|
|
if (
|
|
args.tipoFiltro &&
|
|
args.tipoFiltro !== "todos" &&
|
|
licenca.tipo !== args.tipoFiltro
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const funcionario = await ctx.db.get(licenca.funcionarioId);
|
|
if (!funcionario) continue;
|
|
|
|
if (!licenca.dataInicio || !licenca.dataFim) continue;
|
|
|
|
const cor =
|
|
licenca.tipo === "maternidade"
|
|
? "#ec4899"
|
|
: "#3b82f6"; // rosa ou azul
|
|
|
|
eventos.push({
|
|
id: `licenca-${licenca._id}`,
|
|
title: `${funcionario.nome} - Licença ${
|
|
licenca.tipo === "maternidade" ? "Maternidade" : "Paternidade"
|
|
}`,
|
|
start: licenca.dataInicio,
|
|
end: licenca.dataFim,
|
|
color: cor,
|
|
tipo: licenca.tipo,
|
|
funcionarioNome: funcionario.nome,
|
|
funcionarioId: funcionario._id,
|
|
});
|
|
} catch (error) {
|
|
console.error(`Erro ao processar licença ${licenca._id}:`, error);
|
|
continue;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar licenças:", error);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro geral em obterEventosCalendario:", error);
|
|
return eventos; // Retorna eventos já coletados mesmo se houver erro
|
|
}
|
|
|
|
// Integrar com férias (se não estiver filtrando por tipo específico)
|
|
if (!args.tipoFiltro || args.tipoFiltro === "todos" || args.tipoFiltro === "ferias") {
|
|
try {
|
|
// Buscar solicitações de férias aprovadas
|
|
const solicitacoesFerias = await ctx.db
|
|
.query("solicitacoesFerias")
|
|
.filter((q) =>
|
|
q.or(
|
|
q.eq(q.field("status"), "aprovado"),
|
|
q.eq(q.field("status"), "data_ajustada_aprovada")
|
|
)
|
|
)
|
|
.collect();
|
|
|
|
for (const solicitacao of solicitacoesFerias) {
|
|
try {
|
|
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
|
|
if (!funcionario) continue;
|
|
|
|
// Verificar se periodos existe e é um array
|
|
if (!solicitacao.periodos || !Array.isArray(solicitacao.periodos)) {
|
|
continue;
|
|
}
|
|
|
|
for (const periodo of solicitacao.periodos) {
|
|
if (!periodo.dataInicio || !periodo.dataFim) continue;
|
|
|
|
eventos.push({
|
|
id: `ferias-${solicitacao._id}-${periodo.dataInicio}`,
|
|
title: `${funcionario.nome} - Férias`,
|
|
start: periodo.dataInicio,
|
|
end: periodo.dataFim,
|
|
color: "#10b981", // verde
|
|
tipo: "ferias",
|
|
funcionarioNome: funcionario.nome,
|
|
funcionarioId: funcionario._id,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(`Erro ao processar solicitação de férias ${solicitacao._id}:`, error);
|
|
continue;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar solicitações de férias:", error);
|
|
// Continua mesmo se houver erro ao buscar férias
|
|
}
|
|
}
|
|
|
|
// Filtrar por período se fornecido
|
|
if (args.dataInicio && args.dataFim) {
|
|
const inicio = new Date(args.dataInicio);
|
|
const fim = new Date(args.dataFim);
|
|
return eventos.filter((e) => {
|
|
const eventStart = new Date(e.start);
|
|
const eventEnd = new Date(e.end);
|
|
return (
|
|
(eventStart >= inicio && eventStart <= fim) ||
|
|
(eventEnd >= inicio && eventEnd <= fim) ||
|
|
(eventStart <= inicio && eventEnd >= fim)
|
|
);
|
|
});
|
|
}
|
|
|
|
return eventos;
|
|
},
|
|
});
|
|
|
|
// ========== MUTATIONS ==========
|
|
|
|
/**
|
|
* Gerar URL para upload de documentos
|
|
*/
|
|
export const generateUploadUrl = mutation({
|
|
args: {},
|
|
returns: v.string(),
|
|
handler: async (ctx) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
return await ctx.storage.generateUploadUrl();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Obter URL de um documento armazenado
|
|
*/
|
|
export const obterUrlDocumento = query({
|
|
args: {
|
|
storageId: v.id("_storage"),
|
|
},
|
|
returns: v.union(v.string(), v.null()),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
return await ctx.storage.getUrl(args.storageId);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Criar atestado médico
|
|
*/
|
|
export const criarAtestadoMedico = mutation({
|
|
args: {
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
cid: v.string(),
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
},
|
|
returns: v.id("atestados"),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
// Validar datas
|
|
if (new Date(args.dataFim) < new Date(args.dataInicio)) {
|
|
throw new Error("Data fim deve ser maior ou igual à data início");
|
|
}
|
|
|
|
const atestadoId = await ctx.db.insert("atestados", {
|
|
funcionarioId: args.funcionarioId,
|
|
tipo: "atestado_medico",
|
|
dataInicio: args.dataInicio,
|
|
dataFim: args.dataFim,
|
|
cid: args.cid,
|
|
observacoes: args.observacoes,
|
|
documentoId: args.documentoId,
|
|
criadoPor: usuario._id,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"criar",
|
|
"atestados",
|
|
`Atestado médico criado para funcionário ${args.funcionarioId}`,
|
|
atestadoId
|
|
);
|
|
|
|
return atestadoId;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Criar declaração de comparecimento
|
|
*/
|
|
export const criarDeclaracaoComparecimento = mutation({
|
|
args: {
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
},
|
|
returns: v.id("atestados"),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
// Validar datas
|
|
if (new Date(args.dataFim) < new Date(args.dataInicio)) {
|
|
throw new Error("Data fim deve ser maior ou igual à data início");
|
|
}
|
|
|
|
const atestadoId = await ctx.db.insert("atestados", {
|
|
funcionarioId: args.funcionarioId,
|
|
tipo: "declaracao_comparecimento",
|
|
dataInicio: args.dataInicio,
|
|
dataFim: args.dataFim,
|
|
observacoes: args.observacoes,
|
|
documentoId: args.documentoId,
|
|
criadoPor: usuario._id,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"criar",
|
|
"atestados",
|
|
`Declaração de comparecimento criada para funcionário ${args.funcionarioId}`,
|
|
atestadoId
|
|
);
|
|
|
|
return atestadoId;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Criar licença maternidade
|
|
*/
|
|
export const criarLicencaMaternidade = mutation({
|
|
args: {
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
licencaOriginalId: v.optional(v.id("licencas")),
|
|
},
|
|
returns: v.id("licencas"),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
// Validar datas
|
|
if (new Date(args.dataFim) < new Date(args.dataInicio)) {
|
|
throw new Error("Data fim deve ser maior ou igual à data início");
|
|
}
|
|
|
|
const ehProrrogacao = !!args.licencaOriginalId;
|
|
if (ehProrrogacao && !args.licencaOriginalId) {
|
|
throw new Error("Licença original é obrigatória para prorrogação");
|
|
}
|
|
|
|
const licencaId = await ctx.db.insert("licencas", {
|
|
funcionarioId: args.funcionarioId,
|
|
tipo: "maternidade",
|
|
dataInicio: args.dataInicio,
|
|
dataFim: args.dataFim,
|
|
observacoes: args.observacoes,
|
|
documentoId: args.documentoId,
|
|
licencaOriginalId: args.licencaOriginalId,
|
|
ehProrrogacao,
|
|
criadoPor: usuario._id,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"criar",
|
|
"licencas",
|
|
`Licença maternidade criada para funcionário ${args.funcionarioId}${ehProrrogacao ? " (prorrogação)" : ""}`,
|
|
licencaId
|
|
);
|
|
|
|
return licencaId;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Criar licença paternidade
|
|
*/
|
|
export const criarLicencaPaternidade = mutation({
|
|
args: {
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
},
|
|
returns: v.id("licencas"),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
// Validar datas
|
|
if (new Date(args.dataFim) < new Date(args.dataInicio)) {
|
|
throw new Error("Data fim deve ser maior ou igual à data início");
|
|
}
|
|
|
|
const licencaId = await ctx.db.insert("licencas", {
|
|
funcionarioId: args.funcionarioId,
|
|
tipo: "paternidade",
|
|
dataInicio: args.dataInicio,
|
|
dataFim: args.dataFim,
|
|
observacoes: args.observacoes,
|
|
documentoId: args.documentoId,
|
|
ehProrrogacao: false,
|
|
criadoPor: usuario._id,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"criar",
|
|
"licencas",
|
|
`Licença paternidade criada para funcionário ${args.funcionarioId}`,
|
|
licencaId
|
|
);
|
|
|
|
return licencaId;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Prorrogar licença maternidade
|
|
*/
|
|
export const prorrogarLicencaMaternidade = mutation({
|
|
args: {
|
|
licencaOriginalId: v.id("licencas"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
},
|
|
returns: v.id("licencas"),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
const licencaOriginal = await ctx.db.get(args.licencaOriginalId);
|
|
if (!licencaOriginal) {
|
|
throw new Error("Licença original não encontrada");
|
|
}
|
|
|
|
if (licencaOriginal.tipo !== "maternidade") {
|
|
throw new Error("Apenas licenças de maternidade podem ser prorrogadas");
|
|
}
|
|
|
|
// Validar datas
|
|
if (new Date(args.dataFim) < new Date(args.dataInicio)) {
|
|
throw new Error("Data fim deve ser maior ou igual à data início");
|
|
}
|
|
|
|
const prorrogacaoId = await ctx.db.insert("licencas", {
|
|
funcionarioId: licencaOriginal.funcionarioId,
|
|
tipo: "maternidade",
|
|
dataInicio: args.dataInicio,
|
|
dataFim: args.dataFim,
|
|
observacoes: args.observacoes,
|
|
documentoId: args.documentoId,
|
|
licencaOriginalId: args.licencaOriginalId,
|
|
ehProrrogacao: true,
|
|
criadoPor: usuario._id,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"criar",
|
|
"licencas",
|
|
`Prorrogação de licença maternidade criada para funcionário ${licencaOriginal.funcionarioId}`,
|
|
prorrogacaoId
|
|
);
|
|
|
|
return prorrogacaoId;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Excluir atestado
|
|
*/
|
|
export const excluirAtestado = mutation({
|
|
args: {
|
|
id: v.id("atestados"),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
const atestado = await ctx.db.get(args.id);
|
|
if (!atestado) throw new Error("Atestado não encontrado");
|
|
|
|
await ctx.db.delete(args.id);
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"excluir",
|
|
"atestados",
|
|
`Atestado excluído: ${args.id}`,
|
|
args.id
|
|
);
|
|
|
|
return null;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Excluir licença
|
|
*/
|
|
export const excluirLicenca = mutation({
|
|
args: {
|
|
id: v.id("licencas"),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getUsuarioAutenticado(ctx);
|
|
if (!usuario) throw new Error("Não autenticado");
|
|
|
|
const licenca = await ctx.db.get(args.id);
|
|
if (!licenca) throw new Error("Licença não encontrada");
|
|
|
|
await ctx.db.delete(args.id);
|
|
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
"excluir",
|
|
"licencas",
|
|
`Licença excluída: ${args.id}`,
|
|
args.id
|
|
);
|
|
|
|
return null;
|
|
},
|
|
});
|