Commit df599428 authored by Damien Mat's avatar Damien Mat Committed by Vincent A

[caissedepargne] Split cards from parent accounts on CENet subsite

Works as well on connections where almost no information
on parent account is available.
parent 2477826e
......@@ -22,7 +22,6 @@ from __future__ import unicode_literals
from collections import Counter
from fnmatch import fnmatch
import json
from weboob.browser import LoginBrowser, need_login, StatesMixin
from weboob.browser.url import URL
......@@ -33,6 +32,7 @@ from import Account
from import (
sorted_transactions, omit_deferred_transactions, keep_only_card_transactions,
from import json
from .pages import (
......@@ -138,11 +138,6 @@ class CenetBrowser(LoginBrowser, StatesMixin):
def get_accounts_list(self):
if self.accounts is None:
headers = {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01'
data = {
'contexte': '',
'dateEntree': None,
......@@ -150,33 +145,40 @@ class CenetBrowser(LoginBrowser, StatesMixin):
'filtreEntree': None
# get accounts from CenetAccountsPage
accounts = [account for account in self.cenet_accounts.go(data=json.dumps(data), headers=headers).get_accounts()]
self.accounts = list(self.cenet_accounts.go(json=data).get_accounts())
except ClientError:
# Unauthorized due to wrongpass
raise BrowserIncorrectPassword()
# get cards, and potential missing card's parent accouts from CenetCardsPage
self.cenet_cards.go(data=json.dumps(data), headers=headers)
except BrowserUnavailable:
# for some accounts, the site can throw us an error, during weeks
self.logger.warning('ignoring cards because site is unavailable...')
if not self.accounts:
shallow_parent_accounts = list(
if shallow_parent_accounts:'Found shallow parent account(s)): %s' % shallow_parent_accounts)
cards = list(
redacted_ids = Counter([:4] +[-6:] for card in cards)
for id in redacted_ids:
assert redacted_ids[id] == 1, 'there are several cards with the same %r' % id
for redacted_id in redacted_ids:
assert redacted_ids[redacted_id] == 1, 'there are several cards with the same id %r' % redacted_id
for card in cards:
card.parent = find_object(accounts, id=card._parent_id)
assert card.parent, 'no parent account found for card'
card.parent = find_object(self.accounts, id=card._parent_id)
assert card.parent, 'no parent account found for card %s' % card
# get loans from CenetLoanPage
for account in
self.accounts = accounts
return self.accounts
......@@ -192,36 +194,43 @@ class CenetBrowser(LoginBrowser, StatesMixin):
return []
if account.type == account.TYPE_CARD:
def match_cb(tr):
return self._matches_card(tr, account.number)
hist = self.get_history_base(account.parent, deferred=account.number)
return keep_only_card_transactions(hist, match_cb)
return omit_deferred_transactions(self.get_history_base(account))
def get_history_base(self, account, deferred=None):
headers = {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01'
if not account.parent._formated and account._hist:
# this is a card account with a shallow parent
return []
# this is a card account with data available on the parent
def match_card(tr):
# ex: account.number="1234123456123456", tr.card="1234******123456"
return fnmatch(account.number, tr.card)
hist = self.get_history_base(account.parent, card_number=account.number)
return keep_only_card_transactions(hist, match_card)
# this is any other account
return omit_deferred_transactions(self.get_history_base(account))
def get_history_base(self, account, card_number=None):
data = {
'contexte': '',
'dateEntree': None,
'filtreEntree': None,
'donneesEntree': json.dumps(account._formated),
self.cenet_account_history.go(data=json.dumps(data), headers=headers)
while True:
for tr in
for tr in
# yield transactions from account
# if account is a card, this does not include card_summary detail
yield tr
if tr.type == tr.TYPE_CARD_SUMMARY and deferred:
if tr.type == tr.TYPE_CARD_SUMMARY and card_number:
# cheking if card_cummary is for this card
assert tr.card, 'card summary has no card number?'
if not self._matches_card(tr, deferred):
if not self._matches_card(tr, card_number):
# getting detailed transactions for card_summary
donneesEntree = {}
donneesEntree['Compte'] = account._formated
......@@ -232,7 +241,7 @@ class CenetBrowser(LoginBrowser, StatesMixin):
'donneesEntree': json.dumps(donneesEntree).replace('/', '\\/'),
'filtreEntree': json.dumps(tr._data).replace('/', '\\/')
tr_detail_page =, headers=headers)
tr_detail_page =
parent_tr = tr
for tr in tr_detail_page.get_history():
......@@ -246,20 +255,15 @@ class CenetBrowser(LoginBrowser, StatesMixin):
data['filtreEntree'] = json.dumps({
'Offset': offset,
self.cenet_account_history.go(data=json.dumps(data), headers=headers)
def get_coming(self, account):
if account.type != account.TYPE_CARD:
if account.type != Account.TYPE_CARD:
return []
trs = []
headers = {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01'
data = {
'contexte': '',
'dateEntree': None,
......@@ -267,9 +271,8 @@ class CenetBrowser(LoginBrowser, StatesMixin):
'filtreEntree': None
self.cenet_account_coming.go(data=json.dumps(data), headers=headers)
for tr in
for tr in
return sorted_transactions(trs)
......@@ -17,8 +17,9 @@
# 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 copy import deepcopy
from decimal import Decimal
import re
import json
from datetime import datetime
......@@ -26,7 +27,7 @@ from datetime import datetime
from weboob.browser.pages import LoggedPage, HTMLPage, JsonPage
from weboob.browser.elements import DictElement, ItemElement, method
from weboob.browser.filters.standard import (
Date, CleanDecimal, CleanText, Format, Field, Env, Eval, Regexp, Currency,
Date, CleanDecimal, CleanText, Format, Field, Env, Regexp, Currency,
from weboob.browser.filters.json import Dict
from weboob.capabilities import NotAvailable
......@@ -120,7 +121,7 @@ class CenetAccountsPage(LoggedPage, CenetJsonPage):
class item(ItemElement):
klass = Account
obj_id = CleanText(Dict('Numero'))
obj_id = obj_number = CleanText(Dict('Numero'))
obj_label = CleanText(Dict('Intitule'))
obj_iban = CleanText(Dict('IBAN'))
......@@ -185,10 +186,6 @@ class CenetLoanPage(LoggedPage, CenetJsonPage):
return NotAvailable
def float_to_debit(f):
return -Decimal(str(f))
class CenetCardsPage(LoggedPage, CenetJsonPage):
class iter_cards(DictElement):
......@@ -196,20 +193,24 @@ class CenetCardsPage(LoggedPage, CenetJsonPage):
class item(ItemElement):
def condition(self):
assert self.el['Type'] in ('I', 'D')
# D : Deferred debit card
# I : Immediate debit card
assert self.el['Type'] in ('I', 'D'), 'Unknown card type'
return self.el['Type'] == 'D'
klass = Account
obj_id = obj_number = Dict('Numero') # full card number
obj_id = obj_number = Dict('Numero') # full card number
obj__parent_id = Dict('Compte/Numero')
obj_label = Format('%s%s', Dict('Titulaire/DesignationPersonne'), Dict('Compte/LibelleComplet'))
obj_coming = Eval(float_to_debit, Dict('CumulEnCours/Montant/Valeur'))
obj_label = Format('%s %s', Dict('Titulaire/DesignationPersonne'), Dict('Numero'))
obj_balance = 0
obj_currency = 'EUR'
obj_currency = 'EUR' # not available when no coming
obj_type = Account.TYPE_CARD
obj_coming = CleanDecimal(Dict('CumulEnCours/Montant/Valeur'), sign='-')
def obj__hist(self):
# Real Date will not be accepted as history and coming request parameters done later
# So we store 'el' dict here with all "Date" to None
def reword_dates(card):
for k, v in card.items():
if isinstance(v, dict):
......@@ -221,6 +222,29 @@ class CenetCardsPage(LoggedPage, CenetJsonPage):
return el
class iter_shallow_parent_accounts(DictElement):
Parent accounts are mentioned here, next to their associated cards.
But they bear almost no info.
If they were not found on CenetAccountsPage,
they can be used as shallow parent accounts for the cards found,
and so avoid having orphan cards.
item_xpath = 'DonneesSortie'
ignore_duplicate = True
class item(ItemElement):
klass = Account
obj_id = obj_number = Dict('Compte/Numero')
obj_type = Account.TYPE_CHECKING
obj_label = Dict('Compte/Intitule')
def obj__formated(self):
# Sent empty to signal that this is a shallow account
return {}
class CenetAccountHistoryPage(LoggedPage, CenetJsonPage):
......@@ -252,6 +276,9 @@ class CenetAccountHistoryPage(LoggedPage, CenetJsonPage):
obj_rdate = Date(Dict('DateGroupReglement'), dayfirst=True)
def obj_type(self):
if Env('coming')(self):
return Transaction.TYPE_DEFERRED_CARD
ret = Transaction.TYPE_UNKNOWN
# The API may send the same key for 'PRLV' and 'VIR' transactions
......@@ -281,7 +308,7 @@ class CenetAccountHistoryPage(LoggedPage, CenetJsonPage):
def obj__data(self):
return self.el
obj_card = Regexp(obj_label, r'^CB (\d+\*+\d+)', default=None)
obj_card = Regexp(Field('label'), r'^CB (\d{4}\*{6}\d{6})', default=None)
def next_offset(self):
offset = Dict('OffsetSortie')(self.doc)
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