diff --git a/modules/boursorama/browser.py b/modules/boursorama/browser.py index 45ff7b2a66f1bbd880a2d98e7fe20bb890ad4baa..7fb730da2747df5e4ea9fdaf936fb97084631a62 100644 --- a/modules/boursorama/browser.py +++ b/modules/boursorama/browser.py @@ -31,8 +31,9 @@ from weboob.capabilities.bank import ( Account, AccountNotFound, TransferError, TransferInvalidAmount, TransferInvalidEmitter, TransferInvalidLabel, TransferInvalidRecipient, - AddRecipientStep, Recipient, Rate, TransferBankError, + AddRecipientStep, Recipient, Rate, TransferBankError, AccountOwnership, ) +from weboob.capabilities.base import empty from weboob.capabilities.contact import Advisor from weboob.tools.captcha.virtkeyboard import VirtKeyboardError from weboob.tools.value import Value @@ -116,6 +117,7 @@ class BoursoramaBrowser(RetryLoginBrowser, StatesMixin): authentication = URL('/securisation', AuthenticationPage) iban = URL('/compte/(?P.*)/rib', IbanPage) profile = URL('/mon-profil/', ProfilePage) + profile_children = URL('/mon-profil/coordonnees/enfants', ProfilePage) expert = URL('/compte/derive/', ExpertPage) @@ -187,6 +189,39 @@ def do_login(self): if self.authentication.is_here(): raise BrowserIncorrectAuthenticationCode('Invalid PIN code') + def ownership_guesser(self): + ownerless_accounts = [account for account in self.accounts_list if empty(account.ownership)] + + # On Boursorama website, all mandatory accounts have the real owner name in their label, and + # children names are findable in the PSU profile. + self.profile_children.go() + children_names = self.page.get_children_firstnames() + + for ownerless_account in ownerless_accounts: + for child_name in children_names: + if child_name in ownerless_account.label: + ownerless_account.ownership = AccountOwnership.ATTORNEY + break + + # If there are two deferred card for with the same parent account, we assume that's the parent checking + # account is a 'CO_OWNER' account + parent_accounts = [] + for account in self.accounts_list: + if account.type == Account.TYPE_CARD and empty(account.parent.ownership): + if account.parent in parent_accounts: + account.parent.ownership = AccountOwnership.CO_OWNER + parent_accounts.append(account.parent) + + # We set all accounts without ownership as if they belong to the credential owner + for account in self.accounts_list: + if empty(account.ownership) and account.type != Account.TYPE_CARD: + account.ownership = AccountOwnership.OWNER + + # Account cards should be set with the same ownership of their parents accounts + for account in self.accounts_list: + if account.type == Account.TYPE_CARD: + account.ownership = account.parent.ownership + def go_cards_number(self, link): self.location(link) self.location(self.page.get_cards_number_link()) @@ -268,6 +303,7 @@ def get_accounts_list(self): if exc: raise exc + self.ownership_guesser() return self.accounts_list def get_account(self, id): diff --git a/modules/boursorama/pages.py b/modules/boursorama/pages.py index 9c4b2ccd9ff903e1a735febd597e2fad00f2b48c..c6aece8d57b2be8aac763556602505d017957166 100644 --- a/modules/boursorama/pages.py +++ b/modules/boursorama/pages.py @@ -32,12 +32,13 @@ CleanText, CleanDecimal, Field, Format, Regexp, Date, AsyncLoad, Async, Eval, Env, Currency as CleanCurrency, Map, Coalesce, + MapIn, Lower, ) from weboob.browser.filters.json import Dict from weboob.browser.filters.html import Attr, Link, TableCell from weboob.capabilities.bank import ( Account, Investment, Recipient, Transfer, AccountNotFound, - AddRecipientBankError, TransferInvalidAmount, Loan, + AddRecipientBankError, TransferInvalidAmount, Loan, AccountOwnership, ) from weboob.tools.capabilities.bank.investments import create_french_liquidity from weboob.capabilities.base import NotAvailable, Currency @@ -237,6 +238,12 @@ def is_here(self): 'carte': Account.TYPE_CARD, } + ACCOUNTS_OWNERSHIP = { + 'Comptes de mes enfants': AccountOwnership.ATTORNEY, + 'joint': AccountOwnership.CO_OWNER, + 'commun': AccountOwnership.CO_OWNER, + } + @method class iter_accounts(ListElement): item_xpath = '//table[@class="table table--accounts"]/tr[has-class("table__line--account") and count(descendant::td) > 1 and @data-line-account-href]' @@ -314,6 +321,23 @@ def obj_type(self): return Account.TYPE_UNKNOWN + def obj_ownership(self): + ownership = Coalesce( + MapIn( + CleanText('../tr[contains(@class, "list--accounts--master")]//h4/text()'), + self.page.ACCOUNTS_OWNERSHIP, + default=NotAvailable + ), + MapIn( + Lower(Field('label')), + self.page.ACCOUNTS_OWNERSHIP, + default=NotAvailable + ), + default=NotAvailable + )(self) + + return ownership + def obj_url(self): link = Attr('.//a[has-class("account--name")] | .//a[2] | .//div/a', 'href', default=NotAvailable)(self) return urljoin(self.page.url, link) @@ -778,6 +802,17 @@ def MySelect(*args, **kwargs): class ProfilePage(LoggedPage, HTMLPage): + + def get_children_firstnames(self): + names = [] + + for child in self.doc.xpath('//span[@class="transfer__account-name"]'): + name = child.text.split('\n') + assert len(name) > 1, "There is a child without firstname or the html code has changed !" + names.append(child.text.split('\n')[0]) + + return names + @method class get_profile(ItemElement): klass = Person