.*)'), FrenchTransaction.TYPE_DEPOSIT),
]
class HistoryPage(BNPPage):
CODE_TO_TYPE = {
1: Transaction.TYPE_CHECK, # Chèque émis
2: Transaction.TYPE_CHECK, # Chèque reçu
3: Transaction.TYPE_CASH_DEPOSIT, # Versement espèces
4: Transaction.TYPE_ORDER, # Virements reçus
5: Transaction.TYPE_ORDER, # Virements émis
6: Transaction.TYPE_LOAN_PAYMENT, # Prélèvements / amortissements prêts
7: Transaction.TYPE_CARD, # Paiements carte,
8: Transaction.TYPE_CARD, # Carte / Formule BNP Net,
9: Transaction.TYPE_UNKNOWN, # Opérations Titres
10: Transaction.TYPE_UNKNOWN, # Effets de Commerce
11: Transaction.TYPE_WITHDRAWAL, # Retraits d'espèces carte
12: Transaction.TYPE_UNKNOWN, # Opérations avec l'étranger
13: Transaction.TYPE_CARD, # Remises Carte
14: Transaction.TYPE_WITHDRAWAL, # Retraits guichets
15: Transaction.TYPE_BANK, # Intérêts/frais et commissions
16: Transaction.TYPE_UNKNOWN, # Tercéo
30: Transaction.TYPE_UNKNOWN, # Divers
}
COMING_TYPE_TO_TYPE = {
2: Transaction.TYPE_ORDER, # Prélèvement
3: Transaction.TYPE_CHECK, # Chèque
4: Transaction.TYPE_CARD, # Opération carte
}
def one(self, path, context=None):
try:
return list(self.path(path, context))[0]
except IndexError:
return None
def iter_history(self):
for op in self.get('data.listerOperations.compte.operationPassee') or []:
codeFamille = cast(self.one('operationType.codeFamille', op), int)
tr = Transaction.from_dict({
'id': op.get('idOperation'),
'type': self.CODE_TO_TYPE.get(codeFamille) or Transaction.TYPE_UNKNOWN,
'category': op.get('categorie'),
'amount': self.one('montant.montant', op),
})
tr.parse(raw=op.get('libelleOperation'),
date=parse_french_date(op.get('dateOperation')),
vdate=parse_french_date(self.one('montant.valueDate', op)))
raw_is_summary = re.match(r'FACTURE CARTE SELON RELEVE DU\b|FACTURE CARTE CARTE AFFAIRES \d{4}X{8}\d{4} SUIVANT\b', tr.raw)
if tr.type == Transaction.TYPE_CARD and raw_is_summary:
tr.type = Transaction.TYPE_CARD_SUMMARY
tr.deleted = True
yield tr
def iter_coming(self):
for op in self.path('data.listerOperations.compte.operationAvenir.*.operation.*'):
codeOperation = cast(op.get('codeOperation'), int, 0)
# Coming transactions don't have real id
tr = Transaction.from_dict({
'type': self.COMING_TYPE_TO_TYPE.get(codeOperation) or Transaction.TYPE_UNKNOWN,
'amount': op.get('montant'),
'card': op.get('numeroPorteurCarte'),
})
tr.parse(date=parse_french_date(op.get('dateOperation')),
vdate=parse_french_date(op.get('valueDate')),
raw=op.get('libelle'))
if tr.type == Transaction.TYPE_CARD:
tr.type = self.browser.card_to_transaction_type.get(op.get('keyCarte'),
Transaction.TYPE_DEFERRED_CARD)
yield tr
class ListDetailCardPage(BNPPage):
def get_card_to_transaction_type(self):
d = {}
for card in self.path('data.responseRestitutionCarte.listeCartes.*'):
if 'DIFFERE' in card['typeDebit']:
tr_type = Transaction.TYPE_DEFERRED_CARD
else:
tr_type = Transaction.TYPE_CARD
d[card['numCarteCrypte']] = tr_type
return d
class LifeInsurancesPage(BNPPage):
investments_path = 'data.infosContrat.repartition.listeSupport.*'
def iter_investments(self):
for support in self.path(self.investments_path):
inv = Investment()
if 'codeIsin' in support:
inv.code = inv.id = support['codeIsin']
inv.quantity = support.get('nbUC', NotAvailable)
inv.unitvalue = support.get('valUC', NotAvailable)
inv.label = support['libelle']
inv.valuation = support.get('montant', NotAvailable)
inv.set_empty_fields(NotAvailable)
yield inv
class LifeInsurancesHistoryPage(BNPPage):
def iter_history(self, coming):
for op in self.get('data.listerMouvements.listeMouvements') or []:
#We have not date for this statut so we just skit it
if op.get('statut') == u'En cours':
continue
tr = Transaction.from_dict({
'type': Transaction.TYPE_BANK,
'_state': op.get('statut'),
'amount': op.get('montantNet'),
})
if op.get('statut') == 'Sans suite':
continue
tr.parse(date=parse_french_date(op.get('dateSaisie')),
vdate = parse_french_date(op.get('dateEffet')) if op.get('dateEffet') else None,
raw='%s %s' % (op.get('libelleMouvement'), op.get('canalSaisie') or ''))
tr._op = op
if (op.get('statut') == u'Traité') ^ coming:
yield tr
class LifeInsurancesDetailPage(LifeInsurancesPage):
investments_path = 'data.detailMouvement.listeSupport.*'
class NatioVieProPage(BNPPage):
# This form is required to go to the capitalisation contracts page.
def get_params(self):
params = {
'app': 'BNPNET',
'hageGroup': 'consultationBnpnet',
'init': 'true',
'multiInit': 'false',
}
params['a0'] = self.doc['data']['nationVieProInfos']['a0']
# The number of "p" keys may vary (p0, p1, p2 ... up to p13 or more)
for key, value in self.doc['data']['nationVieProInfos']['listeP'].items():
params[key] = value
# We must decode the values before constructing the URL:
for k, v in params.items():
params[k] = unquote_plus(v)
return params
class CapitalisationPage(LoggedPage, HTMLPage):
def has_contracts(self):
# This message will appear if the page "Assurance Vie" contains no contract.
return not CleanText('//td[@class="message"]/text()[starts-with(., "Pour toute information")]')(self.doc)
# To be completed with other account labels and types seen on the "Assurance Vie" space:
ACCOUNT_TYPES = {
'BNP Paribas Multiplacements': Account.TYPE_LIFE_INSURANCE,
'BNP Paribas Multiciel Privilège': Account.TYPE_CAPITALISATION,
'Plan Epargne Retraite Particulier': Account.TYPE_PERP,
"Plan d'Épargne Retraite des Particuliers": Account.TYPE_PERP,
}
@method
class iter_capitalisation(TableElement):
# Other types of tables may appear on the page (such as Alternative Emprunteur/Capital Assuré)
# But they do not contain bank accounts so we must avoid them.
item_xpath = '//table/tr[preceding-sibling::tr[th[text()="Libellé du contrat"]]][td[@class="ligneTableau"]]'
head_xpath = '//table/tr/th[@class="headerTableau"]'
col_label = 'Libellé du contrat'
col_id = 'Numéro de contrat'
col_balance = 'Montant'
col_currency = "Monnaie d'affichage"
class item(ItemElement):
klass = Account
obj_label = CleanText(TableCell('label'))
obj_id = CleanText(TableCell('id'))
obj_number = CleanText(TableCell('id'))
obj_balance = CleanDecimal(TableCell('balance'), replace_dots=True)
obj_coming = None
obj_iban = None
def obj_type(self):
for k, v in self.page.ACCOUNT_TYPES.items():
if Field('label')(self).startswith(k):
return v
return Account.TYPE_UNKNOWN
def obj_currency(self):
currency = CleanText(TableCell('currency')(self))(self)
return Account.get_currency(currency)
# Required to get the investments of each "Assurances Vie" account:
def obj__details(self):
raw_details = CleanText((TableCell('balance')(self)[0]).xpath('./a/@href'))(self)
m = re.search(r"Window\('(.*?)',window", raw_details)
if m:
return m.group(1)
def get_params(self, account):
form = self.get_form(xpath='//form[@name="formListeContrats"]')
form['postValue'] = account._details
return form
# The investments vdate is out of the investments table and is the same for all investments:
def get_vdate(self):
return parse_french_date(CleanText('//table[tr[th[text()[contains(., "Date de valorisation")]]]]/tr[2]/td[2]')(self.doc))
@method
class iter_investments(TableElement):
# Investment lines contain at least 5 tags
item_xpath = '//table[tr[th[text()[contains(., "Libellé")]]]]/tr[count(td)>=5]'
head_xpath = '//table[tr[th[text()[contains(., "Libellé")]]]]/tr/th[@class="headerTableau"]'
col_label = 'Libellé'
col_code = 'Code ISIN'
col_quantity = 'Nombre de parts'
col_valuation = 'Montant'
col_portfolio_share = 'Montant en %'
class item(ItemElement):
klass = Investment
obj_label = CleanText(TableCell('label'))
obj_valuation = CleanDecimal(TableCell('valuation'), replace_dots=True)
obj_portfolio_share = Eval(lambda x: x / 100, CleanDecimal(TableCell('portfolio_share'), replace_dots=True))
# There is no "unitvalue" information available on the "Assurances Vie" space.
def obj_quantity(self):
quantity = TableCell('quantity')(self)
if CleanText(quantity)(self) == '-':
return NotAvailable
return CleanDecimal(quantity, replace_dots=True)(self)
def obj_code(self):
isin = CleanText(TableCell('code')(self))(self)
return isin or NotAvailable
def obj_code_type(self):
if is_isin_valid(Field('code')(self)):
return Investment.CODE_TYPE_ISIN
return NotAvailable
def obj_vdate(self):
return self.page.get_vdate()
class MarketListPage(BNPPage):
def get_list(self):
return self.get('securityAccountsList') or []
class MarketSynPage(BNPPage):
def get_list(self):
return self.get('synSecurityAccounts') or []
class MarketPage(BNPPage):
investments_path = 'listofPortfolios.*'
def iter_investments(self):
for support in self.path(self.investments_path):
inv = Investment()
inv.code = inv.id = support['securityCode']
inv.quantity = support['quantityOwned']
inv.unitvalue = support['currentQuote']
inv.unitprice = support['averagePrice']
inv.label = support['securityName']
inv.valuation = support['valorizationValuation']
inv.diff = support['profitLossValorisation']
inv.set_empty_fields(NotAvailable)
yield inv
class MarketHistoryPage(BNPPage):
def iter_history(self):
for op in self.get('contentList') or []:
tr = Transaction.from_dict({
'type': Transaction.TYPE_BANK,
'amount': op.get('movementAmount'),
'date': datetime.fromtimestamp(op.get('movementDate') / 1000),
'label': op.get('operationName'),
})
tr.investments = []
inv = Investment()
inv.code = op.get('securityCode')
inv.quantity = op.get('movementQuantity')
inv.label = op.get('securityName')
inv.set_empty_fields(NotAvailable)
tr.investments.append(inv)
yield tr
class AdvisorPage(BNPPage):
def has_error(self):
return (self.doc.get('message') == 'Erreur technique')
@method
class get_advisor(ListElement):
class item(ItemElement):
klass = Advisor
obj_name = Format('%s %s %s', Dict('data/civilite'), Dict('data/prenom'), Dict('data/nom'))
obj_email = Regexp(Dict('data/mail'), '(?=\w)(.*)', default=NotAvailable)
obj_phone = CleanText(Dict('data/telephone'), replace=[(' ', '')])
obj_mobile = CleanText(Dict('data/mobile'), replace=[(' ', '')])
obj_fax = CleanText(Dict('data/fax'), replace=[(' ', '')])
obj_agency = Dict('data/agence')
obj_address = Format('%s %s %s', Dict('data/adresseAgence'), Dict('data/codePostalAgence'), Dict('data/villeAgence'))
class AddRecipPage(BNPPage):
def on_load(self):
code = cast(self.get('codeRetour'), int)
if code:
raise AddRecipientBankError(message=self.get('message'))
def get_recipient(self, recipient):
r = Recipient()
r.iban = recipient.iban
r.id = self.get('data.gestionBeneficiaire.identifiantBeneficiaire')
r.label = recipient.label
r.category = u'Externe'
r.enabled_at = datetime.now().replace(microsecond=0) + timedelta(days=5)
r.currency = u'EUR'
r.bank_name = NotAvailable
return r
class ActivateRecipPage(AddRecipPage):
def get_recipient(self, recipient):
r = Recipient()
r.iban = recipient.iban
r.id = recipient.id
r.label = recipient.label
r.category = u'Externe'
r.enabled_at = datetime.now().replace(microsecond=0) + timedelta(days=5)
r.currency = u'EUR'
r.bank_name = self.get('data.activationBeneficiaire.nomBanque')
return r
woob-9cdd4beb610c3030a6c5a05ffb0cb26c0150e443-modules-bnporc/modules/bnporc/test.py 0000664 0000000 0000000 00000002421 13443176003 0026766 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 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 weboob.tools.test import BackendTest
from random import choice
class BNPorcTest(BackendTest):
MODULE = 'bnporc'
def test_bank(self):
l = list(self.backend.iter_accounts())
if len(l) > 0:
a = l[0]
list(self.backend.iter_coming(a))
list(self.backend.iter_history(a))
def test_msgs(self):
threads = list(self.backend.iter_threads())
thread = self.backend.fillobj(choice(threads), ['root'])
assert len(thread.root.content)
|