Commit 8e509c57 authored by Vincent Ardisson's avatar Vincent Ardisson Committed by Romain Bignon

[bforbank] parse when multiple deferred cards one same checking account

The "index" of the card can only be found in an history page, and same
for the balance.

For the summary transactions, we used to search them in the checking
account and dispatch to the card account, but with multiple cards, it
doesn't fit, so we generate a transaction directly.
parent 6fc85b32
......@@ -17,12 +17,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from datetime import date, timedelta
from weboob.exceptions import BrowserIncorrectPassword
from weboob.browser import LoginBrowser, URL, need_login
from weboob.capabilities.bank import Account, AccountNotFound
from weboob.capabilities.base import empty
from weboob.tools.capabilities.bank.transactions import sorted_transactions
from .pages import (
LoginPage, ErrorPage, AccountsPage, HistoryPage, LoanHistoryPage, RibPage,
......@@ -71,8 +70,6 @@ class BforbankBrowser(LoginBrowser):
self.spirica.deinit()
def do_login(self):
assert isinstance(self.username, basestring)
assert isinstance(self.password, basestring)
if not self.password.isdigit():
raise BrowserIncorrectPassword()
......@@ -96,24 +93,32 @@ class BforbankBrowser(LoginBrowser):
if account.type == Account.TYPE_CHECKING:
self.card_page.go(account=account.id)
card = self.page.get_card(account.id)
if card is not None:
cards = self.page.get_cards(account.id)
account._cards = cards
if cards:
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/0')
indexes = dict(self.page.get_card_indexes())
for card in cards:
# if there's a credit card (not debit), create a separate, virtual account
card.url = account.url
card.balance = account._card_balance
card.currency = account.currency
assert not empty(card.balance)
account._card_account = card
card._checking_account = account
card._index = indexes[card.number]
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/%s' % card._index)
card.balance = self.page.get_balance()
assert not empty(card.balance)
# insert it near its companion checking account
self.accounts.append(card)
return iter(self.accounts)
def _get_card_transactions(self, account):
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/0?month=1')
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/%s?month=1' % account._index)
assert self.card_history.is_here()
return list(self.page.get_operations())
return self.page.get_operations()
@need_login
def get_history(self, account):
......@@ -137,22 +142,14 @@ class BforbankBrowser(LoginBrowser):
return self.page.get_operations()
else:
# TODO same as get_coming, we should handle more than one card
assert account._checking_account
# for summary transactions, the transactions must be on both accounts:
# negative amount on checking account, positive on card account
old = date.today() - timedelta(days=31)
transactions = []
for tr in self.get_history(account._checking_account):
if (tr.rdate or tr.date) < old:
break
if tr.type == tr.TYPE_CARD_SUMMARY:
tr.amount = -tr.amount
transactions.append(tr)
transactions.extend(self._get_card_transactions(account))
transactions.sort(reverse=True, key=lambda tr: tr.rdate or tr.date)
transactions = list(self._get_card_transactions(account))
summary = self.page.create_summary()
transactions = sorted_transactions(transactions)
if summary.amount:
transactions.insert(0, summary)
return transactions
@need_login
......@@ -161,8 +158,7 @@ class BforbankBrowser(LoginBrowser):
self.coming.go(account=account.id)
return self.page.get_operations()
elif account.type == Account.TYPE_CARD:
# TODO there could be multiple cards, how to find the number of cards?
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/0')
self.location(account.url.replace('tableauDeBord', 'encoursCarte') + '/%s' % account._index)
return self.page.get_operations()
else:
raise NotImplementedError()
......
......@@ -17,6 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from collections import OrderedDict
import re
from io import BytesIO
......@@ -213,6 +215,31 @@ def add_qs(url, **kwargs):
class CardHistoryPage(LoggedPage, HTMLPage):
def get_card_indexes(self):
for opt in self.doc.xpath('//select[@id="select-box-card"]/option'):
number = CleanText('.')(opt).replace(' ', '').replace('*', 'x')
number = re.search(r'\d{4}x+\d{4}', number).group(0)
yield number, opt.attrib['value']
def get_balance(self):
div, = self.doc.xpath('//div[@class="m-tabs-tab-meta"]')
for d in div.xpath('.//div[has-class("pull-left")]'):
if 'opération(s):' in CleanText('.')(d):
return MyDecimal('./span', replace_dots=True)(d)
def get_debit_date(self):
return Date(Regexp(CleanText('//div[@class="m-tabs-tab-meta"]'),
r'Ces opérations (?:seront|ont été) débitées sur votre compte le (\d{2}/\d{2}/\d{4})'),
dayfirst=True)(self.doc)
def create_summary(self):
tr = Transaction()
tr.type = Transaction.TYPE_CARD_SUMMARY
tr.amount = abs(self.get_balance())
tr.label = 'Règlement cartes à débit différé'
tr.date = tr.rdate = self.get_debit_date()
return tr
@pagination
@method
class get_operations(TableElement):
......@@ -238,23 +265,24 @@ class CardHistoryPage(LoggedPage, HTMLPage):
obj_raw = CleanText(TableCell('raw'))
obj_vdate = obj_rdate = Date(CleanText(TableCell('vdate')), dayfirst=True)
obj_amount = MyDecimal(TableCell('amount'), replace_dots=True)
obj_date = Date(Regexp(CleanText('//div[@class="m-tabs-tab-meta"]'),
ur'Ces opérations (?:seront|ont été) débitées sur votre compte le (\d{2}/\d{2}/\d{4})'),
dayfirst=True)
def obj_date(self):
return self.page.get_debit_date()
class CardPage(LoggedPage, HTMLPage):
def get_card(self, account_id):
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.')
divs = [d for d in divs if not msgs.search(CleanText('.//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"]')]
if not len(divs):
self.logger.warning('all cards are cancelled, acting as if there is no card')
return
return []
cards = []
for div in divs:
......@@ -262,9 +290,6 @@ class CardPage(LoggedPage, HTMLPage):
number = CleanText('.//div[@class="m-card-infos-body-num"]', default='')(div)
number = re.sub('[^\d*]', '', number).replace('*', 'x')
debit = CleanText(u'.//div[@class="m-card-infos-body-text"][contains(text(),"Débit")]')(div)
if debit == u'Débit immédiat':
self.logger.debug('immediate debit card %s', number)
continue
assert debit == u'Débit différé', 'unrecognized card type %s: %s' % (number, debit)
card = Account()
......@@ -274,9 +299,7 @@ class CardPage(LoggedPage, HTMLPage):
card.type = Account.TYPE_CARD
cards.append(card)
# Crash on multiple cards if at least two are deferred
assert (len(divs) > 1 and len(cards) < 2) or len(divs) == 1
return cards[0] if cards else None
return cards
class LifeInsuranceList(LoggedPage, HTMLPage):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment