ajuste na regra de criação do movimento estorno e quitação de crédito considerando estorno
This commit is contained in:
parent
f085f2623d
commit
65634edcdf
|
|
@ -147,7 +147,13 @@ async function criar(req, res) {
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
ok: true,
|
ok: true,
|
||||||
message: 'Crédito quitado com sucesso.',
|
message: 'Crédito quitado com sucesso.',
|
||||||
data: resultado.quitacao,
|
data: {
|
||||||
|
...(resultado.quitacao || {}),
|
||||||
|
contasQuitadas: resultado.contasQuitadas,
|
||||||
|
valorQuitado: resultado.valorQuitado,
|
||||||
|
impactoSaldo: resultado.impactoSaldo || [],
|
||||||
|
},
|
||||||
|
// Mantidos no topo por compatibilidade com consumidores antigos.
|
||||||
contasQuitadas: resultado.contasQuitadas,
|
contasQuitadas: resultado.contasQuitadas,
|
||||||
valorQuitado: resultado.valorQuitado,
|
valorQuitado: resultado.valorQuitado,
|
||||||
impactoSaldo: resultado.impactoSaldo || [],
|
impactoSaldo: resultado.impactoSaldo || [],
|
||||||
|
|
@ -238,4 +244,4 @@ module.exports = {
|
||||||
criar,
|
criar,
|
||||||
listarHistorico,
|
listarHistorico,
|
||||||
buscarPorId,
|
buscarPorId,
|
||||||
};
|
};
|
||||||
|
|
@ -112,6 +112,19 @@ function idsUnicos(ids = []) {
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function contaEhSaidaAberta(conta) {
|
||||||
|
return conta?.movimento === 'Saida' && conta?.status === 'A pagar';
|
||||||
|
}
|
||||||
|
|
||||||
|
function contaEhEstornoRecebido(conta) {
|
||||||
|
return (conta?.movimento === 'Entrada' || conta?.movimento === 'Estorno') && conta?.status === 'Recebido';
|
||||||
|
}
|
||||||
|
|
||||||
|
function valorContaNaQuitacao(conta) {
|
||||||
|
const valor = Number(conta?.valor || 0);
|
||||||
|
return contaEhEstornoRecebido(conta) ? -valor : valor;
|
||||||
|
}
|
||||||
|
|
||||||
async function buscarBancoParaUpdate(connection, idbancos, idUsuario) {
|
async function buscarBancoParaUpdate(connection, idbancos, idUsuario) {
|
||||||
const [rows] = await connection.query(
|
const [rows] = await connection.query(
|
||||||
`
|
`
|
||||||
|
|
@ -205,10 +218,18 @@ async function listarBancosPagamento(idUsuario) {
|
||||||
function montarWhereContasPreview(filtros = {}) {
|
function montarWhereContasPreview(filtros = {}) {
|
||||||
const where = [
|
const where = [
|
||||||
'cp.deleted_at IS NULL',
|
'cp.deleted_at IS NULL',
|
||||||
"cp.movimento = 'Saida'",
|
`(
|
||||||
"cp.status = 'A pagar'",
|
(cp.movimento = 'Saida' AND cp.status = 'A pagar')
|
||||||
|
OR
|
||||||
|
(cp.movimento IN ('Entrada', 'Estorno') AND cp.status = 'Recebido')
|
||||||
|
)`,
|
||||||
'cp.idbancos = ?',
|
'cp.idbancos = ?',
|
||||||
'b.idusuarios = ?',
|
'b.idusuarios = ?',
|
||||||
|
`NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM contasquitadasref cqr
|
||||||
|
WHERE cqr.idcontasapagar = cp.idcontasapagar
|
||||||
|
)`,
|
||||||
];
|
];
|
||||||
|
|
||||||
const params = [
|
const params = [
|
||||||
|
|
@ -305,6 +326,11 @@ async function previewQuitacao(idUsuario, filtros = {}) {
|
||||||
[...params, limite, offset]
|
[...params, limite, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contas = rows.map((conta) => ({
|
||||||
|
...conta,
|
||||||
|
valor_quitacao: arredondarMoeda(valorContaNaQuitacao(conta)),
|
||||||
|
}));
|
||||||
|
|
||||||
const [countRows] = await pool.query(
|
const [countRows] = await pool.query(
|
||||||
`
|
`
|
||||||
SELECT COUNT(*) AS total
|
SELECT COUNT(*) AS total
|
||||||
|
|
@ -318,7 +344,12 @@ async function previewQuitacao(idUsuario, filtros = {}) {
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) AS quantidade,
|
COUNT(*) AS quantidade,
|
||||||
COALESCE(SUM(cp.valor), 0) AS valorTotal,
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN cp.movimento IN ('Entrada', 'Estorno') AND cp.status = 'Recebido' THEN -cp.valor
|
||||||
|
ELSE cp.valor
|
||||||
|
END
|
||||||
|
), 0) AS valorTotal,
|
||||||
MIN(cp.datavencimento) AS primeiroVencimento,
|
MIN(cp.datavencimento) AS primeiroVencimento,
|
||||||
MAX(cp.datavencimento) AS ultimoVencimento
|
MAX(cp.datavencimento) AS ultimoVencimento
|
||||||
${FROM_CONTA_JOIN}
|
${FROM_CONTA_JOIN}
|
||||||
|
|
@ -333,7 +364,7 @@ async function previewQuitacao(idUsuario, filtros = {}) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bancoCredito,
|
bancoCredito,
|
||||||
contas: rows,
|
contas,
|
||||||
pagination: {
|
pagination: {
|
||||||
total,
|
total,
|
||||||
limite,
|
limite,
|
||||||
|
|
@ -343,8 +374,11 @@ async function previewQuitacao(idUsuario, filtros = {}) {
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
quantidade: Number(resumo.quantidade || 0),
|
quantidade: Number(resumo.quantidade || 0),
|
||||||
|
totalAberto: arredondarMoeda(resumo.valorTotal || 0),
|
||||||
valorTotal: arredondarMoeda(resumo.valorTotal || 0),
|
valorTotal: arredondarMoeda(resumo.valorTotal || 0),
|
||||||
|
saldoAtualCredito: arredondarMoeda(bancoCredito.saldo || 0),
|
||||||
saldoCreditoAtual: arredondarMoeda(bancoCredito.saldo || 0),
|
saldoCreditoAtual: arredondarMoeda(bancoCredito.saldo || 0),
|
||||||
|
saldoAposQuitacaoTotal: arredondarMoeda(Number(bancoCredito.saldo || 0) - Number(resumo.valorTotal || 0)),
|
||||||
saldoCreditoAposQuitacaoTotal: arredondarMoeda(Number(bancoCredito.saldo || 0) - Number(resumo.valorTotal || 0)),
|
saldoCreditoAposQuitacaoTotal: arredondarMoeda(Number(bancoCredito.saldo || 0) - Number(resumo.valorTotal || 0)),
|
||||||
primeiroVencimento: resumo.primeiroVencimento || null,
|
primeiroVencimento: resumo.primeiroVencimento || null,
|
||||||
ultimoVencimento: resumo.ultimoVencimento || null,
|
ultimoVencimento: resumo.ultimoVencimento || null,
|
||||||
|
|
@ -366,6 +400,11 @@ async function buscarContasSelecionadasParaUpdate(connection, ids, idBancoCredit
|
||||||
WHERE cp.idcontasapagar IN (${placeholders})
|
WHERE cp.idcontasapagar IN (${placeholders})
|
||||||
AND cp.idbancos = ?
|
AND cp.idbancos = ?
|
||||||
AND b.idusuarios = ?
|
AND b.idusuarios = ?
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM contasquitadasref cqr
|
||||||
|
WHERE cqr.idcontasapagar = cp.idcontasapagar
|
||||||
|
)
|
||||||
FOR UPDATE
|
FOR UPDATE
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
|
|
@ -475,16 +514,16 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
||||||
throw criarErro(`A conta ${conta.idcontasapagar} não pertence ao banco de crédito selecionado.`);
|
throw criarErro(`A conta ${conta.idcontasapagar} não pertence ao banco de crédito selecionado.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conta.movimento !== 'Saida') {
|
if (!contaEhSaidaAberta(conta) && !contaEhEstornoRecebido(conta)) {
|
||||||
throw criarErro(`A conta ${conta.idcontasapagar} não é uma saída de crédito.`);
|
throw criarErro(
|
||||||
}
|
`A conta ${conta.idcontasapagar} não é uma saída em aberto nem um estorno recebido.`
|
||||||
|
);
|
||||||
if (conta.status !== 'A pagar') {
|
|
||||||
throw criarErro(`A conta ${conta.idcontasapagar} não está em aberto para pagamento.`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const valorTotal = arredondarMoeda(contasAntes.reduce((total, conta) => total + Number(conta.valor || 0), 0));
|
const valorTotal = arredondarMoeda(
|
||||||
|
contasAntes.reduce((total, conta) => total + valorContaNaQuitacao(conta), 0)
|
||||||
|
);
|
||||||
|
|
||||||
if (valorTotal <= 0) {
|
if (valorTotal <= 0) {
|
||||||
throw criarErro('O valor total da quitação deve ser maior que zero.');
|
throw criarErro('O valor total da quitação deve ser maior que zero.');
|
||||||
|
|
@ -555,6 +594,8 @@ async function criarQuitacaoCredito(idUsuario, dados = {}) {
|
||||||
idbancos_p = ?,
|
idbancos_p = ?,
|
||||||
update_date = NOW()
|
update_date = NOW()
|
||||||
WHERE idcontasapagar IN (${placeholders})
|
WHERE idcontasapagar IN (${placeholders})
|
||||||
|
AND movimento = 'Saida'
|
||||||
|
AND status = 'A pagar'
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
dataQuitacao,
|
dataQuitacao,
|
||||||
|
|
@ -837,4 +878,4 @@ module.exports = {
|
||||||
criarQuitacaoCredito,
|
criarQuitacaoCredito,
|
||||||
listarHistorico,
|
listarHistorico,
|
||||||
buscarQuitacaoPorId,
|
buscarQuitacaoPorId,
|
||||||
};
|
};
|
||||||
|
|
@ -59,7 +59,12 @@ type FormSectionProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function hojeISO() {
|
function hojeISO() {
|
||||||
return new Date().toISOString().slice(0, 10);
|
const hoje = new Date();
|
||||||
|
const ano = hoje.getFullYear();
|
||||||
|
const mes = String(hoje.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dia = String(hoje.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${ano}-${mes}-${dia}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcularVencimentoCreditoProximoMes(diaVencimento: number, dataBaseISO?: string) {
|
function calcularVencimentoCreditoProximoMes(diaVencimento: number, dataBaseISO?: string) {
|
||||||
|
|
@ -79,13 +84,11 @@ function calcularVencimentoCreditoProximoMes(diaVencimento: number, dataBaseISO?
|
||||||
|
|
||||||
const diaSeguro = Math.min(Number(diaVencimento), ultimoDiaProximoMes);
|
const diaSeguro = Math.min(Number(diaVencimento), ultimoDiaProximoMes);
|
||||||
|
|
||||||
return new Date(
|
const anoVencimento = proximoMes.getFullYear();
|
||||||
proximoMes.getFullYear(),
|
const mesVencimento = String(proximoMes.getMonth() + 1).padStart(2, '0');
|
||||||
proximoMes.getMonth(),
|
const diaVencimentoFormatado = String(diaSeguro).padStart(2, '0');
|
||||||
diaSeguro
|
|
||||||
)
|
return `${anoVencimento}-${mesVencimento}-${diaVencimentoFormatado}`;
|
||||||
.toISOString()
|
|
||||||
.slice(0, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toNumberOrNull(value: string): number | null {
|
function toNumberOrNull(value: string): number | null {
|
||||||
|
|
@ -287,6 +290,10 @@ export function MovimentoForm({ mode, initialData }: MovimentoFormProps) {
|
||||||
return ['Recebido', 'A receber'];
|
return ['Recebido', 'A receber'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (movimento === 'Estorno') {
|
||||||
|
return ['Recebido'];
|
||||||
|
}
|
||||||
|
|
||||||
if (movimento === 'Saida' || movimento === 'Sangria') {
|
if (movimento === 'Saida' || movimento === 'Sangria') {
|
||||||
return ['Pago', 'A pagar'];
|
return ['Pago', 'A pagar'];
|
||||||
}
|
}
|
||||||
|
|
@ -393,7 +400,7 @@ export function MovimentoForm({ mode, initialData }: MovimentoFormProps) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEdit) return;
|
if (isEdit) return;
|
||||||
if (movimento !== 'Saida') return;
|
if (movimento !== 'Saida' && movimento !== 'Estorno') return;
|
||||||
if (!bancoSelecionado) return;
|
if (!bancoSelecionado) return;
|
||||||
if (Number(bancoSelecionado.debito) !== 0) return;
|
if (Number(bancoSelecionado.debito) !== 0) return;
|
||||||
if (!bancoSelecionado.dia_vencimento) return;
|
if (!bancoSelecionado.dia_vencimento) return;
|
||||||
|
|
@ -405,9 +412,15 @@ export function MovimentoForm({ mode, initialData }: MovimentoFormProps) {
|
||||||
|
|
||||||
setDataVencimento(novaDataVencimento);
|
setDataVencimento(novaDataVencimento);
|
||||||
|
|
||||||
if (status === 'Pago') {
|
if (movimento === 'Saida' && status === 'Pago') {
|
||||||
setStatus('A pagar');
|
setStatus('A pagar');
|
||||||
setDataBaixa('');
|
setDataBaixa('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movimento === 'Estorno') {
|
||||||
|
setStatus('Recebido');
|
||||||
|
setDataBaixa(dataEntrada || hojeISO());
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isEdit,
|
isEdit,
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,23 @@ function impactoColor(valorDelta: number) {
|
||||||
return valorDelta >= 0 ? 'success.main' : 'error.main';
|
return valorDelta >= 0 ? 'success.main' : 'error.main';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function valorContaQuitacao(conta: ContaCreditoQuitacao) {
|
||||||
|
const ehEstornoRecebido =
|
||||||
|
(conta.movimento === 'Entrada' || conta.movimento === 'Estorno') &&
|
||||||
|
conta.status === 'Recebido';
|
||||||
|
|
||||||
|
return Number(
|
||||||
|
conta.valor_quitacao ??
|
||||||
|
(ehEstornoRecebido
|
||||||
|
? -Number(conta.valor || 0)
|
||||||
|
: Number(conta.valor || 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function contaEhEstorno(conta: ContaCreditoQuitacao) {
|
||||||
|
return valorContaQuitacao(conta) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function QuitacoesCreditoPage() {
|
export function QuitacoesCreditoPage() {
|
||||||
const [bancosCredito, setBancosCredito] = useState<BancoQuitacao[]>([]);
|
const [bancosCredito, setBancosCredito] = useState<BancoQuitacao[]>([]);
|
||||||
const [bancosPagamento, setBancosPagamento] = useState<BancoQuitacao[]>([]);
|
const [bancosPagamento, setBancosPagamento] = useState<BancoQuitacao[]>([]);
|
||||||
|
|
@ -142,7 +159,7 @@ export function QuitacoesCreditoPage() {
|
||||||
}, [contas, idsSelecionados]);
|
}, [contas, idsSelecionados]);
|
||||||
|
|
||||||
const totalSelecionado = useMemo(() => {
|
const totalSelecionado = useMemo(() => {
|
||||||
return contasSelecionadas.reduce((total, conta) => total + Number(conta.valor || 0), 0);
|
return contasSelecionadas.reduce((total, conta) => total + valorContaQuitacao(conta), 0);
|
||||||
}, [contasSelecionadas]);
|
}, [contasSelecionadas]);
|
||||||
|
|
||||||
const todasSelecionadas = contas.length > 0 && idsSelecionados.length === contas.length;
|
const todasSelecionadas = contas.length > 0 && idsSelecionados.length === contas.length;
|
||||||
|
|
@ -627,8 +644,12 @@ export function QuitacoesCreditoPage() {
|
||||||
sx={{ margin: 0 }}
|
sx={{ margin: 0 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography fontWeight={950} whiteSpace="nowrap">
|
<Typography
|
||||||
{formatarValor(conta.valor)}
|
fontWeight={950}
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
color={contaEhEstorno(conta) ? 'success.main' : 'inherit'}
|
||||||
|
>
|
||||||
|
{formatarValor(valorContaQuitacao(conta))}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
@ -639,6 +660,9 @@ export function QuitacoesCreditoPage() {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color={statusChipColor(conta.status) as any}
|
color={statusChipColor(conta.status) as any}
|
||||||
/>
|
/>
|
||||||
|
{contaEhEstorno(conta) && (
|
||||||
|
<Chip label="Estorno" size="small" color="success" variant="outlined" />
|
||||||
|
)}
|
||||||
<Chip
|
<Chip
|
||||||
label={`${conta.parcela}/${conta.parcelas}`}
|
label={`${conta.parcela}/${conta.parcelas}`}
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -726,8 +750,11 @@ export function QuitacoesCreditoPage() {
|
||||||
<TableCell>{formatarData(conta.datavencimento)}</TableCell>
|
<TableCell>{formatarData(conta.datavencimento)}</TableCell>
|
||||||
|
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<Typography fontWeight={950}>
|
<Typography
|
||||||
{formatarValor(conta.valor)}
|
fontWeight={950}
|
||||||
|
color={contaEhEstorno(conta) ? 'success.main' : 'inherit'}
|
||||||
|
>
|
||||||
|
{formatarValor(valorContaQuitacao(conta))}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
|
|
@ -967,4 +994,4 @@ export function QuitacoesCreditoPage() {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ export type ContaCreditoQuitacao = {
|
||||||
datavencimento: string | null;
|
datavencimento: string | null;
|
||||||
databaixa: string | null;
|
databaixa: string | null;
|
||||||
valor: number;
|
valor: number;
|
||||||
|
/** Valor com sinal usado na quitação: saídas positivas e estornos negativos. */
|
||||||
|
valor_quitacao?: number;
|
||||||
parcela: number;
|
parcela: number;
|
||||||
parcelas: number;
|
parcelas: number;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
@ -107,4 +109,4 @@ export type ApiResponse<T> = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
data: T;
|
data: T;
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue