Commit 24659b1a authored by Quentin Defenouillere's avatar Quentin Defenouillere Committed by Romain Bignon

[bnporc-entreprises] Split market account from checking account

The market accounts were not scraped yet, the BNP website seems to have
changed recently.
This patch enables scraping of the market accounts.
The iter_investments method already worked as such, however the
pagination is not yet handled.
When there is only one account on the "invests" page, the checking and
the market account are fused into one account, the market balance is not
scraped, nor are the investments.
This patch enables both accounts to be visible with their respective
balances and the investments related to the market account.

Closes: 6827@zendesk
parent c7ef2ae6
......@@ -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 import Account
from weboob.exceptions import BrowserIncorrectPassword, BrowserForbidden
from weboob.browser.url import URL
......@@ -34,7 +33,7 @@ from import sorted_transactions
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/', TransactionPage)
invest = URL(r'/opcvm/lister-composition/', InvestPage)
# the token page is used only if there are several type market accounts
token_inv = URL(r'/opcvm/lister-portefeuilles/', TokenPage)
# The Market page is used only if there are several market accounts
market = URL(r'/opcvm/lister-portefeuilles/', MarketPage)
renew_pass = URL('/sommaire/PseRedirectPasswordConnect', ActionNeededPage)
......@@ -72,7 +71,7 @@ class BNPEnterprise(LoginBrowser):
def do_login(self):
if self.login.is_here() is False:
if not self.login.is_here():
data = {}
......@@ -89,22 +88,26 @@ class BNPEnterprise(LoginBrowser):
def get_accounts_list(self):
accounts = []
if self.token_inv.is_here():
marketaccount =
# redirected to invest page
# it means that there is only 1 market account among the ones showed
marketaccount = []
except BrowserForbidden:
marketaccount = []
# Fetch checking accounts:
for account in self.accounts.stay_or_go().iter_accounts():
label_tmp ='(\d{4,})', account.label)
if (label_tmp and in marketaccount) or account.label in marketaccount:
account.type = Account.TYPE_MARKET
# Fetch market accounts:
for market_account in
market_account.parent = find_object(accounts, label=market_account._parent)
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 =
account.parent = find_object(accounts, label=account._parent)
except BrowserForbidden:
return accounts
......@@ -116,6 +119,9 @@ class BNPEnterprise(LoginBrowser):
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 @@ class BNPEnterprise(LoginBrowser):
def iter_coming_operations(self, account):
# There is no available coming operation for market accounts
if account.type == Account.TYPE_MARKET:
return []
def iter_investment(self, account):
if account.type == Account.TYPE_MARKET:
if self.token_inv.is_here():
if account.type != Account.TYPE_MARKET:
# If there is more than one market account, we must fetch the account params:
if not account._unique:
token =
id_invest =
data = {"numeroCompte": id_invest, "_csrf": token}
self.location('/opcvm/lister-composition/', data=data)
for tr in
yield tr
for inv in
yield inv
def get_profile(self):
......@@ -30,7 +30,7 @@ from weboob.browser.filters.html import TableCell, Attr
from weboob.browser.elements import DictElement, ItemElement, method, TableElement
from weboob.browser.filters.standard import (
CleanText, CleanDecimal, Date, Regexp, Format, Eval, BrowserURL, Field,
Async, Currency,
from import Transaction, Account, Investment
from weboob.capabilities.profile import Person
......@@ -105,8 +105,10 @@ class ActionNeededPage(HTMLPage):
class AccountsPage(LoggedPage, JsonPage):
TYPES = {u'Compte chèque': Account.TYPE_CHECKING,
u'Compte à vue': Account.TYPE_CHECKING}
'Compte chèque': Account.TYPE_CHECKING,
'Compte à vue': Account.TYPE_CHECKING,
class iter_accounts(DictElement):
......@@ -333,7 +335,40 @@ class TransactionPage(LoggedPage, JsonPage):
return self.doc['carteNum']
class TokenPage(LoggedPage, HTMLPage):
class MarketPage(LoggedPage, HTMLPage):
'comptes de titres': Account.TYPE_MARKET,
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
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 @@ class TokenPage(LoggedPage, HTMLPage):
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 ='[0-9]+', account)
if temp != None:
return marketaccount
class InvestPage(LoggedPage, HTMLPage):
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):
class iter_investment(TableElement):
item_xpath = '//table[@class="csv-data-container hide"]//tr'
......@@ -367,6 +407,12 @@ class InvestPage(LoggedPage, HTMLPage):
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 InvestPage(LoggedPage, HTMLPage):
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'(\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'(\w+) - ', string).group(0)[:-3]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment