financeiro/financeiro-api/src/modules/movimentos/controllers/movimentos.controller.js

353 lines
8.3 KiB
JavaScript
Raw Normal View History

2026-05-30 01:51:05 +00:00
const movimentosService = require('../services/movimentos.service');
const MOVIMENTOS_VALIDOS = ['Entrada', 'Saida', 'Sangria', 'Estorno'];
const STATUS_VALIDOS = ['Pago', 'A pagar', 'Recebido', 'A receber'];
function validarStatusPorMovimento(movimento, status) {
if (movimento === 'Entrada' || movimento === 'Estorno') {
return ['A receber', 'Recebido'].includes(status);
}
if (movimento === 'Saida') {
return ['A pagar', 'Pago'].includes(status);
}
if (movimento === 'Sangria') {
return status === 'Pago';
}
return false;
}
2026-05-30 01:51:05 +00:00
function validarDadosMovimento(dados) {
if (!dados.movimento) {
return 'Movimento é obrigatório.';
}
if (!MOVIMENTOS_VALIDOS.includes(dados.movimento)) {
2026-05-30 01:51:05 +00:00
return 'Movimento inválido.';
}
if (!dados.descricao || !String(dados.descricao).trim()) {
return 'Descrição é obrigatória.';
}
2026-05-30 01:51:05 +00:00
if (dados.valor === undefined || dados.valor === null || dados.valor === '') {
return 'Valor é obrigatório.';
}
const valor = Number(dados.valor);
if (!Number.isFinite(valor)) {
return 'Valor inválido.';
}
if (valor <= 0) {
2026-05-30 01:51:05 +00:00
return 'Valor deve ser maior que zero.';
}
if (!dados.status) {
return 'Situação é obrigatória.';
}
if (!STATUS_VALIDOS.includes(dados.status)) {
2026-05-30 01:51:05 +00:00
return 'Situação inválida.';
}
if (!validarStatusPorMovimento(dados.movimento, dados.status)) {
return 'Situação incompatível com o tipo de movimento.';
2026-05-30 01:51:05 +00:00
}
if (!dados.dataentrada) {
return 'Data de entrada é obrigatória.';
}
if (!dados.datavencimento) {
return 'Data de vencimento é obrigatória.';
}
const parcela = Number(dados.parcela || 1);
const parcelas = Number(dados.parcelas || 1);
if (!Number.isInteger(parcela) || parcela < 1) {
return 'Parcela inválida.';
}
if (!Number.isInteger(parcelas) || parcelas < 1) {
return 'Quantidade de parcelas inválida.';
}
if (parcela > parcelas) {
return 'Parcela atual não pode ser maior que o total de parcelas.';
}
if (dados.movimento === 'Sangria') {
if (!dados.idbancos) {
return 'Banco de origem é obrigatório para sangria.';
}
if (!dados.idbancos_p) {
return 'Banco de destino é obrigatório para sangria.';
}
if (Number(dados.idbancos) === Number(dados.idbancos_p)) {
return 'Banco de origem e destino não podem ser iguais.';
}
}
if (dados.modoParcelamento && !['valor_parcela', 'valor_total'].includes(dados.modoParcelamento)) {
return 'Modo de parcelamento inválido.';
}
2026-05-30 01:51:05 +00:00
return null;
}
function tratarErro(error, res, mensagemPadrao) {
if (error?.code === 'DUPLICIDADE_MOVIMENTO') {
return res.status(409).json({
ok: false,
requiresConfirmation: true,
message: error.message,
data: error.data,
});
}
return res.status(500).json({
ok: false,
message: mensagemPadrao,
});
}
2026-05-30 01:51:05 +00:00
async function listar(req, res) {
try {
const resultado = await movimentosService.listarMovimentos({
limite: req.query.limite,
offset: req.query.offset,
page: req.query.page,
movimento: req.query.movimento,
status: req.query.status,
idbancos: req.query.idbancos,
idcentrodecustos: req.query.idcentrodecustos,
idclientes: req.query.idclientes,
idbancos_p: req.query.idbancos_p,
idmovimentosfixos: req.query.idmovimentosfixos,
competencia: req.query.competencia,
grupo_parcelamento: req.query.grupo_parcelamento,
origem: req.query.origem,
saldo_processado: req.query.saldo_processado,
2026-05-30 01:51:05 +00:00
referenciaTipo: req.query.referenciaTipo,
dataCampo: req.query.dataCampo,
dataInicio: req.query.dataInicio,
dataFim: req.query.dataFim,
busca: req.query.busca,
incluirExcluidos: req.query.incluirExcluidos,
2026-05-30 01:51:05 +00:00
orderBy: req.query.orderBy,
orderDirection: req.query.orderDirection,
});
return res.json({
ok: true,
data: resultado.data,
pagination: resultado.pagination,
summary: resultado.summary,
});
} catch (error) {
console.error('Erro ao listar movimentos:', error);
return res.status(500).json({
ok: false,
message: 'Erro ao listar movimentos.',
});
}
}
async function buscarPorId(req, res) {
try {
const id = Number(req.params.id);
if (!id) {
return res.status(400).json({
ok: false,
message: 'ID inválido.',
});
}
const movimento = await movimentosService.buscarMovimentoPorId(id);
if (!movimento) {
return res.status(404).json({
ok: false,
message: 'Movimento não encontrado.',
});
}
return res.json({
ok: true,
data: movimento,
});
} catch (error) {
console.error('Erro ao buscar movimento:', error);
return res.status(500).json({
ok: false,
message: 'Erro ao buscar movimento.',
});
}
}
async function verificarDuplicidade(req, res) {
try {
const duplicado = await movimentosService.verificarDuplicidade(req.body);
return res.json({
ok: true,
data: duplicado,
});
} catch (error) {
console.error('Erro ao verificar duplicidade:', error);
return res.status(500).json({
ok: false,
message: 'Erro ao verificar duplicidade.',
});
}
}
async function preverImpactoSaldo(req, res) {
2026-05-30 01:51:05 +00:00
try {
const erroValidacao = validarDadosMovimento(req.body);
if (erroValidacao) {
return res.status(400).json({
ok: false,
message: erroValidacao,
});
}
const impacto = await movimentosService.preverImpactoSaldo(req.body, req.body.idcontasapagar);
2026-05-30 01:51:05 +00:00
return res.json({
2026-05-30 01:51:05 +00:00
ok: true,
data: impacto,
2026-05-30 01:51:05 +00:00
});
} catch (error) {
console.error('Erro ao prever impacto de saldo:', error);
2026-05-30 01:51:05 +00:00
return res.status(500).json({
ok: false,
message: 'Erro ao prever impacto de saldo.',
2026-05-30 01:51:05 +00:00
});
}
}
async function criar(req, res) {
try {
const erroValidacao = validarDadosMovimento(req.body);
if (erroValidacao) {
return res.status(400).json({
ok: false,
message: erroValidacao,
});
}
const resultado = await movimentosService.criarMovimento(req.user.id, req.body);
return res.status(201).json({
ok: true,
message: 'Movimento cadastrado com sucesso.',
data: resultado.data,
parcelasGeradas: resultado.parcelasGeradas || [],
impactoSaldo: resultado.impactoSaldo || [],
});
} catch (error) {
console.error('Erro ao criar movimento:', error);
return tratarErro(error, res, 'Erro ao criar movimento.');
}
}
2026-05-30 01:51:05 +00:00
async function atualizar(req, res) {
try {
const id = Number(req.params.id);
if (!id) {
return res.status(400).json({
ok: false,
message: 'ID inválido.',
});
}
const erroValidacao = validarDadosMovimento(req.body);
if (erroValidacao) {
return res.status(400).json({
ok: false,
message: erroValidacao,
});
}
const resultado = await movimentosService.atualizarMovimento(id, req.user.id, req.body);
2026-05-30 01:51:05 +00:00
if (!resultado?.data) {
2026-05-30 01:51:05 +00:00
return res.status(404).json({
ok: false,
message: 'Movimento não encontrado.',
});
}
return res.json({
ok: true,
message: 'Movimento atualizado com sucesso.',
data: resultado.data,
impactoSaldo: resultado.impactoSaldo || [],
2026-05-30 01:51:05 +00:00
});
} catch (error) {
console.error('Erro ao atualizar movimento:', error);
return tratarErro(error, res, 'Erro ao atualizar movimento.');
}
}
async function deletar(req, res) {
try {
const id = Number(req.params.id);
if (!id) {
return res.status(400).json({
ok: false,
message: 'ID inválido.',
});
}
const resultado = await movimentosService.deletarMovimento(id, req.user.id);
if (!resultado?.ok) {
return res.status(404).json({
ok: false,
message: 'Movimento não encontrado.',
});
}
return res.json({
ok: true,
message: 'Movimento excluído com sucesso.',
impactoSaldo: resultado.impactoSaldo || [],
});
} catch (error) {
console.error('Erro ao excluir movimento:', error);
2026-05-30 01:51:05 +00:00
return res.status(500).json({
ok: false,
message: 'Erro ao excluir movimento.',
2026-05-30 01:51:05 +00:00
});
}
}
module.exports = {
listar,
buscarPorId,
verificarDuplicidade,
preverImpactoSaldo,
2026-05-30 01:51:05 +00:00
criar,
atualizar,
deletar,
2026-05-30 01:51:05 +00:00
};