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>
This commit is contained in:
2026-05-30 21:30:23 +00:00
parent 1647871a39
commit a3c68f9f05
33 changed files with 2175 additions and 173 deletions

View File

@@ -11,6 +11,31 @@ export const ClienteInativoSchema = z.object({
});
export type ClienteInativo = z.infer<typeof ClienteInativoSchema>;
// Dimensão de meta. O ERP (vw_metas.tipo) define como o cliente acompanha metas:
// GL = global, GR = por grupo. Motor único; outras dimensões (marca/subgrupo/
// produto) entram aqui depois sem mudar a forma.
export const MetaDimensaoSchema = z.enum(['global', 'grupo']);
export type MetaDimensao = z.infer<typeof MetaDimensaoSchema>;
// Linha de meta vs realizado por grupo, multi-medida (codigo=null = rollup global).
// fator = R$/kg (valor/peso); o ERP guarda vl_fator na meta.
export const MetaItemSchema = z.object({
codigo: z.number().int().nullable(),
rotulo: z.string(),
pedidos: z.number().int(), // qtd de pedidos faturados no grupo (realizado)
valorMeta: z.number(),
valorReal: z.number(),
qtdMeta: z.number(),
qtdReal: z.number(),
pesoMeta: z.number(),
pesoReal: z.number(),
fatorMeta: z.number(),
fatorReal: z.number(),
pct: z.number(), // % de valor (real/meta) — base da barra de progresso
falta: z.number(), // valor faltante p/ a meta
});
export type MetaItem = z.infer<typeof MetaItemSchema>;
export const RepDashboardSchema = z.object({
meta: z.object({
atingido: z.number(),
@@ -18,6 +43,9 @@ export const RepDashboardSchema = z.object({
pct: z.number(),
falta: z.number(),
}),
// Dimensão detectada do ERP e detalhamento por grupo (vazio quando global).
metaDimensao: MetaDimensaoSchema.default('global'),
metasPorGrupo: z.array(MetaItemSchema).default([]),
comissao: z.object({
fixa: z.number(),
flex: z.number(),
@@ -32,6 +60,7 @@ export type RepDashboard = z.infer<typeof RepDashboardSchema>;
export const RepInativosSummarySchema = z.object({
codVendedor: z.number().int(),
nomeVendedor: z.string().nullable().optional(),
inativosCount: z.number().int(),
});
export type RepInativosSummary = z.infer<typeof RepInativosSummarySchema>;