diff --git a/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts b/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts index a87a0f0..48066c6 100644 --- a/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts +++ b/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts @@ -541,20 +541,48 @@ function gerarSecaoAjustesPDF(doc: jsPDF, yPosition: number, dias: DiaFichaPonto doc.setTextColor(0, 0, 0); yPosition += 10; + // Função auxiliar para formatar período do ajuste + const formatarPeriodoAjuste = (ajuste: (typeof todosAjustes)[number]): string => { + if ( + ajuste.dataInicio && + ajuste.horaInicio !== undefined && + ajuste.minutoInicio !== undefined && + ajuste.dataFim && + ajuste.horaFim !== undefined && + ajuste.minutoFim !== undefined + ) { + const inicioStr = `${formatarDataDDMMAAAA(ajuste.dataInicio)} ${formatarHoraPonto( + ajuste.horaInicio, + ajuste.minutoInicio + )}`; + const fimStr = `${formatarDataDDMMAAAA(ajuste.dataFim)} ${formatarHoraPonto( + ajuste.horaFim, + ajuste.minutoFim + )}`; + return `${inicioStr} a ${fimStr}`; + } + // Fallback para ajustes antigos sem período + return formatarDataDDMMAAAA(ajuste.data); + }; + const ajustesData = todosAjustes.map((ajuste) => [ formatarDataDDMMAAAA(ajuste.data), ajuste.tipo === 'abonar' ? 'Abonar' : ajuste.tipo === 'descontar' ? 'Descontar' : 'Compensar', formatarMinutos(ajuste.valorMinutos), + formatarPeriodoAjuste(ajuste), ajuste.motivoDescricao || '-' ]); autoTable(doc, { startY: yPosition, - head: [['Data', 'Tipo', 'Valor', 'Motivo']], + head: [['Data Aplicação', 'Tipo', 'Valor', 'Período', 'Motivo']], body: ajustesData, theme: 'grid', headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' }, - styles: { fontSize: 9 } + styles: { fontSize: 9 }, + columnStyles: { + 3: { cellWidth: 'auto', minCellWidth: 60 } // Coluna de período com largura maior + } }); type JsPDFWithAutoTable = jsPDF & { diff --git a/apps/web/src/lib/utils/ponto/processamento.ts b/apps/web/src/lib/utils/ponto/processamento.ts index 8f40993..09daea2 100644 --- a/apps/web/src/lib/utils/ponto/processamento.ts +++ b/apps/web/src/lib/utils/ponto/processamento.ts @@ -598,7 +598,13 @@ export async function processarDadosFichaPonto( tipo: a.tipo, valorMinutos: a.valorMinutos, motivoDescricao: a.motivoDescricao, - gestorId: a.gestorId + gestorId: a.gestorId, + dataInicio: a.dataInicio, + horaInicio: a.horaInicio, + minutoInicio: a.minutoInicio, + dataFim: a.dataFim, + horaFim: a.horaFim, + minutoFim: a.minutoFim })), inconsistencias: inconsistenciasDia.map((i) => ({ _id: i._id, diff --git a/apps/web/src/lib/utils/ponto/tipos.ts b/apps/web/src/lib/utils/ponto/tipos.ts index a70e86d..5895911 100644 --- a/apps/web/src/lib/utils/ponto/tipos.ts +++ b/apps/web/src/lib/utils/ponto/tipos.ts @@ -60,6 +60,13 @@ export interface DiaFichaPonto { valorMinutos: number; motivoDescricao?: string; gestorId?: Id<'usuarios'>; + // Período do ajuste + dataInicio?: string; // YYYY-MM-DD + horaInicio?: number; // 0-23 + minutoInicio?: number; // 0-59 + dataFim?: string; // YYYY-MM-DD + horaFim?: number; // 0-23 + minutoFim?: number; // 0-59 }>; inconsistencias: Array<{ _id: Id<'inconsistenciasBancoHoras'>; diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/controle-ponto/homologacao/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/controle-ponto/homologacao/+page.svelte index 40ca808..812df10 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/controle-ponto/homologacao/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/controle-ponto/homologacao/+page.svelte @@ -264,6 +264,10 @@ return; } + // Converter hora formato HH:mm para hora e minuto + const { hora: horaInicio, minuto: minutoInicio } = timeParaHoraMinuto(horaInicioAjuste); + const { hora: horaFim, minuto: minutoFim } = timeParaHoraMinuto(horaFimAjuste); + try { await client.mutation(api.pontos.ajustarBancoHoras, { funcionarioId: funcionarioSelecionado, @@ -271,6 +275,13 @@ periodoDias: dias, periodoHoras: horas, periodoMinutos: minutos, + dataAplicacao: dataInicioAjuste, // Data escolhida pelo usuário + dataInicio: dataInicioAjuste, + horaInicio, + minutoInicio, + dataFim: dataFimAjuste, + horaFim, + minutoFim, motivoId: motivoId || undefined, motivoTipo: motivoTipo || undefined, motivoDescricao: motivoDescricao || undefined, @@ -910,7 +921,11 @@ {#each homologacoes as homologacao (homologacao._id)} - {new Date(homologacao.criadoEm).toLocaleDateString('pt-BR')} + {#if homologacao.dataAplicacaoAjuste} + {new Date(homologacao.dataAplicacaoAjuste + 'T00:00:00').toLocaleDateString('pt-BR')} + {:else} + {new Date(homologacao.criadoEm).toLocaleDateString('pt-BR')} + {/if} {#if !funcionarioSelecionado} @@ -958,9 +973,16 @@ {:else if homologacao.ajusteMinutos} -
- {homologacao.periodoDias || 0}d {homologacao.periodoHoras || 0}h - {homologacao.periodoMinutos || 0}min +
+
+ {homologacao.periodoDias || 0}d {homologacao.periodoHoras || 0}h + {homologacao.periodoMinutos || 0}min +
+ {#if homologacao.periodoAjuste?.dataInicio && homologacao.periodoAjuste?.horaInicio !== undefined && homologacao.periodoAjuste?.minutoInicio !== undefined && homologacao.periodoAjuste?.dataFim && homologacao.periodoAjuste?.horaFim !== undefined && homologacao.periodoAjuste?.minutoFim !== undefined} +
+ {new Date(homologacao.periodoAjuste.dataInicio + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacao.periodoAjuste.horaInicio, homologacao.periodoAjuste.minutoInicio)} a {new Date(homologacao.periodoAjuste.dataFim + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacao.periodoAjuste.horaFim, homologacao.periodoAjuste.minutoFim)} +
+ {/if}
{/if} @@ -1026,13 +1048,18 @@
Data:  - {new Date(homologacaoSelecionada.criadoEm).toLocaleDateString('pt-BR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - })} + {#if homologacaoSelecionada.dataAplicacaoAjuste} + {new Date(homologacaoSelecionada.dataAplicacaoAjuste + 'T00:00:00').toLocaleDateString('pt-BR')} + (Aplicado em) + {:else} + {new Date(homologacaoSelecionada.criadoEm).toLocaleDateString('pt-BR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + })} + {/if}
Funcionário: @@ -1124,6 +1151,16 @@ {homologacaoSelecionada.periodoHoras || 0}h {homologacaoSelecionada.periodoMinutos || 0}min
+ {#if homologacaoSelecionada.periodoAjuste?.dataInicio && homologacaoSelecionada.periodoAjuste?.horaInicio !== undefined && homologacaoSelecionada.periodoAjuste?.minutoInicio !== undefined && homologacaoSelecionada.periodoAjuste?.dataFim && homologacaoSelecionada.periodoAjuste?.horaFim !== undefined && homologacaoSelecionada.periodoAjuste?.minutoFim !== undefined} +
+ Data/Hora Início:  + {new Date(homologacaoSelecionada.periodoAjuste.dataInicio + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacaoSelecionada.periodoAjuste.horaInicio, homologacaoSelecionada.periodoAjuste.minutoInicio)} +
+
+ Data/Hora Fim:  + {new Date(homologacaoSelecionada.periodoAjuste.dataFim + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacaoSelecionada.periodoAjuste.horaFim, homologacaoSelecionada.periodoAjuste.minutoFim)} +
+ {/if} {#if homologacaoSelecionada.ajusteMinutos}
Ajuste Total:  diff --git a/packages/backend/convex/pontos.ts b/packages/backend/convex/pontos.ts index 21f15e7..057a930 100644 --- a/packages/backend/convex/pontos.ts +++ b/packages/backend/convex/pontos.ts @@ -2618,6 +2618,14 @@ export const ajustarBancoHoras = mutation({ periodoDias: v.number(), periodoHoras: v.number(), periodoMinutos: v.number(), + dataAplicacao: v.string(), // YYYY-MM-DD - Data em que o ajuste deve ser aplicado + // Período do ajuste (data/hora início e fim) + dataInicio: v.optional(v.string()), // YYYY-MM-DD + horaInicio: v.optional(v.number()), // 0-23 + minutoInicio: v.optional(v.number()), // 0-59 + dataFim: v.optional(v.string()), // YYYY-MM-DD + horaFim: v.optional(v.number()), // 0-23 + minutoFim: v.optional(v.number()), // 0-59 motivoId: v.optional(v.string()), motivoTipo: v.optional(v.string()), motivoDescricao: v.optional(v.string()), @@ -2655,8 +2663,8 @@ export const ajustarBancoHoras = mutation({ ajusteFinal = -ajusteMinutos; } - // Buscar banco de horas mais recente ou criar um registro de ajuste - const hoje = new Date().toISOString().split('T')[0]!; + // Usar a data de aplicação fornecida pelo usuário + const dataAplicacao = args.dataAplicacao; // Criar registro de ajuste na nova tabela const ajusteId = await ctx.db.insert('ajustesBancoHoras', { @@ -2666,7 +2674,13 @@ export const ajustarBancoHoras = mutation({ motivoId: args.motivoId, motivoDescricao: args.motivoDescricao || `Ajuste ${args.tipoAjuste}`, valorMinutos: ajusteFinal, - dataAplicacao: hoje, + dataAplicacao: dataAplicacao, + dataInicio: args.dataInicio, + horaInicio: args.horaInicio, + minutoInicio: args.minutoInicio, + dataFim: args.dataFim, + horaFim: args.horaFim, + minutoFim: args.minutoFim, gestorId: usuario._id, observacoes: args.observacoes, aplicado: false, @@ -2676,7 +2690,7 @@ export const ajustarBancoHoras = mutation({ const bancoHorasAtual = await ctx.db .query('bancoHoras') .withIndex('by_funcionario_data', (q) => - q.eq('funcionarioId', args.funcionarioId).eq('data', hoje) + q.eq('funcionarioId', args.funcionarioId).eq('data', dataAplicacao) ) .first(); @@ -2708,7 +2722,7 @@ export const ajustarBancoHoras = mutation({ await ctx.db.insert('bancoHoras', { funcionarioId: args.funcionarioId, - data: hoje, + data: dataAplicacao, cargaHorariaDiaria, horasTrabalhadas: 0, saldoMinutos: ajusteFinal, @@ -2727,7 +2741,7 @@ export const ajustarBancoHoras = mutation({ }); // Recalcular banco de horas mensal após ajuste - const mes = hoje.substring(0, 7); // YYYY-MM + const mes = dataAplicacao.substring(0, 7); // YYYY-MM // Verificar se estamos ajustando um mês passado const hojeDate = new Date(); @@ -2738,6 +2752,7 @@ export const ajustarBancoHoras = mutation({ await calcularBancoHorasMensal(ctx, args.funcionarioId, mes, estaAjustandoMesPassado); // Criar registro de homologação (mantido para compatibilidade) + // Armazenar o ajusteId para facilitar a busca posterior const homologacaoId = await ctx.db.insert('homologacoesPonto', { funcionarioId: args.funcionarioId, gestorId: usuario._id, @@ -2753,6 +2768,9 @@ export const ajustarBancoHoras = mutation({ criadoEm: Date.now() }); + // Armazenar ajusteId na homologação usando um campo customizado ou buscar depois + // Por enquanto, vamos buscar o ajuste no listarHomologacoes usando os critérios + return { success: true, homologacaoId, ajusteId, ajusteMinutos: ajusteFinal }; } }); @@ -2820,6 +2838,62 @@ export const listarHomologacoes = query({ } } + // Buscar dataAplicacao e período do ajuste se for um ajuste de banco de horas + let dataAplicacaoAjuste: string | null = null; + let periodoAjuste: { + dataInicio?: string; + horaInicio?: number; + minutoInicio?: number; + dataFim?: string; + horaFim?: number; + minutoFim?: number; + } | null = null; + if (h.tipoAjuste && h.ajusteMinutos !== undefined) { + // Buscar ajustes criados próximo ao tempo da homologação (dentro de 5 minutos antes) + // O ajuste é criado logo antes da homologação em ajustarBancoHoras + const tempoLimite = h.criadoEm - 5 * 60 * 1000; // 5 minutos antes + + // Buscar todos os ajustes do funcionário com os mesmos critérios + // e criados próximo ao tempo da homologação + const ajustes = await ctx.db + .query('ajustesBancoHoras') + .withIndex('by_funcionario', (q) => q.eq('funcionarioId', h.funcionarioId)) + .filter((q) => + q.and( + q.eq(q.field('motivoTipo'), 'manual'), + q.eq(q.field('tipo'), h.tipoAjuste), + q.eq(q.field('valorMinutos'), h.ajusteMinutos), + q.eq(q.field('gestorId'), h.gestorId), + q.gte(q.field('criadoEm'), tempoLimite), + q.lte(q.field('criadoEm'), h.criadoEm) + ) + ) + .collect(); + + // Se encontrou ajuste(s), encontrar o mais próximo em tempo à homologação + if (ajustes.length > 0) { + // Encontrar o ajuste com timestamp mais próximo ao da homologação + let ajusteMaisProximo = ajustes[0]!; + let menorDiferenca = Math.abs(ajustes[0]!.criadoEm - h.criadoEm); + for (const ajusteCandidato of ajustes) { + const diferenca = Math.abs(ajusteCandidato.criadoEm - h.criadoEm); + if (diferenca < menorDiferenca) { + menorDiferenca = diferenca; + ajusteMaisProximo = ajusteCandidato; + } + } + dataAplicacaoAjuste = ajusteMaisProximo.dataAplicacao; + periodoAjuste = { + dataInicio: ajusteMaisProximo.dataInicio, + horaInicio: ajusteMaisProximo.horaInicio, + minutoInicio: ajusteMaisProximo.minutoInicio, + dataFim: ajusteMaisProximo.dataFim, + horaFim: ajusteMaisProximo.horaFim, + minutoFim: ajusteMaisProximo.minutoFim + }; + } + } + return { ...h, funcionario: funcionario @@ -2839,7 +2913,9 @@ export const listarHomologacoes = query({ data: registro.data, tipo: registro.tipo } - : null + : null, + dataAplicacaoAjuste, + periodoAjuste }; }) ); diff --git a/packages/backend/convex/tables/ponto.ts b/packages/backend/convex/tables/ponto.ts index d206f32..99f6e1d 100644 --- a/packages/backend/convex/tables/ponto.ts +++ b/packages/backend/convex/tables/ponto.ts @@ -368,6 +368,13 @@ export const pontoTables = { valorMinutos: v.number(), // Valor em minutos (positivo para abonar, negativo para descontar) // Data de aplicação dataAplicacao: v.string(), // YYYY-MM-DD + // Período do ajuste (data/hora início e fim) + dataInicio: v.optional(v.string()), // YYYY-MM-DD + horaInicio: v.optional(v.number()), // 0-23 + minutoInicio: v.optional(v.number()), // 0-59 + dataFim: v.optional(v.string()), // YYYY-MM-DD + horaFim: v.optional(v.number()), // 0-23 + minutoFim: v.optional(v.number()), // 0-59 // Gestor responsável (null se automático) gestorId: v.optional(v.id('usuarios')), // Observações