feat: implement error handling and logging in server hooks to capture and notify on 404 and 500 errors, enhancing server reliability and monitoring
This commit is contained in:
684
apps/web/src/lib/utils/ponto/processamento.ts
Normal file
684
apps/web/src/lib/utils/ponto/processamento.ts
Normal file
@@ -0,0 +1,684 @@
|
||||
import type { ConvexClient } from 'convex-svelte';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { DiaFichaPonto, ResumoPeriodo, RegistroPonto, TipoDia } from './tipos';
|
||||
import { calcularSaldoComparativoPorPar } from './calculos';
|
||||
import { registroFoiMarcado } from './validacao';
|
||||
import { formatarDataDDMMAAAA } from '../ponto';
|
||||
import { formatarMinutos, formatarHoras } from './formatacao';
|
||||
|
||||
/**
|
||||
* Gera array de todas as datas do período selecionado
|
||||
*/
|
||||
export 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
|
||||
*/
|
||||
export 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 }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrupa registros por funcionário e data
|
||||
*/
|
||||
export function agruparRegistrosPorFuncionario(
|
||||
registros: Array<{
|
||||
_id: Id<'registrosPonto'>;
|
||||
funcionarioId: Id<'funcionarios'>;
|
||||
data: string;
|
||||
funcionario?: { nome: string; matricula?: string; descricaoCargo?: string } | null;
|
||||
[key: string]: any;
|
||||
}>,
|
||||
config?: {
|
||||
horarioEntrada: string;
|
||||
horarioSaidaAlmoco: string;
|
||||
horarioRetornoAlmoco: string;
|
||||
horarioSaida: string;
|
||||
}
|
||||
): Array<{
|
||||
funcionario: { nome: string; matricula?: string; descricaoCargo?: string } | null;
|
||||
funcionarioId: Id<'funcionarios'>;
|
||||
registrosPorData: Record<
|
||||
string,
|
||||
{
|
||||
data: string;
|
||||
registros: typeof registros;
|
||||
saldoDiario?: {
|
||||
saldoMinutos: number;
|
||||
horas: number;
|
||||
minutos: number;
|
||||
positivo: boolean;
|
||||
};
|
||||
saldoDiarioComparativo?: {
|
||||
trabalhadoMinutos: number;
|
||||
esperadoMinutos: number;
|
||||
diferencaMinutos: number;
|
||||
};
|
||||
}
|
||||
>;
|
||||
}> {
|
||||
const agrupados: Record<
|
||||
string,
|
||||
{
|
||||
funcionario: { nome: string; matricula?: string; descricaoCargo?: string } | null;
|
||||
funcionarioId: Id<'funcionarios'>;
|
||||
registrosPorData: Record<
|
||||
string,
|
||||
{
|
||||
data: string;
|
||||
registros: typeof registros;
|
||||
saldoDiario?: {
|
||||
saldoMinutos: number;
|
||||
horas: number;
|
||||
minutos: number;
|
||||
positivo: boolean;
|
||||
};
|
||||
saldoDiarioComparativo?: {
|
||||
trabalhadoMinutos: number;
|
||||
esperadoMinutos: number;
|
||||
diferencaMinutos: number;
|
||||
};
|
||||
}
|
||||
>;
|
||||
}
|
||||
> = {};
|
||||
|
||||
const registrosProcessados = new Set<string>();
|
||||
|
||||
if (!Array.isArray(registros) || registros.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const registro of registros) {
|
||||
if (!registro || !registro._id || !registro.funcionarioId || !registro.data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const chaveUnica = `${registro._id}`;
|
||||
if (registrosProcessados.has(chaveUnica)) {
|
||||
continue;
|
||||
}
|
||||
registrosProcessados.add(chaveUnica);
|
||||
|
||||
const key = registro.funcionarioId;
|
||||
if (!agrupados[key]) {
|
||||
agrupados[key] = {
|
||||
funcionario: registro.funcionario,
|
||||
funcionarioId: registro.funcionarioId,
|
||||
registrosPorData: {}
|
||||
};
|
||||
}
|
||||
|
||||
const dataKey = registro.data;
|
||||
if (!agrupados[key]!.registrosPorData[dataKey]) {
|
||||
agrupados[key]!.registrosPorData[dataKey] = {
|
||||
data: dataKey,
|
||||
registros: [],
|
||||
saldoDiario: undefined
|
||||
};
|
||||
}
|
||||
|
||||
const jaExiste = agrupados[key]!.registrosPorData[dataKey]!.registros.some(
|
||||
(r) => r._id === registro._id
|
||||
);
|
||||
if (!jaExiste) {
|
||||
agrupados[key]!.registrosPorData[dataKey]!.registros.push(registro);
|
||||
}
|
||||
}
|
||||
|
||||
const resultado = Object.values(agrupados);
|
||||
|
||||
resultado.sort((a, b) => {
|
||||
const nomeA = a.funcionario?.nome || '';
|
||||
const nomeB = b.funcionario?.nome || '';
|
||||
return nomeA.localeCompare(nomeB, 'pt-BR');
|
||||
});
|
||||
|
||||
for (const grupo of resultado) {
|
||||
const datasOrdenadas = Object.keys(grupo.registrosPorData).sort((a, b) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
const registrosPorDataOrdenado: Record<string, (typeof grupo.registrosPorData)[string]> = {};
|
||||
for (const dataKey of datasOrdenadas) {
|
||||
registrosPorDataOrdenado[dataKey] = grupo.registrosPorData[dataKey]!;
|
||||
}
|
||||
grupo.registrosPorData = registrosPorDataOrdenado;
|
||||
|
||||
for (const dataKey in grupo.registrosPorData) {
|
||||
const grupoData = grupo.registrosPorData[dataKey];
|
||||
if (grupoData && grupoData.registros.length > 0) {
|
||||
grupoData.registros.sort((a, b) => {
|
||||
if (a.hora !== b.hora) {
|
||||
return a.hora - b.hora;
|
||||
}
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
|
||||
if (config) {
|
||||
const regsReaisOrdenados = [...grupoData.registros].sort((a, b) => {
|
||||
if (a.hora !== b.hora) return a.hora - b.hora;
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
const saldosComparativosPorPar = calcularSaldoComparativoPorPar(regsReaisOrdenados, config);
|
||||
|
||||
let totalTrabalhado = 0;
|
||||
const paresProcessados = new Set<number>();
|
||||
for (const [, saldo] of saldosComparativosPorPar.entries()) {
|
||||
if (!paresProcessados.has(saldo.parIndex)) {
|
||||
totalTrabalhado += saldo.trabalhadoMinutos;
|
||||
paresProcessados.add(saldo.parIndex);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const minutosPar1EsperadoConfig =
|
||||
horaSaidaAlmocoConfig * 60 +
|
||||
minutoSaidaAlmocoConfig -
|
||||
(horaEntradaConfig * 60 + minutoEntradaConfig);
|
||||
const minutosPar1EsperadoAjustadoConfig =
|
||||
minutosPar1EsperadoConfig < 0
|
||||
? minutosPar1EsperadoConfig + 24 * 60
|
||||
: minutosPar1EsperadoConfig;
|
||||
|
||||
const minutosPar2EsperadoConfig =
|
||||
horaSaidaConfig * 60 +
|
||||
minutoSaidaConfig -
|
||||
(horaRetornoAlmocoConfig * 60 + minutoRetornoAlmocoConfig);
|
||||
const minutosPar2EsperadoAjustadoConfig =
|
||||
minutosPar2EsperadoConfig < 0
|
||||
? minutosPar2EsperadoConfig + 24 * 60
|
||||
: minutosPar2EsperadoConfig;
|
||||
|
||||
const cargaHorariaDiariaEsperadaMinutos =
|
||||
minutosPar1EsperadoAjustadoConfig + minutosPar2EsperadoAjustadoConfig;
|
||||
|
||||
const diferencaMinutos = totalTrabalhado - cargaHorariaDiariaEsperadaMinutos;
|
||||
|
||||
grupoData.saldoDiarioComparativo = {
|
||||
trabalhadoMinutos: totalTrabalhado,
|
||||
esperadoMinutos: cargaHorariaDiariaEsperadaMinutos,
|
||||
diferencaMinutos: diferencaMinutos
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultado.filter((grupo) => {
|
||||
const temRegistros = Object.values(grupo.registrosPorData).some(
|
||||
(grupoData) => grupoData.registros && grupoData.registros.length > 0
|
||||
);
|
||||
return temRegistros;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa dados para ficha de ponto
|
||||
* Esta é uma função grande que processa todos os dados necessários para gerar a ficha
|
||||
*/
|
||||
export async function processarDadosFichaPonto(
|
||||
client: ConvexClient,
|
||||
funcionarioId: Id<'funcionarios'>,
|
||||
dataInicio: string,
|
||||
dataFim: string
|
||||
): Promise<{
|
||||
dias: DiaFichaPonto[];
|
||||
resumo: ResumoPeriodo;
|
||||
config: {
|
||||
horarioEntrada: string;
|
||||
horarioSaidaAlmoco: string;
|
||||
horarioRetornoAlmoco: string;
|
||||
horarioSaida: string;
|
||||
};
|
||||
}> {
|
||||
// Buscar todos os dados necessários
|
||||
const [
|
||||
registrosFuncionario,
|
||||
atestadosLicencas,
|
||||
ausenciasTodas,
|
||||
ajustes,
|
||||
inconsistencias,
|
||||
homologacoes,
|
||||
dispensas,
|
||||
config
|
||||
] = await Promise.all([
|
||||
client.query(api.pontos.listarRegistrosPeriodo, {
|
||||
funcionarioId,
|
||||
dataInicio,
|
||||
dataFim
|
||||
}),
|
||||
client.query(api.atestadosLicencas.listarPorFuncionario, {
|
||||
funcionarioId
|
||||
}),
|
||||
client.query(api.ausencias.listarTodas, {}),
|
||||
client.query(api.pontos.listarAjustesBancoHoras, {
|
||||
funcionarioId
|
||||
}),
|
||||
client.query(api.pontos.listarInconsistenciasBancoHoras, {}),
|
||||
client.query(api.pontos.listarHomologacoes, {
|
||||
funcionarioId
|
||||
}),
|
||||
client.query(api.pontos.listarDispensas, {
|
||||
funcionarioId,
|
||||
apenasAtivas: false
|
||||
}),
|
||||
client.query(api.configuracaoPonto.obterConfiguracao, {})
|
||||
]);
|
||||
|
||||
const atestados = atestadosLicencas?.atestados || [];
|
||||
const licencas = atestadosLicencas?.licencas || [];
|
||||
const ausencias = (ausenciasTodas || []).filter((a) => a.funcionarioId === funcionarioId);
|
||||
|
||||
if (!config) {
|
||||
throw new Error('Configuração de ponto não encontrada');
|
||||
}
|
||||
|
||||
// Filtrar dados pelo período
|
||||
const dataInicioObj = new Date(dataInicio + 'T00:00:00');
|
||||
const dataFimObj = new Date(dataFim + 'T23:59:59');
|
||||
|
||||
const atestadosPeriodo = (atestados || []).filter((a) => {
|
||||
const inicio = new Date(a.dataInicio);
|
||||
const fim = new Date(a.dataFim);
|
||||
return inicio <= dataFimObj && fim >= dataInicioObj;
|
||||
});
|
||||
|
||||
const ausenciasPeriodo = (ausencias || []).filter((a) => {
|
||||
const inicio = new Date(a.dataInicio);
|
||||
const fim = new Date(a.dataFim);
|
||||
return inicio <= dataFimObj && fim >= dataInicioObj;
|
||||
});
|
||||
|
||||
const licencasPeriodo = (licencas || []).filter((l) => {
|
||||
const inicio = new Date(l.dataInicio);
|
||||
const fim = new Date(l.dataFim);
|
||||
return inicio <= dataFimObj && fim >= dataInicioObj;
|
||||
});
|
||||
|
||||
const ajustesPeriodo = (ajustes || []).filter((a) => {
|
||||
const dataAjuste = new Date(a.dataAplicacao);
|
||||
return dataAjuste >= dataInicioObj && dataAjuste <= dataFimObj;
|
||||
});
|
||||
|
||||
const inconsistenciasPeriodo = (inconsistencias || []).filter((i) => {
|
||||
if (i.funcionarioId !== funcionarioId) return false;
|
||||
const dataInconsistencia = new Date(i.dataDetectada);
|
||||
return dataInconsistencia >= dataInicioObj && dataInconsistencia <= dataFimObj;
|
||||
});
|
||||
|
||||
const dataInicioTimestamp = dataInicioObj.getTime();
|
||||
const dataFimTimestamp = dataFimObj.getTime();
|
||||
const homologacoesPeriodo = (homologacoes || []).filter((h) => {
|
||||
return h.criadoEm >= dataInicioTimestamp && h.criadoEm <= dataFimTimestamp;
|
||||
});
|
||||
|
||||
const dispensasPeriodo = (dispensas || []).filter((d) => {
|
||||
const dispensaInicio = new Date(d.dataInicio + 'T00:00:00');
|
||||
const dispensaFim = new Date(d.dataFim + 'T23:59:59');
|
||||
return dispensaInicio <= dataFimObj && dispensaFim >= dataInicioObj;
|
||||
});
|
||||
|
||||
// Gerar todos os dias do período
|
||||
const diasPeriodo = gerarDiasPeriodo(dataInicio, dataFim);
|
||||
const diasProcessados: DiaFichaPonto[] = [];
|
||||
|
||||
// Agrupar registros por data
|
||||
const registrosPorData: Record<string, RegistroPonto[]> = {};
|
||||
for (const r of registrosFuncionario || []) {
|
||||
if (!registrosPorData[r.data]) {
|
||||
registrosPorData[r.data] = [];
|
||||
}
|
||||
registrosPorData[r.data]!.push(r);
|
||||
}
|
||||
|
||||
// Processar cada dia
|
||||
for (const data of diasPeriodo) {
|
||||
const dataObj = new Date(data);
|
||||
const regsReais = registrosPorData[data] || [];
|
||||
const regsEsperados = gerarRegistrosEsperados(data, config);
|
||||
|
||||
// Verificar atestado
|
||||
const atestadoDia =
|
||||
atestadosPeriodo.find((a) => {
|
||||
const inicio = new Date(a.dataInicio);
|
||||
const fim = new Date(a.dataFim);
|
||||
return dataObj >= inicio && dataObj <= fim;
|
||||
}) || null;
|
||||
|
||||
// Verificar ausência
|
||||
const ausenciaDia =
|
||||
ausenciasPeriodo.find((a) => {
|
||||
const inicio = new Date(a.dataInicio);
|
||||
const fim = new Date(a.dataFim);
|
||||
return dataObj >= inicio && dataObj <= fim;
|
||||
}) || null;
|
||||
|
||||
// Verificar licença
|
||||
const licencaDia =
|
||||
licencasPeriodo.find((l) => {
|
||||
const inicio = new Date(l.dataInicio);
|
||||
const fim = new Date(l.dataFim);
|
||||
return dataObj >= inicio && dataObj <= fim;
|
||||
}) || null;
|
||||
|
||||
// Verificar ajustes do dia
|
||||
const ajustesDia = ajustesPeriodo.filter((a) => a.dataAplicacao === data);
|
||||
|
||||
// Verificar inconsistências do dia
|
||||
const inconsistenciasDia = inconsistenciasPeriodo.filter((i) => i.dataDetectada === data);
|
||||
|
||||
// Verificar homologações do dia
|
||||
const homologacoesDia = homologacoesPeriodo.filter((h) => {
|
||||
if (h.registroId) {
|
||||
const registro = regsReais.find((r) => r._id === h.registroId);
|
||||
return registro !== undefined;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Verificar dispensa
|
||||
const dispensaDia =
|
||||
dispensasPeriodo.find((d) => {
|
||||
const dispensaInicio = new Date(d.dataInicio + 'T00:00:00');
|
||||
const dispensaFim = new Date(d.dataFim + 'T23:59:59');
|
||||
return dataObj >= dispensaInicio && dataObj <= dispensaFim;
|
||||
}) || null;
|
||||
|
||||
// Calcular saldo diário
|
||||
const regsReaisOrdenados = [...regsReais].sort((a, b) => {
|
||||
if (a.hora !== b.hora) return a.hora - b.hora;
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
const saldosComparativosPorPar = calcularSaldoComparativoPorPar(regsReaisOrdenados, config);
|
||||
|
||||
let saldoDiario: { diferencaMinutos: number; trabalhadoMinutos: number; esperadoMinutos: number } | null = null;
|
||||
let saldoDiarioTotalDiferencaMinutos = 0;
|
||||
let saldoDiarioTotalTrabalhadoMinutos = 0;
|
||||
let saldoDiarioTotalEsperadoMinutos = 0;
|
||||
|
||||
// Somar saldos dos pares
|
||||
const paresProcessados = new Set<number>();
|
||||
for (const [, saldo] of saldosComparativosPorPar.entries()) {
|
||||
if (!paresProcessados.has(saldo.parIndex)) {
|
||||
saldoDiarioTotalDiferencaMinutos += saldo.diferencaMinutos;
|
||||
saldoDiarioTotalTrabalhadoMinutos += saldo.trabalhadoMinutos;
|
||||
saldoDiarioTotalEsperadoMinutos += saldo.esperadoMinutos;
|
||||
paresProcessados.add(saldo.parIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular saldo para pares não marcados
|
||||
const todosRegistros: Array<{ tipo: string; hora: number; minuto: number; real: boolean }> = [];
|
||||
for (const reg of regsReais) {
|
||||
todosRegistros.push({
|
||||
tipo: reg.tipo,
|
||||
hora: reg.hora,
|
||||
minuto: reg.minuto,
|
||||
real: true
|
||||
});
|
||||
}
|
||||
for (const regEsperado of regsEsperados) {
|
||||
if (!registroFoiMarcado(regEsperado, regsReais)) {
|
||||
todosRegistros.push({
|
||||
tipo: regEsperado.tipo,
|
||||
hora: regEsperado.hora,
|
||||
minuto: regEsperado.minuto,
|
||||
real: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Identificar pares não marcados e calcular saldo negativo
|
||||
for (let i = 0; i < todosRegistros.length; i++) {
|
||||
const reg = todosRegistros[i];
|
||||
if ((reg.tipo === 'entrada' || reg.tipo === 'retorno_almoco') && !reg.real) {
|
||||
const tipoSaidaEsperado = reg.tipo === 'entrada' ? 'saida_almoco' : 'saida';
|
||||
const saidaEsperada = todosRegistros.find((r, idx) => {
|
||||
if (idx <= i) return false;
|
||||
if (r.tipo !== tipoSaidaEsperado || r.real) return false;
|
||||
const minutosEntrada = reg.hora * 60 + reg.minuto;
|
||||
const minutosSaidaEsperada = r.hora * 60 + r.minuto;
|
||||
const temRegistroRealNoIntervalo = regsReais.some((real) => {
|
||||
if (real.tipo !== tipoSaidaEsperado) return false;
|
||||
const minutosReal = real.hora * 60 + real.minuto;
|
||||
return minutosReal >= minutosEntrada && minutosReal < minutosSaidaEsperada;
|
||||
});
|
||||
return !temRegistroRealNoIntervalo;
|
||||
});
|
||||
|
||||
if (saidaEsperada) {
|
||||
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;
|
||||
}
|
||||
|
||||
saldoDiarioTotalDiferencaMinutos -= esperadoMinutos;
|
||||
saldoDiarioTotalEsperadoMinutos += esperadoMinutos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplicar ajustes manuais
|
||||
for (const ajuste of ajustesDia) {
|
||||
if (ajuste.tipo === 'abonar') {
|
||||
saldoDiarioTotalDiferencaMinutos += ajuste.valorMinutos;
|
||||
} else if (ajuste.tipo === 'descontar') {
|
||||
saldoDiarioTotalDiferencaMinutos -= ajuste.valorMinutos;
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular diferença final
|
||||
const diferencaDiariaCorrigida =
|
||||
saldoDiarioTotalTrabalhadoMinutos - saldoDiarioTotalEsperadoMinutos;
|
||||
|
||||
saldoDiario = {
|
||||
diferencaMinutos: diferencaDiariaCorrigida,
|
||||
trabalhadoMinutos: saldoDiarioTotalTrabalhadoMinutos,
|
||||
esperadoMinutos: saldoDiarioTotalEsperadoMinutos
|
||||
};
|
||||
|
||||
// Determinar tipo de dia
|
||||
let tipoDia: TipoDia = 'normal';
|
||||
let computado = true;
|
||||
|
||||
if (dispensaDia) {
|
||||
tipoDia = 'nao_computado';
|
||||
computado = false;
|
||||
} else if (licencaDia) {
|
||||
tipoDia = 'licenca';
|
||||
computado = false;
|
||||
} else if (atestadoDia) {
|
||||
tipoDia = 'atestado';
|
||||
computado = false;
|
||||
} else if (ausenciaDia) {
|
||||
tipoDia = 'ausencia';
|
||||
computado = false;
|
||||
} else if (ajustesDia.some((a) => a.tipo === 'abonar' && a.valorMinutos >= 240)) {
|
||||
tipoDia = 'abonado';
|
||||
}
|
||||
|
||||
if (inconsistenciasDia.length > 0) {
|
||||
tipoDia = 'inconsistente';
|
||||
}
|
||||
|
||||
diasProcessados.push({
|
||||
data,
|
||||
dataFormatada: formatarDataDDMMAAAA(data),
|
||||
tipoDia,
|
||||
registros: regsReais,
|
||||
registrosEsperados: regsEsperados,
|
||||
saldoDiario,
|
||||
saldoAcumulado: 0, // Será calculado depois
|
||||
atestado: atestadoDia
|
||||
? {
|
||||
_id: atestadoDia._id,
|
||||
tipo: atestadoDia.tipo,
|
||||
dataInicio: atestadoDia.dataInicio,
|
||||
dataFim: atestadoDia.dataFim,
|
||||
motivo: atestadoDia.observacoes
|
||||
}
|
||||
: null,
|
||||
ausencia: ausenciaDia
|
||||
? {
|
||||
_id: ausenciaDia._id,
|
||||
motivo: ausenciaDia.motivo,
|
||||
dataInicio: ausenciaDia.dataInicio,
|
||||
dataFim: ausenciaDia.dataFim,
|
||||
status: ausenciaDia.status
|
||||
}
|
||||
: null,
|
||||
licenca: licencaDia
|
||||
? {
|
||||
_id: licencaDia._id,
|
||||
tipo: licencaDia.tipo || 'licenca',
|
||||
dataInicio: licencaDia.dataInicio,
|
||||
dataFim: licencaDia.dataFim
|
||||
}
|
||||
: null,
|
||||
ajustes: ajustesDia.map((a) => ({
|
||||
_id: a._id,
|
||||
tipo: a.tipo,
|
||||
valorMinutos: a.valorMinutos,
|
||||
motivoDescricao: a.motivoDescricao,
|
||||
gestorId: a.gestorId
|
||||
})),
|
||||
inconsistencias: inconsistenciasDia.map((i) => ({
|
||||
_id: i._id,
|
||||
tipo: i.tipo,
|
||||
descricao: i.descricao,
|
||||
dataDetectada: i.dataDetectada,
|
||||
status: i.status,
|
||||
resolvidoPor: i.resolvidoPor,
|
||||
resolvidoEm: i.resolvidoEm
|
||||
})),
|
||||
homologacoes: homologacoesDia.map((h) => ({
|
||||
_id: h._id,
|
||||
motivoDescricao: h.motivoDescricao,
|
||||
gestorId: h.gestorId
|
||||
})),
|
||||
dispensa: dispensaDia
|
||||
? {
|
||||
_id: dispensaDia._id,
|
||||
motivo: dispensaDia.motivo,
|
||||
dataInicio: dispensaDia.dataInicio,
|
||||
dataFim: dispensaDia.dataFim,
|
||||
ativo: dispensaDia.ativo
|
||||
}
|
||||
: null,
|
||||
computado
|
||||
});
|
||||
}
|
||||
|
||||
// Calcular saldo acumulado para cada dia
|
||||
let saldoAcumulado = 0;
|
||||
|
||||
for (const dia of diasProcessados) {
|
||||
if (dia.computado && dia.saldoDiario) {
|
||||
saldoAcumulado += dia.saldoDiario.diferencaMinutos;
|
||||
}
|
||||
dia.saldoAcumulado = saldoAcumulado;
|
||||
}
|
||||
|
||||
// Calcular resumo com formatações
|
||||
const totalHorasTrabalhadas = diasProcessados
|
||||
.filter((d) => d.computado)
|
||||
.reduce((acc, d) => acc + (d.saldoDiario?.trabalhadoMinutos || 0), 0);
|
||||
const totalHorasEsperadas = diasProcessados
|
||||
.filter((d) => d.computado)
|
||||
.reduce((acc, d) => acc + (d.saldoDiario?.esperadoMinutos || 0), 0);
|
||||
const diferencaTotal = diasProcessados
|
||||
.filter((d) => d.computado)
|
||||
.reduce((acc, d) => acc + (d.saldoDiario?.diferencaMinutos || 0), 0);
|
||||
const saldoPeriodo = diferencaTotal;
|
||||
const saldoFinal =
|
||||
diasProcessados.length > 0 ? diasProcessados[diasProcessados.length - 1]!.saldoAcumulado : 0;
|
||||
|
||||
const resumo: ResumoPeriodo = {
|
||||
totalDias: diasProcessados.length,
|
||||
diasTrabalhados: diasProcessados.filter((d) => d.computado && d.registros.length > 0).length,
|
||||
diasComAtestado: diasProcessados.filter((d) => d.atestado !== null).length,
|
||||
diasAusentes: diasProcessados.filter((d) => d.ausencia !== null).length,
|
||||
diasComLicenca: diasProcessados.filter((d) => d.licenca !== null).length,
|
||||
diasAbonados: diasProcessados.filter((d) => d.tipoDia === 'abonado').length,
|
||||
diasNaoComputados: diasProcessados.filter((d) => !d.computado).length,
|
||||
diasComInconsistencia: diasProcessados.filter((d) => d.inconsistencias.length > 0).length,
|
||||
totalHorasTrabalhadas,
|
||||
totalHorasEsperadas,
|
||||
diferencaTotal,
|
||||
saldoInicial: 0,
|
||||
saldoFinal,
|
||||
saldoPeriodo,
|
||||
totalInconsistencias: inconsistenciasPeriodo.length,
|
||||
saldoInicialFormatado: formatarMinutos(0),
|
||||
saldoPeriodoFormatado: formatarMinutos(saldoPeriodo),
|
||||
saldoFinalFormatado: formatarMinutos(saldoFinal),
|
||||
totalHorasTrabalhadasFormatado: formatarHoras(totalHorasTrabalhadas),
|
||||
totalHorasEsperadasFormatado: formatarHoras(totalHorasEsperadas),
|
||||
diferencaTotalFormatado: formatarMinutos(diferencaTotal)
|
||||
};
|
||||
|
||||
return {
|
||||
dias: diasProcessados,
|
||||
resumo,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user