financeiro/financeiro-api/src/modules/movimentos/services/movimentos.service.js

1044 lines
26 KiB
JavaScript

const crypto = require('crypto');
const pool = require('../../../database/mysql');
const movimentosSaldoService = require('./movimentosSaldo.service');
const CAMPOS_MOVIMENTO_SELECT = `
cp.idcontasapagar,
cp.movimento,
cp.descricao,
cp.dataentrada,
cp.datavencimento,
cp.databaixa,
cp.valor,
cp.parcela,
cp.parcelas,
cp.status,
cp.idcentrodecustos,
cp.idbancos,
cp.idusuarios_cad,
cp.idusuarios_baixa,
cp.idclientes,
cp.idveiculosdetalhes,
cp.idbancos_p,
cp.idmovimentosfixos,
cp.competencia,
cp.grupo_parcelamento,
cp.origem,
cp.saldo_processado,
cp.observacao,
cp.insert_date,
cp.update_date,
cp.deleted_at,
b.descricao AS banco_descricao,
b.debito AS banco_debito,
cc.descricao AS centro_custo_descricao,
c.nome AS cliente_nome,
bp.descricao AS banco_referencia_descricao,
bp.debito AS banco_referencia_debito,
mf.descricao AS movimento_fixo_descricao
`;
const FROM_MOVIMENTO_JOIN = `
FROM contasapagar cp
LEFT JOIN bancos b ON b.idbancos = cp.idbancos
LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos
LEFT JOIN clientes c ON c.idclientes = cp.idclientes
LEFT JOIN bancos bp ON bp.idbancos = cp.idbancos_p
LEFT JOIN movimentosfixos mf ON mf.idmovimentosfixos = cp.idmovimentosfixos
`;
function normalizarNumeroOuNull(valor) {
if (valor === undefined || valor === null || valor === '') {
return null;
}
const numero = Number(valor);
if (!Number.isFinite(numero)) {
return null;
}
return numero;
}
function normalizarTextoOuNull(valor) {
if (valor === undefined || valor === null || valor === '') {
return null;
}
const texto = String(valor).trim();
return texto || null;
}
function limitarNumero(valor, padrao, minimo, maximo) {
const numero = Number(valor);
if (!Number.isFinite(numero)) {
return padrao;
}
if (numero < minimo) {
return minimo;
}
if (numero > maximo) {
return maximo;
}
return numero;
}
function hojeDataString() {
return new Date().toISOString().slice(0, 10);
}
function extrairCompetencia(data) {
if (!data) return null;
return String(data).slice(0, 7);
}
function addMesesDataString(data, meses) {
if (!data) return null;
const partes = String(data).slice(0, 10).split('-');
if (partes.length !== 3) {
return data;
}
const ano = Number(partes[0]);
const mes = Number(partes[1]);
const dia = Number(partes[2]);
const dataBase = new Date(ano, mes - 1, dia);
dataBase.setMonth(dataBase.getMonth() + meses);
const y = dataBase.getFullYear();
const m = String(dataBase.getMonth() + 1).padStart(2, '0');
const d = String(dataBase.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
function statusBaixado(status) {
return status === 'Pago' || status === 'Recebido';
}
function normalizarMovimentoParaSalvar(idUsuario, dados, extras = {}) {
const status = dados.status || null;
const baixado = statusBaixado(status);
return {
movimento: dados.movimento,
descricao: normalizarTextoOuNull(dados.descricao),
dataentrada: dados.dataentrada || null,
datavencimento: dados.datavencimento || null,
databaixa: baixado ? (dados.databaixa || hojeDataString()) : null,
valor: Number(dados.valor || 0),
parcela: Number(dados.parcela || 1),
parcelas: Number(dados.parcelas || 1),
status,
idcentrodecustos: normalizarNumeroOuNull(dados.idcentrodecustos),
idbancos: normalizarNumeroOuNull(dados.idbancos),
idusuarios_cad: extras.idusuarios_cad || dados.idusuarios_cad || idUsuario || null,
idusuarios_baixa: baixado
? (normalizarNumeroOuNull(dados.idusuarios_baixa) || idUsuario || null)
: null,
idclientes: normalizarNumeroOuNull(dados.idclientes),
idveiculosdetalhes: normalizarNumeroOuNull(dados.idveiculosdetalhes),
idbancos_p: normalizarNumeroOuNull(dados.idbancos_p),
idmovimentosfixos: normalizarNumeroOuNull(dados.idmovimentosfixos),
competencia: normalizarTextoOuNull(dados.competencia) || extrairCompetencia(dados.datavencimento),
grupo_parcelamento: normalizarTextoOuNull(dados.grupo_parcelamento) || extras.grupo_parcelamento || null,
origem: normalizarTextoOuNull(dados.origem) || extras.origem || 'manual',
saldo_processado: Number(dados.saldo_processado || 0),
observacao: normalizarTextoOuNull(dados.observacao),
};
}
function resolverCampoData(dataCampo) {
const camposPermitidos = {
dataentrada: 'cp.dataentrada',
datavencimento: 'cp.datavencimento',
databaixa: 'cp.databaixa',
insert_date: 'cp.insert_date',
update_date: 'cp.update_date',
deleted_at: 'cp.deleted_at',
};
return camposPermitidos[dataCampo] || 'cp.datavencimento';
}
function resolverOrdenacao(orderBy) {
const camposPermitidos = {
idcontasapagar: 'cp.idcontasapagar',
dataentrada: 'cp.dataentrada',
datavencimento: 'cp.datavencimento',
databaixa: 'cp.databaixa',
insert_date: 'cp.insert_date',
update_date: 'cp.update_date',
valor: 'cp.valor',
descricao: 'cp.descricao',
status: 'cp.status',
movimento: 'cp.movimento',
competencia: 'cp.competencia',
origem: 'cp.origem',
saldo_processado: 'cp.saldo_processado',
banco_descricao: 'b.descricao',
centro_custo_descricao: 'cc.descricao',
cliente_nome: 'c.nome',
};
return camposPermitidos[orderBy] || 'cp.datavencimento';
}
function resolverDirecao(orderDirection) {
return String(orderDirection || '').toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
}
function montarWhereMovimentos(filtros = {}) {
const where = [];
const params = [];
if (String(filtros.incluirExcluidos || '') !== '1') {
where.push('cp.deleted_at IS NULL');
}
if (filtros.idusuarios) {
where.push('b.idusuarios = ?');
params.push(Number(filtros.idusuarios));
}
if (filtros.movimento) {
where.push('cp.movimento = ?');
params.push(filtros.movimento);
}
if (filtros.status) {
where.push('cp.status = ?');
params.push(filtros.status);
}
if (filtros.idbancos) {
where.push('cp.idbancos = ?');
params.push(Number(filtros.idbancos));
}
if (filtros.idcentrodecustos) {
where.push('cp.idcentrodecustos = ?');
params.push(Number(filtros.idcentrodecustos));
}
if (filtros.idclientes) {
where.push('cp.idclientes = ?');
params.push(Number(filtros.idclientes));
}
if (filtros.idbancos_p) {
where.push('cp.idbancos_p = ?');
params.push(Number(filtros.idbancos_p));
}
if (filtros.idmovimentosfixos) {
where.push('cp.idmovimentosfixos = ?');
params.push(Number(filtros.idmovimentosfixos));
}
if (filtros.competencia) {
where.push('cp.competencia = ?');
params.push(filtros.competencia);
}
if (filtros.grupo_parcelamento) {
where.push('cp.grupo_parcelamento = ?');
params.push(filtros.grupo_parcelamento);
}
if (filtros.origem) {
where.push('cp.origem = ?');
params.push(filtros.origem);
}
if (filtros.saldo_processado !== undefined && filtros.saldo_processado !== null && filtros.saldo_processado !== '') {
where.push('cp.saldo_processado = ?');
params.push(Number(filtros.saldo_processado));
}
if (filtros.referenciaTipo === 'cliente') {
where.push('cp.idclientes IS NOT NULL');
}
if (filtros.referenciaTipo === 'banco') {
where.push('cp.idbancos_p IS NOT NULL');
}
if (filtros.referenciaTipo === 'sem_referencia') {
where.push('cp.idclientes IS NULL AND cp.idbancos_p IS NULL');
}
const campoData = resolverCampoData(filtros.dataCampo);
if (filtros.dataInicio) {
where.push(`DATE(${campoData}) >= ?`);
params.push(filtros.dataInicio);
}
if (filtros.dataFim) {
where.push(`DATE(${campoData}) <= ?`);
params.push(filtros.dataFim);
}
if (filtros.busca) {
where.push(`
(
cp.descricao LIKE ?
OR cp.observacao LIKE ?
OR cp.competencia LIKE ?
OR cp.origem LIKE ?
OR b.descricao LIKE ?
OR cc.descricao LIKE ?
OR c.nome LIKE ?
OR c.cpf_cnpj LIKE ?
OR bp.descricao LIKE ?
OR mf.descricao LIKE ?
)
`);
const termo = `%${String(filtros.busca).trim()}%`;
params.push(
termo,
termo,
termo,
termo,
termo,
termo,
termo,
termo,
termo,
termo
);
}
const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
return {
whereSql,
params,
};
}
async function listarMovimentos(filtros = {}) {
const limite = limitarNumero(filtros.limite, 20, 1, 100);
const page = limitarNumero(filtros.page, 1, 1, 999999);
const offset = filtros.offset !== undefined
? limitarNumero(filtros.offset, 0, 0, 999999999)
: (page - 1) * limite;
const orderBy = resolverOrdenacao(filtros.orderBy || filtros.dataCampo);
const orderDirection = resolverDirecao(filtros.orderDirection);
const { whereSql, params } = montarWhereMovimentos(filtros);
const [rows] = await pool.query(
`
SELECT
${CAMPOS_MOVIMENTO_SELECT}
${FROM_MOVIMENTO_JOIN}
${whereSql}
ORDER BY ${orderBy} ${orderDirection}, cp.idcontasapagar DESC
LIMIT ? OFFSET ?
`,
[...params, limite, offset]
);
const [countRows] = await pool.query(
`
SELECT COUNT(*) AS total
${FROM_MOVIMENTO_JOIN}
${whereSql}
`,
params
);
const [summaryRows] = await pool.query(
`
SELECT
COUNT(*) AS quantidade,
COALESCE(SUM(CASE WHEN cp.movimento = 'Entrada' THEN cp.valor ELSE 0 END), 0) AS totalEntradas,
COALESCE(SUM(CASE WHEN cp.movimento = 'Saida' THEN cp.valor ELSE 0 END), 0) AS totalSaidas,
COALESCE(SUM(CASE WHEN cp.movimento = 'Sangria' THEN cp.valor ELSE 0 END), 0) AS totalSangrias,
COALESCE(SUM(CASE WHEN cp.movimento = 'Estorno' THEN cp.valor ELSE 0 END), 0) AS totalEstornos,
COALESCE(SUM(CASE WHEN cp.status IN ('Pago', 'Recebido') THEN cp.valor ELSE 0 END), 0) AS totalBaixado,
COALESCE(SUM(CASE WHEN cp.status IN ('A pagar', 'A receber') THEN cp.valor ELSE 0 END), 0) AS totalAberto,
COALESCE(SUM(CASE WHEN cp.saldo_processado = 1 THEN 1 ELSE 0 END), 0) AS saldoProcessado,
COALESCE(SUM(
CASE
WHEN cp.movimento IN ('Entrada', 'Estorno') THEN cp.valor
WHEN cp.movimento IN ('Saida', 'Sangria') THEN -cp.valor
ELSE 0
END
), 0) AS saldo
${FROM_MOVIMENTO_JOIN}
${whereSql}
`,
params
);
const total = Number(countRows[0]?.total || 0);
const totalPages = Math.max(1, Math.ceil(total / limite));
return {
data: rows,
pagination: {
total,
limite,
offset,
page: Math.floor(offset / limite) + 1,
totalPages,
},
summary: {
quantidade: Number(summaryRows[0]?.quantidade || 0),
totalEntradas: Number(summaryRows[0]?.totalEntradas || 0),
totalSaidas: Number(summaryRows[0]?.totalSaidas || 0),
totalSangrias: Number(summaryRows[0]?.totalSangrias || 0),
totalEstornos: Number(summaryRows[0]?.totalEstornos || 0),
totalBaixado: Number(summaryRows[0]?.totalBaixado || 0),
totalAberto: Number(summaryRows[0]?.totalAberto || 0),
saldoProcessado: Number(summaryRows[0]?.saldoProcessado || 0),
saldo: Number(summaryRows[0]?.saldo || 0),
},
};
}
async function buscarMovimentoPorId(id, opcoes = {}) {
const executor = opcoes.connection || pool;
const incluirExcluidos = opcoes.incluirExcluidos === true;
const idUsuario = opcoes.idUsuario || null;
const params = [id];
let filtroUsuario = '';
if (idUsuario) {
filtroUsuario = 'AND b.idusuarios = ?';
params.push(Number(idUsuario));
}
const [rows] = await executor.query(
`
SELECT
${CAMPOS_MOVIMENTO_SELECT}
${FROM_MOVIMENTO_JOIN}
WHERE cp.idcontasapagar = ?
${incluirExcluidos ? '' : 'AND cp.deleted_at IS NULL'}
${filtroUsuario}
LIMIT 1
`,
params
);
return rows[0] || null;
}
async function buscarMovimentoPorIdParaUpdate(connection, id, idUsuario = null) {
const params = [id];
let filtroUsuario = '';
if (idUsuario) {
filtroUsuario = 'AND b.idusuarios = ?';
params.push(Number(idUsuario));
}
const [rows] = await connection.query(
`
SELECT
cp.*
FROM contasapagar cp
INNER JOIN bancos b ON b.idbancos = cp.idbancos
WHERE cp.idcontasapagar = ?
AND cp.deleted_at IS NULL
${filtroUsuario}
LIMIT 1
FOR UPDATE
`,
params
);
return rows[0] || null;
}
async function inserirAuditoriaMovimento(connection, dados) {
await connection.query(
`
INSERT INTO contasapagar_auditoria (
idcontasapagar,
acao,
dados_antes,
dados_depois,
idusuarios,
insert_date
) VALUES (?, ?, ?, ?, ?, NOW())
`,
[
dados.idcontasapagar || null,
dados.acao,
dados.dados_antes ? JSON.stringify(dados.dados_antes) : null,
dados.dados_depois ? JSON.stringify(dados.dados_depois) : null,
dados.idusuarios || null,
]
);
}
async function inserirMovimento(connection, movimento) {
const [result] = await connection.query(
`
INSERT INTO contasapagar (
movimento,
descricao,
dataentrada,
datavencimento,
databaixa,
valor,
parcela,
parcelas,
status,
idcentrodecustos,
idbancos,
idusuarios_cad,
idusuarios_baixa,
idclientes,
idveiculosdetalhes,
idbancos_p,
idmovimentosfixos,
competencia,
grupo_parcelamento,
origem,
saldo_processado,
observacao,
insert_date,
update_date,
deleted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), NULL)
`,
[
movimento.movimento,
movimento.descricao,
movimento.dataentrada,
movimento.datavencimento,
movimento.databaixa,
movimento.valor,
movimento.parcela,
movimento.parcelas,
movimento.status,
movimento.idcentrodecustos,
movimento.idbancos,
movimento.idusuarios_cad,
movimento.idusuarios_baixa,
movimento.idclientes,
movimento.idveiculosdetalhes,
movimento.idbancos_p,
movimento.idmovimentosfixos,
movimento.competencia,
movimento.grupo_parcelamento,
movimento.origem,
movimento.saldo_processado,
movimento.observacao,
]
);
return result.insertId;
}
async function atualizarMovimentoBase(connection, id, movimento, saldoProcessado) {
await connection.query(
`
UPDATE contasapagar
SET
movimento = ?,
descricao = ?,
dataentrada = ?,
datavencimento = ?,
databaixa = ?,
valor = ?,
parcela = ?,
parcelas = ?,
status = ?,
idcentrodecustos = ?,
idbancos = ?,
idusuarios_baixa = ?,
idclientes = ?,
idveiculosdetalhes = ?,
idbancos_p = ?,
idmovimentosfixos = ?,
competencia = ?,
grupo_parcelamento = ?,
origem = ?,
saldo_processado = ?,
observacao = ?,
update_date = NOW()
WHERE idcontasapagar = ?
`,
[
movimento.movimento,
movimento.descricao,
movimento.dataentrada,
movimento.datavencimento,
movimento.databaixa,
movimento.valor,
movimento.parcela,
movimento.parcelas,
movimento.status,
movimento.idcentrodecustos,
movimento.idbancos,
movimento.idusuarios_baixa,
movimento.idclientes,
movimento.idveiculosdetalhes,
movimento.idbancos_p,
movimento.idmovimentosfixos,
movimento.competencia,
movimento.grupo_parcelamento,
movimento.origem,
saldoProcessado,
movimento.observacao,
id,
]
);
}
async function verificarDuplicidade(dados = {}) {
const idIgnorar = normalizarNumeroOuNull(dados.idcontasapagar);
const valor = Number(dados.valor || 0);
const idcentrodecustos = normalizarNumeroOuNull(dados.idcentrodecustos);
const idbancos = normalizarNumeroOuNull(dados.idbancos);
const params = [
dados.movimento,
valor,
idcentrodecustos,
idbancos,
dados.datavencimento,
];
let filtroId = '';
if (idIgnorar) {
filtroId = 'AND idcontasapagar <> ?';
params.push(idIgnorar);
}
const [rows] = await pool.query(
`
SELECT
idcontasapagar,
movimento,
descricao,
valor,
status,
datavencimento,
idcentrodecustos,
idbancos
FROM contasapagar
WHERE deleted_at IS NULL
AND movimento = ?
AND valor = ?
AND (
(? IS NULL AND idcentrodecustos IS NULL)
OR idcentrodecustos = ?
)
AND (
(? IS NULL AND idbancos IS NULL)
OR idbancos = ?
)
AND DATE(datavencimento) = DATE(?)
${filtroId}
LIMIT 1
`,
[
dados.movimento,
valor,
idcentrodecustos,
idcentrodecustos,
idbancos,
idbancos,
dados.datavencimento,
...(idIgnorar ? [idIgnorar] : []),
]
);
return rows[0] || null;
}
function criarErroDuplicidade(movimentoDuplicado) {
const erro = new Error(
`Já existe um movimento parecido cadastrado: ID ${movimentoDuplicado.idcontasapagar}, descrição "${movimentoDuplicado.descricao}", situação "${movimentoDuplicado.status}".`
);
erro.code = 'DUPLICIDADE_MOVIMENTO';
erro.data = movimentoDuplicado;
return erro;
}
function montarParcelas(idUsuario, dados) {
const parcelas = Number(dados.parcelas || 1);
const parcelaInicial = Number(dados.parcela || 1);
const gerarParcelas = dados.gerarParcelas !== false && parcelas > parcelaInicial;
if (!gerarParcelas) {
return {
grupoParcelamento: normalizarTextoOuNull(dados.grupo_parcelamento),
movimentos: [
normalizarMovimentoParaSalvar(idUsuario, dados, {
origem: normalizarTextoOuNull(dados.origem) || 'manual',
}),
],
};
}
const grupoParcelamento = normalizarTextoOuNull(dados.grupo_parcelamento) || crypto.randomUUID();
const modoParcelamento = dados.modoParcelamento || 'valor_parcela';
const valorInformado = Number(dados.valor || 0);
const valorParcela = modoParcelamento === 'valor_total'
? Number((valorInformado / parcelas).toFixed(2))
: valorInformado;
const movimentos = [];
for (let parcela = parcelaInicial; parcela <= parcelas; parcela++) {
const indiceMes = parcela - parcelaInicial;
const dadosParcela = {
...dados,
valor: valorParcela,
parcela,
parcelas,
datavencimento: addMesesDataString(dados.datavencimento, indiceMes),
competencia: normalizarTextoOuNull(dados.competencia)
? addMesesDataString(`${dados.competencia}-01`, indiceMes).slice(0, 7)
: extrairCompetencia(addMesesDataString(dados.datavencimento, indiceMes)),
};
movimentos.push(
normalizarMovimentoParaSalvar(idUsuario, dadosParcela, {
grupo_parcelamento: grupoParcelamento,
origem: parcela === parcelaInicial
? (normalizarTextoOuNull(dados.origem) || 'manual')
: 'parcelamento',
})
);
}
return {
grupoParcelamento,
movimentos,
};
}
async function aplicarSaldoMovimento(connection, movimento, contexto) {
const deltas = await movimentosSaldoService.calcularImpactoMovimento(connection, movimento, contexto);
if (deltas.length === 0) {
return {
saldoProcessado: 0,
impactos: [],
};
}
const impactos = await movimentosSaldoService.aplicarDeltasSaldo(
connection,
deltas,
contexto
);
return {
saldoProcessado: impactos.length > 0 ? 1 : 0,
impactos,
};
}
async function criarMovimento(idUsuario, dados) {
const duplicado = await verificarDuplicidade(dados);
if (duplicado && dados.confirmarDuplicado !== true) {
throw criarErroDuplicidade(duplicado);
}
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const { movimentos } = montarParcelas(idUsuario, dados);
const movimentosCriados = [];
const impactosTodos = [];
for (const movimento of movimentos) {
const idCriado = await inserirMovimento(connection, movimento);
const movimentoCriado = await buscarMovimentoPorId(idCriado, {
connection,
});
const { saldoProcessado, impactos } = await aplicarSaldoMovimento(
connection,
movimentoCriado,
{
idcontasapagar: idCriado,
tipo_operacao: 'movimento_aplicado',
origem: movimentoCriado.origem || 'movimento',
idusuarios: idUsuario,
}
);
if (saldoProcessado === 1) {
await connection.query(
`
UPDATE contasapagar
SET saldo_processado = 1
WHERE idcontasapagar = ?
`,
[idCriado]
);
}
const movimentoFinal = await buscarMovimentoPorId(idCriado, {
connection,
});
await inserirAuditoriaMovimento(connection, {
idcontasapagar: idCriado,
acao: movimento.parcela > 1 ? 'gerar_parcela' : 'criar',
dados_antes: null,
dados_depois: movimentoFinal,
idusuarios: idUsuario,
});
movimentosCriados.push(movimentoFinal);
impactosTodos.push(...impactos);
}
await connection.commit();
return {
data: movimentosCriados[0],
parcelasGeradas: movimentosCriados.slice(1),
impactoSaldo: impactosTodos,
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
async function atualizarMovimento(id, idUsuario, dados) {
const duplicado = await verificarDuplicidade({
...dados,
idcontasapagar: id,
});
if (duplicado && dados.confirmarDuplicado !== true) {
throw criarErroDuplicidade(duplicado);
}
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
if (!movimentoAntigo) {
await connection.rollback();
return null;
}
const dadosNovos = normalizarMovimentoParaSalvar(idUsuario, dados, {
idusuarios_cad: movimentoAntigo.idusuarios_cad,
grupo_parcelamento: dados.grupo_parcelamento || movimentoAntigo.grupo_parcelamento,
origem: dados.origem || movimentoAntigo.origem || 'manual',
});
const impactosTodos = [];
if (Number(movimentoAntigo.saldo_processado) === 1) {
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
connection,
movimentoAntigo,
{ idusuarios: idUsuario }
);
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(
connection,
movimentosSaldoService.inverterDeltas(deltasAntigos),
{
idcontasapagar: id,
tipo_operacao: 'movimento_revertido',
origem: 'edicao_movimento',
idusuarios: idUsuario,
}
);
impactosTodos.push(...impactosReversao);
}
await atualizarMovimentoBase(connection, id, dadosNovos, 0);
const movimentoNovo = await buscarMovimentoPorId(id, {
connection,
});
const { saldoProcessado, impactos } = await aplicarSaldoMovimento(
connection,
movimentoNovo,
{
idcontasapagar: id,
tipo_operacao: 'movimento_aplicado',
origem: 'edicao_movimento',
idusuarios: idUsuario,
}
);
if (saldoProcessado === 1) {
await connection.query(
`
UPDATE contasapagar
SET saldo_processado = 1
WHERE idcontasapagar = ?
`,
[id]
);
}
const movimentoFinal = await buscarMovimentoPorId(id, {
connection,
});
await inserirAuditoriaMovimento(connection, {
idcontasapagar: id,
acao: 'editar',
dados_antes: movimentoAntigo,
dados_depois: movimentoFinal,
idusuarios: idUsuario,
});
impactosTodos.push(...impactos);
await connection.commit();
return {
data: movimentoFinal,
impactoSaldo: impactosTodos,
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
async function deletarMovimento(id, idUsuario) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
if (!movimentoAntigo) {
await connection.rollback();
return {
ok: false,
};
}
const impactosTodos = [];
if (Number(movimentoAntigo.saldo_processado) === 1) {
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
connection,
movimentoAntigo,
{ idusuarios: idUsuario }
);
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(
connection,
movimentosSaldoService.inverterDeltas(deltasAntigos),
{
idcontasapagar: id,
tipo_operacao: 'movimento_revertido',
origem: 'exclusao_movimento',
idusuarios: idUsuario,
}
);
impactosTodos.push(...impactosReversao);
}
await connection.query(
`
UPDATE contasapagar
SET
deleted_at = NOW(),
saldo_processado = 0,
update_date = NOW()
WHERE idcontasapagar = ?
`,
[id]
);
await inserirAuditoriaMovimento(connection, {
idcontasapagar: id,
acao: 'excluir',
dados_antes: movimentoAntigo,
dados_depois: null,
idusuarios: idUsuario,
});
await connection.commit();
return {
ok: true,
impactoSaldo: impactosTodos,
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}
async function preverImpactoSaldo(dados, idcontasapagar = null) {
let movimentoAntigo = null;
if (idcontasapagar) {
movimentoAntigo = await buscarMovimentoPorId(idcontasapagar);
}
const movimentoNovo = normalizarMovimentoParaSalvar(null, dados, {
idusuarios_cad: dados.idusuarios_cad || null,
grupo_parcelamento: dados.grupo_parcelamento || null,
origem: dados.origem || 'manual',
});
return movimentosSaldoService.preverImpactoSaldo(movimentoNovo, movimentoAntigo);
}
module.exports = {
listarMovimentos,
buscarMovimentoPorId,
verificarDuplicidade,
preverImpactoSaldo,
criarMovimento,
atualizarMovimento,
deletarMovimento,
};