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>
155 lines
6.2 KiB
TypeScript
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>;
|