Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 57 additions & 20 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,29 +135,38 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'parceiro-negocio',
title: 'Parceiro Negocio',
data: ["icon:fas fa-file-contract", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/business-partners/cientes"],
// Permissao so de tela: o GET /business-partners/* da logistica (cliente do carregamento) cobria /cientes.
data: ["icon:fas fa-file-contract", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/tela/clientes/parceiro-negocio",
"usa:get:/business-partners/cientes", "usa:post:/sales-person/search"],
canActivate: [authGuard, roleGuard],
component: ParceiroNegocioComponent
},
{
// Antes de parceiro-negocio/:cardCode, senao "novo" seria lido como CardCode
path: 'parceiro-negocio/novo',
title: 'Novo parceiro',
data: ["hidden", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:post:/business-partners/novo"],
data: ["hidden", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:post:/business-partners/novo",
"usa:get:/state", "usa:get:/city/*", "usa:get:/forma-pagamento/liberadas", "usa:post:/sales-person/search",
"usa:post:/locais/search"],
canActivate: [authGuard, roleGuard],
component: NovoParceiroComponent
},
{
path: 'parceiro-negocio/:cardCode',
title: 'Parceiro Negocio',
data: ["hidden", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/business-partners/*"],
data: ["hidden", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/business-partners/*",
"usa:get:/sales-person/*", "usa:get:/locais/*", "usa:post:/sales-person/search", "usa:get:/business-partners/cotacoes-parceiro",
"usa:get:/business-partners/pedido-venda-parceiro", "usa:get:/contrato-venda-futura", "usa:post:/contrato-venda-futura/nextlink", "usa:get:/business-partners/contas-receber",
"usa:post:/business-partners/contas-receber/nextlink", "usa:get:/invoice/cardcode/*/payment", "usa:get:/installment/*/paid", "usa:get:/tax/*/pdf",
"usa:get:/invoice/*/parcela/*"],
canActivate: [authGuard, roleGuard],
component: ParceiroNegocioComponent
},
{
path: 'transferencia',
title: 'Transferências',
data: ["icon:fas fa-exchange-alt", "role:vendedor_admin", "api:get:/sales-person/replace/*/por/*"],
data: ["icon:fas fa-exchange-alt", "role:vendedor_admin", "api:get:/sales-person/replace/*/por/*",
"usa:post:/sales-person/search", "usa:get:/sales-person/*/business-partners"],
canActivate: [authGuard, roleGuard],
component: TransferenciaClientesComponent
},
Expand All @@ -173,7 +182,10 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
path: 'document',
title: 'Vender',
canActivate: [authGuard, roleGuard],
data: ["icon:fas fa-shopping-cart", "role:vendedor", "role:vendedor_admin", "api:post:/quotation/angular"],
data: ["icon:fas fa-shopping-cart", "role:vendedor", "role:vendedor_admin", "api:post:/quotation/angular",
"usa:get:/branch", "usa:post:/business-partners/search", "usa:get:/business-partners/*", "usa:get:/locais/*",
"usa:get:/regioes/localidade/*", "usa:get:/prazo/tabela/*", "usa:get:/forma-pagamento/filial/*/cardcode/*", "usa:post:/item/search/branch/*",
"usa:get:/batch-stock/item-code/*/stock/*", "usa:get:/comissao/tabela/*"],
component: DocumentStatementComponent
},
{
Expand All @@ -187,20 +199,26 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
path: 'cotacao',
title: 'Cotação',
canActivate: [authGuard, roleGuard],
data: ["icon:fas fa-file-alt", "role:vendedor", "role:vendedor_admin", "api:get:/quotation"],
data: ["icon:fas fa-file-alt", "role:vendedor", "role:vendedor_admin", "api:get:/quotation",
"usa:get:/quotation/*", "usa:get:/sales-person/*"],
component: CotacoesStatementComponent
},
{
path: 'pedidos-venda',
title: 'Pedidos',
canActivate: [authGuard, roleGuard],
component: PedidosVendaStatementComponent,
data: ["icon:fas fa-file-signature", "role:vendedor", "role:vendedor_admin", "api:get:/pedido-venda/listar"],
data: ["icon:fas fa-file-signature", "role:vendedor", "role:vendedor_admin", "api:get:/pedido-venda/listar",
"usa:get:/branch", "usa:post:/business-partners/search", "usa:post:/pedido-venda/listar/nextlink", "usa:get:/pedido-venda/raw/*",
"usa:get:/pedido-venda/*/linhas", "usa:get:/sales-person/*", "usa:get:/pedido-venda/pix/*", "usa:get:/pix/transaction/*"],
},
{
path: 'venda-futura',
title: 'Contratos',
data: ["icon:fas fa-file-contract", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/contrato-venda-futura"],
data: ["icon:fas fa-file-contract", "role:vendedor", "role:vendedor_admin", "role:cobranca", "api:get:/contrato-venda-futura",
"usa:get:/branch", "usa:post:/business-partners/search", "usa:post:/contrato-venda-futura/nextlink", "usa:get:/contrato-venda-futura/*",
"usa:get:/contrato-venda-futura/*/produtos", "usa:get:/contrato-venda-futura/entregas/*", "usa:get:/future-sales/pedidos/*", "usa:get:/down-payment/contrato-venda-futura/*",
"usa:get:/contrato-venda-futura/*/negociacao", "usa:get:/business-partners/*", "usa:get:/pix/transaction/*"],
canActivate: [authGuard, roleGuard],
component: VendaFuturaStatementComponent
},
Expand Down Expand Up @@ -257,7 +275,8 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'liberacao-trava',
title: 'Liberação de Trava',
data: ["icon:fas fa-unlock-alt", "role:liberacao_trava", "api:get:/trava/otp"],
data: ["icon:fas fa-unlock-alt", "role:liberacao_trava", "api:get:/trava/otp",
"usa:get:/trava/regras"],
canActivate: [authGuard, roleGuard],
component: LiberacaoTravaComponent
},
Expand All @@ -272,21 +291,27 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'pix',
title: 'PIX',
data: ["icon:fas fa-qrcode", "role:pix", "role:pix_admin", "api:post:/pix/gerar-chave"],
// Permissao so de tela: o POST /pix/gerar-chave tambem gera o PIX do pedido de venda, e liberar ele nao pode
// trazer junto a tela de PIX avulso.
data: ["icon:fas fa-qrcode", "role:pix", "role:pix_admin", "api:get:/tela/financeiro/pix",
"usa:post:/pix/gerar-chave", "usa:get:/branch", "usa:post:/business-partners/search"],
canActivate: [authGuard, roleGuard],
component: PixPageComponent,
},
{
path: 'notas-fiscais',
title: 'Notas Fiscais',
data: ["icon:fas fa-file-invoice-dollar", "sapDocumentKind:nota-fiscal", "role:admin", "role:cobranca", "role:vendedor", "role:vendedor_admin", "api:get:/invoice"],
data: ["icon:fas fa-file-invoice-dollar", "sapDocumentKind:nota-fiscal", "role:admin", "role:cobranca", "role:vendedor", "role:vendedor_admin", "api:get:/invoice",
"usa:get:/invoice/*", "usa:get:/branch", "usa:get:/sales-person/*"],
canActivate: [authGuard, roleGuard],
component: DocumentosSapComponent,
},
{
path: 'notas-xml-lote',
title: 'Notas e XML em lote',
data: ["icon:fas fa-file-archive", "role:nota_fiscal_lote", "api:get:/nota-fiscal-lote"],
data: ["icon:fas fa-file-archive", "role:nota_fiscal_lote", "api:get:/nota-fiscal-lote",
"usa:get:/nota-fiscal-lote/status-sefaz", "usa:get:/nota-fiscal-lote/status-boleto", "usa:get:/nota-fiscal-lote/formas-pagamento", "usa:post:/nota-fiscal-lote/clientes/search",
"usa:get:/branch", "usa:post:/sales-person/search"],
canActivate: [authGuard, roleGuard],
component: NotaFiscalLoteComponent,
},
Expand Down Expand Up @@ -314,7 +339,8 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'importacao-lancamentos',
title: 'Importação de Lançamentos',
data: ["icon:fas fa-file-import", "role:contabil_importacao", "api:post:/journal/importacao/validar"],
data: ["icon:fas fa-file-import", "role:contabil_importacao", "api:post:/journal/importacao/validar",
"usa:get:/journal/importacao/historico"],
canActivate: [authGuard, roleGuard],
component: ImportacaoLancamentoComponent,
},
Expand All @@ -335,14 +361,17 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
title: 'Títulos',
canActivate: [authGuard, roleGuard],
// /cobranca/dominios e nao /cobranca/titulos: o vendedor tem o GET de titulos (aba do parceiro), nao a tela.
data: ["icon:fas fa-list", "role:cobranca", "api:get:/cobranca/dominios"],
data: ["icon:fas fa-list", "role:cobranca", "api:get:/cobranca/dominios",
"usa:get:/cobranca/titulos", "usa:get:/cobranca/titulos/totais", "usa:get:/cobranca/cobradores", "usa:get:/cobranca/titulos/*/*/*/historico",
"usa:get:/branch", "usa:post:/business-partners/search", "usa:post:/sales-person/search"],
component: CobrancaStatementComponent
},
{
path: 'resultado',
title: 'Resultado',
canActivate: [authGuard, roleGuard],
data: ["icon:fas fa-chart-line", "role:cobranca", "api:get:/cobranca/dashboard"],
data: ["icon:fas fa-chart-line", "role:cobranca", "api:get:/cobranca/dashboard",
"usa:get:/cobranca/dashboard/evolucao", "usa:get:/cobranca/cobradores", "usa:get:/branch", "usa:post:/sales-person/search"],
component: CobrancaDashboardComponent
},
]
Expand All @@ -356,7 +385,8 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'ordem-carregamento',
title: 'Carregamento',
data: ["icon:fas fa-truck-ramp-box", "role:logistica", "role:logistica_faturar", "api:get:/carregamento"],
data: ["icon:fas fa-truck-ramp-box", "role:logistica", "role:logistica_faturar", "api:get:/carregamento",
"usa:get:/carregamento/*", "usa:get:/pedido-venda/findLoadOrders", "usa:post:/pedido-venda/findAllLoadOrders", "usa:get:/carregamento/notas/*"],
canActivate: [authGuard, roleGuard],
component: OrdemCarregamentoStatementComponent
},
Expand All @@ -375,7 +405,9 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
{
path: 'localidades',
title: 'Localidades',
data: ["icon:fas fa-map-marker-alt", "role:logistica", "role:logistica_faturar", "api:post:/locais/search"],
// Permissao so de tela: a busca de localidade tambem e filtro de outras telas (novo parceiro, analise de pedidos).
data: ["icon:fas fa-map-marker-alt", "role:logistica", "role:logistica_faturar", "api:get:/tela/logistica/localidades",
"usa:post:/locais/search"],
canActivate: [authGuard, roleGuard],
component: LocalidadeComponent
},
Expand All @@ -401,7 +433,8 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
title: 'Painel de Vendas',
// Escondido do menu (o v2 ocupou o lugar), mas com a mesma restricao de
// papel do RBAC: some da navegacao e segue protegido por URL.
data: ["hidden", "icon:fas fa-chart-line", "role:vendedor", "role:vendedor_admin", "api:get:/painel-vendas/mensal"],
data: ["hidden", "icon:fas fa-chart-line", "role:vendedor", "role:vendedor_admin", "api:get:/painel-vendas/mensal",
"usa:get:/painel-vendas/mensal/*/*", "usa:get:/painel-vendas/mensal/*/*/produtos"],
canActivate: [authGuard, roleGuard],
component: PainelVendasComponent
},
Expand All @@ -410,14 +443,18 @@ import { NotaFiscalLoteComponent } from './modulos/nota-fiscal-lote/componentes/
title: 'Painel de Vendas',
// O grupo agora abre para todos os papeis: sem restricao aqui o item apareceria para quem o backend
// nega (/painel-vendas/** so para vendedor e vendedor_admin), igual ao painel v1 logo acima.
data: ["icon:fas fa-chart-area", "role:vendedor", "role:vendedor_admin", "api:get:/painel-vendas/v2/kpis"],
data: ["icon:fas fa-chart-area", "role:vendedor", "role:vendedor_admin", "api:get:/painel-vendas/v2/kpis",
"usa:get:/painel-vendas/v2/evolucao", "usa:get:/branch", "usa:post:/sales-person/search"],
canActivate: [authGuard, roleGuard],
component: PainelVendasV2Component
},
{
path: 'analise-pedidos',
title: 'Análise Pedidos',
data: ["icon:fas fa-clipboard-list", "role:vendedor_admin", "role:logistica", "role:qualidade", "api:get:/painel/pedidos"],
// Permissao so de tela: o formulario da ordem de carregamento tambem chama GET /painel/pedidos.
data: ["icon:fas fa-clipboard-list", "role:vendedor_admin", "role:logistica", "role:qualidade", "api:get:/tela/relatorios/analise-pedidos",
"usa:get:/painel/pedidos", "usa:get:/branch", "usa:post:/business-partners/search", "usa:post:/sales-person/search", "usa:post:/locais/search",
"usa:post:/item/search"],
canActivate: [authGuard, roleGuard],
component: PainelExpedicaoPedidosComponent
},
Expand Down
13 changes: 12 additions & 1 deletion src/app/core/route-flags.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/**
* Marcacoes do `data` das rotas deste app. Aqui o `data` e um array de strings, fora do padrao do
* Angular: ["icon:fas fa-x", "role:vendedor", "role-not:business_partner", "api:get:/carregamento", "hidden"].
* "usa:get:/branch" lista as outras chamadas de leitura que a tela faz para ser usada (filtros, detalhe, paginacao):
* nao decide nada no menu, so alimenta o aviso da tela de Regras de Acesso ("abre, mas falta liberar ...").
* "api:get:/tela/<caminho da rota>" e uma permissao so de tela: nenhum endpoint atende essa URL, ela existe para a
* regra decidir o item do menu sem liberar junto uma chamada que outras telas tambem usam.
*
* Menu e guards leem por aqui para os dois entenderem a rota do mesmo jeito.
*/

