rcb-requests 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. rcb_requests/__init__.py +26 -0
  2. rcb_requests/client/__init__.py +3 -0
  3. rcb_requests/client/session.py +122 -0
  4. rcb_requests/page_elements/__init__.py +13 -0
  5. rcb_requests/page_elements/form_login.py +43 -0
  6. rcb_requests/page_elements/popup.py +75 -0
  7. rcb_requests/page_elements/reneg_consulta_detalhe.py +114 -0
  8. rcb_requests/page_elements/reneg_filtro_pesquisa.py +81 -0
  9. rcb_requests/page_elements/sidebar.py +141 -0
  10. rcb_requests/pages/__init__.py +49 -0
  11. rcb_requests/pages/acordos_financiamento_consulta_por_contrato/__init__.py +6 -0
  12. rcb_requests/pages/acordos_financiamento_consulta_por_contrato/acordos_financiamento_consulta_por_contrato_page.py +7 -0
  13. rcb_requests/pages/acordos_financiamento_exclusao/__init__.py +4 -0
  14. rcb_requests/pages/acordos_financiamento_exclusao/acordos_financiamento_exclusao_page.py +7 -0
  15. rcb_requests/pages/acordos_financiamento_inclusao/__init__.py +4 -0
  16. rcb_requests/pages/acordos_financiamento_inclusao/acordos_financiamento_inclusao_page.py +7 -0
  17. rcb_requests/pages/acordos_leasing_consulta_por_contrato/__init__.py +4 -0
  18. rcb_requests/pages/acordos_leasing_consulta_por_contrato/acordos_leasing_consulta_por_contrato_page.py +7 -0
  19. rcb_requests/pages/acordos_leasing_exclusao/__init__.py +4 -0
  20. rcb_requests/pages/acordos_leasing_exclusao/acordos_leasing_exclusao_page.py +7 -0
  21. rcb_requests/pages/acordos_leasing_inclusao/__init__.py +4 -0
  22. rcb_requests/pages/acordos_leasing_inclusao/acordos_leasing_inclusao_page.py +7 -0
  23. rcb_requests/pages/calculadora_pesquisa/__init__.py +4 -0
  24. rcb_requests/pages/calculadora_pesquisa/calculadora_pesquisa_page.py +7 -0
  25. rcb_requests/pages/consulta_gca/__init__.py +4 -0
  26. rcb_requests/pages/consulta_gca/consulta_gca_page.py +7 -0
  27. rcb_requests/pages/home_page.py +11 -0
  28. rcb_requests/pages/lamina_cancelamento/__init__.py +4 -0
  29. rcb_requests/pages/lamina_cancelamento/lamina_cancelamento_page.py +7 -0
  30. rcb_requests/pages/lamina_consulta_por_contrato/__init__.py +4 -0
  31. rcb_requests/pages/lamina_consulta_por_contrato/lamina_consulta_por_contrato_page.py +7 -0
  32. rcb_requests/pages/lamina_solicitacao_por_contrato/__init__.py +4 -0
  33. rcb_requests/pages/lamina_solicitacao_por_contrato/lamina_solicitacao_por_contrato_page.py +7 -0
  34. rcb_requests/pages/login_page.py +82 -0
  35. rcb_requests/pages/renegociacao_consulta/__init__.py +9 -0
  36. rcb_requests/pages/renegociacao_consulta/renegociacao_consulta_detalhe_page.py +16 -0
  37. rcb_requests/pages/renegociacao_consulta/renegociacao_consulta_page.py +9 -0
  38. rcb_requests/pages/renegociacao_consulta/renegociacao_result_search.py +80 -0
  39. rcb_requests/pages/renegociacao_exclusao/__init__.py +4 -0
  40. rcb_requests/pages/renegociacao_exclusao/renegociacao_exclusao_page.py +7 -0
  41. rcb_requests/pages/renegociacao_inclusao/__init__.py +4 -0
  42. rcb_requests/pages/renegociacao_inclusao/renegociacao_inclusao_page.py +7 -0
  43. rcb_requests/pages/renegociacao_reenvio/__init__.py +4 -0
  44. rcb_requests/pages/renegociacao_reenvio/renegociacao_reenvio_page.py +7 -0
  45. rcb_requests/pages/sidebar_pages.py +62 -0
  46. rcb_requests/pages/usuario_secundario_troca_senha/__init__.py +4 -0
  47. rcb_requests/pages/usuario_secundario_troca_senha/usuario_secundario_troca_senha_page.py +7 -0
  48. rcb_requests/shared/__init__.py +6 -0
  49. rcb_requests/shared/page.py +21 -0
  50. rcb_requests/shared/page_element.py +28 -0
  51. rcb_requests/types/__init__.py +37 -0
  52. rcb_requests/types/captcha.py +4 -0
  53. rcb_requests/types/reneg_consulta_detalhe.py +126 -0
  54. rcb_requests/types/reneg_filtro_options.py +47 -0
  55. rcb_requests/types/reneg_lista_row.py +17 -0
  56. rcb_requests/types/response.py +7 -0
  57. rcb_requests/types/sidebar_options.py +60 -0
  58. rcb_requests-0.1.0.dist-info/METADATA +159 -0
  59. rcb_requests-0.1.0.dist-info/RECORD +60 -0
  60. rcb_requests-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,26 @@
