feat(web,api): teto de desconto em tempo real no carrinho do pedido

- GET /politicas/alcada: limites de desconto do proprio usuario logado
- Carrinho (desktop e mobile) mostra "teto: X%" sob o campo de desconto
  de cada item - max(alcada do grupo, promocao, regra vigente), espelho
  do assertAlcadaItens; acima do teto o campo fica vermelho com aviso de
  que a transmissao sera bloqueada

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 18:46:58 +00:00
parent 43daf7ad6d
commit 673130d60b
6 changed files with 143 additions and 25 deletions

View File

@@ -24,6 +24,7 @@ import {
type AlcadaDescontosResponse,
type PromocoesResponse,
type PromocoesVigentesResponse,
type AlcadaRepResponse,
type RegrasDescontoResponse,
type RegrasVigentesResponse,
type GruposProdutoResponse,
@@ -61,6 +62,11 @@ export class PoliticasController {
return this.politicas.listPromocoesVigentes();
}
@Get('alcada')
minhaAlcada(): Promise<AlcadaRepResponse> {
return this.politicas.minhaAlcada();
}
@Post('promocoes')
createPromocao(@Body() body: CreatePromocaoDto): Promise<{ id: number }> {
return this.politicas.createPromocao(body as unknown as CreatePromocaoBody);

View File

@@ -11,6 +11,7 @@ import type {
UpdateRegraDescontoBody,
RegrasVigentesResponse,
PromocoesVigentesResponse,
AlcadaRepResponse,
GruposProdutoResponse,
} from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
@@ -315,6 +316,25 @@ export class PoliticasService {
await prisma.regraDesconto.delete({ where: { id } });
}
// Alçada do próprio usuário logado — o carrinho usa para mostrar o teto de
// desconto por item (codGrupo 0 = default; sem linha 0, o backend assume 5%).
async minhaAlcada(): Promise<AlcadaRepResponse> {
const prisma = this.cls.get('prisma');
if (!prisma) throw new Error('prisma não disponível no CLS');
const idEmpresa = this.cls.get('idEmpresa');
const codVendedor = parseInt(this.cls.get('userId') ?? '0', 10);
const rows = await prisma.alcadaDesconto.findMany({
where: { codVendedor, idEmpresa },
});
return {
limites: rows.map((r) => ({
codGrupo: r.codGrupo,
limitePerc: Number(r.limitePerc),
})),
};
}
// Promoções vigentes hoje — qualquer usuário autenticado. Usadas pelo rep para
// sinalizar no catálogo que o produto tem condição comercial diferenciada.
async listPromocoesVigentes(): Promise<PromocoesVigentesResponse> {