Files
sgse-app/packages/backend/convex/atestadosLicencas.ts
deyvisonwanderley bc3c7df00f feat: refactor document URL retrieval for atestados and licencas
- 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.
2025-11-04 03:46:50 -03:00

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;
},
});