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

@@ -3,10 +3,8 @@ import { ClsService } from 'nestjs-cls';
import type { RepDashboard, SupervisorDashboard } from '@sar/api-interface'; import type { RepDashboard, SupervisorDashboard } from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types'; import type { WorkspaceClsStore } from '../workspace/workspace.types';
// Situa SAR: 1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado // Situa ERP: 2=Liberado, 4=Faturado, 5=Cancelado
const SITUA_APROVADO = 2; // Situa SAR (pedidos novos): 1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado
const SITUA_FATURADO = 4;
const SITUA_CANCELADO = 3;
const SITUA_PENDENTE = 1; const SITUA_PENDENTE = 1;
// tipo='G' em gestao.metavenda = meta geral de valor do mês // tipo='G' em gestao.metavenda = meta geral de valor do mês
@@ -81,17 +79,64 @@ export class DashboardService {
}); });
const flexRate = flexOverride ? Number(flexOverride.taxaFlex) : 1; const flexRate = flexOverride ? Number(flexOverride.taxaFlex) : 1;
// 4. Pedidos aprovados/faturados do mês (base de cálculo) // 4. Atingido do mês — pedidos liberados/faturados no ERP (situa 2=Liberado, 4=Faturado)
const approvedThisMonth = await prisma.pedido.findMany({ const monthStartStr = monthStart.toISOString().slice(0, 10);
where: { const monthEndStr = monthEnd.toISOString().slice(0, 10);
codVendedor,
idEmpresa,
situa: { in: [SITUA_APROVADO, SITUA_FATURADO] },
dtPedido: { gte: monthStart, lte: monthEnd },
},
});
const atingido = approvedThisMonth.reduce((s, o) => s + Number(o.total), 0); interface TotalRow {
total: string;
}
interface CountRow {
count: string;
}
interface RecentRow {
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 [atingidoRows, pedidosMesRows, recentRows] = await Promise.all([
prisma.$queryRawUnsafe<TotalRow[]>(`
SELECT COALESCE(SUM(total), 0)::text AS total
FROM vw_pedidos_erp
WHERE id_empresa = ${idEmpresa}
AND cod_vendedor = ${codVendedor}
AND situa IN (2, 4)
AND dt_pedido >= '${monthStartStr}'
AND dt_pedido <= '${monthEndStr}'
`),
prisma.$queryRawUnsafe<CountRow[]>(`
SELECT COUNT(*)::text AS count
FROM vw_pedidos_erp
WHERE id_empresa = ${idEmpresa}
AND cod_vendedor = ${codVendedor}
AND situa != 5
AND dt_pedido >= '${monthStartStr}'
AND dt_pedido <= '${monthEndStr}'
`),
prisma.$queryRawUnsafe<RecentRow[]>(`
SELECT id_pedido, num_ped_sar, numero, id_cliente, cod_vendedor,
situa, status_descr, dt_pedido, total::text, desconto_perc::text, obs
FROM vw_pedidos_erp
WHERE id_empresa = ${idEmpresa}
AND cod_vendedor = ${codVendedor}
AND situa != 5
AND dt_pedido >= '${new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)}'
ORDER BY dt_pedido DESC
LIMIT 10
`),
]);
const atingido = Number(atingidoRows[0]?.total ?? 0);
const pedidosMes = Number(pedidosMesRows[0]?.count ?? 0);
const pct = targetAmount > 0 ? Math.round((atingido / targetAmount) * 100) : 0; const pct = targetAmount > 0 ? Math.round((atingido / targetAmount) * 100) : 0;
const falta = Math.max(0, targetAmount - atingido); const falta = Math.max(0, targetAmount - atingido);
@@ -101,30 +146,7 @@ export class DashboardService {
? Math.round(atingido * flexRate) / 100 ? Math.round(atingido * flexRate) / 100
: 0; : 0;
// 5. Contagem de pedidos do mês (exceto cancelado) // 7. Clientes inativos — sem pedido no ERP há >30 dias
const pedidosMes = await prisma.pedido.count({
where: {
codVendedor,
idEmpresa,
situa: { not: SITUA_CANCELADO },
dtPedido: { gte: monthStart, lte: monthEnd },
},
});
// 6. Pedidos recentes — últimos 7 dias
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const recentOrders = await prisma.pedido.findMany({
where: {
codVendedor,
idEmpresa,
situa: { not: SITUA_CANCELADO },
dtPedido: { gte: sevenDaysAgo },
},
orderBy: { dtPedido: 'desc' },
take: 10,
});
// 7. Clientes inativos — sem pedido há >30 dias (vw_clientes + sar.pedidos)
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const inactiveClients = await prisma.$queryRawUnsafe<InativoRow[]>(` const inactiveClients = await prisma.$queryRawUnsafe<InativoRow[]>(`
SELECT SELECT
@@ -133,16 +155,16 @@ export class DashboardService {
MAX(p.dt_pedido) AS dt_ultima_compra, MAX(p.dt_pedido) AS dt_ultima_compra,
MAX(p.total)::text AS ultima_compra_valor MAX(p.total)::text AS ultima_compra_valor
FROM vw_clientes c FROM vw_clientes c
LEFT JOIN pedidos p LEFT JOIN vw_pedidos_erp p
ON p.id_cliente = c.id_cliente ON p.id_cliente = c.id_cliente
AND p.id_empresa = c.id_empresa AND p.id_empresa = c.id_empresa
AND p.situa != ${SITUA_CANCELADO} AND p.situa != 5
WHERE c.id_empresa = ${idEmpresa} WHERE c.id_empresa = ${idEmpresa}
AND c.cod_vendedor = ${codVendedor} AND c.cod_vendedor = ${codVendedor}
AND c.ativo = 1 AND c.ativo = 1
GROUP BY c.id_cliente, c.nome GROUP BY c.id_cliente, c.nome
HAVING MAX(p.dt_pedido) IS NULL HAVING MAX(p.dt_pedido) IS NULL
OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString()}' OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString().slice(0, 10)}'
ORDER BY dt_ultima_compra ASC NULLS FIRST ORDER BY dt_ultima_compra ASC NULLS FIRST
LIMIT 10 LIMIT 10
`); `);
@@ -151,17 +173,20 @@ export class DashboardService {
meta: { atingido, total: targetAmount, pct, falta }, meta: { atingido, total: targetAmount, pct, falta },
comissao: { fixa, flex, total: fixa + flex }, comissao: { fixa, flex, total: fixa + flex },
pedidosMes, pedidosMes,
pedidosRecentes: recentOrders.map((o) => ({ pedidosRecentes: recentRows.map((o) => ({
id: o.id, id: `erp-${o.id_pedido}`,
numPedSar: o.numPedSar, numPedSar: (o.num_ped_sar ?? '').trim(),
idCliente: o.idCliente, numero: Number(o.numero),
codVendedor: o.codVendedor, idCliente: Number(o.id_cliente),
situa: o.situa, codVendedor: Number(o.cod_vendedor),
dtPedido: o.dtPedido.toISOString(), situa: Number(o.situa),
total: String(o.total), statusDescr: o.status_descr,
descontoPerc: String(o.descontoPerc), dtPedido: new Date(o.dt_pedido).toISOString(),
obs: o.obs, total: o.total ?? '0',
createdAt: o.createdAt.toISOString(), descontoPerc: o.desconto_perc ?? '0',
obs: o.obs ?? null,
createdAt: new Date(o.dt_pedido).toISOString(),
fonte: 'erp' as const,
})), })),
clientesInativos: inactiveClients.map((c) => ({ clientesInativos: inactiveClients.map((c) => ({
idCliente: Number(c.id_cliente), idCliente: Number(c.id_cliente),
@@ -181,51 +206,57 @@ export class DashboardService {
const idEmpresa = this.cls.get('idEmpresa'); const idEmpresa = this.cls.get('idEmpresa');
const now = new Date(); const now = new Date();
// Fila de aprovações — mais antigos primeiro // Fila de aprovações — pedidos SAR pendentes (novos, ainda não integrados ao ERP)
const approvalQueue = await prisma.pedido.findMany({ const approvalQueue = await prisma.pedido.findMany({
where: { idEmpresa, situa: SITUA_PENDENTE }, where: { idEmpresa, situa: SITUA_PENDENTE },
orderBy: { dtPedido: 'asc' }, orderBy: { dtPedido: 'asc' },
take: 50, take: 50,
}); });
// Pedidos do dia // Pedidos do dia — lê do ERP (situa != 5=Cancelado)
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const todayOrders = await prisma.pedido.findMany({ const todayStr = todayStart.toISOString().slice(0, 10);
where: { const lastWeekStr = new Date(todayStart.getTime() - 7 * 24 * 60 * 60 * 1000)
idEmpresa, .toISOString()
situa: { not: SITUA_CANCELADO }, .slice(0, 10);
dtPedido: { gte: todayStart }, const lastWeekEndStr = new Date(todayStart.getTime() - 6 * 24 * 60 * 60 * 1000)
}, .toISOString()
}); .slice(0, 10);
// Mesmo dia da semana passada (comparativo) interface DayRow {
const lastWeekStart = new Date(todayStart.getTime() - 7 * 24 * 60 * 60 * 1000); count: string;
const lastWeekEnd = new Date(lastWeekStart.getTime() + 24 * 60 * 60 * 1000 - 1); total: string;
const lastWeekOrders = await prisma.pedido.findMany({ }
where: { const [todayRows, lastWeekRows] = await Promise.all([
idEmpresa, prisma.$queryRawUnsafe<DayRow[]>(`
situa: { not: SITUA_CANCELADO }, SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
dtPedido: { gte: lastWeekStart, lte: lastWeekEnd }, FROM vw_pedidos_erp
}, WHERE id_empresa = ${idEmpresa} AND situa != 5 AND dt_pedido >= '${todayStr}'
}); `),
prisma.$queryRawUnsafe<DayRow[]>(`
SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
FROM vw_pedidos_erp
WHERE id_empresa = ${idEmpresa} AND situa != 5
AND dt_pedido >= '${lastWeekStr}' AND dt_pedido < '${lastWeekEndStr}'
`),
]);
// Top 3 reps com mais clientes inativos (>30 dias sem compra) // Top 3 reps com mais clientes inativos (>30 dias sem compra no ERP)
// Subconsulta agrupa por cliente, outer agrupa por rep
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const inativosPorRep = await prisma.$queryRawUnsafe<InativosPorRepRow[]>(` const inativosPorRep = await prisma.$queryRawUnsafe<InativosPorRepRow[]>(`
SELECT cod_vendedor, COUNT(*)::text AS inativos_count SELECT cod_vendedor, COUNT(*)::text AS inativos_count
FROM ( FROM (
SELECT c.id_cliente, c.cod_vendedor SELECT c.id_cliente, c.cod_vendedor
FROM vw_clientes c FROM vw_clientes c
LEFT JOIN pedidos p LEFT JOIN vw_pedidos_erp p
ON p.id_cliente = c.id_cliente ON p.id_cliente = c.id_cliente
AND p.id_empresa = c.id_empresa AND p.id_empresa = c.id_empresa
AND p.situa != ${SITUA_CANCELADO} AND p.situa != 5
WHERE c.id_empresa = ${idEmpresa} WHERE c.id_empresa = ${idEmpresa}
AND c.ativo = 1 AND c.ativo = 1
GROUP BY c.id_cliente, c.cod_vendedor GROUP BY c.id_cliente, c.cod_vendedor
HAVING MAX(p.dt_pedido) IS NULL HAVING MAX(p.dt_pedido) IS NULL
OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString()}' OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString().slice(0, 10)}'
) inativos ) inativos
GROUP BY cod_vendedor GROUP BY cod_vendedor
ORDER BY COUNT(*) DESC ORDER BY COUNT(*) DESC
@@ -243,15 +274,16 @@ export class DashboardService {
descontoPerc: String(o.descontoPerc), descontoPerc: String(o.descontoPerc),
obs: o.obs, obs: o.obs,
createdAt: o.createdAt.toISOString(), createdAt: o.createdAt.toISOString(),
fonte: 'sar' as const,
}); });
return { return {
approvalQueue: approvalQueue.map(mapPedido), approvalQueue: approvalQueue.map(mapPedido),
pedidosDia: { pedidosDia: {
count: todayOrders.length, count: Number(todayRows[0]?.count ?? 0),
total: todayOrders.reduce((s, o) => s + Number(o.total), 0), total: Number(todayRows[0]?.total ?? 0),
countSemanaAnterior: lastWeekOrders.length, countSemanaAnterior: Number(lastWeekRows[0]?.count ?? 0),
totalSemanaAnterior: lastWeekOrders.reduce((s, o) => s + Number(o.total), 0), totalSemanaAnterior: Number(lastWeekRows[0]?.total ?? 0),
}, },
inativosPorRep: inativosPorRep.map((r) => ({ inativosPorRep: inativosPorRep.map((r) => ({
codVendedor: Number(r.cod_vendedor), codVendedor: Number(r.cod_vendedor),

View File

@@ -38,48 +38,65 @@ export class OrdersService {
const codVendedor = userId ? parseInt(userId, 10) : 0; const codVendedor = userId ? parseInt(userId, 10) : 0;
const { idCliente, situa, numPedSar, from, to, page, limit } = query; 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 filters = `
const where: any = { WHERE e.id_empresa = ${idEmpresa}
idEmpresa, ${vendedorFilter} ${clienteFilter} ${situaFilter}
...repFilter, ${pedSarFilter} ${fromFilter} ${toFilter}
...(idCliente != null ? { idCliente } : {}), `;
...(situa != null ? { situa } : {}),
...(numPedSar ? { numPedSar: { contains: numPedSar } } : {}),
...(from || to
? {
dtPedido: {
...(from ? { gte: new Date(from) } : {}),
...(to ? { lte: new Date(to) } : {}),
},
}
: {}),
};
const [rows, total] = await Promise.all([ interface ErpRow {
prisma.pedido.findMany({ id_pedido: number;
where, num_ped_sar: string;
skip, numero: number;
take: limit, id_cliente: number;
orderBy: { dtPedido: 'desc' }, cod_vendedor: number;
}), situa: number;
prisma.pedido.count({ where }), 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) => ({ const data: PedidoSummary[] = rows.map((o) => ({
id: o.id, id: `erp-${o.id_pedido}`,
numPedSar: o.numPedSar, numPedSar: (o.num_ped_sar ?? '').trim(),
idCliente: o.idCliente, numero: Number(o.numero),
codVendedor: o.codVendedor, idCliente: Number(o.id_cliente),
situa: o.situa, codVendedor: Number(o.cod_vendedor),
dtPedido: o.dtPedido.toISOString(), situa: Number(o.situa),
total: decimalToString(o.total), statusDescr: o.status_descr,
descontoPerc: decimalToString(o.descontoPerc), dtPedido: new Date(o.dt_pedido).toISOString(),
obs: o.obs, total: o.total ?? '0',
createdAt: o.createdAt.toISOString(), descontoPerc: o.desconto_perc ?? '0',
obs: o.obs ?? null,
createdAt: new Date(o.dt_pedido).toISOString(),
fonte: 'erp',
})); }));
return { data, total, page, limit }; return { data, total, page, limit };
@@ -388,6 +405,7 @@ export class OrdersService {
acrescimo: decimalToString(o.acrescimo), acrescimo: decimalToString(o.acrescimo),
comissao: decimalToString(o.comissao), comissao: decimalToString(o.comissao),
pedFlex: decimalToString(o.pedFlex), pedFlex: decimalToString(o.pedFlex),
fonte: 'sar' as const,
aprovadoPor: o.aprovadoPor, aprovadoPor: o.aprovadoPor,
aprovadoEm: o.aprovadoEm?.toISOString() ?? null, aprovadoEm: o.aprovadoEm?.toISOString() ?? null,
motivoRecusa: o.motivoRecusa, motivoRecusa: o.motivoRecusa,

View File

@@ -19,31 +19,39 @@ const SITUA_COLOR: Record<number, string> = {
const columns: TableColumnsType<PedidoSummary> = [ const columns: TableColumnsType<PedidoSummary> = [
{ {
title: 'Nº', title: 'Nº',
dataIndex: 'numPedSar', dataIndex: 'numero',
width: 120, width: 120,
render: (num: string, row: PedidoSummary) => ( render: (_: number, row: PedidoSummary) => {
<Link to="/pedidos/$id" params={{ id: row.id }}> const label = row.numero ? String(row.numero) : row.numPedSar || row.id;
{num} return row.fonte === 'erp' ? (
</Link> <span style={{ fontVariantNumeric: 'tabular-nums' }}>{label}</span>
), ) : (
<Link to="/pedidos/$id" params={{ id: row.id }}>
{label}
</Link>
);
},
}, },
{ {
title: 'Status', title: 'Status',
dataIndex: 'situa', dataIndex: 'situa',
width: 150, width: 150,
render: (s: number) => ( render: (s: number, row: PedidoSummary) => {
<Badge const label = row.statusDescr ?? SITUA_LABEL[s] ?? String(s);
status={ return (
(SITUA_COLOR[s] ?? 'default') as <Badge
| 'default' status={
| 'warning' (SITUA_COLOR[s] ?? 'default') as
| 'processing' | 'default'
| 'success' | 'warning'
| 'error' | 'processing'
} | 'success'
text={<Tag color={SITUA_COLOR[s] ?? 'default'}>{SITUA_LABEL[s] ?? String(s)}</Tag>} | 'error'
/> }
), text={<Tag color={SITUA_COLOR[s] ?? 'default'}>{label}</Tag>}
/>
);
},
}, },
{ {
title: 'Total', title: 'Total',

View File

@@ -10,11 +10,11 @@ import { AuthTokenResponseSchema } from '@sar/api-interface';
type DevUser = { userId: string; role: string; label: string }; type DevUser = { userId: string; role: string; label: string };
// userId = cod_vendedor como string; idEmpresa = empresa no ERP (dev default = 1) // userId = cod_vendedor como string; idEmpresa = empresa no ERP (dev default = 1)
// Em dev, o backend força DEV_REP_CODE=29 independente do userId enviado.
const DEV_USERS: DevUser[] = [ const DEV_USERS: DevUser[] = [
{ userId: '101', role: 'rep', label: 'Rafael — Rep (cod 101)' }, { userId: '29', role: 'rep', label: 'PAVEI COMERCIO (cod 29)' },
{ userId: '102', role: 'rep', label: 'Rep 2 (cod 102)' }, { userId: '29', role: 'supervisor', label: 'PAVEI — Supervisor (cod 29)' },
{ userId: '201', role: 'supervisor', label: 'Sandra — Supervisora (cod 201)' }, { userId: '29', role: 'manager', label: 'PAVEI — Gerente (cod 29)' },
{ userId: '301', role: 'manager', label: 'Gerente (cod 301)' },
]; ];
export function DevLogin({ onLogin }: { onLogin: () => void }) { export function DevLogin({ onLogin }: { onLogin: () => void }) {

View File

@@ -24,8 +24,7 @@ export function useOrderList(params: Partial<PedidoListQuery> = {}) {
queryKey: ['orders', params], queryKey: ['orders', params],
queryFn: async () => { queryFn: async () => {
const res = await apiFetch(`/orders${qs ? `?${qs}` : ''}`); const res = await apiFetch(`/orders${qs ? `?${qs}` : ''}`);
if (!res.ok) throw new Error(`orders list error ${res.status}`); return PedidoListResponseSchema.parse(res);
return PedidoListResponseSchema.parse(await res.json());
}, },
}); });
} }
@@ -36,8 +35,7 @@ export function useOrderDetail(id: string | undefined) {
enabled: !!id, enabled: !!id,
queryFn: async () => { queryFn: async () => {
const res = await apiFetch(`/orders/${id}`); const res = await apiFetch(`/orders/${id}`);
if (!res.ok) throw new Error(`order detail error ${res.status}`); return PedidoDetailSchema.parse(res);
return PedidoDetailSchema.parse(await res.json());
}, },
}); });
} }
@@ -48,8 +46,7 @@ export function useClientOrders(idCliente: number | undefined) {
enabled: idCliente != null, enabled: idCliente != null,
queryFn: async () => { queryFn: async () => {
const res = await apiFetch(`/orders?idCliente=${idCliente}&limit=10`); const res = await apiFetch(`/orders?idCliente=${idCliente}&limit=10`);
if (!res.ok) throw new Error(`client orders error ${res.status}`); const data = PedidoListResponseSchema.parse(res);
const data = PedidoListResponseSchema.parse(await res.json());
return data.data; return data.data;
}, },
}); });

View File

@@ -47,16 +47,19 @@ export type HistoricoPedido = z.infer<typeof HistoricoPedidoSchema>;
// ─── Pedido Summary (lista) ─────────────────────────────────────────────────── // ─── Pedido Summary (lista) ───────────────────────────────────────────────────
export const PedidoSummarySchema = z.object({ export const PedidoSummarySchema = z.object({
id: z.string().uuid(), id: z.string(), // UUID para pedidos SAR, 'erp-{id}' para histórico ERP
numPedSar: z.string(), numPedSar: z.string(),
numero: z.number().int().optional(), // número do pedido no ERP
idCliente: z.number().int(), idCliente: z.number().int(),
codVendedor: z.number().int(), codVendedor: z.number().int(),
situa: z.number().int(), situa: z.number().int(),
statusDescr: z.string().optional(), // descrição legível do status
dtPedido: z.string(), dtPedido: z.string(),
total: z.string(), total: z.string(),
descontoPerc: z.string(), descontoPerc: z.string(),
obs: z.string().nullable(), obs: z.string().nullable(),
createdAt: z.iso.datetime(), createdAt: z.iso.datetime(),
fonte: z.enum(['sar', 'erp']).default('sar'),
}); });
export type PedidoSummary = z.infer<typeof PedidoSummarySchema>; export type PedidoSummary = z.infer<typeof PedidoSummarySchema>;