feat: implement comparative balance calculation for entry/exit pairs in registro-pontos page
- Added a new function `calcularSaldoComparativoPorPar` to compute comparative balances for entry and exit pairs, enhancing the accuracy of time management. - Updated the logic to handle expected and actual records, allowing for better visibility of discrepancies in worked hours. - Enhanced the table rendering to display comparative balances, improving user experience and clarity in time tracking.
This commit is contained in:
@@ -596,6 +596,149 @@
|
||||
return saldos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula saldos comparativos por par entrada/saída
|
||||
* Compara horários reais com horários esperados configurados
|
||||
* Retorna mapa com saldo trabalhado, esperado e diferença
|
||||
*/
|
||||
function calcularSaldoComparativoPorPar(
|
||||
registros: Array<{ tipo: string; hora: number; minuto: number }>,
|
||||
config: {
|
||||
horarioEntrada: string;
|
||||
horarioSaidaAlmoco: string;
|
||||
horarioRetornoAlmoco: string;
|
||||
horarioSaida: string;
|
||||
}
|
||||
): Map<number, {
|
||||
trabalhadoMinutos: number;
|
||||
trabalhadoHoras: number;
|
||||
trabalhadoMinutosResto: number;
|
||||
esperadoMinutos: number;
|
||||
esperadoHoras: number;
|
||||
esperadoMinutosResto: number;
|
||||
diferencaMinutos: number;
|
||||
diferencaHoras: number;
|
||||
diferencaMinutosResto: number;
|
||||
parIndex: number;
|
||||
tamanhoPar: number;
|
||||
}> {
|
||||
const saldos = new Map<number, {
|
||||
trabalhadoMinutos: number;
|
||||
trabalhadoHoras: number;
|
||||
trabalhadoMinutosResto: number;
|
||||
esperadoMinutos: number;
|
||||
esperadoHoras: number;
|
||||
esperadoMinutosResto: number;
|
||||
diferencaMinutos: number;
|
||||
diferencaHoras: number;
|
||||
diferencaMinutosResto: number;
|
||||
parIndex: number;
|
||||
tamanhoPar: number;
|
||||
}>();
|
||||
|
||||
if (registros.length === 0) return saldos;
|
||||
|
||||
// Parsear horários esperados da configuração
|
||||
const [horaEntradaEsperada, minutoEntradaEsperado] = config.horarioEntrada.split(':').map(Number);
|
||||
const [horaSaidaAlmocoEsperada, minutoSaidaAlmocoEsperado] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||
const [horaRetornoAlmocoEsperado, minutoRetornoAlmocoEsperado] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||
const [horaSaidaEsperada, minutoSaidaEsperado] = config.horarioSaida.split(':').map(Number);
|
||||
|
||||
// Ordenar registros por hora e minuto
|
||||
const registrosOrdenados = [...registros].sort((a, b) => {
|
||||
if (a.hora !== b.hora) {
|
||||
return a.hora - b.hora;
|
||||
}
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
|
||||
let parIndex = 0;
|
||||
let entradaAtual: { tipo: string; hora: number; minuto: number; index: number } | null = null;
|
||||
let indicesPar: number[] = [];
|
||||
|
||||
for (let i = 0; i < registrosOrdenados.length; i++) {
|
||||
const reg = registrosOrdenados[i];
|
||||
|
||||
// Identificar início de um par (entrada ou retorno_almoco)
|
||||
if (reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco') {
|
||||
// Se havia um par anterior incompleto, limpar
|
||||
if (entradaAtual && indicesPar.length > 0) {
|
||||
indicesPar = [];
|
||||
}
|
||||
entradaAtual = { ...reg, index: i };
|
||||
indicesPar = [i];
|
||||
}
|
||||
// Identificar fim de um par (saida_almoco ou saida)
|
||||
else if ((reg.tipo === 'saida_almoco' || reg.tipo === 'saida') && entradaAtual) {
|
||||
indicesPar.push(i);
|
||||
|
||||
// Calcular tempo trabalhado real (saída - entrada)
|
||||
const minutosEntradaReal = entradaAtual.hora * 60 + entradaAtual.minuto;
|
||||
const minutosSaidaReal = reg.hora * 60 + reg.minuto;
|
||||
let trabalhadoMinutos = minutosSaidaReal - minutosEntradaReal;
|
||||
if (trabalhadoMinutos < 0) {
|
||||
trabalhadoMinutos += 24 * 60;
|
||||
}
|
||||
|
||||
// Calcular tempo esperado baseado no tipo de par
|
||||
let esperadoMinutos: number;
|
||||
if (entradaAtual.tipo === 'entrada') {
|
||||
// Par 1: entrada -> saida_almoco
|
||||
const minutosEntradaEsperada = horaEntradaEsperada * 60 + minutoEntradaEsperado;
|
||||
const minutosSaidaEsperada = horaSaidaAlmocoEsperada * 60 + minutoSaidaAlmocoEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperada - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) {
|
||||
esperadoMinutos += 24 * 60;
|
||||
}
|
||||
} else {
|
||||
// Par 2: retorno_almoco -> saida
|
||||
const minutosEntradaEsperada = horaRetornoAlmocoEsperado * 60 + minutoRetornoAlmocoEsperado;
|
||||
const minutosSaidaEsperada = horaSaidaEsperada * 60 + minutoSaidaEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperada - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) {
|
||||
esperadoMinutos += 24 * 60;
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular diferença (trabalhado - esperado)
|
||||
const diferencaMinutos = trabalhadoMinutos - esperadoMinutos;
|
||||
|
||||
// Converter para horas e minutos
|
||||
const trabalhadoHoras = Math.floor(trabalhadoMinutos / 60);
|
||||
const trabalhadoMinutosResto = trabalhadoMinutos % 60;
|
||||
|
||||
const esperadoHoras = Math.floor(esperadoMinutos / 60);
|
||||
const esperadoMinutosResto = esperadoMinutos % 60;
|
||||
|
||||
const diferencaHoras = Math.floor(Math.abs(diferencaMinutos) / 60);
|
||||
const diferencaMinutosResto = Math.abs(diferencaMinutos) % 60;
|
||||
|
||||
// Associar saldo a todos os registros do par
|
||||
for (const idx of indicesPar) {
|
||||
saldos.set(idx, {
|
||||
trabalhadoMinutos,
|
||||
trabalhadoHoras,
|
||||
trabalhadoMinutosResto,
|
||||
esperadoMinutos,
|
||||
esperadoHoras,
|
||||
esperadoMinutosResto,
|
||||
diferencaMinutos,
|
||||
diferencaHoras,
|
||||
diferencaMinutosResto,
|
||||
parIndex,
|
||||
tamanhoPar: indicesPar.length
|
||||
});
|
||||
}
|
||||
|
||||
parIndex++;
|
||||
entradaAtual = null;
|
||||
indicesPar = [];
|
||||
}
|
||||
}
|
||||
|
||||
return saldos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera array de todas as datas do período selecionado
|
||||
*/
|
||||
@@ -868,9 +1011,17 @@
|
||||
|
||||
if (sections.alteracoesGestor) {
|
||||
try {
|
||||
homologacoes = await client.query(api.pontos.listarHomologacoes, {
|
||||
const todasHomologacoes = await client.query(api.pontos.listarHomologacoes, {
|
||||
funcionarioId,
|
||||
}) || [];
|
||||
|
||||
// Filtrar homologações pelo período selecionado
|
||||
const dataInicioTimestamp = new Date(dataInicio + 'T00:00:00').getTime();
|
||||
const dataFimTimestamp = new Date(dataFim + 'T23:59:59').getTime();
|
||||
|
||||
homologacoes = todasHomologacoes.filter((h) => {
|
||||
return h.criadoEm >= dataInicioTimestamp && h.criadoEm <= dataFimTimestamp;
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar homologações:', error);
|
||||
// Continuar mesmo se houver erro ao buscar homologações
|
||||
@@ -879,10 +1030,22 @@
|
||||
|
||||
if (sections.dispensasRegistro) {
|
||||
try {
|
||||
dispensas = await client.query(api.pontos.listarDispensas, {
|
||||
const todasDispensas = await client.query(api.pontos.listarDispensas, {
|
||||
funcionarioId,
|
||||
apenasAtivas: false,
|
||||
}) || [];
|
||||
|
||||
// Filtrar dispensas que têm interseção com o período selecionado
|
||||
const dataInicioPeriodo = new Date(dataInicio + 'T00:00:00');
|
||||
const dataFimPeriodo = new Date(dataFim + 'T23:59:59');
|
||||
|
||||
dispensas = todasDispensas.filter((d) => {
|
||||
const dispensaInicio = new Date(d.dataInicio + 'T00:00:00');
|
||||
const dispensaFim = new Date(d.dataFim + 'T23:59:59');
|
||||
|
||||
// Verificar se há interseção entre os períodos
|
||||
return dispensaInicio <= dataFimPeriodo && dispensaFim >= dataInicioPeriodo;
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar dispensas:', error);
|
||||
// Continuar mesmo se houver erro ao buscar dispensas
|
||||
@@ -890,7 +1053,11 @@
|
||||
}
|
||||
|
||||
// Variável para armazenar saldos diários (usada no resumo do banco de horas)
|
||||
const saldosDiariosPorData: Record<string, number> = {};
|
||||
const saldosDiariosPorData: Record<string, {
|
||||
diferencaMinutos: number;
|
||||
trabalhadoMinutos: number;
|
||||
esperadoMinutos: number;
|
||||
}> = {};
|
||||
|
||||
// Tabela de registros
|
||||
if (sections.registrosPonto) {
|
||||
@@ -990,42 +1157,134 @@
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
|
||||
// Calcular saldos por par entrada/saída (apenas com registros reais)
|
||||
// Calcular saldos comparativos por par entrada/saída (apenas com registros reais)
|
||||
const regsReaisOrdenados = [...regsReais].sort((a, b) => {
|
||||
if (a.hora !== b.hora) return a.hora - b.hora;
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
const saldosPorPar = calcularSaldosPorPar(regsReaisOrdenados);
|
||||
const saldosComparativosPorPar = calcularSaldoComparativoPorPar(regsReaisOrdenados, config);
|
||||
|
||||
// Calcular saldos esperados para pares incompletos ou dias sem registros
|
||||
const saldosEsperadosPorPar: Map<number, { saldoMinutos: number; horas: number; minutos: number; tamanhoPar: number; incompleto: boolean }> = new Map();
|
||||
const saldosEsperadosPorPar: Map<number, {
|
||||
trabalhadoMinutos: number;
|
||||
trabalhadoHoras: number;
|
||||
trabalhadoMinutosResto: number;
|
||||
esperadoMinutos: number;
|
||||
esperadoHoras: number;
|
||||
esperadoMinutosResto: number;
|
||||
diferencaMinutos: number;
|
||||
diferencaHoras: number;
|
||||
diferencaMinutosResto: number;
|
||||
tamanhoPar: number;
|
||||
incompleto: boolean;
|
||||
}> = new Map();
|
||||
|
||||
// Calcular saldo diário total (diferença acumulada de todos os pares)
|
||||
// Declarar variáveis ANTES de usá-las
|
||||
let saldoDiarioTotalDiferencaMinutos = 0;
|
||||
let saldoDiarioTotalTrabalhadoMinutos = 0;
|
||||
let saldoDiarioTotalEsperadoMinutos = 0;
|
||||
|
||||
// Criar conjunto de chaves de registros que já foram processados como completos
|
||||
// Isso será usado tanto para evitar criar pares incompletos quanto para evitar somar duplicados
|
||||
const chavesProcessadasCompletas = new Set<string>();
|
||||
const paresCompletosProcessados = new Set<number>(); // Rastrear parIndex já processados
|
||||
|
||||
// Criar conjunto de chaves e somar saldos dos pares completos
|
||||
// IMPORTANTE: saldosComparativosPorPar associa o saldo a AMBOS os registros do par (entrada + saída)
|
||||
// Então precisamos garantir que somamos apenas UMA VEZ por par, não uma vez por registro
|
||||
for (const [index, saldo] of saldosComparativosPorPar.entries()) {
|
||||
const regEntrada = regsReaisOrdenados[index];
|
||||
if (regEntrada) {
|
||||
// Verificar se este parIndex já foi processado
|
||||
if (!paresCompletosProcessados.has(saldo.parIndex)) {
|
||||
// Primeira vez processando este par, somar o saldo
|
||||
saldoDiarioTotalDiferencaMinutos += saldo.diferencaMinutos;
|
||||
saldoDiarioTotalTrabalhadoMinutos += saldo.trabalhadoMinutos;
|
||||
saldoDiarioTotalEsperadoMinutos += saldo.esperadoMinutos;
|
||||
paresCompletosProcessados.add(saldo.parIndex);
|
||||
}
|
||||
|
||||
// Adicionar chaves para evitar processamento duplicado depois
|
||||
const chaveEntrada = `${regEntrada.tipo}-${regEntrada.hora}-${regEntrada.minuto}`;
|
||||
chavesProcessadasCompletas.add(chaveEntrada);
|
||||
// Encontrar saída correspondente
|
||||
const tipoSaidaEsperado = regEntrada.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
for (let j = index + 1; j < regsReaisOrdenados.length; j++) {
|
||||
const regSaida = regsReaisOrdenados[j];
|
||||
if (regSaida && regSaida.tipo === tipoSaidaEsperado) {
|
||||
const chaveSaida = `${regSaida.tipo}-${regSaida.hora}-${regSaida.minuto}`;
|
||||
chavesProcessadasCompletas.add(chaveSaida);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Identificar pares incompletos e calcular saldos esperados
|
||||
// IMPORTANTE: Só processar pares que NÃO foram processados como completos
|
||||
for (let i = 0; i < todosRegistros.length; i++) {
|
||||
const reg = todosRegistros[i];
|
||||
|
||||
// Se é entrada ou retorno_almoco (início de par) e é real
|
||||
if ((reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco') && reg.real) {
|
||||
// Verificar se este registro já foi processado como par completo
|
||||
const chaveRegistro = `${reg.tipo}-${reg.hora}-${reg.minuto}`;
|
||||
if (chavesProcessadasCompletas.has(chaveRegistro)) {
|
||||
continue; // Já foi processado como par completo, pular
|
||||
}
|
||||
|
||||
// Verificar se há saída correspondente real
|
||||
const tipoSaidaEsperado = reg.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
const saidaEncontrada = regsReais.find(r => r.tipo === tipoSaidaEsperado);
|
||||
|
||||
// Se há saída correspondente, verificar se ela também está em chavesProcessadasCompletas
|
||||
// Se estiver, significa que este par já foi processado como completo
|
||||
if (saidaEncontrada) {
|
||||
const chaveSaida = `${saidaEncontrada.tipo}-${saidaEncontrada.hora}-${saidaEncontrada.minuto}`;
|
||||
if (chavesProcessadasCompletas.has(chaveSaida)) {
|
||||
continue; // Par completo já processado, pular
|
||||
}
|
||||
}
|
||||
|
||||
if (!saidaEncontrada) {
|
||||
// Par incompleto: calcular saldo esperado baseado na configuração
|
||||
// Par incompleto: entrada real sem saída correspondente
|
||||
// NÃO calcular tempo trabalhado aqui porque não há saída marcada
|
||||
// O tempo trabalhado será 0, e a diferença será negativa (0 - esperado)
|
||||
const regEsperado = regsEsperados.find(r => r.tipo === tipoSaidaEsperado);
|
||||
if (regEsperado) {
|
||||
const minutosEntrada = reg.hora * 60 + reg.minuto;
|
||||
const minutosSaidaEsperada = regEsperado.hora * 60 + regEsperado.minuto;
|
||||
let saldoMinutos = minutosSaidaEsperada - minutosEntrada;
|
||||
if (saldoMinutos < 0) {
|
||||
saldoMinutos += 24 * 60;
|
||||
// Tempo trabalhado = 0 (não há saída marcada, então não podemos assumir tempo trabalhado)
|
||||
const trabalhadoMinutos = 0;
|
||||
|
||||
// Calcular tempo esperado
|
||||
let esperadoMinutos: number;
|
||||
if (reg.tipo === 'entrada') {
|
||||
const [horaEntradaEsperada, minutoEntradaEsperado] = config.horarioEntrada.split(':').map(Number);
|
||||
const [horaSaidaAlmocoEsperada, minutoSaidaAlmocoEsperado] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||
const minutosEntradaEsperada = horaEntradaEsperada * 60 + minutoEntradaEsperado;
|
||||
const minutosSaidaEsperadaConfig = horaSaidaAlmocoEsperada * 60 + minutoSaidaAlmocoEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperadaConfig - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) esperadoMinutos += 24 * 60;
|
||||
} else {
|
||||
const [horaRetornoAlmocoEsperado, minutoRetornoAlmocoEsperado] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||
const [horaSaidaEsperada, minutoSaidaEsperado] = config.horarioSaida.split(':').map(Number);
|
||||
const minutosEntradaEsperada = horaRetornoAlmocoEsperado * 60 + minutoRetornoAlmocoEsperado;
|
||||
const minutosSaidaEsperadaConfig = horaSaidaEsperada * 60 + minutoSaidaEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperadaConfig - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) esperadoMinutos += 24 * 60;
|
||||
}
|
||||
|
||||
const horas = Math.floor(saldoMinutos / 60);
|
||||
const minutos = saldoMinutos % 60;
|
||||
|
||||
// Calcular diferença (0 - esperado = negativo)
|
||||
const diferencaMinutos = -esperadoMinutos;
|
||||
|
||||
const trabalhadoHoras = 0;
|
||||
const trabalhadoMinutosResto = 0;
|
||||
const esperadoHoras = Math.floor(esperadoMinutos / 60);
|
||||
const esperadoMinutosResto = esperadoMinutos % 60;
|
||||
const diferencaHoras = Math.floor(Math.abs(diferencaMinutos) / 60);
|
||||
const diferencaMinutosResto = Math.abs(diferencaMinutos) % 60;
|
||||
|
||||
// Contar quantos registros fazem parte deste par na lista todosRegistros
|
||||
// Encontrar índice do registro na lista ordenada todosRegistros
|
||||
const indexNaListaTodos = todosRegistros.findIndex(
|
||||
(r, idx) => idx >= i && r.tipo === reg.tipo && r.hora === reg.hora && r.minuto === reg.minuto
|
||||
);
|
||||
@@ -1041,9 +1300,15 @@
|
||||
|
||||
if (indexNaListaTodos >= 0) {
|
||||
saldosEsperadosPorPar.set(indexNaListaTodos, {
|
||||
saldoMinutos,
|
||||
horas,
|
||||
minutos,
|
||||
trabalhadoMinutos,
|
||||
trabalhadoHoras,
|
||||
trabalhadoMinutosResto,
|
||||
esperadoMinutos,
|
||||
esperadoHoras,
|
||||
esperadoMinutosResto,
|
||||
diferencaMinutos,
|
||||
diferencaHoras,
|
||||
diferencaMinutosResto,
|
||||
tamanhoPar,
|
||||
incompleto: true
|
||||
});
|
||||
@@ -1053,10 +1318,117 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular saldo diário total (soma de todos os pares reais)
|
||||
let saldoDiarioTotalMinutos = 0;
|
||||
for (const saldo of saldosPorPar.values()) {
|
||||
saldoDiarioTotalMinutos += saldo.saldoMinutos;
|
||||
// Identificar pares completamente não marcados (quando há registros reais mas um par completo não foi marcado)
|
||||
if (regsReais.length > 0) {
|
||||
for (let i = 0; i < todosRegistros.length; i++) {
|
||||
const reg = todosRegistros[i];
|
||||
|
||||
// Se é entrada ou retorno_almoco (início de par) e NÃO é real
|
||||
if ((reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco') && !reg.real) {
|
||||
// Verificar se a saída correspondente também não foi marcada
|
||||
const tipoSaidaEsperado = reg.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
const saidaEsperada = todosRegistros.find(
|
||||
(r, idx) => idx > i && r.tipo === tipoSaidaEsperado && !r.real
|
||||
);
|
||||
|
||||
if (saidaEsperada) {
|
||||
// Par completamente não marcado: calcular saldo negativo
|
||||
// Tempo trabalhado = 0, tempo esperado = configurado, diferença = -esperado
|
||||
let esperadoMinutos: number;
|
||||
if (reg.tipo === 'entrada') {
|
||||
const [horaEntradaEsperada, minutoEntradaEsperado] = config.horarioEntrada.split(':').map(Number);
|
||||
const [horaSaidaAlmocoEsperada, minutoSaidaAlmocoEsperado] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||
const minutosEntradaEsperada = horaEntradaEsperada * 60 + minutoEntradaEsperado;
|
||||
const minutosSaidaEsperadaConfig = horaSaidaAlmocoEsperada * 60 + minutoSaidaAlmocoEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperadaConfig - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) esperadoMinutos += 24 * 60;
|
||||
} else {
|
||||
const [horaRetornoAlmocoEsperado, minutoRetornoAlmocoEsperado] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||
const [horaSaidaEsperada, minutoSaidaEsperado] = config.horarioSaida.split(':').map(Number);
|
||||
const minutosEntradaEsperada = horaRetornoAlmocoEsperado * 60 + minutoRetornoAlmocoEsperado;
|
||||
const minutosSaidaEsperadaConfig = horaSaidaEsperada * 60 + minutoSaidaEsperado;
|
||||
esperadoMinutos = minutosSaidaEsperadaConfig - minutosEntradaEsperada;
|
||||
if (esperadoMinutos < 0) esperadoMinutos += 24 * 60;
|
||||
}
|
||||
|
||||
// Trabalhado = 0, diferença = -esperado
|
||||
const trabalhadoMinutos = 0;
|
||||
const diferencaMinutos = -esperadoMinutos;
|
||||
|
||||
const trabalhadoHoras = 0;
|
||||
const trabalhadoMinutosResto = 0;
|
||||
const esperadoHoras = Math.floor(esperadoMinutos / 60);
|
||||
const esperadoMinutosResto = esperadoMinutos % 60;
|
||||
const diferencaHoras = Math.floor(Math.abs(diferencaMinutos) / 60);
|
||||
const diferencaMinutosResto = Math.abs(diferencaMinutos) % 60;
|
||||
|
||||
// Encontrar índice da saída esperada na lista
|
||||
const indexSaidaEsperada = todosRegistros.findIndex(
|
||||
(r, idx) => idx > i && r.tipo === tipoSaidaEsperado && !r.real
|
||||
);
|
||||
|
||||
// Associar saldo negativo ao início do par (entrada)
|
||||
saldosEsperadosPorPar.set(i, {
|
||||
trabalhadoMinutos,
|
||||
trabalhadoHoras,
|
||||
trabalhadoMinutosResto,
|
||||
esperadoMinutos,
|
||||
esperadoHoras,
|
||||
esperadoMinutosResto,
|
||||
diferencaMinutos,
|
||||
diferencaHoras,
|
||||
diferencaMinutosResto,
|
||||
tamanhoPar: indexSaidaEsperada >= 0 ? 2 : 1, // entrada + saída
|
||||
incompleto: false // Par completo não marcado
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTA: Os saldos dos pares completos já foram somados acima quando criamos chavesProcessadasCompletas
|
||||
// As variáveis saldoDiarioTotal* já foram declaradas e inicializadas acima
|
||||
|
||||
// Somar saldos dos pares completamente não marcados e incompletos
|
||||
// IMPORTANTE: Não somar pares que já foram processados como completos acima
|
||||
// Usar o conjunto chavesProcessadasCompletas criado anteriormente
|
||||
for (const [indexNaListaTodos, saldo] of saldosEsperadosPorPar.entries()) {
|
||||
const regNaListaTodos = todosRegistros[indexNaListaTodos];
|
||||
|
||||
// Verificar se este registro real já foi processado como par completo
|
||||
if (regNaListaTodos && regNaListaTodos.real) {
|
||||
const chaveRegistro = `${regNaListaTodos.tipo}-${regNaListaTodos.hora}-${regNaListaTodos.minuto}`;
|
||||
if (chavesProcessadasCompletas.has(chaveRegistro)) {
|
||||
// Este registro está em chavesProcessadasCompletas
|
||||
// Verificar se há uma saída correspondente que também está lá
|
||||
// Se ambas estão, significa que o par completo já foi processado
|
||||
const tipoSaidaEsperado = regNaListaTodos.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
|
||||
// Procurar saída correspondente em regsReais que também está em chavesProcessadasCompletas
|
||||
const saidaCompletaEncontrada = regsReais.find(r => {
|
||||
if (r.tipo !== tipoSaidaEsperado) return false;
|
||||
const chaveSaida = `${r.tipo}-${r.hora}-${r.minuto}`;
|
||||
return chavesProcessadasCompletas.has(chaveSaida);
|
||||
});
|
||||
|
||||
if (saidaCompletaEncontrada) {
|
||||
continue; // Par completo já processado (entrada + saída estão em chavesProcessadasCompletas), não somar novamente
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saldo.trabalhadoMinutos === 0 && !saldo.incompleto) {
|
||||
// Par completamente não marcado: adicionar diferença negativa
|
||||
saldoDiarioTotalDiferencaMinutos += saldo.diferencaMinutos;
|
||||
saldoDiarioTotalTrabalhadoMinutos += saldo.trabalhadoMinutos; // 0
|
||||
saldoDiarioTotalEsperadoMinutos += saldo.esperadoMinutos;
|
||||
} else if (saldo.incompleto) {
|
||||
// Par incompleto: entrada real sem saída correspondente
|
||||
// Só somar se não foi processado como completo acima
|
||||
saldoDiarioTotalDiferencaMinutos += saldo.diferencaMinutos;
|
||||
saldoDiarioTotalTrabalhadoMinutos += saldo.trabalhadoMinutos;
|
||||
saldoDiarioTotalEsperadoMinutos += saldo.esperadoMinutos;
|
||||
}
|
||||
}
|
||||
|
||||
// Se não há registros reais, calcular saldo esperado baseado na configuração
|
||||
@@ -1079,10 +1451,17 @@
|
||||
let saldoPar2 = minutosPar2Saida - minutosPar2Entrada;
|
||||
if (saldoPar2 < 0) saldoPar2 += 24 * 60;
|
||||
|
||||
saldoDiarioTotalMinutos = saldoPar1 + saldoPar2;
|
||||
saldoDiarioTotalTrabalhadoMinutos = 0; // Nenhum tempo trabalhado
|
||||
saldoDiarioTotalEsperadoMinutos = saldoPar1 + saldoPar2;
|
||||
saldoDiarioTotalDiferencaMinutos = -saldoDiarioTotalEsperadoMinutos; // Diferença negativa (0 - esperado)
|
||||
}
|
||||
|
||||
saldosDiariosPorData[data] = saldoDiarioTotalMinutos;
|
||||
// Armazenar saldo diário completo (usado no resumo do banco de horas)
|
||||
saldosDiariosPorData[data] = {
|
||||
diferencaMinutos: saldoDiarioTotalDiferencaMinutos,
|
||||
trabalhadoMinutos: saldoDiarioTotalTrabalhadoMinutos,
|
||||
esperadoMinutos: saldoDiarioTotalEsperadoMinutos
|
||||
};
|
||||
|
||||
// Criar linhas da tabela
|
||||
for (let i = 0; i < todosRegistros.length; i++) {
|
||||
@@ -1105,7 +1484,7 @@
|
||||
linha._naoMarcado = true;
|
||||
}
|
||||
|
||||
// Saldo Diário por par entrada/saída
|
||||
// Saldo Diário por par entrada/saída com cálculo comparativo
|
||||
if (sections.saldoDiario) {
|
||||
const isInicioPar = reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco';
|
||||
|
||||
@@ -1117,17 +1496,23 @@
|
||||
// Verificar se há saldo esperado (par incompleto)
|
||||
const saldoEsperado = saldosEsperadosPorPar.get(i);
|
||||
if (saldoEsperado) {
|
||||
// Par incompleto: mostrar saldo esperado em vermelho
|
||||
// Par incompleto: mostrar saldo comparativo em vermelho
|
||||
linha._saldoVermelho = true;
|
||||
const sinalDiferenca = saldoEsperado.diferencaMinutos >= 0 ? '+' : '-';
|
||||
linha.push({
|
||||
content: `+${saldoEsperado.horas}h ${saldoEsperado.minutos}min`,
|
||||
content: `+${saldoEsperado.trabalhadoHoras}h ${saldoEsperado.trabalhadoMinutosResto}min / ${sinalDiferenca}${saldoEsperado.diferencaHoras}h ${saldoEsperado.diferencaMinutosResto}min`,
|
||||
rowSpan: saldoEsperado.tamanhoPar
|
||||
});
|
||||
} else if (indexReal >= 0) {
|
||||
const saldoPar = saldosPorPar.get(indexReal);
|
||||
const saldoPar = saldosComparativosPorPar.get(indexReal);
|
||||
if (saldoPar) {
|
||||
const sinalDiferenca = saldoPar.diferencaMinutos >= 0 ? '+' : '-';
|
||||
// Aplicar cor vermelha se diferença for negativa
|
||||
if (saldoPar.diferencaMinutos < 0) {
|
||||
linha._saldoVermelho = true;
|
||||
}
|
||||
linha.push({
|
||||
content: `+${saldoPar.horas}h ${saldoPar.minutos}min`,
|
||||
content: `+${saldoPar.trabalhadoHoras}h ${saldoPar.trabalhadoMinutosResto}min / ${sinalDiferenca}${saldoPar.diferencaHoras}h ${saldoPar.diferencaMinutosResto}min`,
|
||||
rowSpan: saldoPar.tamanhoPar
|
||||
});
|
||||
} else {
|
||||
@@ -1152,14 +1537,24 @@
|
||||
// Verificar se há saldo esperado para este par
|
||||
const saldoEsperado = saldosEsperadosPorPar.get(i);
|
||||
if (saldoEsperado) {
|
||||
// Par incompleto: mostrar saldo esperado em vermelho
|
||||
// Par incompleto ou completamente não marcado: mostrar saldo em vermelho
|
||||
linha._saldoVermelho = true;
|
||||
linha.push({
|
||||
content: `+${saldoEsperado.horas}h ${saldoEsperado.minutos}min`,
|
||||
rowSpan: saldoEsperado.tamanhoPar
|
||||
});
|
||||
const sinalDiferenca = saldoEsperado.diferencaMinutos >= 0 ? '+' : '-';
|
||||
|
||||
// Se par completamente não marcado (trabalhado = 0), mostrar apenas diferença negativa
|
||||
if (saldoEsperado.trabalhadoMinutos === 0) {
|
||||
linha.push({
|
||||
content: `+0h 0min / ${sinalDiferenca}${saldoEsperado.diferencaHoras}h ${saldoEsperado.diferencaMinutosResto}min`,
|
||||
rowSpan: saldoEsperado.tamanhoPar
|
||||
});
|
||||
} else {
|
||||
linha.push({
|
||||
content: `+${saldoEsperado.trabalhadoHoras}h ${saldoEsperado.trabalhadoMinutosResto}min / ${sinalDiferenca}${saldoEsperado.diferencaHoras}h ${saldoEsperado.diferencaMinutosResto}min`,
|
||||
rowSpan: saldoEsperado.tamanhoPar
|
||||
});
|
||||
}
|
||||
} else if (regsReais.length === 0) {
|
||||
// Dia sem registros: calcular saldo esperado completo
|
||||
// Dia sem registros: calcular saldo esperado completo com diferença negativa
|
||||
if (reg.tipo === 'entrada') {
|
||||
// Par 1 completo esperado
|
||||
const [horaEntrada, minutoEntrada] = config.horarioEntrada.split(':').map(Number);
|
||||
@@ -1171,8 +1566,9 @@
|
||||
const horas = Math.floor(saldoMinutos / 60);
|
||||
const minutos = saldoMinutos % 60;
|
||||
linha._saldoVermelho = true;
|
||||
// Para dia sem registros, mostrar 0h trabalhado e diferença negativa
|
||||
linha.push({
|
||||
content: `+${horas}h ${minutos}min`,
|
||||
content: `+0h 0min / -${horas}h ${minutos}min`,
|
||||
rowSpan: 2 // entrada + saida_almoco
|
||||
});
|
||||
} else if (reg.tipo === 'retorno_almoco') {
|
||||
@@ -1186,19 +1582,53 @@
|
||||
const horas = Math.floor(saldoMinutos / 60);
|
||||
const minutos = saldoMinutos % 60;
|
||||
linha._saldoVermelho = true;
|
||||
// Para dia sem registros, mostrar 0h trabalhado e diferença negativa
|
||||
linha.push({
|
||||
content: `+${horas}h ${minutos}min`,
|
||||
content: `+0h 0min / -${horas}h ${minutos}min`,
|
||||
rowSpan: 2 // retorno_almoco + saida
|
||||
});
|
||||
} else {
|
||||
linha.push('-');
|
||||
}
|
||||
} else {
|
||||
linha.push('-');
|
||||
// Há registros reais mas este par não foi marcado completamente
|
||||
// Verificar se é um par completamente não marcado
|
||||
const tipoSaidaEsperado = reg.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
const saidaEsperadaExiste = todosRegistros.some(
|
||||
(r, idx) => idx > i && r.tipo === tipoSaidaEsperado && !r.real
|
||||
);
|
||||
|
||||
if (saidaEsperadaExiste) {
|
||||
// Par completamente não marcado: calcular saldo negativo
|
||||
const saldoEsperadoCompleto = saldosEsperadosPorPar.get(i);
|
||||
if (saldoEsperadoCompleto) {
|
||||
linha._saldoVermelho = true;
|
||||
const sinalDiferenca = saldoEsperadoCompleto.diferencaMinutos >= 0 ? '+' : '-';
|
||||
linha.push({
|
||||
content: `+0h 0min / ${sinalDiferenca}${saldoEsperadoCompleto.diferencaHoras}h ${saldoEsperadoCompleto.diferencaMinutosResto}min`,
|
||||
rowSpan: saldoEsperadoCompleto.tamanhoPar
|
||||
});
|
||||
} else {
|
||||
linha.push('-');
|
||||
}
|
||||
} else {
|
||||
linha.push('-');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Saída sem entrada correspondente: não tem saldo
|
||||
linha.push('-');
|
||||
// Saída não marcada: verificar se faz parte de um par completamente não marcado
|
||||
// Se a entrada correspondente também não foi marcada, o saldo já foi adicionado na linha da entrada
|
||||
// Então apenas não adicionar nada aqui (será coberto pelo rowspan)
|
||||
const tipoEntradaEsperado = reg.tipo === 'saida_almoco' ? 'entrada' : 'retorno_almoco';
|
||||
const entradaEsperadaExiste = todosRegistros.some(
|
||||
(r, idx) => idx < i && r.tipo === tipoEntradaEsperado && !r.real
|
||||
);
|
||||
|
||||
if (!entradaEsperadaExiste) {
|
||||
// Saída sem entrada correspondente esperada: não tem saldo
|
||||
linha.push('-');
|
||||
}
|
||||
// Se entrada esperada existe, o saldo já foi adicionado com rowspan na linha da entrada
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1269,12 +1699,48 @@
|
||||
funcionarioId,
|
||||
});
|
||||
|
||||
// Calcular saldo do período selecionado baseado nos saldos diários calculados
|
||||
let saldoPeriodoMinutos = 0;
|
||||
// Calcular total de dias do período selecionado
|
||||
const diasPeriodo = gerarDiasPeriodo(dataInicio, dataFim);
|
||||
const totalDiasPeriodo = diasPeriodo.length;
|
||||
|
||||
// Calcular carga horária diária esperada baseada na configuração
|
||||
const [horaEntradaConfig, minutoEntradaConfig] = config.horarioEntrada.split(':').map(Number);
|
||||
const [horaSaidaAlmocoConfig, minutoSaidaAlmocoConfig] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||
const [horaRetornoAlmocoConfig, minutoRetornoAlmocoConfig] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||
const [horaSaidaConfig, minutoSaidaConfig] = config.horarioSaida.split(':').map(Number);
|
||||
|
||||
// Par 1: entrada -> saida_almoco
|
||||
const minutosPar1EsperadoConfig = (horaSaidaAlmocoConfig * 60 + minutoSaidaAlmocoConfig) - (horaEntradaConfig * 60 + minutoEntradaConfig);
|
||||
const minutosPar1EsperadoAjustadoConfig = minutosPar1EsperadoConfig < 0 ? minutosPar1EsperadoConfig + 24 * 60 : minutosPar1EsperadoConfig;
|
||||
|
||||
// Par 2: retorno_almoco -> saida
|
||||
const minutosPar2EsperadoConfig = (horaSaidaConfig * 60 + minutoSaidaConfig) - (horaRetornoAlmocoConfig * 60 + minutoRetornoAlmocoConfig);
|
||||
const minutosPar2EsperadoAjustadoConfig = minutosPar2EsperadoConfig < 0 ? minutosPar2EsperadoConfig + 24 * 60 : minutosPar2EsperadoConfig;
|
||||
|
||||
const cargaHorariaDiariaEsperadaMinutos = minutosPar1EsperadoAjustadoConfig + minutosPar2EsperadoAjustadoConfig;
|
||||
|
||||
// Calcular saldos do período selecionado baseado nos saldos diários calculados
|
||||
let saldoPeriodoDiferencaMinutos = 0;
|
||||
let saldoPeriodoTrabalhadoMinutos = 0;
|
||||
let diasComSaldoPositivo = 0;
|
||||
let diasComSaldoNegativo = 0;
|
||||
let diasSemRegistros = 0;
|
||||
|
||||
if (sections.registrosPonto && Object.keys(saldosDiariosPorData).length > 0) {
|
||||
// Somar todos os saldos diários do período
|
||||
for (const saldoMinutos of Object.values(saldosDiariosPorData)) {
|
||||
saldoPeriodoMinutos += saldoMinutos;
|
||||
for (const saldo of Object.values(saldosDiariosPorData)) {
|
||||
saldoPeriodoDiferencaMinutos += saldo.diferencaMinutos;
|
||||
saldoPeriodoTrabalhadoMinutos += saldo.trabalhadoMinutos;
|
||||
|
||||
if (saldo.diferencaMinutos > 0) {
|
||||
diasComSaldoPositivo++;
|
||||
} else if (saldo.diferencaMinutos < 0) {
|
||||
diasComSaldoNegativo++;
|
||||
}
|
||||
|
||||
if (saldo.trabalhadoMinutos === 0 && saldo.esperadoMinutos > 0) {
|
||||
diasSemRegistros++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback: calcular a partir dos registros se não tiver saldos diários
|
||||
@@ -1295,19 +1761,50 @@
|
||||
for (const regs of Object.values(registrosPorDataPeriodo)) {
|
||||
const saldoDiario = calcularSaldoDiario(regs);
|
||||
if (saldoDiario) {
|
||||
saldoPeriodoMinutos += saldoDiario.saldoMinutos;
|
||||
saldoPeriodoDiferencaMinutos += saldoDiario.saldoMinutos;
|
||||
saldoPeriodoTrabalhadoMinutos += saldoDiario.saldoMinutos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const horasPeriodo = Math.floor(Math.abs(saldoPeriodoMinutos) / 60);
|
||||
const minutosPeriodo = Math.abs(saldoPeriodoMinutos) % 60;
|
||||
const sinalPeriodo = saldoPeriodoMinutos >= 0 ? '+' : '-';
|
||||
const saldoPeriodoFormatado = `${sinalPeriodo}${horasPeriodo}h ${minutosPeriodo}min`;
|
||||
// Calcular saldo esperado do período: carga horária diária × número de dias
|
||||
const saldoPeriodoEsperadoMinutos = cargaHorariaDiariaEsperadaMinutos * totalDiasPeriodo;
|
||||
|
||||
// Calcular total de dias do período selecionado
|
||||
const diasPeriodo = gerarDiasPeriodo(dataInicio, dataFim);
|
||||
const totalDiasPeriodo = diasPeriodo.length;
|
||||
// Calcular médias diárias
|
||||
const mediaDiariaTrabalhadaHoras = totalDiasPeriodo > 0 ? Math.floor(saldoPeriodoTrabalhadoMinutos / 60 / totalDiasPeriodo) : 0;
|
||||
const mediaDiariaTrabalhadaMinutos = totalDiasPeriodo > 0 ? Math.floor((saldoPeriodoTrabalhadoMinutos / totalDiasPeriodo) % 60) : 0;
|
||||
|
||||
// Calcular média esperada baseada na configuração padrão (não no período)
|
||||
const [horaEntrada, minutoEntrada] = config.horarioEntrada.split(':').map(Number);
|
||||
const [horaSaidaAlmoco, minutoSaidaAlmoco] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||
const [horaRetornoAlmoco, minutoRetornoAlmoco] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||
const [horaSaida, minutoSaida] = config.horarioSaida.split(':').map(Number);
|
||||
|
||||
// Par 1: entrada -> saida_almoco
|
||||
const minutosPar1Esperado = (horaSaidaAlmoco * 60 + minutoSaidaAlmoco) - (horaEntrada * 60 + minutoEntrada);
|
||||
const minutosPar1EsperadoAjustado = minutosPar1Esperado < 0 ? minutosPar1Esperado + 24 * 60 : minutosPar1Esperado;
|
||||
|
||||
// Par 2: retorno_almoco -> saida
|
||||
const minutosPar2Esperado = (horaSaida * 60 + minutoSaida) - (horaRetornoAlmoco * 60 + minutoRetornoAlmoco);
|
||||
const minutosPar2EsperadoAjustado = minutosPar2Esperado < 0 ? minutosPar2Esperado + 24 * 60 : minutosPar2Esperado;
|
||||
|
||||
const totalEsperadoDiarioMinutos = minutosPar1EsperadoAjustado + minutosPar2EsperadoAjustado;
|
||||
const mediaDiariaEsperadaHoras = Math.floor(totalEsperadoDiarioMinutos / 60);
|
||||
const mediaDiariaEsperadaMinutos = totalEsperadoDiarioMinutos % 60;
|
||||
|
||||
// Formatar valores
|
||||
const horasPeriodoDiferenca = Math.floor(Math.abs(saldoPeriodoDiferencaMinutos) / 60);
|
||||
const minutosPeriodoDiferenca = Math.abs(saldoPeriodoDiferencaMinutos) % 60;
|
||||
const sinalPeriodoDiferenca = saldoPeriodoDiferencaMinutos >= 0 ? '+' : '-';
|
||||
const saldoPeriodoDiferencaFormatado = `${sinalPeriodoDiferenca}${horasPeriodoDiferenca}h ${minutosPeriodoDiferenca}min`;
|
||||
|
||||
const horasPeriodoTrabalhado = Math.floor(saldoPeriodoTrabalhadoMinutos / 60);
|
||||
const minutosPeriodoTrabalhado = saldoPeriodoTrabalhadoMinutos % 60;
|
||||
const saldoPeriodoTrabalhadoFormatado = `+${horasPeriodoTrabalhado}h ${minutosPeriodoTrabalhado}min`;
|
||||
|
||||
const horasPeriodoEsperado = Math.floor(saldoPeriodoEsperadoMinutos / 60);
|
||||
const minutosPeriodoEsperado = saldoPeriodoEsperadoMinutos % 60;
|
||||
const saldoPeriodoEsperadoFormatado = `+${horasPeriodoEsperado}h ${minutosPeriodoEsperado}min`;
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
@@ -1324,20 +1821,45 @@
|
||||
const sinal = saldoMinutos >= 0 ? '+' : '-';
|
||||
const saldoFormatado = `${sinal}${horas}h ${minutos}min`;
|
||||
|
||||
// Preparar dados da tabela
|
||||
const bancoHorasData: string[][] = [
|
||||
// Calcular saldo acumulado de períodos anteriores
|
||||
// Saldo Anterior = Saldo Total - Saldo do Período Atual
|
||||
const saldoAnteriorMinutos = saldoMinutos - saldoPeriodoDiferencaMinutos;
|
||||
const horasAnterior = Math.floor(Math.abs(saldoAnteriorMinutos) / 60);
|
||||
const minutosAnterior = Math.abs(saldoAnteriorMinutos) % 60;
|
||||
const sinalAnterior = saldoAnteriorMinutos >= 0 ? '+' : '-';
|
||||
const saldoAnteriorFormatado = `${sinalAnterior}${horasAnterior}h ${minutosAnterior}min`;
|
||||
|
||||
// Calcular resultado final
|
||||
const resultadoFinalMinutos = saldoAnteriorMinutos + saldoPeriodoDiferencaMinutos;
|
||||
const horasResultadoFinal = Math.floor(Math.abs(resultadoFinalMinutos) / 60);
|
||||
const minutosResultadoFinal = Math.abs(resultadoFinalMinutos) % 60;
|
||||
const sinalResultadoFinal = resultadoFinalMinutos >= 0 ? '+' : '-';
|
||||
const resultadoFinalFormatado = `${sinalResultadoFinal}${horasResultadoFinal}h ${minutosResultadoFinal}min`;
|
||||
|
||||
// Preparar dados da tabela com melhorias
|
||||
const bancoHorasData: any[][] = [
|
||||
['Saldo Atual', saldoFormatado],
|
||||
['Saldo do Período Selecionado', saldoPeriodoFormatado]
|
||||
['Saldo Banco Acumulado de Períodos Anteriores', saldoAnteriorFormatado],
|
||||
['Saldo do Período Exibido', saldoPeriodoDiferencaFormatado],
|
||||
['Resultado Final', resultadoFinalFormatado]
|
||||
];
|
||||
|
||||
if (saldoMinutos > 0) {
|
||||
bancoHorasData.push(['Horas Excedentes', `${horas}h ${minutos}min`]);
|
||||
}
|
||||
// Adicionar detalhamento
|
||||
bancoHorasData.push(['', '']); // Linha separadora
|
||||
bancoHorasData.push(['Saldo Trabalhado do Período', saldoPeriodoTrabalhadoFormatado]);
|
||||
bancoHorasData.push(['Saldo Esperado do Período', saldoPeriodoEsperadoFormatado]);
|
||||
bancoHorasData.push(['Diferença do Período', saldoPeriodoDiferencaFormatado]);
|
||||
|
||||
if (saldoMinutos < 0) {
|
||||
bancoHorasData.push(['Horas a Pagar', `${horas}h ${minutos}min`]);
|
||||
}
|
||||
// Adicionar estatísticas
|
||||
bancoHorasData.push(['', '']); // Linha separadora
|
||||
bancoHorasData.push(['Média Diária de Horas Trabalhadas', `+${mediaDiariaTrabalhadaHoras}h ${mediaDiariaTrabalhadaMinutos}min`]);
|
||||
bancoHorasData.push(['Média Diária Esperada', `+${mediaDiariaEsperadaHoras}h ${mediaDiariaEsperadaMinutos}min`]);
|
||||
|
||||
// Adicionar contagens
|
||||
bancoHorasData.push(['', '']); // Linha separadora
|
||||
bancoHorasData.push(['Dias com Saldo Positivo', `${diasComSaldoPositivo} dias`]);
|
||||
bancoHorasData.push(['Dias com Saldo Negativo', `${diasComSaldoNegativo} dias`]);
|
||||
bancoHorasData.push(['Dias sem Registros', `${diasSemRegistros} dias`]);
|
||||
bancoHorasData.push(['Total de Dias do Período', `${totalDiasPeriodo} dias`]);
|
||||
|
||||
// Criar tabela no mesmo estilo das outras seções
|
||||
@@ -1353,18 +1875,69 @@
|
||||
1: { cellWidth: 'auto' }
|
||||
},
|
||||
didParseCell: function(data) {
|
||||
// Ignorar linhas separadoras vazias
|
||||
if (data.cell.text[0] === '' && data.column.index === 0) {
|
||||
data.cell.styles.fillColor = [240, 240, 240]; // Cor de fundo cinza claro
|
||||
return;
|
||||
}
|
||||
|
||||
const campo = data.cell.text[0];
|
||||
// Destacar "Saldo do Período Selecionado" em vermelho se negativo
|
||||
if (campo === 'Saldo do Período Selecionado' && saldoPeriodoMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0];
|
||||
if (data.column.index === 1) {
|
||||
const valor = data.cell.text[1] || '';
|
||||
|
||||
// Aplicar cores baseado no valor
|
||||
if (data.column.index === 1 && valor) {
|
||||
// Aplicar cor no Saldo Atual (vermelho para negativo, azul para positivo)
|
||||
if (campo === 'Saldo Atual') {
|
||||
if (saldoMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0]; // Vermelho para negativo
|
||||
} else if (saldoMinutos > 0) {
|
||||
data.cell.styles.textColor = [0, 100, 200]; // Azul para positivo
|
||||
}
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
}
|
||||
// Destacar "Horas a Pagar" em vermelho se saldo negativo
|
||||
if (campo === 'Horas a Pagar' && saldoMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0];
|
||||
if (data.column.index === 1) {
|
||||
|
||||
// Verificar se o valor contém sinal negativo
|
||||
if (valor.includes('-') && !valor.includes('±')) {
|
||||
data.cell.styles.textColor = [200, 0, 0]; // Vermelho para negativo
|
||||
if (campo === 'Saldo do Período Exibido' || campo === 'Diferença do Período' || campo === 'Resultado Final') {
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
} else if (valor.includes('+') || campo.includes('Trabalhado') || campo.includes('Esperado')) {
|
||||
// Verde para valores positivos ou campos de trabalhado/esperado
|
||||
if (campo.includes('Diferença') && saldoPeriodoDiferencaMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0]; // Vermelho se diferença negativa
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
} else if (campo.includes('Diferença') && saldoPeriodoDiferencaMinutos > 0) {
|
||||
data.cell.styles.textColor = [0, 128, 0]; // Verde se diferença positiva
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
} else if (campo.includes('Trabalhado') || campo.includes('Esperado')) {
|
||||
data.cell.styles.textColor = [0, 128, 0]; // Verde para trabalhado/esperado
|
||||
}
|
||||
}
|
||||
|
||||
// Destacar campos específicos
|
||||
if (campo === 'Dias com Saldo Negativo') {
|
||||
data.cell.styles.textColor = [200, 0, 0];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
if (campo === 'Dias com Saldo Positivo') {
|
||||
data.cell.styles.textColor = [0, 128, 0];
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
if (campo === 'Resultado Final') {
|
||||
if (resultadoFinalMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0];
|
||||
} else if (resultadoFinalMinutos > 0) {
|
||||
data.cell.styles.textColor = [0, 128, 0];
|
||||
}
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
if (campo === 'Saldo do Período Exibido') {
|
||||
if (saldoPeriodoDiferencaMinutos < 0) {
|
||||
data.cell.styles.textColor = [200, 0, 0];
|
||||
} else if (saldoPeriodoDiferencaMinutos > 0) {
|
||||
data.cell.styles.textColor = [0, 128, 0];
|
||||
}
|
||||
data.cell.styles.fontStyle = 'bold';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user