diff --git a/modules/bnporc/company/browser.py b/modules/bnporc/company/browser.py index 9409c3a2ceea2782109998eda57834a929d59dba..580ec7e5772bc1a8ff28a5d6500c10e8615fa36a 100644 --- a/modules/bnporc/company/browser.py +++ b/modules/bnporc/company/browser.py @@ -96,6 +96,10 @@ def iter_coming_operations(self, account): def iter_investment(self, account): raise NotImplementedError() + @need_login + def iter_market_orders(self, account): + raise NotImplementedError() + @need_login def get_transfer_accounts(self): raise NotImplementedError() diff --git a/modules/bnporc/enterprise/browser.py b/modules/bnporc/enterprise/browser.py index 5b368ca09d68687fbf0c100d5cc2ba90b0126e4c..30587733f9f143ab47fb4496c7f8ba593bd1ab94 100644 --- a/modules/bnporc/enterprise/browser.py +++ b/modules/bnporc/enterprise/browser.py @@ -208,6 +208,11 @@ def iter_investment(self, account): for inv in self.page.iter_investment(): yield inv + @need_login + def iter_market_orders(self, account): + self.logger.warning('This is an "enterprise" connection, market orders are not implemented.') + raise NotImplementedError() + @need_login def get_profile(self): self.auth.go() diff --git a/modules/bnporc/module.py b/modules/bnporc/module.py index 8a2b12030bbb12cc80a354e07859bd73e1761637..e77e067c00570a59ad23bee4e7b4931ca0434ff4 100644 --- a/modules/bnporc/module.py +++ b/modules/bnporc/module.py @@ -124,6 +124,9 @@ def iter_coming(self, account): def iter_investment(self, account): return self.browser.iter_investment(account) + def iter_market_orders(self, account): + return self.browser.iter_market_orders(account) + def iter_transfer_recipients(self, origin_account): if self.config['website'].get() != 'pp': raise NotImplementedError() diff --git a/modules/bnporc/pp/browser.py b/modules/bnporc/pp/browser.py index 35bc103c91e9a44fa9851d87acc5c6bf2de2e15e..078d71e33b1277450ea0c545d7651f36299637a9 100644 --- a/modules/bnporc/pp/browser.py +++ b/modules/bnporc/pp/browser.py @@ -49,7 +49,7 @@ from .pages import ( LoginPage, AccountsPage, AccountsIBANPage, HistoryPage, TransferInitPage, ConnectionThresholdPage, LifeInsurancesPage, LifeInsurancesHistoryPage, - LifeInsurancesDetailPage, NatioVieProPage, CapitalisationPage, + LifeInsurancesDetailPage, NatioVieProPage, CapitalisationPage, MarketOrdersPage, MarketListPage, MarketPage, MarketHistoryPage, MarketSynPage, BNPKeyboard, RecipientsPage, ValidateTransferPage, RegisterTransferPage, AdvisorPage, AddRecipPage, ActivateRecipPage, ProfilePage, ListDetailCardPage, ListErrorPage, @@ -112,6 +112,7 @@ class BNPParibasBrowser(LoginBrowser, StatesMixin): market_syn = URL(r'pe-war/rpc/synthesis/get', MarketSynPage) market = URL(r'pe-war/rpc/portfolioDetails/get', MarketPage) market_history = URL(r'/pe-war/rpc/turnOverHistory/get', MarketHistoryPage) + market_orders = URL(r'/pe-war/rpc/orderDetailList/get', MarketOrdersPage) recipients = URL(r'/virement-wspl/rest/listerBeneficiaire', RecipientsPage) add_recip = URL(r'/virement-wspl/rest/ajouterBeneficiaire', AddRecipPage) @@ -374,11 +375,11 @@ def iter_investment(self, account): self.market_list.go(json={}, method='POST') except ServerError: self.logger.warning("An Internal Server Error occurred") - return iter([]) + return [] for market_acc in self.page.get_list(): if account.number[-4:] == market_acc['securityAccountNumber'][-4:] and not account.iban: - # Sometimes generate an Internal Server Error ... try: + # Sometimes generates an Internal Server Error ... self.market.go(json={ "securityAccountNumber": market_acc['securityAccountNumber'], }) @@ -387,7 +388,40 @@ def iter_investment(self, account): break return self.page.iter_investments() - return iter([]) + return [] + + @need_login + def iter_market_orders(self, account): + if ( + account.type not in (Account.TYPE_MARKET, account.TYPE_PEA) + or 'espèces' in account.label.lower() + ): + return [] + + try: + self.market_list.go(json={}, method='POST') + except ServerError: + self.logger.warning('An Internal Server Error occurred') + return [] + + for market_acc in self.page.get_list(): + if account.number[-4:] == market_acc['securityAccountNumber'][-4:] and not account.iban: + json = { + 'securityAccountNumber': market_acc['securityAccountNumber'], + 'filterCriteria': [], + 'sortColumn': 'orderDateTransmission', + 'sortType': 'desc', + } + try: + # Sometimes generates an Internal Server Error ... + self.market_orders.go(json=json) + except ServerError: + self.logger.warning('An Internal Server Error occurred') + break + return self.page.iter_market_orders() + + # In case we haven't found the account with get_list + return [] @need_login def iter_recipients(self, origin_account_id): diff --git a/modules/bnporc/pp/pages.py b/modules/bnporc/pp/pages.py index 05ff10c475b1f8f5d89af5b17870526c85c00243..1d0f6d0af0c0121799259c805c3cc1146ab95d87 100644 --- a/modules/bnporc/pp/pages.py +++ b/modules/bnporc/pp/pages.py @@ -45,7 +45,9 @@ Emitter, EmitterNumberType, TransferStatus, TransferDateType, ) -from weboob.capabilities.wealth import Investment +from weboob.capabilities.wealth import ( + Investment, MarketOrder, MarketOrderDirection, +) from weboob.capabilities.base import empty from weboob.capabilities.contact import Advisor from weboob.capabilities.profile import Person, ProfileMissing @@ -58,7 +60,7 @@ from weboob.tools.capabilities.bank.transactions import FrenchTransaction, parse_with_patterns from weboob.tools.captcha.virtkeyboard import GridVirtKeyboard from weboob.tools.date import parse_french_date -from weboob.tools.capabilities.bank.investments import is_isin_valid +from weboob.tools.capabilities.bank.investments import is_isin_valid, IsinCode from weboob.tools.compat import unquote_plus from weboob.tools.html import html2text @@ -366,8 +368,10 @@ def validate(self, obj): LABEL_TO_TYPE = { 'PEA Espèces': Account.TYPE_PEA, + 'PEA PME Espèces': Account.TYPE_PEA, 'PEA Titres': Account.TYPE_PEA, 'PEL': Account.TYPE_SAVINGS, + 'BNPP MP PERP': Account.TYPE_PERP, 'Plan Epargne Retraite Particulier': Account.TYPE_PERP, 'Crédit immobilier': Account.TYPE_MORTGAGE, 'Réserve Provisio': Account.TYPE_REVOLVING_CREDIT, @@ -996,6 +1000,57 @@ def iter_history(self): yield tr +MARKET_ORDER_DIRECTIONS = { + 'Achat': MarketOrderDirection.BUY, + 'Vente': MarketOrderDirection.SALE, +} + + +class MarketOrdersPage(BNPPage): + @method + class iter_market_orders(DictElement): + item_xpath = 'contentList' + + class item(ItemElement): + klass = MarketOrder + + # Note: there is no information on the order type + obj_id = CleanText(Dict('orderReference')) + obj_label = CleanText(Dict('securityName')) + obj_state = CleanText(Dict('orderStatusLabel')) + obj_code = IsinCode(CleanText(Dict('securityCode')), default=NotAvailable) + obj_stock_market = CleanText(Dict('stockExchangeName')) + obj_date = FromTimestamp( + Eval(lambda t: t / 1000, Dict('orderDateTransmission')) + ) + obj_direction = Map( + CleanText(Dict('orderNatureLabel')), + MARKET_ORDER_DIRECTIONS, + MarketOrderDirection.UNKNOWN + ) + + def obj_quantity(self): + if empty(Dict('quantity')(self)): + return NotAvailable + return Decimal(str(Dict('quantity')(self))) + + def obj_unitprice(self): + if empty(Dict('executionPrice')(self)): + return NotAvailable + return Decimal(str(Dict('executionPrice')(self))) + + def obj_ordervalue(self): + if empty(Dict('limitPrice')(self)): + return NotAvailable + return Decimal(str(Dict('limitPrice')(self))) + + def obj_currency(self): + # Most of the times the currency is set to null + if empty(Dict('orderCurrency')(self)): + return NotAvailable + return Currency(Dict('orderCurrency'), default=NotAvailable)(self) + + class AdvisorPage(BNPPage): def has_error(self): return (self.doc.get('message') == 'Erreur technique')