feat(web+api): cockpit rep — novo pedido v2, endereço de entrega e cancelamento de orçamento

- NewOrderPage: etapas 1-5 — merge de cards, busca de produto melhorada
  (autocomplete com estoque + modal catálogo), responsividade mobile (tabela →
  cards, footer compacto), histórico de compras do cliente e campo de
  endereço de entrega (cadastrado vs livre)
- endEntrega: campo TEXT adicionado em sar.pedidos; contrato CreatePedidoSchema
  e PedidoDetailSchema atualizados; aparece no detalhe (OrderDetailPage,
  OrderDetailModal) e no PDF (OrderPrintPage)
- Cancelamento de orçamento: PATCH /orders/:id/cancel — rep cancela seu
  próprio orçamento (situa 0); histórico registrado; UI com Modal.confirm
  substituindo alert() em OrderActionsMenu
- Fix: useClientDetail chamado para cliente selecionado manualmente, garantindo
  que clientEndStr seja preenchido mesmo sem clientIdParam na URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 14:25:24 +00:00
parent 5bda00009f
commit a6cb1df2aa
10 changed files with 1181 additions and 202 deletions

View File

@@ -45,6 +45,7 @@ model Pedido {
comissao Decimal @default(0) @db.Decimal(15, 2) comissao Decimal @default(0) @db.Decimal(15, 2)
pedFlex Decimal @default(0) @db.Decimal(15, 2) @map("ped_flex") pedFlex Decimal @default(0) @db.Decimal(15, 2) @map("ped_flex")
obs String? obs String?
endEntrega String? @map("end_entrega")
aprovadoPor Int? @map("aprovado_por") aprovadoPor Int? @map("aprovado_por")
aprovadoEm DateTime? @map("aprovado_em") aprovadoEm DateTime? @map("aprovado_em")
motivoRecusa String? @map("motivo_recusa") motivoRecusa String? @map("motivo_recusa")

View File

@@ -439,6 +439,7 @@ export class ClientsService {
interface Row { interface Row {
id_produto: number; id_produto: number;
codigo: string;
descricao: string; descricao: string;
unidade: string | null; unidade: string | null;
qtd_total: string; qtd_total: string;
@@ -449,37 +450,39 @@ export class ClientsService {
const rows = await prisma.$queryRawUnsafe<Row[]>(` const rows = await prisma.$queryRawUnsafe<Row[]>(`
SELECT SELECT
i.produ AS id_produto, i.id_produto,
TRIM(p.codigo) AS codigo,
TRIM(p.descricao) AS descricao, TRIM(p.descricao) AS descricao,
TRIM(p.unidade) AS unidade, TRIM(p.unidade) AS unidade,
SUM(i.qtd)::numeric(15,3)::text AS qtd_total, SUM(i.qtd)::numeric(15,3)::text AS qtd_total,
COUNT(DISTINCT ped.id_pedido)::text AS num_pedidos, COUNT(DISTINCT ped.id_pedido)::text AS num_pedidos,
MAX(ped.data) AS ultima_compra, MAX(ped.dt_pedido) AS ultima_compra,
( (
SELECT i2.pruni::numeric(15,2)::text SELECT i2.preco_unitario::numeric(15,2)::text
FROM sig.peditens i2 FROM sar.vw_peditens_erp i2
JOIN sig.pedidos p2 ON p2.id_pedido = i2.id_pedido JOIN sar.vw_pedidos_erp p2 ON p2.id_pedido = i2.id_pedido
WHERE i2.produ = i.produ WHERE i2.id_produto = i.id_produto
AND p2.clien = ped.clien AND p2.id_cliente = ped.id_cliente
AND p2.id_empresa = ${idEmpresa} AND p2.id_empresa = ${idEmpresa}
AND p2.situa NOT IN (5) AND p2.situa NOT IN (5)
ORDER BY p2.data DESC, p2.id_pedido DESC ORDER BY p2.dt_pedido DESC, p2.id_pedido DESC
LIMIT 1 LIMIT 1
) AS ultimo_preco ) AS ultimo_preco
FROM sig.pedidos ped FROM sar.vw_pedidos_erp ped
JOIN sig.peditens i ON i.id_pedido = ped.id_pedido JOIN sar.vw_peditens_erp i ON i.id_pedido = ped.id_pedido
JOIN gestao.produto p ON p.id_erp = i.produ JOIN sar.vw_produtos p ON p.id_erp = i.id_produto
AND p.id_empresa = ${idEmpresaMatriz} AND p.id_empresa = ${idEmpresaMatriz}
WHERE ped.clien = ${idCliente} WHERE ped.id_cliente = ${idCliente}
AND ped.id_empresa = ${idEmpresa} AND ped.id_empresa = ${idEmpresa}
AND ped.situa NOT IN (5) AND ped.situa NOT IN (5)
GROUP BY i.produ, p.descricao, p.unidade, ped.clien GROUP BY i.id_produto, p.descricao, p.unidade, ped.id_cliente
ORDER BY SUM(i.qtd) DESC ORDER BY SUM(i.qtd) DESC
LIMIT ${limit} LIMIT ${limit}
`); `);
return rows.map((r) => ({ return rows.map((r) => ({
idProduto: Number(r.id_produto), idProduto: Number(r.id_produto),
codProduto: r.codigo ?? '',
descricao: r.descricao, descricao: r.descricao,
unidade: r.unidade, unidade: r.unidade,
qtdTotal: parseFloat(r.qtd_total ?? '0'), qtdTotal: parseFloat(r.qtd_total ?? '0'),
@@ -646,12 +649,12 @@ export class ClientsService {
nf.dt_emissao, nf.dt_emissao,
nf.vl_nf::text AS vl_nf, nf.vl_nf::text AS vl_nf,
TRIM(nf.chave_acesso_nfe) AS chave_acesso_nfe TRIM(nf.chave_acesso_nfe) AS chave_acesso_nfe
FROM gestao.nf nf FROM sar.vw_nf nf
JOIN sig.pedidos p JOIN sar.vw_pedidos_erp p
ON p.numero = nf.num_entrega ON p.numero = nf.num_entrega
AND p.tipo = 'E' AND p.tipo = 'E'
AND p.id_empresa IN (1, 9001) AND p.id_empresa IN (1, 9001)
WHERE p.clien = $1 WHERE p.id_cliente = $1
AND nf.id_empresa = 1 AND nf.id_empresa = 1
AND nf.status = 'E' AND nf.status = 'E'
AND TRIM(COALESCE(nf.chave_acesso_nfe, '')) != '' AND TRIM(COALESCE(nf.chave_acesso_nfe, '')) != ''

View File

@@ -84,6 +84,11 @@ export class OrdersController {
return this.orders.reject(id, parsed); return this.orders.reject(id, parsed);
} }
@Patch(':id/cancel')
cancel(@Param('id', ParseUUIDPipe) id: string): Promise<PedidoDetail> {
return this.orders.cancel(id);
}
@Get('erp/:idPedido') @Get('erp/:idPedido')
findOneErp(@Param('idPedido', ParseIntPipe) idPedido: number): Promise<PedidoDetail> { findOneErp(@Param('idPedido', ParseIntPipe) idPedido: number): Promise<PedidoDetail> {
return this.orders.findOneErp(idPedido); return this.orders.findOneErp(idPedido);

View File

@@ -128,10 +128,10 @@ export class OrdersService {
const dataHistoricoStr = dataHistorico.toISOString().split('T')[0]; const dataHistoricoStr = dataHistorico.toISOString().split('T')[0];
const vendedorFilter = role === 'rep' ? `AND a.cod_vendedor = ${codVendedor}` : ''; const vendedorFilter = role === 'rep' ? `AND a.cod_vendedor = ${codVendedor}` : '';
const fromFilterE = from ? `AND COALESCE(a.data, b.data) >= '${from}'` : ''; const fromFilterE = from ? `AND COALESCE(a.dt_pedido, b.dt_pedido) >= '${from}'` : '';
const toFilterE = to ? `AND COALESCE(a.data, b.data) <= '${to}'` : ''; const toFilterE = to ? `AND COALESCE(a.dt_pedido, b.dt_pedido) <= '${to}'` : '';
const fromFilterP = from ? `AND a.data >= '${from}'` : ''; const fromFilterP = from ? `AND a.dt_pedido >= '${from}'` : '';
const toFilterP = to ? `AND a.data <= '${to}'` : ''; const toFilterP = to ? `AND a.dt_pedido <= '${to}'` : '';
const safe = (s: string) => s.replace(/'/g, "''"); const safe = (s: string) => s.replace(/'/g, "''");
const searchFilter = search const searchFilter = search
? `AND (r.numero_pedido::text ILIKE '%${safe(search)}%' OR LOWER(cl.nome) LIKE LOWER('%${safe(search)}%') OR LOWER(cl.razao) LIKE LOWER('%${safe(search)}%'))` ? `AND (r.numero_pedido::text ILIKE '%${safe(search)}%' OR LOWER(cl.nome) LIKE LOWER('%${safe(search)}%') OR LOWER(cl.razao) LIKE LOWER('%${safe(search)}%'))`
@@ -164,14 +164,14 @@ export class OrdersService {
WITH all_rows AS ( WITH all_rows AS (
SELECT SELECT
b.numero AS numero_pedido, b.numero AS numero_pedido,
COALESCE(b.data, a.data) AS data, COALESCE(b.dt_pedido, a.dt_pedido) AS data,
a.dtprevent AS dt_prevent, a.dt_prevent,
a.data_emissao, a.dt_emissao AS data_emissao,
a.situa, a.situa,
NULL::text AS status_descr, NULL::text AS status_descr,
a.clien::integer AS id_cliente, a.id_cliente,
a.cod_vendedor, a.cod_vendedor,
TRIM(c.descr) AS forma_pagamento, a.forma_pagamento,
a.total::text, a.total::text,
a.obs, a.obs,
'E'::varchar(1) AS tipo, 'E'::varchar(1) AS tipo,
@@ -181,36 +181,33 @@ export class OrdersService {
d.chave_acesso_nfe, d.chave_acesso_nfe,
b.situa AS sit_ped, b.situa AS sit_ped,
a.id_pedido a.id_pedido
FROM sig.pedidos a FROM sar.vw_pedidos_erp a
LEFT JOIN sig.pedidos b LEFT JOIN sar.vw_pedidos_erp b
ON a.numer_ped_vinc = b.numero ON a.numer_ped_vinc = b.numero
AND a.id_empresa = b.id_empresa AND a.id_empresa = b.id_empresa
AND b.tipo = 'P' AND b.tipo = 'P'
LEFT JOIN gestao.formapag c LEFT JOIN sar.vw_nf d
ON c.id_empresa = ${idMatriz}
AND a.cod_formapag = c.codigo
LEFT JOIN gestao.nf d
ON a.numero = d.num_entrega ON a.numero = d.num_entrega
AND d.id_empresa = ${idMatriz} AND d.id_empresa = ${idMatriz}
WHERE a.id_empresa = ${idEmpresa} WHERE a.id_empresa = ${idEmpresa}
AND a.tipo = 'E' AND a.tipo = 'E'
${vendedorFilter} ${vendedorFilter}
AND b.id_pedido IS NOT NULL AND b.id_pedido IS NOT NULL
AND COALESCE(a.data, b.data) >= '${dataHistoricoStr}' AND COALESCE(a.dt_pedido, b.dt_pedido) >= '${dataHistoricoStr}'
${fromFilterE} ${toFilterE} ${fromFilterE} ${toFilterE}
UNION ALL UNION ALL
SELECT SELECT
a.numero AS numero_pedido, a.numero AS numero_pedido,
a.data, a.dt_pedido AS data,
a.dtprevent AS dt_prevent, a.dt_prevent,
a.data_emissao, a.dt_emissao AS data_emissao,
a.situa, a.situa,
c.descricao AS status_descr, c.descricao AS status_descr,
a.clien::integer AS id_cliente, a.id_cliente,
a.cod_vendedor, a.cod_vendedor,
TRIM(b.descr) AS forma_pagamento, a.forma_pagamento,
a.total::text, a.total::text,
a.obs, a.obs,
'P'::varchar(1) AS tipo, 'P'::varchar(1) AS tipo,
@@ -220,10 +217,7 @@ export class OrdersService {
NULL::varchar AS chave_acesso_nfe, NULL::varchar AS chave_acesso_nfe,
NULL::integer AS sit_ped, NULL::integer AS sit_ped,
a.id_pedido a.id_pedido
FROM sig.pedidos a FROM sar.vw_pedidos_erp a
LEFT JOIN gestao.formapag b
ON b.id_empresa = 1
AND a.cod_formapag = b.codigo
LEFT JOIN vw_sitpedido c LEFT JOIN vw_sitpedido c
ON c.id_sitpedido = a.situa ON c.id_sitpedido = a.situa
WHERE a.id_empresa = ${idEmpresa} WHERE a.id_empresa = ${idEmpresa}
@@ -369,6 +363,7 @@ export class OrdersService {
descontoPerc: dto.descontoPerc, descontoPerc: dto.descontoPerc,
descontoValor: descontoValorGlobal, descontoValor: descontoValorGlobal,
obs: dto.obs ?? null, obs: dto.obs ?? null,
endEntrega: dto.endEntrega ?? null,
idempotencyKey: dto.idempotencyKey ?? null, idempotencyKey: dto.idempotencyKey ?? null,
itens: { create: itemsData }, itens: { create: itemsData },
historico: { historico: {
@@ -552,6 +547,44 @@ export class OrdersService {
return this.mapDetail(final); return this.mapDetail(final);
} }
async cancel(id: string): Promise<PedidoDetail> {
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 userId = this.cls.get('userId') ?? '0';
const codVendedor = parseInt(userId, 10);
// Rep só pode cancelar seu próprio orçamento (situa 0).
const pedido = await prisma.pedido.findFirst({ where: { id, idEmpresa, codVendedor } });
if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
if (pedido.situa !== SITUA_ORCAMENTO)
throw new BadRequestException('Apenas orçamentos podem ser cancelados pelo representante');
const now = new Date();
await prisma.pedido.update({ where: { id }, data: { situa: SITUA_CANCELADO } });
await prisma.historicoPedido.create({
data: {
idPedido: id,
situaAnterior: SITUA_ORCAMENTO,
situaNova: SITUA_CANCELADO,
changedBy: codVendedor,
changedAt: now,
nota: 'Cancelado pelo representante',
},
});
const final = await prisma.pedido.findUniqueOrThrow({
where: { id },
include: {
itens: { orderBy: { ordem: 'asc' } },
historico: { orderBy: { changedAt: 'asc' } },
},
});
return this.mapDetail(final);
}
// Resolve nome do cliente (nome + razão) e nome do representante a partir dos // Resolve nome do cliente (nome + razão) e nome do representante a partir dos
// códigos, lendo das views do ERP. Usado no detalhe de pedidos SAR-nativos. // códigos, lendo das views do ERP. Usado no detalhe de pedidos SAR-nativos.
private async lookupNames( private async lookupNames(
@@ -602,6 +635,7 @@ export class OrdersService {
aprovadoEm: Date | null; aprovadoEm: Date | null;
motivoRecusa: string | null; motivoRecusa: string | null;
obs: string | null; obs: string | null;
endEntrega: string | null;
idempotencyKey: string | null; idempotencyKey: string | null;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -639,6 +673,7 @@ export class OrdersService {
total: decimalToString(o.total), total: decimalToString(o.total),
descontoPerc: decimalToString(o.descontoPerc), descontoPerc: decimalToString(o.descontoPerc),
obs: o.obs, obs: o.obs,
endEntrega: o.endEntrega,
createdAt: o.createdAt.toISOString(), createdAt: o.createdAt.toISOString(),
totalProdutos: decimalToString(o.totalProdutos), totalProdutos: decimalToString(o.totalProdutos),
totalIpi: decimalToString(o.totalIpi), totalIpi: decimalToString(o.totalIpi),

File diff suppressed because it is too large Load Diff

View File

@@ -425,18 +425,23 @@ export function OrderDetailPage() {
)} )}
<Descriptions.Item label="Total produtos">{fmt(order.totalProdutos)}</Descriptions.Item> <Descriptions.Item label="Total produtos">{fmt(order.totalProdutos)}</Descriptions.Item>
<Descriptions.Item label="Desc. Global">{order.descontoPerc}%</Descriptions.Item> <Descriptions.Item label="Desc. Global">{order.descontoPerc}%</Descriptions.Item>
<Descriptions.Item label="Total"> <Descriptions.Item label="Total" span={isOrcamento ? 1 : 2}>
<Text strong style={{ fontSize: 16 }}> <Text strong style={{ fontSize: 16 }}>
{fmt(order.total)} {fmt(order.total)}
</Text> </Text>
</Descriptions.Item> </Descriptions.Item>
{order.endEntrega && (
<Descriptions.Item label="End. Entrega" span={isOrcamento ? 3 : 2}>
{order.endEntrega}
</Descriptions.Item>
)}
{order.obs && ( {order.obs && (
<Descriptions.Item label="Observações" span={2}> <Descriptions.Item label="Observações" span={isOrcamento ? 3 : 2}>
{order.obs} {order.obs}
</Descriptions.Item> </Descriptions.Item>
)} )}
{order.motivoRecusa && ( {order.motivoRecusa && (
<Descriptions.Item label="Motivo Recusa" span={2}> <Descriptions.Item label="Motivo Recusa" span={isOrcamento ? 3 : 2}>
<Text type="danger">{order.motivoRecusa}</Text> <Text type="danger">{order.motivoRecusa}</Text>
</Descriptions.Item> </Descriptions.Item>
)} )}

View File

@@ -399,11 +399,23 @@ export function OrderPrintPage() {
</div> </div>
</div> </div>
{/* ── Observações + rodapé ────────────────────────────────────────── */} {/* ── Endereço de entrega + Observações ───────────────────────────── */}
{order.obs && ( {(order.endEntrega || order.obs) && (
<div style={{ padding: '10px 28px 0' }}> <div style={{ padding: '10px 28px 0', display: 'flex', gap: 24, flexWrap: 'wrap' }}>
<span style={label}>Observações</span> {order.endEntrega && (
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>{order.obs}</div> <div style={{ flex: 1, minWidth: 220 }}>
<span style={label}>Endereço de entrega</span>
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>
{order.endEntrega}
</div>
</div>
)}
{order.obs && (
<div style={{ flex: 1, minWidth: 220 }}>
<span style={label}>Observações</span>
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>{order.obs}</div>
</div>
)}
</div> </div>
)} )}
<div <div

View File

@@ -1,4 +1,5 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { import {
App, App,
Button, Button,
@@ -43,6 +44,7 @@ import { SITUA_LABEL } from '@sar/api-interface';
import { useOrderList, useOrderDetail } from '../../lib/queries/orders'; import { useOrderList, useOrderDetail } from '../../lib/queries/orders';
import { usePendingOrders } from '../../lib/hooks/usePendingOrders'; import { usePendingOrders } from '../../lib/hooks/usePendingOrders';
import { removePendingOrder, retryPendingOrder } from '../../lib/offline/order-queue'; import { removePendingOrder, retryPendingOrder } from '../../lib/offline/order-queue';
import { apiFetch } from '../../lib/api-client';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const { useBreakpoint } = Grid; const { useBreakpoint } = Grid;
@@ -250,6 +252,19 @@ function OrderActionsMenu({
onView: (id: string) => void; onView: (id: string) => void;
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const qc = useQueryClient();
const { modal, message: msg } = App.useApp();
const cancelMutation = useMutation({
mutationFn: () => apiFetch(`/orders/${order.id}/cancel`, { method: 'PATCH' }),
onSuccess: () => {
void msg.success('Orçamento cancelado.');
void qc.invalidateQueries({ queryKey: ['orders'] });
},
onError: (e: unknown) => void msg.error(e instanceof Error ? e.message : 'Erro ao cancelar'),
});
const canCancel = order.situa === 0 && order.fonte !== 'erp';
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
{ {
@@ -287,8 +302,18 @@ function OrderActionsMenu({
icon: <CloseCircleOutlined />, icon: <CloseCircleOutlined />,
label: 'Cancelar pedido', label: 'Cancelar pedido',
danger: true, danger: true,
disabled: order.situa === 3, disabled: !canCancel,
onClick: () => alert('Cancelamento em breve'), onClick: () => {
if (!canCancel) return;
void modal.confirm({
title: 'Cancelar orçamento?',
content: `O pedido ${order.numPedSar} será marcado como cancelado. Esta ação não pode ser desfeita.`,
okText: 'Cancelar pedido',
okButtonProps: { danger: true },
cancelText: 'Voltar',
onOk: () => cancelMutation.mutate(),
});
},
}, },
]; ];
@@ -387,7 +412,7 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
{isLoading && <Spin style={{ display: 'block', margin: '48px auto' }} />} {isLoading && <Spin style={{ display: 'block', margin: '48px auto' }} />}
{data && ( {data && (
<Space direction="vertical" size={20} style={{ width: '100%' }}> <Space orientation="vertical" size={20} style={{ width: '100%' }}>
{/* Status */} {/* Status */}
<div> <div>
<OrderStatusBadge situa={data.situa} descr={data.statusDescr} /> <OrderStatusBadge situa={data.situa} descr={data.statusDescr} />
@@ -434,6 +459,12 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
<span style={label}>Representante</span> <span style={label}>Representante</span>
<Text>{data.nomeVendedor ?? `Cód. ${data.codVendedor}`}</Text> <Text>{data.nomeVendedor ?? `Cód. ${data.codVendedor}`}</Text>
</Col> </Col>
{data.endEntrega && (
<Col span={24}>
<span style={label}>End. Entrega</span>
<Text type="secondary">{data.endEntrega}</Text>
</Col>
)}
{data.obs && ( {data.obs && (
<Col span={24}> <Col span={24}>
<span style={label}>Observações</span> <span style={label}>Observações</span>
@@ -460,7 +491,7 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
title: 'Produto', title: 'Produto',
key: 'produto', key: 'produto',
render: (_: unknown, item) => ( render: (_: unknown, item) => (
<Space direction="vertical" size={0}> <Space orientation="vertical" size={0}>
<Text style={{ fontSize: 11, color: '#94A3B8' }}>{item.codProduto}</Text> <Text style={{ fontSize: 11, color: '#94A3B8' }}>{item.codProduto}</Text>
<Text style={{ fontWeight: 500 }}>{item.descProduto}</Text> <Text style={{ fontWeight: 500 }}>{item.descProduto}</Text>
</Space> </Space>

View File

@@ -115,6 +115,7 @@ export type ContatoResult = z.infer<typeof ContatoResultSchema>;
export const TopProdutoSchema = z.object({ export const TopProdutoSchema = z.object({
idProduto: z.number().int(), idProduto: z.number().int(),
codProduto: z.string(),
descricao: z.string(), descricao: z.string(),
unidade: z.string().nullable(), unidade: z.string().nullable(),
qtdTotal: z.number(), qtdTotal: z.number(),

View File

@@ -88,6 +88,7 @@ export const PedidoDetailSchema = PedidoSummarySchema.extend({
comissao: z.string(), comissao: z.string(),
pedFlex: z.string(), pedFlex: z.string(),
formaPagamento: z.string().nullable().optional(), formaPagamento: z.string().nullable().optional(),
endEntrega: z.string().nullable().optional(),
aprovadoPor: z.number().int().nullable(), aprovadoPor: z.number().int().nullable(),
aprovadoEm: z.iso.datetime().nullable(), aprovadoEm: z.iso.datetime().nullable(),
motivoRecusa: z.string().nullable(), motivoRecusa: z.string().nullable(),
@@ -138,6 +139,7 @@ export const CreatePedidoSchema = z.object({
idPauta: z.number().int().optional(), idPauta: z.number().int().optional(),
codFormapag: z.number().int().optional(), codFormapag: z.number().int().optional(),
obs: z.string().optional(), obs: z.string().optional(),
endEntrega: z.string().optional(),
idempotencyKey: z.string().optional(), idempotencyKey: z.string().optional(),
itens: z.array(CreatePedidoItemSchema).min(1), itens: z.array(CreatePedidoItemSchema).min(1),
}); });