feat: add period adjustment fields to point processing and PDF generation, enhancing data capture and display for time adjustments

This commit is contained in:
2025-12-24 10:41:12 -03:00
parent bdc0afccb8
commit c7a64eb116
6 changed files with 182 additions and 21 deletions

View File

@@ -541,20 +541,48 @@ function gerarSecaoAjustesPDF(doc: jsPDF, yPosition: number, dias: DiaFichaPonto
doc.setTextColor(0, 0, 0); doc.setTextColor(0, 0, 0);
yPosition += 10; 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) => [ const ajustesData = todosAjustes.map((ajuste) => [
formatarDataDDMMAAAA(ajuste.data), formatarDataDDMMAAAA(ajuste.data),
ajuste.tipo === 'abonar' ? 'Abonar' : ajuste.tipo === 'descontar' ? 'Descontar' : 'Compensar', ajuste.tipo === 'abonar' ? 'Abonar' : ajuste.tipo === 'descontar' ? 'Descontar' : 'Compensar',
formatarMinutos(ajuste.valorMinutos), formatarMinutos(ajuste.valorMinutos),
formatarPeriodoAjuste(ajuste),
ajuste.motivoDescricao || '-' ajuste.motivoDescricao || '-'
]); ]);
autoTable(doc, { autoTable(doc, {
startY: yPosition, startY: yPosition,
head: [['Data', 'Tipo', 'Valor', 'Motivo']], head: [['Data Aplicação', 'Tipo', 'Valor', 'Período', 'Motivo']],
body: ajustesData, body: ajustesData,
theme: 'grid', theme: 'grid',
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' }, 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 & { type JsPDFWithAutoTable = jsPDF & {

View File

@@ -598,7 +598,13 @@ export async function processarDadosFichaPonto(
tipo: a.tipo, tipo: a.tipo,
valorMinutos: a.valorMinutos, valorMinutos: a.valorMinutos,
motivoDescricao: a.motivoDescricao, 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) => ({ inconsistencias: inconsistenciasDia.map((i) => ({
_id: i._id, _id: i._id,

View File

@@ -60,6 +60,13 @@ export interface DiaFichaPonto {
valorMinutos: number; valorMinutos: number;
motivoDescricao?: string; motivoDescricao?: string;
gestorId?: Id<'usuarios'>; 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<{ inconsistencias: Array<{
_id: Id<'inconsistenciasBancoHoras'>; _id: Id<'inconsistenciasBancoHoras'>;

View File

@@ -264,6 +264,10 @@
return; 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 { try {
await client.mutation(api.pontos.ajustarBancoHoras, { await client.mutation(api.pontos.ajustarBancoHoras, {
funcionarioId: funcionarioSelecionado, funcionarioId: funcionarioSelecionado,
@@ -271,6 +275,13 @@
periodoDias: dias, periodoDias: dias,
periodoHoras: horas, periodoHoras: horas,
periodoMinutos: minutos, periodoMinutos: minutos,
dataAplicacao: dataInicioAjuste, // Data escolhida pelo usuário
dataInicio: dataInicioAjuste,
horaInicio,
minutoInicio,
dataFim: dataFimAjuste,
horaFim,
minutoFim,
motivoId: motivoId || undefined, motivoId: motivoId || undefined,
motivoTipo: motivoTipo || undefined, motivoTipo: motivoTipo || undefined,
motivoDescricao: motivoDescricao || undefined, motivoDescricao: motivoDescricao || undefined,
@@ -910,7 +921,11 @@
{#each homologacoes as homologacao (homologacao._id)} {#each homologacoes as homologacao (homologacao._id)}
<tr> <tr>
<td> <td>
{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}
</td> </td>
{#if !funcionarioSelecionado} {#if !funcionarioSelecionado}
<td> <td>
@@ -958,9 +973,16 @@
</span> </span>
</div> </div>
{:else if homologacao.ajusteMinutos} {:else if homologacao.ajusteMinutos}
<div class="text-sm"> <div class="text-sm space-y-1">
{homologacao.periodoDias || 0}d {homologacao.periodoHoras || 0}h <div>
{homologacao.periodoMinutos || 0}min {homologacao.periodoDias || 0}d {homologacao.periodoHoras || 0}h
{homologacao.periodoMinutos || 0}min
</div>
{#if homologacao.periodoAjuste?.dataInicio && homologacao.periodoAjuste?.horaInicio !== undefined && homologacao.periodoAjuste?.minutoInicio !== undefined && homologacao.periodoAjuste?.dataFim && homologacao.periodoAjuste?.horaFim !== undefined && homologacao.periodoAjuste?.minutoFim !== undefined}
<div class="text-xs text-base-content/60">
{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)}
</div>
{/if}
</div> </div>
{/if} {/if}
</td> </td>
@@ -1026,13 +1048,18 @@
<div class="grid grid-cols-2 gap-4 text-sm"> <div class="grid grid-cols-2 gap-4 text-sm">
<div> <div>
<span class="font-medium">Data:&nbsp;</span> <span class="font-medium">Data:&nbsp;</span>
{new Date(homologacaoSelecionada.criadoEm).toLocaleDateString('pt-BR', { {#if homologacaoSelecionada.dataAplicacaoAjuste}
day: '2-digit', {new Date(homologacaoSelecionada.dataAplicacaoAjuste + 'T00:00:00').toLocaleDateString('pt-BR')}
month: '2-digit', <span class="text-base-content/60 text-xs"> (Aplicado em)</span>
year: 'numeric', {:else}
hour: '2-digit', {new Date(homologacaoSelecionada.criadoEm).toLocaleDateString('pt-BR', {
minute: '2-digit' day: '2-digit',
})} month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
{/if}
</div> </div>
<div> <div>
<span class="font-medium">Funcionário:</span> <span class="font-medium">Funcionário:</span>
@@ -1124,6 +1151,16 @@
{homologacaoSelecionada.periodoHoras || 0}h {homologacaoSelecionada.periodoHoras || 0}h
{homologacaoSelecionada.periodoMinutos || 0}min {homologacaoSelecionada.periodoMinutos || 0}min
</div> </div>
{#if homologacaoSelecionada.periodoAjuste?.dataInicio && homologacaoSelecionada.periodoAjuste?.horaInicio !== undefined && homologacaoSelecionada.periodoAjuste?.minutoInicio !== undefined && homologacaoSelecionada.periodoAjuste?.dataFim && homologacaoSelecionada.periodoAjuste?.horaFim !== undefined && homologacaoSelecionada.periodoAjuste?.minutoFim !== undefined}
<div>
<span class="font-medium">Data/Hora Início:&nbsp;</span>
{new Date(homologacaoSelecionada.periodoAjuste.dataInicio + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacaoSelecionada.periodoAjuste.horaInicio, homologacaoSelecionada.periodoAjuste.minutoInicio)}
</div>
<div>
<span class="font-medium">Data/Hora Fim:&nbsp;</span>
{new Date(homologacaoSelecionada.periodoAjuste.dataFim + 'T00:00:00').toLocaleDateString('pt-BR')} {formatarHoraPonto(homologacaoSelecionada.periodoAjuste.horaFim, homologacaoSelecionada.periodoAjuste.minutoFim)}
</div>
{/if}
{#if homologacaoSelecionada.ajusteMinutos} {#if homologacaoSelecionada.ajusteMinutos}
<div> <div>
<span class="font-medium">Ajuste Total:&nbsp;</span> <span class="font-medium">Ajuste Total:&nbsp;</span>

View File

@@ -2618,6 +2618,14 @@ export const ajustarBancoHoras = mutation({
periodoDias: v.number(), periodoDias: v.number(),
periodoHoras: v.number(), periodoHoras: v.number(),
periodoMinutos: 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()), motivoId: v.optional(v.string()),
motivoTipo: v.optional(v.string()), motivoTipo: v.optional(v.string()),
motivoDescricao: v.optional(v.string()), motivoDescricao: v.optional(v.string()),
@@ -2655,8 +2663,8 @@ export const ajustarBancoHoras = mutation({
ajusteFinal = -ajusteMinutos; ajusteFinal = -ajusteMinutos;
} }
// Buscar banco de horas mais recente ou criar um registro de ajuste // Usar a data de aplicação fornecida pelo usuário
const hoje = new Date().toISOString().split('T')[0]!; const dataAplicacao = args.dataAplicacao;
// Criar registro de ajuste na nova tabela // Criar registro de ajuste na nova tabela
const ajusteId = await ctx.db.insert('ajustesBancoHoras', { const ajusteId = await ctx.db.insert('ajustesBancoHoras', {
@@ -2666,7 +2674,13 @@ export const ajustarBancoHoras = mutation({
motivoId: args.motivoId, motivoId: args.motivoId,
motivoDescricao: args.motivoDescricao || `Ajuste ${args.tipoAjuste}`, motivoDescricao: args.motivoDescricao || `Ajuste ${args.tipoAjuste}`,
valorMinutos: ajusteFinal, 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, gestorId: usuario._id,
observacoes: args.observacoes, observacoes: args.observacoes,
aplicado: false, aplicado: false,
@@ -2676,7 +2690,7 @@ export const ajustarBancoHoras = mutation({
const bancoHorasAtual = await ctx.db const bancoHorasAtual = await ctx.db
.query('bancoHoras') .query('bancoHoras')
.withIndex('by_funcionario_data', (q) => .withIndex('by_funcionario_data', (q) =>
q.eq('funcionarioId', args.funcionarioId).eq('data', hoje) q.eq('funcionarioId', args.funcionarioId).eq('data', dataAplicacao)
) )
.first(); .first();
@@ -2708,7 +2722,7 @@ export const ajustarBancoHoras = mutation({
await ctx.db.insert('bancoHoras', { await ctx.db.insert('bancoHoras', {
funcionarioId: args.funcionarioId, funcionarioId: args.funcionarioId,
data: hoje, data: dataAplicacao,
cargaHorariaDiaria, cargaHorariaDiaria,
horasTrabalhadas: 0, horasTrabalhadas: 0,
saldoMinutos: ajusteFinal, saldoMinutos: ajusteFinal,
@@ -2727,7 +2741,7 @@ export const ajustarBancoHoras = mutation({
}); });
// Recalcular banco de horas mensal após ajuste // 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 // Verificar se estamos ajustando um mês passado
const hojeDate = new Date(); const hojeDate = new Date();
@@ -2738,6 +2752,7 @@ export const ajustarBancoHoras = mutation({
await calcularBancoHorasMensal(ctx, args.funcionarioId, mes, estaAjustandoMesPassado); await calcularBancoHorasMensal(ctx, args.funcionarioId, mes, estaAjustandoMesPassado);
// Criar registro de homologação (mantido para compatibilidade) // Criar registro de homologação (mantido para compatibilidade)
// Armazenar o ajusteId para facilitar a busca posterior
const homologacaoId = await ctx.db.insert('homologacoesPonto', { const homologacaoId = await ctx.db.insert('homologacoesPonto', {
funcionarioId: args.funcionarioId, funcionarioId: args.funcionarioId,
gestorId: usuario._id, gestorId: usuario._id,
@@ -2753,6 +2768,9 @@ export const ajustarBancoHoras = mutation({
criadoEm: Date.now() 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 }; 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 { return {
...h, ...h,
funcionario: funcionario funcionario: funcionario
@@ -2839,7 +2913,9 @@ export const listarHomologacoes = query({
data: registro.data, data: registro.data,
tipo: registro.tipo tipo: registro.tipo
} }
: null : null,
dataAplicacaoAjuste,
periodoAjuste
}; };
}) })
); );

View File

@@ -368,6 +368,13 @@ export const pontoTables = {
valorMinutos: v.number(), // Valor em minutos (positivo para abonar, negativo para descontar) valorMinutos: v.number(), // Valor em minutos (positivo para abonar, negativo para descontar)
// Data de aplicação // Data de aplicação
dataAplicacao: v.string(), // YYYY-MM-DD 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) // Gestor responsável (null se automático)
gestorId: v.optional(v.id('usuarios')), gestorId: v.optional(v.id('usuarios')),
// Observações // Observações