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, };