adicionado tela de rendimentos e filtrado bancos por usuario

This commit is contained in:
Diego Freitas 2026-06-01 22:30:10 -03:00
parent 345d6033a8
commit 0731328492
25 changed files with 2873 additions and 170 deletions

View File

@ -7,6 +7,7 @@ const referenciasRoutes = require('./modules/referencias/routes/referencias.rout
const relatoriosRoutes = require('./modules/relatorios/routes/relatorios.routes'); const relatoriosRoutes = require('./modules/relatorios/routes/relatorios.routes');
const clientesRoutes = require('./modules/clientes/routes/clientes.routes'); const clientesRoutes = require('./modules/clientes/routes/clientes.routes');
const bancosRoutes = require('./modules/bancos/routes/bancos.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 centrosCustoRoutes = require('./modules/centrosCusto/routes/centrosCusto.routes');
const movimentosFixosRoutes = require('./modules/movimentosFixos/routes/movimentosFixos.routes'); const movimentosFixosRoutes = require('./modules/movimentosFixos/routes/movimentosFixos.routes');
const usuariosRoutes = require('./modules/usuarios/routes/usuarios.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/relatorios', relatoriosRoutes);
app.use('/api/clientes', clientesRoutes); app.use('/api/clientes', clientesRoutes);
app.use('/api/bancos', bancosRoutes); app.use('/api/bancos', bancosRoutes);
app.use('/api/bancos/rendimentos', bancosRendimentosRoutes);
app.use('/api/centros-custo', centrosCustoRoutes); app.use('/api/centros-custo', centrosCustoRoutes);
app.use('/api/movimentos-fixos', movimentosFixosRoutes); app.use('/api/movimentos-fixos', movimentosFixosRoutes);
app.use('/api/usuarios', usuariosRoutes); app.use('/api/usuarios', usuariosRoutes);

View File

@ -1,6 +1,14 @@
const bancosService = require('../services/bancos.service'); const bancosService = require('../services/bancos.service');
function validarDadosBanco(dados) { 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()) { if (!dados.descricao || !String(dados.descricao).trim()) {
return 'Descrição é obrigatória.'; 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 !== '') { if (dados.habilitado !== undefined && dados.habilitado !== null && dados.habilitado !== '') {
const habilitado = Number(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 !== '') { if (dados.dia_vencimento !== undefined && dados.dia_vencimento !== null && dados.dia_vencimento !== '') {
const dia_vencimento = Number(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.'; return 'Dia de Vencimeneto inválido.';
} }
} }
@ -47,7 +63,9 @@ async function listar(req, res) {
offset: req.query.offset, offset: req.query.offset,
page: req.query.page, page: req.query.page,
busca: req.query.busca, busca: req.query.busca,
idusuarios: req.user.id,
debito: req.query.debito, debito: req.query.debito,
investimento: req.query.investimento,
dia_vencimento: req.query.dia_vencimento, dia_vencimento: req.query.dia_vencimento,
habilitado: req.query.habilitado, habilitado: req.query.habilitado,
orderBy: req.query.orderBy, 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) { if (!banco) {
return res.status(404).json({ 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({ return res.status(201).json({
ok: true, 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) { if (!bancoAtualizado) {
return res.status(404).json({ 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) { if (!bancoAtualizado) {
return res.status(404).json({ 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) { if (!deletado) {
return res.status(404).json({ return res.status(404).json({

View File

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

View File

@ -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;

View File

@ -2,9 +2,11 @@ const pool = require('../../../database/mysql');
const CAMPOS_BANCO_SELECT = ` const CAMPOS_BANCO_SELECT = `
idbancos, idbancos,
idusuarios,
descricao, descricao,
saldo, saldo,
debito, debito,
investimento,
habilitado, habilitado,
dia_vencimento, dia_vencimento,
insert_date, insert_date,
@ -53,10 +55,12 @@ function limitarNumero(valor, padrao, minimo, maximo) {
function resolverOrdenacao(orderBy) { function resolverOrdenacao(orderBy) {
const camposPermitidos = { const camposPermitidos = {
idusuarios: 'idusuarios',
idbancos: 'idbancos', idbancos: 'idbancos',
descricao: 'descricao', descricao: 'descricao',
saldo: 'saldo', saldo: 'saldo',
debito: 'debito', debito: 'debito',
investimento: 'investimento',
habilitado: 'habilitado', habilitado: 'habilitado',
dia_vencimento: 'dia_vencimento', dia_vencimento: 'dia_vencimento',
insert_date: 'insert_date', insert_date: 'insert_date',
@ -74,11 +78,21 @@ function montarWhereBancos(filtros = {}) {
const where = []; const where = [];
const params = []; 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 !== '') { if (filtros.debito !== undefined && filtros.debito !== null && filtros.debito !== '') {
where.push('debito = ?'); where.push('debito = ?');
params.push(Number(filtros.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 !== '') { if (filtros.habilitado !== undefined && filtros.habilitado !== null && filtros.habilitado !== '') {
where.push('habilitado = ?'); where.push('habilitado = ?');
params.push(Number(filtros.habilitado)); params.push(Number(filtros.habilitado));
@ -146,8 +160,9 @@ async function listarBancos(filtros = {}) {
SELECT SELECT
COUNT(*) AS quantidade, COUNT(*) AS quantidade,
COALESCE(SUM(saldo), 0) AS saldoTotal, 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 = 0 THEN saldo ELSE 0 END), 0) AS saldoCredito,
COALESCE(SUM(CASE WHEN debito = 1 THEN saldo ELSE 0 END), 0) AS saldoDebito 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 FROM bancos
${whereSql} ${whereSql}
`, `,
@ -169,32 +184,36 @@ async function listarBancos(filtros = {}) {
summary: { summary: {
quantidade: Number(summaryRows[0]?.quantidade || 0), quantidade: Number(summaryRows[0]?.quantidade || 0),
saldoTotal: Number(summaryRows[0]?.saldoTotal || 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), 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( const [rows] = await pool.query(
` `
SELECT SELECT
${CAMPOS_BANCO_SELECT} ${CAMPOS_BANCO_SELECT}
FROM bancos FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
LIMIT 1 LIMIT 1
`, `,
[id] [id, idusuarios]
); );
return rows[0] || null; return rows[0] || null;
} }
async function criarBanco(dados) { async function criarBanco(_idusuarios, dados) {
const { const {
idusuarios,
descricao, descricao,
saldo, saldo,
debito, debito,
investimento,
habilitado, habilitado,
dia_vencimento, dia_vencimento,
} = dados; } = dados;
@ -202,19 +221,23 @@ async function criarBanco(dados) {
const [result] = await pool.query( const [result] = await pool.query(
` `
INSERT INTO bancos ( INSERT INTO bancos (
idusuarios,
descricao, descricao,
saldo, saldo,
debito, debito,
investimento,
habilitado, habilitado,
dia_vencimento, dia_vencimento,
insert_date, insert_date,
update_date update_date
) VALUES (?, ?, ?, ?, ?, NOW(), NOW()) ) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`, `,
[ [
normalizarNumero(_idusuarios, 0),
normalizarTextoOuNull(descricao), normalizarTextoOuNull(descricao),
normalizarNumero(saldo, 0), normalizarNumero(saldo, 0),
Number(debito || 0), Number(debito || 0),
Number(investimento || 0),
habilitado === undefined || habilitado === null || habilitado === '' habilitado === undefined || habilitado === null || habilitado === ''
? 1 ? 1
: Number(habilitado), : Number(habilitado),
@ -222,20 +245,22 @@ async function criarBanco(dados) {
] ]
); );
return buscarBancoPorId(result.insertId); return buscarBancoPorId(result.insertId, _idusuarios);
} }
async function atualizarBanco(id, dados) { async function atualizarBanco(id, _idusuarios, dados) {
const bancoAtual = await buscarBancoPorId(id); const bancoAtual = await buscarBancoPorId(id, _idusuarios);
if (!bancoAtual) { if (!bancoAtual) {
return null; return null;
} }
const { const {
idusuarios,
descricao, descricao,
saldo, saldo,
debito, debito,
investimento,
habilitado, habilitado,
dia_vencimento dia_vencimento
} = dados; } = dados;
@ -244,31 +269,37 @@ async function atualizarBanco(id, dados) {
` `
UPDATE bancos UPDATE bancos
SET SET
idusuarios = ?,
descricao = ?, descricao = ?,
saldo = ?, saldo = ?,
debito = ?, debito = ?,
investimento = ?,
habilitado = ?, habilitado = ?,
dia_vencimento = ?, dia_vencimento = ?,
update_date = NOW() update_date = NOW()
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
`, `,
[ [
normalizarNumero(_idusuarios, 0),
normalizarTextoOuNull(descricao), normalizarTextoOuNull(descricao),
normalizarNumero(saldo, 0), normalizarNumero(saldo, 0),
Number(debito || 0), Number(debito || 0),
Number(investimento || 0),
habilitado === undefined || habilitado === null || habilitado === '' habilitado === undefined || habilitado === null || habilitado === ''
? 1 ? 1
: Number(habilitado), : Number(habilitado),
normalizarNumero(dia_vencimento, null), normalizarNumero(dia_vencimento, null),
id id,
_idusuarios
] ]
); );
return buscarBancoPorId(id); return buscarBancoPorId(id, _idusuarios);
} }
async function alterarHabilitadoBanco(id, habilitado) { async function alterarHabilitadoBanco(id, idusuarios, habilitado) {
const bancoAtual = await buscarBancoPorId(id); const bancoAtual = await buscarBancoPorId(id, idusuarios);
if (!bancoAtual) { if (!bancoAtual) {
return null; return null;
@ -281,18 +312,20 @@ async function alterarHabilitadoBanco(id, habilitado) {
habilitado = ?, habilitado = ?,
update_date = NOW() update_date = NOW()
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
`, `,
[ [
Number(habilitado), Number(habilitado),
id, id,
idusuarios
] ]
); );
return buscarBancoPorId(id); return buscarBancoPorId(id, idusuarios);
} }
async function deletarBanco(id) { async function deletarBanco(id, idusuarios) {
const bancoAtual = await buscarBancoPorId(id); const bancoAtual = await buscarBancoPorId(id, idusuarios);
if (!bancoAtual) { if (!bancoAtual) {
return false; return false;
@ -302,8 +335,9 @@ async function deletarBanco(id) {
` `
DELETE FROM bancos DELETE FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
`, `,
[id] [id, idusuarios]
); );
return true; return true;

View File

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

View File

@ -1,8 +1,21 @@
const dashboardService = require('../services/dashboard.service'); const dashboardService = require('../services/dashboard.service');
function resolverIdUsuario(req) {
return Number(req.user?.idusuarios || req.user?.id);
}
async function buscarResumo(req, res) { async function buscarResumo(req, res) {
try { 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, dataInicio: req.query.dataInicio,
dataFim: req.query.dataFim, dataFim: req.query.dataFim,
}); });

View File

@ -52,27 +52,95 @@ function calcularPercentual(valor, base) {
return (valorNormalizado / baseNormalizada) * 100; return (valorNormalizado / baseNormalizada) * 100;
} }
async function buscarCardsBancos() { async function buscarCardsBancos(idUsuario) {
const [rows] = await pool.query(` const [rows] = await pool.query(
`
SELECT SELECT
COALESCE(SUM(CASE WHEN debito = 1 THEN saldo ELSE 0 END), 0) AS saldoDebito, COALESCE(SUM(CASE
COALESCE(SUM(CASE WHEN debito = 0 THEN saldo ELSE 0 END), 0) AS dividaCredito, 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, COUNT(*) AS quantidadeCarteiras,
SUM(CASE WHEN habilitado = 1 THEN 1 ELSE 0 END) AS carteirasHabilitadas
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 FROM bancos
`); WHERE idusuarios = ?
`,
[idUsuario]
);
const row = rows[0] || {}; 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 { return {
saldoDebito: normalizarNumero(row.saldoDebito), saldoDisponivel,
dividaCredito: normalizarNumero(row.dividaCredito), totalInvestido,
saldoDebitoTotal,
saldoDebito: saldoDisponivel, // mantém compatibilidade com o front atual
dividaCredito,
patrimonioBruto: saldoDisponivel + totalInvestido,
patrimonioLiquido: saldoDisponivel + totalInvestido - dividaCredito,
quantidadeCarteiras: normalizarNumero(row.quantidadeCarteiras), quantidadeCarteiras: normalizarNumero(row.quantidadeCarteiras),
carteirasHabilitadas: normalizarNumero(row.carteirasHabilitadas), 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( const [rows] = await pool.query(
` `
SELECT SELECT
@ -144,12 +212,15 @@ async function buscarCardsMovimentos(dataInicio, dataFim) {
COUNT(*) AS quantidadeMovimentos COUNT(*) AS quantidadeMovimentos
FROM contasapagar cp FROM contasapagar cp
LEFT JOIN bancos b
ON b.idbancos = cp.idbancos
LEFT JOIN centrodecustos cc LEFT JOIN centrodecustos cc
ON cc.idcentrodecustos = cp.idcentrodecustos ON cc.idcentrodecustos = cp.idcentrodecustos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND b.idusuarios = ?
AND DATE(cp.datavencimento) BETWEEN ? AND ? AND DATE(cp.datavencimento) BETWEEN ? AND ?
`, `,
[dataInicio, dataFim] [idUsuario, dataInicio, dataFim]
); );
const row = rows[0] || {}; const row = rows[0] || {};
@ -188,7 +259,7 @@ async function buscarCardsMovimentos(dataInicio, dataFim) {
}; };
} }
async function buscarAlertasVencimento() { async function buscarAlertasVencimento(idUsuario) {
const hoje = hojeISO(); const hoje = hojeISO();
const [rows] = await pool.query( const [rows] = await pool.query(
@ -286,9 +357,12 @@ async function buscarAlertasVencimento() {
FROM contasapagar cp FROM contasapagar cp
LEFT JOIN centrodecustos cc LEFT JOIN centrodecustos cc
ON cc.idcentrodecustos = cp.idcentrodecustos ON cc.idcentrodecustos = cp.idcentrodecustos
LEFT JOIN bancos b
ON b.idbancos = cp.idbancos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND cp.status IN ('A pagar', 'A receber') AND cp.status IN ('A pagar', 'A receber')
AND cp.datavencimento IS NOT NULL AND cp.datavencimento IS NOT NULL
AND b.idusuarios = ?
`, `,
[ [
hoje, hoje, hoje, hoje,
@ -304,6 +378,8 @@ async function buscarAlertasVencimento() {
hoje, hoje, hoje, hoje,
hoje, hoje, hoje, hoje,
idUsuario
] ]
); );
@ -334,7 +410,7 @@ async function buscarAlertasVencimento() {
}; };
} }
async function buscarProximosVencimentos() { async function buscarProximosVencimentos(idUsuario) {
const [rows] = await pool.query(` const [rows] = await pool.query(`
SELECT SELECT
cp.idcontasapagar, cp.idcontasapagar,
@ -357,16 +433,18 @@ async function buscarProximosVencimentos() {
LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos
LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND bc.idusuarios = ?
AND cp.status IN ('A pagar', 'A receber') AND cp.status IN ('A pagar', 'A receber')
AND cp.datavencimento IS NOT NULL AND cp.datavencimento IS NOT NULL
ORDER BY DATE(cp.datavencimento) ASC, cp.idcontasapagar ASC ORDER BY DATE(cp.datavencimento) ASC, cp.idcontasapagar ASC
LIMIT 10 LIMIT 10
`); `,
[idUsuario]);
return rows; return rows;
} }
async function buscarCreditosEmAberto(dataInicio, dataFim) { async function buscarCreditosEmAberto(idUsuario, dataInicio, dataFim) {
const [rows] = await pool.query( const [rows] = await pool.query(
` `
SELECT SELECT
@ -423,7 +501,8 @@ async function buscarCreditosEmAberto(dataInicio, dataFim) {
AND cp.deleted_at IS NULL AND cp.deleted_at IS NULL
AND cp.status = 'A pagar' AND cp.status = 'A pagar'
AND cp.movimento IN ('Saida', 'Sangria') AND cp.movimento IN ('Saida', 'Sangria')
WHERE bc.debito = 0 WHERE bc.idusuarios = ?
AND bc.debito = 0
AND bc.habilitado = 1 AND bc.habilitado = 1
GROUP BY GROUP BY
bc.idbancos, bc.idbancos,
@ -443,6 +522,7 @@ async function buscarCreditosEmAberto(dataInicio, dataFim) {
dataFim, dataFim,
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( const [rows] = await pool.query(
` `
SELECT SELECT
@ -472,21 +552,25 @@ async function buscarCarteiras(dataInicio, dataFim) {
bc.dia_vencimento, bc.dia_vencimento,
bc.insert_date, bc.insert_date,
bc.update_date, bc.update_date,
bc.investimento,
COALESCE(aud.delta_periodo, 0) AS delta_periodo COALESCE(aud.delta_periodo, 0) AS delta_periodo
FROM bancos bc FROM bancos bc
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
idbancos, bsa.idbancos,
COALESCE(SUM(valor_delta), 0) AS delta_periodo COALESCE(SUM(bsa.valor_delta), 0) AS delta_periodo
FROM bancos_saldo_auditoria FROM bancos_saldo_auditoria bsa
WHERE DATE(insert_date) BETWEEN ? AND ? INNER JOIN bancos b2 ON b2.idbancos = bsa.idbancos
GROUP BY idbancos WHERE b2.idusuarios = ?
AND DATE(bsa.insert_date) BETWEEN ? AND ?
GROUP BY bsa.idbancos
) aud ON aud.idbancos = bc.idbancos ) aud ON aud.idbancos = bc.idbancos
WHERE bc.habilitado = 1 WHERE bc.habilitado = 1
AND bc.idusuarios = ?
ORDER BY bc.debito DESC, bc.descricao ASC ORDER BY bc.debito DESC, bc.descricao ASC
`, `,
[dataInicio, dataFim] [idUsuario, dataInicio, dataFim, idUsuario]
); );
return rows.map((item) => { return rows.map((item) => {
@ -494,6 +578,7 @@ async function buscarCarteiras(dataInicio, dataFim) {
const deltaPeriodo = normalizarNumero(item.delta_periodo); const deltaPeriodo = normalizarNumero(item.delta_periodo);
const saldoMesAnterior = saldoAtual - deltaPeriodo; const saldoMesAnterior = saldoAtual - deltaPeriodo;
const variacaoValor = saldoAtual - saldoMesAnterior; const variacaoValor = saldoAtual - saldoMesAnterior;
const isInvestimento = Number(item.investimento || 0) === 1;
const variacaoPercentual = saldoMesAnterior !== 0 const variacaoPercentual = saldoMesAnterior !== 0
? (variacaoValor / Math.abs(saldoMesAnterior)) * 100 ? (variacaoValor / Math.abs(saldoMesAnterior)) * 100
@ -512,11 +597,16 @@ async function buscarCarteiras(dataInicio, dataFim) {
variacao_valor: variacaoValor, variacao_valor: variacaoValor,
variacao_percentual: variacaoPercentual, variacao_percentual: variacaoPercentual,
tendencia_boa: tendenciaBoa, tendencia_boa: tendenciaBoa,
tipo_carteira: isCredito
? 'credito'
: isInvestimento
? 'investimento'
: 'disponivel',
}; };
}); });
} }
async function buscarUltimosMovimentos() { async function buscarUltimosMovimentos(idUsuario) {
const [rows] = await pool.query(` const [rows] = await pool.query(`
SELECT SELECT
cp.idcontasapagar, cp.idcontasapagar,
@ -543,14 +633,16 @@ async function buscarUltimosMovimentos() {
LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos LEFT JOIN bancos bc ON bc.idbancos = cp.idbancos
LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos LEFT JOIN centrodecustos cc ON cc.idcentrodecustos = cp.idcentrodecustos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND bc.idusuarios = ?
ORDER BY cp.insert_date DESC, cp.idcontasapagar DESC ORDER BY cp.insert_date DESC, cp.idcontasapagar DESC
LIMIT 10 LIMIT 10
`); `,
[idUsuario]);
return rows; return rows;
} }
async function buscarGraficos(dataInicio, dataFim) { async function buscarGraficos(idUsuario, dataInicio, dataFim) {
const [centrosRows] = await pool.query( const [centrosRows] = await pool.query(
` `
SELECT SELECT
@ -560,9 +652,12 @@ async function buscarGraficos(dataInicio, dataFim) {
FROM contasapagar cp FROM contasapagar cp
LEFT JOIN centrodecustos cc LEFT JOIN centrodecustos cc
ON cc.idcentrodecustos = cp.idcentrodecustos ON cc.idcentrodecustos = cp.idcentrodecustos
LEFT JOIN bancos b
ON b.idbancos = cp.idbancos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND cp.movimento IN ('Saida') AND cp.movimento IN ('Saida')
AND DATE(cp.datavencimento) BETWEEN ? AND ? AND DATE(cp.datavencimento) BETWEEN ? AND ?
AND b.idusuarios = ?
GROUP BY GROUP BY
cp.idcentrodecustos, cp.idcentrodecustos,
COALESCE(cc.descricao, 'Sem centro de custo') COALESCE(cc.descricao, 'Sem centro de custo')
@ -570,7 +665,7 @@ async function buscarGraficos(dataInicio, dataFim) {
ORDER BY valor DESC ORDER BY valor DESC
LIMIT 8 LIMIT 8
`, `,
[dataInicio, dataFim] [dataInicio, dataFim, idUsuario]
); );
const [entradasSaidasRows] = await pool.query( const [entradasSaidasRows] = await pool.query(
@ -595,10 +690,13 @@ async function buscarGraficos(dataInicio, dataFim) {
FROM contasapagar cp FROM contasapagar cp
LEFT JOIN centrodecustos cc LEFT JOIN centrodecustos cc
ON cc.idcentrodecustos = cp.idcentrodecustos ON cc.idcentrodecustos = cp.idcentrodecustos
LEFT JOIN bancos b
ON b.idbancos = cp.idbancos
WHERE cp.deleted_at IS NULL WHERE cp.deleted_at IS NULL
AND DATE(cp.datavencimento) BETWEEN ? AND ? AND DATE(cp.datavencimento) BETWEEN ? AND ?
AND b.idusuarios = ?
`, `,
[dataInicio, dataFim] [dataInicio, dataFim, idUsuario]
); );
const entradas = normalizarNumero(entradasSaidasRows[0]?.entradas); 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 dataInicio = normalizarDataISO(filtros.dataInicio, inicioMesAtualISO());
const dataFim = normalizarDataISO(filtros.dataFim, fimMesAtualISO()); const dataFim = normalizarDataISO(filtros.dataFim, fimMesAtualISO());
const [ const [
cardsBancos, cardsBancos,
cardsMovimentos, cardsMovimentos,
cardsInvestimentos,
alertasVencimento, alertasVencimento,
proximosVencimentos, proximosVencimentos,
creditos, creditos,
@ -641,14 +764,15 @@ async function buscarResumoDashboard(filtros = {}) {
ultimosMovimentos, ultimosMovimentos,
graficos, graficos,
] = await Promise.all([ ] = await Promise.all([
buscarCardsBancos(), buscarCardsBancos(idUsuario),
buscarCardsMovimentos(dataInicio, dataFim), buscarCardsMovimentos(idUsuario, dataInicio, dataFim),
buscarAlertasVencimento(), buscarCardsInvestimentos(idUsuario, dataInicio, dataFim),
buscarProximosVencimentos(), buscarAlertasVencimento(idUsuario),
buscarCreditosEmAberto(dataInicio, dataFim), buscarProximosVencimentos(idUsuario),
buscarCarteiras(dataInicio, dataFim), buscarCreditosEmAberto(idUsuario, dataInicio, dataFim),
buscarUltimosMovimentos(), buscarCarteiras(idUsuario, dataInicio, dataFim),
buscarGraficos(dataInicio, dataFim), buscarUltimosMovimentos(idUsuario),
buscarGraficos(idUsuario, dataInicio, dataFim),
]); ]);
return { return {
@ -660,6 +784,7 @@ async function buscarResumoDashboard(filtros = {}) {
cards: { cards: {
...cardsBancos, ...cardsBancos,
...cardsMovimentos, ...cardsMovimentos,
...cardsInvestimentos,
...alertasVencimento, ...alertasVencimento,
}, },

View File

@ -124,6 +124,7 @@ async function listar(req, res) {
limite: req.query.limite, limite: req.query.limite,
offset: req.query.offset, offset: req.query.offset,
page: req.query.page, page: req.query.page,
busca: req.query.busca,
movimento: req.query.movimento, movimento: req.query.movimento,
status: req.query.status, status: req.query.status,
idbancos: req.query.idbancos, idbancos: req.query.idbancos,
@ -139,10 +140,11 @@ async function listar(req, res) {
dataCampo: req.query.dataCampo, dataCampo: req.query.dataCampo,
dataInicio: req.query.dataInicio, dataInicio: req.query.dataInicio,
dataFim: req.query.dataFim, dataFim: req.query.dataFim,
busca: req.query.busca,
incluirExcluidos: req.query.incluirExcluidos, incluirExcluidos: req.query.incluirExcluidos,
orderBy: req.query.orderBy, orderBy: req.query.orderBy,
orderDirection: req.query.orderDirection, orderDirection: req.query.orderDirection,
idusuarios: req.user.idusuarios || req.user.id,
}); });
return res.json({ 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) { if (!movimento) {
return res.status(404).json({ return res.status(404).json({

View File

@ -206,6 +206,11 @@ function montarWhereMovimentos(filtros = {}) {
where.push('cp.deleted_at IS NULL'); where.push('cp.deleted_at IS NULL');
} }
if (filtros.idusuarios) {
where.push('b.idusuarios = ?');
params.push(Number(filtros.idusuarios));
}
if (filtros.movimento) { if (filtros.movimento) {
where.push('cp.movimento = ?'); where.push('cp.movimento = ?');
params.push(filtros.movimento); params.push(filtros.movimento);
@ -411,6 +416,15 @@ async function listarMovimentos(filtros = {}) {
async function buscarMovimentoPorId(id, opcoes = {}) { async function buscarMovimentoPorId(id, opcoes = {}) {
const executor = opcoes.connection || pool; const executor = opcoes.connection || pool;
const incluirExcluidos = opcoes.incluirExcluidos === true; 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( const [rows] = await executor.query(
` `
@ -419,26 +433,37 @@ async function buscarMovimentoPorId(id, opcoes = {}) {
${FROM_MOVIMENTO_JOIN} ${FROM_MOVIMENTO_JOIN}
WHERE cp.idcontasapagar = ? WHERE cp.idcontasapagar = ?
${incluirExcluidos ? '' : 'AND cp.deleted_at IS NULL'} ${incluirExcluidos ? '' : 'AND cp.deleted_at IS NULL'}
${filtroUsuario}
LIMIT 1 LIMIT 1
`, `,
[id] params
); );
return rows[0] || null; 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( const [rows] = await connection.query(
` `
SELECT SELECT
cp.* cp.*
FROM contasapagar cp FROM contasapagar cp
INNER JOIN bancos b ON b.idbancos = cp.idbancos
WHERE cp.idcontasapagar = ? WHERE cp.idcontasapagar = ?
AND cp.deleted_at IS NULL AND cp.deleted_at IS NULL
${filtroUsuario}
LIMIT 1 LIMIT 1
FOR UPDATE FOR UPDATE
`, `,
[id] params
); );
return rows[0] || null; return rows[0] || null;
@ -713,7 +738,7 @@ function montarParcelas(idUsuario, dados) {
} }
async function aplicarSaldoMovimento(connection, movimento, contexto) { 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) { if (deltas.length === 0) {
return { return {
@ -826,7 +851,7 @@ async function atualizarMovimento(id, idUsuario, dados) {
try { try {
await connection.beginTransaction(); await connection.beginTransaction();
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id); const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
if (!movimentoAntigo) { if (!movimentoAntigo) {
await connection.rollback(); await connection.rollback();
@ -845,7 +870,8 @@ async function atualizarMovimento(id, idUsuario, dados) {
if (Number(movimentoAntigo.saldo_processado) === 1) { if (Number(movimentoAntigo.saldo_processado) === 1) {
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento( const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
connection, connection,
movimentoAntigo movimentoAntigo,
{ idusuarios: idUsuario }
); );
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo( const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(
@ -924,7 +950,7 @@ async function deletarMovimento(id, idUsuario) {
try { try {
await connection.beginTransaction(); await connection.beginTransaction();
const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id); const movimentoAntigo = await buscarMovimentoPorIdParaUpdate(connection, id, idUsuario);
if (!movimentoAntigo) { if (!movimentoAntigo) {
await connection.rollback(); await connection.rollback();
@ -939,7 +965,8 @@ async function deletarMovimento(id, idUsuario) {
if (Number(movimentoAntigo.saldo_processado) === 1) { if (Number(movimentoAntigo.saldo_processado) === 1) {
const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento( const deltasAntigos = await movimentosSaldoService.calcularImpactoMovimento(
connection, connection,
movimentoAntigo movimentoAntigo,
{ idusuarios: idUsuario }
); );
const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo( const impactosReversao = await movimentosSaldoService.aplicarDeltasSaldo(

View File

@ -12,42 +12,64 @@ function bancoEhCredito(banco) {
return Number(banco?.debito) === 0; return Number(banco?.debito) === 0;
} }
async function buscarBanco(connection, idbancos) { async function buscarBanco(connection, idbancos, idusuarios = null) {
if (!idbancos) return null; if (!idbancos) return null;
const params = [idbancos];
let filtroUsuario = '';
if (idusuarios) {
filtroUsuario = 'AND idusuarios = ?';
params.push(idusuarios);
}
const [rows] = await connection.query( const [rows] = await connection.query(
` `
SELECT SELECT
idbancos, idbancos,
descricao, descricao,
saldo, saldo,
debito debito,
investimento,
idusuarios
FROM bancos FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
${filtroUsuario}
LIMIT 1 LIMIT 1
`, `,
[idbancos] params
); );
return rows[0] || null; return rows[0] || null;
} }
async function buscarBancoParaUpdate(connection, idbancos) { async function buscarBancoParaUpdate(connection, idbancos, idusuarios = null) {
if (!idbancos) return null; if (!idbancos) return null;
const params = [idbancos];
let filtroUsuario = '';
if (idusuarios) {
filtroUsuario = 'AND idusuarios = ?';
params.push(idusuarios);
}
const [rows] = await connection.query( const [rows] = await connection.query(
` `
SELECT SELECT
idbancos, idbancos,
descricao, descricao,
saldo, saldo,
debito debito,
investimento,
idusuarios
FROM bancos FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
${filtroUsuario}
LIMIT 1 LIMIT 1
FOR UPDATE FOR UPDATE
`, `,
[idbancos] params
); );
return rows[0] || null; return rows[0] || null;
@ -145,9 +167,9 @@ function calcularDeltasParaMovimento(movimento, bancoOrigem, bancoDestino = null
return deltas; return deltas;
} }
async function calcularImpactoMovimento(connection, movimento) { async function calcularImpactoMovimento(connection, movimento, contexto = {}) {
const bancoOrigem = await buscarBanco(connection, movimento.idbancos); const bancoOrigem = await buscarBanco(connection, movimento.idbancos, contexto.idusuarios);
const bancoDestino = await buscarBanco(connection, movimento.idbancos_p); const bancoDestino = await buscarBanco(connection, movimento.idbancos_p, contexto.idusuarios);
return calcularDeltasParaMovimento(movimento, bancoOrigem, bancoDestino); return calcularDeltasParaMovimento(movimento, bancoOrigem, bancoDestino);
} }
@ -164,7 +186,7 @@ async function aplicarDeltasSaldo(connection, deltas, contexto = {}) {
const impactos = []; const impactos = [];
for (const delta of deltas) { for (const delta of deltas) {
const banco = await buscarBancoParaUpdate(connection, delta.idbancos); const banco = await buscarBancoParaUpdate(connection, delta.idbancos, contexto.idusuarios);
if (!banco) { if (!banco) {
continue; continue;

View File

@ -1,5 +1,9 @@
const quitacoesCreditoService = require('../services/quitacoesCredito.service'); const quitacoesCreditoService = require('../services/quitacoesCredito.service');
function resolverIdUsuario(req) {
return Number(req.user?.idusuarios || req.user?.id);
}
function tratarErro(error, res, mensagemPadrao) { function tratarErro(error, res, mensagemPadrao) {
if (error?.statusCode) { if (error?.statusCode) {
return res.status(error.statusCode).json({ return res.status(error.statusCode).json({
@ -43,7 +47,16 @@ function validarCriacao(dados) {
async function listarBancosCredito(req, res) { async function listarBancosCredito(req, res) {
try { 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({ return res.json({
ok: true, ok: true,
@ -57,7 +70,16 @@ async function listarBancosCredito(req, res) {
async function listarBancosPagamento(req, res) { async function listarBancosPagamento(req, res) {
try { 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({ return res.json({
ok: true, ok: true,
@ -71,7 +93,16 @@ async function listarBancosPagamento(req, res) {
async function preview(req, res) { async function preview(req, res) {
try { 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, idBancoCredito: req.query.idBancoCredito || req.query.idbancos,
dataInicio: req.query.dataInicio, dataInicio: req.query.dataInicio,
dataFim: req.query.dataFim, 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({ return res.status(201).json({
ok: true, ok: true,
@ -120,7 +160,16 @@ async function criar(req, res) {
async function listarHistorico(req, res) { async function listarHistorico(req, res) {
try { 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, limite: req.query.limite,
offset: req.query.offset, offset: req.query.offset,
page: req.query.page, 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) { if (!quitacao) {
return res.status(404).json({ return res.status(404).json({

View File

@ -112,7 +112,7 @@ function idsUnicos(ids = []) {
)]; )];
} }
async function buscarBancoParaUpdate(connection, idbancos) { async function buscarBancoParaUpdate(connection, idbancos, idUsuario) {
const [rows] = await connection.query( const [rows] = await connection.query(
` `
SELECT SELECT
@ -120,13 +120,15 @@ async function buscarBancoParaUpdate(connection, idbancos) {
descricao, descricao,
saldo, saldo,
debito, debito,
habilitado habilitado,
idusuarios
FROM bancos FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
LIMIT 1 LIMIT 1
FOR UPDATE FOR UPDATE
`, `,
[idbancos] [idbancos, idUsuario]
); );
return rows[0] || null; 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( const [rows] = await pool.query(
` `
SELECT SELECT
@ -166,16 +168,18 @@ async function listarBancosCredito() {
insert_date, insert_date,
update_date update_date
FROM bancos FROM bancos
WHERE debito = 0 WHERE idusuarios = ?
AND debito = 0
AND COALESCE(habilitado, 1) = 1 AND COALESCE(habilitado, 1) = 1
ORDER BY descricao ASC, idbancos ASC ORDER BY descricao ASC, idbancos ASC
` `,
[idUsuario]
); );
return rows; return rows;
} }
async function listarBancosPagamento() { async function listarBancosPagamento(idUsuario) {
const [rows] = await pool.query( const [rows] = await pool.query(
` `
SELECT SELECT
@ -187,10 +191,12 @@ async function listarBancosPagamento() {
insert_date, insert_date,
update_date update_date
FROM bancos FROM bancos
WHERE debito = 1 WHERE idusuarios = ?
AND debito = 1
AND COALESCE(habilitado, 1) = 1 AND COALESCE(habilitado, 1) = 1
ORDER BY descricao ASC, idbancos ASC ORDER BY descricao ASC, idbancos ASC
` `,
[idUsuario]
); );
return rows; return rows;
@ -202,9 +208,13 @@ function montarWhereContasPreview(filtros = {}) {
"cp.movimento = 'Saida'", "cp.movimento = 'Saida'",
"cp.status = 'A pagar'", "cp.status = 'A pagar'",
'cp.idbancos = ?', 'cp.idbancos = ?',
'b.idusuarios = ?',
]; ];
const params = [Number(filtros.idBancoCredito)]; const params = [
Number(filtros.idBancoCredito),
Number(filtros.idUsuario),
];
if (filtros.dataInicio) { if (filtros.dataInicio) {
where.push('DATE(cp.datavencimento) >= ?'); 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); const idBancoCredito = normalizarNumeroOuNull(filtros.idBancoCredito || filtros.idbancos);
if (!idBancoCredito) { if (!idBancoCredito) {
@ -257,12 +267,14 @@ async function previewQuitacao(filtros = {}) {
descricao, descricao,
saldo, saldo,
debito, debito,
habilitado habilitado,
idusuarios
FROM bancos FROM bancos
WHERE idbancos = ? WHERE idbancos = ?
AND idusuarios = ?
LIMIT 1 LIMIT 1
`, `,
[idBancoCredito] [idBancoCredito, idUsuario]
); );
const bancoCredito = bancoRows[0] || null; const bancoCredito = bancoRows[0] || null;
@ -278,6 +290,7 @@ async function previewQuitacao(filtros = {}) {
const { whereSql, params } = montarWhereContasPreview({ const { whereSql, params } = montarWhereContasPreview({
...filtros, ...filtros,
idBancoCredito, idBancoCredito,
idUsuario,
}); });
const [rows] = await pool.query( 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 []; if (!ids.length) return [];
const placeholders = ids.map(() => '?').join(', '); const placeholders = ids.map(() => '?').join(', ');
@ -349,16 +362,23 @@ async function buscarContasSelecionadasParaUpdate(connection, ids) {
SELECT SELECT
cp.* cp.*
FROM contasapagar cp FROM contasapagar cp
INNER JOIN bancos b ON b.idbancos = cp.idbancos
WHERE cp.idcontasapagar IN (${placeholders}) WHERE cp.idcontasapagar IN (${placeholders})
AND cp.idbancos = ?
AND b.idusuarios = ?
FOR UPDATE FOR UPDATE
`, `,
ids [
...ids,
idBancoCredito,
idUsuario,
]
); );
return rows; return rows;
} }
async function buscarContasDetalhadasPorIds(connection, ids) { async function buscarContasDetalhadasPorIds(connection, ids, idUsuario) {
if (!ids.length) return []; if (!ids.length) return [];
const placeholders = ids.map(() => '?').join(', '); const placeholders = ids.map(() => '?').join(', ');
@ -369,9 +389,10 @@ async function buscarContasDetalhadasPorIds(connection, ids) {
${CAMPOS_CONTA_SELECT} ${CAMPOS_CONTA_SELECT}
${FROM_CONTA_JOIN} ${FROM_CONTA_JOIN}
WHERE cp.idcontasapagar IN (${placeholders}) WHERE cp.idcontasapagar IN (${placeholders})
AND b.idusuarios = ?
ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC
`, `,
ids [...ids, idUsuario]
); );
return rows; return rows;
@ -405,8 +426,8 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
try { try {
await connection.beginTransaction(); await connection.beginTransaction();
const bancoCredito = await buscarBancoParaUpdate(connection, idBancoCredito); const bancoCredito = await buscarBancoParaUpdate(connection, idBancoCredito, idUsuario);
const bancoPagamento = await buscarBancoParaUpdate(connection, idBancoPagamento); const bancoPagamento = await buscarBancoParaUpdate(connection, idBancoPagamento, idUsuario);
if (!bancoCredito) { if (!bancoCredito) {
throw criarErro('Banco de crédito não encontrado.', 404); 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.'); 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) { if (contasAntes.length !== idsContas.length) {
const encontrados = new Set(contasAntes.map((conta) => Number(conta.idcontasapagar))); 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])); const contasDepoisPorId = new Map(contasDepois.map((conta) => [Number(conta.idcontasapagar), conta]));
for (const contaAntes of contasAntes) { 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 limite = limitarNumero(filtros.limite, 20, 1, 100);
const page = limitarNumero(filtros.page, 1, 1, 999999); const page = limitarNumero(filtros.page, 1, 1, 999999);
const offset = filtros.offset !== undefined const offset = filtros.offset !== undefined
? limitarNumero(filtros.offset, 0, 0, 999999999) ? limitarNumero(filtros.offset, 0, 0, 999999999)
: (page - 1) * limite; : (page - 1) * limite;
const where = ['cq.deleted_at IS NULL']; const where = [
const params = []; 'cq.deleted_at IS NULL',
'bp.idusuarios = ?',
'bc.idusuarios = ?',
];
const params = [idUsuario, idUsuario];
if (filtros.idBancoCredito) { if (filtros.idBancoCredito) {
where.push('cq.idbancos_p = ?'); 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( const [quitacaoRows] = await pool.query(
` `
SELECT SELECT
@ -768,9 +798,11 @@ async function buscarQuitacaoPorId(id) {
LEFT JOIN bancos bc ON bc.idbancos = cq.idbancos_p LEFT JOIN bancos bc ON bc.idbancos = cq.idbancos_p
LEFT JOIN usuarios u ON u.idusuarios = cq.idusuarios LEFT JOIN usuarios u ON u.idusuarios = cq.idusuarios
WHERE cq.idcontasquitadas = ? WHERE cq.idcontasquitadas = ?
AND bp.idusuarios = ?
AND bc.idusuarios = ?
LIMIT 1 LIMIT 1
`, `,
[id] [id, idUsuario, idUsuario]
); );
const quitacao = quitacaoRows[0] || null; const quitacao = quitacaoRows[0] || null;
@ -786,9 +818,10 @@ async function buscarQuitacaoPorId(id) {
${FROM_CONTA_JOIN} ${FROM_CONTA_JOIN}
INNER JOIN contasquitadasref cqr ON cqr.idcontasapagar = cp.idcontasapagar INNER JOIN contasquitadasref cqr ON cqr.idcontasapagar = cp.idcontasapagar
WHERE cqr.idcontasquitadas = ? WHERE cqr.idcontasquitadas = ?
AND b.idusuarios = ?
ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC ORDER BY cp.datavencimento ASC, cp.idcontasapagar ASC
`, `,
[id] [id, idUsuario]
); );
return { return {

View File

@ -4,14 +4,27 @@ async function listarBancos() {
const [rows] = await pool.query( const [rows] = await pool.query(
` `
SELECT SELECT
idbancos AS id, b.idbancos AS id,
descricao, b.idbancos,
saldo, b.descricao AS banco_descricao,
debito, u.idusuarios,
dia_vencimento u.nome AS usuario_nome,
FROM bancos CONCAT(
WHERE habilitado = 1 COALESCE(u.nome, 'Usuário sem nome'),
ORDER BY descricao ASC ' - ',
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
` `
); );

View File

@ -12,6 +12,7 @@ import { NotFoundPage } from '../pages/NotFoundPage';
import { BancosPage } from '../features/bancos/pages/BancosPage'; import { BancosPage } from '../features/bancos/pages/BancosPage';
import { NovoBancoPage } from '../features/bancos/pages/NovoBancoPage'; import { NovoBancoPage } from '../features/bancos/pages/NovoBancoPage';
import { EditarBancoPage } from '../features/bancos/pages/EditarBancoPage'; import { EditarBancoPage } from '../features/bancos/pages/EditarBancoPage';
import { RendimentosPage } from '../features/bancos/pages/RendimentosPage';
import { ClientesPage } from '../features/clientes/pages/ClientesPage'; import { ClientesPage } from '../features/clientes/pages/ClientesPage';
import { NovoClientePage } from '../features/clientes/pages/NovoClientePage'; import { NovoClientePage } from '../features/clientes/pages/NovoClientePage';
import { EditarClientePage } from '../features/clientes/pages/EditarClientePage'; import { EditarClientePage } from '../features/clientes/pages/EditarClientePage';
@ -73,6 +74,10 @@ export const routes: RouteObject[] = [
path: 'bancos/:id/editar', path: 'bancos/:id/editar',
element: <EditarBancoPage />, element: <EditarBancoPage />,
}, },
{
path: 'rendimentos',
element: <RendimentosPage />,
},
{ {
path: 'clientes', path: 'clientes',
element: <ClientesPage />, element: <ClientesPage />,

View File

@ -11,6 +11,7 @@ import DashboardIcon from '@mui/icons-material/Dashboard';
import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
import AssessmentIcon from '@mui/icons-material/Assessment'; import AssessmentIcon from '@mui/icons-material/Assessment';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import SavingsIcon from '@mui/icons-material/Savings';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import PeopleAltIcon from '@mui/icons-material/PeopleAlt'; import PeopleAltIcon from '@mui/icons-material/PeopleAlt';
import AccountTreeIcon from '@mui/icons-material/AccountTree'; import AccountTreeIcon from '@mui/icons-material/AccountTree';
@ -49,6 +50,11 @@ const menuItems = [
path: '/bancos', path: '/bancos',
icon: <AccountBalanceWalletIcon />, icon: <AccountBalanceWalletIcon />,
}, },
{
label: 'Rendimentos',
path: '/rendimentos',
icon: <SavingsIcon />,
},
{ {
label: 'Centros de custo', label: 'Centros de custo',
path: '/centros-custo', path: '/centros-custo',

View File

@ -110,7 +110,7 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
const [descricao, setDescricao] = useState(''); const [descricao, setDescricao] = useState('');
const [saldo, setSaldo] = useState('0'); const [saldo, setSaldo] = useState('0');
const [debito, setDebito] = useState('1'); const [tipoCarteira, setTipoCarteira] = useState<'debito' | 'credito' | 'investimento'>('debito');
const [habilitado, setHabilitado] = useState('1'); const [habilitado, setHabilitado] = useState('1');
const [diaVencimento, setDiaVencimento] = useState(''); const [diaVencimento, setDiaVencimento] = useState('');
@ -124,17 +124,24 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
setDescricao(initialData.descricao || ''); setDescricao(initialData.descricao || '');
setSaldo(String(initialData.saldo ?? 0)); setSaldo(String(initialData.saldo ?? 0));
setDebito(String(initialData.debito ?? 1));
setHabilitado(String(initialData.habilitado ?? 1)); setHabilitado(String(initialData.habilitado ?? 1));
setDiaVencimento( setDiaVencimento(
initialData.dia_vencimento ? String(initialData.dia_vencimento) : '' 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]); }, [initialData]);
function limparFormulario() { function limparFormulario() {
setDescricao(''); setDescricao('');
setSaldo('0'); setSaldo('0');
setDebito('1'); setTipoCarteira('debito');
setHabilitado('1'); setHabilitado('1');
setDiaVencimento(''); setDiaVencimento('');
} }
@ -152,7 +159,7 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
return; return;
} }
if (Number(debito) === 0) { if (tipoCarteira === 'credito') {
const dia = Number(diaVencimento); const dia = Number(diaVencimento);
if (!diaVencimento || !Number.isInteger(dia) || dia < 1 || dia > 31) { if (!diaVencimento || !Number.isInteger(dia) || dia < 1 || dia > 31) {
@ -169,9 +176,10 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
const payload: CriarBancoRequest = { const payload: CriarBancoRequest = {
descricao: descricao.trim(), descricao: descricao.trim(),
saldo: Number(saldo || 0), saldo: Number(saldo || 0),
debito: Number(debito), debito: tipoCarteira === 'credito' ? 0 : 1,
investimento: tipoCarteira === 'investimento' ? 1 : 0,
habilitado: Number(habilitado || 1), habilitado: Number(habilitado || 1),
dia_vencimento: Number(debito) === 0 ? Number(diaVencimento) : null, dia_vencimento: tipoCarteira === 'credito' ? Number(diaVencimento) : null,
}; };
if (isEdit) { if (isEdit) {
@ -281,16 +289,19 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
<TextField <TextField
select select
label="Tipo" label="Tipo"
value={debito} value={tipoCarteira}
onChange={(event) => setDebito(event.target.value)} onChange={(event) =>
setTipoCarteira(event.target.value as 'debito' | 'credito' | 'investimento')
}
fullWidth fullWidth
sx={fieldSx} sx={fieldSx}
> >
<MenuItem value="1">Débito</MenuItem> <MenuItem value="debito">Débito</MenuItem>
<MenuItem value="0">Crédito</MenuItem> <MenuItem value="credito">Crédito</MenuItem>
<MenuItem value="investimento">Investimento</MenuItem>
</TextField> </TextField>
{Number(debito) === 0 && ( {tipoCarteira === 'credito' && (
<TextField <TextField
label="Dia de vencimento" label="Dia de vencimento"
type="number" type="number"
@ -414,11 +425,15 @@ export function BancoForm({ mode, initialData }: BancoFormProps) {
Tipo Tipo
</Typography> </Typography>
<Typography fontWeight={800}> <Typography fontWeight={800}>
{Number(debito) === 1 ? 'Débito' : 'Crédito'} {tipoCarteira === 'investimento'
? 'Investimento'
: tipoCarteira === 'credito'
? 'Crédito'
: 'Débito'}
</Typography> </Typography>
</Box> </Box>
{Number(debito) === 0 && ( {tipoCarteira === 'credito' && (
<Box> <Box>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
Dia de vencimento Dia de vencimento

View File

@ -61,12 +61,14 @@ function formatarData(data: string | null) {
return new Date(`${String(data).slice(0, 10)}T00:00:00`).toLocaleDateString('pt-BR'); return new Date(`${String(data).slice(0, 10)}T00:00:00`).toLocaleDateString('pt-BR');
} }
function tipoBancoLabel(debito: number) { function tipoBancoLabel(item: Banco) {
return Number(debito) === 1 ? 'Débito' : 'Crédito'; if (Number(item.investimento) === 1) return 'Investimento';
return Number(item.debito) === 1 ? 'Débito' : 'Crédito';
} }
function tipoBancoColor(debito: number) { function tipoBancoColor(item: Banco) {
return Number(debito) === 1 ? 'success' : 'info'; if (Number(item.investimento) === 1) return 'secondary';
return Number(item.debito) === 1 ? 'success' : 'info';
} }
function statusBancoLabel(habilitado: number) { function statusBancoLabel(habilitado: number) {
@ -96,8 +98,9 @@ export function BancosPage() {
const [summary, setSummary] = useState<BancosSummary>({ const [summary, setSummary] = useState<BancosSummary>({
quantidade: 0, quantidade: 0,
saldoTotal: 0, saldoTotal: 0,
saldoCarteiras: 0, saldoCredito: 0,
saldoDebito: 0, saldoDebito: 0,
saldoInvestimento: 0,
}); });
const [alterandoStatusId, setAlterandoStatusId] = useState<number | null>(null); const [alterandoStatusId, setAlterandoStatusId] = useState<number | null>(null);
@ -108,7 +111,7 @@ export function BancosPage() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [busca, setBusca] = useState(''); const [busca, setBusca] = useState('');
const [debito, setDebito] = useState<number | ''>(''); const [tipoFiltro, setTipoFiltro] = useState<'' | 'credito' | 'debito' | 'investimento'>('');
const [habilitado, setHabilitado] = useState<number | ''>(''); const [habilitado, setHabilitado] = useState<number | ''>('');
const [diaVencimento, setDiaVencimento] = useState<number | ''>(''); const [diaVencimento, setDiaVencimento] = useState<number | ''>('');
const [orderBy, setOrderBy] = useState('descricao'); const [orderBy, setOrderBy] = useState('descricao');
@ -121,12 +124,12 @@ export function BancosPage() {
let count = 0; let count = 0;
if (busca.trim()) count += 1; if (busca.trim()) count += 1;
if (debito !== '') count += 1; if (tipoFiltro !== '') count += 1;
if (habilitado !== '') count += 1; if (habilitado !== '') count += 1;
if (diaVencimento !== '') count += 1; if (diaVencimento !== '') count += 1;
return count; return count;
}, [busca, debito, habilitado, diaVencimento]); }, [busca, tipoFiltro, habilitado, diaVencimento]);
async function carregarBancos(pageToLoad = page) { async function carregarBancos(pageToLoad = page) {
try { try {
@ -137,7 +140,18 @@ export function BancosPage() {
limite: LIMITE_PADRAO, limite: LIMITE_PADRAO,
page: pageToLoad, page: pageToLoad,
busca: busca.trim() || undefined, busca: busca.trim() || undefined,
debito, debito:
tipoFiltro === 'credito'
? 0
: tipoFiltro === 'debito'
? 1
: undefined,
investimento:
tipoFiltro === 'investimento'
? 1
: tipoFiltro === 'credito' || tipoFiltro === 'debito'
? 0
: undefined,
habilitado, habilitado,
dia_vencimento: diaVencimento, dia_vencimento: diaVencimento,
orderBy, orderBy,
@ -168,7 +182,7 @@ export function BancosPage() {
function limparFiltros() { function limparFiltros() {
setBusca(''); setBusca('');
setDebito(''); setTipoFiltro('');
setHabilitado(''); setHabilitado('');
setDiaVencimento(''); setDiaVencimento('');
setOrderBy('descricao'); setOrderBy('descricao');
@ -303,7 +317,7 @@ export function BancosPage() {
display="grid" display="grid"
gridTemplateColumns={{ gridTemplateColumns={{
xs: '1fr', xs: '1fr',
md: 'repeat(4, 1fr)', md: 'repeat(5, 1fr)',
}} }}
gap={{ xs: 2, md: 2.5 }} gap={{ xs: 2, md: 2.5 }}
marginBottom={{ xs: 2.5, md: 3 }} marginBottom={{ xs: 2.5, md: 3 }}
@ -337,10 +351,10 @@ export function BancosPage() {
<Card> <Card>
<CardContent> <CardContent>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Dívida em crédito Saldo em crédito
</Typography> </Typography>
<Typography variant="h5" fontWeight={950} color="success.main"> <Typography variant="h5" fontWeight={950} color="info.main">
{formatarValor(summary.saldoCarteiras)} {formatarValor(summary.saldoCredito)}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@ -350,11 +364,22 @@ export function BancosPage() {
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Saldo em débito Saldo em débito
</Typography> </Typography>
<Typography variant="h5" fontWeight={950} color="warning.main"> <Typography variant="h5" fontWeight={950} color="success.main">
{formatarValor(summary.saldoDebito)} {formatarValor(summary.saldoDebito)}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </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> </Box>
<Card sx={{ marginBottom: { xs: 2.5, md: 3 } }}> <Card sx={{ marginBottom: { xs: 2.5, md: 3 } }}>
@ -382,7 +407,7 @@ export function BancosPage() {
display="grid" display="grid"
gridTemplateColumns={{ gridTemplateColumns={{
xs: '1fr', xs: '1fr',
md: 'repeat(4, 1fr)', md: 'repeat(5, 1fr)',
}} }}
gap={{ xs: 2, md: 2.25 }} gap={{ xs: 2, md: 2.25 }}
> >
@ -397,15 +422,16 @@ export function BancosPage() {
<TextField <TextField
select select
label="Tipo" label="Tipo"
value={debito} value={tipoFiltro}
onChange={(event) => onChange={(event) =>
setDebito(event.target.value === '' ? '' : Number(event.target.value)) setTipoFiltro(event.target.value as '' | 'credito' | 'debito' | 'investimento')
} }
fullWidth fullWidth
> >
<MenuItem value="">Todos</MenuItem> <MenuItem value="">Todos</MenuItem>
<MenuItem value={0}>Crédito</MenuItem> <MenuItem value="credito">Crédito</MenuItem>
<MenuItem value={1}>Débito</MenuItem> <MenuItem value="debito">Débito</MenuItem>
<MenuItem value="investimento">Investimento</MenuItem>
</TextField> </TextField>
<TextField <TextField
@ -420,8 +446,12 @@ export function BancosPage() {
max: 31, max: 31,
step: 1, step: 1,
}} }}
disabled={debito === 1} disabled={tipoFiltro !== 'credito'}
helperText={debito === 1 ? 'Disponível apenas para crédito' : 'Filtra bancos de crédito'} helperText={
tipoFiltro !== 'credito'
? 'Disponível apenas para crédito'
: 'Filtra bancos de crédito'
}
fullWidth fullWidth
/> />
@ -550,9 +580,9 @@ export function BancosPage() {
<Stack direction="row" flexWrap="wrap" spacing={1} useFlexGap> <Stack direction="row" flexWrap="wrap" spacing={1} useFlexGap>
<Chip <Chip
label={tipoBancoLabel(item.debito)} label={tipoBancoLabel(item)}
size="small" size="small"
color={tipoBancoColor(item.debito) as any} color={tipoBancoColor(item) as any}
variant="outlined" variant="outlined"
/> />
@ -664,9 +694,9 @@ export function BancosPage() {
<TableCell> <TableCell>
<Chip <Chip
label={tipoBancoLabel(item.debito)} label={tipoBancoLabel(item)}
size="small" size="small"
color={tipoBancoColor(item.debito) as any} color={tipoBancoColor(item) as any}
variant="outlined" variant="outlined"
/> />
</TableCell> </TableCell>

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ export type ListarBancosParams = {
page?: number; page?: number;
busca?: string; busca?: string;
debito?: number | ''; debito?: number | '';
investimento?: number | '';
habilitado?: number | ''; habilitado?: number | '';
dia_vencimento?: number | ''; dia_vencimento?: number | '';
orderBy?: string; orderBy?: string;

View File

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

View File

@ -1,8 +1,10 @@
export type Banco = { export type Banco = {
idbancos: number; idbancos: number;
idusuarios: number;
descricao: string; descricao: string;
saldo: number; saldo: number;
debito: number; debito: number;
investimento: number;
habilitado: number; habilitado: number;
dia_vencimento: number | null; dia_vencimento: number | null;
insert_date: string | null; insert_date: string | null;
@ -13,6 +15,7 @@ export type CriarBancoRequest = {
descricao: string; descricao: string;
saldo: number; saldo: number;
debito: number; debito: number;
investimento: number;
habilitado: number; habilitado: number;
dia_vencimento: number | null; dia_vencimento: number | null;
}; };
@ -30,8 +33,9 @@ export type BancosPagination = {
export type BancosSummary = { export type BancosSummary = {
quantidade: number; quantidade: number;
saldoTotal: number; saldoTotal: number;
saldoCarteiras: number; saldoCredito: number;
saldoDebito: number; saldoDebito: number;
saldoInvestimento: number;
}; };
export type ApiResponse<T> = { export type ApiResponse<T> = {

View File

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

View File

@ -291,7 +291,7 @@ export function QuitacoesCreditoPage() {
}); });
setSucesso( setSucesso(
`Quitação realizada com sucesso. Valor quitado: ${formatarValor(response.valor)}.` `Quitação realizada com sucesso. Valor quitado: ${formatarValor(response.valorQuitado)}.`
); );
setImpactoSaldo(response.impactoSaldo || []); setImpactoSaldo(response.impactoSaldo || []);

View File

@ -79,7 +79,7 @@ export type QuitacaoCreditoResponse = {
idcontasquitadas: number; idcontasquitadas: number;
idbancos: number; idbancos: number;
idbancos_p: number; idbancos_p: number;
valor: number; valorQuitado: number;
descricao: string | null; descricao: string | null;
data_quitacao: string | null; data_quitacao: string | null;
contasQuitadas?: ContaCreditoQuitacao[]; contasQuitadas?: ContaCreditoQuitacao[];