feat: enhance registro-pontos page with date range handling and expected record generation
- Introduced functions to generate all dates within a selected period and to create expected records based on user configuration. - Updated the logic to process daily records, combining real and expected entries, and calculating daily balances. - Enhanced table rendering to visually distinguish between marked and unmarked records, improving user experience and clarity in time management.
This commit is contained in:
@@ -596,6 +596,55 @@
|
|||||||
return saldos;
|
return saldos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gera array de todas as datas do período selecionado
|
||||||
|
*/
|
||||||
|
function gerarDiasPeriodo(dataInicio: string, dataFim: string): string[] {
|
||||||
|
const dias: string[] = [];
|
||||||
|
const inicio = new Date(dataInicio);
|
||||||
|
const fim = new Date(dataFim);
|
||||||
|
|
||||||
|
for (let d = new Date(inicio); d <= fim; d.setDate(d.getDate() + 1)) {
|
||||||
|
dias.push(d.toISOString().split('T')[0]!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gera registros esperados para um dia baseado na configuração
|
||||||
|
*/
|
||||||
|
function gerarRegistrosEsperados(data: string, config: {
|
||||||
|
horarioEntrada: string;
|
||||||
|
horarioSaidaAlmoco: string;
|
||||||
|
horarioRetornoAlmoco: string;
|
||||||
|
horarioSaida: string;
|
||||||
|
}): Array<{ tipo: string; hora: number; minuto: number; data: string }> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ tipo: 'entrada', hora: horaEntrada, minuto: minutoEntrada, data },
|
||||||
|
{ tipo: 'saida_almoco', hora: horaSaidaAlmoco, minuto: minutoSaidaAlmoco, data },
|
||||||
|
{ tipo: 'retorno_almoco', hora: horaRetornoAlmoco, minuto: minutoRetornoAlmoco, data },
|
||||||
|
{ tipo: 'saida', hora: horaSaida, minuto: minutoSaida, data },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se um registro esperado foi marcado
|
||||||
|
*/
|
||||||
|
function registroFoiMarcado(
|
||||||
|
registroEsperado: { tipo: string; hora: number; minuto: number },
|
||||||
|
registrosReais: Array<{ tipo: string; hora: number; minuto: number }>
|
||||||
|
): boolean {
|
||||||
|
return registrosReais.some(
|
||||||
|
(r) => r.tipo === registroEsperado.tipo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function abrirModalImpressao(funcionarioId: Id<'funcionarios'>) {
|
function abrirModalImpressao(funcionarioId: Id<'funcionarios'>) {
|
||||||
funcionarioParaImprimir = funcionarioId;
|
funcionarioParaImprimir = funcionarioId;
|
||||||
mostrarModalImpressao = true;
|
mostrarModalImpressao = true;
|
||||||
@@ -840,12 +889,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variável para armazenar saldos diários (usada no resumo do banco de horas)
|
||||||
|
const saldosDiariosPorData: Record<string, number> = {};
|
||||||
|
|
||||||
// Tabela de registros
|
// Tabela de registros
|
||||||
if (sections.registrosPonto) {
|
if (sections.registrosPonto) {
|
||||||
const config = await client.query(api.configuracaoPonto.obterConfiguracao, {});
|
const config = await client.query(api.configuracaoPonto.obterConfiguracao, {});
|
||||||
const tableData: string[][] = [];
|
if (!config) {
|
||||||
|
throw new Error('Configuração de ponto não encontrada');
|
||||||
|
}
|
||||||
|
|
||||||
// Agrupar por data para incluir saldo diário
|
const tableData: any[][] = [];
|
||||||
|
|
||||||
|
// Agrupar registros reais por data
|
||||||
const registrosPorData: Record<
|
const registrosPorData: Record<
|
||||||
string,
|
string,
|
||||||
Array<{
|
Array<{
|
||||||
@@ -885,24 +941,152 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Criar dados da tabela com saldo diário por par
|
// Gerar todos os dias do período
|
||||||
for (const [data, regs] of Object.entries(registrosPorData)) {
|
const diasPeriodo = gerarDiasPeriodo(dataInicio, dataFim);
|
||||||
// Formatar data para exibição usando função centralizada (DD/MM/AAAA)
|
|
||||||
const dataFormatada = formatarDataDDMMAAAA(data);
|
|
||||||
|
|
||||||
// Ordenar registros por hora e minuto para garantir ordem correta
|
// Processar cada dia do período
|
||||||
const regsOrdenados = [...regs].sort((a, b) => {
|
for (const data of diasPeriodo) {
|
||||||
|
const dataFormatada = formatarDataDDMMAAAA(data);
|
||||||
|
const regsReais = registrosPorData[data] || [];
|
||||||
|
const regsEsperados = gerarRegistrosEsperados(data, config);
|
||||||
|
|
||||||
|
// Combinar registros reais e esperados
|
||||||
|
const todosRegistros: Array<{
|
||||||
|
tipo: string;
|
||||||
|
hora: number;
|
||||||
|
minuto: number;
|
||||||
|
real: boolean;
|
||||||
|
dentroDoPrazo?: boolean;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
// Adicionar registros reais
|
||||||
|
for (const reg of regsReais) {
|
||||||
|
todosRegistros.push({
|
||||||
|
tipo: reg.tipo,
|
||||||
|
hora: reg.hora,
|
||||||
|
minuto: reg.minuto,
|
||||||
|
real: true,
|
||||||
|
dentroDoPrazo: reg.dentroDoPrazo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adicionar registros esperados não marcados (em vermelho)
|
||||||
|
for (const regEsperado of regsEsperados) {
|
||||||
|
if (!registroFoiMarcado(regEsperado, regsReais)) {
|
||||||
|
todosRegistros.push({
|
||||||
|
tipo: regEsperado.tipo,
|
||||||
|
hora: regEsperado.hora,
|
||||||
|
minuto: regEsperado.minuto,
|
||||||
|
real: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenar todos os registros por hora e minuto
|
||||||
|
todosRegistros.sort((a, b) => {
|
||||||
if (a.hora !== b.hora) {
|
if (a.hora !== b.hora) {
|
||||||
return a.hora - b.hora;
|
return a.hora - b.hora;
|
||||||
}
|
}
|
||||||
return a.minuto - b.minuto;
|
return a.minuto - b.minuto;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calcular saldos por par entrada/saída
|
// Calcular saldos por par entrada/saída (apenas com registros reais)
|
||||||
const saldosPorPar = calcularSaldosPorPar(regsOrdenados);
|
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);
|
||||||
|
|
||||||
for (let i = 0; i < regsOrdenados.length; i++) {
|
// Calcular saldos esperados para pares incompletos ou dias sem registros
|
||||||
const reg = regsOrdenados[i];
|
const saldosEsperadosPorPar: Map<number, { saldoMinutos: number; horas: number; minutos: number; tamanhoPar: number; incompleto: boolean }> = new Map();
|
||||||
|
|
||||||
|
// Identificar pares incompletos e calcular saldos esperados
|
||||||
|
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 há saída correspondente real
|
||||||
|
const tipoSaidaEsperado = reg.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||||
|
const saidaEncontrada = regsReais.find(r => r.tipo === tipoSaidaEsperado);
|
||||||
|
|
||||||
|
if (!saidaEncontrada) {
|
||||||
|
// Par incompleto: calcular saldo esperado baseado na configuração
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const horas = Math.floor(saldoMinutos / 60);
|
||||||
|
const minutos = saldoMinutos % 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
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contar registros do par (entrada + saída esperada)
|
||||||
|
let tamanhoPar = 1; // entrada
|
||||||
|
const saidaEsperadaNaLista = todosRegistros.find(
|
||||||
|
(r, idx) => idx > indexNaListaTodos && r.tipo === tipoSaidaEsperado && !r.real
|
||||||
|
);
|
||||||
|
if (saidaEsperadaNaLista) {
|
||||||
|
tamanhoPar++; // saída faltante já está na lista
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexNaListaTodos >= 0) {
|
||||||
|
saldosEsperadosPorPar.set(indexNaListaTodos, {
|
||||||
|
saldoMinutos,
|
||||||
|
horas,
|
||||||
|
minutos,
|
||||||
|
tamanhoPar,
|
||||||
|
incompleto: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcular saldo diário total (soma de todos os pares reais)
|
||||||
|
let saldoDiarioTotalMinutos = 0;
|
||||||
|
for (const saldo of saldosPorPar.values()) {
|
||||||
|
saldoDiarioTotalMinutos += saldo.saldoMinutos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se não há registros reais, calcular saldo esperado baseado na configuração
|
||||||
|
if (regsReais.length === 0) {
|
||||||
|
// Calcular saldo esperado do dia completo
|
||||||
|
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 minutosPar1Entrada = horaEntrada * 60 + minutoEntrada;
|
||||||
|
const minutosPar1Saida = horaSaidaAlmoco * 60 + minutoSaidaAlmoco;
|
||||||
|
let saldoPar1 = minutosPar1Saida - minutosPar1Entrada;
|
||||||
|
if (saldoPar1 < 0) saldoPar1 += 24 * 60;
|
||||||
|
|
||||||
|
// Par 2: retorno_almoco -> saida
|
||||||
|
const minutosPar2Entrada = horaRetornoAlmoco * 60 + minutoRetornoAlmoco;
|
||||||
|
const minutosPar2Saida = horaSaida * 60 + minutoSaida;
|
||||||
|
let saldoPar2 = minutosPar2Saida - minutosPar2Entrada;
|
||||||
|
if (saldoPar2 < 0) saldoPar2 += 24 * 60;
|
||||||
|
|
||||||
|
saldoDiarioTotalMinutos = saldoPar1 + saldoPar2;
|
||||||
|
}
|
||||||
|
|
||||||
|
saldosDiariosPorData[data] = saldoDiarioTotalMinutos;
|
||||||
|
|
||||||
|
// Criar linhas da tabela
|
||||||
|
for (let i = 0; i < todosRegistros.length; i++) {
|
||||||
|
const reg = todosRegistros[i];
|
||||||
const linha: any[] = [
|
const linha: any[] = [
|
||||||
dataFormatada,
|
dataFormatada,
|
||||||
config
|
config
|
||||||
@@ -916,28 +1100,110 @@
|
|||||||
formatarHoraPonto(reg.hora, reg.minuto),
|
formatarHoraPonto(reg.hora, reg.minuto),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Marcar linha como não marcada para aplicar cor vermelha depois
|
||||||
|
if (!reg.real) {
|
||||||
|
linha._naoMarcado = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Saldo Diário por par entrada/saída
|
// Saldo Diário por par entrada/saída
|
||||||
if (sections.saldoDiario) {
|
if (sections.saldoDiario) {
|
||||||
const saldoPar = saldosPorPar.get(i);
|
|
||||||
if (saldoPar) {
|
|
||||||
// Verificar se é o primeiro registro do par (entrada ou retorno_almoco)
|
|
||||||
const isInicioPar = reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco';
|
const isInicioPar = reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco';
|
||||||
|
|
||||||
if (isInicioPar) {
|
if (reg.real && isInicioPar) {
|
||||||
// Primeira linha do par: adicionar saldo com rowspan
|
const indexReal = regsReaisOrdenados.findIndex(
|
||||||
|
(r) => r.tipo === reg.tipo && r.hora === reg.hora && r.minuto === reg.minuto
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verificar se há saldo esperado (par incompleto)
|
||||||
|
const saldoEsperado = saldosEsperadosPorPar.get(i);
|
||||||
|
if (saldoEsperado) {
|
||||||
|
// Par incompleto: mostrar saldo esperado em vermelho
|
||||||
|
linha._saldoVermelho = true;
|
||||||
|
linha.push({
|
||||||
|
content: `+${saldoEsperado.horas}h ${saldoEsperado.minutos}min`,
|
||||||
|
rowSpan: saldoEsperado.tamanhoPar
|
||||||
|
});
|
||||||
|
} else if (indexReal >= 0) {
|
||||||
|
const saldoPar = saldosPorPar.get(indexReal);
|
||||||
|
if (saldoPar) {
|
||||||
linha.push({
|
linha.push({
|
||||||
content: `+${saldoPar.horas}h ${saldoPar.minutos}min`,
|
content: `+${saldoPar.horas}h ${saldoPar.minutos}min`,
|
||||||
rowSpan: saldoPar.tamanhoPar
|
rowSpan: saldoPar.tamanhoPar
|
||||||
});
|
});
|
||||||
}
|
|
||||||
// Outras linhas do par (saida_almoco ou saida): não adicionar coluna (o rowspan cobre)
|
|
||||||
} else {
|
} else {
|
||||||
// Registro sem par completo: mostrar "-" em célula individual
|
linha.push('-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
linha.push('-');
|
||||||
|
}
|
||||||
|
} else if (reg.real) {
|
||||||
|
// Saída real: não adicionar (já coberto pelo rowspan da entrada)
|
||||||
|
// Mas verificar se precisa adicionar '-' se não houver par completo
|
||||||
|
const tipoEntradaEsperado = reg.tipo === 'saida_almoco' ? 'entrada' : 'retorno_almoco';
|
||||||
|
const entradaReal = regsReais.find(r => r.tipo === tipoEntradaEsperado);
|
||||||
|
if (!entradaReal) {
|
||||||
|
linha.push('-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Registro não marcado: verificar se faz parte de um par incompleto ou dia sem registros
|
||||||
|
const isInicioPar = reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco';
|
||||||
|
|
||||||
|
if (isInicioPar) {
|
||||||
|
// Verificar se há saldo esperado para este par
|
||||||
|
const saldoEsperado = saldosEsperadosPorPar.get(i);
|
||||||
|
if (saldoEsperado) {
|
||||||
|
// Par incompleto: mostrar saldo esperado em vermelho
|
||||||
|
linha._saldoVermelho = true;
|
||||||
|
linha.push({
|
||||||
|
content: `+${saldoEsperado.horas}h ${saldoEsperado.minutos}min`,
|
||||||
|
rowSpan: saldoEsperado.tamanhoPar
|
||||||
|
});
|
||||||
|
} else if (regsReais.length === 0) {
|
||||||
|
// Dia sem registros: calcular saldo esperado completo
|
||||||
|
if (reg.tipo === 'entrada') {
|
||||||
|
// Par 1 completo esperado
|
||||||
|
const [horaEntrada, minutoEntrada] = config.horarioEntrada.split(':').map(Number);
|
||||||
|
const [horaSaidaAlmoco, minutoSaidaAlmoco] = config.horarioSaidaAlmoco.split(':').map(Number);
|
||||||
|
const minutosEntrada = horaEntrada * 60 + minutoEntrada;
|
||||||
|
const minutosSaida = horaSaidaAlmoco * 60 + minutoSaidaAlmoco;
|
||||||
|
let saldoMinutos = minutosSaida - minutosEntrada;
|
||||||
|
if (saldoMinutos < 0) saldoMinutos += 24 * 60;
|
||||||
|
const horas = Math.floor(saldoMinutos / 60);
|
||||||
|
const minutos = saldoMinutos % 60;
|
||||||
|
linha._saldoVermelho = true;
|
||||||
|
linha.push({
|
||||||
|
content: `+${horas}h ${minutos}min`,
|
||||||
|
rowSpan: 2 // entrada + saida_almoco
|
||||||
|
});
|
||||||
|
} else if (reg.tipo === 'retorno_almoco') {
|
||||||
|
// Par 2 completo esperado
|
||||||
|
const [horaRetornoAlmoco, minutoRetornoAlmoco] = config.horarioRetornoAlmoco.split(':').map(Number);
|
||||||
|
const [horaSaida, minutoSaida] = config.horarioSaida.split(':').map(Number);
|
||||||
|
const minutosEntrada = horaRetornoAlmoco * 60 + minutoRetornoAlmoco;
|
||||||
|
const minutosSaida = horaSaida * 60 + minutoSaida;
|
||||||
|
let saldoMinutos = minutosSaida - minutosEntrada;
|
||||||
|
if (saldoMinutos < 0) saldoMinutos += 24 * 60;
|
||||||
|
const horas = Math.floor(saldoMinutos / 60);
|
||||||
|
const minutos = saldoMinutos % 60;
|
||||||
|
linha._saldoVermelho = true;
|
||||||
|
linha.push({
|
||||||
|
content: `+${horas}h ${minutos}min`,
|
||||||
|
rowSpan: 2 // retorno_almoco + saida
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
linha.push('-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
linha.push('-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Saída sem entrada correspondente: não tem saldo
|
||||||
linha.push('-');
|
linha.push('-');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
linha.push(reg.dentroDoPrazo ? 'Sim' : 'Não');
|
linha.push(reg.real ? (reg.dentroDoPrazo ? 'Sim' : 'Não') : 'Não marcado');
|
||||||
|
|
||||||
tableData.push(linha);
|
tableData.push(linha);
|
||||||
}
|
}
|
||||||
@@ -959,6 +1225,23 @@
|
|||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||||
styles: { fontSize: 9 },
|
styles: { fontSize: 9 },
|
||||||
|
didParseCell: function(data) {
|
||||||
|
// Aplicar cor vermelha para registros não marcados
|
||||||
|
if (data.row.raw && (data.row.raw as any)._naoMarcado) {
|
||||||
|
// Aplicar cor vermelha nas colunas Tipo e Horário
|
||||||
|
if (data.column.index === 1 || data.column.index === 2) {
|
||||||
|
data.cell.styles.textColor = [200, 0, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Aplicar cor vermelha na coluna de saldo diário quando marcado
|
||||||
|
if (data.row.raw && (data.row.raw as any)._saldoVermelho) {
|
||||||
|
// Coluna de saldo diário (índice 3 se saldoDiario estiver ativo)
|
||||||
|
const indiceSaldoDiario = sections.saldoDiario ? 3 : -1;
|
||||||
|
if (data.column.index === indiceSaldoDiario) {
|
||||||
|
data.cell.styles.textColor = [200, 0, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calcular posição Y após a tabela
|
// Calcular posição Y após a tabela
|
||||||
@@ -986,8 +1269,15 @@
|
|||||||
funcionarioId,
|
funcionarioId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calcular saldo do período selecionado
|
// Calcular saldo do período selecionado baseado nos saldos diários calculados
|
||||||
let saldoPeriodoMinutos = 0;
|
let saldoPeriodoMinutos = 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;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: calcular a partir dos registros se não tiver saldos diários
|
||||||
const registrosPorDataPeriodo: Record<string, Array<{ tipo: string; hora: number; minuto: number }>> = {};
|
const registrosPorDataPeriodo: Record<string, Array<{ tipo: string; hora: number; minuto: number }>> = {};
|
||||||
|
|
||||||
for (const r of registrosFuncionario) {
|
for (const r of registrosFuncionario) {
|
||||||
@@ -1002,19 +1292,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Somar todos os saldos diários do período
|
|
||||||
for (const regs of Object.values(registrosPorDataPeriodo)) {
|
for (const regs of Object.values(registrosPorDataPeriodo)) {
|
||||||
const saldoDiario = calcularSaldoDiario(regs);
|
const saldoDiario = calcularSaldoDiario(regs);
|
||||||
if (saldoDiario) {
|
if (saldoDiario) {
|
||||||
saldoPeriodoMinutos += saldoDiario.saldoMinutos;
|
saldoPeriodoMinutos += saldoDiario.saldoMinutos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const horasPeriodo = Math.floor(Math.abs(saldoPeriodoMinutos) / 60);
|
const horasPeriodo = Math.floor(Math.abs(saldoPeriodoMinutos) / 60);
|
||||||
const minutosPeriodo = Math.abs(saldoPeriodoMinutos) % 60;
|
const minutosPeriodo = Math.abs(saldoPeriodoMinutos) % 60;
|
||||||
const sinalPeriodo = saldoPeriodoMinutos >= 0 ? '+' : '-';
|
const sinalPeriodo = saldoPeriodoMinutos >= 0 ? '+' : '-';
|
||||||
const saldoPeriodoFormatado = `${sinalPeriodo}${horasPeriodo}h ${minutosPeriodo}min`;
|
const saldoPeriodoFormatado = `${sinalPeriodo}${horasPeriodo}h ${minutosPeriodo}min`;
|
||||||
|
|
||||||
|
// Calcular total de dias do período selecionado
|
||||||
|
const diasPeriodo = gerarDiasPeriodo(dataInicio, dataFim);
|
||||||
|
const totalDiasPeriodo = diasPeriodo.length;
|
||||||
|
|
||||||
doc.setFontSize(12);
|
doc.setFontSize(12);
|
||||||
doc.setFont('helvetica', 'bold');
|
doc.setFont('helvetica', 'bold');
|
||||||
doc.setTextColor(41, 128, 185);
|
doc.setTextColor(41, 128, 185);
|
||||||
@@ -1044,7 +1338,7 @@
|
|||||||
bancoHorasData.push(['Horas a Pagar', `${horas}h ${minutos}min`]);
|
bancoHorasData.push(['Horas a Pagar', `${horas}h ${minutos}min`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bancoHorasData.push(['Total de Dias com Registro', `${bancoHoras.totalDias} dias`]);
|
bancoHorasData.push(['Total de Dias do Período', `${totalDiasPeriodo} dias`]);
|
||||||
|
|
||||||
// Criar tabela no mesmo estilo das outras seções
|
// Criar tabela no mesmo estilo das outras seções
|
||||||
autoTable(doc, {
|
autoTable(doc, {
|
||||||
|
|||||||
Reference in New Issue
Block a user