Financeiro (Parcelas) e Regras de Negociação — Visão Geral
Ticket: [<Link do ticket>](<Link do ticket>)
Título: <Nome do ticket ou funcionalidade>
Última atualização: 30 de dezembro de 2025
Visão Geral
Este documento descreve o funcionamento do módulo Financeiro na criação de pedidos (Nova Venda), incluindo:
- Seleção da regra de negociação pelo tipo de venda (
CODTIPVENDA). - Geração automática das parcelas (rows) com vencimento e percentuais.
- Regras de cálculo de datas de vencimento considerando feriados/finais de semana.
- Regras de distribuição do valor do pedido entre parcelas (percentual/valor).
- Regras de validação de quantidade mínima/máxima de parcelas (dependendo da empresa/configuração).
- Conversão de linhas do UI para o payload do backend.
Arquivos envolvidos
| Arquivo | Localização | Responsabilidade |
|---|---|---|
rules-negotiation.sales.create.controller.ts | src/controllers/sale/create/ | Regras/algoritmos de negociação (seletores, cálculo de data, distribuição, validações, payload) |
FinanceListController.tsx | src/screens/nova-venda/components/Finance/ | Controller do UI: edição/redistribuição/validações e integrações com Redux |
FinanceList.tsx | src/screens/nova-venda/components/Finance/ | Grid do Financeiro (DataGrid): edição de vencimento/valor/%/formas de pagamento |
sale-create.slice.ts | src/store/ | Estado rulesNegotiationRows e action setRulesNegotiationRow |
Estruturas de dados
1) RulesNegotiation e PARCELAS
Origem: state.salesCreateUtils.rulesNegotiation.
Cada regra possui uma lista PARCELAS (tipo RulesNegotiationParcelas) contendo, por parcela padrão:
SEQUENCIA: identificador/ordemPRAZO: prazo em dias para vencimentoPERCENTUAL: percentual do valor do pedidoVENCNAOUTIL: comportamento quando vencer em não-útilSUBTIPOVENDA: forma de pagamentoCODTIPTITPAD: tipo de títuloDESCRSUBTIPOVENDA/DESCRTIPTIT: descrições
2) Linhas do Financeiro no UI: RulesNegotiationRow
Origem: state.salesCreate.rulesNegotiationRows.
Interface (resumo):
DT_VENCIMENTO(string DD/MM/YYYY)VALOR(number)PERCENTUAL(number)FORMA_PAGAMENTO(string/subtipo venda)TIPO_DE_TITULO(number/código do título)AD_CARTOES(opcional; usado em cenários específicos)
3) Payload enviado ao backend: RulesNegotiationBody
Gerado por RulesNegotiationController.getRulesNegotiationBodySelector:
export interface RulesNegotiationBody {
CODTIPTIT: string;
DTVENC: string;
VLRDESDOB: string;
AD_SEQVDY: string;
AD_CARTAO?: string;
}
Regra 1 — Seleção da Regra de Negociação (getRulesNegotiationSelector)
A regra de negociação é selecionada comparando rule.CODTIPVENDA com header.CODTIPVENDA.value.
Trecho real:
let data = rulesNegotiation?.find?.(
(rule) => rule.CODTIPVENDA?.toString() === CODTIPVENDA?.value?.toString?.()
);
Ajuste por empresa no caso Dalla
Quando a empresa é Dalla e existe regra selecionada, a lista PARCELAS é filtrada por CODEMP do header.
if (permissions?.permissionByCompany?.isDalla() && data) {
data = {
...data,
PARCELAS:
data?.PARCELAS?.filter(
(rule) => rule?.CODEMP?.toString?.() === CODEMP?.value?.toString?.()
) || [],
};
}
Regra 2 — Agrupamento de métodos de pagamento (getPaymentMethodsSelector)
As parcelas disponíveis (PARCELAS) são agrupadas por:
SUBTIPOVENDA(forma de pagamento)CODTIPTITPAD(tipo de título)
Implementação:
export const groupByCodTipTitPadAndSubTipoVenda = (
data: RulesNegotiationParcelas[]
) => {
return data.reduce((acc: any, item) => {
if (!acc[item.SUBTIPOVENDA]) {
acc[item.SUBTIPOVENDA] = {};
}
if (!acc[item.SUBTIPOVENDA][item.CODTIPTITPAD]) {
acc[item.SUBTIPOVENDA][item.CODTIPTITPAD] = [];
}
acc[item.SUBTIPOVENDA][item.CODTIPTITPAD].push(item);
return acc;
}, {});
};
Leitura prática
- A lista do select de Forma de Pagamento (UI) é derivada do agrupamento por
SUBTIPOVENDA. - A lista do select de Tipo de Título (UI) depende da forma de pagamento selecionada.
Regra 3 — Cálculos básicos de percentual/valor
3.1 Valor a partir de percentual
export const calculateValueByPercentual = (value: number, percentual: number) =>
Number(((value * percentual) / 100).toFixed(2));
3.2 Percentual a partir de valor
export const calculatePercentualByValue = (
value: number,
totalValue: number
) => {
return (value * 100) / totalValue;
};
Regra 4 — Cálculo da data de vencimento por parcela (calculateDate)
A data base utilizada é:
DTNEG(data do pedido) quando informada- caso contrário, a data corrente (
dayjs().format('DD/MM/YYYY'))
A data inicial do vencimento é:
DTNEG + PRAZO(em dias)
const dt = DTNEG ?? dayjs().format("DD/MM/YYYY");
let result = dayjs(dt, "DD/MM/YYYY").add(rule?.PRAZO ?? 0, "day");
Ajuste de vencimento não útil (feriado/fim de semana)
A função considera não-útil quando:
- for feriado (
holiday.DTFERIADO.includes(date.format('DDMMYYYY'))) - ou for sábado/domingo (
date.day() === 6 || date.day() === 0)
const isHolidayOrWeekend = (date) => {
const isHoliday = Object.values(holidays).some((holiday) =>
holiday.DTFERIADO.includes(date.format("DDMMYYYY"))
);
const isWeekend = date.day() === 6 || date.day() === 0;
return isHoliday || isWeekend;
};
Quando rule.VENCNAOUTIL !== 0 e a data cair em não-útil:
VENCNAOUTIL === 1→ avança para o próximo dia útil.- caso contrário → volta para o dia útil anterior.
if (rule.VENCNAOUTIL !== 0 && isHolidayOrWeekend(result)) {
do {
if (rule.VENCNAOUTIL === 1) {
result = result.add(1, "day");
} else {
result = result.subtract(1, "day");
}
} while (isHolidayOrWeekend(result));
}
Retorno:
DD/MM/YYYY
Regra 5 — Geração automática das parcelas (setRulesNegotiation)
A função gera a lista de parcelas padrão a partir de rulesNegotiation.PARCELAS e do total do pedido.
5.1 Caso especial: inicialização com financeiro zerado
Quando rulesNegotiation.AD_INIFINZERO === 'S', a regra zera as parcelas da negociação:
if (rulesNegotiation && rulesNegotiation?.AD_INIFINZERO === "S") {
rulesNegotiation = {
...rulesNegotiation,
PARCELAS: [],
};
}
5.2 Construção das linhas
let result =
rulesNegotiation?.PARCELAS?.map?.((rule, id) => ({
id: Number(rule?.SEQUENCIA) >= 0 ? Number(rule?.SEQUENCIA) : id,
DESDOBRAMENTO: rule?.SEQUENCIA,
DT_VENCIMENTO: RulesNegotiationController.calculateDate(
rule,
holidays,
DTNEG
),
VALOR: RulesNegotiationController.calculateValueByPercentual(
totalValue.total,
rule?.PERCENTUAL
),
PERCENTUAL: rule?.PERCENTUAL,
FORMA_PAGAMENTO: rule?.SUBTIPOVENDA,
TIPO_DE_TITULO: rule?.CODTIPTITPAD,
AD_SEQVDY: parseInt(rule.SEQUENCIA) || id + 1,
})) || [];
5.3 Ajuste de arredondamento/resíduo (setRestRulesNegotiation)
Após calcular as parcelas, é aplicado um ajuste para que a soma dos VALOR seja igual ao total do pedido com 2 casas.
const total = Number(
result.reduce((acc, row) => acc + row.VALOR, 0)?.toFixed(2)
);
const saleTotal = Number(totalValue?.toFixed(2));
const diff = saleTotal - total;
if (diff > 0 && diff < 1) {
const lastRow = result[result.length - 1];
result[result.length - 1] = {
...lastRow,
VALOR: lastRow.VALOR + Number(diff.toFixed(2)),
};
} else if (diff < 0) {
const lastRow = result[result.length - 1];
result[result.length - 1] = {
...lastRow,
VALOR: lastRow.VALOR + Number(diff.toFixed(2)),
};
}
Ao final, as linhas são gravadas no state via setRulesNegotiationRow(result).
Regra 6 — Redistribuição do total quando o valor do pedido muda
Quando o total é alterado (ex.: inclusão/remoção de itens, descontos, frete), é possível recalcular os valores mantendo os percentuais:
export const recalculateValueTotal = (dispatch, rows, totalValue) => {
let result = rows.map((row) => ({
...row,
VALOR: RulesNegotiationController.calculateValueByPercentual(
totalValue,
row.PERCENTUAL
),
}));
result = setRestRulesNegotiation({ list: result, totalValue });
dispatch(setRulesNegotiationRow(result));
};
Regra 7 — Controle de distribuído x restante (getTotalDistributedSelector)
A regra soma VALOR das linhas e compara com totalvalue.total:
distributed: soma do valor distribuídoremaining: saldo restante (total - distributed), com tolerância de centavos
const distributed = rows.reduce((acc, row) => acc + row.VALOR, 0);
const total = Number(totalvalue.total?.toFixed(2));
let remaining = total - distributed;
if (remaining < 0.01) {
remaining = 0;
}
return { distributed, remaining };
Regra 8 — Conversão do UI para payload (getRulesNegotiationBodySelector)
A conversão prepara o corpo para envio, com:
CODTIPTIT(tipo de título)DTVENC(vencimento)VLRDESDOB(valor)AD_SEQVDY(sequência)AD_CARTAO(opcional; usado quando Papel e Cia e forma de pagamento = cartão)
return rows.map((row) => ({
CODTIPTIT: row.TIPO_DE_TITULO?.toString?.(),
DTVENC: row.DT_VENCIMENTO?.toString?.(),
VLRDESDOB: row.VALOR?.toString?.(),
AD_CARTAO: getAD_CARTAO(row),
AD_SEQVDY:
!!row.DESDOBRAMENTO && Number.isFinite(Number(row.DESDOBRAMENTO))
? row.DESDOBRAMENTO.toString()
: !!row.id && Number.isFinite(Number(row.id))
? row.id
: "1",
}));
Regra 9 — Validações de limite de parcelas
9.1 Quantidade máxima de parcelas (UI)
No controller do Financeiro (useFinanceListController), existe validação do limite máximo (rulesNegotiation.NUMAXIMOPARC).
Regras principais:
- calcula quantas parcelas já existem para uma combinação de:
- cartão (
FORMA_PAGAMENTO === '7') +AD_CARTOES - ou forma de pagamento + tipo de título
- cartão (
- impede adicionar/replicar se ultrapassar
NUMAXIMOPARC
Trecho (resumo):
if (quantityParc > Number(NUMAXIMOPARC)) {
toast.dismiss();
toast.error(`Número máximo de parcelas é ${NUMAXIMOPARC}`);
return false;
}
9.2 Quantidade mínima de parcelas (regra por tipo de título)
Implementada em RulesNegotiationController.isValidMINPARC.
Contextos em que a validação é executada (controle por permissões/config):
if (
(permissions.permissionByCompany?.isPapelAndCia?.() ||
permissions?.permissionByCompany?.isNatuzzi() ||
permissions?.UTILFINANC === "S") &&
atualFin?.toString?.() !== "0"
) {
// ...
}
Como a validação agrupa:
- por
CODTIPTIT - e (quando aplicável) por
AD_CARTAO
const key = `${parcel?.CODTIPTIT}_${parcel?.AD_CARTAO || "default"}`;
A regra compara a quantidade atual de parcelas do grupo com MINPARC configurado em rulesNegotiationSelected.PARCELAS para o CODTIPTITPAD.
Integração com UI (Financeiro)
Tela/Componente
FinanceList.tsxrenderiza o DataGrid e chamacontroller.setRows(...)conforme edição do usuário.
Controle
useFinanceListControllercentraliza:- estado (
rows) - total do pedido (
getTotalValue) - regra selecionada (
getRulesNegotiationSelector) - distribuição (distribuído/restante)
- ações (reset, distribuir, adicionar, remover, replicar)
- estado (
Inventário de usos (referências)
-
src/controllers/sale/create/rules-negotiation.sales.create.controller.tsRulesNegotiationController.getRulesNegotiationSelectorRulesNegotiationController.getPaymentMethodsSelectorRulesNegotiationController.calculateDateRulesNegotiationController.setRulesNegotiationRulesNegotiationController.setRestRulesNegotiationRulesNegotiationController.recalculateValueTotalRulesNegotiationController.getTotalDistributedSelectorRulesNegotiationController.getRulesNegotiationBodySelectorRulesNegotiationController.isValidMINPARC
-
src/screens/nova-venda/components/Finance/FinanceListController.tsxuseFinanceListController
-
src/screens/nova-venda/components/Finance/FinanceList.tsx- DataGrid e edição de parcelas
-
src/store/sale-create.slice.tsrulesNegotiationRowssetRulesNegotiationRow