diff --git a/apps/api/src/app/orders/orders.controller.ts b/apps/api/src/app/orders/orders.controller.ts index d8f9826..5d966b7 100644 --- a/apps/api/src/app/orders/orders.controller.ts +++ b/apps/api/src/app/orders/orders.controller.ts @@ -5,6 +5,7 @@ import { Get, HttpCode, Param, + ParseIntPipe, ParseUUIDPipe, Patch, Post, @@ -79,6 +80,11 @@ export class OrdersController { return this.orders.reject(id, parsed); } + @Get('erp/:idPedido') + findOneErp(@Param('idPedido', ParseIntPipe) idPedido: number): Promise { + return this.orders.findOneErp(idPedido); + } + @Get(':id') findOne(@Param('id', ParseUUIDPipe) id: string): Promise { return this.orders.findOne(id); diff --git a/apps/api/src/app/orders/orders.service.ts b/apps/api/src/app/orders/orders.service.ts index b6463a5..d29e931 100644 --- a/apps/api/src/app/orders/orders.service.ts +++ b/apps/api/src/app/orders/orders.service.ts @@ -595,4 +595,131 @@ export class OrdersService { })), }; } + + async findOneErp(idPedido: number): Promise { + 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 role = this.cls.get('role'); + const userId = this.cls.get('userId') ?? '0'; + const codVendedor = parseInt(userId, 10); + + interface ErpHeader { + id_pedido: number; + num_ped_sar: string; + numero: number; + id_cliente: number; + cod_vendedor: number; + situa: number; + status_descr: string; + dt_pedido: Date; + total_produtos: string; + total_ipi: string; + total_icmsst: string; + total: string; + desconto_perc: string; + desconto_valor: string; + acrescimo: string; + comissao: string; + ped_flex: string; + obs: string | null; + forma_pagamento: string | null; + nome_cliente: string | null; + razao_cliente: string | null; + nome_vendedor: string | null; + } + + interface ErpItem { + ordem: number; + id_produto: number; + codigo: string | null; + descricao: string | null; + qtd: string; + preco_unitario: string; + desconto_perc: string; + total: string; + } + + const vendedorFilter = role === 'rep' ? `AND e.cod_vendedor = ${codVendedor}` : ''; + const idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa; + + const [headerRows, itemRows] = await Promise.all([ + prisma.$queryRawUnsafe(` + SELECT e.id_pedido, e.num_ped_sar, e.numero, e.id_cliente, e.cod_vendedor, + e.situa, e.status_descr, e.dt_pedido, + e.total_produtos::text, e.total_ipi::text, e.total_icmsst::text, + e.total::text, e.desconto_perc::text, e.desconto_valor::text, + e.acrescimo::text, e.comissao::text, e.ped_flex::text, e.obs, + TRIM(e.forma_pagamento) AS forma_pagamento, + c.nome AS nome_cliente, c.razao AS razao_cliente, + (SELECT r.nome FROM vw_representantes r + WHERE r.codigo = e.cod_vendedor LIMIT 1) AS nome_vendedor + FROM vw_pedidos_erp e + LEFT JOIN vw_clientes c ON c.id_cliente = e.id_cliente + WHERE e.id_empresa = ${idEmpresa} + AND e.id_pedido = ${idPedido} + ${vendedorFilter} + LIMIT 1 + `), + prisma.$queryRawUnsafe(` + SELECT ei.ordem, ei.id_produto, + TRIM(p.codigo) AS codigo, TRIM(p.descricao) AS descricao, + ei.qtd::text, ei.preco_unitario::text, + ei.desconto_perc::text, ei.total::text + FROM vw_peditens_erp ei + LEFT JOIN vw_produtos p + ON p.id_erp = ei.id_produto + AND p.id_empresa = ${idMatriz} + WHERE ei.id_pedido = ${idPedido} + ORDER BY ei.ordem + `), + ]); + + if (!headerRows[0]) throw new NotFoundException(`Pedido ERP ${idPedido} não encontrado`); + const h = headerRows[0]; + + return { + id: `erp-${h.id_pedido}`, + numPedSar: (h.num_ped_sar ?? '').trim(), + numero: Number(h.numero), + idCliente: Number(h.id_cliente), + nomeCliente: h.nome_cliente ?? null, + razaoCliente: h.razao_cliente ?? null, + codVendedor: Number(h.cod_vendedor), + nomeVendedor: h.nome_vendedor ?? null, + situa: sigToSar(Number(h.situa)), + statusDescr: h.status_descr, + dtPedido: new Date(h.dt_pedido).toISOString(), + total: h.total ?? '0', + descontoPerc: h.desconto_perc ?? '0', + obs: h.obs?.trim() || null, + createdAt: new Date(h.dt_pedido).toISOString(), + updatedAt: new Date(h.dt_pedido).toISOString(), + fonte: 'erp' as const, + formaPagamento: h.forma_pagamento || null, + totalProdutos: h.total_produtos ?? '0', + totalIpi: h.total_ipi ?? '0', + totalIcmsst: h.total_icmsst ?? '0', + descontoValor: h.desconto_valor ?? '0', + acrescimo: h.acrescimo ?? '0', + comissao: h.comissao ?? '0', + pedFlex: h.ped_flex ?? '0', + aprovadoPor: null, + aprovadoEm: null, + motivoRecusa: null, + idempotencyKey: null, + itens: itemRows.map((it) => ({ + id: `${idPedido}-${it.ordem}`, + idProduto: Number(it.id_produto), + codProduto: it.codigo ?? null, + descProduto: it.descricao ?? null, + ordem: Number(it.ordem), + qtd: it.qtd, + precoUnitario: it.preco_unitario, + descontoPerc: it.desconto_perc, + total: it.total, + })), + historico: [], + }; + } } diff --git a/apps/web/src/cockpits/rep/OrderDetailPage.tsx b/apps/web/src/cockpits/rep/OrderDetailPage.tsx index 30f6b61..b8f406f 100644 --- a/apps/web/src/cockpits/rep/OrderDetailPage.tsx +++ b/apps/web/src/cockpits/rep/OrderDetailPage.tsx @@ -252,8 +252,9 @@ export function OrderDetailPage() { const { data: clientOrders } = useClientOrders(order?.idCliente); const role = getRoleFromToken(); - const canAct = role !== 'rep' && order?.situa === 1; - const canTransmit = role === 'rep' && order?.situa === 0; + const isErp = order?.fonte === 'erp'; + const canAct = !isErp && role !== 'rep' && order?.situa === 1; + const canTransmit = !isErp && role === 'rep' && order?.situa === 0; const canShare = role === 'rep' && (order?.situa === 2 || order?.situa === 4) && @@ -417,6 +418,11 @@ export function OrderDetailPage() { {new Date(order.aprovadoEm).toLocaleString('pt-BR')} — cód. {order.aprovadoPor} )} + {order.formaPagamento && ( + + {order.formaPagamento} + + )} {fmt(order.totalProdutos)} {order.descontoPerc}% diff --git a/apps/web/src/lib/queries/orders.ts b/apps/web/src/lib/queries/orders.ts index 22cace7..3e30d97 100644 --- a/apps/web/src/lib/queries/orders.ts +++ b/apps/web/src/lib/queries/orders.ts @@ -34,7 +34,11 @@ export function useOrderDetail(id: string | undefined) { queryKey: ['orders', id], enabled: !!id, queryFn: async () => { - const res = await apiFetch(`/orders/${id}`); + // Pedidos ERP têm id 'erp-{idPedido}' — endpoint separado sem ParseUUIDPipe + const path = id?.startsWith('erp-') + ? `/orders/erp/${id.replace('erp-', '')}` + : `/orders/${id}`; + const res = await apiFetch(path); return PedidoDetailSchema.parse(res); }, }); diff --git a/libs/shared/api-interface/src/lib/order.contract.ts b/libs/shared/api-interface/src/lib/order.contract.ts index 3c67e09..e11974b 100644 --- a/libs/shared/api-interface/src/lib/order.contract.ts +++ b/libs/shared/api-interface/src/lib/order.contract.ts @@ -31,7 +31,7 @@ export const SITUA_LABEL: Record = { // ─── PedidoItem ─────────────────────────────────────────────────────────────── export const PedidoItemSchema = z.object({ - id: z.string().uuid(), + id: z.string(), // UUID para pedidos SAR; '{idPedido}-{ordem}' para ERP idProduto: z.number().int(), codProduto: z.string().nullable(), descProduto: z.string().nullable(), @@ -87,6 +87,7 @@ export const PedidoDetailSchema = PedidoSummarySchema.extend({ acrescimo: z.string(), comissao: z.string(), pedFlex: z.string(), + formaPagamento: z.string().nullable().optional(), aprovadoPor: z.number().int().nullable(), aprovadoEm: z.iso.datetime().nullable(), motivoRecusa: z.string().nullable(),