feat(api,web): catalogo na area do gerente com todas as pautas e precos

- Menu Catalogo no cockpit do gerente (rota /catalogo existente)
- /catalog/pautas: gestor recebe TODAS as pautas ativas com contagem de
  reps que usam cada uma (rep segue vendo so as suas cod_pauta1..6)
- Detalhe do produto: gestor ve o preco em todas as pautas (rep segue
  restrito as dele)
- Select de pauta mostra "N reps" quando disponivel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 20:40:34 +00:00
parent 725a94ede1
commit 756e56d8d3
4 changed files with 52 additions and 18 deletions

View File

@@ -180,29 +180,49 @@ export class CatalogService {
const idEmpresa = matrizEmpresa(this.cls.get('idEmpresa')); const idEmpresa = matrizEmpresa(this.cls.get('idEmpresa'));
const userId = this.cls.get('userId'); const userId = this.cls.get('userId');
const codVendedor = userId ? parseInt(userId, 10) : 0; const codVendedor = userId ? parseInt(userId, 10) : 0;
const role = this.cls.get('role') ?? 'rep';
interface PautaRow { interface PautaRow {
id_pauta: number; id_pauta: number;
codigo: number; codigo: number;
descricao: string; descricao: string;
qtd_reps?: string;
} }
const rows = await prisma.$queryRawUnsafe<PautaRow[]>(` // Rep vê só as pautas atribuídas a ele (cod_pauta1..6); gestor vê TODAS as
SELECT DISTINCT pa.id_pauta, pa.codigo, TRIM(pa.descricao) AS descricao // pautas ativas, com a contagem de reps que usam cada uma.
FROM vw_pautas pa const rows =
JOIN vw_representantes r ON pa.codigo IN ( role === 'rep'
r.cod_pauta1, r.cod_pauta2, r.cod_pauta3, ? await prisma.$queryRawUnsafe<PautaRow[]>(`
r.cod_pauta4, r.cod_pauta5, r.cod_pauta6 SELECT DISTINCT pa.id_pauta, pa.codigo, TRIM(pa.descricao) AS descricao
) FROM vw_pautas pa
WHERE pa.id_empresa = ${idEmpresa} JOIN vw_representantes r ON pa.codigo IN (
AND pa.ativo = 1 r.cod_pauta1, r.cod_pauta2, r.cod_pauta3,
AND r.codigo = ${codVendedor} r.cod_pauta4, r.cod_pauta5, r.cod_pauta6
ORDER BY pa.codigo )
`); WHERE pa.id_empresa = ${idEmpresa}
AND pa.ativo = 1
AND r.codigo = ${codVendedor}
ORDER BY pa.codigo
`)
: await prisma.$queryRawUnsafe<PautaRow[]>(`
SELECT pa.id_pauta, pa.codigo, TRIM(pa.descricao) AS descricao,
COUNT(DISTINCT r.codigo)::text AS qtd_reps
FROM vw_pautas pa
LEFT 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 = ${idEmpresa}
AND pa.ativo = 1
GROUP BY pa.id_pauta, pa.codigo, pa.descricao
ORDER BY pa.codigo
`);
return rows.map((r) => ({ return rows.map((r) => ({
idPauta: Number(r.id_pauta), idPauta: Number(r.id_pauta),
codigo: Number(r.codigo), codigo: Number(r.codigo),
descricao: r.descricao, descricao: r.descricao,
...(r.qtd_reps != null && { qtdReps: Number(r.qtd_reps) }),
})); }));
} }
@@ -359,12 +379,16 @@ export class CatalogService {
JOIN vw_pautas pa ON pa.id_pauta = pp.id_pauta JOIN vw_pautas pa ON pa.id_pauta = pp.id_pauta
AND pa.id_empresa = ${idEmpresa} AND pa.id_empresa = ${idEmpresa}
AND pa.ativo = 1 AND pa.ativo = 1
JOIN vw_representantes r ON pa.codigo IN ( ${
r.cod_pauta1, r.cod_pauta2, r.cod_pauta3, // Rep vê só as pautas dele; gestor vê os preços de TODAS as pautas
r.cod_pauta4, r.cod_pauta5, r.cod_pauta6 (this.cls.get('role') ?? 'rep') === 'rep'
) ? `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
) AND r.codigo = ${codVendedor}`
: ''
}
WHERE pp.id_produto = ${idErp} WHERE pp.id_produto = ${idErp}
AND r.codigo = ${codVendedor}
AND pp.preco1 > 0 AND pp.preco1 > 0
ORDER BY pa.codigo ORDER BY pa.codigo
`), `),

View File

@@ -161,7 +161,10 @@ export function CatalogPage() {
}} }}
options={pautas?.map((p) => ({ options={pautas?.map((p) => ({
value: p.idPauta, value: p.idPauta,
label: `${p.codigo}${p.descricao}`, label:
p.qtdReps != null
? `${p.codigo}${p.descricao} · ${p.qtdReps} rep${p.qtdReps !== 1 ? 's' : ''}`
: `${p.codigo}${p.descricao}`,
}))} }))}
/> />
</Space> </Space>

View File

@@ -92,6 +92,11 @@ const GERENTE_ITEMS: ItemType[] = [
icon: <FontAwesomeIcon icon={faAddressCard} fixedWidth />, icon: <FontAwesomeIcon icon={faAddressCard} fixedWidth />,
label: 'Clientes', label: 'Clientes',
}, },
{
key: '/catalogo',
icon: <FontAwesomeIcon icon={faBoxesStacked} fixedWidth />,
label: 'Catálogo',
},
{ {
key: '/relatorios', key: '/relatorios',
icon: <FontAwesomeIcon icon={faFileInvoiceDollar} fixedWidth />, icon: <FontAwesomeIcon icon={faFileInvoiceDollar} fixedWidth />,

View File

@@ -27,6 +27,8 @@ export const PautaSchema = z.object({
idPauta: z.number().int(), idPauta: z.number().int(),
codigo: z.number().int(), codigo: z.number().int(),
descricao: z.string(), descricao: z.string(),
// Presente só para gestor: quantos representantes usam esta pauta
qtdReps: z.number().int().optional(),
}); });
export type Pauta = z.infer<typeof PautaSchema>; export type Pauta = z.infer<typeof PautaSchema>;