feat(api,web): alcada bloqueia ja a criacao do orcamento, nao so a transmissao
- create() valida desconto global e por item (mesma regra do transmit):
Concluir com desconto acima do teto retorna 400 com a mensagem do
item que passou; transmit segue validando (defesa em profundidade)
- Botao Concluir desabilita em tempo real com o motivo quando algum
item esta acima do teto; caminho offline tambem barrado no submit
- Mensagem de alcada generalizada ("para continuar")
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1346,30 +1346,36 @@ export function NewOrderPage() {
|
||||
})();
|
||||
const hasClientEnd = !!clientEndStr;
|
||||
|
||||
// Teto de desconto por item (alçada + promoção/regra) — aviso em tempo real
|
||||
const tetoDesconto = useTetoDesconto();
|
||||
const tetoDe = (it: CartItem) =>
|
||||
tetoDesconto({ codigo: it.codProduto, codGrupo: it.codGrupo, codSubgrupo: it.codSubgrupo });
|
||||
|
||||
const totalPedido = cart.reduce((acc, it) => acc + itemTotal(it), 0);
|
||||
const loteErrors = cart.filter((it) => {
|
||||
const lote = it.loteMulVenda ?? 1;
|
||||
return lote > 1 && it.qtd % lote !== 0;
|
||||
});
|
||||
const canSubmit = !!effectiveClient && cart.length > 0 && loteErrors.length === 0;
|
||||
// Desconto acima do teto bloqueia o Concluir — o backend recusa a criação
|
||||
// do orçamento de qualquer forma; aqui a mensagem chega antes do clique.
|
||||
const tetoErrors = cart.filter((it) => it.descontoPerc > tetoDe(it) + 0.001);
|
||||
const canSubmit =
|
||||
!!effectiveClient && cart.length > 0 && loteErrors.length === 0 && tetoErrors.length === 0;
|
||||
const missingReason = !effectiveClient
|
||||
? 'Selecione um cliente para continuar'
|
||||
: cart.length === 0
|
||||
? 'Adicione ao menos um produto ao pedido'
|
||||
: loteErrors.length > 0
|
||||
? 'Corrija as quantidades com lote múltiplo'
|
||||
: undefined;
|
||||
: tetoErrors.length > 0
|
||||
? `Desconto acima do teto em: ${tetoErrors.map((it) => it.codProduto.trim()).join(', ')}`
|
||||
: undefined;
|
||||
|
||||
function clearClient() {
|
||||
setSelectedClient(null);
|
||||
setClientSearch('');
|
||||
}
|
||||
|
||||
// Teto de desconto por item (alçada + promoção/regra) — aviso em tempo real
|
||||
const tetoDesconto = useTetoDesconto();
|
||||
const tetoDe = (it: CartItem) =>
|
||||
tetoDesconto({ codigo: it.codProduto, codGrupo: it.codGrupo, codSubgrupo: it.codSubgrupo });
|
||||
|
||||
// ── Handlers do carrinho ──
|
||||
// Item entra SEM desconto mesmo quando há promoção/regra vigente (selo
|
||||
// "Condição comercial" avisa o rep): a proposta é carta de negociação — o rep
|
||||
@@ -1451,6 +1457,13 @@ export function NewOrderPage() {
|
||||
`Quantidade inválida: ${invalidos.map((it) => `${it.codProduto.trim()} (lote múltiplo: ${it.loteMulVenda})`).join(', ')}`,
|
||||
);
|
||||
}
|
||||
// Cobre também o caminho offline (enqueueOrder não passa pelo backend)
|
||||
const acimaTeto = cart.filter((it) => it.descontoPerc > tetoDe(it) + 0.001);
|
||||
if (acimaTeto.length > 0) {
|
||||
throw new Error(
|
||||
`Desconto acima do teto: ${acimaTeto.map((it) => `${it.codProduto.trim()} (máx. ${tetoDe(it)}%)`).join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const obsCompleta = [
|
||||
contato ? `Contato: ${contato}` : null,
|
||||
|
||||
Reference in New Issue
Block a user