fix(api): restringe produtos e precos a pauta vinculada ao representante
Rep so enxerga e vende itens das pautas do cadastro dele (cod_pauta1..6): - catalog: lista de produtos exige pauta e recusa pauta de terceiro - orders: create/update validam pauta do pedido e todo item na pauta com preco > 0, fechando preco forjado e produto fora da pauta Gestor/admin seguem navegando livremente. O teto de desconto continua em assertAlcadaItens; aqui so ancora o preco de tabela. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -230,10 +230,22 @@ export class CatalogService {
|
||||
const prisma = this.cls.get('prisma');
|
||||
if (!prisma) throw new Error('prisma não disponível no CLS');
|
||||
const idEmpresa = matrizEmpresa(this.cls.get('idEmpresa'));
|
||||
const role = this.cls.get('role') ?? 'rep';
|
||||
const userId = this.cls.get('userId');
|
||||
const codVendedor = userId ? parseInt(userId, 10) : 0;
|
||||
|
||||
const { q, codGrupo, idPauta, page, limit } = query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// PGD-AUTHZ: o rep só enxerga produtos/preços das pautas vinculadas ao
|
||||
// cadastro dele (cod_pauta1..6). Sem pauta selecionada → lista vazia (precisa
|
||||
// escolher a pauta primeiro); pauta de terceiro → recusa. Gestor/admin
|
||||
// navegam livremente (preço base ou pauta que escolherem).
|
||||
if (role === 'rep') {
|
||||
if (idPauta == null) return { data: [], total: 0, page, limit };
|
||||
await this.assertPautaDoRep(idPauta, codVendedor, idEmpresa);
|
||||
}
|
||||
|
||||
const grupoFilter = codGrupo != null ? `AND p.cod_grupo = ${codGrupo}` : '';
|
||||
const searchFilter = q
|
||||
? `AND (p.descricao ILIKE '%${escSql(q)}%' OR p.codigo ILIKE '%${escSql(q)}%')`
|
||||
@@ -310,6 +322,33 @@ export class CatalogService {
|
||||
return { data: rows.map((p) => this.mapRow(p, p.vl_preco1)), total, page, limit };
|
||||
}
|
||||
|
||||
// Confere que a pauta pertence às pautas do rep (cod_pauta1..6 do cadastro).
|
||||
// codVendedor/idPauta são Int (JWT/query coercida) — interpolação segura.
|
||||
private async assertPautaDoRep(
|
||||
idPauta: number,
|
||||
codVendedor: number,
|
||||
idMatriz: number,
|
||||
): Promise<void> {
|
||||
const prisma = this.cls.get('prisma');
|
||||
if (!prisma) throw new Error('prisma não disponível no CLS');
|
||||
const rows = await prisma.$queryRawUnsafe<{ id_pauta: number }[]>(`
|
||||
SELECT DISTINCT pa.id_pauta
|
||||
FROM vw_pautas pa
|
||||
JOIN vw_representantes r ON pa.codigo IN (
|
||||
r.cod_pauta1, r.cod_pauta2, r.cod_pauta3,
|
||||
r.cod_pauta4, r.cod_pauta5, r.cod_pauta6
|
||||
)
|
||||
WHERE pa.id_empresa = ${idMatriz}
|
||||
AND pa.ativo = 1
|
||||
AND r.codigo = ${codVendedor}
|
||||
AND pa.id_pauta = ${Number(idPauta)}
|
||||
LIMIT 1
|
||||
`);
|
||||
if (!rows[0]) {
|
||||
throw new ForbiddenException('Pauta de preços não vinculada ao seu cadastro');
|
||||
}
|
||||
}
|
||||
|
||||
private mapRow(p: ProdutoRow, preco: string): ProdutoSummary {
|
||||
return {
|
||||
idErp: Number(p.id_erp),
|
||||
|
||||
Reference in New Issue
Block a user