diff --git a/modules/caissedepargne/browser.py b/modules/caissedepargne/browser.py index 65e901c9..8c024a18 100644 --- a/modules/caissedepargne/browser.py +++ b/modules/caissedepargne/browser.py @@ -21,8 +21,9 @@ from urlparse import urlsplit from weboob.deprecated.browser import Browser, BrowserIncorrectPassword +from weboob.capabilities.bank import Account -from .pages import LoginPage, IndexPage, ErrorPage, UnavailablePage +from .pages import LoginPage, IndexPage, ErrorPage, UnavailablePage, MarketPage, LifeInsurance __all__ = ['CaisseEpargne'] @@ -37,6 +38,11 @@ class CaisseEpargne(Browser): 'https://[^/]+/login.aspx': ErrorPage, 'https://[^/]+/Pages/logout.aspx.*': ErrorPage, 'https://[^/]+/page_hs_dei_.*.aspx': UnavailablePage, + 'https://[^/]+/Pages/Bourse.*': MarketPage, + 'https://www.caisse-epargne.offrebourse.com/ReroutageSJR': MarketPage, + 'https://www.caisse-epargne.offrebourse.com/Portefeuille': MarketPage, + 'https://[^/]+/Assurance/Pages/Assurance.aspx': LifeInsurance, + 'https://www.extranet2.caisse-epargne.fr.*': LifeInsurance, } def __init__(self, nuser, *args, **kwargs): @@ -98,6 +104,8 @@ class CaisseEpargne(Browser): return None def _get_history(self, info): + if not info['link'].startswith('HISTORIQUE'): + return if self.is_on_page(IndexPage): self.page.go_list() else: @@ -122,3 +130,21 @@ class CaisseEpargne(Browser): for tr in self._get_history(info): tr.type = tr.TYPE_CARD yield tr + + def get_investment(self, account): + if account.type is not Account.TYPE_LIFE_INSURANCE and account.type is not Account.TYPE_MARKET: + raise NotImplementedError() + if self.is_on_page(IndexPage): + self.page.go_list() + else: + self.location(self.buildurl('/Portail.aspx')) + + self.page.go_history(account._info) + if account.type is Account.TYPE_MARKET: + self.page.submit() + self.location('https://www.caisse-epargne.offrebourse.com/Portefeuille') + elif account.type is Account.TYPE_LIFE_INSURANCE: + self.page.go_life_insurance() + self.page.submit() + self.location('https://www.extranet2.caisse-epargne.fr%s' % self.page.get_cons_repart()) + return self.page.iter_investment() diff --git a/modules/caissedepargne/module.py b/modules/caissedepargne/module.py index b9549341..933b7f9e 100644 --- a/modules/caissedepargne/module.py +++ b/modules/caissedepargne/module.py @@ -72,3 +72,6 @@ class CaisseEpargneModule(Module, CapBank): def iter_coming(self, account): with self.browser: return self.browser.get_coming(account) + + def iter_investment(self, account): + return self.browser.get_investment(account) diff --git a/modules/caissedepargne/pages.py b/modules/caissedepargne/pages.py index 832a0437..da88fb0e 100644 --- a/modules/caissedepargne/pages.py +++ b/modules/caissedepargne/pages.py @@ -28,7 +28,7 @@ from weboob.deprecated.mech import ClientForm from weboob.tools.ordereddict import OrderedDict from weboob.deprecated.browser import Page, BrokenPageError, BrowserUnavailable, BrowserIncorrectPassword from weboob.capabilities import NotAvailable -from weboob.capabilities.bank import Account +from weboob.capabilities.bank import Account, Investment from weboob.tools.capabilities.bank.transactions import FrenchTransaction @@ -126,7 +126,7 @@ class IndexPage(Page): } def _get_account_info(self, a): - m = re.search("PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"]HISTORIQUE_([\d\w&]+)['\"]", a.attrib.get('href', '')) + m = re.search("PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"]([HISTORIQUE_\w|SYNTHESE_ASSURANCE_CNP|BOURSE][\d\w&]+)?['\"]", a.attrib.get('href', '')) if m is None: return None else: @@ -134,7 +134,21 @@ class IndexPage(Page): # and is necessary for navigation. link = m.group(2) parts = link.split('&') - return {'type': parts[0], 'id': parts[1], 'link': link} + info = {} + if len(parts) > 1: + info['type'] = parts[0] + info['id'] = parts[1] + else: + id = re.search("([\d]+)", a.attrib.get('title')) + info['type'] = link + info['id'] = id.group(1) + if info['type'] == 'SYNTHESE_ASSURANCE_CNP': + info['acc_type'] = Account.TYPE_LIFE_INSURANCE + if info['type'] == 'BOURSE': + info['acc_type'] = Account.TYPE_MARKET + info['link'] = link + return info + def _add_account(self, accounts, link, label, account_type, balance): info = self._get_account_info(link) @@ -146,12 +160,12 @@ class IndexPage(Page): account.id = info['id'] account._info = info account.label = label - account.type = account_type - account.balance = Decimal(FrenchTransaction.clean_amount(balance)) + account.type = info['acc_type'] if 'acc_type' in info else account_type + account.balance = Decimal(FrenchTransaction.clean_amount(balance)) if balance else NotAvailable account.currency = account.get_currency(balance) account._card_links = [] - if account._info['type'] == 'CB' and account.id in accounts: + if account._info['type'] == 'HISTORIQUE_CB' and account.id in accounts: a = accounts[account.id] if not a.coming: a.coming = Decimal('0.0') @@ -233,7 +247,7 @@ class IndexPage(Page): self.browser.select_form(name='main') self.browser.set_all_readonly(False) self.browser['__EVENTTARGET'] = 'MM$SYNTHESE' - self.browser['__EVENTARGUMENT'] = 'HISTORIQUE_%s' % info['link'] + self.browser['__EVENTARGUMENT'] = info['link'] try: self.browser['MM$m_CH$IsMsgInit'] = '0' except ControlNotFoundError: @@ -323,3 +337,90 @@ class IndexPage(Page): self.browser.submit() return True + + def go_life_insurance(self): + link = self.document.xpath('//table[@summary="Mes contrats d\'assurance vie"]/tbody/tr//a')[0] + m = re.search("PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"](REDIR_ASS_VIE[\d\w&]+)?['\"]", link.attrib.get('href', '')) + if m is not None: + self.browser.select_form(name='main') + self.browser.set_all_readonly(False) + self.browser['__EVENTTARGET'] = 'MM$SYNTHESE_ASSURANCE_CNP' + self.browser['__EVENTARGUMENT'] = m.group(2) + try: + self.browser['MM$m_CH$IsMsgInit'] = '0' + except ControlNotFoundError: + # Not available on new website. + pass + self.browser.controls.append(ClientForm.TextControl('text', 'm_ScriptManager', {'value': ''})) + self.browser['m_ScriptManager'] = 'MM$m_UpdatePanel|MM$SYNTHESE' + try: + self.browser.controls.remove(self.browser.find_control(name='Cartridge$imgbtnMessagerie', type='image')) + self.browser.controls.remove(self.browser.find_control(name='MM$m_CH$ButtonImageFondMessagerie', type='image')) + self.browser.controls.remove(self.browser.find_control(name='MM$m_CH$ButtonImageMessagerie', type='image')) + except ControlNotFoundError: + pass + self.browser.submit() + + +class MarketPage(Page): + def parse_decimal(self, td): + value = self.parser.tocleanstring(td) + if value and value != '-': + return Decimal(FrenchTransaction.clean_amount(value)) + else: + return NotAvailable + + def submit(self): + self.browser.select_form(nr=1) + self.browser.submit() + + def iter_investment(self): + for tbody in self.document.xpath(u'//table[@summary="Contenu du portefeuille valorisé"]/tbody'): + + inv = Investment() + inv.label = self.parser.tocleanstring(tbody.xpath('./tr[1]/td[1]/a/span')[0]) + inv.code = self.parser.tocleanstring(tbody.xpath('./tr[1]/td[1]/a')[0]).split(' - ')[1] + inv.quantity = self.parse_decimal(tbody.xpath('./tr[2]/td[2]')[0]) + inv.unitvalue = self.parse_decimal(tbody.xpath('./tr[2]/td[3]')[0]) + inv.unitprice = self.parse_decimal(tbody.xpath('./tr[2]/td[5]')[0]) + inv.valuation = self.parse_decimal(tbody.xpath('./tr[2]/td[4]')[0]) + inv.diff = self.parse_decimal(tbody.xpath('./tr[2]/td[7]')[0]) + + yield inv + + +class LifeInsurance(MarketPage): + def get_cons_repart(self): + return self.document.xpath('//tr[@id="sousMenuConsultation3"]/td/div/a')[0].attrib['href'] + + def iter_investment(self): + for tr in self.document.xpath(u'//table[@class="boursedetail"]/tr[@class and not(@class="total")]'): + + inv = Investment() + libelle = self.parser.tocleanstring(tr.xpath('./td[1]')[0]).split(' ') + inv.label, inv.code = self.split_label_code(libelle) + diff = self.parse_decimal(tr.xpath('./td[6]')[0]) + inv.quantity = self.parse_decimal(tr.xpath('./td[2]')[0]) + inv.unitvalue = self.parse_decimal(tr.xpath('./td[3]')[0]) + inv.unitprice = self.calc(inv.unitvalue, diff) + inv.valuation = self.parse_decimal(tr.xpath('./td[5]')[0]) + inv.diff = self.get_diff(inv.valuation, self.calc(inv.valuation, diff)) + + yield inv + + def calc(self, value, diff): + if value is NotAvailable or diff is NotAvailable: + return NotAvailable + return Decimal(value) / (1 + Decimal(diff)/100) + + def get_diff(self, valuation, calc): + if valuation is NotAvailable or calc is NotAvailable: + return NotAvailable + return valuation - calc + + def split_label_code(self, libelle): + m = re.search('FR\d+', libelle[-1]) + if m: + return ' '.join(libelle[:-1]), libelle[-1] + else: + return ' '.join(libelle), NotAvailable