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:
@@ -18,6 +18,7 @@ import type {
|
||||
TopProduto,
|
||||
} from '@sar/api-interface';
|
||||
import type { WorkspaceClsStore } from '../workspace/workspace.types';
|
||||
import { getTeamCodes } from '../workspace/team-scope.util';
|
||||
|
||||
// Thresholds de atividade (FR-2.3). Configuráveis por empresa futuramente.
|
||||
const ALERT_DAYS = 30;
|
||||
@@ -114,11 +115,12 @@ const ACTIVITY_CASE = (alias_erp = 'erp_ped', alias_sar = 'sar_ped') => `
|
||||
export class ClientsService {
|
||||
constructor(private readonly cls: ClsService<WorkspaceClsStore>) {}
|
||||
|
||||
// PGD-AUTHZ: rep só acessa clientes da própria carteira; gestores passam direto.
|
||||
// PGD-AUTHZ: rep só acessa clientes da própria carteira; supervisor acessa as
|
||||
// carteiras da sua equipe (cod_supervisor); gerente/admin passam direto.
|
||||
// NotFound (não Forbidden) para não revelar a existência de clientes de terceiros.
|
||||
private async assertClienteDaCarteira(idCliente: number): Promise<void> {
|
||||
const role = this.cls.get('role') ?? 'rep';
|
||||
if (role !== 'rep') return;
|
||||
if (role !== 'rep' && role !== 'supervisor') return;
|
||||
const prisma = this.cls.get('prisma');
|
||||
if (!prisma) throw new Error('prisma não disponível no CLS');
|
||||
const userId = this.cls.get('userId');
|
||||
@@ -128,7 +130,10 @@ export class ClientsService {
|
||||
`SELECT cod_vendedor FROM vw_clientes WHERE id_cliente = $1 LIMIT 1`,
|
||||
idCliente,
|
||||
);
|
||||
if (!rows[0] || Number(rows[0].cod_vendedor) !== codVendedor) {
|
||||
const dono = rows[0] ? Number(rows[0].cod_vendedor) : null;
|
||||
const permitidos =
|
||||
role === 'supervisor' ? await getTeamCodes(prisma, codVendedor) : [codVendedor];
|
||||
if (dono == null || !permitidos.includes(dono)) {
|
||||
throw new NotFoundException(`Cliente ${idCliente} não encontrado`);
|
||||
}
|
||||
}
|
||||
@@ -144,8 +149,13 @@ export class ClientsService {
|
||||
const { q, status, page, limit } = query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Rep vê apenas sua carteira (cod_vendedor = seu código)
|
||||
const vendedorFilter = role === 'rep' ? `AND c.cod_vendedor = ${codVendedor}` : '';
|
||||
// Rep vê apenas sua carteira; supervisor vê as carteiras da equipe
|
||||
const vendedorFilter =
|
||||
role === 'rep'
|
||||
? `AND c.cod_vendedor = ${codVendedor}`
|
||||
: role === 'supervisor'
|
||||
? `AND c.cod_vendedor IN (${(await getTeamCodes(prisma, codVendedor)).join(',')})`
|
||||
: '';
|
||||
const searchFilter = q
|
||||
? `AND (c.nome ILIKE '%${escSql(q)}%' OR c.cgcpf LIKE '%${escSql(q)}%')`
|
||||
: '';
|
||||
|
||||
Reference in New Issue
Block a user