From 93fe9b90bebf740c68e5124b774241b93f268ee5 Mon Sep 17 00:00:00 2001 From: Sylvie Ye Date: Mon, 13 May 2019 09:52:16 +0200 Subject: [PATCH] [sgpe] retrieve market accounts and invests --- modules/societegenerale/sgpe/browser.py | 73 +++++++++++++++++++- modules/societegenerale/sgpe/pages.py | 88 ++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/modules/societegenerale/sgpe/browser.py b/modules/societegenerale/sgpe/browser.py index ab1fec866d..b9eeb93de9 100644 --- a/modules/societegenerale/sgpe/browser.py +++ b/modules/societegenerale/sgpe/browser.py @@ -26,7 +26,7 @@ from weboob.browser.url import URL from weboob.browser.exceptions import ClientError from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException -from weboob.capabilities.base import find_object +from weboob.capabilities.base import find_object, NotAvailable from weboob.capabilities.bank import ( AccountNotFound, RecipientNotFound, AddRecipientStep, AddRecipientBankError, Recipient, TransferBankError, AccountOwnerType, @@ -36,7 +36,7 @@ from .pages import ( LoginPage, CardsPage, CardHistoryPage, IncorrectLoginPage, ProfileProPage, ProfileEntPage, ChangePassPage, SubscriptionPage, InscriptionPage, - ErrorPage, UselessPage, + ErrorPage, UselessPage, MainPage, MarketAccountPage, MarketInvestmentPage, ) from .json_pages import ( AccountsJsonPage, BalancesJsonPage, HistoryJsonPage, BankStatementPage, @@ -107,6 +107,10 @@ def card_history(self, account, coming): @need_login def get_cb_operations(self, account): + if account.type in (account.TYPE_MARKET, ): + # market account transactions are in checking account + return + self.location('/Pgn/NavigationServlet?PageID=Cartes&MenuID=%sOPF&Classeur=1&NumeroPage=1&Rib=%s&Devise=%s' % (self.MENUID, account.id, account.currency)) if self.inscription_page.is_here(): @@ -132,6 +136,8 @@ class SGEnterpriseBrowser(SGPEBrowser): MENUID = 'BANREL' CERTHASH = '2231d5ddb97d2950d5e6fc4d986c23be4cd231c31ad530942343a8fdcc44bb99' + main_page = URL('/icd-web/syd-front/index-comptes.html', MainPage) + accounts = URL('/icd/syd-front/data/syd-comptes-accederDepuisMenu.json', AccountsJsonPage) intraday_accounts = URL('/icd/syd-front/data/syd-intraday-accederDepuisMenu.json', AccountsJsonPage) @@ -142,6 +148,13 @@ class SGEnterpriseBrowser(SGPEBrowser): '/icd/syd-front/data/syd-intraday-chargerDetail.json', HistoryJsonPage) history_next = URL('/icd/syd-front/data/syd-comptes-chargerProchainLotEcriture.json', HistoryJsonPage) + market_investment = URL(r'/Pgn/NavigationServlet\?.*PageID=CompteTitreDetailFrame', + r'/Pgn/NavigationServlet\?.*PageID=CompteTitreDetail', + MarketInvestmentPage) + market_accounts = URL(r'/Pgn/NavigationServlet\?.*PageID=CompteTitreFrame', + r'/Pgn/NavigationServlet\?.*PageID=CompteTitre', + MarketAccountPage) + profile = URL('/gae/afficherModificationMesDonnees.html', ProfileEntPage) subscription = URL(r'/Pgn/NavigationServlet\?MenuID=BANRELRIE&PageID=ReleveRIE&NumeroPage=1&Origine=Menu', SubscriptionPage) @@ -177,14 +190,37 @@ def get_accounts_list(self): acc.owner_type = AccountOwnerType.ORGANIZATION yield acc + # retrieve market accounts if exist + for market_account in self.iter_market_accounts(): + yield market_account + @need_login def iter_history(self, account): + if account.type in (account.TYPE_MARKET, ): + # market account transactions are in checking account + return + value = self.history.go(data={'cl500_compte': account._id, 'cl200_typeReleve': 'valeur'}).get_value() for tr in self.history.go(data={'cl500_compte': account._id, 'cl200_typeReleve': value}).iter_history(value=value): yield tr for tr in self.location('/icd/syd-front/data/syd-intraday-chargerDetail.json', data={'cl500_compte': account._id}).page.iter_history(): yield tr + @need_login + def iter_market_accounts(self): + self.main_page.go() + # retrieve market accounts if exist + market_accounts_link = self.page.get_market_accounts_link() + + # there are no examples of entreprise space with market accounts yet + assert not market_accounts_link, 'There are market accounts, retrieve them.' + return [] + + @need_login + def iter_investment(self, account): + # there are no examples of entreprise space with market accounts yet + return [] + @need_login def iter_subscription(self): subscriber = self.get_profile() @@ -257,6 +293,39 @@ def load_state(self, state): self.need_reload_state = None super(SGProfessionalBrowser, self).load_state(state) + @need_login + def iter_market_accounts(self): + self.main_page.go() + # retrieve market accounts if exist + market_accounts_link = self.page.get_market_accounts_link() + if market_accounts_link is NotAvailable: + return [] + assert market_accounts_link, 'Market accounts link xpath may have changed' + + # need to be on market accounts page to get the accounts iframe + self.location(market_accounts_link) + market_accounts_list_link = self.page.get_table_iframe_link() + if market_accounts_list_link is NotAvailable: + return [] + assert market_accounts_link, 'Market accounts iframe link xpath may have changed' + + self.location(market_accounts_list_link) + return self.page.iter_market_accounts() + + @need_login + def iter_investment(self, account): + if account.type not in (account.TYPE_MARKET, ): + return [] + + assert account._url_data, 'This account has no url to retrieve investments' + # need to be on market accounts investment page to get the invetment iframe + self.location('/Pgn/NavigationServlet?%s' % account._url_data) + + invests_list_link = self.page.get_table_iframe_link() + assert invests_list_link, 'It seems that this market account has no investment' + self.location(invests_list_link) + return self.page.iter_investment() + def copy_recipient_obj(self, recipient): rcpt = Recipient() rcpt.id = recipient.iban diff --git a/modules/societegenerale/sgpe/pages.py b/modules/societegenerale/sgpe/pages.py index df869fbc00..ab075ee4b1 100644 --- a/modules/societegenerale/sgpe/pages.py +++ b/modules/societegenerale/sgpe/pages.py @@ -24,15 +24,17 @@ from io import BytesIO from weboob.browser.pages import HTMLPage, LoggedPage -from weboob.browser.elements import ListElement, ItemElement, method +from weboob.browser.elements import ListElement, ItemElement, method, TableElement from weboob.browser.filters.standard import ( CleanText, CleanDecimal, Date, - Env, Regexp, Field, Format, + Env, Regexp, Field, Format, TableCell, ) -from weboob.browser.filters.html import Attr +from weboob.browser.filters.html import Attr, Link +from weboob.tools.capabilities.bank.investments import is_isin_valid from weboob.tools.capabilities.bank.transactions import FrenchTransaction from weboob.capabilities.profile import Profile, Person from weboob.capabilities.bill import Document, Subscription +from weboob.capabilities.bank import Account, Investment from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable from weboob.tools.json import json @@ -269,3 +271,83 @@ def get_error(self): class UselessPage(LoggedPage, SGPEPage): pass + + +class MainPage(LoggedPage, SGPEPage): + def get_market_accounts_link(self): + market_accounts_link = Link('//li/a[@title="Comptes titres"]', default=None)(self.doc) + + if market_accounts_link: + return market_accounts_link + elif self.doc.xpath('//span[contains(text(), "Comptes titres") and contains(@title, "pas habilité à utiliser ce service")]'): + return NotAvailable + # return None when we don't know if there are market accounts or not + # it will be handled in `browser.py` + + +class MarketAccountPage(LoggedPage, SGPEPage): + def get_table_iframe_link(self): + if self.doc.xpath('//div[contains(text(), "Aucun compte-titres")]'): + return NotAvailable + return Attr('//iframe[@id="frameTableau"]', 'src')(self.doc) + + @method + class iter_market_accounts(TableElement): + item_xpath = '//table[@id="tab-corps"]//tr' + head_xpath = '//table[@id="tab-entete"]//td' + + col_id = 'COMPTE' + col_label = 'INTITULE' + col_balance = 'EVALUATION' + + class item(ItemElement): + def condition(self): + # table with empty row filled by empty `td` + return Field('number')(self) + + klass = Account + + obj_id = Format('%s_TITRE', CleanText(TableCell('id'), replace=[(' ', '')])) + obj_number = CleanText(TableCell('id'), replace=[(' ', '')]) + obj_label = CleanText(TableCell('label')) + obj_balance = CleanDecimal.French(CleanText(TableCell('balance'))) + obj_type = Account.TYPE_MARKET + + # all `a` balises have same `href` + obj__url_data = Regexp(Link('(.//a)[1]'), r"lienParent\('(.*)'\)", default=NotAvailable) + + +class MarketInvestmentPage(LoggedPage, SGPEPage): + def get_table_iframe_link(self): + return Attr('//iframe[@id="frameTableau"]', 'src')(self.doc) + + @method + class iter_investment(TableElement): + item_xpath = '//table[@id="tab-corps"]//tr' + head_xpath = '//table[@id="tab-entete"]//td' + + col_code = 'CODE' + col_label = 'VALEUR' + col_valuation = 'MONTANT' + col_quantity = 'QUANTITE' + col_unitvalue = 'COURS' + + class item(ItemElement): + def condition(self): + # table with empty row filled by empty `td` + return Field('valuation')(self) + + klass = Investment + + obj_code_type = Investment.CODE_TYPE_ISIN + obj_label = CleanText(TableCell('label')) + obj_valuation = CleanDecimal.French(CleanText(TableCell('valuation'))) + obj_quantity = CleanDecimal.French(CleanText(TableCell('quantity'))) + obj_unitvalue = CleanDecimal.French(CleanText(TableCell('unitvalue'))) + + def obj_code(self): + code = CleanText(TableCell('code'))(self) + # there is no example of invests without valid ISIN code + # wait for it to retrieve them corretly + assert is_isin_valid(code), 'This code is not a valid ISIN, please check what invest is it.' + return code -- GitLab