From 73da99510957c73c49f689c12b1a280cc0d94654 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Tue, 9 Dec 2025 15:06:36 -0300 Subject: [PATCH] feat: add functionality to manage employee status during point registration, preventing point logging for employees on vacation or leave; enhance UI alerts to inform users of their current status --- .../lib/components/ponto/RegistroPonto.svelte | 55 ++++++++++++++++- .../atestados-licencas/+page.svelte | 60 +++++++++++++------ packages/backend/convex/ferias.ts | 13 ++++ packages/backend/convex/pontos.ts | 11 +++- 4 files changed, 119 insertions(+), 20 deletions(-) diff --git a/apps/web/src/lib/components/ponto/RegistroPonto.svelte b/apps/web/src/lib/components/ponto/RegistroPonto.svelte index 6d23edc..f8b1feb 100644 --- a/apps/web/src/lib/components/ponto/RegistroPonto.svelte +++ b/apps/web/src/lib/components/ponto/RegistroPonto.svelte @@ -62,6 +62,12 @@ funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip' ); + // Query para obter status atual do funcionário (férias/licença) + const funcionarioStatusQuery = useQuery( + api.funcionarios.getCurrent, + currentUser?.data ? {} : 'skip' + ); + // Estados let mostrandoWebcam = $state(false); let registrando = $state(false); @@ -86,6 +92,12 @@ const registrosHoje = $derived(registrosHojeQuery?.data || []); const config = $derived(configQuery?.data); + // Status de férias/licença do funcionário + const funcionarioStatus = $derived(funcionarioStatusQuery?.data); + const statusFerias = $derived(funcionarioStatus?.statusFerias ?? 'ativo'); + const emFerias = $derived(statusFerias === 'em_ferias'); + const emLicenca = $derived(statusFerias === 'em_licenca'); + const proximoTipo = $derived.by(() => { if (registrosHoje.length === 0) { return 'entrada'; @@ -209,6 +221,16 @@ return; } + // Verificar se funcionário está em férias ou licença + if (emFerias || emLicenca) { + mensagemErroModal = 'Registro de ponto não permitido'; + detalhesErroModal = emFerias + ? 'Seu status atual é "Em Férias". Durante o período de férias não é permitido registrar ponto.' + : 'Seu status atual é "Em Licença". Durante o período de licença não é permitido registrar ponto.'; + mostrarModalErro = true; + return; + } + // Verificar permissões antes de registrar const permissoes = await verificarPermissoes(); if (!permissoes.localizacao || !permissoes.webcam) { @@ -824,6 +846,8 @@ !coletandoInfo && config !== undefined && !estaDispensado && + !emFerias && + !emLicenca && temFuncionarioAssociado ); }); @@ -1051,6 +1075,25 @@ {/if} + + {#if temFuncionarioAssociado && (emFerias || emLicenca)} +
+ +
+

Status do Funcionário

