feat(erp): pedidos e dashboard leem histórico do ERP (vw_pedidos_erp)

- OrdersService.list: substitui sar.pedidos por vw_pedidos_erp — 44k pedidos
  históricos do rep 29 visíveis; sar.pedidos continua sendo a tabela de escrita
  para novos pedidos SAR que serão integrados ao ERP
- DashboardService: atingido/pedidosMes/recentes/inativos todos via vw_pedidos_erp;
  supervisor usa vw_pedidos_erp para pedidosDia
- PedidoSummarySchema: id relaxado de uuid() para string(); adiciona numero,
  statusDescr e fonte ('sar'|'erp')
- orders.ts: corrige bug — apiFetch retorna JSON diretamente, não Response;
  remove res.ok/res.json() incorretos
- OrdersPage: coluna Nº mostra numero do ERP; statusDescr no badge
- DevLogin: atualiza para PAVEI COMERCIO cod 29

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 14:30:51 +00:00
parent 2abe5e8697
commit 24408ecd83
6 changed files with 204 additions and 146 deletions

View File

@@ -38,48 +38,65 @@ export class OrdersService {
const codVendedor = userId ? parseInt(userId, 10) : 0;
const { idCliente, situa, numPedSar, from, to, page, limit } = query;
const skip = (page - 1) * limit;
const offset = (page - 1) * limit;
const repFilter = role === 'rep' ? { codVendedor } : {};
const vendedorFilter = role === 'rep' ? `AND e.cod_vendedor = ${codVendedor}` : '';
const clienteFilter = idCliente != null ? `AND e.id_cliente = ${idCliente}` : '';
const situaFilter = situa != null ? `AND e.situa = ${situa}` : '';
const pedSarFilter = numPedSar ? `AND TRIM(e.num_ped_sar) ILIKE '%${numPedSar}%'` : '';
const fromFilter = from ? `AND e.dt_pedido >= '${from}'` : '';
const toFilter = to ? `AND e.dt_pedido <= '${to}'` : '';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const where: any = {
idEmpresa,
...repFilter,
...(idCliente != null ? { idCliente } : {}),
...(situa != null ? { situa } : {}),
...(numPedSar ? { numPedSar: { contains: numPedSar } } : {}),
...(from || to
? {
dtPedido: {
...(from ? { gte: new Date(from) } : {}),
...(to ? { lte: new Date(to) } : {}),
},
}
: {}),
};
const filters = `
WHERE e.id_empresa = ${idEmpresa}
${vendedorFilter} ${clienteFilter} ${situaFilter}
${pedSarFilter} ${fromFilter} ${toFilter}
`;
const [rows, total] = await Promise.all([
prisma.pedido.findMany({
where,
skip,
take: limit,
orderBy: { dtPedido: 'desc' },
}),
prisma.pedido.count({ where }),
interface ErpRow {
id_pedido: number;
num_ped_sar: string;
numero: number;
id_cliente: number;
cod_vendedor: number;
situa: number;
status_descr: string;
dt_pedido: Date;
total: string;
desconto_perc: string;
obs: string | null;
}
const [rows, countRows] = await Promise.all([
prisma.$queryRawUnsafe<ErpRow[]>(`
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::text, e.desconto_perc::text, e.obs
FROM vw_pedidos_erp e
${filters}
ORDER BY e.dt_pedido DESC
LIMIT ${limit} OFFSET ${offset}
`),
prisma.$queryRawUnsafe<[{ count: string }]>(`
SELECT COUNT(*)::text AS count FROM vw_pedidos_erp e ${filters}
`),
]);
const total = Number(countRows[0]?.count ?? 0);
const data: PedidoSummary[] = rows.map((o) => ({
id: o.id,
numPedSar: o.numPedSar,
idCliente: o.idCliente,
codVendedor: o.codVendedor,
situa: o.situa,
dtPedido: o.dtPedido.toISOString(),
total: decimalToString(o.total),
descontoPerc: decimalToString(o.descontoPerc),
obs: o.obs,
createdAt: o.createdAt.toISOString(),
id: `erp-${o.id_pedido}`,
numPedSar: (o.num_ped_sar ?? '').trim(),
numero: Number(o.numero),
idCliente: Number(o.id_cliente),
codVendedor: Number(o.cod_vendedor),
situa: Number(o.situa),
statusDescr: o.status_descr,
dtPedido: new Date(o.dt_pedido).toISOString(),
total: o.total ?? '0',
descontoPerc: o.desconto_perc ?? '0',
obs: o.obs ?? null,
createdAt: new Date(o.dt_pedido).toISOString(),
fonte: 'erp',
}));
return { data, total, page, limit };
@@ -388,6 +405,7 @@ export class OrdersService {
acrescimo: decimalToString(o.acrescimo),
comissao: decimalToString(o.comissao),
pedFlex: decimalToString(o.pedFlex),
fonte: 'sar' as const,
aprovadoPor: o.aprovadoPor,
aprovadoEm: o.aprovadoEm?.toISOString() ?? null,
motivoRecusa: o.motivoRecusa,