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:
@@ -3,13 +3,16 @@ import { PedidoSummarySchema } from './order.contract';
|
||||
|
||||
// ADR 0006 revogado: OrderSummary → PedidoSummary, ids numéricos.
|
||||
|
||||
export const ClienteInativoSchema = z.object({
|
||||
export const ClienteNaoPositivadoSchema = z.object({
|
||||
idCliente: z.number().int(),
|
||||
nome: z.string(),
|
||||
diasSemCompra: z.number().int(),
|
||||
ultimaCompraValor: z.string().nullable(),
|
||||
razao: z.string().nullable(),
|
||||
diasSemPedido: z.number().int(), // 999 = nunca comprou
|
||||
ultimoPedido: z.string().nullable(), // ISO date YYYY-MM-DD
|
||||
comprouAntes: z.boolean(),
|
||||
whatsapp: z.string().nullable(),
|
||||
});
|
||||
export type ClienteInativo = z.infer<typeof ClienteInativoSchema>;
|
||||
export type ClienteNaoPositivado = z.infer<typeof ClienteNaoPositivadoSchema>;
|
||||
|
||||
// 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/
|
||||
@@ -22,7 +25,7 @@ export type MetaDimensao = z.infer<typeof MetaDimensaoSchema>;
|
||||
export const MetaItemSchema = z.object({
|
||||
codigo: z.number().int().nullable(),
|
||||
rotulo: z.string(),
|
||||
pedidos: z.number().int(), // qtd de pedidos faturados no grupo (realizado)
|
||||
pedidos: z.number().int(),
|
||||
valorMeta: z.number(),
|
||||
valorReal: z.number(),
|
||||
qtdMeta: z.number(),
|
||||
@@ -31,8 +34,8 @@ export const MetaItemSchema = z.object({
|
||||
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
|
||||
pct: z.number(),
|
||||
falta: z.number(),
|
||||
});
|
||||
export type MetaItem = z.infer<typeof MetaItemSchema>;
|
||||
|
||||
@@ -43,17 +46,12 @@ 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(),
|
||||
total: z.number(),
|
||||
}),
|
||||
pedidosMes: z.number().int(),
|
||||
pedidosRecentes: z.array(PedidoSummarySchema),
|
||||
clientesInativos: z.array(ClienteInativoSchema),
|
||||
naoPositivados: z.array(ClienteNaoPositivadoSchema),
|
||||
totalNaoPositivados: z.number().int(),
|
||||
syncedAt: z.iso.datetime(),
|
||||
});
|
||||
export type RepDashboard = z.infer<typeof RepDashboardSchema>;
|
||||
|
||||
Reference in New Issue
Block a user