feat(web+api): catálogo com detalhe do produto, preços por pauta e consulta de pedidos ERP

- Catálogo: modal de detalhe centralizado (1100px) com identificação, estoque,
  preços base e lista de preços por pauta do representante (DISTINCT para evitar duplicatas)
- Contrato: loteMulVenda promovido para ProdutoSummarySchema; PautaPrecoSchema adicionado
- Pedido novo: valida lote múltiplo de venda — qtd inicial = lote, step = lote,
  InputNumber em erro quando inválido, alerta e bloqueio do submit
- Consulta ERP: tela OrdersErpPage com listagem de pedidos do ERP (P/E), contrato
  PedidoErpConsulta, endpoint no orders.controller, rota e sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 20:27:19 +00:00
parent 0858e1e941
commit a2bab75bad
15 changed files with 1804 additions and 492 deletions

View File

@@ -153,3 +153,46 @@ export const RecusarPedidoSchema = z.object({
motivo: z.string().min(1),
});
export type RecusarPedido = z.infer<typeof RecusarPedidoSchema>;
// ─── ERP Consulta ─────────────────────────────────────────────────────────────
export const PedidoErpConsultaItemSchema = z.object({
numeroPedido: z.number().int(),
data: z.string(),
dtPrevent: z.string().nullable(),
dataEmissao: z.string().nullable(),
situa: z.number().int(),
statusDescr: z.string().nullable(),
idCliente: z.number().int(),
nomeCliente: z.string().nullable(),
razaoCliente: z.string().nullable(),
codVendedor: z.number().int(),
formaPagamento: z.string().nullable(),
total: z.string(),
obs: z.string().nullable(),
tipo: z.enum(['P', 'E']),
numPedSar: z.string().nullable(),
numeroEntrega: z.number().int().nullable(),
numeroNf: z.number().int().nullable(),
chaveAcessoNfe: z.string().nullable(),
sitPed: z.number().int().nullable(),
idPedido: z.number().int().nullable(),
});
export type PedidoErpConsultaItem = z.infer<typeof PedidoErpConsultaItemSchema>;
export const PedidoErpConsultaQuerySchema = z.object({
search: z.string().optional(),
from: z.string().optional(),
to: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(200).default(50),
});
export type PedidoErpConsultaQuery = z.infer<typeof PedidoErpConsultaQuerySchema>;
export const PedidoErpConsultaResponseSchema = z.object({
data: z.array(PedidoErpConsultaItemSchema),
total: z.number().int().nonnegative(),
page: z.number().int().positive(),
limit: z.number().int().positive(),
});
export type PedidoErpConsultaResponse = z.infer<typeof PedidoErpConsultaResponseSchema>;

View File

@@ -20,6 +20,7 @@ export const ProdutoSummarySchema = z.object({
ativo: z.number().int(),
qtdEstoque: z.string().nullable(),
listaParauta: z.number().int().nullable(),
loteMulVenda: z.number().int().nullable(),
});
export const PautaSchema = z.object({
@@ -42,6 +43,14 @@ export type ProdutoSummary = z.infer<typeof ProdutoSummarySchema>;
// ─── Produto Detail ───────────────────────────────────────────────────────────
export const PautaPrecoSchema = z.object({
idPauta: z.number().int(),
codigo: z.number().int(),
descricao: z.string(),
preco: z.string(),
});
export type PautaPreco = z.infer<typeof PautaPrecoSchema>;
export const ProdutoDetailSchema = ProdutoSummarySchema.extend({
referencia: z.string().nullable(),
descricaoDetalhada: z.string().nullable(),
@@ -50,9 +59,9 @@ export const ProdutoDetailSchema = ProdutoSummarySchema.extend({
aliqIpi: z.string().nullable(),
pesoLiquido: z.string().nullable(),
qtdVolume: z.string().nullable(),
loteMulVenda: z.number().int().nullable(),
precoComIpi: z.string().nullable(),
precoPromocional: z.string().nullable(),
pautaPrecos: z.array(PautaPrecoSchema),
});
export type ProdutoDetail = z.infer<typeof ProdutoDetailSchema>;