feat(politicas): regras de desconto por grupo/subgrupo/rep e condicoes comerciais no catalogo

- Nova tabela sar.regras_desconto (grupo, subgrupo, representante, % e
  vigencia; campos nulos = todos) com migration idempotente, espelho no
  sar-erp-schema.sql e GRANT no provision-client
- CRUD manager-only em /politicas/regras + aba "Regras de Desconto" na
  tela de Politicas Comerciais (selects com nomes de rep/grupo/subgrupo
  via /politicas/grupos-produto)
- GET /politicas/regras/vigentes (rep ve so as que o alcancam) e
  GET /politicas/promocoes/vigentes para o catalogo
- Regra vigente pre-aplica o desconto no carrinho do novo pedido
- Selos "Promocao"/"Cond. especial" na busca de produto, catalogo do
  pedido e pagina de catalogo; secao "Condicoes Comerciais" no modal de
  detalhe do produto (descricao, % e validade)
- Empty imageStyle deprecado -> styles.image no carrinho

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 17:24:21 +00:00
parent 518769218f
commit f543b8ac7f
15 changed files with 1065 additions and 12 deletions

View File

@@ -53,3 +53,92 @@ export const UpdatePromocaoBodySchema = CreatePromocaoBodySchema.partial().exten
ativa: z.boolean().optional(),
});
export type UpdatePromocaoBody = z.infer<typeof UpdatePromocaoBodySchema>;
// ─── Regras de Desconto ───────────────────────────────────────────────────────
// Regra criada pelo Gerente: % de desconto liberado por grupo e/ou subgrupo de
// produto e/ou representante, com vigência. Campos nulos = "todos".
export const RegraDescontoSchema = z.object({
id: z.number().int(),
descricao: z.string(),
codGrupo: z.number().int().nullable(),
grupo: z.string().nullable(),
codSubgrupo: z.number().int().nullable(),
subgrupo: z.string().nullable(),
codVendedor: z.number().int().nullable(),
nomeVendedor: z.string().nullable(),
descPct: z.number(),
dataInicio: z.string(),
dataFim: z.string(),
ativa: z.boolean(),
createdAt: z.string(),
createdBy: z.string(),
});
export type RegraDesconto = z.infer<typeof RegraDescontoSchema>;
export const RegrasDescontoResponseSchema = z.object({
regras: z.array(RegraDescontoSchema),
});
export type RegrasDescontoResponse = z.infer<typeof RegrasDescontoResponseSchema>;
export const CreateRegraDescontoBodySchema = z.object({
descricao: z.string().min(1).max(200),
codGrupo: z.number().int().nullable().optional(),
codSubgrupo: z.number().int().nullable().optional(),
codVendedor: z.number().int().nullable().optional(),
descPct: z.number().min(0).max(100),
dataInicio: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
dataFim: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});
export type CreateRegraDescontoBody = z.infer<typeof CreateRegraDescontoBodySchema>;
export const UpdateRegraDescontoBodySchema = CreateRegraDescontoBodySchema.partial().extend({
ativa: z.boolean().optional(),
});
export type UpdateRegraDescontoBody = z.infer<typeof UpdateRegraDescontoBodySchema>;
// Promoções vigentes hoje — visíveis ao rep para sinalizar condição no catálogo
export const PromocaoVigenteSchema = z.object({
id: z.number().int(),
descricao: z.string(),
codProduto: z.string().nullable(),
grpProd: z.string().nullable(),
descPct: z.number(),
dataFim: z.string(),
});
export type PromocaoVigente = z.infer<typeof PromocaoVigenteSchema>;
export const PromocoesVigentesResponseSchema = z.object({
promocoes: z.array(PromocaoVigenteSchema),
});
export type PromocoesVigentesResponse = z.infer<typeof PromocoesVigentesResponseSchema>;
// Regras vigentes para o representante logado — aplicadas na montagem do pedido
export const RegraVigenteSchema = z.object({
id: z.number().int(),
descricao: z.string(),
codGrupo: z.number().int().nullable(),
codSubgrupo: z.number().int().nullable(),
descPct: z.number(),
dataFim: z.string(),
});
export type RegraVigente = z.infer<typeof RegraVigenteSchema>;
export const RegrasVigentesResponseSchema = z.object({
regras: z.array(RegraVigenteSchema),
});
export type RegrasVigentesResponse = z.infer<typeof RegrasVigentesResponseSchema>;
// Grupos/subgrupos de produto (combos do formulário do gerente)
export const GrupoProdutoItemSchema = z.object({
codGrupo: z.number().int().nullable(),
grupo: z.string().nullable(),
codSubgrupo: z.number().int().nullable(),
subgrupo: z.string().nullable(),
});
export type GrupoProdutoItem = z.infer<typeof GrupoProdutoItemSchema>;
export const GruposProdutoResponseSchema = z.object({
grupos: z.array(GrupoProdutoItemSchema),
});
export type GruposProdutoResponse = z.infer<typeof GruposProdutoResponseSchema>;