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

@@ -4,6 +4,8 @@ import { EyeOutlined } from '@ant-design/icons';
import type { TableColumnsType } from 'antd';
import type { ProdutoSummary } from '@sar/api-interface';
import { useCatalog, usePautas } from '../../lib/queries/catalog';
import { useCondicoesComerciais, type CondicaoComercial } from '../../lib/condicoes-comerciais';
import { CondicaoTag } from '../../components/CondicaoTag';
import { ProductDetailDrawer } from './ProductDetailDrawer';
const { useBreakpoint } = Grid;
@@ -16,7 +18,10 @@ function fmtPrice(v: string | null | undefined): string {
return n > 0 ? n.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }) : '—';
}
function buildColumns(onDetail: (id: number) => void): TableColumnsType<ProdutoSummary> {
function buildColumns(
onDetail: (id: number) => void,
condicoesDe: (p: ProdutoSummary) => CondicaoComercial[],
): TableColumnsType<ProdutoSummary> {
return [
{
title: 'Código',
@@ -29,7 +34,18 @@ function buildColumns(onDetail: (id: number) => void): TableColumnsType<ProdutoS
dataIndex: 'descricao',
render: (v: string, row: ProdutoSummary) => (
<div>
<div style={{ fontWeight: 500 }}>{v.trim()}</div>
<div
style={{
fontWeight: 500,
display: 'flex',
alignItems: 'center',
gap: 6,
flexWrap: 'wrap',
}}
>
{v.trim()}
<CondicaoTag conds={condicoesDe(row)} compact />
</div>
{row.grupo && <div style={{ fontSize: 12, color: '#888' }}>{row.grupo.trim()}</div>}
</div>
),
@@ -101,8 +117,9 @@ export function CatalogPage() {
const { data: pautas, isLoading: pautasLoading } = usePautas();
const { data, isLoading } = useCatalog({ q: q || undefined, idPauta, page, limit });
const condicoesDe = useCondicoesComerciais();
const columns = buildColumns((id) => setSelectedIdErp(id));
const columns = buildColumns((id) => setSelectedIdErp(id), condicoesDe);
return (
<div style={{ maxWidth: 1400, margin: '0 auto' }}>