Pular para o conteúdo principal

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

ArquivoLocalizaçãoResponsabilidade
rules-negotiation.sales.create.controller.tssrc/controllers/sale/create/Regras/algoritmos de negociação (seletores, cálculo de data, distribuição, validações, payload)
FinanceListController.tsxsrc/screens/nova-venda/components/Finance/Controller do UI: edição/redistribuição/validações e integrações com Redux
FinanceList.tsxsrc/screens/nova-venda/components/Finance/Grid do Financeiro (DataGrid): edição de vencimento/valor/%/formas de pagamento
sale-create.slice.tssrc/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/ordem
  • PRAZO: prazo em dias para vencimento
  • PERCENTUAL: percentual do valor do pedido
  • VENCNAOUTIL: comportamento quando vencer em não-útil
  • SUBTIPOVENDA: forma de pagamento
  • CODTIPTITPAD: tipo de título
  • DESCRSUBTIPOVENDA/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ído
  • remaining: 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
  • 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.tsx renderiza o DataGrid e chama controller.setRows(...) conforme edição do usuário.

Controle

  • useFinanceListController centraliza:
    • estado (rows)
    • total do pedido (getTotalValue)
    • regra selecionada (getRulesNegotiationSelector)
    • distribuição (distribuído/restante)
    • ações (reset, distribuir, adicionar, remover, replicar)

Inventário de usos (referências)

  • src/controllers/sale/create/rules-negotiation.sales.create.controller.ts

    • RulesNegotiationController.getRulesNegotiationSelector
    • RulesNegotiationController.getPaymentMethodsSelector
    • RulesNegotiationController.calculateDate
    • RulesNegotiationController.setRulesNegotiation
    • RulesNegotiationController.setRestRulesNegotiation
    • RulesNegotiationController.recalculateValueTotal
    • RulesNegotiationController.getTotalDistributedSelector
    • RulesNegotiationController.getRulesNegotiationBodySelector
    • RulesNegotiationController.isValidMINPARC
  • src/screens/nova-venda/components/Finance/FinanceListController.tsx

    • useFinanceListController
  • src/screens/nova-venda/components/Finance/FinanceList.tsx

    • DataGrid e edição de parcelas
  • src/store/sale-create.slice.ts

    • rulesNegotiationRows
    • setRulesNegotiationRow