feat(auth,ger): usuarios dev por papel, escopo do supervisor por equipe e positivacao diaria
- 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>
This commit is contained in:
@@ -8,6 +8,7 @@ import type {
|
||||
PositivacaoRep,
|
||||
} from '@sar/api-interface';
|
||||
import type { WorkspaceClsStore } from '../workspace/workspace.types';
|
||||
import { getTeamCodes } from '../workspace/team-scope.util';
|
||||
|
||||
// Situa ERP: 2=Liberado, 4=Faturado, 5=Cancelado
|
||||
// Situa SAR (pedidos novos): 1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado
|
||||
@@ -413,6 +414,10 @@ export class DashboardService {
|
||||
total_clientes: string;
|
||||
clientes_positivados: string;
|
||||
}
|
||||
interface PositivacaoDiaRow {
|
||||
dia: string;
|
||||
clientes: string;
|
||||
}
|
||||
|
||||
const [
|
||||
statsRows,
|
||||
@@ -422,6 +427,7 @@ export class DashboardService {
|
||||
positivacaoRows,
|
||||
metaGrupoRows,
|
||||
realizadoGrupoRows,
|
||||
positivacaoDiariaRows,
|
||||
] = await Promise.all([
|
||||
prisma.$queryRawUnsafe<TotalRow[]>(`
|
||||
SELECT COUNT(*)::text AS count, COALESCE(SUM(total), 0)::text AS total
|
||||
@@ -518,6 +524,17 @@ export class DashboardService {
|
||||
AND e.dt_pedido <= '${monthEndStr}'
|
||||
GROUP BY p.cod_grupo, grupo
|
||||
`),
|
||||
prisma.$queryRawUnsafe<PositivacaoDiaRow[]>(`
|
||||
SELECT dt_pedido::date::text AS dia,
|
||||
COUNT(DISTINCT id_cliente)::text AS clientes
|
||||
FROM vw_pedidos_erp
|
||||
WHERE id_empresa = ${idEmpresa}
|
||||
AND situa NOT IN (1, 5)
|
||||
AND dt_pedido >= '${monthStartStr}'
|
||||
AND dt_pedido <= '${monthEndStr}'
|
||||
GROUP BY dt_pedido::date
|
||||
ORDER BY dia
|
||||
`),
|
||||
]);
|
||||
|
||||
const pedidosMes = Number(statsRows[0]?.count ?? 0);
|
||||
@@ -590,6 +607,10 @@ export class DashboardService {
|
||||
metasPorGrupo,
|
||||
rankingReps,
|
||||
positivacaoReps,
|
||||
positivacaoDiaria: positivacaoDiariaRows.map((r) => ({
|
||||
dia: r.dia,
|
||||
clientes: Number(r.clientes),
|
||||
})),
|
||||
syncedAt: now.toISOString(),
|
||||
};
|
||||
}
|
||||
@@ -600,9 +621,21 @@ export class DashboardService {
|
||||
const idEmpresa = this.cls.get('idEmpresa');
|
||||
const now = new Date();
|
||||
|
||||
// Supervisor vê só a própria equipe (cod_supervisor em vw_representantes);
|
||||
// gerente/admin acessando este painel veem a empresa toda.
|
||||
const role = this.cls.get('role');
|
||||
const userId = this.cls.get('userId');
|
||||
const team =
|
||||
role === 'supervisor' ? await getTeamCodes(prisma, userId ? parseInt(userId, 10) : 0) : null;
|
||||
const teamSqlFilter = (col: string) => (team ? `AND ${col} IN (${team.join(',')})` : '');
|
||||
|
||||
// Fila de aprovações — pedidos SAR pendentes (novos, ainda não integrados ao ERP)
|
||||
const approvalQueue = await prisma.pedido.findMany({
|
||||
where: { idEmpresa, situa: SITUA_PENDENTE },
|
||||
where: {
|
||||
idEmpresa,
|
||||
situa: SITUA_PENDENTE,
|
||||
...(team ? { codVendedor: { in: team } } : {}),
|
||||
},
|
||||
orderBy: { dtPedido: 'asc' },
|
||||
take: 50,
|
||||
});
|
||||
@@ -626,12 +659,14 @@ export class DashboardService {
|
||||
SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
|
||||
FROM vw_pedidos_erp
|
||||
WHERE id_empresa = ${idEmpresa} AND situa != 5 AND dt_pedido >= '${todayStr}'
|
||||
${teamSqlFilter('cod_vendedor')}
|
||||
`),
|
||||
prisma.$queryRawUnsafe<DayRow[]>(`
|
||||
SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
|
||||
FROM vw_pedidos_erp
|
||||
WHERE id_empresa = ${idEmpresa} AND situa != 5
|
||||
AND dt_pedido >= '${lastWeekStr}' AND dt_pedido < '${lastWeekEndStr}'
|
||||
${teamSqlFilter('cod_vendedor')}
|
||||
`),
|
||||
]);
|
||||
|
||||
@@ -651,6 +686,7 @@ export class DashboardService {
|
||||
AND p.id_empresa = ${idEmpresa}
|
||||
AND p.situa != 5
|
||||
WHERE c.ativo = 1
|
||||
${teamSqlFilter('c.cod_vendedor')}
|
||||
GROUP BY c.id_cliente, c.cod_vendedor
|
||||
HAVING MAX(p.dt_pedido) IS NULL
|
||||
OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString().slice(0, 10)}'
|
||||
|
||||
Reference in New Issue
Block a user