Commit 42cec892 authored by Romain Bignon's avatar Romain Bignon

backport master modules fixes

parent 030cf474
Pipeline #2591 failed with stages
in 7 minutes and 27 seconds
......@@ -67,9 +67,12 @@ class AppValidation(DecoupledValidation):
from weboob.exceptions import BrowserRedirect as _BrowserRedirect
class BrowserRedirect(_BrowserRedirect):
def __init__(self, url):
def __init__(self, url, resource=None):
self.url = url
# Needed for transfer redirection
self.resource = resource
def __str__(self):
return 'Redirecting to %s' % self.url
......
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
# Copyright(C) 2019 Budget Insight
#
# 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 Affero General Public License as published by
# 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 Affero General Public License for more details.
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from .module import AmeliModule
......
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
# Copyright(C) 2019 Budget Insight
#
# 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 Affero General Public License as published by
# 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 Affero General Public License for more details.
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded
from .pages import (
LoginPage, HomePage, CguPage, AccountPage, LastPaymentsPage, PaymentsPage, PaymentDetailsPage, Raw, UnavailablePage,
)
from weboob.tools.compat import basestring
from datetime import date
from time import time
from dateutil.relativedelta import relativedelta
__all__ = ['AmeliBrowser']
from weboob.browser import LoginBrowser, URL, need_login
from .pages import ErrorPage, LoginPage, SubscriptionPage, DocumentsPage
class AmeliBrowser(LoginBrowser):
BASEURL = 'https://assure.ameli.fr'
loginp = URL(r'/PortailAS/appmanager/PortailAS/assure\?.*_pageLabel=as_login_page', LoginPage)
homep = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_accueil_page', HomePage)
cgup = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page', CguPage)
accountp = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page', AccountPage)
paymentsp = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_paiements_page', PaymentsPage)
paymentdetailsp = URL(r'/PortailAS/paiements.do\?actionEvt=chargerDetailPaiements.*', PaymentDetailsPage)
lastpaymentsp = URL(r'/PortailAS/paiements.do\?actionEvt=afficherPaiements.*', LastPaymentsPage)
pdf_page = URL(r'PortailAS/PDFServletReleveMensuel.dopdf\?PDF.moisRecherche=.*', Raw)
unavailablep = URL(r'/vu/INDISPO_COMPTE_ASSURES.html', UnavailablePage)
error_page = URL(r'/vu/INDISPO_COMPTE_ASSURES.html', ErrorPage)
login_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&connexioncompte_2actionEvt=afficher.*', LoginPage)
subscription_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page.*', SubscriptionPage)
documents_page = URL(r'/PortailAS/paiements.do', DocumentsPage)
def do_login(self):
self.logger.debug('call Browser.do_login')
# set this cookie to get login form in response
self.session.cookies['hbc'] = ''
self.loginp.stay_or_go()
if self.homep.is_here():
return True
self.login_page.go()
self.page.login(self.username, self.password)
error = self.page.is_error()
if error:
raise BrowserIncorrectPassword(error)
self.page.locate_to_cgu_page()
if self.cgup.is_here():
raise ActionNeeded(self.page.get_cgu())
self.homep.stay_or_go() # Redirection not interpreted by browser. Manually redirect on homep
if not self.homep.is_here():
raise BrowserIncorrectPassword()
@need_login
def iter_subscription_list(self):
self.logger.debug('call Browser.iter_subscription_list')
self.accountp.stay_or_go()
return self.page.iter_subscription_list()
@need_login
def get_subscription(self, id):
self.logger.debug('call Browser.get_subscription')
assert isinstance(id, basestring)
for sub in self.iter_subscription_list():
if id == sub._id:
return sub
return None
@need_login
def iter_history(self, sub):
transactions = []
self.logger.debug('call Browser.iter_history')
self.paymentsp.stay_or_go()
payments_url = self.page.get_last_payments_url()
self.location(payments_url)
assert self.lastpaymentsp.is_here()
urls = self.page.iter_last_payments()
for url in urls:
self.location(url)
assert self.paymentdetailsp.is_here()
for payment in self.page.iter_payment_details(sub):
transactions.append(payment)
# go to a page with a "deconnexion" link so that logged property
# stays True and next call to do_login doesn't crash when using the
# blackbox
self.accountp.go()
return transactions
@need_login
def iter_documents(self, sub):
self.logger.debug('call Browser.iter_documents')
self.paymentsp.stay_or_go()
payments_url = self.page.get_last_payments_url()
self.location(payments_url)
assert self.lastpaymentsp.is_here()
for document in self.page.iter_documents(sub):
yield document
def iter_subscription(self):
self.subscription_page.go()
return self.page.iter_subscriptions()
@need_login
def get_document(self, id):
self.logger.debug('call Browser.get_document')
assert isinstance(id, basestring)
subs = self.iter_subscription_list()
for sub in subs:
for b in self.iter_documents(sub):
if id == b.id:
return b
return False
def iter_documents(self, subscription):
end_date = date.today()
start_date = end_date - relativedelta(years=1)
# FUN FACT, website tell us documents are available for 6 months
# let's suppose today is 28/05/19, website frontend limit DateDebut to 28/11/18 but we can get a little bit more
# by setting a previous date and get documents that are no longer available for simple user
params = {
'Beneficiaire': 'tout_selectionner',
'DateDebut': start_date.strftime('%d/%m/%Y'),
'DateFin': end_date.strftime('%d/%m/%Y'),
'actionEvt': 'afficherPaiementsComplementaires',
'afficherIJ': 'false',
'afficherInva': 'false',
'afficherPT': 'false',
'afficherRS': 'false',
'afficherReleves': 'false',
'afficherRentes': 'false',
'idNoCache': int(time()*1000)
}
# the second request is stateful
# first value of actionEvt is afficherPaiementsComplementaires to get all payments from last 6 months
# (start_date 6 months in the past is needed but not enough)
self.documents_page.go(params=params)
# then we set Rechercher to actionEvt to filter for this subscription, within last 6 months
# without first request we would have filter for this subscription but within last 2 months
params['actionEvt'] = 'Rechercher'
params['Beneficiaire'] = subscription._param
self.documents_page.go(params=params)
return self.page.iter_documents(subid=subscription.id)
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
# Copyright(C) 2019 Budget Insight
#
# 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 Affero General Public License as published by
# 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 Affero General Public License for more details.
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from weboob.capabilities.bill import DocumentTypes, CapDocument, SubscriptionNotFound, DocumentNotFound, Subscription, Bill
from weboob.capabilities.base import find_object
from weboob.tools.backend import Module, BackendConfig
from weboob.capabilities.bill import CapDocument, Document, DocumentTypes, SubscriptionNotFound, DocumentNotFound
from weboob.tools.value import ValueBackendPassword
from .browser import AmeliBrowser
__all__ = ['AmeliModule']
class AmeliModule(Module, CapDocument):
NAME = 'ameli'
DESCRIPTION = 'Ameli website: French Health Insurance'
MAINTAINER = 'Christophe Lampin'
EMAIL = 'weboob@lampin.net'
DESCRIPTION = "le site de l'Assurance Maladie en ligne"
MAINTAINER = 'Florian Duguet'
EMAIL = 'florian.duguet@budget-insight.com'
LICENSE = 'LGPLv3+'
VERSION = '1.5'
LICENSE = 'AGPLv3+'
BROWSER = AmeliBrowser
CONFIG = BackendConfig(ValueBackendPassword('login', label='Numero de SS', regexp=r'^\d{13}$', masked=False),
ValueBackendPassword('password', label='Password', masked=True))
CONFIG = BackendConfig(ValueBackendPassword('login', label='Mon numero de sécurité sociale', regexp=r'^\d{13}$', masked=False),
ValueBackendPassword('password', label='Mon code (4 à 13 chiffres)', regexp=r'^\d{4,13}', masked=True))
accepted_document_types = (DocumentTypes.BILL,)
def create_default_browser(self):
return self.create_browser(self.config['login'].get(),
self.config['password'].get())
return self.create_browser(self.config['login'].get(), self.config['password'].get())
def iter_subscription(self):
return self.browser.iter_subscription_list()
return self.browser.iter_subscription()
def get_subscription(self, _id):
subscription = self.browser.get_subscription(_id)
if not subscription:
raise SubscriptionNotFound()
else:
return subscription
def iter_documents_history(self, subscription):
if not isinstance(subscription, Subscription):
subscription = self.get_subscription(subscription)
return self.browser.iter_history(subscription)
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 get_document(self, id):
bill = self.browser.get_document(id)
if not bill:
raise DocumentNotFound()
else:
return bill
def download_document(self, bill):
if not isinstance(bill, Bill):
bill = self.get_document(bill)
response = self.browser.open(bill.url, stream=True)
if not response or response.headers['content-type'] != "application/pdf":
return None
return response.content
def get_document(self, _id):
subid = _id.rsplit('_', 1)[0]
subscription = self.get_subscription(subid)
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
This diff is collapsed.
......@@ -540,18 +540,20 @@ class AccountsPage(LoggedPage, MyHTMLPage):
u'Synthèse': None, # ignore this title
}
PATTERN = [(re.compile('.*Titres Pea.*'), Account.TYPE_PEA),
(re.compile(".*Plan D'epargne En Actions.*"), Account.TYPE_PEA),
(re.compile(".*Compte Especes Pea.*"), Account.TYPE_PEA),
(re.compile('.*Plan Epargne Retraite.*'), Account.TYPE_PERP),
(re.compile('.*Titres.*'), Account.TYPE_MARKET),
(re.compile('.*Selection Vie.*'),Account.TYPE_LIFE_INSURANCE),
(re.compile('^Fructi Pulse.*'), Account.TYPE_MARKET),
(re.compile('^(Quintessa|Solevia).*'), Account.TYPE_MARKET),
(re.compile('^Plan Epargne Enfant Mul.*'), Account.TYPE_MARKET),
(re.compile('^Alc Premium'), Account.TYPE_MARKET),
(re.compile('^Plan Epargne Enfant Msu.*'), Account.TYPE_LIFE_INSURANCE),
]
PATTERN = [
(re.compile(r'.*Titres Pea.*'), Account.TYPE_PEA),
(re.compile(r".*Plan D'epargne En Actions.*"), Account.TYPE_PEA),
(re.compile(r".*Compte Especes Pea.*"), Account.TYPE_PEA),
(re.compile(r'.*Plan Epargne Retraite.*'), Account.TYPE_PERP),
(re.compile(r'.*Titres.*'), Account.TYPE_MARKET),
(re.compile(r'.*Selection Vie.*'), Account.TYPE_LIFE_INSURANCE),
(re.compile(r'^Fructi Pulse.*'), Account.TYPE_MARKET),
(re.compile(r'^(Quintessa|Solevia).*'), Account.TYPE_MARKET),
(re.compile(r'^Plan Epargne Enfant Mul.*'), Account.TYPE_MARKET),
(re.compile(r'^Alc Premium'), Account.TYPE_MARKET),
(re.compile(r'^Plan Epargne Enfant Msu.*'), Account.TYPE_LIFE_INSURANCE),
(re.compile(r'^Parts Sociales.*'), Account.TYPE_MARKET),
]
def pop_up(self):
if self.doc.xpath('//span[contains(text(), "du navigateur Internet.")]'):
......
......@@ -109,26 +109,28 @@ class BnpcartesentrepriseBrowser(LoginBrowser):
account.coming = self.page.get_balance()
yield account
if self.type == '2':
for rib in self.page.get_rib_list():
for company in self.page.get_companies():
self.accounts.stay_or_go()
self.page.expand(rib=rib)
accounts = list(self.page.iter_accounts(rib=rib))
ids = {}
prev_rib = None
for account in accounts:
if account.id in ids:
self.logger.warning('duplicate account %r', account.id)
account.id += '_%s' % ''.join(account.label.split())
if prev_rib != account._rib:
self.coming.go()
self.page.expand(rib=account._rib)
account.coming = self.page.get_balance(account)
prev_rib = account._rib
ids[account.id] = account
yield account
self.page.expand(company=company)
for rib in self.page.get_rib_list():
self.page.expand(rib=rib, company=company)
accounts = list(self.page.iter_accounts(rib=rib, company=company))
ids = {}
prev_rib = None
for account in accounts:
if account.id in ids:
self.logger.warning('duplicate account %r', account.id)
account.id += '_%s' % ''.join(account.label.split())
if prev_rib != account._rib:
self.coming.go()
self.page.expand(rib=account._rib, company=account._company)
account.coming = self.page.get_balance(account)
prev_rib = account._rib
ids[account.id] = account
yield account
# Could be the very same as non corporate but this shitty website seems
# completely bugged
......@@ -136,34 +138,34 @@ class BnpcartesentrepriseBrowser(LoginBrowser):
if account.id not in self.transactions_dict:
self.transactions_dict[account.id] = []
self.ti_histo_go()
self.page.expand(self.page.get_periods()[0], account=account)
self.page.expand(self.page.get_periods()[0], account=account, company=account._company)
for tr in sorted_transactions(self.page.get_history()):
self.transactions_dict[account.id].append(tr)
return self.transactions_dict[account.id]
def get_ti_transactions(self, account):
self.ti_card_go()
self.page.expand(account=account)
self.page.expand(account=account, company=account._company)
for tr in sorted_transactions(self.page.get_history()):
yield tr
self.ti_histo_go()
self.page.expand(self.page.get_periods()[0], account=account)
self.page.expand(self.page.get_periods()[0], account=account, company=account._company)
for period in self.page.get_periods():
self.page.expand(period, account=account)
self.page.expand(period, account=account, company=account._company)
for tr in sorted_transactions(self.page.get_history()):
yield tr
def get_ge_transactions(self, account):
transactions = []
self.coming.go()
self.page.expand(rib=account._rib)
self.page.expand(account=account, rib=account._rib, company=account._company)
link = self.page.get_link(account)
if link:
self.location(link)
transactions += self.page.get_history()
self.history.go()
for period in self.page.get_periods():
self.page.expand(period, rib=account._rib)
self.page.expand(period, rib=account._rib, company=account._company, account=account)
link = self.page.get_link(account)
if link:
self.location(link)
......
......@@ -49,12 +49,18 @@ class LoginPage(HTMLPage):
class ExpandablePage(LoggedPage, HTMLPage):
def expand(self, account=None, rib=None):
def expand(self, account=None, rib=None, company=None):
form = self.get_form()
if rib is not None:
form['ribSaisi'] = rib
if account is not None:
form['numCarteSaisi'] = account._nav_num
form['nomCarteSaisi'] = account.label # some forms use 'nomCarteSaisi' some use 'titulaireSaisie'
form['titulaireSaisie'] = account.label
if company is not None:
if 'entrepriseSaisie' in form.keys(): # some forms use 'entrepriseSaisie' some use 'entrepriseSaisi'
form['entrepriseSaisie'] = company
else:
form['entrepriseSaisi'] = company
# needed if coporate titulaire
form.url = form.url.replace('Appliquer', 'Afficher')
form.submit()
......@@ -80,13 +86,16 @@ class PeriodsPage(LoggedPage, HTMLPage):
periods.append(period)
return periods
def expand(self, period, account=None, rib=None):
def expand(self, period, account=None, rib=None, company=None):
form = self.get_form(submit='//input[@value="Display"]')
if account is not None:
form['numCarteSaisi'] = account._nav_num
form['nomCarteSaisi'] = account.label
form['titulaireSaisi'] = account.label
form['periodeSaisie'] = period
if rib is not None:
form['ribSaisi'] = rib
if company is not None:
form['entrepriseSaisi'] = company
# needed if coporate titulaire
form.url = form.url.replace('Appliquer', 'Afficher')
form.submit()
......@@ -109,6 +118,7 @@ class AccountsPage(ExpandablePage, GetableLinksPage):
obj_label = CleanText('./td[1]')
obj_type = Account.TYPE_CARD
obj__rib = Env('rib')
obj__company = Env('company')
obj_currency = u'EUR'
obj_number = CleanText('./td[2]', replace=[(' ', '')])
obj_url = AbsoluteLink('./td[2]/a')
......@@ -118,6 +128,9 @@ class AccountsPage(ExpandablePage, GetableLinksPage):
def store(self, obj):
return obj
def get_companies(self):
return self.doc.xpath('//select[@name="entrepriseSaisie"]/option/@value')
class ComingPage(ExpandablePage):
def get_link(self, account):
......
......@@ -334,6 +334,7 @@ class AccountsPage(BNPPage):
'Crédit immobilier': Account.TYPE_MORTGAGE,
'Réserve Provisio': Account.TYPE_REVOLVING_CREDIT,
'Prêt personnel': Account.TYPE_CONSUMER_CREDIT,
'Crédit Silo': Account.TYPE_REVOLVING_CREDIT,
}
klass = Account
......
......@@ -333,20 +333,18 @@ class BoursoramaBrowser(RetryLoginBrowser, StatesMixin):
return self.get_regular_transactions(account, coming)
def get_regular_transactions(self, account, coming):
# We look for 3 years of history.
params = {}
params['movementSearch[toDate]'] = (date.today() + relativedelta(days=40)).strftime('%d/%m/%Y')
params['movementSearch[fromDate]'] = (date.today() - relativedelta(years=3)).strftime('%d/%m/%Y')
params['movementSearch[selectedAccounts][]'] = account._webid
if not coming:
# We look for 3 years of history.
params = {}
params['movementSearch[toDate]'] = (date.today() + relativedelta(days=40)).strftime('%d/%m/%Y')
params['movementSearch[fromDate]'] = (date.today() - relativedelta(years=3)).strftime('%d/%m/%Y')
params['movementSearch[selectedAccounts][]'] = account._webid
self.location('%s/mouvements' % account.url.rstrip('/'), params=params)
for transaction in self.page.iter_history():
yield transaction
elif account.type == Account.TYPE_CHECKING:
self.location('%s/mouvements-a-venir' % account.url.rstrip('/'), params=params)
for transaction in self.page.iter_history(coming=True):
yield transaction
# Note: Checking accounts have a 'Mes prélèvements à venir' tab,
# but these transactions have no date anymore so we ignore them.
def get_card_transactions(self, account, coming):
# All card transactions can be found in the CSV (history and coming),
......
......@@ -460,7 +460,6 @@ class HistoryPage(LoggedPage, HTMLPage):
class item(ItemElement):
klass = Transaction
obj_date = Date(Attr('.//time', 'datetime'))
obj_amount = CleanDecimal('.//div[has-class("list__movement__line--amount")]', replace_dots=True)
obj_category = CleanText('.//span[has-class("category")]')
obj__account_name = CleanText('.//span[contains(@class, "account__name-xs")]', default=None)
......
......@@ -617,7 +617,7 @@ class BProBrowser(BPBrowser):
self.location(self.accounts_url)
assert self.pro_accounts_list.is_here()
for account in self.page.get_accounts_list():
for account in self.page.iter_accounts():
ids.add(account.id)
accounts.append(account)
......@@ -625,7 +625,7 @@ class BProBrowser(BPBrowser):
self.location(self.accounts_and_loans_url)
assert self.pro_accounts_list.is_here()
for account in self.page.get_accounts_list():
for account in self.page.iter_accounts():
if account.id not in ids:
ids.add(account.id)
accounts.append(account)
......
......@@ -39,31 +39,26 @@ from .base import MyHTMLPage
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile(u'^(?P<category>CHEQUE)( N)? (?P<text>.*)'),
FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(?P<category>ACHAT CB) (?P<text>.*) (?P<dd>\d{2})\.(?P<mm>\d{2}).(?P<yy>\d{2,4}).*'),
FrenchTransaction.TYPE_CARD),
(re.compile('^(?P<category>(PRELEVEMENT DE|TELEREGLEMENT|TIP)) (?P<text>.*)'),
FrenchTransaction.TYPE_ORDER),
(re.compile('^(?P<category>ECHEANCEPRET)(?P<text>.*)'),
FrenchTransaction.TYPE_LOAN_PAYMENT),
(re.compile(r'^CARTE \w+ (?P<dd>\d{2})/(?P<mm>\d{2})/(?P<yy>\d{2,4}) A \d+H\d+ (?P<category>RETRAIT DAB) (?P<text>.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?P<category>RETRAIT DAB) (?P<dd>\d{2})/(?P<mm>\d{2})/(?P<yy>\d{2,4}) \d+H\d+ (?P<text>.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?P<category>RETRAIT) (?P<text>.*) (?P<dd>\d{2})\.(?P<mm>\d{2})\.(?P<yy>\d{2,4})'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile('^(?P<category>VIR(EMEN)?T?) (DE |POUR )?(?P<text>.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile('^(?P<category>REMBOURST)(?P<text>.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile('^(?P<category>COMMISSIONS)(?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P<category>FRAIS POUR)(?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P<text>(?P<category>REMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P<category>REMISE DE CHEQUES?) (?P<text>.*)'), FrenchTransaction.TYPE_DEPOSIT),
(re.compile(u'^(?P<text>DEBIT CARTE BANCAIRE DIFFERE.*)'), FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile('^COTISATION TRIMESTRIELLE.*'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P<category>FRAIS (TRIMESTRIELS) DE TENUE DE COMPTE.*)'), FrenchTransaction.TYPE_BANK)
]
PATTERNS = [
(re.compile(r'^(?P<category>CHEQUE)( N)? (?P<text>.*)'), FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(?P<category>ACHAT CB) (?P<text>.*) (?P<dd>\d{2})\.(?P<mm>\d{2}).(?P<yy>\d{2,4}).*'), FrenchTransaction.TYPE_CARD),
(re.compile(r'^(?P<category>(PRELEVEMENT DE|TELEREGLEMENT|TIP)) (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
(re.compile(r'^(?P<category>ECHEANCEPRET)(?P<text>.*)'), FrenchTransaction.TYPE_LOAN_PAYMENT),
(re.compile(r'^CARTE \w+ (?P<dd>\d{2})/(?P<mm>\d{2})/(?P<yy>\d{2,4}) A \d+H\d+ (?P<category>RETRAIT DAB) (?P<text>.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?P<category>RETRAIT DAB) (?P<dd>\d{2})/(?P<mm>\d{2})/(?P<yy>\d{2,4}) \d+H\d+ (?P<text>.*)'), FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?P<category>RETRAIT) (?P<text>.*) (?P<dd>\d{2})\.(?P<mm>\d{2})\.(?P<yy>\d{2,4})'), FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^(?P<category>VIR(EMEN)?T?) (DE |POUR )?(?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^(?P<category>REMBOURST)(?P<text>.*)'), FrenchTransaction.TYPE_PAYBACK),
(re.compile(r'^(?P<category>COMMISSIONS)(?P<text>.*)'), FrenchTransaction.TYPE_BANK),
(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<text>DEBIT CARTE BANCAIRE DIFFERE.*)'), FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^COTISATION TRIMESTRIELLE.*'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^REMISE COMMERCIALE.*'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^.*UTILISATION DU DECOUVERT$'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^(?P<category>FRAIS (TRIMESTRIELS) DE TENUE DE COMPTE.*)'), FrenchTransaction.TYPE_BANK)
]
class AccountHistory(LoggedPage, MyHTMLPage):
......
......@@ -17,17 +17,15 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
import re
from decimal import Decimal
from __future__ import unicode_literals
from weboob.browser.elements import ListElement, ItemElement, method
from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Date
from weboob.browser.filters.html import Link
from .compat.weboob_browser_filters_standard import CleanText, CleanDecimal, Coalesce, Currency, Date, Map, Field, Regexp
from weboob.browser.filters.html import AbsoluteLink, Link
from weboob.browser.pages import LoggedPage, pagination
from .compat.weboob_capabilities_bank import Account
from weboob.capabilities.profile import Company
from weboob.capabilities.base import NotAvailable
from weboob.tools.compat import urljoin, unicode
from weboob.exceptions import BrowserUnavailable
from .accounthistory import Transaction
......@@ -36,55 +34,55 @@ from .base import MyHTMLPage
class RedirectPage(LoggedPage, MyHTMLPage):
def check_for_perso(self):
return self.doc.xpath(u'//p[contains(text(), "L\'identifiant utilisé est celui d\'un compte de Particuliers")]')
return self.doc.xpath('''//p[contains(text(), "L'identifiant utilisé est celui d'un compte de Particuliers")]''')
ACCOUNT_TYPES = {
'Comptes titres': Account.TYPE_MARKET,
'Comptes épargne': Account.TYPE_SAVINGS,
'Comptes courants': Account.TYPE_CHECKING,
}
class ProAccountsList(LoggedPage, MyHTMLPage):
# TODO Be careful about connections with personnalized account groups
# According to their presentation video (https://www.labanquepostale.fr/pmo/nouvel-espace-client-business.html),
# on the new website people are able to make personnalized groups of account instead of the usual drop-down categories on which to parse to find a match in ACCOUNT_TYPES
# If clients use the functionnality we might need to add entries new in ACCOUNT_TYPES
def on_load(self):
if self.doc.xpath('//div[@id="erreur_generale"]'):
raise BrowserUnavailable(CleanText(u'//div[@id="erreur_generale"]//p[contains(text(), "Le service est momentanément indisponible")]')(self.doc))
ACCOUNT_TYPES = {u'comptes titres': Account.TYPE_MARKET,
u'comptes épargne': Account.TYPE_SAVINGS,
# wtf? ^
u'comptes épargne': Account.TYPE_SAVINGS,
u'comptes courants': Account.TYPE_CHECKING,
}
def get_accounts_list(self):
for table in self.doc.xpath('//div[@class="comptestabl"]/table'):
try:
account_type = self.ACCOUNT_TYPES[table.get('summary').lower()]
if not account_type:
account_type = self.ACCOUNT_TYPES[table.xpath('./caption/text()')[0].strip().lower()]
except (IndexError,KeyError):
account_type = Account.TYPE_UNKNOWN
for tr in table.xpath('./tbody/tr'):
cols = tr.findall('td')