Commit c7330d62 authored by ntome's avatar ntome

backport master module fixes

parent 27ae0de5
Pipeline #2630 failed with stages
in 6 minutes and 14 seconds
......@@ -56,6 +56,9 @@ class AmundiBrowser(LoginBrowser):
def iter_investment(self, account):
if account.balance == 0:'Account %s has a null balance, no investment available.', account.label)
headers = {'X-noee-authorization': 'noeprd %s' % self.token}
for inv in
......@@ -515,7 +515,7 @@ class LifeInsuranceIframe(LoggedPage, HTMLPage):
obj_code = Regexp(CleanText(TableCell('code')), r'(.{12})', default=NotAvailable)
obj_code_type = lambda self: Investment.CODE_TYPE_ISIN if Field('code')(self) is not NotAvailable else NotAvailable
def obj_diff_percent(self):
def obj_diff_ratio(self):
diff_percent = MyDecimal(TableCell('diff')(self)[0])(self)
return diff_percent/100 if diff_percent != NotAvailable else diff_percent
......@@ -17,6 +17,8 @@
# 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
from collections import OrderedDict
from functools import reduce
......@@ -34,38 +36,38 @@ __all__ = ['BanquePopulaireModule']
class BanquePopulaireModule(Module, CapBankWealth, CapContact, CapProfile):
NAME = 'banquepopulaire'
MAINTAINER = u'Romain Bignon'
MAINTAINER = 'Romain Bignon'
EMAIL = ''
VERSION = '1.5'
DESCRIPTION = u'Banque Populaire'
DESCRIPTION = 'Banque Populaire'
website_choices = OrderedDict([(k, u'%s (%s)' % (v, k)) for k, v in sorted({
'': u'Alpes',
'': u'Alsace Lorraine Champagne',
'' : u'Alsace Lorraine Champagne',
'': u'Aquitaine Centre atlantique',
'': u'Atlantique',
'': u'Grand Ouest',
'': u'Auvergne Rhône Alpes',
'': u'Auvergne Rhône Alpes',
'': u'Banque de Savoie',
'': u'Bourgogne Franche-Comté',
'': u'Crédit Maritime Bretagne Normandie',
'': u'Crédit Maritime Atlantique',
'': u'Crédit Maritime du Littoral du Sud-Ouest',
'': u'Lorraine Champagne',
'': u'Massif central',
'': u'Méditerranée',
'': u'Nord',
'': u'Occitane',
'': u'Ouest',
'': u'Rives de Paris',
'': u'Sud',
'': u'Val de France',
website_choices = OrderedDict([(k, '%s (%s)' % (v, k)) for k, v in sorted({
'': 'Alpes',
'': 'Alsace Lorraine Champagne',
'' : 'Alsace Lorraine Champagne',
'': 'Aquitaine Centre atlantique',
'': 'Atlantique',
'': 'Grand Ouest',
'': 'Auvergne Rhône Alpes',
'': 'Auvergne Rhône Alpes',
'': 'Banque de Savoie',
'': 'Bourgogne Franche-Comté',
'': 'Crédit Maritime Bretagne Normandie',
'': 'Crédit Maritime Atlantique',
'': 'Crédit Maritime du Littoral du Sud-Ouest',
'': 'Lorraine Champagne',
'': 'Massif central',
'': 'Méditerranée',
'': 'Nord',
'': 'Occitane',
'': 'Ouest',
'': 'Rives de Paris',
'': 'Sud',
'': 'Val de France',
}.items(), key=lambda k_v: (k_v[1], k_v[0]))])
CONFIG = BackendConfig(Value('website', label=u'Région', choices=website_choices),
CONFIG = BackendConfig(Value('website', label='Région', choices=website_choices),
ValueBackendPassword('login', label='Identifiant', masked=False),
ValueBackendPassword('password', label='Mot de passee'))
ValueBackendPassword('password', label='Mot de passe'))
BROWSER = BanquePopulaire
def create_default_browser(self):
......@@ -23,7 +23,7 @@ from __future__ import unicode_literals
from requests.exceptions import ConnectionError
from weboob.browser.browsers import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
from .compat.weboob_capabilities_bank import Account
from weboob.capabilities.base import NotAvailable
from import retry
......@@ -111,8 +111,10 @@ class Barclays(LoginBrowser):
error_message =
if error_message:
assert 'Saisie incorrecte' in error_message, error_message
raise BrowserIncorrectPassword(error_message)
if 'Saisie incorrecte' in error_message:
raise BrowserIncorrectPassword(error_message)
elif 'Votre accès est suspendu' in error_message:
raise ActionNeeded(error_message)
# can't login if there is ' ' in the 2 characters asked
if not
......@@ -306,10 +306,13 @@ class CardPage(LoggedPage, HTMLPage):
def get_cards(self, account_id):
divs = self.doc.xpath('//div[@class="content-boxed"]')
assert len(divs)
msgs = re.compile(u'Vous avez fait opposition sur cette carte bancaire.' +
'|Votre carte bancaire a été envoyée.' +
'|BforBank a fait opposition sur votre carte' +
'|Pour des raisons de sécurité, la demande de réception du code confidentiel de votre carte par SMS est indisponible')
msgs = re.compile(
'Vous avez fait opposition sur cette carte bancaire.' +
'|Votre carte bancaire a été envoyée.' +
'|Carte bancaire commandée.' +
'|BforBank a fait opposition sur votre carte' +
'|Pour des raisons de sécurité, la demande de réception du code confidentiel de votre carte par SMS est indisponible'
divs = [d for d in divs if not'.//div[has-class("alert")]', default='')(d))]
divs = [d.xpath('.//div[@class="m-card-infos"]')[0] for d in divs]
divs = [d for d in divs if not d.xpath('.//div[@class="m-card-infos-body-text"][text()="Débit immédiat"]')]
......@@ -59,6 +59,7 @@ class BinckBrowser(LoginBrowser):
history = URL(r'/TransactionsOverview/GetTransactions',
r'/TransactionsOverview/FilteredOverview', HistoryPage)
questions = URL(r'/FDL_Complex_FR_Compte',
r'FsmaMandatoryQuestionnairesOverview', QuestionPage)
change_pass = URL(r'/ChangePassword/Index',
r'/EditSetting/GetSetting\?code=MutationPassword', ChangePassPage)
......@@ -43,7 +43,7 @@ class QuestionPage(HTMLPage):
if self.doc.xpath(u'//h1[contains(text(), "Questionnaires connaissance et expérience")]'):
form = self.get_form('//form[@action="/FsmaMandatoryQuestionnairesOverview/PostponeQuestionnaires"]')
form = self.get_form('//form[@action="/FDL_Complex_FR_Compte/Introduction/SkipQuestionnaire"]')
form = self.get_form('//form[contains(@action, "Complex_FR_Compte/Introduction/SkipQuestionnaire")]')
......@@ -217,7 +217,7 @@ class InvestmentPage(LoggedPage, JsonPage):
obj_unitprice = Env('unitprice', default=NotAvailable)
obj_valuation = MyDecimal(Dict('ValueInEuro'))
obj_diff = MyDecimal(Dict('ResultValueInEuro'))
obj_diff_percent = Eval(lambda x: x / 100, MyDecimal(Dict('ResultPercentageInEuro')))
obj_diff_ratio = Eval(lambda x: x / 100, MyDecimal(Dict('ResultPercentageInEuro')))
obj_original_currency = Env('o_currency', default=NotAvailable)
obj_original_unitvalue = Env('o_unitvalue', default=NotAvailable)
obj_original_unitprice = Env('o_unitprice', default=NotAvailable)
......@@ -76,10 +76,14 @@ class BnpcartesentrepriseBrowser(LoginBrowser):
if self.error.is_here() or
raise BrowserIncorrectPassword()
if self.type == '2' and'Manager corporate connection')
raise SiteSwitch('corporate')
# ti corporate and ge corporate are not detected the same way ..
if 'corporate' in'Carholder corporate connection')
self.is_corporate = True
else:'Cardholder connection')
def ti_card_go(self):
if self.is_corporate:
......@@ -79,6 +79,14 @@ class BNPCompany(LoginBrowser):
( - timedelta(days=90)).strftime('%Y%m%d'),'%Y%m%d'))
def iter_documents(self, subscription):
raise NotImplementedError()
def iter_subscription(self):
raise NotImplementedError()
def iter_coming_operations(self, account):
return self.get_transactions(,
......@@ -33,7 +33,7 @@ from import sorted_transactions
from .pages import (
LoginPage, AuthPage, AccountsPage, AccountHistoryViewPage, AccountHistoryPage,
ActionNeededPage, TransactionPage, MarketPage, InvestPage
ActionNeededPage, TransactionPage, MarketPage, InvestPage,
......@@ -124,6 +124,14 @@ class BNPEnterprise(LoginBrowser):
return []
return self._iter_history_base(account)
def iter_documents(self, subscription):
raise NotImplementedError()
def iter_subscription(self):
raise NotImplementedError()
def _iter_history_base(self, account):
dformat = "%Y%m%d"
......@@ -230,6 +230,7 @@ class AccountHistoryPage(LoggedPage, JsonPage):
u'0083': Transaction.TYPE_DEFERRED_CARD,
u'0813': Transaction.TYPE_LOAN_PAYMENT,
u'0568': Transaction.TYPE_TRANSFER,
u'1194': Transaction.TYPE_DEFERRED_CARD, # PAYBACK typed as DEFERRED_CARD
......@@ -33,6 +33,10 @@ from weboob.capabilities.profile import CapProfile
from weboob.capabilities.base import find_object, strict_find_object
from import Module, BackendConfig
from import ValueBackendPassword, Value, ValueBool
from weboob.capabilities.bill import (
Subscription, CapDocument, SubscriptionNotFound, DocumentNotFound, Document,
from .enterprise.browser import BNPEnterprise
from .company.browser import BNPCompany
......@@ -42,7 +46,7 @@ from .pp.browser import BNPPartPro, HelloBank
__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 = ''
......@@ -61,6 +65,13 @@ class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessag
'ent2': 'Entreprises et PME (nouveau site)'}))
STORAGE = {'seen': []}
accepted_document_types = (
# Store the messages *list* for this duration
CACHE_THREADS = timedelta(seconds=3 * 60 * 60)
......@@ -74,6 +85,14 @@ class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessag
self.BROWSER = b[self.config['website'].get()]
return self.create_browser(self.config)
def iter_resources(self, objs, split_path):
if Account in objs:
return self.iter_accounts()
if Subscription in objs:
return self.iter_subscription()
def iter_accounts(self):
return self.browser.iter_accounts()
......@@ -205,4 +224,27 @@ class BNPorcModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapMessag'seen', default=[]).append(
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)
OBJECTS = {Thread: fill_thread}
......@@ -30,13 +30,14 @@ from .compat.weboob_capabilities_bank import (
AccountNotFound, Account, AddRecipientStep, AddRecipientTimeout,
TransferInvalidRecipient, Loan,
from weboob.capabilities.bill import Subscription
from weboob.capabilities.profile import ProfileMissing
from import retry
from import sorted_transactions
from import json
from weboob.browser.exceptions import ServerError
from weboob.browser.elements import DataError
from weboob.exceptions import BrowserIncorrectPassword
from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable
from import Value, ValueBool
from import create_french_liquidity
......@@ -50,6 +51,7 @@ from .pages import (
UselessPage, TransferAssertionError, LoanDetailsPage,
from .document_pages import DocumentsPage, TitulairePage
__all__ = ['BNPPartPro', 'HelloBank']
......@@ -92,12 +94,14 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
r'/fr/systeme/page-indisponible', ConnectionThresholdPage)
accounts = URL(r'udc-wspl/rest/getlstcpt', AccountsPage)
loan_details = URL(r'caraccomptes-wspl/rpc/(?P<loan_type>.*)', LoanDetailsPage)
ibans = URL(r'rib-wspl/rpc/comptes', AccountsIBANPage)
history = URL(r'rop2-wspl/rest/releveOp', HistoryPage)
history_old = URL(r'rop-wspl/rest/releveOp', HistoryPage)
transfer_init = URL(r'virement-wspl/rest/initialisationVirement', TransferInitPage)
lifeinsurances = URL(r'mefav-wspl/rest/infosContrat', LifeInsurancesPage)
......@@ -121,6 +125,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)
......@@ -275,14 +282,21 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
if not self.card_to_transaction_type:
self.card_to_transaction_type =
data = JSON({
"pastOrPending": 1,
"triAV": 0,
"startDate": ( - relativedelta(years=1)).strftime('%d%m%Y'),
except BrowserUnavailable:
# old url is still used for certain connections bu we don't know which one is,
# so the same HistoryPage is attained by the old url in another URL object
data[1]['startDate'] = ( - relativedelta(years=3)).strftime('%d%m%Y')
# old url authorizes up to 3 years of history
if coming:
return sorted_transactions(
......@@ -510,6 +524,40 @@ class BNPParibasBrowser(JsonBrowserMixin, LoginBrowser):
def get_thread(self, thread):
raise NotImplementedError()
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
# 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': ( - relativedelta(years=3)).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
return, sub_number=subscription._number, baseurl=self.BASEURL)
def iter_subscription(self):
acc_list = self.iter_accounts()
for acc in acc_list:
sub = Subscription()
sub.label = acc.label
sub.subscriber = acc._subscriber =
# 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):
# -*- 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
# 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 .compat.weboob_browser_filters_standard import Format, Date, Env
from weboob.browser.pages import JsonPage, LoggedPage
from weboob.capabilities.bill import Document, DocumentTypes
from import urlencode
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, 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):
class iter_documents(DictElement):
item_xpath = 'data/rechercheCriteresDemat/*/*/listeDocument'
ignore_duplicate = True
class item(ItemElement):
klass = Document
def condition(self):
# There is two type of json, the one with the ibancrypte in it
# and the one with the idcontrat in it, here we check if
# the document belong to the subscritpion.
if 'ibanCrypte' in self.el:
return Env('sub_id')(self) in Dict('ibanCrypte')(self)
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))
return '%s %s N° %s' % (Dict('dateDoc')(self), Dict('libelleSousFamille')(self), Dict('idContrat')(self))
def obj_url(self):
keys_to_copy = {
'idDocument' :'idDoc',
'dateDocument': 'dateDoc',
'idLocalisation': 'idLocalisation',
'viDocDocument': 'viDocDocument',
# Here we parse the json with ibancrypte in it, for most cases
if 'ibanCrypte' in self.el:
url = 'demat-wspl/rest/consultationDocumentDemat?'
'typeCpt': 'typeCompte',
'familleDoc': 'famDoc',
'ibanCrypte': 'ibanCrypte',
'typeDoc': 'typeDoc',
'consulted': 'consulted',
request_params = {'typeFamille': 'R001', 'ikpiPersonne': ''}
# Here we parse the json with idcontrat in it. For the cases present
# on privee.mabanque where sometimes the doc url is different
url = 'demat-wspl/rest/consultationDocumentSpecialBpfDemat?'
'heureDocument': 'heureDoc',
'numClient': 'numClient',
'typeReport': 'typeReport',
request_params = {'ibanCrypte': ''}
for k, v in keys_to_copy.items():
request_params[k] = Dict(v)(self)
return Env('baseurl')(self) + url + urlencode(request_params)
def obj_type(self):
return get_document_type(Dict('libelleSousFamille')(self))
......@@ -30,7 +30,8 @@ import lxml.html as html
from weboob.browser.elements import DictElement, ListElement, TableElement, ItemElement, method
from weboob.browser.filters.json import Dict
from .compat.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,
from weboob.browser.filters.html import TableCell
from weboob.browser.pages import JsonPage, LoggedPage, HTMLPage
......@@ -107,7 +108,10 @@ class ConnectionThresholdPage(HTMLPage):
return True
def on_load(self):
msg = CleanText('//div[@class="confirmation"]//span[span]')(self.doc)
msg = (
CleanText('//div[@class="confirmation"]//span[span]')(self.doc) or
CleanText('//p[contains(text(), "Vous avez atteint la date de fin de vie de votre code secret")]')(self.doc)
self.logger.warning('Password expired.')
if not self.browser.rotating_password:
......@@ -306,7 +310,6 @@ class ProfilePage(LoggedPage, JsonPage):
class AccountsPage(BNPPage):
class iter_accounts(DictElement):
item_xpath = 'data/infoUdc/familleCompte'
......@@ -315,6 +318,12 @@ class AccountsPage(BNPPage):
item_xpath = 'compte'
class item(ItemElement):
def validate(self, obj):
# We skip loans with a balance of 0 because the JSON returned gives
# us no info (only `null` values on all fields), so there is nothing
# useful to display
return obj.type != Account.TYPE_LOAN or obj.balance != 0
2: Account.TYPE_SAVINGS,
......@@ -354,6 +363,8 @@ class AccountsPage(BNPPage):
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,11 +392,15 @@ class LoanDetailsPage(BNPPage):
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
class fill_revolving_details(ItemElement):
obj_total_amount = Dict('data/montantDisponible')
obj_rate = Dict('data/tauxInterets')
obj__subscriber = Format('%s %s', Dict('data/titulaire/nom'), Dict('data/titulaire/prenom'))
obj__iduser = None
class AccountsIBANPage(BNPPage):
......@@ -441,6 +456,8 @@ class RecipientsPage(BNPPage):
class iter_recipients(DictElement):
item_xpath = 'data/infoBeneficiaire/listeBeneficiaire'
# We ignore duplicate because BNP allows differents recipients with the same iban
ignore_duplicate = True
class item(MyRecipient):
# For the moment, only yield ready to transfer on recipients.
......@@ -745,6 +762,8 @@ class CapitalisationPage(LoggedPage, HTMLPage):
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
......@@ -54,6 +54,7 @@ class Transaction(FrenchTransaction):
(re.compile(r'^(?P<category>FRAIS POUR)(?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^(?P<text>(?P<category>REMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^(?P<category>REMISE DE CHEQUES?) (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(r'^(?P<category>VERSEMENT DAB) (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(r'^(?P<text>DEBIT CARTE BANCAIRE DIFFERE.*)'), FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^(?P<category>COTISATION TRIMESTRIELLE).*'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^REMISE COMMERCIALE.*'), FrenchTransaction.TYPE_BANK),
......@@ -136,6 +136,7 @@ class item_account_generic(ItemElement):
'livrets?': Account.TYPE_SAVINGS,
'epargnes? logement': Account.TYPE_SAVINGS,
"autres produits d'epargne": Account.TYPE_SAVINGS,
'compte relais': Account.TYPE_SAVINGS,
'comptes? titres? et pea': Account.TYPE_MARKET,
'compte-titres': Account.TYPE_MARKET,
'assurances? vie': Account.TYPE_LIFE_INSURANCE,
......@@ -52,6 +52,7 @@ from .pages import (
SmsPage, SmsPageOption, SmsRequest, AuthentPage, RecipientPage, CanceledAuth, CaissedepargneKeyboard,
TransactionsDetailsPage, LoadingPage, ConsLoanPage, MeasurePage, NatixisLIHis, NatixisLIInv, NatixisRedirectPage,
SubscriptionPage, CreditCooperatifMarketPage, UnavailablePage, CardsPage, CardsComingPage, CardsOldWebsitePage, TransactionPopupPage,
OldLeviesPage, NewLeviesPage,
from .linebourse_browser import LinebourseAPIBrowser
......@@ -86,6 +87,8 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
cards_old = URL('https://.*/Portail.aspx.*', CardsOldWebsitePage)
cards = URL('https://.*/Portail.aspx.*', CardsPage)
cards_coming = URL('https://.*/Portail.aspx.*', CardsComingPage)
old_checkings_levies = URL(r'https://.*/Portail.aspx.*', OldLeviesPage)
new_checkings_levies = URL(r'https://.*/Portail.aspx.*', NewLeviesPage)
authent = URL('https://.*/Portail.aspx.*', AuthentPage)
subscription = URL('https://.*/Portail.aspx\?tache=(?P<tache>).*', SubscriptionPage)
transaction_popup = URL(r'https://.*/Portail.aspx.*', TransactionPopupPage)
......@@ -458,7 +461,7 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
- In CardsPage, there are cards (with "Business" in the label) without checking account on the
website (neither history nor coming), so we skip them.
- Some card on the CardsPage that have a checking account parent, but if we follow the link to
reach it with CardsComingPage, we find an other card that not in CardsPage.
reach it with CardsComingPage, we find an other card that is not in CardsPage.
if self.new_website:
for account in self.accounts:
......@@ -736,33 +739,51 @@ class CaisseEpargne(LoginBrowser, StatesMixin):
def get_coming(self, account):
if account.type != account.TYPE_CARD:
return []
if account.type == account.TYPE_CHECKING:
return self.get_coming_checking(account)
elif account.type == account.TYPE_CARD:
return self.get_coming_card(account)
return []
def get_coming_checking(self, account):
# The accounts list or account history page does not contain comings for checking accounts
# We need to go to a specific levies page where we can find past and coming levies (such as recurring ones)
trs = []
self.home.go() # need to go to cards page to have access to the nav bar where we can choose LeviesPage from
if not
return trs # need to go to a general page where we find levies for all accounts before requesting a specific account
if not
return trs
if self.new_checkings_levies.is_here() or self.old_checkings_levies.is_here():