feat: implement automatic adjustment removal for deleted records in absence and atestado mutations, enhancing data integrity and recalculating work hours for specific periods
This commit is contained in:
@@ -1254,6 +1254,7 @@ export const excluirAtestado = mutation({
|
|||||||
const funcionarioId = atestado.funcionarioId;
|
const funcionarioId = atestado.funcionarioId;
|
||||||
const dataInicio = atestado.dataInicio; // Data início do atestado
|
const dataInicio = atestado.dataInicio; // Data início do atestado
|
||||||
const dataFim = atestado.dataFim; // Data fim do atestado
|
const dataFim = atestado.dataFim; // Data fim do atestado
|
||||||
|
const atestadoId = args.id.toString(); // ID do atestado para remover ajustes
|
||||||
|
|
||||||
// Excluir o registro do banco de dados
|
// Excluir o registro do banco de dados
|
||||||
await ctx.db.delete(args.id);
|
await ctx.db.delete(args.id);
|
||||||
@@ -1267,6 +1268,19 @@ export const excluirAtestado = mutation({
|
|||||||
args.id
|
args.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Remover ajustes automáticos relacionados ao atestado excluído
|
||||||
|
try {
|
||||||
|
await ctx.runMutation(internal.pontos.removerAjustesAutomaticosInternal, {
|
||||||
|
funcionarioId,
|
||||||
|
motivoTipo: 'atestado',
|
||||||
|
motivoId: atestadoId,
|
||||||
|
dataInicio,
|
||||||
|
dataFim
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[excluirAtestado] Erro ao remover ajustes automáticos:', error);
|
||||||
|
}
|
||||||
|
|
||||||
// Recalcular banco de horas APENAS para o período específico do atestado excluído
|
// Recalcular banco de horas APENAS para o período específico do atestado excluído
|
||||||
// Isso garante que os dias do atestado sejam removidos corretamente dos registros de ponto
|
// Isso garante que os dias do atestado sejam removidos corretamente dos registros de ponto
|
||||||
await recalcularBancoHorasPeriodo(ctx, funcionarioId, dataInicio, dataFim);
|
await recalcularBancoHorasPeriodo(ctx, funcionarioId, dataInicio, dataFim);
|
||||||
|
|||||||
@@ -941,10 +941,13 @@ export const excluirSolicitacao = mutation({
|
|||||||
throw new Error('Solicitação não encontrada');
|
throw new Error('Solicitação não encontrada');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apenas solicitações ainda não processadas podem ser excluídas
|
// IMPORTANTE: Salvar o período exato da ausência ANTES de excluir
|
||||||
if (solicitacao.status !== 'aguardando_aprovacao') {
|
// para recalcular o banco de horas apenas para esse período específico
|
||||||
throw new Error('Apenas solicitações pendentes podem ser excluídas');
|
const funcionarioId = solicitacao.funcionarioId;
|
||||||
}
|
const dataInicio = solicitacao.dataInicio;
|
||||||
|
const dataFim = solicitacao.dataFim;
|
||||||
|
const statusOriginal = solicitacao.status;
|
||||||
|
const ausenciaId = args.solicitacaoId.toString(); // ID da ausência para remover ajustes
|
||||||
|
|
||||||
// Verificar se o usuário é o criador original da solicitação
|
// Verificar se o usuário é o criador original da solicitação
|
||||||
const usuario = await ctx.db.get(args.usuarioId);
|
const usuario = await ctx.db.get(args.usuarioId);
|
||||||
@@ -963,7 +966,34 @@ export const excluirSolicitacao = mutation({
|
|||||||
throw new Error('Você não tem permissão para excluir esta solicitação');
|
throw new Error('Você não tem permissão para excluir esta solicitação');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Permitir exclusão de ausências aprovadas (não apenas pendentes)
|
||||||
|
// Se estiver aprovada, o gestor pode excluir para corrigir erros
|
||||||
|
if (statusOriginal === 'aprovado' && !usuarioEhGestor) {
|
||||||
|
throw new Error('Apenas o gestor pode excluir ausências aprovadas');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excluir o registro do banco de dados
|
||||||
await ctx.db.delete(args.solicitacaoId);
|
await ctx.db.delete(args.solicitacaoId);
|
||||||
|
|
||||||
|
// Remover ajustes automáticos relacionados à ausência excluída (apenas se estava aprovada)
|
||||||
|
if (statusOriginal === 'aprovado') {
|
||||||
|
try {
|
||||||
|
await ctx.runMutation(internal.pontos.removerAjustesAutomaticosInternal, {
|
||||||
|
funcionarioId,
|
||||||
|
motivoTipo: 'ausencia',
|
||||||
|
motivoId: ausenciaId,
|
||||||
|
dataInicio,
|
||||||
|
dataFim
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[excluirSolicitacao] Erro ao remover ajustes automáticos:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcular banco de horas APENAS para o período específico da ausência excluída
|
||||||
|
// Isso garante que os dias da ausência sejam removidos corretamente dos registros de ponto
|
||||||
|
await recalcularBancoHorasPeriodo(ctx, funcionarioId, dataInicio, dataFim);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,43 @@
|
|||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { mutation, query, internalMutation } from './_generated/server';
|
import { mutation, query, internalMutation } from './_generated/server';
|
||||||
import { internal } from './_generated/api';
|
import { internal } from './_generated/api';
|
||||||
|
import type { MutationCtx } from './_generated/server';
|
||||||
import { Id, Doc } from './_generated/dataModel';
|
import { Id, Doc } from './_generated/dataModel';
|
||||||
import { verificarLicencaAtiva } from './atestadosLicencas';
|
import { verificarLicencaAtiva } from './atestadosLicencas';
|
||||||
import { getCurrentUserFunction } from './auth';
|
import { getCurrentUserFunction } from './auth';
|
||||||
import { formatarDataBR } from './utils/datas';
|
import { formatarDataBR } from './utils/datas';
|
||||||
import { api } from './_generated/api';
|
import { api } from './_generated/api';
|
||||||
|
|
||||||
|
// Helper: Recalcular banco de horas em um período
|
||||||
|
async function recalcularBancoHorasPeriodo(
|
||||||
|
ctx: MutationCtx,
|
||||||
|
funcionarioId: Id<'funcionarios'>,
|
||||||
|
dataInicio: string,
|
||||||
|
dataFim: string
|
||||||
|
): Promise<void> {
|
||||||
|
// Gerar todas as datas do período
|
||||||
|
const dataInicioObj = new Date(dataInicio);
|
||||||
|
const dataFimObj = new Date(dataFim);
|
||||||
|
const datas: string[] = [];
|
||||||
|
const dataAtual = new Date(dataInicioObj);
|
||||||
|
|
||||||
|
while (dataAtual <= dataFimObj) {
|
||||||
|
const ano = dataAtual.getFullYear();
|
||||||
|
const mes = String(dataAtual.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dia = String(dataAtual.getDate()).padStart(2, '0');
|
||||||
|
datas.push(`${ano}-${mes}-${dia}`);
|
||||||
|
dataAtual.setDate(dataAtual.getDate() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcular para cada data usando a mutation interna (agendar para execução assíncrona)
|
||||||
|
for (let i = 0; i < datas.length; i++) {
|
||||||
|
await ctx.scheduler.runAfter(i * 100, internal.pontos.recalcularBancoHorasData, {
|
||||||
|
funcionarioId,
|
||||||
|
data: datas[i]!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validador para períodos
|
// Validador para períodos
|
||||||
const periodoValidator = v.object({
|
const periodoValidator = v.object({
|
||||||
dataInicio: v.string(),
|
dataInicio: v.string(),
|
||||||
@@ -878,6 +909,26 @@ export const atualizarStatus = mutation({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Se o status foi alterado para Cancelado_RH, recalcular banco de horas
|
||||||
|
// para garantir que os dias de férias sejam removidos do registro de ponto
|
||||||
|
if (args.novoStatus === 'Cancelado_RH') {
|
||||||
|
// IMPORTANTE: Recalcular banco de horas para o período das férias canceladas
|
||||||
|
// Isso garante que os dias de férias sejam removidos corretamente dos registros de ponto
|
||||||
|
try {
|
||||||
|
await recalcularBancoHorasPeriodo(
|
||||||
|
ctx,
|
||||||
|
registro.funcionarioId,
|
||||||
|
registro.dataInicio,
|
||||||
|
registro.dataFim
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'[ferias.atualizarStatus] Erro ao recalcular banco de horas após cancelamento:',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Se o status foi alterado para Cancelado_RH, notificar o funcionário
|
// Se o status foi alterado para Cancelado_RH, notificar o funcionário
|
||||||
if (args.novoStatus === 'Cancelado_RH') {
|
if (args.novoStatus === 'Cancelado_RH') {
|
||||||
const funcionario = await ctx.db.get(registro.funcionarioId);
|
const funcionario = await ctx.db.get(registro.funcionarioId);
|
||||||
|
|||||||
@@ -1535,6 +1535,54 @@ async function verificarAusenciaAprovada(
|
|||||||
return { temAusencia: false };
|
return { temAusencia: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove ajustes automáticos relacionados a um registro excluído
|
||||||
|
* Busca e remove ajustes que referenciam o motivoId fornecido
|
||||||
|
*/
|
||||||
|
async function removerAjustesAutomaticos(
|
||||||
|
ctx: MutationCtx,
|
||||||
|
funcionarioId: Id<'funcionarios'>,
|
||||||
|
motivoTipo: 'atestado' | 'licenca' | 'ausencia',
|
||||||
|
motivoId: string,
|
||||||
|
dataInicio: string,
|
||||||
|
dataFim: string
|
||||||
|
): Promise<void> {
|
||||||
|
// Gerar todas as datas do período
|
||||||
|
const dataInicioObj = new Date(dataInicio);
|
||||||
|
const dataFimObj = new Date(dataFim);
|
||||||
|
const datas: string[] = [];
|
||||||
|
const dataAtual = new Date(dataInicioObj);
|
||||||
|
|
||||||
|
while (dataAtual <= dataFimObj) {
|
||||||
|
const ano = dataAtual.getFullYear();
|
||||||
|
const mes = String(dataAtual.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dia = String(dataAtual.getDate()).padStart(2, '0');
|
||||||
|
datas.push(`${ano}-${mes}-${dia}`);
|
||||||
|
dataAtual.setDate(dataAtual.getDate() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar todos os ajustes automáticos relacionados ao motivoId no período
|
||||||
|
for (const data of datas) {
|
||||||
|
const ajustes = await ctx.db
|
||||||
|
.query('ajustesBancoHoras')
|
||||||
|
.filter((q) =>
|
||||||
|
q.and(
|
||||||
|
q.eq(q.field('funcionarioId'), funcionarioId),
|
||||||
|
q.eq(q.field('dataAplicacao'), data),
|
||||||
|
q.eq(q.field('motivoTipo'), motivoTipo),
|
||||||
|
q.eq(q.field('motivoId'), motivoId),
|
||||||
|
q.eq(q.field('aplicado'), true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Remover cada ajuste encontrado
|
||||||
|
for (const ajuste of ajustes) {
|
||||||
|
await ctx.db.delete(ajuste._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifica ajustes manuais aplicados no dia
|
* Verifica ajustes manuais aplicados no dia
|
||||||
*/
|
*/
|
||||||
@@ -1693,10 +1741,13 @@ async function atualizarBancoHoras(
|
|||||||
const ajustesIds: Array<Id<'ajustesBancoHoras'>> = [];
|
const ajustesIds: Array<Id<'ajustesBancoHoras'>> = [];
|
||||||
|
|
||||||
// Aplicar ajustes automáticos se houver atestado, licença ou ausência
|
// Aplicar ajustes automáticos se houver atestado, licença ou ausência
|
||||||
if (atestadoInfo.temAtestado) {
|
// IMPORTANTE: Verificar se o registro ainda existe antes de criar ajuste
|
||||||
tipoDia = 'atestado';
|
if (atestadoInfo.temAtestado && atestadoInfo.atestadoId) {
|
||||||
motivoAbono = atestadoInfo.motivo;
|
// Verificar se o atestado ainda existe no banco
|
||||||
if (atestadoInfo.atestadoId) {
|
const atestado = await ctx.db.get(atestadoInfo.atestadoId as Id<'atestados'>);
|
||||||
|
if (atestado) {
|
||||||
|
tipoDia = 'atestado';
|
||||||
|
motivoAbono = atestadoInfo.motivo;
|
||||||
const ajusteId = await aplicarAjusteAutomatico(
|
const ajusteId = await aplicarAjusteAutomatico(
|
||||||
ctx,
|
ctx,
|
||||||
funcionarioId,
|
funcionarioId,
|
||||||
@@ -1708,10 +1759,12 @@ async function atualizarBancoHoras(
|
|||||||
);
|
);
|
||||||
ajustesIds.push(ajusteId);
|
ajustesIds.push(ajusteId);
|
||||||
}
|
}
|
||||||
} else if (licencaInfo.temLicenca) {
|
} else if (licencaInfo.temLicenca && licencaInfo.licencaId) {
|
||||||
tipoDia = 'licenca';
|
// Verificar se a licença ainda existe no banco
|
||||||
motivoAbono = licencaInfo.motivo;
|
const licenca = await ctx.db.get(licencaInfo.licencaId as Id<'licencas'>);
|
||||||
if (licencaInfo.licencaId) {
|
if (licenca) {
|
||||||
|
tipoDia = 'licenca';
|
||||||
|
motivoAbono = licencaInfo.motivo;
|
||||||
const ajusteId = await aplicarAjusteAutomatico(
|
const ajusteId = await aplicarAjusteAutomatico(
|
||||||
ctx,
|
ctx,
|
||||||
funcionarioId,
|
funcionarioId,
|
||||||
@@ -1723,10 +1776,12 @@ async function atualizarBancoHoras(
|
|||||||
);
|
);
|
||||||
ajustesIds.push(ajusteId);
|
ajustesIds.push(ajusteId);
|
||||||
}
|
}
|
||||||
} else if (ausenciaInfo.temAusencia) {
|
} else if (ausenciaInfo.temAusencia && ausenciaInfo.ausenciaId) {
|
||||||
tipoDia = 'ausencia';
|
// Verificar se a ausência ainda existe no banco e está aprovada
|
||||||
motivoAbono = ausenciaInfo.motivo;
|
const ausencia = await ctx.db.get(ausenciaInfo.ausenciaId as Id<'solicitacoesAusencias'>);
|
||||||
if (ausenciaInfo.ausenciaId) {
|
if (ausencia && ausencia.status === 'aprovado') {
|
||||||
|
tipoDia = 'ausencia';
|
||||||
|
motivoAbono = ausenciaInfo.motivo;
|
||||||
const ajusteId = await aplicarAjusteAutomatico(
|
const ajusteId = await aplicarAjusteAutomatico(
|
||||||
ctx,
|
ctx,
|
||||||
funcionarioId,
|
funcionarioId,
|
||||||
@@ -4238,6 +4293,31 @@ export const recalcularBancoHoras = mutation({
|
|||||||
/**
|
/**
|
||||||
* Mutation interna para recalcular banco de horas de uma data específica
|
* Mutation interna para recalcular banco de horas de uma data específica
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Internal mutation para remover ajustes automáticos relacionados a um registro excluído
|
||||||
|
*/
|
||||||
|
export const removerAjustesAutomaticosInternal = internalMutation({
|
||||||
|
args: {
|
||||||
|
funcionarioId: v.id('funcionarios'),
|
||||||
|
motivoTipo: v.union(v.literal('atestado'), v.literal('licenca'), v.literal('ausencia')),
|
||||||
|
motivoId: v.string(),
|
||||||
|
dataInicio: v.string(),
|
||||||
|
dataFim: v.string()
|
||||||
|
},
|
||||||
|
returns: v.null(),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
await removerAjustesAutomaticos(
|
||||||
|
ctx,
|
||||||
|
args.funcionarioId,
|
||||||
|
args.motivoTipo,
|
||||||
|
args.motivoId,
|
||||||
|
args.dataInicio,
|
||||||
|
args.dataFim
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const recalcularBancoHorasData = internalMutation({
|
export const recalcularBancoHorasData = internalMutation({
|
||||||
args: {
|
args: {
|
||||||
funcionarioId: v.id('funcionarios'),
|
funcionarioId: v.id('funcionarios'),
|
||||||
|
|||||||
Reference in New Issue
Block a user