/** Rota do router.config (tem `data`) ou snapshot da rota ativa (tem `routeConfig` e `data`). */
type ComData = { data?: unknown; routeConfig?: { data?: unknown } | null };

export type Marcacao = 'role' | 'role-not' | 'api';
export type Marcacao = 'role' | 'role-not' | 'api' | 'usa';

/** Uma chamada ao backend que a tela declara como principal: "api:get:/carregamento". */
export interface ApiDaRota {
Expand Down Expand Up @@ -55,3 +59,10 @@ export function apisDaRota(route: ComData | null | undefined): ApiDaRota[] {
.map(lerApi)
.filter((it): it is ApiDaRota => it !== null);
}

/** As "usa:" da rota: chamadas de que a tela depende alem da principal. */
export function dependenciasDaRota(route: ComData | null | undefined): ApiDaRota[] {
return flagsDaRota(route, 'usa')
.map(lerApi)
.filter((it): it is ApiDaRota => it !== null);
}
56 changes: 56 additions & 0 deletions src/app/sap/components/regras-acesso/previa-menu.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Route } from '@angular/router';
import { consultasDasRotas, previaDoMenu } from './previa-menu';

class Tela {}

const rotas: Route[] = [
{
path: 'venda', title: 'Venda', data: ['role:vendedor', 'role:cobranca'],
children: [
{ path: 'contratos', title: 'Contratos', component: Tela,
data: ['role:vendedor', 'role:cobranca', 'api:get:/contrato-venda-futura', 'usa:get:/branch', 'usa:get:/contrato-venda-futura/*'] },
{ path: 'vender', title: 'Vender', component: Tela, data: ['role:vendedor', 'api:post:/quotation/angular'] },
],
},
{ path: 'escondida', title: 'Escondida', component: Tela, data: ['hidden', 'api:get:/x'] },
{ path: 'cliente', title: 'Cliente', component: Tela, data: ['role-not:controladoria', 'api:get:/contrato-venda-futura'] },
];

