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.
- rcb_requests/__init__.py +26 -0
- rcb_requests/client/__init__.py +3 -0
- rcb_requests/client/session.py +122 -0
- rcb_requests/page_elements/__init__.py +13 -0
- rcb_requests/page_elements/form_login.py +43 -0
- rcb_requests/page_elements/popup.py +75 -0
- rcb_requests/page_elements/reneg_consulta_detalhe.py +114 -0
- rcb_requests/page_elements/reneg_filtro_pesquisa.py +81 -0
- rcb_requests/page_elements/sidebar.py +141 -0
- rcb_requests/pages/__init__.py +49 -0
- rcb_requests/pages/acordos_financiamento_consulta_por_contrato/__init__.py +6 -0
- rcb_requests/pages/acordos_financiamento_consulta_por_contrato/acordos_financiamento_consulta_por_contrato_page.py +7 -0
- rcb_requests/pages/acordos_financiamento_exclusao/__init__.py +4 -0
- rcb_requests/pages/acordos_financiamento_exclusao/acordos_financiamento_exclusao_page.py +7 -0
- rcb_requests/pages/acordos_financiamento_inclusao/__init__.py +4 -0
- rcb_requests/pages/acordos_financiamento_inclusao/acordos_financiamento_inclusao_page.py +7 -0
- rcb_requests/pages/acordos_leasing_consulta_por_contrato/__init__.py +4 -0
- rcb_requests/pages/acordos_leasing_consulta_por_contrato/acordos_leasing_consulta_por_contrato_page.py +7 -0
- rcb_requests/pages/acordos_leasing_exclusao/__init__.py +4 -0
- rcb_requests/pages/acordos_leasing_exclusao/acordos_leasing_exclusao_page.py +7 -0
- rcb_requests/pages/acordos_leasing_inclusao/__init__.py +4 -0
- rcb_requests/pages/acordos_leasing_inclusao/acordos_leasing_inclusao_page.py +7 -0
- rcb_requests/pages/calculadora_pesquisa/__init__.py +4 -0
- rcb_requests/pages/calculadora_pesquisa/calculadora_pesquisa_page.py +7 -0
- rcb_requests/pages/consulta_gca/__init__.py +4 -0
- rcb_requests/pages/consulta_gca/consulta_gca_page.py +7 -0
- rcb_requests/pages/home_page.py +11 -0
- rcb_requests/pages/lamina_cancelamento/__init__.py +4 -0
- rcb_requests/pages/lamina_cancelamento/lamina_cancelamento_page.py +7 -0
- rcb_requests/pages/lamina_consulta_por_contrato/__init__.py +4 -0
- rcb_requests/pages/lamina_consulta_por_contrato/lamina_consulta_por_contrato_page.py +7 -0
- rcb_requests/pages/lamina_solicitacao_por_contrato/__init__.py +4 -0
- rcb_requests/pages/lamina_solicitacao_por_contrato/lamina_solicitacao_por_contrato_page.py +7 -0
- rcb_requests/pages/login_page.py +82 -0
- rcb_requests/pages/renegociacao_consulta/__init__.py +9 -0
- rcb_requests/pages/renegociacao_consulta/renegociacao_consulta_detalhe_page.py +16 -0
- rcb_requests/pages/renegociacao_consulta/renegociacao_consulta_page.py +9 -0
- rcb_requests/pages/renegociacao_consulta/renegociacao_result_search.py +80 -0
- rcb_requests/pages/renegociacao_exclusao/__init__.py +4 -0
- rcb_requests/pages/renegociacao_exclusao/renegociacao_exclusao_page.py +7 -0
- rcb_requests/pages/renegociacao_inclusao/__init__.py +4 -0
- rcb_requests/pages/renegociacao_inclusao/renegociacao_inclusao_page.py +7 -0
- rcb_requests/pages/renegociacao_reenvio/__init__.py +4 -0
- rcb_requests/pages/renegociacao_reenvio/renegociacao_reenvio_page.py +7 -0
- rcb_requests/pages/sidebar_pages.py +62 -0
- rcb_requests/pages/usuario_secundario_troca_senha/__init__.py +4 -0
- rcb_requests/pages/usuario_secundario_troca_senha/usuario_secundario_troca_senha_page.py +7 -0
- rcb_requests/shared/__init__.py +6 -0
- rcb_requests/shared/page.py +21 -0
- rcb_requests/shared/page_element.py +28 -0
- rcb_requests/types/__init__.py +37 -0
- rcb_requests/types/captcha.py +4 -0
- rcb_requests/types/reneg_consulta_detalhe.py +126 -0
- rcb_requests/types/reneg_filtro_options.py +47 -0
- rcb_requests/types/reneg_lista_row.py +17 -0
- rcb_requests/types/response.py +7 -0
- rcb_requests/types/sidebar_options.py +60 -0
- rcb_requests-0.1.0.dist-info/METADATA +159 -0
- rcb_requests-0.1.0.dist-info/RECORD +60 -0
- rcb_requests-0.1.0.dist-info/WHEEL +4 -0
rcb_requests/__init__.py
ADDED
|
@@ -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,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
|
+
]
|