- DevLogin com 3 usuarios reais: Pavei (rep 29), Sidnei (supervisor 191) e Lucas (gerente 156); guard deixa de forcar DEV_REP_CODE em dev e usa sub/id_empresa do JWT (fallback para os valores do .env); idEmpresa do token dev opcional, default DEV_EMPRESA_ID - getTeamCodes (vw_representantes.cod_supervisor): supervisor ve so a equipe em pedidos SAR/ERP, detalhe, aprovar/recusar, clientes (lista e carteira), dashboard supervisor, chamados SAC e relatorios carteira/curva ABC; gerente/admin seguem vendo a empresa toda - Painel gerencial: card "Positivacao Diaria de Clientes" (clientes distintos por dia via vw_pedidos_erp) com campo de meta/dia persistido em localStorage, linha de meta tracejada e contador de dias na meta Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
128 lines
4.2 KiB
TypeScript
128 lines
4.2 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 MesAnoSchema = z.object({
|
|
mes: z.number().int().min(1).max(12),
|
|
valor: z.number(),
|
|
meta: z.number(),
|
|
atingiu: z.boolean(),
|
|
});
|
|
export type MesAno = z.infer<typeof MesAnoSchema>;
|
|
|
|
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(),
|
|
historicoMensal: z.array(MesAnoSchema).default([]),
|
|
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>;
|
|
|
|
// Clientes distintos positivados por dia do período (YYYY-MM-DD)
|
|
export const PositivacaoDiaSchema = z.object({
|
|
dia: z.string(),
|
|
clientes: z.number().int(),
|
|
});
|
|
export type PositivacaoDia = z.infer<typeof PositivacaoDiaSchema>;
|
|
|
|
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(),
|
|
metasPorGrupo: z.array(MetaItemSchema).default([]),
|
|
rankingReps: z.array(RankingRepSchema),
|
|
positivacaoReps: z.array(PositivacaoRepSchema),
|
|
positivacaoDiaria: z.array(PositivacaoDiaSchema).default([]),
|
|
syncedAt: z.iso.datetime(),
|
|
});
|
|
export type ManagerDashboard = z.infer<typeof ManagerDashboardSchema>;
|