Files
sar/libs/shared/api-interface/src/lib/order.contract.ts
julian a3c68f9f05 feat(mvp-rep): formas de pagamento do ERP + suporte offline completo
Formas de pagamento:
- Endpoint GET /catalog/payment-methods lendo vw_formas_pagamento
  filtrado por ativa=1 e integrar_sar=1
- FormaPagamento schema/type no shared api-interface
- Hook useFormasPagamento (staleTime 1h) substituindo lista hardcoded

Offline (FR-4.2 / NFR-2.1–2.4):
- IndexedDB queue: lib/offline/idb.ts + order-queue.ts sem deps externos
- NewOrderPage detecta !navigator.onLine → enqueueOrder() → toast + reset
- useOfflineSync: auto-sync ao reconectar (POST orders + PATCH transmit)
- usePendingOrders: fila reativa via CustomEvents
- AppShell: banner offline + useOfflineSync() global
- OrdersPage: seção de pedidos pendentes com retry/descartar
- sw.js: network-first para API GETs cacheáveis + stale-while-revalidate
  para assets + app shell navigate fallback

Docs:
- architecture.md: documento de decisões de arquitetura do SAR MVP

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 21:30:23 +00:00

155 lines
6.2 KiB
TypeScript

import { z } from 'zod';
// Contratos canônicos de C3 — Pedidos SAR.
// Consumidos pela API (output tipado) e pela Web (TanStack Query + parse).
// ADR 0006 revogado: OrderStatus enum → situa Int (1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado)
// ─── Situa ────────────────────────────────────────────────────────────────────
// Ciclo de vida do pedido SAR:
// 0=Orçamento → 1=Ag. Aprovação (se desconto > alçada) → 2=Transmitido
// Estados que o SAR controla: Orçamento e Transmitido (1 é o gate de desconto).
// Após Transmitido, o status passa a refletir o ERP (Emitido/Cancelado/Aguardando…)
// — espelhado quando a integração existir.
export const SituaPedidoSchema = z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
]);
export type SituaPedido = z.infer<typeof SituaPedidoSchema>;
export const SITUA_LABEL: Record<number, string> = {
0: 'Orçamento',
1: 'Ag. Aprovação',
2: 'Transmitido',
3: 'Cancelado',
4: 'Faturado',
};
// ─── PedidoItem ───────────────────────────────────────────────────────────────
export const PedidoItemSchema = z.object({
id: z.string().uuid(),
idProduto: z.number().int(),
codProduto: z.string().nullable(),
descProduto: z.string().nullable(),
ordem: z.number().int(),
qtd: z.string(),
precoUnitario: z.string(),
descontoPerc: z.string(),
total: z.string(),
});
export type PedidoItem = z.infer<typeof PedidoItemSchema>;
// ─── HistoricoPedido ──────────────────────────────────────────────────────────
export const HistoricoPedidoSchema = z.object({
id: z.string().uuid(),
situaAnterior: z.number().int().nullable(),
situaNova: z.number().int(),
changedBy: z.number().int(),
nota: z.string().nullable(),
changedAt: z.iso.datetime(),
});
export type HistoricoPedido = z.infer<typeof HistoricoPedidoSchema>;
// ─── Pedido Summary (lista) ───────────────────────────────────────────────────
export const PedidoSummarySchema = z.object({
id: z.string(), // UUID para pedidos SAR, 'erp-{id}' para histórico ERP
numPedSar: z.string(),
numero: z.number().int().optional(), // número do pedido no ERP
idCliente: z.number().int(),
nomeCliente: z.string().nullable().optional(),
razaoCliente: z.string().nullable().optional(),
codVendedor: z.number().int(),
nomeVendedor: z.string().nullable().optional(),
situa: z.number().int(),
statusDescr: z.string().optional(), // descrição legível do status
dtPedido: z.string(),
total: z.string(),
descontoPerc: z.string(),
obs: z.string().nullable(),
createdAt: z.iso.datetime(),
fonte: z.enum(['sar', 'erp']).default('sar'),
});
export type PedidoSummary = z.infer<typeof PedidoSummarySchema>;
// ─── Pedido Detail ────────────────────────────────────────────────────────────
export const PedidoDetailSchema = PedidoSummarySchema.extend({
totalProdutos: z.string(),
totalIpi: z.string(),
totalIcmsst: z.string(),
descontoValor: z.string(),
acrescimo: z.string(),
comissao: z.string(),
pedFlex: z.string(),
aprovadoPor: z.number().int().nullable(),
aprovadoEm: z.iso.datetime().nullable(),
motivoRecusa: z.string().nullable(),
idempotencyKey: z.string().nullable(),
updatedAt: z.iso.datetime(),
itens: z.array(PedidoItemSchema),
historico: z.array(HistoricoPedidoSchema),
});
export type PedidoDetail = z.infer<typeof PedidoDetailSchema>;
// ─── List query + response ────────────────────────────────────────────────────
export const PedidoListQuerySchema = z.object({
idCliente: z.coerce.number().int().optional(),
situa: z.coerce.number().int().optional(),
numPedSar: z.string().optional(),
from: z.string().optional(),
to: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(200).default(50),
});
export type PedidoListQuery = z.infer<typeof PedidoListQuerySchema>;
export const PedidoListResponseSchema = z.object({
data: z.array(PedidoSummarySchema),
total: z.number().int().nonnegative(),
page: z.number().int().positive(),
limit: z.number().int().positive(),
});
export type PedidoListResponse = z.infer<typeof PedidoListResponseSchema>;
// ─── Mutações ─────────────────────────────────────────────────────────────────
export const CreatePedidoItemSchema = z.object({
idProduto: z.number().int().positive(),
codProduto: z.string().optional(),
descProduto: z.string().min(1),
ordem: z.number().int().min(1),
qtd: z.number().positive(),
precoUnitario: z.number().nonnegative(),
descontoPerc: z.number().min(0).max(100).default(0),
});
export type CreatePedidoItem = z.infer<typeof CreatePedidoItemSchema>;
export const CreatePedidoSchema = z.object({
idCliente: z.number().int().positive(),
descontoPerc: z.number().min(0).max(100).default(0),
idPauta: z.number().int().optional(),
codFormapag: z.number().int().optional(),
obs: z.string().optional(),
idempotencyKey: z.string().optional(),
itens: z.array(CreatePedidoItemSchema).min(1),
});
export type CreatePedido = z.infer<typeof CreatePedidoSchema>;
export const AprovarPedidoSchema = z.object({
descontoPerc: z.number().min(0).max(100).optional(),
nota: z.string().optional(),
});
export type AprovarPedido = z.infer<typeof AprovarPedidoSchema>;
export const RecusarPedidoSchema = z.object({
motivo: z.string().min(1),
});
export type RecusarPedido = z.infer<typeof RecusarPedidoSchema>;