From 5b367292ef0f8d908f6b54e9316e90028f5963a2 Mon Sep 17 00:00:00 2001 From: Dorian Roly Date: Tue, 13 Aug 2019 10:39:14 +0200 Subject: [PATCH] [BNP] Add documents to personnal pages Now bnp personnal, bnp pro and bnp privee can get documents --- modules/bnporc/company/browser.py | 8 ++ modules/bnporc/enterprise/browser.py | 10 ++- modules/bnporc/module.py | 44 +++++++++- modules/bnporc/pp/browser.py | 39 +++++++++ modules/bnporc/pp/document_pages.py | 115 +++++++++++++++++++++++++++ modules/bnporc/pp/pages.py | 10 ++- 6 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 modules/bnporc/pp/document_pages.py diff --git a/modules/bnporc/company/browser.py b/modules/bnporc/company/browser.py index 238beca888..1e1a5ff231 100644 --- a/modules/bnporc/company/browser.py +++ b/modules/bnporc/company/browser.py @@ -79,6 +79,14 @@ def iter_history(self, account): (date.today() - timedelta(days=90)).strftime('%Y%m%d'), date.today().strftime('%Y%m%d')) + @need_login + def iter_documents(self, subscription): + raise NotImplementedError() + + @need_login + def iter_subscription(self): + raise NotImplementedError() + @need_login def iter_coming_operations(self, account): return self.get_transactions(account.id, diff --git a/modules/bnporc/enterprise/browser.py b/modules/bnporc/enterprise/browser.py index ef2ea54f20..ec7128f6a3 100644 --- a/modules/bnporc/enterprise/browser.py +++ b/modules/bnporc/enterprise/browser.py @@ -33,7 +33,7 @@ from .pages import ( LoginPage, AuthPage, AccountsPage, AccountHistoryViewPage, AccountHistoryPage, - ActionNeededPage, TransactionPage, MarketPage, InvestPage + ActionNeededPage, TransactionPage, MarketPage, InvestPage, ) @@ -124,6 +124,14 @@ def iter_history(self, account): return [] return self._iter_history_base(account) + @need_login + def iter_documents(self, subscription): + raise NotImplementedError() + + @need_login + def iter_subscription(self): + raise NotImplementedError() + def _iter_history_base(self, account): dformat = "%Y%m%d" diff --git a/modules/bnporc/module.py b/modules/bnporc/module.py index 92d9c3a1f4..e3c0cd431f 100644 --- a/modules/bnporc/module.py +++ b/modules/bnporc/module.py @@ -33,6 +33,10 @@ from weboob.capabilities.base import find_object, strict_find_object from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import ValueBackendPassword, Value, ValueBool +from weboob.capabilities.bill import ( + Subscription, CapDocument, SubscriptionNotFound, DocumentNotFound, Document, + DocumentTypes, +) from .enterprise.browser import BNPEnterprise from .company.browser import BNPCompany @@ -42,7 +46,7 @@ __all__ = ['BNPorcModule'] -class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessages, CapContact, CapProfile): +class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessages, CapContact, CapProfile, CapDocument): NAME = 'bnporc' MAINTAINER = u'Romain Bignon' EMAIL = 'romain@weboob.org' @@ -61,6 +65,13 @@ class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessag 'ent2': 'Entreprises et PME (nouveau site)'})) STORAGE = {'seen': []} + accepted_document_types = ( + DocumentTypes.STATEMENT, + DocumentTypes.REPORT, + DocumentTypes.BILL, + DocumentTypes.OTHER, + ) + # Store the messages *list* for this duration CACHE_THREADS = timedelta(seconds=3 * 60 * 60) @@ -74,6 +85,14 @@ def create_default_browser(self): self.BROWSER = b[self.config['website'].get()] return self.create_browser(self.config) + def iter_resources(self, objs, split_path): + if Account in objs: + self._restrict_level(split_path) + return self.iter_accounts() + if Subscription in objs: + self._restrict_level(split_path) + return self.iter_subscription() + def iter_accounts(self): return self.browser.iter_accounts() @@ -205,4 +224,27 @@ def set_message_read(self, message): self.storage.get('seen', default=[]).append(message.thread.id) self.storage.save() + def get_subscription(self, _id): + return find_object(self.iter_subscription(), id=_id, error=SubscriptionNotFound) + + def iter_documents(self, subscription): + if not isinstance(subscription, Subscription): + subscription = self.get_subscription(subscription) + + return self.browser.iter_documents(subscription) + + def iter_subscription(self): + return self.browser.iter_subscription() + + def get_document(self, _id): + subscription_id = _id.split('_')[0] + subscription = self.get_subscription(subscription_id) + return find_object(self.iter_documents(subscription), id=_id, error=DocumentNotFound) + + def download_document(self, document): + if not isinstance(document, Document): + document = self.get_document(document) + + return self.browser.open(document.url).content + OBJECTS = {Thread: fill_thread} diff --git a/modules/bnporc/pp/browser.py b/modules/bnporc/pp/browser.py index f4e8181cdd..07bb8946e6 100644 --- a/modules/bnporc/pp/browser.py +++ b/modules/bnporc/pp/browser.py @@ -30,6 +30,7 @@ AccountNotFound, Account, AddRecipientStep, AddRecipientTimeout, TransferInvalidRecipient, Loan, ) +from weboob.capabilities.bill import Subscription from weboob.capabilities.profile import ProfileMissing from weboob.tools.decorators import retry from weboob.tools.capabilities.bank.transactions import sorted_transactions @@ -50,6 +51,7 @@ UselessPage, TransferAssertionError, LoanDetailsPage, ) +from .document_pages import DocumentsPage, TitulairePage __all__ = ['BNPPartPro', 'HelloBank'] @@ -121,6 +123,9 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser): advisor = URL(r'/conseiller-wspl/rest/monConseiller', AdvisorPage) + titulaire = URL(r'/demat-wspl/rest/listerTitulairesDemat', TitulairePage) + document = URL(r'/demat-wspl/rest/rechercheCriteresDemat', DocumentsPage) + profile = URL(r'/kyc-wspl/rest/informationsClient', ProfilePage) list_detail_card = URL(r'/udcarte-wspl/rest/listeDetailCartes', ListDetailCardPage) @@ -510,6 +515,40 @@ def iter_threads(self): def get_thread(self, thread): raise NotImplementedError() + @need_login + def iter_documents(self, subscription): + titulaires = self.titulaire.go().get_titulaires() + # Calling '/demat-wspl/rest/listerDocuments' before the request on 'document' + # is necessary when you specify an ikpi, otherwise no documents are returned + self.location('/demat-wspl/rest/listerDocuments') + # When we only have one titulaire, no need to use the ikpi parameter in the request, + # all document are provided with this simple request + data = { + 'dateDebut': (datetime.now() - relativedelta(years=3)).strftime('%d/%m/%Y'), + 'dateFin': datetime.now().strftime('%d/%m/%Y'), + } + # Ikpi is necessary for multi titulaires accounts to get each document of each titulaires + if len(titulaires) > 1: + data['ikpiPersonne'] = subscription._iduser + self.document.go(json=data) + return self.page.iter_documents(sub_id=subscription.id, sub_number=subscription._number, baseurl=self.BASEURL) + + @need_login + def iter_subscription(self): + acc_list = self.iter_accounts() + + for acc in acc_list: + sub = Subscription() + sub.label = acc.label + sub.subscriber = acc._subscriber + sub.id = acc.id + # number is the hidden number of an account like "****1234" + # and it's used in the parsing of the docs in iter_documents + sub._number = acc.number + # iduser is the ikpi affiliate to the account, + # usefull for multi titulaires connexions + sub._iduser = acc._iduser + yield sub class BNPPartPro(BNPParibasBrowser): BASEURL_TEMPLATE = r'https://%s.bnpparibas/' diff --git a/modules/bnporc/pp/document_pages.py b/modules/bnporc/pp/document_pages.py new file mode 100644 index 0000000000..ff043353d4 --- /dev/null +++ b/modules/bnporc/pp/document_pages.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2009-2019 Romain Bignon +# +# This file is part of a weboob module. +# +# This weboob module is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This weboob module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this weboob module. If not, see . + +from __future__ import unicode_literals + +import re + +from weboob.browser.elements import DictElement, ItemElement, method +from weboob.browser.filters.json import Dict +from weboob.browser.filters.standard import Format, Date, Env +from weboob.browser.pages import JsonPage, LoggedPage +from weboob.capabilities.bill import Document, DocumentTypes + +patterns = { + r'Relevé': DocumentTypes.STATEMENT, + r'Livret(s) A': DocumentTypes.STATEMENT, + r'développement durable': DocumentTypes.STATEMENT, + r'Synthèse': DocumentTypes.STATEMENT, + r'Echelles/Décomptes': DocumentTypes.STATEMENT, + r'épargne logement': DocumentTypes.STATEMENT, + r'Livret(s) jeune': DocumentTypes.STATEMENT, + r'Compte(s) sur Livret': DocumentTypes.STATEMENT, + r'Récapitulatifs annuels': DocumentTypes.REPORT, + r"Avis d'exécution": DocumentTypes.REPORT, + r'Factures': DocumentTypes.BILL, +} + +def get_document_type(family): + for patt, type in patterns.items(): + if re.search(re.escape(patt), family): + return type + return DocumentTypes.OTHER + + +class TitulairePage(LoggedPage, JsonPage): + def get_titulaires(self): + return set([t['idKpiTitulaire'] for t in self.doc['data']['listeTitulairesDemat']['listeTitulaires']]) + + +class DocumentsPage(LoggedPage, JsonPage): + @method + class iter_documents(DictElement): + item_xpath = 'data/rechercheCriteresDemat/*/*/listeDocument' + ignore_duplicate = True + + class item(ItemElement): + klass = Document + + def condition(self): + if 'ibanCrypte' in self.el: + return Env('sub_id')(self) in Dict('ibanCrypte')(self) + else: + return Env('sub_number')(self) in Dict('idContrat')(self) + + obj_date = Date(Dict('dateDoc'), dayfirst=True) + obj_format = 'pdf' + obj_id = Format('%s_%s', Env('sub_id'), Dict('idDoc')) + + def obj_label(self): + if 'ibanCrypte' in self.el: + return '%s %s N° %s' % (Dict('dateDoc')(self), Dict('libelleSousFamille')(self), Dict('numeroCompteAnonymise')(self)) + else: + return '%s %s N° %s' % (Dict('dateDoc')(self), Dict('libelleSousFamille')(self), Dict('idContrat')(self)) + + def obj_url(self): + # For most of the cases on the json + if 'ibanCrypte' in self.el: + ibanCrypte = Dict('ibanCrypte')(self) + idDoc = Dict('idDoc')(self) + typeCompte = Dict('typeCompte')(self) + typeDoc = Dict('typeDoc')(self) + typeFamille = 'R001' # add typeFamille correctly for professional and private pages + idLocalisation = Dict('idLocalisation')(self) + viDocDocument = Dict('viDocDocument')(self) + famDoc = Dict('famDoc')(self) + consulted = Dict('consulted')(self) + dateDoc = Dict('dateDoc')(self) + + return '%sdemat-wspl/rest/consultationDocumentDemat?'\ + 'ibanCrypte=%s&idDocument=%s&typeCpt=%s&typeDoc=%s&typeFamille=%s&idLocalisation=%s'\ + '&viDocDocument=%s&familleDoc=%s&consulted=%s&dateDocument=%s&ikpiPersonne=' % (Env('baseurl')(self), ibanCrypte, idDoc, typeCompte, + typeDoc, typeFamille, idLocalisation, viDocDocument, famDoc, consulted, dateDoc) + # For the cases present on privee.mabanque where sometimes the doc url is the different + else: + idDoc = Dict('idDoc')(self) + numClient = Dict('numClient')(self) + heureDoc = Dict('heureDoc')(self) + idLocalisation = Dict('idLocalisation')(self) + viDocDocument = Dict('viDocDocument')(self) + typeReport = Dict('typeReport')(self) + dateDoc = Dict('dateDoc')(self) + + return '%sdemat-wspl/rest/consultationDocumentSpecialBpfDemat?'\ + 'ibanCrypte=&idDocument=%s&numClient=%s&heureDocument=%s'\ + '&idLocalisation=%s&typeReport=%s&viDocDocument=%s&dateDocument=%s' % (Env('baseurl')(self), idDoc, numClient, + heureDoc, idLocalisation, typeReport, viDocDocument, dateDoc) + + def obj_type(self): + return get_document_type(Dict('libelleSousFamille')(self)) diff --git a/modules/bnporc/pp/pages.py b/modules/bnporc/pp/pages.py index 8560053fe4..1ca5e37b41 100644 --- a/modules/bnporc/pp/pages.py +++ b/modules/bnporc/pp/pages.py @@ -30,7 +30,8 @@ from weboob.browser.elements import DictElement, ListElement, TableElement, ItemElement, method from weboob.browser.filters.json import Dict from weboob.browser.filters.standard import ( - Format, Eval, Regexp, CleanText, Date, CleanDecimal, Field, Coalesce, Map, Env, Currency, + Format, Eval, Regexp, CleanText, Date, CleanDecimal, Field, Coalesce, Map, Env, + Currency, ) from weboob.browser.filters.html import TableCell from weboob.browser.pages import JsonPage, LoggedPage, HTMLPage @@ -306,7 +307,6 @@ def obj_company_siren(self): class AccountsPage(BNPPage): - @method class iter_accounts(DictElement): item_xpath = 'data/infoUdc/familleCompte' @@ -354,6 +354,8 @@ class item(ItemElement): obj_balance = Dict('soldeDispo') obj_coming = Dict('soldeAVenir') obj_number = Dict('value') + obj__subscriber = Format('%s %s', Dict('titulaire/nom'), Dict('titulaire/prenom')) + obj__iduser = Dict('titulaire/ikpi') def obj_iban(self): iban = Map(Dict('key'), Env('ibans')(self), default=NotAvailable)(self) @@ -381,6 +383,8 @@ class fill_loan_details(ItemElement): obj_rate = Dict('data/tauxRemboursement') obj_nb_payments_left = Dict('data/nbRemboursementRestant') obj_next_payment_date = Date(Dict('data/dateProchainAmortissement'), dayfirst=True) + obj__subscriber = Format('%s %s', Dict('data/titulaire/nom'), Dict('data/titulaire/prenom')) + obj__iduser = None @method class fill_revolving_details(ItemElement): @@ -745,6 +749,8 @@ class item(ItemElement): obj_balance = CleanDecimal(TableCell('balance'), replace_dots=True) obj_coming = None obj_iban = None + obj__subscriber = None + obj__iduser = None def obj_type(self): for k, v in self.page.ACCOUNT_TYPES.items(): -- GitLab