diff --git a/modules/bnporc/enterprise/browser.py b/modules/bnporc/enterprise/browser.py index cd6a8b944e0a62f52a05d62604edcdd03c649cfe..e7b752d27bd02924caf0ea363d8ee277819bbea1 100644 --- a/modules/bnporc/enterprise/browser.py +++ b/modules/bnporc/enterprise/browser.py @@ -19,14 +19,13 @@ from __future__ import unicode_literals -import re - from datetime import datetime from dateutil.rrule import rrule, MONTHLY from dateutil.relativedelta import relativedelta from weboob.browser import LoginBrowser, need_login +from weboob.capabilities.base import find_object from weboob.capabilities.bank import Account from weboob.exceptions import BrowserIncorrectPassword, BrowserForbidden from weboob.browser.url import URL @@ -34,7 +33,7 @@ from .pages import ( LoginPage, AuthPage, AccountsPage, AccountHistoryViewPage, AccountHistoryPage, - ActionNeededPage, TransactionPage, TokenPage, InvestPage + ActionNeededPage, TransactionPage, MarketPage, InvestPage ) @@ -61,8 +60,8 @@ class BNPEnterprise(LoginBrowser): transaction_detail = URL(r'/NCCPresentationWeb/e21/getOptBDDF.do', TransactionPage) invest = URL(r'/opcvm/lister-composition/afficher.do', InvestPage) - # the token page is used only if there are several type market accounts - token_inv = URL(r'/opcvm/lister-portefeuilles/afficher.do', TokenPage) + # The Market page is used only if there are several market accounts + market = URL(r'/opcvm/lister-portefeuilles/afficher.do', MarketPage) renew_pass = URL('/sommaire/PseRedirectPasswordConnect', ActionNeededPage) @@ -72,7 +71,7 @@ def __init__(self, *args, **kwargs): def do_login(self): self.login.go() - if self.login.is_here() is False: + if not self.login.is_here(): return data = {} @@ -89,22 +88,26 @@ def do_login(self): @need_login def get_accounts_list(self): accounts = [] - try: - self.token_inv.go() - if self.token_inv.is_here(): - marketaccount = self.page.market_search() - else: - # redirected to invest page - # it means that there is only 1 market account among the ones showed - marketaccount = [self.page.get_market_account_label()] - except BrowserForbidden: - marketaccount = [] - + # Fetch checking accounts: for account in self.accounts.stay_or_go().iter_accounts(): - label_tmp = re.search(r'(\d{4,})', account.label) - if (label_tmp and label_tmp.group(0) in marketaccount) or account.label in marketaccount: - account.type = Account.TYPE_MARKET accounts.append(account) + # Fetch market accounts: + try: + self.market.go() + if self.market.is_here(): + for market_account in self.page.iter_market_accounts(): + market_account.parent = find_object(accounts, label=market_account._parent) + accounts.append(market_account) + + elif self.invest.is_here(): + # Redirected to invest page, meaning there is only 1 market account. + # We thus create an Account object for this unique market account. + account = self.page.get_unique_market_account() + account.parent = find_object(accounts, label=account._parent) + accounts.append(account) + + except BrowserForbidden: + pass return accounts @@ -116,6 +119,9 @@ def get_account(self, _id): @need_login def iter_history(self, account): + # There is no available history for market accounts + if account.type == Account.TYPE_MARKET: + return [] return self._iter_history_base(account) def _iter_history_base(self, account): @@ -144,24 +150,30 @@ def _iter_history_base(self, account): @need_login def iter_coming_operations(self, account): + # There is no available coming operation for market accounts + if account.type == Account.TYPE_MARKET: + return [] + self.account_coming_view.go(identifiant=account.iban) self.account_coming.go(identifiant=account.iban) return self.page.iter_coming() @need_login def iter_investment(self, account): - if account.type == Account.TYPE_MARKET: - self.token_inv.go() - if self.token_inv.is_here(): + if account.type != Account.TYPE_MARKET: + return + + self.market.go() + # If there is more than one market account, we must fetch the account params: + if not account._unique: + if self.market.is_here(): token = self.page.get_token() id_invest = self.page.get_id(label=account.label) data = {"numeroCompte": id_invest, "_csrf": token} self.location('/opcvm/lister-composition/redirect-afficher.do', data=data) - else: - self.invest.go() - for tr in self.page.iter_investment(): - yield tr + for inv in self.page.iter_investment(): + yield inv @need_login def get_profile(self): diff --git a/modules/bnporc/enterprise/pages.py b/modules/bnporc/enterprise/pages.py index 297b20efddf91ba866e53937ce35ce2e0532d834..b01ac8cce94b13eb4e921014003fbb9c1ed373a5 100644 --- a/modules/bnporc/enterprise/pages.py +++ b/modules/bnporc/enterprise/pages.py @@ -30,7 +30,7 @@ from weboob.browser.elements import DictElement, ItemElement, method, TableElement from weboob.browser.filters.standard import ( CleanText, CleanDecimal, Date, Regexp, Format, Eval, BrowserURL, Field, - Async, + Async, Currency, ) from weboob.capabilities.bank import Transaction, Account, Investment from weboob.capabilities.profile import Person @@ -105,8 +105,10 @@ def on_load(self): class AccountsPage(LoggedPage, JsonPage): - TYPES = {u'Compte chèque': Account.TYPE_CHECKING, - u'Compte à vue': Account.TYPE_CHECKING} + TYPES = { + 'Compte chèque': Account.TYPE_CHECKING, + 'Compte à vue': Account.TYPE_CHECKING, + } @method class iter_accounts(DictElement): @@ -333,7 +335,40 @@ def get_redacted_card(self): return self.doc['carteNum'] -class TokenPage(LoggedPage, HTMLPage): +class MarketPage(LoggedPage, HTMLPage): + TYPES = { + 'comptes de titres': Account.TYPE_MARKET, + } + + @method + class iter_market_accounts(TableElement): + item_xpath = '//table[@id="table-portefeuille"]/tbody[@class="main-content"]/tr' + head_xpath = '//table[@id="table-portefeuille"]/thead/tr/th/label' + + col_label = 'Portefeuille-titres' + col_balance = re.compile('Valorisation') + col__parent = re.compile('Compte courant') + + class item(ItemElement): + klass = Account + + # Market accounts have no IBAN so we use the account number and specify + # "MARKET" at the end to differentiate from its parent account. + obj_id = Format('%sMARKET', Regexp(CleanText(TableCell('label')), r'\*(.*)\*', default=None)) + obj_label = CleanText(TableCell('label')) + obj_balance = CleanDecimal(TableCell('balance'), replace_dots=True) + obj_currency = Currency(TableCell('balance')) + obj__parent = CleanText(TableCell('_parent')) + obj_coming = NotAvailable + obj_iban = NotAvailable + obj__unique = False + + def obj_type(self): + for key, type in self.page.TYPES.items(): + if key in CleanText(TableCell('label'))(self).lower(): + return type + return Account.TYPE_UNKNOWN + def get_token(self): return Attr('//meta[@name="_csrf"]', 'content')(self.doc) @@ -343,18 +378,23 @@ def get_id(self, label): if id_simple in CleanText(options)(self.doc): return CleanText(options.xpath('./@value'))(self) - def market_search(self): - marketaccount = [] - for account in self.doc.xpath('//div[@class="filterbox-content hide"]//select[@id="numero-compte-titre"]//option'): - account = CleanText(account)(self.doc) - temp = re.search(r'[0-9]+', account) - if temp != None: - marketaccount.append(temp.group(0)) - return marketaccount +class InvestPage(LoggedPage, HTMLPage): + @method + class get_unique_market_account(ItemElement): + klass = Account + + # Market accounts have no IBAN so we use the account number and specify + # "MARKET" at the end to differentiate it from its parent account. + obj_id = Format('%sMARKET', Regexp(CleanText('//div[@class="head"]/h2'), r'\*(.*)\*', default=None)) + obj_label = CleanText('//div[@class="head"]/h2') + obj_balance = CleanDecimal('//div[@id="apercu-val"]/h1', replace_dots=True) + obj_currency = Currency('//div[@id="apercu-val"]/h1') + obj_type = Account.TYPE_MARKET + obj__parent = CleanText('//h3/span[span[@class="info-cheque"]]', children=False) + obj__unique = True -class InvestPage(LoggedPage, HTMLPage): @method class iter_investment(TableElement): item_xpath = '//table[@class="csv-data-container hide"]//tr' @@ -367,6 +407,12 @@ class iter_investment(TableElement): col_valuation = 'Valorisation' col_diff = '+/- value' + """ + Note: Pagination is not handled yet for investments, if we find a + customer with more than 10 invests we might have to handle clicking + on the button to get 50 invests per page or check if there is a link. + """ + class item(ItemElement): klass = Investment @@ -379,8 +425,5 @@ class item(ItemElement): obj_code_type = lambda self: Investment.CODE_TYPE_ISIN if Field('code')(self) is not NotAvailable else NotAvailable def obj_code(self): - chaine = CleanText(TableCell('label'))(self) - return re.search(r'(\w+) - ', chaine).group(0)[:-3] - - def get_market_account_label(self): - return CleanText('//h3/span[span]', children=False)(self.doc) + string = CleanText(TableCell('label'))(self) + return re.search(r'(\w+) - ', string).group(0)[:-3]