1044 lines
26 KiB
JavaScript
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,
|
|
}; |