diff --git a/modules/lcl/browser.py b/modules/lcl/browser.py index cb97842e91c6af9c48a5fd88ab109633e3f8f39f..4898deaed62b30ccaed7d8f7f108af5f20e18d89 100644 --- a/modules/lcl/browser.py +++ b/modules/lcl/browser.py @@ -28,7 +28,10 @@ from weboob.browser import LoginBrowser, URL, need_login, StatesMixin from weboob.browser.exceptions import ServerError from weboob.capabilities.base import NotAvailable -from weboob.capabilities.bank import Account, AddRecipientBankError, AddRecipientStep, Recipient, AccountOwnerType +from weboob.capabilities.bank import ( + Account, AddRecipientBankError, AddRecipientStep, Recipient, AccountOwnerType, + AccountOwnership, +) from weboob.capabilities.base import find_object from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.tools.compat import basestring, urlsplit, unicode @@ -268,13 +271,14 @@ def get_accounts(self): if self.login.is_here(): return self.get_accounts_list() + owner_name = re.search(r' (.+)', self.get_profile().name).group(1).upper() # retrieve life insurance accounts self.assurancevie.stay_or_go() if self.no_perm.is_here(): self.logger.warning('Life insurances are unavailable.') else: # retrieve life insurances from popups - for a in self.page.get_popup_life_insurance(): + for a in self.page.get_popup_life_insurance(name=owner_name): self.update_accounts(a) # retrieve life insurances from calie website @@ -312,7 +316,7 @@ def get_accounts(self): # retrieve accounts on main page self.accounts.go() - for a in self.page.get_list(): + for a in self.page.get_accounts_list(name=owner_name): if not self.check_accounts(a): continue @@ -346,7 +350,7 @@ def get_accounts(self): self.update_accounts(a) if self.connexion_bourse(): - for a in self.page.get_list(): + for a in self.page.get_list(name=owner_name): self.update_accounts(a) self.deconnexion_bourse() # Disconnecting from bourse portal before returning account list @@ -357,7 +361,7 @@ def get_accounts(self): if self.no_perm.is_here(): self.logger.warning('Deposits are unavailable.') else: - for a in self.page.get_list(): + for a in self.page.get_list(name=owner_name): # There is no id on the page listing the 'Compte à terme' # So a form must be submitted to access the id of the contract self.set_deposit_account_id(a) @@ -390,11 +394,24 @@ def get_accounts_list(self): a._card_position = card_position self.update_accounts(a) + owner_name = re.search(r' (.+)', self.get_profile().name).group(1).upper() for account in self.accounts_list: account.owner_type = self.owner_type + self.set_ownership(account, owner_name) return iter(self.accounts_list) + def set_ownership(self, account, owner_name): + if not account.ownership: + if account.parent and account.parent.ownership: + account.ownership = account.parent.ownership + elif re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', account.label, re.IGNORECASE): + account.ownership = AccountOwnership.CO_OWNER + elif all(n in account.label for n in owner_name.split()): + account.ownership = AccountOwnership.OWNER + else: + account.ownership = AccountOwnership.ATTORNEY + def get_bourse_accounts_ids(self): bourse_accounts_ids = [] for account in self.get_accounts_list(): diff --git a/modules/lcl/pages.py b/modules/lcl/pages.py index 35796ab2065afb3d737a0a99304e6482c3249ab1..3b76f230366c8e56870e6498faa4443437d76b80 100644 --- a/modules/lcl/pages.py +++ b/modules/lcl/pages.py @@ -30,6 +30,7 @@ from weboob.capabilities.base import empty, find_object, NotAvailable from weboob.capabilities.bank import ( Account, Investment, Recipient, TransferError, TransferBankError, Transfer, + AccountOwnership, ) from weboob.capabilities.bill import Document, Subscription, DocumentTypes from weboob.capabilities.profile import Person, ProfileMissing @@ -231,6 +232,15 @@ def on_load(self): self.select_contract() +class OwnedItemElement(ItemElement): + def get_ownership(self, owner): + if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', owner, re.IGNORECASE): + return AccountOwnership.CO_OWNER + elif all(n in owner for n in self.env['name'].split()): + return AccountOwnership.OWNER + return AccountOwnership.ATTORNEY + + class AccountsPage(LoggedPage, HTMLPage): def on_load(self): warn = self.doc.xpath('//div[@id="attTxt"]') @@ -241,7 +251,7 @@ def get_name(self): return CleanText('//li[@id="nomClient"]/p')(self.doc) @method - class get_list(ListElement): + class get_accounts_list(ListElement): # XXX Ugly Hack to replace account by second occurrence. # LCL pro website sometimes display the same account twice and only second link is valid to fetch transactions. @@ -255,12 +265,19 @@ def store(self, obj): item_xpath = '//tr[contains(@onclick, "redirect")]' flush_at_end = True - class account(ItemElement): + class account(OwnedItemElement): klass = Account def condition(self): return '/outil/UWLM/ListeMouvement' in self.el.attrib['onclick'] + def load_details(self): + link_id = Field('_link_id')(self) + if link_id: + account_url = urljoin(self.page.browser.BASEURL, link_id) + return self.page.browser.async_open(url=account_url) + return NotAvailable + NATURE2TYPE = { '001': Account.TYPE_SAVINGS, '004': Account.TYPE_CHECKING, @@ -290,6 +307,11 @@ def condition(self): obj__market_link = None obj_number = Field('id') + def obj_ownership(self): + async_page = Async('details').loaded_page(self) + owner = CleanText('//h5[contains(text(), "Titulaire")]')(async_page.doc) + return self.get_ownership(owner) + def get_deferred_cards(self): trs = self.doc.xpath('//tr[contains(@onclick, "EncoursCB")]') links = [] @@ -340,6 +362,11 @@ def obj_label(self): has_type = CleanText('./ancestor::table[.//th[contains(text(), "Type")]]', default=None)(self) return CleanText('./td[2]')(self) if has_type else CleanText('./ancestor::table/preceding-sibling::div[1]')(self).split(' - ')[0] + def obj_ownership(self): + if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\b(ou)? (m|mr|me|mme|mlle|mle|ml)\b(.*)', CleanText(TableCell('id'))(self), re.IGNORECASE): + return AccountOwnership.CO_OWNER + return AccountOwnership.OWNER + def parse(self, el): label = Field('label')(self) trs = self.xpath('//td[contains(text(), $label)]/ancestor::tr[1] | ./ancestor::table[1]/tbody/tr', label=label) @@ -668,10 +695,11 @@ class get_list(TableElement): head_xpath = '//table[has-class("tableau_comptes_details")]/thead/tr/th' col_label = 'Comptes' + col_owner = re.compile('Titulaire') col_titres = re.compile('Valorisation') col_especes = re.compile('Solde espèces') - class item(ItemElement): + class item(OwnedItemElement): klass = Account load_details = Field('_market_link') & AsyncLoad @@ -708,6 +736,10 @@ def obj_type(self): return self.page.TYPES.get(key) return Account.TYPE_MARKET + def obj_ownership(self): + owner = CleanText(TableCell('owner'))(self) + return self.get_ownership(owner) + def get_logout_link(self): return Link('//a[@class="link-underline" and contains(text(), "espace client")]')(self.doc) @@ -828,7 +860,7 @@ def get_calie_life_insurances_first_index(self): class get_popup_life_insurance(ListElement): item_xpath = '//table[@class]/tbody/tr' - class item(ItemElement): + class item(OwnedItemElement): klass = Account def condition(self): @@ -851,6 +883,10 @@ def condition(self): obj__external_website = False obj__is_calie_account = False + def obj_ownership(self): + owner = CleanText(Field('_owner'))(self) + return self.get_ownership(owner) + def obj_id(self): _id = CleanText('.//td/@id')(self) # in old code, we use _id, it seems that is not used anymore @@ -1418,7 +1454,7 @@ class get_list(TableElement): col_name = 'Nom du contrat' col_balance = 'Capital investi' - class item(ItemElement): + class item(OwnedItemElement): klass = Account obj_type = Account.TYPE_DEPOSIT @@ -1431,5 +1467,9 @@ class item(ItemElement): obj_id = None obj__transfer_id = None + def obj_ownership(self): + owner = CleanText(TableCell('owner'))(self) + return self.get_ownership(owner) + def set_deposit_account_id(self, account): account.id = CleanText('//td[contains(text(), "N° contrat")]/following::td[1]//b')(self.doc)