describe('previaDoMenu', () => {
const respostas = {
'get /contrato-venda-futura': true,
'get /branch': true,
'get /contrato-venda-futura/*': false,
'post /quotation/angular': false,
};

it('com a flag, a api: decide, o grupo segue os filhos e a usa: negada vira aviso', () => {
const menu = previaDoMenu(rotas, { perfis: ['controladoria'], respostas, menuPorPermissao: true, modoOperacao: 'external' });

expect(menu.map(i => i.titulo)).toEqual(['Venda']);
expect(menu[0].filhos.map(i => i.titulo)).toEqual(['Contratos']);
expect(menu[0].filhos[0].faltando).toEqual(['GET /contrato-venda-futura/*']);
});

it('sem a flag vale o nome do perfil, e a chamada principal negada também entra no aviso', () => {
const menu = previaDoMenu(rotas, { perfis: ['vendedor'], respostas, menuPorPermissao: false, modoOperacao: 'external' });

const filhos = menu.find(i => i.titulo === 'Venda')!.filhos;
expect(filhos.map(i => i.titulo)).toEqual(['Contratos', 'Vender']);
expect(filhos[1].peloNome).toBeTrue();
expect(filhos[1].faltando).toEqual(['POST /quotation/angular']);
});

it('permissao so de tela (/tela/...) nao aparece como chamada que da 403', () => {
const soTela: Route[] = [{ path: 'pix', title: 'PIX', component: Tela, data: ['role:pix', 'api:get:/tela/financeiro/pix', 'usa:post:/pix/gerar-chave'] }];
const menu = previaDoMenu(soTela, { perfis: ['pix'], respostas: {}, menuPorPermissao: false, modoOperacao: 'external' });

expect(menu[0].faltando).toEqual(['POST /pix/gerar-chave']);
});

it('consultasDasRotas junta api: e usa: sem repetir', () => {
expect(consultasDasRotas(rotas).map(a => a.chave).sort()).toEqual([
'get /branch', 'get /contrato-venda-futura', 'get /contrato-venda-futura/*', 'get /x', 'post /quotation/angular',
]);
});
});
Loading
Loading