## API - GET /dashboard/manager: KPIs agregados (faturamento, pedidos, ticket médio, promoções ativas), meta total do período, ranking top 10 com clientes atendidos e % meta, positivação por representante, venda necessária/dia - Parâmetros opcionais ?mes&ano para filtrar período - GET /equipe: lista de reps com pedidos, faturamento, ticket médio e % meta (deduplicação de vw_representantes) - GET/POST /politicas/descontos: alçada de desconto por rep (AlcadaDesconto) - GET/POST/PATCH/DELETE /politicas/promocoes: CRUD de promoções com validade - Metas lidas de sar.vw_metas (tipo=GR), não de vw_metas do ERP ## Schema - Novo model Promocao (sar.promocoes) criado via prisma db execute ## Frontend - Cockpit /ger: GerPainel, EquipePage, PoliticasPage - GerPainel: filtro mes/ano, cards KPI, cards de meta vs realizado (atingimento, falta, venda/dia), ranking com clientes atendidos, positivação de clientes por representante (paginada) - Sidebar e BottomNav role-aware: rep / supervisor / gerente (manager+admin) - Clientes acessível ao gerente (carteira completa de todos os reps) - Rotas: HomeRoute redireciona por role; /ger, /ger/equipe, /ger/politicas Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import { z } from 'zod';
|
|
import { PedidoSummarySchema } from './order.contract';
|
|
|
|
// ADR 0006 revogado: OrderSummary → PedidoSummary, ids numéricos.
|
|
|
|
export const ClienteNaoPositivadoSchema = z.object({
|
|
idCliente: z.number().int(),
|
|
nome: z.string(),
|
|
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 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/
|
|
// 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(),
|
|
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(),
|
|
falta: z.number(),
|
|
});
|
|
export type MetaItem = z.infer<typeof MetaItemSchema>;
|
|
|
|
export const RepDashboardSchema = z.object({
|
|
meta: z.object({
|
|
atingido: z.number(),
|
|
total: z.number(),
|
|
pct: z.number(),
|
|
falta: z.number(),
|
|
}),
|
|
metaDimensao: MetaDimensaoSchema.default('global'),
|
|
metasPorGrupo: z.array(MetaItemSchema).default([]),
|
|
pedidosMes: z.number().int(),
|
|
pedidosRecentes: z.array(PedidoSummarySchema),
|
|
naoPositivados: z.array(ClienteNaoPositivadoSchema),
|
|
totalNaoPositivados: z.number().int(),
|
|
syncedAt: z.iso.datetime(),
|
|
});
|
|
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>;
|
|
|
|
export const SupervisorDashboardSchema = z.object({
|
|
approvalQueue: z.array(PedidoSummarySchema),
|
|
pedidosDia: z.object({
|
|
count: z.number().int(),
|
|
total: z.number(),
|
|
countSemanaAnterior: z.number().int(),
|
|
totalSemanaAnterior: z.number(),
|
|
}),
|
|
inativosPorRep: z.array(RepInativosSummarySchema),
|
|
syncedAt: z.iso.datetime(),
|
|
});
|
|
export type SupervisorDashboard = z.infer<typeof SupervisorDashboardSchema>;
|
|
|
|
export const PositivacaoRepSchema = z.object({
|
|
codVendedor: z.number().int(),
|
|
nomeVendedor: z.string().nullable(),
|
|
totalClientes: z.number().int(),
|
|
clientesPositivados: z.number().int(),
|
|
pctPositivacao: z.number(),
|
|
});
|
|
export type PositivacaoRep = z.infer<typeof PositivacaoRepSchema>;
|
|
|
|
export const RankingRepSchema = z.object({
|
|
codVendedor: z.number().int(),
|
|
nomeVendedor: z.string().nullable(),
|
|
pedidos: z.number().int(),
|
|
clientesAtendidos: z.number().int(),
|
|
faturamento: z.number(),
|
|
pctMeta: z.number(),
|
|
});
|
|
export type RankingRep = z.infer<typeof RankingRepSchema>;
|
|
|
|
export const ManagerDashboardSchema = z.object({
|
|
faturamentoMes: z.number(),
|
|
pedidosMes: z.number().int(),
|
|
ticketMedio: z.number(),
|
|
totalReps: z.number().int(),
|
|
promocoesAtivas: z.number().int(),
|
|
metaTotal: z.number(),
|
|
rankingReps: z.array(RankingRepSchema),
|
|
positivacaoReps: z.array(PositivacaoRepSchema),
|
|
syncedAt: z.iso.datetime(),
|
|
});
|
|
export type ManagerDashboard = z.infer<typeof ManagerDashboardSchema>;
|