feat(web+api): ficha do cliente — contatos, CTR, NF-e, pedidos e produtos

- Contatos: grid de cards 3 colunas, botão "Novo Contato" no topo da página
- Sync ERP↔SAR corrigido: vw_contatos aceita formato COR#<id>, trigger normaliza id_empresa (9001→1)
- CTR + NF-e: layout 50/50 — lista de títulos abertos com badge vencido/a vencer e lista de notas com botão copiar chave NF-e
- Histórico de pedidos: UNION SAR+ERP, top 5 + modal "Ver todos"
- Produtos mais comprados: top 5 com último preço + modal "Ver todos"
- Novos endpoints: ctr-list, notas, orders-history, top-produtos
- AppShell: overflow-x travado, sem scroll horizontal na aplicação

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 14:13:21 +00:00
parent a2bab75bad
commit 400fcb3360
23 changed files with 2532 additions and 356 deletions

View File

@@ -111,6 +111,54 @@ export const ContatoResultSchema = z.object({
});
export type ContatoResult = z.infer<typeof ContatoResultSchema>;
// ─── Produtos mais comprados por cliente ─────────────────────────────────────
export const TopProdutoSchema = z.object({
idProduto: z.number().int(),
descricao: z.string(),
unidade: z.string().nullable(),
qtdTotal: z.number(),
numPedidos: z.number().int(),
ultimaCompra: z.string(),
ultimoPreco: z.number(),
});
export type TopProduto = z.infer<typeof TopProdutoSchema>;
// ─── Contas a Receber (resumo por cliente) ───────────────────────────────────
export const CtrSummarySchema = z.object({
qtdAberto: z.number().int(),
totalAberto: z.number(),
qtdVencido: z.number().int(),
totalVencido: z.number(),
maiorAtrasoDias: z.number().int(),
});
export type CtrSummary = z.infer<typeof CtrSummarySchema>;
// ─── Contas a Receber (lista de títulos em aberto) ────────────────────────────
export const CtrTituloSchema = z.object({
idCtr: z.number().int(),
numero: z.string(),
prefixo: z.string(),
dtEmissao: z.string(),
dtVencimento: z.string(),
saldo: z.number(),
});
export type CtrTitulo = z.infer<typeof CtrTituloSchema>;
// ─── Notas Fiscais por cliente ───────────────────────────────────────────────
export const NotaFiscalSchema = z.object({
idNf: z.number().int(),
numero: z.number().int(),
serie: z.string(),
dtEmissao: z.string(),
vlNf: z.number(),
chaveAcessoNfe: z.string().nullable(),
});
export type NotaFiscal = z.infer<typeof NotaFiscalSchema>;
// ─── Municipio ───────────────────────────────────────────────────────────────
export const MunicipioSchema = z.object({