+
+ {#if emFerias} + Seu status atual é Em Férias. Durante o período de férias não é permitido + registrar ponto. + {:else} + Seu status atual é Em Licença. Durante o período de licença não é permitido + registrar ponto. + {/if} +
+
+
+ {/if} +
{#if registrando} @@ -1098,6 +1145,12 @@ {:else if estaDispensado} Registro Indisponível + {:else if emFerias} + + Em Férias + {:else if emLicenca} + + Em Licença {:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'} Registrar Entrada diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte index 32f9cdc..8b250e3 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte @@ -132,13 +132,37 @@ }); // Funções auxiliares + /** + * Converte string de data no formato "YYYY-MM-DD" (com ou sem parte de hora) + * para um objeto Date no fuso local, evitando o problema de "um dia a menos" + * causado por `new Date('YYYY-MM-DD')` em timezones como UTC-3. + */ + function parseDataLocal(data: string): Date { + if (!data) return new Date(NaN); + + // Garante que só a parte da data seja usada (descarta "T..."). + const apenasData = data.substring(0, 10); + const [anoStr, mesStr, diaStr] = apenasData.split('-'); + const ano = Number(anoStr); + const mes = Number(mesStr); + const dia = Number(diaStr); + + if (!ano || !mes || !dia) { + return new Date(NaN); + } + + return new Date(ano, mes - 1, dia); + } + function formatarData(data: string) { - return new Date(data).toLocaleDateString('pt-BR'); + const date = parseDataLocal(data); + if (isNaN(date.getTime())) return ''; + return date.toLocaleDateString('pt-BR'); } function calcularDias(dataInicio: string, dataFim: string): number { - const inicio = new Date(dataInicio); - const fim = new Date(dataFim); + const inicio = parseDataLocal(dataInicio); + const fim = parseDataLocal(dataFim); const diffTime = Math.abs(fim.getTime() - inicio.getTime()); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; } @@ -508,7 +532,7 @@ !licencaMaternidade.ehProrrogacao && !licencaMaternidade.dataFim ) { - const inicio = new Date(licencaMaternidade.dataInicio); + const inicio = parseDataLocal(licencaMaternidade.dataInicio); if (!isNaN(inicio.getTime())) { inicio.setDate(inicio.getDate() + 120); // 120 dias licencaMaternidade.dataFim = inicio.toISOString().split('T')[0]; @@ -522,7 +546,7 @@ licencaPaternidade.dataInicio && !licencaPaternidade.dataFim ) { - const inicio = new Date(licencaPaternidade.dataInicio); + const inicio = parseDataLocal(licencaPaternidade.dataInicio); if (!isNaN(inicio.getTime())) { inicio.setDate(inicio.getDate() + 20); // 20 dias licencaPaternidade.dataFim = inicio.toISOString().split('T')[0]; @@ -574,11 +598,11 @@ // Filtro por período if (filtroDataInicio && filtroDataFim) { - const inicio = new Date(filtroDataInicio); - const fim = new Date(filtroDataFim); + const inicio = parseDataLocal(filtroDataInicio); + const fim = parseDataLocal(filtroDataFim); atestados = atestados.filter((a) => { - const aInicio = new Date(a.dataInicio); - const aFim = new Date(a.dataFim); + const aInicio = parseDataLocal(a.dataInicio); + const aFim = parseDataLocal(a.dataFim); return ( (aInicio >= inicio && aInicio <= fim) || (aFim >= inicio && aFim <= fim) || @@ -586,8 +610,8 @@ ); }); licencas = licencas.filter((l) => { - const lInicio = new Date(l.dataInicio); - const lFim = new Date(l.dataFim); + const lInicio = parseDataLocal(l.dataInicio); + const lFim = parseDataLocal(l.dataFim); return ( (lInicio >= inicio && lInicio <= fim) || (lFim >= inicio && lFim <= fim) || @@ -645,11 +669,11 @@ // Filtro por período if (relatorioPeriodoInicio && relatorioPeriodoFim) { - const inicio = new Date(relatorioPeriodoInicio); - const fim = new Date(relatorioPeriodoFim); + const inicio = parseDataLocal(relatorioPeriodoInicio); + const fim = parseDataLocal(relatorioPeriodoFim); atestados = atestados.filter((a) => { - const aInicio = new Date(a.dataInicio); - const aFim = new Date(a.dataFim); + const aInicio = parseDataLocal(a.dataInicio); + const aFim = parseDataLocal(a.dataFim); return ( (aInicio >= inicio && aInicio <= fim) || (aFim >= inicio && aFim <= fim) || @@ -657,8 +681,8 @@ ); }); licencas = licencas.filter((l) => { - const lInicio = new Date(l.dataInicio); - const lFim = new Date(l.dataFim); + const lInicio = parseDataLocal(l.dataInicio); + const lFim = parseDataLocal(l.dataFim); return ( (lInicio >= inicio && lInicio <= fim) || (lFim >= inicio && lFim <= fim) || @@ -759,7 +783,7 @@ doc.setFontSize(11); doc.setTextColor(0, 0, 0); doc.text( - `Período: ${format(new Date(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(new Date(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`, + `Período: ${format(parseDataLocal(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(parseDataLocal(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`, 105, yPosition, { align: 'center' } diff --git a/packages/backend/convex/ferias.ts b/packages/backend/convex/ferias.ts index 13fbdfd..2d8f314 100644 --- a/packages/backend/convex/ferias.ts +++ b/packages/backend/convex/ferias.ts @@ -705,6 +705,19 @@ export const atualizarStatus = mutation({ await ctx.db.patch(registro._id, updateData); } + // Recalcular imediatamente o status de férias/licença do funcionário + // para refletir o cancelamento (ou outra mudança) sem depender apenas do cron diário + try { + await ctx.runMutation(internal.ferias.atualizarStatusFuncionario, { + funcionarioId: registro.funcionarioId + }); + } catch (error) { + console.error( + '[ferias.atualizarStatus] Erro ao atualizar statusFerias do funcionário:', + error + ); + } + return null; } }); diff --git a/packages/backend/convex/pontos.ts b/packages/backend/convex/pontos.ts index 59a7217..1a66441 100644 --- a/packages/backend/convex/pontos.ts +++ b/packages/backend/convex/pontos.ts @@ -551,12 +551,21 @@ export const registrarPonto = mutation({ throw new Error('Usuário não possui funcionário associado'); } - // Verificar se funcionário está ativo + // Verificar se funcionário existe const funcionario = await ctx.db.get(usuario.funcionarioId); if (!funcionario) { throw new Error('Funcionário não encontrado'); } + // Bloquear registro de ponto para funcionários em férias ou licença + if (funcionario.statusFerias === 'em_ferias') { + throw new Error('Não é possível registrar ponto: funcionário está em férias.'); + } + + if (funcionario.statusFerias === 'em_licenca') { + throw new Error('Não é possível registrar ponto: funcionário está em licença.'); + } + // Obter configuração de ponto const config = await ctx.db .query('configuracaoPonto')