feat(orders): detalhe completo de pedidos ERP com produtos e pagamento
- Endpoint GET /orders/erp/:idPedido para pedidos do histórico ERP
(endpoint estático antes de /:id com ParseUUIDPipe, sem conflito)
- JOIN vw_peditens_erp + vw_produtos: itens com codigo + descricao do produto
- forma_pagamento direto da vw_pedidos_erp (ex: "28/35/42 DIAS")
- Retorna PedidoDetail completo: totais, ipi, icmsst, comissao, obs
- Frontend: useOrderDetail detecta 'erp-*' → chama /orders/erp/{id}
- OrderDetailPage: Cond. Pagamento nas Descriptions; oculta botões
Transmitir/Aprovar/Recusar para pedidos ERP (read-only)
- PedidoItemSchema.id relaxado de uuid() para string() (ERP usa '{id}-{ordem}')
- PedidoDetailSchema: campo formaPagamento opcional adicionado
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<PedidoDetail> {
|
||||
return this.orders.findOneErp(idPedido);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id', ParseUUIDPipe) id: string): Promise<PedidoDetail> {
|
||||
return this.orders.findOne(id);
|
||||
|
||||
@@ -595,4 +595,131 @@ export class OrdersService {
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async findOneErp(idPedido: number): 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 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<ErpHeader[]>(`
|
||||
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<ErpItem[]>(`
|
||||
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: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{order.formaPagamento && (
|
||||
<Descriptions.Item label="Cond. Pagamento" span={2}>
|
||||
{order.formaPagamento}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label="Total produtos">{fmt(order.totalProdutos)}</Descriptions.Item>
|
||||
<Descriptions.Item label="Desc. Global">{order.descontoPerc}%</Descriptions.Item>
|
||||
<Descriptions.Item label="Total">
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SITUA_LABEL: Record<number, string> = {
|
||||
// ─── 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(),
|
||||
|
||||
Reference in New Issue
Block a user