feat(c4): lançamento de pedido — catálogo, alçada por linha, POST /orders

- Prisma: Product + RepDiscountLimit + productCategory em OrderItem + migration
- Seed: 28 produtos (5 categorias) + alçadas user-001 (default 10%, bebidas 8%, perecíveis 5%)
- @sar/api-interface: ProductSummarySchema, ProductDetailSchema, ProductSyncRequestSchema, CreateOrderSchema
- API: CatalogModule (GET /catalog, GET /catalog/:id, POST /catalog/sync)
- API: POST /orders — valida alçada por linha/produto (OQ-2), idempotency-key (FR-4.3), desnorm cliente
- Web: NewOrderPage (3 steps: catálogo → desconto/obs → confirmação)
- Web: botão Novo Pedido na ClientDetailPage (desabilitado se financialStatus=blocked)
- Web: rota /pedidos/novo com search param clientId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 23:45:11 +00:00
parent c36451dd33
commit 6769a0d82a
16 changed files with 1372 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
import { Descriptions, Tag, Table, Typography, Spin, Alert, Space, Divider } from 'antd';
import { Button, Descriptions, Tag, Table, Typography, Spin, Alert, Space, Divider } from 'antd';
import type { TableColumnsType } from 'antd';
import { Link, useParams } from '@tanstack/react-router';
import { Link, useNavigate, useParams } from '@tanstack/react-router';
import type { OrderSummary, OrderStatus } from '@sar/api-interface';
import { useClientDetail } from '../../lib/queries/clients';
import { useClientOrders } from '../../lib/queries/orders';
@@ -77,6 +77,7 @@ const orderColumns: TableColumnsType<OrderSummary> = [
export function ClientDetailPage() {
const { id } = useParams({ from: '/clientes/$id' });
const navigate = useNavigate();
const { data: client, isLoading: clientLoading, error: clientError } = useClientDetail(id);
const { data: orders, isLoading: ordersLoading } = useClientOrders(id);
@@ -88,7 +89,7 @@ export function ClientDetailPage() {
return (
<div style={{ padding: 24 }}>
<Space align="center" style={{ marginBottom: 16 }}>
<Space align="center" style={{ marginBottom: 16 }} wrap>
<Link to="/clientes"> Clientes</Link>
<Title level={3} style={{ margin: 0 }}>
{client.tradeName ?? client.name}
@@ -99,6 +100,13 @@ export function ClientDetailPage() {
<Tag color={ACTIVITY_COLOR[client.activityStatus]}>
{ACTIVITY_LABEL[client.activityStatus]}
</Tag>
<Button
type="primary"
onClick={() => void navigate({ to: '/pedidos/novo', search: { clientId: id } })}
disabled={client.financialStatus === 'blocked'}
>
Novo Pedido
</Button>
</Space>
<Descriptions bordered size="small" column={2} style={{ marginBottom: 24 }}>