Adicionado geração de movimentos fixos
This commit is contained in:
parent
0731328492
commit
66287cff29
|
|
@ -1,4 +1,5 @@
|
|||
const movimentosService = require('../services/movimentos.service');
|
||||
const movimentosFixosGeracaoService = require('../../movimentosFixos/services/movimentosFixosGeracao.service');
|
||||
|
||||
const MOVIMENTOS_VALIDOS = ['Entrada', 'Saida', 'Sangria', 'Estorno'];
|
||||
const STATUS_VALIDOS = ['Pago', 'A pagar', 'Recebido', 'A receber'];
|
||||
|
|
@ -120,6 +121,14 @@ function tratarErro(error, res, mensagemPadrao) {
|
|||
|
||||
async function listar(req, res) {
|
||||
try {
|
||||
const idUsuario = req.user.idusuarios || req.user.id;
|
||||
|
||||
const geracaoFixos = await movimentosFixosGeracaoService.gerarParaIntervalo({
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
idUsuario,
|
||||
});
|
||||
|
||||
const resultado = await movimentosService.listarMovimentos({
|
||||
limite: req.query.limite,
|
||||
offset: req.query.offset,
|
||||
|
|
@ -144,7 +153,7 @@ async function listar(req, res) {
|
|||
orderBy: req.query.orderBy,
|
||||
orderDirection: req.query.orderDirection,
|
||||
|
||||
idusuarios: req.user.idusuarios || req.user.id,
|
||||
idusuarios: idUsuario,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
|
|
@ -152,6 +161,7 @@ async function listar(req, res) {
|
|||
data: resultado.data,
|
||||
pagination: resultado.pagination,
|
||||
summary: resultado.summary,
|
||||
movimentosFixosGerados: geracaoFixos,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao listar movimentos:', error);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,441 @@
|
|||
const pool = require('../../../database/mysql');
|
||||
|
||||
function normalizarDataOuNull(valor) {
|
||||
if (!valor) return null;
|
||||
|
||||
const texto = String(valor).slice(0, 10);
|
||||
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(texto)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return texto;
|
||||
}
|
||||
|
||||
function hojeDataString() {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function competenciaAtual() {
|
||||
return hojeDataString().slice(0, 7);
|
||||
}
|
||||
|
||||
function primeiroDiaCompetencia(competencia) {
|
||||
return `${competencia}-01`;
|
||||
}
|
||||
|
||||
function ultimoDiaDoMes(ano, mes) {
|
||||
return new Date(ano, mes, 0).getDate();
|
||||
}
|
||||
|
||||
function montarDataVencimento(competencia, diaVencimento) {
|
||||
const [anoStr, mesStr] = competencia.split('-');
|
||||
const ano = Number(anoStr);
|
||||
const mes = Number(mesStr);
|
||||
|
||||
const ultimoDia = ultimoDiaDoMes(ano, mes);
|
||||
const dia = Math.min(Math.max(Number(diaVencimento || 1), 1), ultimoDia);
|
||||
|
||||
return `${competencia}-${String(dia).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function addMesesCompetencia(competencia, meses) {
|
||||
const [anoStr, mesStr] = competencia.split('-');
|
||||
const data = new Date(Number(anoStr), Number(mesStr) - 1, 1);
|
||||
|
||||
data.setMonth(data.getMonth() + meses);
|
||||
|
||||
const ano = data.getFullYear();
|
||||
const mes = String(data.getMonth() + 1).padStart(2, '0');
|
||||
|
||||
return `${ano}-${mes}`;
|
||||
}
|
||||
|
||||
function listarCompetenciasEntre(dataInicio, dataFim) {
|
||||
const inicio = normalizarDataOuNull(dataInicio) || hojeDataString();
|
||||
const fim = normalizarDataOuNull(dataFim) || inicio;
|
||||
|
||||
let competenciaInicio = inicio.slice(0, 7);
|
||||
const competenciaFim = fim.slice(0, 7);
|
||||
const atual = competenciaAtual();
|
||||
|
||||
// Regra anti-retroativo:
|
||||
// se o filtro começa no passado, começa no mês atual.
|
||||
if (competenciaInicio < atual) {
|
||||
competenciaInicio = atual;
|
||||
}
|
||||
|
||||
if (competenciaInicio > competenciaFim) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const competencias = [];
|
||||
let cursor = competenciaInicio;
|
||||
|
||||
while (cursor <= competenciaFim) {
|
||||
competencias.push(cursor);
|
||||
cursor = addMesesCompetencia(cursor, 1);
|
||||
}
|
||||
|
||||
return competencias;
|
||||
}
|
||||
|
||||
function statusInicialPorMovimento(movimento) {
|
||||
if (movimento === 'Entrada' || movimento === 'Estorno') {
|
||||
return 'A receber';
|
||||
}
|
||||
|
||||
if (movimento === 'Saida') {
|
||||
return 'A pagar';
|
||||
}
|
||||
|
||||
if (movimento === 'Sangria') {
|
||||
return 'Pago';
|
||||
}
|
||||
|
||||
return 'A pagar';
|
||||
}
|
||||
|
||||
function dataBaixaInicialPorMovimento(movimento, dataVencimento) {
|
||||
if (movimento === 'Sangria') {
|
||||
return dataVencimento;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function buscarFixosDoUsuario(connection, idUsuario) {
|
||||
const [rows] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
mf.idmovimentosfixos,
|
||||
mf.movimento,
|
||||
mf.descricao,
|
||||
mf.valor,
|
||||
mf.dia_vencimento,
|
||||
mf.idcentrodecustos,
|
||||
mf.idbancos
|
||||
FROM movimentosfixos mf
|
||||
INNER JOIN bancos b ON b.idbancos = mf.idbancos
|
||||
WHERE mf.habilitado = 1
|
||||
AND mf.gerar_automatico = 1
|
||||
AND b.idusuarios = ?
|
||||
ORDER BY mf.idmovimentosfixos ASC
|
||||
`,
|
||||
[idUsuario]
|
||||
);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function jaExisteMovimentoFixo(connection, fixo, competencia) {
|
||||
const [rowsGeracao] = await connection.query(
|
||||
`
|
||||
SELECT idmovimentosfixosgeracoes, idcontasapagar, status
|
||||
FROM movimentosfixosgeracoes
|
||||
WHERE idmovimentosfixos = ?
|
||||
AND competencia = ?
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia]
|
||||
);
|
||||
|
||||
if (rowsGeracao.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [rowsMovimentoVinculado] = await connection.query(
|
||||
`
|
||||
SELECT idcontasapagar
|
||||
FROM contasapagar
|
||||
WHERE idmovimentosfixos = ?
|
||||
AND competencia = ?
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia]
|
||||
);
|
||||
|
||||
if (rowsMovimentoVinculado.length > 0) {
|
||||
await connection.query(
|
||||
`
|
||||
INSERT INTO movimentosfixosgeracoes (
|
||||
idmovimentosfixos,
|
||||
competencia,
|
||||
idcontasapagar,
|
||||
status,
|
||||
mensagem,
|
||||
insert_date,
|
||||
update_date
|
||||
) VALUES (?, ?, ?, 'existente', 'Movimento fixo já existia vinculado em contasapagar.', NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
idcontasapagar = VALUES(idcontasapagar),
|
||||
status = VALUES(status),
|
||||
mensagem = VALUES(mensagem),
|
||||
update_date = NOW()
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia, rowsMovimentoVinculado[0].idcontasapagar]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const [rowsMovimentoLegado] = await connection.query(
|
||||
`
|
||||
SELECT idcontasapagar
|
||||
FROM contasapagar
|
||||
WHERE deleted_at IS NULL
|
||||
AND idmovimentosfixos IS NULL
|
||||
AND competencia IS NULL
|
||||
AND movimento = ?
|
||||
AND descricao = ?
|
||||
AND idbancos = ?
|
||||
AND (
|
||||
(? IS NULL AND idcentrodecustos IS NULL)
|
||||
OR idcentrodecustos = ?
|
||||
)
|
||||
AND DATE(datavencimento) BETWEEN ? AND ?
|
||||
ORDER BY idcontasapagar DESC
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[
|
||||
fixo.movimento,
|
||||
fixo.descricao,
|
||||
fixo.idbancos,
|
||||
fixo.idcentrodecustos || null,
|
||||
fixo.idcentrodecustos || null,
|
||||
`${competencia}-01`,
|
||||
`${competencia}-31`,
|
||||
]
|
||||
);
|
||||
|
||||
if (rowsMovimentoLegado.length > 0) {
|
||||
const idContasPagar = rowsMovimentoLegado[0].idcontasapagar;
|
||||
|
||||
await connection.query(
|
||||
`
|
||||
UPDATE contasapagar
|
||||
SET
|
||||
idmovimentosfixos = ?,
|
||||
competencia = ?,
|
||||
origem = COALESCE(origem, 'movimento_fixo_legado'),
|
||||
update_date = NOW()
|
||||
WHERE idcontasapagar = ?
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia, idContasPagar]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`
|
||||
INSERT INTO movimentosfixosgeracoes (
|
||||
idmovimentosfixos,
|
||||
competencia,
|
||||
idcontasapagar,
|
||||
status,
|
||||
mensagem,
|
||||
insert_date,
|
||||
update_date
|
||||
) VALUES (?, ?, ?, 'existente', 'Movimento legado vinculado automaticamente ao fixo.', NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
idcontasapagar = VALUES(idcontasapagar),
|
||||
status = VALUES(status),
|
||||
mensagem = VALUES(mensagem),
|
||||
update_date = NOW()
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia, idContasPagar]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function inserirMovimentoFixoGerado(connection, fixo, competencia, idUsuario) {
|
||||
const dataEntrada = primeiroDiaCompetencia(competencia);
|
||||
const dataVencimento = montarDataVencimento(competencia, fixo.dia_vencimento);
|
||||
const status = statusInicialPorMovimento(fixo.movimento);
|
||||
const dataBaixa = dataBaixaInicialPorMovimento(fixo.movimento, dataVencimento);
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, 1, 1, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?, NULL, 'movimento_fixo', 0, NULL, NOW(), NOW(), NULL)
|
||||
`,
|
||||
[
|
||||
fixo.movimento,
|
||||
fixo.descricao,
|
||||
dataEntrada,
|
||||
dataVencimento,
|
||||
dataBaixa,
|
||||
Number(fixo.valor || 0),
|
||||
status,
|
||||
fixo.idcentrodecustos || null,
|
||||
fixo.idbancos || null,
|
||||
idUsuario,
|
||||
dataBaixa ? idUsuario : null,
|
||||
fixo.idmovimentosfixos,
|
||||
competencia,
|
||||
]
|
||||
);
|
||||
|
||||
const idContasPagar = result.insertId;
|
||||
|
||||
await connection.query(
|
||||
`
|
||||
INSERT INTO movimentosfixos_geracoes (
|
||||
idmovimentosfixos,
|
||||
competencia,
|
||||
idcontasapagar,
|
||||
status,
|
||||
mensagem,
|
||||
insert_date,
|
||||
update_date
|
||||
) VALUES (?, ?, ?, 'gerado', 'Movimento gerado automaticamente.', NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
idcontasapagar = VALUES(idcontasapagar),
|
||||
status = VALUES(status),
|
||||
mensagem = VALUES(mensagem),
|
||||
update_date = NOW()
|
||||
`,
|
||||
[fixo.idmovimentosfixos, competencia, idContasPagar]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`
|
||||
UPDATE movimentosfixos
|
||||
SET
|
||||
ultimo_mes_gerado = CASE
|
||||
WHEN ultimo_mes_gerado IS NULL OR ultimo_mes_gerado < ? THEN ?
|
||||
ELSE ultimo_mes_gerado
|
||||
END,
|
||||
update_date = NOW()
|
||||
WHERE idmovimentosfixos = ?
|
||||
`,
|
||||
[competencia, competencia, fixo.idmovimentosfixos]
|
||||
);
|
||||
|
||||
return idContasPagar;
|
||||
}
|
||||
|
||||
async function gerarParaIntervalo(opcoes = {}) {
|
||||
const idUsuario = Number(opcoes.idUsuario || opcoes.idusuarios || 0);
|
||||
|
||||
if (!idUsuario) {
|
||||
return {
|
||||
gerados: 0,
|
||||
ignorados: 0,
|
||||
competencias: [],
|
||||
detalhes: [],
|
||||
};
|
||||
}
|
||||
|
||||
const competencias = listarCompetenciasEntre(opcoes.dataInicio, opcoes.dataFim);
|
||||
|
||||
if (competencias.length === 0) {
|
||||
return {
|
||||
gerados: 0,
|
||||
ignorados: 0,
|
||||
competencias: [],
|
||||
detalhes: [],
|
||||
};
|
||||
}
|
||||
|
||||
const connection = await pool.getConnection();
|
||||
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const fixos = await buscarFixosDoUsuario(connection, idUsuario);
|
||||
|
||||
let gerados = 0;
|
||||
let ignorados = 0;
|
||||
const detalhes = [];
|
||||
|
||||
for (const competencia of competencias) {
|
||||
for (const fixo of fixos) {
|
||||
const existe = await jaExisteMovimentoFixo(
|
||||
connection,
|
||||
fixo,
|
||||
competencia
|
||||
);
|
||||
|
||||
if (existe) {
|
||||
ignorados++;
|
||||
|
||||
detalhes.push({
|
||||
idmovimentosfixos: fixo.idmovimentosfixos,
|
||||
competencia,
|
||||
status: 'ignorado',
|
||||
mensagem: 'Já existia geração ou movimento vinculado.',
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const idcontasapagar = await inserirMovimentoFixoGerado(
|
||||
connection,
|
||||
fixo,
|
||||
competencia,
|
||||
idUsuario
|
||||
);
|
||||
|
||||
gerados++;
|
||||
|
||||
detalhes.push({
|
||||
idmovimentosfixos: fixo.idmovimentosfixos,
|
||||
competencia,
|
||||
idcontasapagar,
|
||||
status: 'gerado',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
|
||||
return {
|
||||
gerados,
|
||||
ignorados,
|
||||
competencias,
|
||||
detalhes,
|
||||
};
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
gerarParaIntervalo,
|
||||
};
|
||||
Loading…
Reference in New Issue