adicionado tela de rendimentos e filtrado bancos por usuario
This commit is contained in:
parent
345d6033a8
commit
0731328492
|
|
@ -7,6 +7,7 @@ const referenciasRoutes = require('./modules/referencias/routes/referencias.rout
|
|||
const relatoriosRoutes = require('./modules/relatorios/routes/relatorios.routes');
|
||||
const clientesRoutes = require('./modules/clientes/routes/clientes.routes');
|
||||
const bancosRoutes = require('./modules/bancos/routes/bancos.routes');
|
||||
const bancosRendimentosRoutes = require('./modules/bancos/routes/bancosRendimentos.routes');
|
||||
const centrosCustoRoutes = require('./modules/centrosCusto/routes/centrosCusto.routes');
|
||||
const movimentosFixosRoutes = require('./modules/movimentosFixos/routes/movimentosFixos.routes');
|
||||
const usuariosRoutes = require('./modules/usuarios/routes/usuarios.routes');
|
||||
|
|
@ -41,6 +42,7 @@ app.use('/api/referencias', referenciasRoutes);
|
|||
app.use('/api/relatorios', relatoriosRoutes);
|
||||
app.use('/api/clientes', clientesRoutes);
|
||||
app.use('/api/bancos', bancosRoutes);
|
||||
app.use('/api/bancos/rendimentos', bancosRendimentosRoutes);
|
||||
app.use('/api/centros-custo', centrosCustoRoutes);
|
||||
app.use('/api/movimentos-fixos', movimentosFixosRoutes);
|
||||
app.use('/api/usuarios', usuariosRoutes);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
const bancosService = require('../services/bancos.service');
|
||||
|
||||
function validarDadosBanco(dados) {
|
||||
if (dados.idusuarios !== undefined && dados.idusuarios !== null && dados.idusuarios !== '') {
|
||||
const idusuarios = Number(dados.idusuarios);
|
||||
|
||||
if (!Number.isFinite(idusuarios)) {
|
||||
return 'ID usuário inválido.';
|
||||
}
|
||||
}
|
||||
|
||||
if (!dados.descricao || !String(dados.descricao).trim()) {
|
||||
return 'Descrição é obrigatória.';
|
||||
}
|
||||
|
|
@ -21,6 +29,14 @@ function validarDadosBanco(dados) {
|
|||
}
|
||||
}
|
||||
|
||||
if (dados.investimento !== undefined && dados.investimento !== null && dados.investimento !== '') {
|
||||
const investimento = Number(dados.investimento);
|
||||
|
||||
if (![0, 1].includes(investimento)) {
|
||||
return 'Tipo investimento inválido.';
|
||||
}
|
||||
}
|
||||
|
||||
if (dados.habilitado !== undefined && dados.habilitado !== null && dados.habilitado !== '') {
|
||||
const habilitado = Number(dados.habilitado);
|
||||
|
||||
|
|
@ -32,7 +48,7 @@ function validarDadosBanco(dados) {
|
|||
if (dados.dia_vencimento !== undefined && dados.dia_vencimento !== null && dados.dia_vencimento !== '') {
|
||||
const dia_vencimento = Number(dados.dia_vencimento);
|
||||
|
||||
if (!Number.isFinite(dia_vencimento) || dia_vencimento > 31) {
|
||||
if (!Number.isFinite(dia_vencimento) || dia_vencimento < 1 || dia_vencimento > 31) {
|
||||
return 'Dia de Vencimeneto inválido.';
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +63,9 @@ async function listar(req, res) {
|
|||
offset: req.query.offset,
|
||||
page: req.query.page,
|
||||
busca: req.query.busca,
|
||||
idusuarios: req.user.id,
|
||||
debito: req.query.debito,
|
||||
investimento: req.query.investimento,
|
||||
dia_vencimento: req.query.dia_vencimento,
|
||||
habilitado: req.query.habilitado,
|
||||
orderBy: req.query.orderBy,
|
||||
|
|
@ -81,7 +99,7 @@ async function buscarPorId(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const banco = await bancosService.buscarBancoPorId(id);
|
||||
const banco = await bancosService.buscarBancoPorId(id, req.user.id);
|
||||
|
||||
if (!banco) {
|
||||
return res.status(404).json({
|
||||
|
|
@ -115,7 +133,7 @@ async function criar(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const novoBanco = await bancosService.criarBanco(req.body);
|
||||
const novoBanco = await bancosService.criarBanco(req.user.id, req.body);
|
||||
|
||||
return res.status(201).json({
|
||||
ok: true,
|
||||
|
|
@ -152,7 +170,7 @@ async function atualizar(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const bancoAtualizado = await bancosService.atualizarBanco(id, req.body);
|
||||
const bancoAtualizado = await bancosService.atualizarBanco(id, req.user.id, req.body);
|
||||
|
||||
if (!bancoAtualizado) {
|
||||
return res.status(404).json({
|
||||
|
|
@ -196,7 +214,7 @@ async function alterarHabilitado(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const bancoAtualizado = await bancosService.alterarHabilitadoBanco(id, habilitado);
|
||||
const bancoAtualizado = await bancosService.alterarHabilitadoBanco(id, req.user.id, habilitado);
|
||||
|
||||
if (!bancoAtualizado) {
|
||||
return res.status(404).json({
|
||||
|
|
@ -233,7 +251,7 @@ async function deletar(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const deletado = await bancosService.deletarBanco(id);
|
||||
const deletado = await bancosService.deletarBanco(id, req.user.id);
|
||||
|
||||
if (!deletado) {
|
||||
return res.status(404).json({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
const bancosRendimentosService = require('../services/bancosRendimentos.service');
|
||||
|
||||
function resolverIdUsuario(req) {
|
||||
return Number(req.user?.idusuarios || req.user?.id);
|
||||
}
|
||||
|
||||
async function dashboard(req, res) {
|
||||
try {
|
||||
const idusuarios = resolverIdUsuario(req);
|
||||
|
||||
if (!idusuarios) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await bancosRendimentosService.buscarDashboard(idusuarios, {
|
||||
periodoMeses: req.query.periodoMeses,
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
mesesProjecao: req.query.mesesProjecao,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
ok: true,
|
||||
data: resultado,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dashboard de rendimentos:', error);
|
||||
|
||||
return res.status(500).json({
|
||||
ok: false,
|
||||
message: 'Erro ao buscar dashboard de rendimentos.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function historico(req, res) {
|
||||
try {
|
||||
const idusuarios = resolverIdUsuario(req);
|
||||
|
||||
if (!idusuarios) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await bancosRendimentosService.buscarHistorico(idusuarios, {
|
||||
idbancos: req.query.idbancos,
|
||||
periodoMeses: req.query.periodoMeses,
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
ok: true,
|
||||
data: resultado,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar histórico de rendimentos:', error);
|
||||
|
||||
return res.status(500).json({
|
||||
ok: false,
|
||||
message: 'Erro ao buscar histórico de rendimentos.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function computarRendimentos(req, res) {
|
||||
try {
|
||||
const idusuarios = resolverIdUsuario(req);
|
||||
|
||||
if (!idusuarios) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const itens = Array.isArray(req.body?.itens) ? req.body.itens : [];
|
||||
|
||||
if (itens.length === 0) {
|
||||
return res.status(400).json({
|
||||
ok: false,
|
||||
message: 'Informe ao menos um rendimento para computar.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await bancosRendimentosService.computarRendimentos(
|
||||
idusuarios,
|
||||
{
|
||||
itens,
|
||||
data: req.body?.data,
|
||||
descricao: req.body?.descricao,
|
||||
confirmarDuplicado: req.body?.confirmarDuplicado,
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
ok: true,
|
||||
message: 'Rendimentos computados com sucesso.',
|
||||
data: resultado,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao computar rendimentos:', error);
|
||||
|
||||
if (error.code === 'DUPLICIDADE_MOVIMENTO') {
|
||||
return res.status(409).json({
|
||||
ok: false,
|
||||
message: error.message,
|
||||
data: error.data,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
ok: false,
|
||||
message: 'Erro ao computar rendimentos.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dashboard,
|
||||
historico,
|
||||
computarRendimentos,
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
const express = require('express');
|
||||
const authMiddleware = require('../../../middlewares/auth.middleware');
|
||||
const bancosRendimentosController = require('../controllers/bancosRendimentos.controller');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authMiddleware);
|
||||
|
||||
router.get('/dashboard', bancosRendimentosController.dashboard);
|
||||
router.get('/historico', bancosRendimentosController.historico);
|
||||
router.post('/computar', bancosRendimentosController.computarRendimentos);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -2,9 +2,11 @@ const pool = require('../../../database/mysql');
|
|||
|
||||
const CAMPOS_BANCO_SELECT = `
|
||||
idbancos,
|
||||
idusuarios,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
dia_vencimento,
|
||||
insert_date,
|
||||
|
|
@ -53,10 +55,12 @@ function limitarNumero(valor, padrao, minimo, maximo) {
|
|||
|
||||
function resolverOrdenacao(orderBy) {
|
||||
const camposPermitidos = {
|
||||
idusuarios: 'idusuarios',
|
||||
idbancos: 'idbancos',
|
||||
descricao: 'descricao',
|
||||
saldo: 'saldo',
|
||||
debito: 'debito',
|
||||
investimento: 'investimento',
|
||||
habilitado: 'habilitado',
|
||||
dia_vencimento: 'dia_vencimento',
|
||||
insert_date: 'insert_date',
|
||||
|
|
@ -74,11 +78,21 @@ function montarWhereBancos(filtros = {}) {
|
|||
const where = [];
|
||||
const params = [];
|
||||
|
||||
if (filtros.idusuarios !== undefined && filtros.idusuarios !== null && filtros.idusuarios !== '') {
|
||||
where.push('idusuarios = ?');
|
||||
params.push(Number(filtros.idusuarios));
|
||||
}
|
||||
|
||||
if (filtros.debito !== undefined && filtros.debito !== null && filtros.debito !== '') {
|
||||
where.push('debito = ?');
|
||||
params.push(Number(filtros.debito));
|
||||
}
|
||||
|
||||
if (filtros.investimento !== undefined && filtros.investimento !== null && filtros.investimento !== '') {
|
||||
where.push('investimento = ?');
|
||||
params.push(Number(filtros.investimento));
|
||||
}
|
||||
|
||||
if (filtros.habilitado !== undefined && filtros.habilitado !== null && filtros.habilitado !== '') {
|
||||
where.push('habilitado = ?');
|
||||
params.push(Number(filtros.habilitado));
|
||||
|
|
@ -146,8 +160,9 @@ async function listarBancos(filtros = {}) {
|
|||
SELECT
|
||||
COUNT(*) AS quantidade,
|
||||
COALESCE(SUM(saldo), 0) AS saldoTotal,
|
||||
COALESCE(SUM(CASE WHEN debito = 0 THEN saldo ELSE 0 END), 0) AS saldoCarteiras,
|
||||
COALESCE(SUM(CASE WHEN debito = 1 THEN saldo ELSE 0 END), 0) AS saldoDebito
|
||||
COALESCE(SUM(CASE WHEN debito = 0 THEN saldo ELSE 0 END), 0) AS saldoCredito,
|
||||
COALESCE(SUM(CASE WHEN debito = 1 and investimento = 0 THEN saldo ELSE 0 END), 0) AS saldoDebito,
|
||||
COALESCE(SUM(CASE WHEN debito = 1 and investimento = 1 THEN saldo ELSE 0 END), 0) AS saldoInvestimento
|
||||
FROM bancos
|
||||
${whereSql}
|
||||
`,
|
||||
|
|
@ -169,32 +184,36 @@ async function listarBancos(filtros = {}) {
|
|||
summary: {
|
||||
quantidade: Number(summaryRows[0]?.quantidade || 0),
|
||||
saldoTotal: Number(summaryRows[0]?.saldoTotal || 0),
|
||||
saldoCarteiras: Number(summaryRows[0]?.saldoCarteiras || 0),
|
||||
saldoCredito: Number(summaryRows[0]?.saldoCredito || 0),
|
||||
saldoDebito: Number(summaryRows[0]?.saldoDebito || 0),
|
||||
saldoInvestimento: Number(summaryRows[0]?.saldoInvestimento || 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function buscarBancoPorId(id) {
|
||||
async function buscarBancoPorId(id, idusuarios) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
${CAMPOS_BANCO_SELECT}
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[id]
|
||||
[id, idusuarios]
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function criarBanco(dados) {
|
||||
async function criarBanco(_idusuarios, dados) {
|
||||
const {
|
||||
idusuarios,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
dia_vencimento,
|
||||
} = dados;
|
||||
|
|
@ -202,19 +221,23 @@ async function criarBanco(dados) {
|
|||
const [result] = await pool.query(
|
||||
`
|
||||
INSERT INTO bancos (
|
||||
idusuarios,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
dia_vencimento,
|
||||
insert_date,
|
||||
update_date
|
||||
) VALUES (?, ?, ?, ?, ?, NOW(), NOW())
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`,
|
||||
[
|
||||
normalizarNumero(_idusuarios, 0),
|
||||
normalizarTextoOuNull(descricao),
|
||||
normalizarNumero(saldo, 0),
|
||||
Number(debito || 0),
|
||||
Number(investimento || 0),
|
||||
habilitado === undefined || habilitado === null || habilitado === ''
|
||||
? 1
|
||||
: Number(habilitado),
|
||||
|
|
@ -222,20 +245,22 @@ async function criarBanco(dados) {
|
|||
]
|
||||
);
|
||||
|
||||
return buscarBancoPorId(result.insertId);
|
||||
return buscarBancoPorId(result.insertId, _idusuarios);
|
||||
}
|
||||
|
||||
async function atualizarBanco(id, dados) {
|
||||
const bancoAtual = await buscarBancoPorId(id);
|
||||
async function atualizarBanco(id, _idusuarios, dados) {
|
||||
const bancoAtual = await buscarBancoPorId(id, _idusuarios);
|
||||
|
||||
if (!bancoAtual) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
idusuarios,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
dia_vencimento
|
||||
} = dados;
|
||||
|
|
@ -244,31 +269,37 @@ async function atualizarBanco(id, dados) {
|
|||
`
|
||||
UPDATE bancos
|
||||
SET
|
||||
idusuarios = ?,
|
||||
descricao = ?,
|
||||
saldo = ?,
|
||||
debito = ?,
|
||||
investimento = ?,
|
||||
habilitado = ?,
|
||||
dia_vencimento = ?,
|
||||
update_date = NOW()
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
`,
|
||||
[
|
||||
normalizarNumero(_idusuarios, 0),
|
||||
normalizarTextoOuNull(descricao),
|
||||
normalizarNumero(saldo, 0),
|
||||
Number(debito || 0),
|
||||
Number(investimento || 0),
|
||||
habilitado === undefined || habilitado === null || habilitado === ''
|
||||
? 1
|
||||
: Number(habilitado),
|
||||
normalizarNumero(dia_vencimento, null),
|
||||
id
|
||||
id,
|
||||
_idusuarios
|
||||
]
|
||||
);
|
||||
|
||||
return buscarBancoPorId(id);
|
||||
return buscarBancoPorId(id, _idusuarios);
|
||||
}
|
||||
|
||||
async function alterarHabilitadoBanco(id, habilitado) {
|
||||
const bancoAtual = await buscarBancoPorId(id);
|
||||
async function alterarHabilitadoBanco(id, idusuarios, habilitado) {
|
||||
const bancoAtual = await buscarBancoPorId(id, idusuarios);
|
||||
|
||||
if (!bancoAtual) {
|
||||
return null;
|
||||
|
|
@ -281,18 +312,20 @@ async function alterarHabilitadoBanco(id, habilitado) {
|
|||
habilitado = ?,
|
||||
update_date = NOW()
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
`,
|
||||
[
|
||||
Number(habilitado),
|
||||
id,
|
||||
idusuarios
|
||||
]
|
||||
);
|
||||
|
||||
return buscarBancoPorId(id);
|
||||
return buscarBancoPorId(id, idusuarios);
|
||||
}
|
||||
|
||||
async function deletarBanco(id) {
|
||||
const bancoAtual = await buscarBancoPorId(id);
|
||||
async function deletarBanco(id, idusuarios) {
|
||||
const bancoAtual = await buscarBancoPorId(id, idusuarios);
|
||||
|
||||
if (!bancoAtual) {
|
||||
return false;
|
||||
|
|
@ -302,8 +335,9 @@ async function deletarBanco(id) {
|
|||
`
|
||||
DELETE FROM bancos
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
`,
|
||||
[id]
|
||||
[id, idusuarios]
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,726 @@
|
|||
const pool = require('../../../database/mysql');
|
||||
const movimentosService = require('../../movimentos/services/movimentos.service');
|
||||
|
||||
const ID_CENTRO_CUSTO_RENDIMENTO = 33;
|
||||
const ORIGEM_RENDIMENTO = 'rendimento_investimento';
|
||||
|
||||
function hojeDataString() {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function extrairCompetencia(data) {
|
||||
return String(data || hojeDataString()).slice(0, 7);
|
||||
}
|
||||
|
||||
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 arredondar2(valor) {
|
||||
return Number(Number(valor || 0).toFixed(2));
|
||||
}
|
||||
|
||||
function calcularPercentual(delta, base) {
|
||||
const valorBase = Number(base || 0);
|
||||
|
||||
if (!valorBase) return 0;
|
||||
|
||||
return Number(((Number(delta || 0) / valorBase) * 100).toFixed(4));
|
||||
}
|
||||
|
||||
function resolverPeriodo(filtros = {}) {
|
||||
const periodoMeses = limitarNumero(filtros.periodoMeses, 12, 1, 60);
|
||||
|
||||
const hoje = new Date();
|
||||
const dataFim = filtros.dataFim || hoje.toISOString().slice(0, 10);
|
||||
|
||||
const dataInicioObj = new Date(hoje.getFullYear(), hoje.getMonth() - periodoMeses + 1, 1);
|
||||
const dataInicioPadrao = dataInicioObj.toISOString().slice(0, 10);
|
||||
|
||||
return {
|
||||
dataInicio: filtros.dataInicio || dataInicioPadrao,
|
||||
dataFim,
|
||||
periodoMeses,
|
||||
};
|
||||
}
|
||||
|
||||
async function listarBancosInvestimento(idusuarios) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
idbancos,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
update_date
|
||||
FROM bancos
|
||||
WHERE idusuarios = ?
|
||||
AND investimento = 1
|
||||
AND habilitado = 1
|
||||
ORDER BY descricao ASC
|
||||
`,
|
||||
[idusuarios]
|
||||
);
|
||||
|
||||
return rows.map((item) => ({
|
||||
...item,
|
||||
saldo: Number(item.saldo || 0),
|
||||
}));
|
||||
}
|
||||
|
||||
async function buscarBancoInvestimento(idusuarios, idbancos) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
idbancos,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
investimento,
|
||||
habilitado,
|
||||
update_date
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
AND investimento = 1
|
||||
AND habilitado = 1
|
||||
LIMIT 1
|
||||
`,
|
||||
[idbancos, idusuarios]
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function buscarUltimasApuracoes(idusuarios) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
bsa.idbancos,
|
||||
MAX(bsa.insert_date) AS ultima_apuracao
|
||||
FROM bancos_saldo_auditoria bsa
|
||||
INNER JOIN bancos b ON b.idbancos = bsa.idbancos
|
||||
WHERE bsa.idusuarios = ?
|
||||
AND b.idusuarios = ?
|
||||
AND b.investimento = 1
|
||||
AND bsa.origem = ?
|
||||
GROUP BY bsa.idbancos
|
||||
`,
|
||||
[idusuarios, idusuarios, ORIGEM_RENDIMENTO]
|
||||
);
|
||||
|
||||
const mapa = new Map();
|
||||
|
||||
rows.forEach((row) => {
|
||||
mapa.set(Number(row.idbancos), row.ultima_apuracao);
|
||||
});
|
||||
|
||||
return mapa;
|
||||
}
|
||||
|
||||
async function buscarHistorico(idusuarios, filtros = {}) {
|
||||
const { dataInicio, dataFim } = resolverPeriodo(filtros);
|
||||
|
||||
const params = [
|
||||
idusuarios,
|
||||
idusuarios,
|
||||
ORIGEM_RENDIMENTO,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
];
|
||||
|
||||
let filtroBanco = '';
|
||||
|
||||
if (filtros.idbancos) {
|
||||
filtroBanco = 'AND bsa.idbancos = ?';
|
||||
params.push(Number(filtros.idbancos));
|
||||
}
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
bsa.idbancossaldoauditoria,
|
||||
bsa.idbancos,
|
||||
b.descricao AS banco_descricao,
|
||||
bsa.idcontasapagar,
|
||||
bsa.tipo_operacao,
|
||||
bsa.origem,
|
||||
bsa.valor_delta,
|
||||
bsa.saldo_anterior,
|
||||
bsa.saldo_posterior,
|
||||
bsa.descricao,
|
||||
bsa.insert_date
|
||||
FROM bancos_saldo_auditoria bsa
|
||||
INNER JOIN bancos b ON b.idbancos = bsa.idbancos
|
||||
WHERE bsa.idusuarios = ?
|
||||
AND b.idusuarios = ?
|
||||
AND b.investimento = 1
|
||||
AND bsa.origem = ?
|
||||
AND DATE(bsa.insert_date) >= ?
|
||||
AND DATE(bsa.insert_date) <= ?
|
||||
${filtroBanco}
|
||||
ORDER BY bsa.insert_date ASC, bsa.idbancossaldoauditoria ASC
|
||||
`,
|
||||
params
|
||||
);
|
||||
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
valor_delta: Number(row.valor_delta || 0),
|
||||
saldo_anterior: Number(row.saldo_anterior || 0),
|
||||
saldo_posterior: Number(row.saldo_posterior || 0),
|
||||
percentual: calcularPercentual(row.valor_delta, row.saldo_anterior),
|
||||
}));
|
||||
}
|
||||
|
||||
function montarSeriesMensais(historico) {
|
||||
const porBancoMes = new Map();
|
||||
|
||||
historico.forEach((item) => {
|
||||
const mes = String(item.insert_date).slice(0, 7);
|
||||
const chave = `${item.idbancos}|${mes}`;
|
||||
|
||||
if (!porBancoMes.has(chave)) {
|
||||
porBancoMes.set(chave, {
|
||||
idbancos: item.idbancos,
|
||||
banco_descricao: item.banco_descricao,
|
||||
competencia: mes,
|
||||
rendimento: 0,
|
||||
saldo_final: Number(item.saldo_posterior || 0),
|
||||
eventos: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const atual = porBancoMes.get(chave);
|
||||
|
||||
atual.rendimento = arredondar2(atual.rendimento + Number(item.valor_delta || 0));
|
||||
atual.saldo_final = Number(item.saldo_posterior || atual.saldo_final || 0);
|
||||
atual.eventos += 1;
|
||||
});
|
||||
|
||||
return Array.from(porBancoMes.values()).sort((a, b) => {
|
||||
if (a.competencia === b.competencia) {
|
||||
return String(a.banco_descricao).localeCompare(String(b.banco_descricao));
|
||||
}
|
||||
|
||||
return String(a.competencia).localeCompare(String(b.competencia));
|
||||
});
|
||||
}
|
||||
|
||||
function montarResumo(bancos, historico, ranking = [], projecao = null) {
|
||||
const totalInvestidoAtual = bancos.reduce(
|
||||
(acc, banco) => acc + Number(banco.saldo || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const rendimentoPeriodo = historico.reduce(
|
||||
(acc, item) => acc + Number(item.valor_delta || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const positivos = historico.filter((item) => Number(item.valor_delta || 0) > 0);
|
||||
|
||||
const rendimentoMedioPercentual = positivos.length
|
||||
? positivos.reduce((acc, item) => acc + Number(item.percentual || 0), 0) / positivos.length
|
||||
: 0;
|
||||
|
||||
const melhorBanco = ranking[0] || null;
|
||||
|
||||
return {
|
||||
totalInvestidoAtual: arredondar2(totalInvestidoAtual),
|
||||
rendimentoPeriodo: arredondar2(rendimentoPeriodo),
|
||||
rendimentoMedioPercentual: Number(rendimentoMedioPercentual.toFixed(4)),
|
||||
quantidadeBancos: bancos.length,
|
||||
melhorBanco,
|
||||
projecao12Meses: projecao
|
||||
? {
|
||||
saldoProjetado: projecao.saldo_projetado_total,
|
||||
rendimentoProjetado: projecao.rendimento_projetado_total,
|
||||
taxaMediaMensal: projecao.taxa_media_mensal_total,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function agruparRendimentoPorBanco(historico) {
|
||||
const mapa = new Map();
|
||||
|
||||
historico.forEach((item) => {
|
||||
const idbancos = Number(item.idbancos);
|
||||
const valorDelta = Number(item.valor_delta || 0);
|
||||
const percentual = Number(item.percentual || 0);
|
||||
|
||||
if (!mapa.has(idbancos)) {
|
||||
mapa.set(idbancos, {
|
||||
idbancos,
|
||||
banco_descricao: item.banco_descricao,
|
||||
rendimento_total: 0,
|
||||
quantidade_eventos: 0,
|
||||
soma_percentual: 0,
|
||||
maior_rendimento: null,
|
||||
menor_rendimento: null,
|
||||
primeira_data: item.insert_date,
|
||||
ultima_data: item.insert_date,
|
||||
});
|
||||
}
|
||||
|
||||
const atual = mapa.get(idbancos);
|
||||
|
||||
atual.rendimento_total = arredondar2(atual.rendimento_total + valorDelta);
|
||||
atual.quantidade_eventos += 1;
|
||||
atual.soma_percentual += percentual;
|
||||
|
||||
if (!atual.maior_rendimento || valorDelta > Number(atual.maior_rendimento.valor_delta || 0)) {
|
||||
atual.maior_rendimento = {
|
||||
valor_delta: valorDelta,
|
||||
percentual,
|
||||
insert_date: item.insert_date,
|
||||
};
|
||||
}
|
||||
|
||||
if (!atual.menor_rendimento || valorDelta < Number(atual.menor_rendimento.valor_delta || 0)) {
|
||||
atual.menor_rendimento = {
|
||||
valor_delta: valorDelta,
|
||||
percentual,
|
||||
insert_date: item.insert_date,
|
||||
};
|
||||
}
|
||||
|
||||
if (String(item.insert_date) < String(atual.primeira_data)) {
|
||||
atual.primeira_data = item.insert_date;
|
||||
}
|
||||
|
||||
if (String(item.insert_date) > String(atual.ultima_data)) {
|
||||
atual.ultima_data = item.insert_date;
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(mapa.values()).map((item) => ({
|
||||
...item,
|
||||
percentual_medio: item.quantidade_eventos > 0
|
||||
? Number((item.soma_percentual / item.quantidade_eventos).toFixed(4))
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
function montarRanking(bancos, historico) {
|
||||
const agrupado = agruparRendimentoPorBanco(historico);
|
||||
|
||||
const saldoPorBanco = new Map();
|
||||
|
||||
bancos.forEach((banco) => {
|
||||
saldoPorBanco.set(Number(banco.idbancos), Number(banco.saldo || 0));
|
||||
});
|
||||
|
||||
const ranking = agrupado.map((item) => {
|
||||
const saldoAtual = saldoPorBanco.get(Number(item.idbancos)) || 0;
|
||||
|
||||
return {
|
||||
...item,
|
||||
saldo_atual: arredondar2(saldoAtual),
|
||||
participacao_rendimento: 0,
|
||||
};
|
||||
});
|
||||
|
||||
const totalRendimento = ranking.reduce(
|
||||
(acc, item) => acc + Number(item.rendimento_total || 0),
|
||||
0
|
||||
);
|
||||
|
||||
return ranking
|
||||
.map((item) => ({
|
||||
...item,
|
||||
participacao_rendimento: totalRendimento
|
||||
? Number(((Number(item.rendimento_total || 0) / totalRendimento) * 100).toFixed(4))
|
||||
: 0,
|
||||
}))
|
||||
.sort((a, b) => Number(b.rendimento_total || 0) - Number(a.rendimento_total || 0));
|
||||
}
|
||||
|
||||
function montarGraficoBarrasMensal(seriesMensais) {
|
||||
const mapa = new Map();
|
||||
|
||||
seriesMensais.forEach((item) => {
|
||||
const competencia = item.competencia;
|
||||
|
||||
if (!mapa.has(competencia)) {
|
||||
mapa.set(competencia, {
|
||||
competencia,
|
||||
total: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const row = mapa.get(competencia);
|
||||
const nomeBanco = item.banco_descricao;
|
||||
|
||||
row[nomeBanco] = arredondar2(Number(row[nomeBanco] || 0) + Number(item.rendimento || 0));
|
||||
row.total = arredondar2(Number(row.total || 0) + Number(item.rendimento || 0));
|
||||
});
|
||||
|
||||
return Array.from(mapa.values()).sort((a, b) =>
|
||||
String(a.competencia).localeCompare(String(b.competencia))
|
||||
);
|
||||
}
|
||||
|
||||
function montarGraficoSaldoMensal(seriesMensais) {
|
||||
const mapa = new Map();
|
||||
|
||||
seriesMensais.forEach((item) => {
|
||||
const competencia = item.competencia;
|
||||
|
||||
if (!mapa.has(competencia)) {
|
||||
mapa.set(competencia, {
|
||||
competencia,
|
||||
});
|
||||
}
|
||||
|
||||
const row = mapa.get(competencia);
|
||||
row[item.banco_descricao] = Number(item.saldo_final || 0);
|
||||
});
|
||||
|
||||
return Array.from(mapa.values()).sort((a, b) =>
|
||||
String(a.competencia).localeCompare(String(b.competencia))
|
||||
);
|
||||
}
|
||||
|
||||
function montarGraficoRendimentoAcumulado(seriesMensais) {
|
||||
const bancos = Array.from(
|
||||
new Set(seriesMensais.map((item) => item.banco_descricao))
|
||||
);
|
||||
|
||||
const competencias = Array.from(
|
||||
new Set(seriesMensais.map((item) => item.competencia))
|
||||
).sort();
|
||||
|
||||
const acumuladoPorBanco = new Map();
|
||||
|
||||
bancos.forEach((nomeBanco) => {
|
||||
acumuladoPorBanco.set(nomeBanco, 0);
|
||||
});
|
||||
|
||||
return competencias.map((competencia) => {
|
||||
const row = {
|
||||
competencia,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
bancos.forEach((nomeBanco) => {
|
||||
const item = seriesMensais.find(
|
||||
(serie) =>
|
||||
serie.competencia === competencia &&
|
||||
serie.banco_descricao === nomeBanco
|
||||
);
|
||||
|
||||
const rendimentoMes = item ? Number(item.rendimento || 0) : 0;
|
||||
const novoAcumulado = Number(acumuladoPorBanco.get(nomeBanco) || 0) + rendimentoMes;
|
||||
|
||||
acumuladoPorBanco.set(nomeBanco, arredondar2(novoAcumulado));
|
||||
row[nomeBanco] = arredondar2(novoAcumulado);
|
||||
row.total = arredondar2(Number(row.total || 0) + novoAcumulado);
|
||||
});
|
||||
|
||||
return row;
|
||||
});
|
||||
}
|
||||
|
||||
function calcularMesesEntreDatas(dataInicio, dataFim) {
|
||||
if (!dataInicio || !dataFim) return 0;
|
||||
|
||||
const inicio = new Date(dataInicio);
|
||||
const fim = new Date(dataFim);
|
||||
|
||||
if (Number.isNaN(inicio.getTime()) || Number.isNaN(fim.getTime())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const anos = fim.getFullYear() - inicio.getFullYear();
|
||||
const meses = fim.getMonth() - inicio.getMonth();
|
||||
|
||||
return Math.max(1, anos * 12 + meses + 1);
|
||||
}
|
||||
|
||||
function montarProjecao(bancos, seriesMensais, mesesProjetar = 12) {
|
||||
const resultadoPorBanco = [];
|
||||
|
||||
bancos.forEach((banco) => {
|
||||
const seriesBanco = seriesMensais
|
||||
.filter((item) => Number(item.idbancos) === Number(banco.idbancos))
|
||||
.sort((a, b) => String(a.competencia).localeCompare(String(b.competencia)));
|
||||
|
||||
const ultimasSeries = seriesBanco.slice(-6);
|
||||
|
||||
const rendimentosValidos = ultimasSeries.filter(
|
||||
(item) => Number(item.saldo_final || 0) > 0
|
||||
);
|
||||
|
||||
const taxasMensais = rendimentosValidos.map((item) => {
|
||||
const saldoFinal = Number(item.saldo_final || 0);
|
||||
const rendimento = Number(item.rendimento || 0);
|
||||
const saldoBase = saldoFinal - rendimento;
|
||||
|
||||
if (saldoBase <= 0) return 0;
|
||||
|
||||
return rendimento / saldoBase;
|
||||
});
|
||||
|
||||
const taxaMediaMensal = taxasMensais.length
|
||||
? taxasMensais.reduce((acc, taxa) => acc + taxa, 0) / taxasMensais.length
|
||||
: 0;
|
||||
|
||||
const saldoAtual = Number(banco.saldo || 0);
|
||||
const saldoProjetado = saldoAtual * Math.pow(1 + taxaMediaMensal, mesesProjetar);
|
||||
const rendimentoProjetado = saldoProjetado - saldoAtual;
|
||||
|
||||
resultadoPorBanco.push({
|
||||
idbancos: banco.idbancos,
|
||||
banco_descricao: banco.descricao,
|
||||
saldo_atual: arredondar2(saldoAtual),
|
||||
taxa_media_mensal: Number((taxaMediaMensal * 100).toFixed(4)),
|
||||
meses_base: taxasMensais.length,
|
||||
meses_projecao: mesesProjetar,
|
||||
saldo_projetado: arredondar2(saldoProjetado),
|
||||
rendimento_projetado: arredondar2(rendimentoProjetado),
|
||||
});
|
||||
});
|
||||
|
||||
const saldoAtualTotal = resultadoPorBanco.reduce(
|
||||
(acc, item) => acc + Number(item.saldo_atual || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const saldoProjetadoTotal = resultadoPorBanco.reduce(
|
||||
(acc, item) => acc + Number(item.saldo_projetado || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const rendimentoProjetadoTotal = saldoProjetadoTotal - saldoAtualTotal;
|
||||
|
||||
return {
|
||||
meses_projecao: mesesProjetar,
|
||||
saldo_atual_total: arredondar2(saldoAtualTotal),
|
||||
saldo_projetado_total: arredondar2(saldoProjetadoTotal),
|
||||
rendimento_projetado_total: arredondar2(rendimentoProjetadoTotal),
|
||||
taxa_media_mensal_total: saldoAtualTotal
|
||||
? Number(((Math.pow(saldoProjetadoTotal / saldoAtualTotal, 1 / mesesProjetar) - 1) * 100).toFixed(4))
|
||||
: 0,
|
||||
bancos: resultadoPorBanco.sort((a, b) =>
|
||||
Number(b.rendimento_projetado || 0) - Number(a.rendimento_projetado || 0)
|
||||
),
|
||||
observacao: 'Projeção estimada com base no histórico recente de rendimentos. Não representa garantia de rendimento futuro.',
|
||||
};
|
||||
}
|
||||
|
||||
async function buscarDashboard(idusuarios, filtros = {}) {
|
||||
const bancos = await listarBancosInvestimento(idusuarios);
|
||||
const ultimasApuracoes = await buscarUltimasApuracoes(idusuarios);
|
||||
const historico = await buscarHistorico(idusuarios, filtros);
|
||||
const seriesMensais = montarSeriesMensais(historico);
|
||||
|
||||
const mesesProjecao = limitarNumero(filtros.mesesProjecao, 12, 1, 60);
|
||||
|
||||
const ranking = montarRanking(bancos, historico);
|
||||
const projecao = montarProjecao(bancos, seriesMensais, mesesProjecao);
|
||||
|
||||
const graficos = {
|
||||
barrasMensal: montarGraficoBarrasMensal(seriesMensais),
|
||||
saldoMensal: montarGraficoSaldoMensal(seriesMensais),
|
||||
rendimentoAcumulado: montarGraficoRendimentoAcumulado(seriesMensais),
|
||||
};
|
||||
|
||||
const resumo = montarResumo(bancos, historico, ranking, projecao);
|
||||
|
||||
const bancosComMetricas = bancos.map((banco) => {
|
||||
const rankingBanco = ranking.find(
|
||||
(item) => Number(item.idbancos) === Number(banco.idbancos)
|
||||
);
|
||||
|
||||
const projecaoBanco = projecao.bancos.find(
|
||||
(item) => Number(item.idbancos) === Number(banco.idbancos)
|
||||
);
|
||||
|
||||
return {
|
||||
...banco,
|
||||
ultima_apuracao: ultimasApuracoes.get(Number(banco.idbancos)) || null,
|
||||
rendimento_periodo: rankingBanco?.rendimento_total || 0,
|
||||
percentual_medio_periodo: rankingBanco?.percentual_medio || 0,
|
||||
projecao: projecaoBanco || null,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
resumo,
|
||||
bancos: bancosComMetricas,
|
||||
historico,
|
||||
seriesMensais,
|
||||
ranking,
|
||||
projecao,
|
||||
graficos,
|
||||
};
|
||||
}
|
||||
|
||||
function validarItemComputar(item) {
|
||||
const idbancos = Number(item.idbancos);
|
||||
const novoSaldo = Number(item.novoSaldo);
|
||||
|
||||
if (!Number.isFinite(idbancos) || idbancos <= 0) {
|
||||
return 'Banco inválido.';
|
||||
}
|
||||
|
||||
if (!Number.isFinite(novoSaldo)) {
|
||||
return 'Novo saldo inválido.';
|
||||
}
|
||||
|
||||
if (novoSaldo < 0) {
|
||||
return 'Novo saldo não pode ser negativo.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function computarRendimentoBanco(idusuarios, item, opcoes = {}) {
|
||||
const erro = validarItemComputar(item);
|
||||
|
||||
if (erro) {
|
||||
return {
|
||||
ok: false,
|
||||
idbancos: item.idbancos,
|
||||
message: erro,
|
||||
};
|
||||
}
|
||||
|
||||
const idbancos = Number(item.idbancos);
|
||||
const novoSaldo = arredondar2(item.novoSaldo);
|
||||
|
||||
const banco = await buscarBancoInvestimento(idusuarios, idbancos);
|
||||
|
||||
if (!banco) {
|
||||
return {
|
||||
ok: false,
|
||||
idbancos,
|
||||
message: 'Banco de investimento não encontrado para este usuário.',
|
||||
};
|
||||
}
|
||||
|
||||
const saldoAnterior = arredondar2(banco.saldo);
|
||||
const valorDelta = arredondar2(novoSaldo - saldoAnterior);
|
||||
|
||||
if (valorDelta === 0) {
|
||||
return {
|
||||
ok: true,
|
||||
ignorado: true,
|
||||
idbancos,
|
||||
banco_descricao: banco.descricao,
|
||||
saldo_anterior: saldoAnterior,
|
||||
saldo_posterior: novoSaldo,
|
||||
valor_delta: 0,
|
||||
percentual: 0,
|
||||
message: 'Sem alteração de saldo.',
|
||||
};
|
||||
}
|
||||
|
||||
if (valorDelta < 0) {
|
||||
return {
|
||||
ok: false,
|
||||
idbancos,
|
||||
banco_descricao: banco.descricao,
|
||||
saldo_anterior: saldoAnterior,
|
||||
saldo_posterior: novoSaldo,
|
||||
valor_delta: valorDelta,
|
||||
percentual: calcularPercentual(valorDelta, saldoAnterior),
|
||||
message: 'O novo saldo é menor que o saldo atual. Para rendimento, informe apenas crescimento positivo.',
|
||||
};
|
||||
}
|
||||
|
||||
const data = opcoes.data || hojeDataString();
|
||||
|
||||
const descricaoBase = item.descricao
|
||||
|| opcoes.descricao
|
||||
|| `Rendimento de investimento - ${banco.descricao}`;
|
||||
|
||||
const movimento = await movimentosService.criarMovimento(idusuarios, {
|
||||
movimento: 'Entrada',
|
||||
descricao: descricaoBase,
|
||||
dataentrada: data,
|
||||
datavencimento: data,
|
||||
databaixa: data,
|
||||
valor: valorDelta,
|
||||
parcela: 1,
|
||||
parcelas: 1,
|
||||
status: 'Recebido',
|
||||
idcentrodecustos: ID_CENTRO_CUSTO_RENDIMENTO,
|
||||
idbancos,
|
||||
idclientes: null,
|
||||
idveiculosdetalhes: null,
|
||||
idbancos_p: null,
|
||||
idmovimentosfixos: null,
|
||||
competencia: extrairCompetencia(data),
|
||||
origem: ORIGEM_RENDIMENTO,
|
||||
saldo_processado: 0,
|
||||
observacao: `Saldo anterior: ${saldoAnterior}. Novo saldo informado: ${novoSaldo}.`,
|
||||
confirmarDuplicado: opcoes.confirmarDuplicado === true,
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
idbancos,
|
||||
banco_descricao: banco.descricao,
|
||||
saldo_anterior: saldoAnterior,
|
||||
saldo_posterior: novoSaldo,
|
||||
valor_delta: valorDelta,
|
||||
percentual: calcularPercentual(valorDelta, saldoAnterior),
|
||||
movimento: movimento.data,
|
||||
impactoSaldo: movimento.impactoSaldo || [],
|
||||
};
|
||||
}
|
||||
|
||||
async function computarRendimentos(idusuarios, dados = {}) {
|
||||
const itens = Array.isArray(dados.itens) ? dados.itens : [];
|
||||
|
||||
const resultados = [];
|
||||
|
||||
for (const item of itens) {
|
||||
const resultado = await computarRendimentoBanco(idusuarios, item, {
|
||||
data: dados.data,
|
||||
descricao: dados.descricao,
|
||||
confirmarDuplicado: dados.confirmarDuplicado,
|
||||
});
|
||||
|
||||
resultados.push(resultado);
|
||||
}
|
||||
|
||||
const processados = resultados.filter((item) => item.ok && !item.ignorado);
|
||||
const ignorados = resultados.filter((item) => item.ok && item.ignorado);
|
||||
const erros = resultados.filter((item) => !item.ok);
|
||||
|
||||
return {
|
||||
processados,
|
||||
ignorados,
|
||||
erros,
|
||||
resumo: {
|
||||
quantidadeProcessada: processados.length,
|
||||
quantidadeIgnorada: ignorados.length,
|
||||
quantidadeErro: erros.length,
|
||||
totalRendimento: arredondar2(
|
||||
processados.reduce((acc, item) => acc + Number(item.valor_delta || 0), 0)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
buscarDashboard,
|
||||
buscarHistorico,
|
||||
computarRendimentos,
|
||||
};
|
||||
|
|
@ -1,8 +1,21 @@
|
|||
const dashboardService = require('../services/dashboard.service');
|
||||
|
||||
function resolverIdUsuario(req) {
|
||||
return Number(req.user?.idusuarios || req.user?.id);
|
||||
}
|
||||
|
||||
async function buscarResumo(req, res) {
|
||||
try {
|
||||
const resultado = await dashboardService.buscarResumoDashboard({
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await dashboardService.buscarResumoDashboard(idUsuario, {
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,27 +52,95 @@ function calcularPercentual(valor, base) {
|
|||
return (valorNormalizado / baseNormalizada) * 100;
|
||||
}
|
||||
|
||||
async function buscarCardsBancos() {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN debito = 1 THEN saldo ELSE 0 END), 0) AS saldoDebito,
|
||||
COALESCE(SUM(CASE WHEN debito = 0 THEN saldo ELSE 0 END), 0) AS dividaCredito,
|
||||
COUNT(*) AS quantidadeCarteiras,
|
||||
SUM(CASE WHEN habilitado = 1 THEN 1 ELSE 0 END) AS carteirasHabilitadas
|
||||
FROM bancos
|
||||
`);
|
||||
async function buscarCardsBancos(idUsuario) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
COALESCE(SUM(CASE
|
||||
WHEN debito = 1
|
||||
AND COALESCE(investimento, 0) = 0
|
||||
THEN saldo
|
||||
ELSE 0
|
||||
END), 0) AS saldoDisponivel,
|
||||
|
||||
COALESCE(SUM(CASE
|
||||
WHEN debito = 1
|
||||
AND COALESCE(investimento, 0) = 1
|
||||
THEN saldo
|
||||
ELSE 0
|
||||
END), 0) AS totalInvestido,
|
||||
|
||||
COALESCE(SUM(CASE
|
||||
WHEN debito = 1
|
||||
THEN saldo
|
||||
ELSE 0
|
||||
END), 0) AS saldoDebitoTotal,
|
||||
|
||||
COALESCE(SUM(CASE
|
||||
WHEN debito = 0
|
||||
THEN saldo
|
||||
ELSE 0
|
||||
END), 0) AS dividaCredito,
|
||||
|
||||
COUNT(*) AS quantidadeCarteiras,
|
||||
|
||||
SUM(CASE
|
||||
WHEN habilitado = 1 THEN 1
|
||||
ELSE 0
|
||||
END) AS carteirasHabilitadas,
|
||||
|
||||
SUM(CASE
|
||||
WHEN debito = 1
|
||||
AND COALESCE(investimento, 0) = 0
|
||||
AND habilitado = 1
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END) AS quantidadeContasDisponiveis,
|
||||
|
||||
SUM(CASE
|
||||
WHEN debito = 1
|
||||
AND COALESCE(investimento, 0) = 1
|
||||
AND habilitado = 1
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END) AS quantidadeInvestimentos,
|
||||
|
||||
SUM(CASE
|
||||
WHEN debito = 0
|
||||
AND habilitado = 1
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END) AS quantidadeCreditos
|
||||
FROM bancos
|
||||
WHERE idusuarios = ?
|
||||
`,
|
||||
[idUsuario]
|
||||
);
|
||||
|
||||
const row = rows[0] || {};
|
||||
|
||||
const saldoDisponivel = normalizarNumero(row.saldoDisponivel);
|
||||
const totalInvestido = normalizarNumero(row.totalInvestido);
|
||||
const saldoDebitoTotal = normalizarNumero(row.saldoDebitoTotal);
|
||||
const dividaCredito = normalizarNumero(row.dividaCredito);
|
||||
|
||||
return {
|
||||
saldoDebito: normalizarNumero(row.saldoDebito),
|
||||
dividaCredito: normalizarNumero(row.dividaCredito),
|
||||
saldoDisponivel,
|
||||
totalInvestido,
|
||||
saldoDebitoTotal,
|
||||
saldoDebito: saldoDisponivel, // mantém compatibilidade com o front atual
|
||||
dividaCredito,
|
||||
patrimonioBruto: saldoDisponivel + totalInvestido,
|
||||
patrimonioLiquido: saldoDisponivel + totalInvestido - dividaCredito,
|
||||
quantidadeCarteiras: normalizarNumero(row.quantidadeCarteiras),
|
||||
carteirasHabilitadas: normalizarNumero(row.carteirasHabilitadas),
|
||||
quantidadeContasDisponiveis: normalizarNumero(row.quantidadeContasDisponiveis),
|
||||
quantidadeInvestimentos: normalizarNumero(row.quantidadeInvestimentos),
|
||||
quantidadeCreditos: normalizarNumero(row.quantidadeCreditos),
|
||||
};
|
||||
}
|
||||
|
||||
async function buscarCardsMovimentos(dataInicio, dataFim) {
|
||||
async function buscarCardsMovimentos(idUsuario, dataInicio, dataFim) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -144,12 +212,15 @@ async function buscarCardsMovimentos(dataInicio, dataFim) {
|
|||
|
||||
COUNT(*) AS quantidadeMovimentos
|
||||
FROM contasapagar cp
|
||||
LEFT JOIN bancos b
|
||||
ON b.idbancos = cp.idbancos
|
||||
LEFT JOIN centrodecustos cc
|
||||
ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND b.idusuarios = ?
|
||||
AND DATE(cp.datavencimento) BETWEEN ? AND ?
|
||||
`,
|
||||
[dataInicio, dataFim]
|
||||
[idUsuario, dataInicio, dataFim]
|
||||
);
|
||||
|
||||
const row = rows[0] || {};
|
||||
|
|
@ -188,7 +259,7 @@ async function buscarCardsMovimentos(dataInicio, dataFim) {
|
|||
};
|
||||
}
|
||||
|
||||
async function buscarAlertasVencimento() {
|
||||
async function buscarAlertasVencimento(idUsuario) {
|
||||
const hoje = hojeISO();
|
||||
|
||||
const [rows] = await pool.query(
|
||||
|
|
@ -286,9 +357,12 @@ async function buscarAlertasVencimento() {
|
|||
FROM contasapagar cp
|
||||
LEFT JOIN centrodecustos cc
|
||||
ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
LEFT JOIN bancos b
|
||||
ON b.idbancos = cp.idbancos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND cp.status IN ('A pagar', 'A receber')
|
||||
AND cp.datavencimento IS NOT NULL
|
||||
AND b.idusuarios = ?
|
||||
`,
|
||||
[
|
||||
hoje, hoje,
|
||||
|
|
@ -304,6 +378,8 @@ async function buscarAlertasVencimento() {
|
|||
|
||||
hoje, hoje,
|
||||
hoje, hoje,
|
||||
|
||||
idUsuario
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -334,7 +410,7 @@ async function buscarAlertasVencimento() {
|
|||
};
|
||||
}
|
||||
|
||||
async function buscarProximosVencimentos() {
|
||||
async function buscarProximosVencimentos(idUsuario) {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
cp.idcontasapagar,
|
||||
|
|
@ -357,16 +433,18 @@ async function buscarProximosVencimentos() {
|
|||
LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos
|
||||
LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND bc.idusuarios = ?
|
||||
AND cp.status IN ('A pagar', 'A receber')
|
||||
AND cp.datavencimento IS NOT NULL
|
||||
ORDER BY DATE(cp.datavencimento) ASC, cp.idcontasapagar ASC
|
||||
LIMIT 10
|
||||
`);
|
||||
`,
|
||||
[idUsuario]);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function buscarCreditosEmAberto(dataInicio, dataFim) {
|
||||
async function buscarCreditosEmAberto(idUsuario, dataInicio, dataFim) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -423,7 +501,8 @@ async function buscarCreditosEmAberto(dataInicio, dataFim) {
|
|||
AND cp.deleted_at IS NULL
|
||||
AND cp.status = 'A pagar'
|
||||
AND cp.movimento IN ('Saida', 'Sangria')
|
||||
WHERE bc.debito = 0
|
||||
WHERE bc.idusuarios = ?
|
||||
AND bc.debito = 0
|
||||
AND bc.habilitado = 1
|
||||
GROUP BY
|
||||
bc.idbancos,
|
||||
|
|
@ -443,6 +522,7 @@ async function buscarCreditosEmAberto(dataInicio, dataFim) {
|
|||
dataFim,
|
||||
dataFim,
|
||||
dataFim,
|
||||
idUsuario,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -460,7 +540,7 @@ async function buscarCreditosEmAberto(dataInicio, dataFim) {
|
|||
}));
|
||||
}
|
||||
|
||||
async function buscarCarteiras(dataInicio, dataFim) {
|
||||
async function buscarCarteiras(idUsuario, dataInicio, dataFim) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -472,21 +552,25 @@ async function buscarCarteiras(dataInicio, dataFim) {
|
|||
bc.dia_vencimento,
|
||||
bc.insert_date,
|
||||
bc.update_date,
|
||||
bc.investimento,
|
||||
|
||||
COALESCE(aud.delta_periodo, 0) AS delta_periodo
|
||||
FROM bancos bc
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
idbancos,
|
||||
COALESCE(SUM(valor_delta), 0) AS delta_periodo
|
||||
FROM bancos_saldo_auditoria
|
||||
WHERE DATE(insert_date) BETWEEN ? AND ?
|
||||
GROUP BY idbancos
|
||||
bsa.idbancos,
|
||||
COALESCE(SUM(bsa.valor_delta), 0) AS delta_periodo
|
||||
FROM bancos_saldo_auditoria bsa
|
||||
INNER JOIN bancos b2 ON b2.idbancos = bsa.idbancos
|
||||
WHERE b2.idusuarios = ?
|
||||
AND DATE(bsa.insert_date) BETWEEN ? AND ?
|
||||
GROUP BY bsa.idbancos
|
||||
) aud ON aud.idbancos = bc.idbancos
|
||||
WHERE bc.habilitado = 1
|
||||
AND bc.idusuarios = ?
|
||||
ORDER BY bc.debito DESC, bc.descricao ASC
|
||||
`,
|
||||
[dataInicio, dataFim]
|
||||
[idUsuario, dataInicio, dataFim, idUsuario]
|
||||
);
|
||||
|
||||
return rows.map((item) => {
|
||||
|
|
@ -494,6 +578,7 @@ async function buscarCarteiras(dataInicio, dataFim) {
|
|||
const deltaPeriodo = normalizarNumero(item.delta_periodo);
|
||||
const saldoMesAnterior = saldoAtual - deltaPeriodo;
|
||||
const variacaoValor = saldoAtual - saldoMesAnterior;
|
||||
const isInvestimento = Number(item.investimento || 0) === 1;
|
||||
|
||||
const variacaoPercentual = saldoMesAnterior !== 0
|
||||
? (variacaoValor / Math.abs(saldoMesAnterior)) * 100
|
||||
|
|
@ -512,11 +597,16 @@ async function buscarCarteiras(dataInicio, dataFim) {
|
|||
variacao_valor: variacaoValor,
|
||||
variacao_percentual: variacaoPercentual,
|
||||
tendencia_boa: tendenciaBoa,
|
||||
tipo_carteira: isCredito
|
||||
? 'credito'
|
||||
: isInvestimento
|
||||
? 'investimento'
|
||||
: 'disponivel',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function buscarUltimosMovimentos() {
|
||||
async function buscarUltimosMovimentos(idUsuario) {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
cp.idcontasapagar,
|
||||
|
|
@ -543,14 +633,16 @@ async function buscarUltimosMovimentos() {
|
|||
LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos
|
||||
LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND bc.idusuarios = ?
|
||||
ORDER BY cp.insert_date DESC, cp.idcontasapagar DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
`,
|
||||
[idUsuario]);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function buscarGraficos(dataInicio, dataFim) {
|
||||
async function buscarGraficos(idUsuario, dataInicio, dataFim) {
|
||||
const [centrosRows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -560,9 +652,12 @@ async function buscarGraficos(dataInicio, dataFim) {
|
|||
FROM contasapagar cp
|
||||
LEFT JOIN centrodecustos cc
|
||||
ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
LEFT JOIN bancos b
|
||||
ON b.idbancos = cp.idbancos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND cp.movimento IN ('Saida')
|
||||
AND DATE(cp.datavencimento) BETWEEN ? AND ?
|
||||
AND b.idusuarios = ?
|
||||
GROUP BY
|
||||
cp.idcentrodecustos,
|
||||
COALESCE(cc.descricao, 'Sem centro de custo')
|
||||
|
|
@ -570,7 +665,7 @@ async function buscarGraficos(dataInicio, dataFim) {
|
|||
ORDER BY valor DESC
|
||||
LIMIT 8
|
||||
`,
|
||||
[dataInicio, dataFim]
|
||||
[dataInicio, dataFim, idUsuario]
|
||||
);
|
||||
|
||||
const [entradasSaidasRows] = await pool.query(
|
||||
|
|
@ -595,10 +690,13 @@ async function buscarGraficos(dataInicio, dataFim) {
|
|||
FROM contasapagar cp
|
||||
LEFT JOIN centrodecustos cc
|
||||
ON cc.idcentrodecustos = cp.idcentrodecustos
|
||||
LEFT JOIN bancos b
|
||||
ON b.idbancos = cp.idbancos
|
||||
WHERE cp.deleted_at IS NULL
|
||||
AND DATE(cp.datavencimento) BETWEEN ? AND ?
|
||||
AND b.idusuarios = ?
|
||||
`,
|
||||
[dataInicio, dataFim]
|
||||
[dataInicio, dataFim, idUsuario]
|
||||
);
|
||||
|
||||
const entradas = normalizarNumero(entradasSaidasRows[0]?.entradas);
|
||||
|
|
@ -627,13 +725,38 @@ async function buscarGraficos(dataInicio, dataFim) {
|
|||
};
|
||||
}
|
||||
|
||||
async function buscarResumoDashboard(filtros = {}) {
|
||||
async function buscarCardsInvestimentos(idUsuario, dataInicio, dataFim) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
COALESCE(SUM(bsa.valor_delta), 0) AS rendimentoInvestimentosPeriodo,
|
||||
COUNT(*) AS quantidadeRendimentosPeriodo
|
||||
FROM bancos_saldo_auditoria bsa
|
||||
INNER JOIN bancos b ON b.idbancos = bsa.idbancos
|
||||
WHERE b.idusuarios = ?
|
||||
AND b.investimento = 1
|
||||
AND bsa.origem = 'rendimento_investimento'
|
||||
AND DATE(bsa.insert_date) BETWEEN ? AND ?
|
||||
`,
|
||||
[idUsuario, dataInicio, dataFim]
|
||||
);
|
||||
|
||||
const row = rows[0] || {};
|
||||
|
||||
return {
|
||||
rendimentoInvestimentosPeriodo: normalizarNumero(row.rendimentoInvestimentosPeriodo),
|
||||
quantidadeRendimentosPeriodo: normalizarNumero(row.quantidadeRendimentosPeriodo),
|
||||
};
|
||||
}
|
||||
|
||||
async function buscarResumoDashboard(idUsuario, filtros = {}) {
|
||||
const dataInicio = normalizarDataISO(filtros.dataInicio, inicioMesAtualISO());
|
||||
const dataFim = normalizarDataISO(filtros.dataFim, fimMesAtualISO());
|
||||
|
||||
const [
|
||||
cardsBancos,
|
||||
cardsMovimentos,
|
||||
cardsInvestimentos,
|
||||
alertasVencimento,
|
||||
proximosVencimentos,
|
||||
creditos,
|
||||
|
|
@ -641,14 +764,15 @@ async function buscarResumoDashboard(filtros = {}) {
|
|||
ultimosMovimentos,
|
||||
graficos,
|
||||
] = await Promise.all([
|
||||
buscarCardsBancos(),
|
||||
buscarCardsMovimentos(dataInicio, dataFim),
|
||||
buscarAlertasVencimento(),
|
||||
buscarProximosVencimentos(),
|
||||
buscarCreditosEmAberto(dataInicio, dataFim),
|
||||
buscarCarteiras(dataInicio, dataFim),
|
||||
buscarUltimosMovimentos(),
|
||||
buscarGraficos(dataInicio, dataFim),
|
||||
buscarCardsBancos(idUsuario),
|
||||
buscarCardsMovimentos(idUsuario, dataInicio, dataFim),
|
||||
buscarCardsInvestimentos(idUsuario, dataInicio, dataFim),
|
||||
buscarAlertasVencimento(idUsuario),
|
||||
buscarProximosVencimentos(idUsuario),
|
||||
buscarCreditosEmAberto(idUsuario, dataInicio, dataFim),
|
||||
buscarCarteiras(idUsuario, dataInicio, dataFim),
|
||||
buscarUltimosMovimentos(idUsuario),
|
||||
buscarGraficos(idUsuario, dataInicio, dataFim),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
@ -660,6 +784,7 @@ async function buscarResumoDashboard(filtros = {}) {
|
|||
cards: {
|
||||
...cardsBancos,
|
||||
...cardsMovimentos,
|
||||
...cardsInvestimentos,
|
||||
...alertasVencimento,
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ async function listar(req, res) {
|
|||
limite: req.query.limite,
|
||||
offset: req.query.offset,
|
||||
page: req.query.page,
|
||||
busca: req.query.busca,
|
||||
movimento: req.query.movimento,
|
||||
status: req.query.status,
|
||||
idbancos: req.query.idbancos,
|
||||
|
|
@ -139,10 +140,11 @@ async function listar(req, res) {
|
|||
dataCampo: req.query.dataCampo,
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
busca: req.query.busca,
|
||||
incluirExcluidos: req.query.incluirExcluidos,
|
||||
orderBy: req.query.orderBy,
|
||||
orderDirection: req.query.orderDirection,
|
||||
|
||||
idusuarios: req.user.idusuarios || req.user.id,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
|
|
@ -172,7 +174,9 @@ async function buscarPorId(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const movimento = await movimentosService.buscarMovimentoPorId(id);
|
||||
const movimento = await movimentosService.buscarMovimentoPorId(id, {
|
||||
idUsuario: req.user.idusuarios || req.user.id,
|
||||
});
|
||||
|
||||
if (!movimento) {
|
||||
return res.status(404).json({
|
||||
|
|
|
|||
|
|
@ -206,6 +206,11 @@ function montarWhereMovimentos(filtros = {}) {
|
|||
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);
|
||||
|
|
@ -411,6 +416,15 @@ async function listarMovimentos(filtros = {}) {
|
|||
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(
|
||||
`
|
||||
|
|
@ -419,26 +433,37 @@ async function buscarMovimentoPorId(id, opcoes = {}) {
|
|||
${FROM_MOVIMENTO_JOIN}
|
||||
WHERE cp.idcontasapagar = ?
|
||||
${incluirExcluidos ? '' : 'AND cp.deleted_at IS NULL'}
|
||||
${filtroUsuario}
|
||||
LIMIT 1
|
||||
`,
|
||||
[id]
|
||||
params
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function buscarMovimentoPorIdParaUpdate(connection, id) {
|
||||
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
|
||||
AND cp.deleted_at IS NULL
|
||||
${filtroUsuario}
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[id]
|
||||
params
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
|
|
@ -713,7 +738,7 @@ function montarParcelas(idUsuario, dados) {
|
|||
}
|
||||
|
||||
async function aplicarSaldoMovimento(connection, movimento, contexto) {
|
||||
const deltas = await movimentosSaldoService.calcularImpactoMovimento(connection, movimento);
|
||||
const deltas = await movimentosSaldoService.calcularImpactoMovimento(connection, movimento, contexto);
|
||||
|
||||
if (deltas.length === 0) {
|
||||
return {
|
||||
|
|
@ -826,7 +851,7 @@ async function atualizarMovimento(id, idUsuario, dados) {
|
|||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id);
|
||||
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
|
||||
|
||||
if (!movimentoAntigo) {
|
||||
await connection.rollback();
|
||||
|
|
@ -845,7 +870,8 @@ async function atualizarMovimento(id, idUsuario, dados) {
|
|||
if (Number(movimentoAntigo.saldo_processado) === 1) {
|
||||
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
|
||||
connection,
|
||||
movimentoAntigo
|
||||
movimentoAntigo,
|
||||
{ idusuarios: idUsuario }
|
||||
);
|
||||
|
||||
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(
|
||||
|
|
@ -924,7 +950,7 @@ async function deletarMovimento(id, idUsuario) {
|
|||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id);
|
||||
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
|
||||
|
||||
if (!movimentoAntigo) {
|
||||
await connection.rollback();
|
||||
|
|
@ -939,7 +965,8 @@ async function deletarMovimento(id, idUsuario) {
|
|||
if (Number(movimentoAntigo.saldo_processado) === 1) {
|
||||
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
|
||||
connection,
|
||||
movimentoAntigo
|
||||
movimentoAntigo,
|
||||
{ idusuarios: idUsuario }
|
||||
);
|
||||
|
||||
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(
|
||||
|
|
|
|||
|
|
@ -12,42 +12,64 @@ function bancoEhCredito(banco) {
|
|||
return Number(banco?.debito) === 0;
|
||||
}
|
||||
|
||||
async function buscarBanco(connection, idbancos) {
|
||||
async function buscarBanco(connection, idbancos, idusuarios = null) {
|
||||
if (!idbancos) return null;
|
||||
|
||||
const params = [idbancos];
|
||||
let filtroUsuario = '';
|
||||
|
||||
if (idusuarios) {
|
||||
filtroUsuario = 'AND idusuarios = ?';
|
||||
params.push(idusuarios);
|
||||
}
|
||||
|
||||
const [rows] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
idbancos,
|
||||
descricao,
|
||||
saldo,
|
||||
debito
|
||||
debito,
|
||||
investimento,
|
||||
idusuarios
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
${filtroUsuario}
|
||||
LIMIT 1
|
||||
`,
|
||||
[idbancos]
|
||||
params
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function buscarBancoParaUpdate(connection, idbancos) {
|
||||
async function buscarBancoParaUpdate(connection, idbancos, idusuarios = null) {
|
||||
if (!idbancos) return null;
|
||||
|
||||
const params = [idbancos];
|
||||
let filtroUsuario = '';
|
||||
|
||||
if (idusuarios) {
|
||||
filtroUsuario = 'AND idusuarios = ?';
|
||||
params.push(idusuarios);
|
||||
}
|
||||
|
||||
const [rows] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
idbancos,
|
||||
descricao,
|
||||
saldo,
|
||||
debito
|
||||
debito,
|
||||
investimento,
|
||||
idusuarios
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
${filtroUsuario}
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[idbancos]
|
||||
params
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
|
|
@ -145,9 +167,9 @@ function calcularDeltasParaMovimento(movimento, bancoOrigem, bancoDestino = null
|
|||
return deltas;
|
||||
}
|
||||
|
||||
async function calcularImpactoMovimento(connection, movimento) {
|
||||
const bancoOrigem = await buscarBanco(connection, movimento.idbancos);
|
||||
const bancoDestino = await buscarBanco(connection, movimento.idbancos_p);
|
||||
async function calcularImpactoMovimento(connection, movimento, contexto = {}) {
|
||||
const bancoOrigem = await buscarBanco(connection, movimento.idbancos, contexto.idusuarios);
|
||||
const bancoDestino = await buscarBanco(connection, movimento.idbancos_p, contexto.idusuarios);
|
||||
|
||||
return calcularDeltasParaMovimento(movimento, bancoOrigem, bancoDestino);
|
||||
}
|
||||
|
|
@ -164,7 +186,7 @@ async function aplicarDeltasSaldo(connection, deltas, contexto = {}) {
|
|||
const impactos = [];
|
||||
|
||||
for (const delta of deltas) {
|
||||
const banco = await buscarBancoParaUpdate(connection, delta.idbancos);
|
||||
const banco = await buscarBancoParaUpdate(connection, delta.idbancos, contexto.idusuarios);
|
||||
|
||||
if (!banco) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
const quitacoesCreditoService = require('../services/quitacoesCredito.service');
|
||||
|
||||
function resolverIdUsuario(req) {
|
||||
return Number(req.user?.idusuarios || req.user?.id);
|
||||
}
|
||||
|
||||
function tratarErro(error, res, mensagemPadrao) {
|
||||
if (error?.statusCode) {
|
||||
return res.status(error.statusCode).json({
|
||||
|
|
@ -43,7 +47,16 @@ function validarCriacao(dados) {
|
|||
|
||||
async function listarBancosCredito(req, res) {
|
||||
try {
|
||||
const bancos = await quitacoesCreditoService.listarBancosCredito();
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const bancos = await quitacoesCreditoService.listarBancosCredito(idUsuario);
|
||||
|
||||
return res.json({
|
||||
ok: true,
|
||||
|
|
@ -57,7 +70,16 @@ async function listarBancosCredito(req, res) {
|
|||
|
||||
async function listarBancosPagamento(req, res) {
|
||||
try {
|
||||
const bancos = await quitacoesCreditoService.listarBancosPagamento();
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const bancos = await quitacoesCreditoService.listarBancosPagamento(idUsuario);
|
||||
|
||||
return res.json({
|
||||
ok: true,
|
||||
|
|
@ -71,7 +93,16 @@ async function listarBancosPagamento(req, res) {
|
|||
|
||||
async function preview(req, res) {
|
||||
try {
|
||||
const resultado = await quitacoesCreditoService.previewQuitacao({
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await quitacoesCreditoService.previewQuitacao(idUsuario, {
|
||||
idBancoCredito: req.query.idBancoCredito || req.query.idbancos,
|
||||
dataInicio: req.query.dataInicio,
|
||||
dataFim: req.query.dataFim,
|
||||
|
|
@ -102,7 +133,16 @@ async function criar(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const resultado = await quitacoesCreditoService.criarQuitacaoCredito(req.user.id, req.body);
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await quitacoesCreditoService.criarQuitacaoCredito(idUsuario, req.body);
|
||||
|
||||
return res.status(201).json({
|
||||
ok: true,
|
||||
|
|
@ -120,7 +160,16 @@ async function criar(req, res) {
|
|||
|
||||
async function listarHistorico(req, res) {
|
||||
try {
|
||||
const resultado = await quitacoesCreditoService.listarHistorico({
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await quitacoesCreditoService.listarHistorico(idUsuario, {
|
||||
limite: req.query.limite,
|
||||
offset: req.query.offset,
|
||||
page: req.query.page,
|
||||
|
|
@ -154,7 +203,16 @@ async function buscarPorId(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
const quitacao = await quitacoesCreditoService.buscarQuitacaoPorId(id);
|
||||
const idUsuario = resolverIdUsuario(req);
|
||||
|
||||
if (!idUsuario) {
|
||||
return res.status(401).json({
|
||||
ok: false,
|
||||
message: 'Usuário inválido.',
|
||||
});
|
||||
}
|
||||
|
||||
const quitacao = await quitacoesCreditoService.buscarQuitacaoPorId(id, idUsuario);
|
||||
|
||||
if (!quitacao) {
|
||||
return res.status(404).json({
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ function idsUnicos(ids = []) {
|
|||
)];
|
||||
}
|
||||
|
||||
async function buscarBancoParaUpdate(connection, idbancos) {
|
||||
async function buscarBancoParaUpdate(connection, idbancos, idUsuario) {
|
||||
const [rows] = await connection.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -120,13 +120,15 @@ async function buscarBancoParaUpdate(connection, idbancos) {
|
|||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
habilitado
|
||||
habilitado,
|
||||
idusuarios
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[idbancos]
|
||||
[idbancos, idUsuario]
|
||||
);
|
||||
|
||||
return rows[0] || null;
|
||||
|
|
@ -154,7 +156,7 @@ async function inserirAuditoriaConta(connection, dados) {
|
|||
);
|
||||
}
|
||||
|
||||
async function listarBancosCredito() {
|
||||
async function listarBancosCredito(idUsuario) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -166,16 +168,18 @@ async function listarBancosCredito() {
|
|||
insert_date,
|
||||
update_date
|
||||
FROM bancos
|
||||
WHERE debito = 0
|
||||
AND COALESCE(habilitado, 1) = 1
|
||||
WHERE idusuarios = ?
|
||||
AND debito = 0
|
||||
AND COALESCE(habilitado, 1) = 1
|
||||
ORDER BY descricao ASC, idbancos ASC
|
||||
`
|
||||
`,
|
||||
[idUsuario]
|
||||
);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function listarBancosPagamento() {
|
||||
async function listarBancosPagamento(idUsuario) {
|
||||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -187,10 +191,12 @@ async function listarBancosPagamento() {
|
|||
insert_date,
|
||||
update_date
|
||||
FROM bancos
|
||||
WHERE debito = 1
|
||||
AND COALESCE(habilitado, 1) = 1
|
||||
WHERE idusuarios = ?
|
||||
AND debito = 1
|
||||
AND COALESCE(habilitado, 1) = 1
|
||||
ORDER BY descricao ASC, idbancos ASC
|
||||
`
|
||||
`,
|
||||
[idUsuario]
|
||||
);
|
||||
|
||||
return rows;
|
||||
|
|
@ -202,9 +208,13 @@ function montarWhereContasPreview(filtros = {}) {
|
|||
"cp.movimento = 'Saida'",
|
||||
"cp.status = 'A pagar'",
|
||||
'cp.idbancos = ?',
|
||||
'b.idusuarios = ?',
|
||||
];
|
||||
|
||||
const params = [Number(filtros.idBancoCredito)];
|
||||
const params = [
|
||||
Number(filtros.idBancoCredito),
|
||||
Number(filtros.idUsuario),
|
||||
];
|
||||
|
||||
if (filtros.dataInicio) {
|
||||
where.push('DATE(cp.datavencimento) >= ?');
|
||||
|
|
@ -237,7 +247,7 @@ function montarWhereContasPreview(filtros = {}) {
|
|||
};
|
||||
}
|
||||
|
||||
async function previewQuitacao(filtros = {}) {
|
||||
async function previewQuitacao(idUsuario, filtros = {}) {
|
||||
const idBancoCredito = normalizarNumeroOuNull(filtros.idBancoCredito || filtros.idbancos);
|
||||
|
||||
if (!idBancoCredito) {
|
||||
|
|
@ -257,12 +267,14 @@ async function previewQuitacao(filtros = {}) {
|
|||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
habilitado
|
||||
habilitado,
|
||||
idusuarios
|
||||
FROM bancos
|
||||
WHERE idbancos = ?
|
||||
AND idusuarios = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[idBancoCredito]
|
||||
[idBancoCredito, idUsuario]
|
||||
);
|
||||
|
||||
const bancoCredito = bancoRows[0] || null;
|
||||
|
|
@ -278,6 +290,7 @@ async function previewQuitacao(filtros = {}) {
|
|||
const { whereSql, params } = montarWhereContasPreview({
|
||||
...filtros,
|
||||
idBancoCredito,
|
||||
idUsuario,
|
||||
});
|
||||
|
||||
const [rows] = await pool.query(
|
||||
|
|
@ -339,7 +352,7 @@ async function previewQuitacao(filtros = {}) {
|
|||
};
|
||||
}
|
||||
|
||||
async function buscarContasSelecionadasParaUpdate(connection, ids) {
|
||||
async function buscarContasSelecionadasParaUpdate(connection, ids, idBancoCredito, idUsuario) {
|
||||
if (!ids.length) return [];
|
||||
|
||||
const placeholders = ids.map(() => '?').join(', ');
|
||||
|
|
@ -349,16 +362,23 @@ async function buscarContasSelecionadasParaUpdate(connection, ids) {
|
|||
SELECT
|
||||
cp.*
|
||||
FROM contasapagar cp
|
||||
INNER JOIN bancos b ON b.idbancos = cp.idbancos
|
||||
WHERE cp.idcontasapagar IN (${placeholders})
|
||||
AND cp.idbancos = ?
|
||||
AND b.idusuarios = ?
|
||||
FOR UPDATE
|
||||
`,
|
||||
ids
|
||||
[
|
||||
...ids,
|
||||
idBancoCredito,
|
||||
idUsuario,
|
||||
]
|
||||
);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function buscarContasDetalhadasPorIds(connection, ids) {
|
||||
async function buscarContasDetalhadasPorIds(connection, ids, idUsuario) {
|
||||
if (!ids.length) return [];
|
||||
|
||||
const placeholders = ids.map(() => '?').join(', ');
|
||||
|
|
@ -369,9 +389,10 @@ async function buscarContasDetalhadasPorIds(connection, ids) {
|
|||
${CAMPOS_CONTA_SELECT}
|
||||
${FROM_CONTA_JOIN}
|
||||
WHERE cp.idcontasapagar IN (${placeholders})
|
||||
AND b.idusuarios = ?
|
||||
ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC
|
||||
`,
|
||||
ids
|
||||
[...ids, idUsuario]
|
||||
);
|
||||
|
||||
return rows;
|
||||
|
|
@ -405,8 +426,8 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
|||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const bancoCredito = await buscarBancoParaUpdate(connection, idBancoCredito);
|
||||
const bancoPagamento = await buscarBancoParaUpdate(connection, idBancoPagamento);
|
||||
const bancoCredito = await buscarBancoParaUpdate(connection, idBancoCredito, idUsuario);
|
||||
const bancoPagamento = await buscarBancoParaUpdate(connection, idBancoPagamento, idUsuario);
|
||||
|
||||
if (!bancoCredito) {
|
||||
throw criarErro('Banco de crédito não encontrado.', 404);
|
||||
|
|
@ -432,7 +453,12 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
|||
throw criarErro('O banco de pagamento está desabilitado.');
|
||||
}
|
||||
|
||||
const contasAntes = await buscarContasSelecionadasParaUpdate(connection, idsContas);
|
||||
const contasAntes = await buscarContasSelecionadasParaUpdate(
|
||||
connection,
|
||||
idsContas,
|
||||
idBancoCredito,
|
||||
idUsuario
|
||||
);
|
||||
|
||||
if (contasAntes.length !== idsContas.length) {
|
||||
const encontrados = new Set(contasAntes.map((conta) => Number(conta.idcontasapagar)));
|
||||
|
|
@ -538,7 +564,7 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
|||
]
|
||||
);
|
||||
|
||||
const contasDepois = await buscarContasDetalhadasPorIds(connection, idsContas);
|
||||
const contasDepois = await buscarContasDetalhadasPorIds(connection, idsContas, idUsuario);
|
||||
const contasDepoisPorId = new Map(contasDepois.map((conta) => [Number(conta.idcontasapagar), conta]));
|
||||
|
||||
for (const contaAntes of contasAntes) {
|
||||
|
|
@ -615,15 +641,19 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
async function listarHistorico(filtros = {}) {
|
||||
async function listarHistorico(idUsuario, 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 where = ['cq.deleted_at IS NULL'];
|
||||
const params = [];
|
||||
const where = [
|
||||
'cq.deleted_at IS NULL',
|
||||
'bp.idusuarios = ?',
|
||||
'bc.idusuarios = ?',
|
||||
];
|
||||
const params = [idUsuario, idUsuario];
|
||||
|
||||
if (filtros.idBancoCredito) {
|
||||
where.push('cq.idbancos_p = ?');
|
||||
|
|
@ -746,7 +776,7 @@ async function listarHistorico(filtros = {}) {
|
|||
};
|
||||
}
|
||||
|
||||
async function buscarQuitacaoPorId(id) {
|
||||
async function buscarQuitacaoPorId(id, idUsuario) {
|
||||
const [quitacaoRows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
|
|
@ -768,9 +798,11 @@ async function buscarQuitacaoPorId(id) {
|
|||
LEFT JOIN bancos bc ON bc.idbancos = cq.idbancos_p
|
||||
LEFT JOIN usuarios u ON u.idusuarios = cq.idusuarios
|
||||
WHERE cq.idcontasquitadas = ?
|
||||
AND bp.idusuarios = ?
|
||||
AND bc.idusuarios = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[id]
|
||||
[id, idUsuario, idUsuario]
|
||||
);
|
||||
|
||||
const quitacao = quitacaoRows[0] || null;
|
||||
|
|
@ -786,9 +818,10 @@ async function buscarQuitacaoPorId(id) {
|
|||
${FROM_CONTA_JOIN}
|
||||
INNER JOIN contasquitadasref cqr ON cqr.idcontasapagar = cp.idcontasapagar
|
||||
WHERE cqr.idcontasquitadas = ?
|
||||
AND b.idusuarios = ?
|
||||
ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC
|
||||
`,
|
||||
[id]
|
||||
[id, idUsuario]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,27 @@ async function listarBancos() {
|
|||
const [rows] = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
idbancos AS id,
|
||||
descricao,
|
||||
saldo,
|
||||
debito,
|
||||
dia_vencimento
|
||||
FROM bancos
|
||||
WHERE habilitado = 1
|
||||
ORDER BY descricao ASC
|
||||
b.idbancos AS id,
|
||||
b.idbancos,
|
||||
b.descricao AS banco_descricao,
|
||||
u.idusuarios,
|
||||
u.nome AS usuario_nome,
|
||||
CONCAT(
|
||||
COALESCE(u.nome, 'Usuário sem nome'),
|
||||
' - ',
|
||||
b.descricao
|
||||
) AS descricao,
|
||||
b.saldo,
|
||||
b.debito,
|
||||
b.investimento,
|
||||
b.dia_vencimento
|
||||
FROM bancos b
|
||||
LEFT JOIN usuarios u
|
||||
ON u.idusuarios = b.idusuarios
|
||||
WHERE b.habilitado = 1
|
||||
ORDER BY
|
||||
b.descricao ASC,
|
||||
u.nome ASC
|
||||
`
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { NotFoundPage } from '../pages/NotFoundPage';
|
|||
import { BancosPage } from '../features/bancos/pages/BancosPage';
|
||||
import { NovoBancoPage } from '../features/bancos/pages/NovoBancoPage';
|
||||
import { EditarBancoPage } from '../features/bancos/pages/EditarBancoPage';
|
||||
import { RendimentosPage } from '../features/bancos/pages/RendimentosPage';
|
||||
import { ClientesPage } from '../features/clientes/pages/ClientesPage';
|
||||
import { NovoClientePage } from '../features/clientes/pages/NovoClientePage';
|
||||
import { EditarClientePage } from '../features/clientes/pages/EditarClientePage';
|
||||
|
|
@ -73,6 +74,10 @@ export const routes: RouteObject[] = [
|
|||
path: 'bancos/:id/editar',
|
||||
element: <EditarBancoPage />,
|
||||
},
|
||||
{
|
||||
path: 'rendimentos',
|
||||
element: <RendimentosPage />,
|
||||
},
|
||||
{
|
||||
path: 'clientes',
|
||||
element: <ClientesPage />,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import DashboardIcon from '@mui/icons-material/Dashboard';
|
|||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
|
||||
import SavingsIcon from '@mui/icons-material/Savings';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import PeopleAltIcon from '@mui/icons-material/PeopleAlt';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
|
|
@ -49,6 +50,11 @@ const menuItems = [
|
|||
path: '/bancos',
|
||||
icon: <AccountBalanceWalletIcon />,
|
||||
},
|
||||
{
|
||||
label: 'Rendimentos',
|
||||
path: '/rendimentos',
|
||||
icon: <SavingsIcon />,
|
||||
},
|
||||
{
|
||||
label: 'Centros de custo',
|
||||
path: '/centros-custo',
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
|
||||
const [descricao, setDescricao] = useState('');
|
||||
const [saldo, setSaldo] = useState('0');
|
||||
const [debito, setDebito] = useState('1');
|
||||
const [tipoCarteira, setTipoCarteira] = useState<'debito' | 'credito' | 'investimento'>('debito');
|
||||
const [habilitado, setHabilitado] = useState('1');
|
||||
const [diaVencimento, setDiaVencimento] = useState('');
|
||||
|
||||
|
|
@ -124,17 +124,24 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
|
||||
setDescricao(initialData.descricao || '');
|
||||
setSaldo(String(initialData.saldo ?? 0));
|
||||
setDebito(String(initialData.debito ?? 1));
|
||||
setHabilitado(String(initialData.habilitado ?? 1));
|
||||
setDiaVencimento(
|
||||
initialData.dia_vencimento ? String(initialData.dia_vencimento) : ''
|
||||
);
|
||||
|
||||
if (Number(initialData.investimento) === 1) {
|
||||
setTipoCarteira('investimento');
|
||||
} else if (Number(initialData.debito) === 0) {
|
||||
setTipoCarteira('credito');
|
||||
} else {
|
||||
setTipoCarteira('debito');
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
function limparFormulario() {
|
||||
setDescricao('');
|
||||
setSaldo('0');
|
||||
setDebito('1');
|
||||
setTipoCarteira('debito');
|
||||
setHabilitado('1');
|
||||
setDiaVencimento('');
|
||||
}
|
||||
|
|
@ -152,7 +159,7 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (Number(debito) === 0) {
|
||||
if (tipoCarteira === 'credito') {
|
||||
const dia = Number(diaVencimento);
|
||||
|
||||
if (!diaVencimento || !Number.isInteger(dia) || dia < 1 || dia > 31) {
|
||||
|
|
@ -169,9 +176,10 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
const payload: CriarBancoRequest = {
|
||||
descricao: descricao.trim(),
|
||||
saldo: Number(saldo || 0),
|
||||
debito: Number(debito),
|
||||
debito: tipoCarteira === 'credito' ? 0 : 1,
|
||||
investimento: tipoCarteira === 'investimento' ? 1 : 0,
|
||||
habilitado: Number(habilitado || 1),
|
||||
dia_vencimento: Number(debito) === 0 ? Number(diaVencimento) : null,
|
||||
dia_vencimento: tipoCarteira === 'credito' ? Number(diaVencimento) : null,
|
||||
};
|
||||
|
||||
if (isEdit) {
|
||||
|
|
@ -281,16 +289,19 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
<TextField
|
||||
select
|
||||
label="Tipo"
|
||||
value={debito}
|
||||
onChange={(event) => setDebito(event.target.value)}
|
||||
value={tipoCarteira}
|
||||
onChange={(event) =>
|
||||
setTipoCarteira(event.target.value as 'debito' | 'credito' | 'investimento')
|
||||
}
|
||||
fullWidth
|
||||
sx={fieldSx}
|
||||
>
|
||||
<MenuItem value="1">Débito</MenuItem>
|
||||
<MenuItem value="0">Crédito</MenuItem>
|
||||
<MenuItem value="debito">Débito</MenuItem>
|
||||
<MenuItem value="credito">Crédito</MenuItem>
|
||||
<MenuItem value="investimento">Investimento</MenuItem>
|
||||
</TextField>
|
||||
|
||||
{Number(debito) === 0 && (
|
||||
{tipoCarteira === 'credito' && (
|
||||
<TextField
|
||||
label="Dia de vencimento"
|
||||
type="number"
|
||||
|
|
@ -414,11 +425,15 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
|
|||
Tipo
|
||||
</Typography>
|
||||
<Typography fontWeight={800}>
|
||||
{Number(debito) === 1 ? 'Débito' : 'Crédito'}
|
||||
{tipoCarteira === 'investimento'
|
||||
? 'Investimento'
|
||||
: tipoCarteira === 'credito'
|
||||
? 'Crédito'
|
||||
: 'Débito'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{Number(debito) === 0 && (
|
||||
{tipoCarteira === 'credito' && (
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Dia de vencimento
|
||||
|
|
|
|||
|
|
@ -61,12 +61,14 @@ function formatarData(data: string | null) {
|
|||
return new Date(`${String(data).slice(0, 10)}T00:00:00`).toLocaleDateString('pt-BR');
|
||||
}
|
||||
|
||||
function tipoBancoLabel(debito: number) {
|
||||
return Number(debito) === 1 ? 'Débito' : 'Crédito';
|
||||
function tipoBancoLabel(item: Banco) {
|
||||
if (Number(item.investimento) === 1) return 'Investimento';
|
||||
return Number(item.debito) === 1 ? 'Débito' : 'Crédito';
|
||||
}
|
||||
|
||||
function tipoBancoColor(debito: number) {
|
||||
return Number(debito) === 1 ? 'success' : 'info';
|
||||
function tipoBancoColor(item: Banco) {
|
||||
if (Number(item.investimento) === 1) return 'secondary';
|
||||
return Number(item.debito) === 1 ? 'success' : 'info';
|
||||
}
|
||||
|
||||
function statusBancoLabel(habilitado: number) {
|
||||
|
|
@ -96,8 +98,9 @@ export function BancosPage() {
|
|||
const [summary, setSummary] = useState<BancosSummary>({
|
||||
quantidade: 0,
|
||||
saldoTotal: 0,
|
||||
saldoCarteiras: 0,
|
||||
saldoCredito: 0,
|
||||
saldoDebito: 0,
|
||||
saldoInvestimento: 0,
|
||||
});
|
||||
|
||||
const [alterandoStatusId, setAlterandoStatusId] = useState<number | null>(null);
|
||||
|
|
@ -108,7 +111,7 @@ export function BancosPage() {
|
|||
|
||||
const [page, setPage] = useState(1);
|
||||
const [busca, setBusca] = useState('');
|
||||
const [debito, setDebito] = useState<number | ''>('');
|
||||
const [tipoFiltro, setTipoFiltro] = useState<'' | 'credito' | 'debito' | 'investimento'>('');
|
||||
const [habilitado, setHabilitado] = useState<number | ''>('');
|
||||
const [diaVencimento, setDiaVencimento] = useState<number | ''>('');
|
||||
const [orderBy, setOrderBy] = useState('descricao');
|
||||
|
|
@ -121,12 +124,12 @@ export function BancosPage() {
|
|||
let count = 0;
|
||||
|
||||
if (busca.trim()) count += 1;
|
||||
if (debito !== '') count += 1;
|
||||
if (tipoFiltro !== '') count += 1;
|
||||
if (habilitado !== '') count += 1;
|
||||
if (diaVencimento !== '') count += 1;
|
||||
|
||||
return count;
|
||||
}, [busca, debito, habilitado, diaVencimento]);
|
||||
}, [busca, tipoFiltro, habilitado, diaVencimento]);
|
||||
|
||||
async function carregarBancos(pageToLoad = page) {
|
||||
try {
|
||||
|
|
@ -137,7 +140,18 @@ export function BancosPage() {
|
|||
limite: LIMITE_PADRAO,
|
||||
page: pageToLoad,
|
||||
busca: busca.trim() || undefined,
|
||||
debito,
|
||||
debito:
|
||||
tipoFiltro === 'credito'
|
||||
? 0
|
||||
: tipoFiltro === 'debito'
|
||||
? 1
|
||||
: undefined,
|
||||
investimento:
|
||||
tipoFiltro === 'investimento'
|
||||
? 1
|
||||
: tipoFiltro === 'credito' || tipoFiltro === 'debito'
|
||||
? 0
|
||||
: undefined,
|
||||
habilitado,
|
||||
dia_vencimento: diaVencimento,
|
||||
orderBy,
|
||||
|
|
@ -168,7 +182,7 @@ export function BancosPage() {
|
|||
|
||||
function limparFiltros() {
|
||||
setBusca('');
|
||||
setDebito('');
|
||||
setTipoFiltro('');
|
||||
setHabilitado('');
|
||||
setDiaVencimento('');
|
||||
setOrderBy('descricao');
|
||||
|
|
@ -303,7 +317,7 @@ export function BancosPage() {
|
|||
display="grid"
|
||||
gridTemplateColumns={{
|
||||
xs: '1fr',
|
||||
md: 'repeat(4, 1fr)',
|
||||
md: 'repeat(5, 1fr)',
|
||||
}}
|
||||
gap={{ xs: 2, md: 2.5 }}
|
||||
marginBottom={{ xs: 2.5, md: 3 }}
|
||||
|
|
@ -337,10 +351,10 @@ export function BancosPage() {
|
|||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Dívida em crédito
|
||||
Saldo em crédito
|
||||
</Typography>
|
||||
<Typography variant="h5" fontWeight={950} color="success.main">
|
||||
{formatarValor(summary.saldoCarteiras)}
|
||||
<Typography variant="h5" fontWeight={950} color="info.main">
|
||||
{formatarValor(summary.saldoCredito)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -350,11 +364,22 @@ export function BancosPage() {
|
|||
<Typography variant="body2" color="text.secondary">
|
||||
Saldo em débito
|
||||
</Typography>
|
||||
<Typography variant="h5" fontWeight={950} color="warning.main">
|
||||
<Typography variant="h5" fontWeight={950} color="success.main">
|
||||
{formatarValor(summary.saldoDebito)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Investimentos
|
||||
</Typography>
|
||||
<Typography variant="h5" fontWeight={950} color="secondary.main">
|
||||
{formatarValor(summary.saldoInvestimento)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
<Card sx={{ marginBottom: { xs: 2.5, md: 3 } }}>
|
||||
|
|
@ -382,7 +407,7 @@ export function BancosPage() {
|
|||
display="grid"
|
||||
gridTemplateColumns={{
|
||||
xs: '1fr',
|
||||
md: 'repeat(4, 1fr)',
|
||||
md: 'repeat(5, 1fr)',
|
||||
}}
|
||||
gap={{ xs: 2, md: 2.25 }}
|
||||
>
|
||||
|
|
@ -397,15 +422,16 @@ export function BancosPage() {
|
|||
<TextField
|
||||
select
|
||||
label="Tipo"
|
||||
value={debito}
|
||||
value={tipoFiltro}
|
||||
onChange={(event) =>
|
||||
setDebito(event.target.value === '' ? '' : Number(event.target.value))
|
||||
setTipoFiltro(event.target.value as '' | 'credito' | 'debito' | 'investimento')
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<MenuItem value="">Todos</MenuItem>
|
||||
<MenuItem value={0}>Crédito</MenuItem>
|
||||
<MenuItem value={1}>Débito</MenuItem>
|
||||
<MenuItem value="credito">Crédito</MenuItem>
|
||||
<MenuItem value="debito">Débito</MenuItem>
|
||||
<MenuItem value="investimento">Investimento</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
|
|
@ -420,8 +446,12 @@ export function BancosPage() {
|
|||
max: 31,
|
||||
step: 1,
|
||||
}}
|
||||
disabled={debito === 1}
|
||||
helperText={debito === 1 ? 'Disponível apenas para crédito' : 'Filtra bancos de crédito'}
|
||||
disabled={tipoFiltro !== 'credito'}
|
||||
helperText={
|
||||
tipoFiltro !== 'credito'
|
||||
? 'Disponível apenas para crédito'
|
||||
: 'Filtra bancos de crédito'
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
|
|
@ -550,9 +580,9 @@ export function BancosPage() {
|
|||
|
||||
<Stack direction="row" flexWrap="wrap" spacing={1} useFlexGap>
|
||||
<Chip
|
||||
label={tipoBancoLabel(item.debito)}
|
||||
label={tipoBancoLabel(item)}
|
||||
size="small"
|
||||
color={tipoBancoColor(item.debito) as any}
|
||||
color={tipoBancoColor(item) as any}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
|
|
@ -664,9 +694,9 @@ export function BancosPage() {
|
|||
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={tipoBancoLabel(item.debito)}
|
||||
label={tipoBancoLabel(item)}
|
||||
size="small"
|
||||
color={tipoBancoColor(item.debito) as any}
|
||||
color={tipoBancoColor(item) as any}
|
||||
variant="outlined"
|
||||
/>
|
||||
</TableCell>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,6 +15,7 @@ export type ListarBancosParams = {
|
|||
page?: number;
|
||||
busca?: string;
|
||||
debito?: number | '';
|
||||
investimento?: number | '';
|
||||
habilitado?: number | '';
|
||||
dia_vencimento?: number | '';
|
||||
orderBy?: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import { api } from '../../../services/api';
|
||||
import type {
|
||||
ApiResponse,
|
||||
ComputarRendimentosRequest,
|
||||
ComputarRendimentosResponse,
|
||||
RendimentosDashboard,
|
||||
RendimentoHistoricoItem,
|
||||
} from '../types/rendimentosTypes';
|
||||
|
||||
export type BuscarRendimentosDashboardParams = {
|
||||
periodoMeses?: number;
|
||||
mesesProjecao?: number;
|
||||
dataInicio?: string;
|
||||
dataFim?: string;
|
||||
};
|
||||
|
||||
export type BuscarHistoricoRendimentosParams = {
|
||||
idbancos?: number | '';
|
||||
periodoMeses?: number;
|
||||
dataInicio?: string;
|
||||
dataFim?: string;
|
||||
};
|
||||
|
||||
export async function buscarDashboardRendimentos(
|
||||
params?: BuscarRendimentosDashboardParams
|
||||
): Promise<RendimentosDashboard> {
|
||||
const response = await api.get<ApiResponse<RendimentosDashboard>>(
|
||||
'/bancos/rendimentos/dashboard',
|
||||
{ params }
|
||||
);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function buscarHistoricoRendimentos(
|
||||
params?: BuscarHistoricoRendimentosParams
|
||||
): Promise<RendimentoHistoricoItem[]> {
|
||||
const response = await api.get<ApiResponse<RendimentoHistoricoItem[]>>(
|
||||
'/bancos/rendimentos/historico',
|
||||
{ params }
|
||||
);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function computarRendimentos(
|
||||
data: ComputarRendimentosRequest
|
||||
): Promise<ComputarRendimentosResponse> {
|
||||
const response = await api.post<ApiResponse<ComputarRendimentosResponse>>(
|
||||
'/bancos/rendimentos/computar',
|
||||
data
|
||||
);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
export type Banco = {
|
||||
idbancos: number;
|
||||
idusuarios: number;
|
||||
descricao: string;
|
||||
saldo: number;
|
||||
debito: number;
|
||||
investimento: number;
|
||||
habilitado: number;
|
||||
dia_vencimento: number | null;
|
||||
insert_date: string | null;
|
||||
|
|
@ -13,6 +15,7 @@ export type CriarBancoRequest = {
|
|||
descricao: string;
|
||||
saldo: number;
|
||||
debito: number;
|
||||
investimento: number;
|
||||
habilitado: number;
|
||||
dia_vencimento: number | null;
|
||||
};
|
||||
|
|
@ -30,8 +33,9 @@ export type BancosPagination = {
|
|||
export type BancosSummary = {
|
||||
quantidade: number;
|
||||
saldoTotal: number;
|
||||
saldoCarteiras: number;
|
||||
saldoCredito: number;
|
||||
saldoDebito: number;
|
||||
saldoInvestimento: number;
|
||||
};
|
||||
|
||||
export type ApiResponse<T> = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
export type BancoInvestimentoProjecao = {
|
||||
idbancos: number;
|
||||
banco_descricao: string;
|
||||
saldo_atual: number;
|
||||
taxa_media_mensal: number;
|
||||
meses_base: number;
|
||||
meses_projecao: number;
|
||||
saldo_projetado: number;
|
||||
rendimento_projetado: number;
|
||||
};
|
||||
|
||||
export type BancoInvestimento = {
|
||||
idbancos: number;
|
||||
descricao: string;
|
||||
saldo: number;
|
||||
debito: number;
|
||||
investimento: number;
|
||||
habilitado: number;
|
||||
update_date: string | null;
|
||||
ultima_apuracao: string | null;
|
||||
rendimento_periodo?: number;
|
||||
percentual_medio_periodo?: number;
|
||||
projecao?: BancoInvestimentoProjecao | null;
|
||||
};
|
||||
|
||||
export type RendimentoHistoricoItem = {
|
||||
idbancossaldoauditoria: number;
|
||||
idbancos: number;
|
||||
banco_descricao: string;
|
||||
idcontasapagar: number | null;
|
||||
tipo_operacao: string | null;
|
||||
origem: string | null;
|
||||
valor_delta: number;
|
||||
saldo_anterior: number;
|
||||
saldo_posterior: number;
|
||||
descricao: string | null;
|
||||
insert_date: string;
|
||||
percentual: number;
|
||||
};
|
||||
|
||||
export type RendimentoSerieMensal = {
|
||||
idbancos: number;
|
||||
banco_descricao: string;
|
||||
competencia: string;
|
||||
rendimento: number;
|
||||
saldo_final: number;
|
||||
eventos: number;
|
||||
};
|
||||
|
||||
export type RendimentoRankingItem = {
|
||||
idbancos: number;
|
||||
banco_descricao: string;
|
||||
rendimento_total: number;
|
||||
quantidade_eventos: number;
|
||||
soma_percentual: number;
|
||||
percentual_medio: number;
|
||||
maior_rendimento: {
|
||||
valor_delta: number;
|
||||
percentual: number;
|
||||
insert_date: string;
|
||||
} | null;
|
||||
menor_rendimento: {
|
||||
valor_delta: number;
|
||||
percentual: number;
|
||||
insert_date: string;
|
||||
} | null;
|
||||
primeira_data: string;
|
||||
ultima_data: string;
|
||||
saldo_atual: number;
|
||||
participacao_rendimento: number;
|
||||
};
|
||||
|
||||
export type RendimentosProjecao = {
|
||||
meses_projecao: number;
|
||||
saldo_atual_total: number;
|
||||
saldo_projetado_total: number;
|
||||
rendimento_projetado_total: number;
|
||||
taxa_media_mensal_total: number;
|
||||
bancos: BancoInvestimentoProjecao[];
|
||||
observacao: string;
|
||||
};
|
||||
|
||||
export type RendimentosGraficos = {
|
||||
barrasMensal: Array<Record<string, string | number>>;
|
||||
saldoMensal: Array<Record<string, string | number>>;
|
||||
rendimentoAcumulado: Array<Record<string, string | number>>;
|
||||
};
|
||||
|
||||
export type RendimentosResumo = {
|
||||
totalInvestidoAtual: number;
|
||||
rendimentoPeriodo: number;
|
||||
rendimentoMedioPercentual: number;
|
||||
quantidadeBancos: number;
|
||||
melhorBanco: RendimentoRankingItem | null;
|
||||
projecao12Meses?: {
|
||||
saldoProjetado: number;
|
||||
rendimentoProjetado: number;
|
||||
taxaMediaMensal: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type RendimentosDashboard = {
|
||||
resumo: RendimentosResumo;
|
||||
bancos: BancoInvestimento[];
|
||||
historico: RendimentoHistoricoItem[];
|
||||
seriesMensais: RendimentoSerieMensal[];
|
||||
ranking: RendimentoRankingItem[];
|
||||
projecao: RendimentosProjecao;
|
||||
graficos: RendimentosGraficos;
|
||||
};
|
||||
|
||||
export type ComputarRendimentoItemRequest = {
|
||||
idbancos: number;
|
||||
novoSaldo: number;
|
||||
descricao?: string;
|
||||
};
|
||||
|
||||
export type ComputarRendimentosRequest = {
|
||||
data?: string;
|
||||
descricao?: string;
|
||||
confirmarDuplicado?: boolean;
|
||||
itens: ComputarRendimentoItemRequest[];
|
||||
};
|
||||
|
||||
export type ComputarRendimentoResultado = {
|
||||
ok: boolean;
|
||||
ignorado?: boolean;
|
||||
idbancos: number;
|
||||
banco_descricao?: string;
|
||||
saldo_anterior?: number;
|
||||
saldo_posterior?: number;
|
||||
valor_delta?: number;
|
||||
percentual?: number;
|
||||
message?: string;
|
||||
movimento?: any;
|
||||
impactoSaldo?: any[];
|
||||
};
|
||||
|
||||
export type ComputarRendimentosResponse = {
|
||||
processados: ComputarRendimentoResultado[];
|
||||
ignorados: ComputarRendimentoResultado[];
|
||||
erros: ComputarRendimentoResultado[];
|
||||
resumo: {
|
||||
quantidadeProcessada: number;
|
||||
quantidadeIgnorada: number;
|
||||
quantidadeErro: number;
|
||||
totalRendimento: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ApiResponse<T> = {
|
||||
ok: boolean;
|
||||
message?: string;
|
||||
data: T;
|
||||
};
|
||||
|
|
@ -291,7 +291,7 @@ export function QuitacoesCreditoPage() {
|
|||
});
|
||||
|
||||
setSucesso(
|
||||
`Quitação realizada com sucesso. Valor quitado: ${formatarValor(response.valor)}.`
|
||||
`Quitação realizada com sucesso. Valor quitado: ${formatarValor(response.valorQuitado)}.`
|
||||
);
|
||||
|
||||
setImpactoSaldo(response.impactoSaldo || []);
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export type QuitacaoCreditoResponse = {
|
|||
idcontasquitadas: number;
|
||||
idbancos: number;
|
||||
idbancos_p: number;
|
||||
valor: number;
|
||||
valorQuitado: number;
|
||||
descricao: string | null;
|
||||
data_quitacao: string | null;
|
||||
contasQuitadas?: ContaCreditoQuitacao[];
|
||||
|
|
|
|||
Loading…
Reference in New Issue