refactor: update vacation management structure and enhance status handling

- Renamed and refactored vacation-related types and components for clarity, transitioning from 'SolicitacaoFerias' to 'PeriodoFerias'.
- Improved the handling of vacation statuses, including the addition of 'EmFérias' to the status options.
- Streamlined the vacation request and approval components to better reflect individual vacation periods.
- Enhanced data handling in backend queries and schema to support the new structure and ensure accurate status updates.
- Improved user experience by refining UI elements related to vacation periods and their statuses.
This commit is contained in:
2025-11-13 15:54:59 -03:00
parent 4ae5baffcc
commit c058865817
11 changed files with 1150 additions and 949 deletions

View File

@@ -10,22 +10,81 @@ const periodoValidator = v.object({
diasCorridos: v.number(),
});
// Query: Listar TODAS as solicitações (para RH)
// Retorna tipo inferido automaticamente pelo Convex
// Helper: Calcular dias entre duas datas
function calcularDiasEntreDatas(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;
}
// Helper: Agrupar registros de ferias por funcionarioId + anoReferencia
function agruparPorSolicitacao(
registros: Array<Doc<"ferias">>
): Array<{
funcionarioId: Id<"funcionarios">;
anoReferencia: number;
periodos: Array<Doc<"ferias">>;
status: string;
observacao?: string;
motivoReprovacao?: string;
gestorId?: Id<"usuarios">;
dataAprovacao?: number;
dataReprovacao?: number;
historicoAlteracoes?: Array<{
data: number;
usuarioId: Id<"usuarios">;
acao: string;
}>;
}> {
const grupos = new Map<string, Array<Doc<"ferias">>>();
for (const registro of registros) {
const chave = `${registro.funcionarioId}_${registro.anoReferencia}`;
if (!grupos.has(chave)) {
grupos.set(chave, []);
}
grupos.get(chave)!.push(registro);
}
return Array.from(grupos.entries()).map(([_, periodos]) => {
// Ordenar por data de criação para manter ordem
periodos.sort((a, b) => a._creationTime - b._creationTime);
// Pegar informações da primeira solicitação (todos têm os mesmos campos compartilhados)
const primeiro = periodos[0];
return {
funcionarioId: primeiro.funcionarioId,
anoReferencia: primeiro.anoReferencia,
periodos,
status: primeiro.status,
observacao: primeiro.observacao,
motivoReprovacao: primeiro.motivoReprovacao,
gestorId: primeiro.gestorId,
dataAprovacao: primeiro.dataAprovacao,
dataReprovacao: primeiro.dataReprovacao,
historicoAlteracoes: primeiro.historicoAlteracoes,
};
});
}
// Query: Listar TODAS as solicitações (para RH) - períodos individuais
export const listarTodas = query({
args: {},
handler: async (ctx) => {
const solicitacoes = await ctx.db.query("solicitacoesFerias").collect();
const todasFerias = await ctx.db.query("ferias").collect();
const solicitacoesComDetalhes = await Promise.all(
solicitacoes.map(async (s) => {
const funcionario = await ctx.db.get(s.funcionarioId);
const periodosComDetalhes = await Promise.all(
todasFerias.map(async (ferias) => {
const funcionario = await ctx.db.get(ferias.funcionarioId);
// Buscar time do funcionário
const membroTime = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", s.funcionarioId)
q.eq("funcionarioId", ferias.funcionarioId)
)
.filter((q) => q.eq(q.field("ativo"), true))
.first();
@@ -36,65 +95,54 @@ export const listarTodas = query({
}
return {
...s,
...ferias,
funcionario,
time,
};
})
);
return solicitacoesComDetalhes.sort(
(a, b) => b._creationTime - a._creationTime
);
return periodosComDetalhes.sort((a, b) => b._creationTime - a._creationTime);
},
});
// Query: Listar solicitações do funcionário
// Query: Listar solicitações do funcionário - períodos individuais
export const listarMinhasSolicitacoes = query({
args: { funcionarioId: v.id("funcionarios") },
// returns não especificado - TypeScript inferirá automaticamente o tipo correto
handler: async (ctx, args) => {
const solicitacoes = await ctx.db
.query("solicitacoesFerias")
const todasFerias = await ctx.db
.query("ferias")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", args.funcionarioId)
)
.order("desc")
.collect();
// Enriquecer com dados do funcionário e time
const solicitacoesComDetalhes = await Promise.all(
solicitacoes.map(async (s) => {
const funcionario = await ctx.db.get(s.funcionarioId);
// Buscar time do funcionário
const membroTime = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", s.funcionarioId)
)
.filter((q) => q.eq(q.field("ativo"), true))
.first();
const funcionario = await ctx.db.get(args.funcionarioId);
// Buscar time do funcionário
const membroTime = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", args.funcionarioId)
)
.filter((q) => q.eq(q.field("ativo"), true))
.first();
let time = null;
if (membroTime) {
time = await ctx.db.get(membroTime.timeId);
}
let time = null;
if (membroTime) {
time = await ctx.db.get(membroTime.timeId);
}
return {
...s,
funcionario,
time,
};
})
);
return solicitacoesComDetalhes;
// Retornar períodos individuais com detalhes
return todasFerias.map((ferias) => ({
...ferias,
funcionario,
time,
})).sort((a, b) => b._creationTime - a._creationTime);
},
});
// Query: Listar solicitações dos subordinados (para gestores)
// Retorna tipo inferido automaticamente pelo Convex
// Query: Listar solicitações dos subordinados (para gestores) - períodos individuais
export const listarSolicitacoesSubordinados = query({
args: { gestorId: v.id("usuarios") },
handler: async (ctx, args) => {
@@ -105,10 +153,7 @@ export const listarSolicitacoesSubordinados = query({
.filter((q) => q.eq(q.field("ativo"), true))
.collect();
const solicitacoes: Array<Doc<"solicitacoesFerias"> & {
funcionario: Doc<"funcionarios"> | null;
time: Doc<"times"> | null;
}> = [];
const todasFerias: Array<Doc<"ferias">> = [];
for (const time of timesGestor) {
// Buscar membros do time
@@ -119,54 +164,90 @@ export const listarSolicitacoesSubordinados = query({
)
.collect();
// Buscar solicitações de cada membro
// Buscar férias de cada membro
for (const membro of membros) {
const solic = await ctx.db
.query("solicitacoesFerias")
const ferias = await ctx.db
.query("ferias")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", membro.funcionarioId)
)
.collect();
// Adicionar info do funcionário
for (const s of solic) {
const funcionario = await ctx.db.get(s.funcionarioId);
solicitacoes.push({
...s,
funcionario,
time,
});
}
todasFerias.push(...ferias);
}
}
return solicitacoes.sort((a, b) => b._creationTime - a._creationTime);
// Adicionar info do funcionário e time para cada período
const periodosComDetalhes = await Promise.all(
todasFerias.map(async (ferias) => {
const funcionario = await ctx.db.get(ferias.funcionarioId);
// Buscar time do funcionário
const membroTime = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", ferias.funcionarioId)
)
.filter((q) => q.eq(q.field("ativo"), true))
.first();
let time = null;
if (membroTime) {
time = await ctx.db.get(membroTime.timeId);
}
return {
...ferias,
funcionario,
time,
};
})
);
return periodosComDetalhes.sort((a, b) => b._creationTime - a._creationTime);
},
});
// Query: Obter detalhes completos de uma solicitação
// Retorna tipo inferido automaticamente pelo Convex
// Query: Obter detalhes de um período individual
export const obterDetalhes = query({
args: { solicitacaoId: v.id("solicitacoesFerias") },
args: {
feriasId: v.id("ferias")
},
handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) return null;
const ferias = await ctx.db.get(args.feriasId);
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
if (!ferias) return null;
const funcionario = await ctx.db.get(ferias.funcionarioId);
let gestor = null;
if (solicitacao.gestorId) {
gestor = await ctx.db.get(solicitacao.gestorId);
if (ferias.gestorId) {
gestor = await ctx.db.get(ferias.gestorId);
}
// Buscar time do funcionário
const membroTime = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) =>
q.eq("funcionarioId", ferias.funcionarioId)
)
.filter((q) => q.eq(q.field("ativo"), true))
.first();
let time = null;
if (membroTime) {
time = await ctx.db.get(membroTime.timeId);
}
return {
...solicitacao,
...ferias,
funcionario,
gestor,
time,
};
},
});
// Mutation: Criar solicitação de férias (com validação de saldo)
// Mutation: Criar solicitação de férias (cria um registro por período)
export const criarSolicitacao = mutation({
args: {
funcionarioId: v.id("funcionarios"),
@@ -174,7 +255,7 @@ export const criarSolicitacao = mutation({
periodos: v.array(periodoValidator),
observacao: v.optional(v.string()),
},
returns: v.id("solicitacoesFerias"),
returns: v.array(v.id("ferias")),
handler: async (ctx, args) => {
if (args.periodos.length === 0) {
throw new Error("É necessário adicionar pelo menos 1 período");
@@ -183,19 +264,6 @@ export const criarSolicitacao = mutation({
const funcionario = await ctx.db.get(args.funcionarioId);
if (!funcionario) throw new Error("Funcionário não encontrado");
// Calcular total de dias
let totalDias = 0;
for (const p of args.periodos) {
totalDias += p.diasCorridos;
}
// Reservar dias no saldo (impede uso duplo)
await ctx.runMutation(internal.saldoFerias.reservarDias, {
funcionarioId: args.funcionarioId,
anoReferencia: args.anoReferencia,
totalDias,
});
// Buscar usuário que está criando (pode não ser o próprio funcionário)
const usuario = await ctx.db
.query("usuarios")
@@ -204,59 +272,75 @@ export const criarSolicitacao = mutation({
)
.first();
const solicitacaoId = await ctx.db.insert("solicitacoesFerias", {
funcionarioId: args.funcionarioId,
anoReferencia: args.anoReferencia,
status: "aguardando_aprovacao",
periodos: args.periodos,
observacao: args.observacao,
historicoAlteracoes: [
{
data: Date.now(),
usuarioId: usuario?._id || funcionario.gestorId!,
acao: "Solicitação criada",
},
],
});
const historicoInicial = [
{
data: Date.now(),
usuarioId: usuario?._id || funcionario.gestorId!,
acao: "Solicitação criada",
},
];
// Notificar gestor
if (funcionario.gestorId) {
// Criar um registro para cada período
const idsCriados: Array<Id<"ferias">> = [];
for (const periodo of args.periodos) {
const feriasId = await ctx.db.insert("ferias", {
funcionarioId: args.funcionarioId,
anoReferencia: args.anoReferencia,
dataInicio: periodo.dataInicio,
dataFim: periodo.dataFim,
diasFerias: periodo.diasCorridos,
status: "aguardando_aprovacao",
observacao: args.observacao,
diasAbono: 0,
historicoAlteracoes: historicoInicial,
});
idsCriados.push(feriasId);
}
// Notificar gestor (usar o primeiro ID criado)
if (funcionario.gestorId && idsCriados.length > 0) {
await ctx.db.insert("notificacoesFerias", {
destinatarioId: funcionario.gestorId,
solicitacaoFeriasId: solicitacaoId,
feriasId: idsCriados[0],
tipo: "nova_solicitacao",
lida: false,
mensagem: `${funcionario.nome} solicitou férias`,
});
}
return solicitacaoId;
return idsCriados;
},
});
// Mutation: Aprovar férias
// Mutation: Aprovar período de férias individual
export const aprovar = mutation({
args: {
solicitacaoId: v.id("solicitacoesFerias"),
feriasId: v.id("ferias"),
gestorId: v.id("usuarios"),
},
returns: v.null(),
handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) throw new Error("Solicitação não encontrada");
// Buscar o registro específico
const registro = await ctx.db.get(args.feriasId);
if (solicitacao.status !== "aguardando_aprovacao") {
throw new Error("Esta solicitação já foi processada");
if (!registro) {
throw new Error("Período de férias não encontrado");
}
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
// Verificar se está aguardando aprovação
if (registro.status !== "aguardando_aprovacao") {
throw new Error("Este período já foi processado");
}
await ctx.db.patch(args.solicitacaoId, {
const funcionario = await ctx.db.get(registro.funcionarioId);
// Atualizar o registro
await ctx.db.patch(registro._id, {
status: "aprovado",
gestorId: args.gestorId,
dataAprovacao: Date.now(),
historicoAlteracoes: [
...(solicitacao.historicoAlteracoes || []),
...(registro.historicoAlteracoes || []),
{
data: Date.now(),
usuarioId: args.gestorId,
@@ -265,11 +349,6 @@ export const aprovar = mutation({
],
});
// Atualizar saldo (de pendente para usado)
await ctx.runMutation(internal.saldoFerias.atualizarSaldoAposAprovacao, {
solicitacaoId: args.solicitacaoId,
});
// Notificar funcionário
if (funcionario) {
const usuario = await ctx.db
@@ -282,10 +361,10 @@ export const aprovar = mutation({
if (usuario) {
await ctx.db.insert("notificacoesFerias", {
destinatarioId: usuario._id,
solicitacaoFeriasId: args.solicitacaoId,
feriasId: registro._id,
tipo: "aprovado",
lida: false,
mensagem: "Suas férias foram aprovadas!",
mensagem: `Período de férias de ${registro.diasFerias} dias foi aprovado!`,
});
}
}
@@ -294,31 +373,37 @@ export const aprovar = mutation({
},
});
// Mutation: Reprovar férias
// Mutation: Reprovar período de férias individual
export const reprovar = mutation({
args: {
solicitacaoId: v.id("solicitacoesFerias"),
feriasId: v.id("ferias"),
gestorId: v.id("usuarios"),
motivoReprovacao: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) throw new Error("Solicitação não encontrada");
// Buscar o registro específico
const registro = await ctx.db.get(args.feriasId);
if (solicitacao.status !== "aguardando_aprovacao") {
throw new Error("Esta solicitação já foi processada");
if (!registro) {
throw new Error("Período de férias não encontrado");
}
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
// Verificar se está aguardando aprovação
if (registro.status !== "aguardando_aprovacao") {
throw new Error("Este período já foi processado");
}
await ctx.db.patch(args.solicitacaoId, {
const funcionario = await ctx.db.get(registro.funcionarioId);
// Atualizar o registro
await ctx.db.patch(registro._id, {
status: "reprovado",
gestorId: args.gestorId,
dataReprovacao: Date.now(),
motivoReprovacao: args.motivoReprovacao,
historicoAlteracoes: [
...(solicitacao.historicoAlteracoes || []),
...(registro.historicoAlteracoes || []),
{
data: Date.now(),
usuarioId: args.gestorId,
@@ -327,11 +412,6 @@ export const reprovar = mutation({
],
});
// Liberar dias reservados de volta ao saldo
await ctx.runMutation(internal.saldoFerias.liberarDias, {
solicitacaoId: args.solicitacaoId,
});
// Notificar funcionário
if (funcionario) {
const usuario = await ctx.db
@@ -344,10 +424,10 @@ export const reprovar = mutation({
if (usuario) {
await ctx.db.insert("notificacoesFerias", {
destinatarioId: usuario._id,
solicitacaoFeriasId: args.solicitacaoId,
feriasId: registro._id,
tipo: "reprovado",
lida: false,
mensagem: `Suas férias foram reprovadas: ${args.motivoReprovacao}`,
mensagem: `Período de férias de ${registro.diasFerias} dias foi reprovado: ${args.motivoReprovacao}`,
});
}
}
@@ -356,66 +436,51 @@ export const reprovar = mutation({
},
});
// Mutation: Ajustar data e aprovar
// Mutation: Ajustar data e aprovar período individual
export const ajustarEAprovar = mutation({
args: {
solicitacaoId: v.id("solicitacoesFerias"),
feriasId: v.id("ferias"),
gestorId: v.id("usuarios"),
novosPeriodos: v.array(periodoValidator),
novaDataInicio: v.string(),
novaDataFim: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) throw new Error("Solicitação não encontrada");
// Buscar o registro específico
const registroAntigo = await ctx.db.get(args.feriasId);
if (solicitacao.status !== "aguardando_aprovacao") {
throw new Error("Esta solicitação já foi processada");
if (!registroAntigo) {
throw new Error("Período de férias não encontrado");
}
if (args.novosPeriodos.length === 0) {
throw new Error("É necessário adicionar pelo menos 1 período");
// Verificar se está aguardando aprovação
if (registroAntigo.status !== "aguardando_aprovacao") {
throw new Error("Este período já foi processado");
}
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
const funcionario = await ctx.db.get(registroAntigo.funcionarioId);
// Liberar dias antigos
await ctx.runMutation(internal.saldoFerias.liberarDias, {
solicitacaoId: args.solicitacaoId,
});
// Calcular novos dias
const novosDias = calcularDiasEntreDatas(args.novaDataInicio, args.novaDataFim);
// Calcular novos dias e reservar
let totalNovosDias = 0;
for (const p of args.novosPeriodos) {
totalNovosDias += p.diasCorridos;
}
await ctx.runMutation(internal.saldoFerias.reservarDias, {
funcionarioId: solicitacao.funcionarioId,
anoReferencia: solicitacao.anoReferencia,
totalDias: totalNovosDias,
});
await ctx.db.patch(args.solicitacaoId, {
// Atualizar o registro com novas datas
await ctx.db.patch(registroAntigo._id, {
dataInicio: args.novaDataInicio,
dataFim: args.novaDataFim,
diasFerias: novosDias,
status: "data_ajustada_aprovada",
periodos: args.novosPeriodos,
gestorId: args.gestorId,
dataAprovacao: Date.now(),
historicoAlteracoes: [
...(solicitacao.historicoAlteracoes || []),
...(registroAntigo.historicoAlteracoes || []),
{
data: Date.now(),
usuarioId: args.gestorId,
acao: "Data ajustada e aprovada",
periodosAnteriores: solicitacao.periodos,
acao: `Data ajustada e aprovada: ${registroAntigo.dataInicio} - ${registroAntigo.dataFim}${args.novaDataInicio} - ${args.novaDataFim}`,
},
],
});
// Atualizar saldo (marcar como usado)
await ctx.runMutation(internal.saldoFerias.atualizarSaldoAposAprovacao, {
solicitacaoId: args.solicitacaoId,
});
// Notificar funcionário
if (funcionario) {
const usuario = await ctx.db
@@ -428,10 +493,10 @@ export const ajustarEAprovar = mutation({
if (usuario) {
await ctx.db.insert("notificacoesFerias", {
destinatarioId: usuario._id,
solicitacaoFeriasId: args.solicitacaoId,
feriasId: registroAntigo._id,
tipo: "data_ajustada",
lida: false,
mensagem: "Suas férias foram aprovadas com ajuste de datas",
mensagem: `Período de férias foi aprovado com ajuste de datas: ${args.novaDataInicio} a ${args.novaDataFim}`,
});
}
}
@@ -448,15 +513,15 @@ export const verificarStatusFerias = query({
const hoje = new Date();
hoje.setHours(0, 0, 0, 0);
const solicitacoesAprovadas = await ctx.db
.query("solicitacoesFerias")
const feriasAprovadas = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q.eq("funcionarioId", args.funcionarioId).eq("status", "aprovado")
)
.collect();
const solicitacoesAjustadas = await ctx.db
.query("solicitacoesFerias")
const feriasAjustadas = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q
.eq("funcionarioId", args.funcionarioId)
@@ -464,21 +529,27 @@ export const verificarStatusFerias = query({
)
.collect();
const todasSolicitacoes = [
...solicitacoesAprovadas,
...solicitacoesAjustadas,
const feriasEmFerias = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q.eq("funcionarioId", args.funcionarioId).eq("status", "EmFérias")
)
.collect();
const todasFerias = [
...feriasAprovadas,
...feriasAjustadas,
...feriasEmFerias,
];
for (const solicitacao of todasSolicitacoes) {
for (const periodo of solicitacao.periodos) {
const inicio = new Date(periodo.dataInicio);
const fim = new Date(periodo.dataFim);
inicio.setHours(0, 0, 0, 0);
fim.setHours(23, 59, 59, 999);
for (const ferias of todasFerias) {
const inicio = new Date(ferias.dataInicio);
const fim = new Date(ferias.dataFim);
inicio.setHours(0, 0, 0, 0);
fim.setHours(23, 59, 59, 999);
if (hoje >= inicio && hoje <= fim) {
return "em_ferias";
}
if (hoje >= inicio && hoje <= fim) {
return "em_ferias";
}
}
@@ -509,10 +580,10 @@ export const marcarComoLida = mutation({
},
});
// Mutation: Atualizar status da solicitação (para voltar para aguardando_aprovacao)
// Mutation: Atualizar status de um período individual
export const atualizarStatus = mutation({
args: {
solicitacaoId: v.id("solicitacoesFerias"),
feriasId: v.id("ferias"),
novoStatus: v.union(
v.literal("aguardando_aprovacao"),
v.literal("aprovado"),
@@ -523,40 +594,16 @@ export const atualizarStatus = mutation({
},
returns: v.null(),
handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) throw new Error("Solicitação não encontrada");
// Buscar o registro específico
const registro = await ctx.db.get(args.feriasId);
// Se está mudando de aprovado para aguardando_aprovacao, precisa liberar os dias
if (solicitacao.status === "aprovado" || solicitacao.status === "data_ajustada_aprovada") {
if (args.novoStatus === "aguardando_aprovacao") {
// Liberar dias de volta ao saldo
await ctx.runMutation(internal.saldoFerias.liberarDias, {
solicitacaoId: args.solicitacaoId,
});
}
}
// Se está mudando de reprovado para aguardando_aprovacao, os dias já foram liberados anteriormente
// Mas precisamos reservar novamente
if (solicitacao.status === "reprovado" && args.novoStatus === "aguardando_aprovacao") {
// Calcular total de dias
let totalDias = 0;
for (const p of solicitacao.periodos) {
totalDias += p.diasCorridos;
}
// Reservar dias novamente
await ctx.runMutation(internal.saldoFerias.reservarDias, {
funcionarioId: solicitacao.funcionarioId,
anoReferencia: solicitacao.anoReferencia,
totalDias,
});
if (!registro) {
throw new Error("Período de férias não encontrado");
}
// Atualizar status e histórico
const acao = `Status alterado de ${solicitacao.status} para ${args.novoStatus}`;
const acao = `Status alterado para ${args.novoStatus}`;
// Preparar dados de atualização
const updateData: {
status: typeof args.novoStatus;
historicoAlteracoes: Array<{
@@ -564,10 +611,14 @@ export const atualizarStatus = mutation({
usuarioId: Id<"usuarios">;
acao: string;
}>;
gestorId?: undefined;
dataAprovacao?: undefined;
dataReprovacao?: undefined;
motivoReprovacao?: undefined;
} = {
status: args.novoStatus,
historicoAlteracoes: [
...(solicitacao.historicoAlteracoes || []),
...(registro.historicoAlteracoes || []),
{
data: Date.now(),
usuarioId: args.usuarioId,
@@ -576,16 +627,17 @@ export const atualizarStatus = mutation({
],
};
// Se voltar para aguardando_aprovacao, limpar campos relacionados usando replace
// Se voltar para aguardando_aprovacao, limpar campos relacionados
if (args.novoStatus === "aguardando_aprovacao") {
// Usar replace para limpar campos opcionais - omitir os campos que queremos limpar
const { gestorId, dataAprovacao, dataReprovacao, motivoReprovacao, ...solicitacaoLimpa } = solicitacao;
await ctx.db.replace(args.solicitacaoId, {
...solicitacaoLimpa,
await ctx.db.patch(registro._id, {
...updateData,
gestorId: undefined,
dataAprovacao: undefined,
dataReprovacao: undefined,
motivoReprovacao: undefined,
});
} else {
await ctx.db.patch(args.solicitacaoId, updateData);
await ctx.db.patch(registro._id, updateData);
}
return null;
@@ -603,39 +655,106 @@ export const atualizarStatusTodosFuncionarios = internalMutation({
const hoje = new Date();
hoje.setHours(0, 0, 0, 0);
const solicitacoesAprovadas = await ctx.db
.query("solicitacoesFerias")
// Buscar todos os registros de férias que podem estar em férias
// Buscar por status específico para criar mapas de referência
const feriasAprovadas = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q.eq("funcionarioId", func._id).eq("status", "aprovado")
)
.collect();
const solicitacoesAjustadas = await ctx.db
.query("solicitacoesFerias")
const feriasAjustadas = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q.eq("funcionarioId", func._id).eq("status", "data_ajustada_aprovada")
)
.collect();
const todasSolicitacoes = [
...solicitacoesAprovadas,
...solicitacoesAjustadas,
const feriasEmFerias = await ctx.db
.query("ferias")
.withIndex("by_funcionario_and_status", (q) =>
q.eq("funcionarioId", func._id).eq("status", "EmFérias")
)
.collect();
// Criar mapas para verificar status original
// Quando um registro está "EmFérias", precisamos saber qual era o status anterior
// Vamos usar o histórico ou verificar se o ID estava nas listas antes
const idsAprovados = new Set(feriasAprovadas.map(f => f._id));
const idsAjustados = new Set(feriasAjustadas.map(f => f._id));
// Para registros que estão "EmFérias", verificar o histórico para determinar status anterior
// Se não houver histórico claro, usar lógica: se foi aprovado recentemente, provavelmente era "aprovado"
// Por enquanto, vamos usar uma heurística: se o registro está "EmFérias" e não está nas listas,
// vamos verificar o histórico de alterações para encontrar o status anterior
const statusAnteriorPorId = new Map<Id<"ferias">, "aprovado" | "data_ajustada_aprovada">();
for (const ferias of feriasEmFerias) {
// Verificar histórico para encontrar status anterior
if (ferias.historicoAlteracoes && ferias.historicoAlteracoes.length > 0) {
// Procurar pela última alteração que mudou para "EmFérias" ou antes disso
const historico = ferias.historicoAlteracoes;
for (let i = historico.length - 1; i >= 0; i--) {
const entrada = historico[i];
if (entrada.acao.includes("Aprovado") || entrada.acao.includes("aprovado")) {
statusAnteriorPorId.set(ferias._id, "aprovado");
break;
} else if (entrada.acao.includes("Data ajustada") || entrada.acao.includes("ajustada")) {
statusAnteriorPorId.set(ferias._id, "data_ajustada_aprovada");
break;
}
}
}
// Se não encontrou no histórico, usar fallback: assumir "aprovado"
if (!statusAnteriorPorId.has(ferias._id)) {
statusAnteriorPorId.set(ferias._id, "aprovado");
}
}
// Combinar todos os registros
const todasFerias = [
...feriasAprovadas,
...feriasAjustadas,
...feriasEmFerias,
];
let emFerias = false;
for (const solicitacao of todasSolicitacoes) {
for (const periodo of solicitacao.periodos) {
const inicio = new Date(periodo.dataInicio);
const fim = new Date(periodo.dataFim);
inicio.setHours(0, 0, 0, 0);
fim.setHours(23, 59, 59, 999);
for (const ferias of todasFerias) {
const inicio = new Date(ferias.dataInicio);
const fim = new Date(ferias.dataFim);
inicio.setHours(0, 0, 0, 0);
fim.setHours(23, 59, 59, 999);
if (hoje >= inicio && hoje <= fim) {
emFerias = true;
break;
if (hoje >= inicio && hoje <= fim) {
emFerias = true;
// Atualizar status para "EmFérias" se ainda não estiver
if (ferias.status !== "EmFérias") {
await ctx.db.patch(ferias._id, {
status: "EmFérias",
});
}
} else {
// Se saiu do período e está "EmFérias", voltar para o status anterior
if (ferias.status === "EmFérias") {
// Determinar status anterior
let statusAnterior: "aprovado" | "data_ajustada_aprovada";
if (idsAprovados.has(ferias._id)) {
statusAnterior = "aprovado";
} else if (idsAjustados.has(ferias._id)) {
statusAnterior = "data_ajustada_aprovada";
} else {
// Usar histórico ou fallback
statusAnterior = statusAnteriorPorId.get(ferias._id) || "aprovado";
}
await ctx.db.patch(ferias._id, {
status: statusAnterior,
});
}
}
if (emFerias) break;
}
const novoStatus = emFerias ? "em_ferias" : "ativo";