1
+ """Cliente async (Page Object) para automação do portal RCB Santander."""
2
+
3
+ from rcb_requests.types import (
4
+ AcordosFinanciamento,
5
+ AcordosLeasing,
6
+ Calculadora,
7
+ ConsultaGCA,
8
+ Lamina,
9
+ Renegociacao,
10
+ SidebarOption,
11
+ UsuarioSecundario,
12
+ )
13
+
14
+ __version__ = "0.1.0"
15
+
16
+ __all__ = [
17
+ "__version__",
18
+ "SidebarOption",
19
+ "AcordosLeasing",
20
+ "AcordosFinanciamento",
21
+ "Calculadora",
22
+ "ConsultaGCA",
23
+ "Lamina",
24
+ "Renegociacao",
25
+ "UsuarioSecundario",
26
+ ]
@@ -0,0 +1,3 @@
1
+ from .session import Session
2
+
3
+ __all__ = ['Session']
@@ -0,0 +1,122 @@
1
+ import logging
2
+
3
+ from aiohttp import ClientSession
4
+ from bs4 import BeautifulSoup
5
+ from utils_rpa import extract_inputs
6
+
7
+
8
+ class Session:
9
+ """Encapsula um ``aiohttp.ClientSession`` e mantém o estado da última página.
10
+
11
+ Responsável apenas pelo transporte HTTP e pelo parsing do HTML. As páginas e
12
+ elementos recebem uma instância desta classe por composição.
13
+ """
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ def __init__(self, client: ClientSession):
18
+ self._client = client
19
+ self.last_page : BeautifulSoup | None = None
20
+
21
+ async def get(self, url: str, params: dict | None = None) -> BeautifulSoup:
22
+ self.logger.debug('Fazendo GET request')
23
+ async with self._client.get(
24
+ url,
25
+ params=params,
26
+ allow_redirects=True,
27
+ ) as response:
28
+ self.last_page = self.get_soup(await response.text())
29
+ return self.last_page
30
+
31
+ async def post(
32
+ self,
33
+ url: str,
34
+ json: dict | None = None,
35
+ data: dict | None = None,
36
+ ) -> BeautifulSoup:
37
+ self.logger.debug('Fazendo POST request')
38
+ async with self._client.post(
39
+ url,
40
+ json=json,
41
+ data=data,
42
+ allow_redirects=True,
43
+ ) as response:
44
+ self.last_page = self.get_soup(await response.text())
45
+ return self.last_page
46
+
47
+ async def close(self):
48
+ self.logger.debug('Fechando sessão')
49
+ await self._client.close()
50
+
51
+ def extract_inputs(self, soup: BeautifulSoup) -> dict:
52
+ self.logger.debug('Extraindo inputs')
53
+ return extract_inputs(soup)
54
+
55
+ def create_payload(self, clicked_element: BeautifulSoup) -> tuple[dict[str, str], str]:
56
+ """Cria o payload ideal para cada requisição no RCB, com base no elemento clicado.
57
+
58
+ Args:
59
+ clicked_element (BeautifulSoup): Elemento em que foi realizado o clique
60
+
61
+ Returns:
62
+ tuple[dict[str, str], str]: payload key:value e url (action do form)
63
+ """
64
+ self.logger.debug('Criando payload')
65
+ element_name = clicked_element.name
66
+ form = clicked_element.find_parent('form')
67
+ if form is None:
68
+ raise ValueError('Elemento clicado não está dentro de um <form>.')
69
+
70
+ data = self.extract_inputs(form)
71
+ tag_id = clicked_element.get('id', '')
72
+ form_id = form.get('id', '')
73
+
74
+ data.update({
75
+ 'ice.event.captured': tag_id,
76
+ 'javax.faces.source': tag_id,
77
+ form_id: form_id,
78
+ f'{form_id}_SUBMIT': 1,
79
+ })
80
+
81
+ match element_name:
82
+ case 'div':
83
+ self.logger.info('click na div')
84
+ a_tag = clicked_element.find('a')
85
+ a_id = a_tag.get('id', '') if a_tag else ''
86
+ data.update({
87
+ 'ice.focus': a_id,
88
+ 'ice.event.target': a_id,
89
+ tag_id: tag_id,
90
+ })
91
+ case 'input':
92
+ self.logger.info('click no input')
93
+ input_value = clicked_element.get('value', '')
94
+ data.update({
95
+ 'ice.focus': tag_id,
96
+ 'ice.event.target': tag_id,
97
+ tag_id: input_value,
98
+ })
99
+ case 'select':
100
+ self.logger.info('click em select')
101
+ parent_span = clicked_element.find_parent('span', {'id': True})
102
+ data.update({
103
+ 'ice.focus': tag_id,
104
+ 'ice.event.target': tag_id,
105
+ 'ice.event.captured': parent_span.get('id', '') if parent_span else tag_id,
106
+ 'javax.faces.source': parent_span.get('id', '') if parent_span else tag_id,
107
+ })
108
+ case _:
109
+ self.logger.info('click no default')
110
+ data.update({
111
+ 'ice.focus': tag_id,
112
+ 'ice.event.target': tag_id,
113
+ tag_id: tag_id,
114
+ })
115
+ return data, form.attrs.get('action', '')
116
+
117
+ def save_last_page_debug(self, filename: str = 'last_page') -> None:
118
+ with open(f'{filename}.html', 'w', encoding='utf-8') as f:
119
+ f.write(self.last_page.prettify())
120
+
121
+ def get_soup(self, html: str) -> BeautifulSoup:
122
+ return BeautifulSoup(html, 'html.parser')
@@ -0,0 +1,13 @@
1
+ from .form_login import FormLogin
2
+ from .popup import Popup
3
+ from .reneg_consulta_detalhe import RenegConsultaDetalhe
4
+ from .reneg_filtro_pesquisa import RenegFiltroPesquisa
5
+ from .sidebar import Sidebar
6
+
7
+ __all__ = [
8
+ 'FormLogin',
9
+ 'Popup',
10
+ 'RenegConsultaDetalhe',
11
+ 'RenegFiltroPesquisa',
12
+ 'Sidebar',
13
+ ]
@@ -0,0 +1,43 @@
1
+
2
+ from rcb_requests.shared import PageElement
3
+
4
+
5
+ class FormLogin(PageElement):
6
+ __locator__ = '#j_id_x'
7
+
8
+ # Locators
9
+ _captcha_locator = '#imgElem1'
10
+ _button_login = '[id="j_id_x:__1l"]'
11
+
12
+ # Nomes dos campos (chaves do payload)
13
+ _cnpj = 'j_id_x:__13_2'
14
+ _username = 'j_id_x:__16'
15
+ _password = 'j_id_x:__18'
16
+ _captcha = 'j_id_x:__1j_input'
17
+
18
+ @property
19
+ def captcha_image(self) -> str | None:
20
+ img = self.element.select_one(self._captcha_locator)
21
+ return img.get('src').split(",")[1].strip() if img else None
22
+
23
+ async def login(self, cnpj: str, username: str, password: str, captcha: str):
24
+ """Realiza o login no sistema.
25
+
26
+ Args:
27
+ cnpj: CNPJ do usuário.
28
+ username: Nome de usuário do usuário.
29
+ password: Senha do usuário.
30
+ captcha: Texto da imagem de captcha.
31
+ """
32
+ button = self.element.select_one(self._button_login)
33
+ if button is None:
34
+ raise ValueError(f'Botão de login não encontrado ({self._button_login}).')
35
+
36
+ payload, url = self.session.create_payload(button)
37
+ payload.update({
38
+ self._cnpj: cnpj,
39
+ self._username: username,
40
+ self._password: password,
41
+ self._captcha: captcha,
42
+ })
43
+ await self.session.post(url, data=payload)
@@ -0,0 +1,75 @@
1
+
2
+ from rcb_requests.shared import PageElement
3
+
4
+
5
+ class Popup(PageElement):
6
+ """Diálogo (ICEfaces ace:dialog) atualmente visível na página.
7
+
8
+ Os ids externos (ex.: ``j_id_84_main``) são gerados dinamicamente e mudam a
9
+ cada view, então não servem de locator. O que identifica um popup *visível*
10
+ é a AUSÊNCIA da classe ``ace-dialog-hidden``. Cada popup traz o próprio
11
+ ``<form>`` (com ViewState) e botões, então reaproveitamos
12
+ ``session.create_payload`` para interagir.
13
+ """
14
+
15
+ # O :not(...) já descarta os diálogos ocultos (classe ``ace-dialog-hidden``),
16
+ # sobrando apenas o popup efetivamente visível.
17
+ __locator__ = 'div[id$="_main"][class*="dialog"]:not([class*="ace-dialog-hidden"])'
18
+
19
+ _title_locator = '.pop-titulo'
20
+ _message_locator = '.pop-msg'
21
+ _button_locator = 'input[type="submit"], .botao'
22
+
23
+ @property
24
+ def is_open(self) -> bool:
25
+ return self.element is not None
26
+
27
+ @property
28
+ def title(self) -> str | None:
29
+ el = self.element
30
+ node = el.select_one(self._title_locator) if el else None
31
+ return node.get_text(strip=True) if node else None
32
+
33
+ @property
34
+ def message(self) -> str | None:
35
+ el = self.element
36
+ node = el.select_one(self._message_locator) if el else None
37
+ return node.get_text(strip=True) if node else None
38
+
39
+ @property
40
+ def buttons(self) -> list[str]:
41
+ el = self.element
42
+ if el is None:
43
+ return []
44
+ return [b.get('value', '').strip() for b in el.select(self._button_locator)]
45
+
46
+ async def click_button(self, value: str|None=None):
47
+ """Clica no botão do popup cujo ``value`` corresponda (case-insensitive).
48
+ Se ``value`` for ``None``, clica no ultimo botão.
49
+ """
50
+ el = self.element
51
+ if el is None:
52
+ raise RuntimeError('Nenhum popup aberto para interagir.')
53
+ buttons = el.select(self._button_locator)
54
+ if value is None:
55
+ button = buttons[-1]
56
+ else:
57
+ button = next(
58
+ (
59
+ b for b in el.select(self._button_locator)
60
+ if b.get('value', '').strip().casefold() == value.casefold()
61
+ ),
62
+ None,
63
+ )
64
+ if button is None:
65
+ raise ValueError(
66
+ f'Botão {value!r} não encontrado. Disponíveis: {self.buttons}'
67
+ )
68
+ payload, url = self.session.create_payload(button)
69
+ await self.session.post(url, data=payload)
70
+
71
+ async def confirm(self):
72
+ await self.click_button('Confirmar')
73
+
74
+ async def cancel(self):
75
+ await self.click_button('Voltar')
@@ -0,0 +1,114 @@
1
+ from rcb_requests.shared import PageElement
2
+ from rcb_requests.types.reneg_consulta_detalhe import (
3
+ RenegConsultaContratoDados,
4
+ RenegConsultaDetalheDados,
5
+ RenegConsultaRenegociacaoDados,
6
+ )
7
+
8
+
9
+ class RenegConsultaDetalhe(PageElement):
10
+ """Formulário de detalhe da consulta de renegociação (``form#j_id_x``)."""
11
+
12
+ __locator__ = 'form#j_id_x[action*="consulta/consulta.jsf"]'
13
+
14
+ _CONTRATO_FIELDS: dict[str, str] = {
15
+ 'contrato': 'j_id_x:__11',
16
+ 'cpf_cnpj': 'j_id_x:cnpjCpfCliente',
17
+ 'nome_cliente': 'j_id_x:__14',
18
+ 'empresa': 'j_id_x:empresa',
19
+ 'subsegmento': 'j_id_x:subsegmento',
20
+ 'produto': 'j_id_x:produto',
21
+ 'dt_emissao': 'j_id_x:dtEmissao',
22
+ 'dt_termino': 'j_id_x:dtTermino',
23
+ 'dt_vencimento': 'j_id_x:dtVencimento',
24
+ 'taxa_contrato': 'j_id_x:taxaContrato',
25
+ 'valor_principal': 'j_id_x:valorPrincipal',
26
+ 'valor_juros': 'j_id_x:valorJuros',
27
+ 'qtde_parc_vencidas': 'j_id_x:qtdeParcVencidas',
28
+ 'valor_parc_vencidas':'j_id_x:valorParcVencidas',
29
+ 'dias_atraso': 'j_id_x:diasAtraso',
30
+ 'qtde_parc_a_vencer': 'j_id_x:qtdeParcAVencer',
31
+ 'valor_parc_a_vencer':'j_id_x:valorParcAVencer',
32
+ 'saldo_gca': 'j_id_x:saldoGca',
33
+ 'saldo_renegociar': 'j_id_x:saldoRenegociar',
34
+ }
35
+
36
+ _RENEGOCIACAO_FIELDS: dict[str, str] = {
37
+ 'renegociacao': 'j_id_x:renegociacao',
38
+ 'status': 'j_id_x:status',
39
+ 'produto': 'j_id_x:produtoR',
40
+ 'campanha': 'j_id_x:campanha',
41
+ 'taxa_renegociacao': 'j_id_x:txReneg',
42
+ 'pct_desc_principal': 'j_id_x:descPrinc',
43
+ 'valor_desc_principal': 'j_id_x:vlDesPrinc',
44
+ 'pct_desc_juros': 'j_id_x:descJuros',
45
+ 'valor_desc_juros': 'j_id_x:vlDescJuros',
46
+ 'pct_dispensa_gca': 'j_id_x:pctDispGca',
47
+ 'valor_dispensa_gca': 'j_id_x:vlDispGca',
48
+ 'valor_comissao': 'j_id_x:vlComissao',
49
+ 'qtde_parcelas_entrada': 'j_id_x:qtdeParcEntrada',
50
+ 'valor_entrada': 'j_id_x:vlEntrada',
51
+ 'dt_vencimento_entrada': 'j_id_x:dtVctoEntrada',
52
+ 'dt_vencimento_parcela': 'j_id_x:dtVctoParcela',
53
+ 'cod_escritorio_escob': 'j_id_x:codEscob',
54
+ 'nome_escob': 'j_id_x:nomeEscob',
55
+ 'email_cliente': 'j_id_x:email',
56
+ 'valor_iof': 'j_id_x:vlIOF',
57
+ 'saldo_a_renegociar': 'j_id_x:saldo',
58
+ 'saldo_renegociado': 'j_id_x:saldoRenegociado',
59
+ 'qtde_parcelas': 'j_id_x:qtdeParcelas',
60
+ 'valor_parcela': 'j_id_x:vlParcela',
61
+ }
62
+
63
+ _CONTRATO_RADIOS: dict[str, str] = {
64
+ 'contrato_com_seguro': 'j_id_x:__28',
65
+ }
66
+
67
+ _RENEGOCIACAO_RADIOS: dict[str, str] = {
68
+ 'origem': 'j_id_x:__46',
69
+ }
70
+
71
+ def __init__(self, session):
72
+ super().__init__(session)
73
+ self._dados: RenegConsultaDetalheDados | None = None
74
+
75
+ @property
76
+ def dados(self) -> RenegConsultaDetalheDados:
77
+ if self._dados is None:
78
+ self._dados = self._parse()
79
+ return self._dados
80
+
81
+ def _text(self, field_id: str) -> str:
82
+ inp = self.element.select_one(f'input[name="{field_id}_input"]')
83
+ return inp.get('value', '').strip() if inp else ''
84
+
85
+ def _radio_label(self, name: str) -> str:
86
+ for inp in self.element.select(f'input[name="{name}"][type="radio"]'):
87
+ if inp.get('checked') is not None:
88
+ label = self.element.select_one(f'label[for="{inp["id"]}"]')
89
+ return label.get_text(strip=True) if label else inp.get('value', '')
90
+ return ''
91
+
92
+ def _parse_group(
93
+ self,
94
+ fields: dict[str, str],
95
+ radios: dict[str, str],
96
+ dataclass: type,
97
+ ):
98
+ values = {attr: self._text(field_id) for attr, field_id in fields.items()}
99
+ values.update({attr: self._radio_label(name) for attr, name in radios.items()})
100
+ return dataclass(**values)
101
+
102
+ def _parse(self) -> RenegConsultaDetalheDados:
103
+ return RenegConsultaDetalheDados(
104
+ contrato=self._parse_group(
105
+ self._CONTRATO_FIELDS,
106
+ self._CONTRATO_RADIOS,
107
+ RenegConsultaContratoDados,
108
+ ),
109
+ renegociacao=self._parse_group(
110
+ self._RENEGOCIACAO_FIELDS,
111
+ self._RENEGOCIACAO_RADIOS,
112
+ RenegConsultaRenegociacaoDados,
113
+ ),
114
+ )
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import TYPE_CHECKING
5
+
6
+ from rcb_requests.shared import PageElement
7
+ from rcb_requests.types.reneg_filtro_options import Empresa, StatusRenegociacao
8
+
9
+ if TYPE_CHECKING:
10
+ from rcb_requests.pages.renegociacao_consulta.renegociacao_result_search import (
11
+ RenegociacaoResultSearchPage,
12
+ )
13
+
14
+
15
+ class RenegFiltroPesquisa(PageElement):
16
+ """Formulário de filtro da consulta de renegociação (``form#j_id_x``)."""
17
+
18
+ __locator__ = 'form#j_id_x'
19
+
20
+ _field_empresa = 'j_id_x:empresa_input'
21
+ _field_contrato = 'j_id_x:contrato_input'
22
+ _field_renegociacao = 'j_id_x:renegociacao_input'
23
+ _field_cpf_cnpj = 'j_id_x:cnpjCpf_input'
24
+ _field_status = 'j_id_x:status_input'
25
+ _field_dt_inicio = 'j_id_x:dtInicioSolic_input'
26
+ _field_dt_fim = 'j_id_x:dtFimSolic_input'
27
+ _button_continuar = 'div.ice-pushbutton[id="j_id_x:__1j"]'
28
+
29
+ @staticmethod
30
+ def _field_value(value: str | Enum | None) -> str | None:
31
+ if not value:
32
+ return ''
33
+ return value.value if isinstance(value, Enum) else value
34
+
35
+ async def submit_filter(
36
+ self,
37
+ *,
38
+ empresa: Empresa | str | None = '',
39
+ contrato: str | None = '',
40
+ renegociacao: str | None = '',
41
+ cpf_cnpj: str | None = '',
42
+ status: StatusRenegociacao | str | None = '',
43
+ dt_inicio_solicitacao: str | None = '',
44
+ dt_fim_solicitacao: str | None = '',
45
+ ) -> RenegociacaoResultSearchPage:
46
+ """Submete o filtro clicando em *Continuar*.
47
+
48
+ Apenas os parâmetros informados são enviados; os demais permanecem com o
49
+ valor já presente no formulário (vazio por padrão).
50
+
51
+ Args:
52
+ empresa: Código ou :class:`Empresa` (ex.: ``Empresa.BANCO_SANTANDER_BRASIL``).
53
+ contrato: Número do contrato (até 11 dígitos).
54
+ renegociacao: Número da renegociação (até 11 dígitos).
55
+ cpf_cnpj: CPF ou CNPJ do cliente (com ou sem máscara).
56
+ status: Sigla ou :class:`StatusRenegociacao` (ex.: ``StatusRenegociacao.APROVADA``).
57
+ dt_inicio_solicitacao: Data início da solicitação (``DD/MM/AAAA``).
58
+ dt_fim_solicitacao: Data fim da solicitação (``DD/MM/AAAA``).
59
+ """
60
+ button = self.element.select_one(self._button_continuar)
61
+ if button is None:
62
+ raise ValueError(f'Botão Continuar não encontrado ({self._button_continuar}).')
63
+
64
+ payload, url = self.session.create_payload(button)
65
+
66
+ payload.update({
67
+ self._field_empresa: self._field_value(empresa),
68
+ self._field_contrato: contrato,
69
+ self._field_renegociacao: renegociacao,
70
+ self._field_cpf_cnpj: cpf_cnpj,
71
+ self._field_status: self._field_value(status),
72
+ self._field_dt_inicio: dt_inicio_solicitacao,
73
+ self._field_dt_fim: dt_fim_solicitacao,
74
+ })
75
+ await self.session.post(url, data=payload)
76
+
77
+ from rcb_requests.pages.renegociacao_consulta.renegociacao_result_search import (
78
+ RenegociacaoResultSearchPage,
79
+ )
80
+
81
+ return RenegociacaoResultSearchPage(self.session)
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Literal, overload
4
+
5
+ from bs4 import Tag
6
+
7
+ from rcb_requests.shared import PageElement
8
+ from rcb_requests.types import (
9
+ AcordosFinanciamento,
10
+ AcordosLeasing,
11
+ Calculadora,
12
+ ConsultaGCA,
13
+ Lamina,
14
+ Renegociacao,
15
+ SidebarOption,
16
+ UsuarioSecundario,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from rcb_requests.pages.acordos_financiamento_consulta_por_contrato import (
21
+ AcordosFinanciamentoConsultaPorContratoPage,
22
+ )
23
+ from rcb_requests.pages.acordos_financiamento_exclusao import (
24
+ AcordosFinanciamentoExclusaoPage,
25
+ )
26
+ from rcb_requests.pages.acordos_financiamento_inclusao import (
27
+ AcordosFinanciamentoInclusaoPage,
28
+ )
29
+ from rcb_requests.pages.acordos_leasing_consulta_por_contrato import (
30
+ AcordosLeasingConsultaPorContratoPage,
31
+ )
32
+ from rcb_requests.pages.acordos_leasing_exclusao import AcordosLeasingExclusaoPage
33
+ from rcb_requests.pages.acordos_leasing_inclusao import AcordosLeasingInclusaoPage
34
+ from rcb_requests.pages.calculadora_pesquisa import CalculadoraPesquisaPage
35
+ from rcb_requests.pages.consulta_gca import ConsultaGcaPage
36
+ from rcb_requests.pages.home_page import HomePage
37
+ from rcb_requests.pages.lamina_cancelamento import LaminaCancelamentoPage
38
+ from rcb_requests.pages.lamina_consulta_por_contrato import LaminaConsultaPorContratoPage
39
+ from rcb_requests.pages.lamina_solicitacao_por_contrato import (
40
+ LaminaSolicitacaoPorContratoPage,
41
+ )
42
+ from rcb_requests.pages.renegociacao_consulta import RenegociacaoConsultaPage
43
+ from rcb_requests.pages.renegociacao_exclusao import RenegociacaoExclusaoPage
44
+ from rcb_requests.pages.renegociacao_inclusao import RenegociacaoInclusaoPage
45
+ from rcb_requests.pages.renegociacao_reenvio import RenegociacaoReenvioPage
46
+ from rcb_requests.pages.usuario_secundario_troca_senha import (
47
+ UsuarioSecundarioTrocaSenhaPage,
48
+ )
49
+
50
+
51
+ class Sidebar(PageElement):
52
+ """Menu lateral (form ``mnu1``) do RCB."""
53
+
54
+ __locator__ = 'div#global div#container div#coluna1 form'
55
+
56
+ def _find_link(self, menu: str, submenu: str) -> Tag:
57
+ selector = (
58
+ f'div.ui-panel[id^="mnu1:panel"]'
59
+ f':has(label.menutitle:-soup-contains("{menu}"))'
60
+ f' div.ice-linkbutton.submenu'
61
+ )
62
+ for link in self.element.select(selector):
63
+ a = link.select_one('a')
64
+ if a and a.get_text(strip=True) == submenu:
65
+ return link
66
+ raise ValueError(f'Opção {menu!r} > {submenu!r} não encontrada na sidebar.')
67
+
68
+ @overload
69
+ async def click_option(
70
+ self, option: Literal[AcordosLeasing.CONSULTA_POR_CONTRATO],
71
+ ) -> AcordosLeasingConsultaPorContratoPage: ...
72
+
73
+ @overload
74
+ async def click_option(self, option: Literal[AcordosLeasing.INCLUSAO]) -> AcordosLeasingInclusaoPage: ...
75
+
76
+ @overload
77
+ async def click_option(self, option: Literal[AcordosLeasing.EXCLUSAO]) -> AcordosLeasingExclusaoPage: ...
78
+
79
+ @overload
80
+ async def click_option(
81
+ self, option: Literal[AcordosFinanciamento.CONSULTA_POR_CONTRATO],
82
+ ) -> AcordosFinanciamentoConsultaPorContratoPage: ...
83
+
84
+ @overload
85
+ async def click_option(
86
+ self, option: Literal[AcordosFinanciamento.INCLUSAO],
87
+ ) -> AcordosFinanciamentoInclusaoPage: ...
88
+
89
+ @overload
90
+ async def click_option(
91
+ self, option: Literal[AcordosFinanciamento.EXCLUSAO],
92
+ ) -> AcordosFinanciamentoExclusaoPage: ...
93
+
94
+ @overload
95
+ async def click_option(
96
+ self, option: Literal[Calculadora.PESQUISA_CALCULADORA],
97
+ ) -> CalculadoraPesquisaPage: ...
98
+
99
+ @overload
100
+ async def click_option(self, option: Literal[ConsultaGCA.CONSULTAR]) -> ConsultaGcaPage: ...
101
+
102
+ @overload
103
+ async def click_option(
104
+ self, option: Literal[Lamina.CONSULTA_POR_CONTRATO],
105
+ ) -> LaminaConsultaPorContratoPage: ...
106
+
107
+ @overload
108
+ async def click_option(
109
+ self, option: Literal[Lamina.SOLICITACAO_POR_CONTRATO],
110
+ ) -> LaminaSolicitacaoPorContratoPage: ...
111
+
112
+ @overload
113
+ async def click_option(self, option: Literal[Lamina.CANCELAMENTO]) -> LaminaCancelamentoPage: ...
114
+
115
+ @overload
116
+ async def click_option(self, option: Literal[Renegociacao.CONSULTA]) -> RenegociacaoConsultaPage: ...
117
+
118
+ @overload
119
+ async def click_option(self, option: Literal[Renegociacao.INCLUSAO]) -> RenegociacaoInclusaoPage: ...
120
+
121
+ @overload
122
+ async def click_option(self, option: Literal[Renegociacao.EXCLUSAO]) -> RenegociacaoExclusaoPage: ...
123
+
124
+ @overload
125
+ async def click_option(self, option: Literal[Renegociacao.REENVIO]) -> RenegociacaoReenvioPage: ...
126
+
127
+ @overload
128
+ async def click_option(
129
+ self, option: Literal[UsuarioSecundario.TROCA_DE_SENHA],
130
+ ) -> UsuarioSecundarioTrocaSenhaPage: ...
131
+
132
+ @overload
133
+ async def click_option(self, option: SidebarOption) -> HomePage: ...
134
+
135
+ async def click_option(self, option: SidebarOption) -> HomePage:
136
+ from rcb_requests.pages.sidebar_pages import page_class_for
137
+
138
+ link = self._find_link(option.menu, option.submenu)
139
+ payload, url = self.session.create_payload(link)
140
+ await self.session.post(url, data=payload)
141
+ return page_class_for(option)(self.session)
@@ -0,0 +1,49 @@
1
+ from .acordos_financiamento_consulta_por_contrato import (
2
+ AcordosFinanciamentoConsultaPorContratoPage,
3
+ )
4
+ from .acordos_financiamento_exclusao import AcordosFinanciamentoExclusaoPage
5
+ from .acordos_financiamento_inclusao import AcordosFinanciamentoInclusaoPage
6
+ from .acordos_leasing_consulta_por_contrato import AcordosLeasingConsultaPorContratoPage
7
+ from .acordos_leasing_exclusao import AcordosLeasingExclusaoPage
8
+ from .acordos_leasing_inclusao import AcordosLeasingInclusaoPage
9
+ from .calculadora_pesquisa import CalculadoraPesquisaPage
10
+ from .consulta_gca import ConsultaGcaPage
11
+ from .home_page import HomePage
12
+ from .lamina_cancelamento import LaminaCancelamentoPage
13
+ from .lamina_consulta_por_contrato import LaminaConsultaPorContratoPage
14
+ from .lamina_solicitacao_por_contrato import LaminaSolicitacaoPorContratoPage
15
+ from .login_page import LoginPage
16
+ from .renegociacao_consulta import (
17
+ RenegociacaoConsultaDetalhePage,
18
+ RenegociacaoConsultaPage,
19
+ RenegociacaoResultSearchPage,
20
+ )
21
+ from .renegociacao_exclusao import RenegociacaoExclusaoPage
22
+ from .renegociacao_inclusao import RenegociacaoInclusaoPage
23
+ from .renegociacao_reenvio import RenegociacaoReenvioPage
24
+ from .sidebar_pages import page_class_for
25
+ from .usuario_secundario_troca_senha import UsuarioSecundarioTrocaSenhaPage
26
+
27
+ __all__ = [
28
+ 'AcordosFinanciamentoConsultaPorContratoPage',
29
+ 'AcordosFinanciamentoExclusaoPage',
30
+ 'AcordosFinanciamentoInclusaoPage',
31
+ 'AcordosLeasingConsultaPorContratoPage',
32
+ 'AcordosLeasingExclusaoPage',
33
+ 'AcordosLeasingInclusaoPage',
34
+ 'CalculadoraPesquisaPage',
35
+ 'ConsultaGcaPage',
36
+ 'HomePage',
37
+ 'LaminaCancelamentoPage',
38
+ 'LaminaConsultaPorContratoPage',
39
+ 'LaminaSolicitacaoPorContratoPage',
40
+ 'LoginPage',
41
+ 'RenegociacaoConsultaPage',
42
+ 'RenegociacaoConsultaDetalhePage',
43
+ 'RenegociacaoResultSearchPage',
44
+ 'RenegociacaoExclusaoPage',
45
+ 'RenegociacaoInclusaoPage',
46
+ 'RenegociacaoReenvioPage',
47
+ 'UsuarioSecundarioTrocaSenhaPage',
48
+ 'page_class_for',
49
+ ]
@@ -0,0 +1,6 @@
1
+ from .acordos_financiamento_consulta_por_contrato_page import (
2
+ AcordosFinanciamentoConsultaPorContratoPage,
3
+ )
4
+
5
+ __all__ = ['AcordosFinanciamentoConsultaPorContratoPage']
6
+