# -*- coding: utf-8 -*-
# Copyright(C) 2013-2014 Florent Fourcot
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob 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.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
from __future__ import unicode_literals
import re
from decimal import Decimal
from weboob.capabilities.base import NotAvailable
from weboob.capabilities.bank import Investment
from weboob.browser.pages import RawPage, HTMLPage, LoggedPage, pagination
from weboob.browser.elements import ListElement, TableElement, ItemElement, method
from weboob.browser.filters.standard import CleanDecimal, CleanText, Date, Regexp, Env, Async, AsyncLoad
from weboob.browser.filters.html import Link, Attr, TableCell
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.compat import unicode
class NetissimaPage(HTMLPage):
pass
class Transaction(FrenchTransaction):
pass
class TitreValuePage(LoggedPage, HTMLPage):
def get_isin(self):
return unicode(self.doc.xpath('//div[@id="headFiche"]//span[@id="test3"]/text()')[0].split(' - ')[0].strip())
class TitrePage(LoggedPage, RawPage):
def build_doc(self, content):
return content.decode(self.encoding)
def get_balance(self):
return CleanDecimal(default=None, replace_dots=True).filter(self.doc.split('{')[0])
def iter_investments(self, account):
# We did not get some html, but something like that (XX is a quantity, YY a price):
# "message=' €{ €{0,01 €{ €{0,00{{05/17{{03/05/2017{11:06{-XX €{710TI81000029397EUR{XX €{XX €{|OPHTHOTECH(NASDAQ)#cotationValeur.php?val=OPHT&pl=11&nc=2&
# popup=2{6{E:ALO{PAR{{reel{695{380{ALSTOM REGROUPT#XX#YY,YY €#YY,YY €#1 YYY,YY €#-YYY,YY €#-42,42%#-0,98 %#42,42 %#|1|AXA#cotationValeur.php?val=E:CS&pl=6&nc=1&
# popup=2{6{E:CS{PAR{{reel{695{380{AXA#XX#YY,YY €#YY,YYY €#YYY,YY €#YY,YY €#3,70%#42,42 %#42,42 %#|1|blablablab #cotationValeur.php?val=P:CODE&pl=6&nc=1&
# [...]
data = self.browser.cache["investments_data"].get(account.id, self.doc)
lines = data.split("|1|")
message = lines[0]
if len(lines) > 1:
start = 1
lines[0] = lines[0].split("|")[1]
else:
start = 0
lines = data.split("popup=2")
lines.pop(0)
invests = []
for line in lines:
_id, _pl = None, None
columns = line.split('#')
if columns[1] != '':
_pl = columns[start].split('{')[1]
_id = columns[start].split('{')[2]
invest = Investment()
invest.label = columns[start].split('{')[-1]
invest.code = _id or NotAvailable
if invest.code and ':' in invest.code:
invest.code = self.browser.titrevalue.open(val=invest.code,pl=_pl).get_isin()
# The code we got is not a real ISIN code.
if invest.code and not re.match('^[A-Z]{2}[\d]{10}$|^[A-Z]{2}[\d]{5}[A-Z]{1}[\d]{4}$', invest.code):
m = re.search('\{([A-Z]{2}[\d]{10})\{|\{([A-Z]{2}[\d]{5}[A-Z]{1}[\d]{4})\{', line)
if m:
invest.code = m.group(1) or m.group(2)
for x, attr in enumerate(['quantity', 'unitprice', 'unitvalue', 'valuation', 'diff'], 1):
currency = FrenchTransaction.Currency().filter(columns[start + x])
amount = CleanDecimal(default=NotAvailable).filter(FrenchTransaction.clean_amount(columns[start + x]))
if currency and currency != account.currency:
invest.original_currency = currency
attr = "original_" + attr
setattr(invest, attr, amount)
# valuation is not nullable, use 0 as default value
if not invest.valuation:
invest.valuation = Decimal('0')
# On some case we have a multine investment with a total column
# for now we have only see this on 2 lines, we will need to adapt it when o
if columns[9 if start == 0 else 0] == u'|Total' and _id == 'fichevaleur':
prev_inv = invest
invest = invests.pop(-1)
if prev_inv.quantity:
invest.quantity = invest.quantity + prev_inv.quantity
if prev_inv.valuation:
invest.valuation = invest.valuation + prev_inv.valuation
if prev_inv.diff:
invest.diff = invest.diff + prev_inv.diff
invests.append(invest)
# There is no investment on life insurance in the process to be created.
if len(message.split('&')) >= 4:
# We also have to get the liquidity as an investment.
invest = Investment()
invest.label = "Liquidités"
invest.code = "XX-liquidity"
invest.valuation = CleanDecimal(None, True).filter(message.split('&')[3].replace('euro;{','').strip())
invests.append(invest)
for invest in invests:
yield invest
class TitreHistory(LoggedPage, HTMLPage):
@method
class iter_history(ListElement):
item_xpath = '//table[@class="datas retour"]/tr'
class item(ItemElement):
klass = Transaction
condition = lambda self: len(self.el.xpath('td[@class="impaire"]')) > 0
obj_raw = Transaction.Raw('td[4] | td[3]')
obj_date = Date(CleanText('td[2]'), dayfirst=True)
obj_amount = CleanDecimal('td[7]', replace_dots=True)
class ASVHistory(LoggedPage, HTMLPage):
@method
class get_investments(TableElement):
item_xpath = '//table[@class="Tableau"]/tr[td[not(has-class("enteteTableau"))]]'
head_xpath = '//table[@class="Tableau"]/tr[td[has-class("enteteTableau")]]/td'
col_label = u'Support(s)'
col_vdate = u'Date de valeur'
col_unitvalue = u'Valeur de part'
col_quantity = [u'(*) Nb de parts', u'Nb de parts']
col_valuation = [u'Montant', u'Montant versé']
class item(ItemElement):
klass = Investment
load_details = Regexp(Attr('./td/a', 'onclick', default=""), 'PageExterne\(\'([^\']+)', default=None) & AsyncLoad
obj_label = CleanText(TableCell('label'))
obj_code = Async('details') & CleanText('//td[contains(text(), "CodeISIN")]/b', default=NotAvailable)
obj_quantity = CleanDecimal(TableCell('quantity'), replace_dots=True, default=NotAvailable)
obj_unitvalue = CleanDecimal(TableCell('unitvalue'), replace_dots=True, default=NotAvailable)
obj_valuation = CleanDecimal(TableCell('valuation'), replace_dots=True)
obj_vdate = Date(CleanText(TableCell('vdate')), dayfirst=True)
@pagination
@method
class iter_history(TableElement):
item_xpath = '//table[@class="Tableau"]/tr[td[not(has-class("enteteTableau"))]]'
head_xpath = '//table[@class="Tableau"]/tr[td[has-class("enteteTableau")]]/td'
col_date = u'Date d\'effet'
col_raw = u'Nature du mouvement'
col_amount = u'Montant brut'
next_page = Link('//a[contains(@href, "PageSuivante")]', default=None)
class item(ItemElement):
klass = Transaction
obj_date = Date(CleanText(TableCell('date')), dayfirst=True)
obj_raw = Transaction.Raw(TableCell('raw'))
obj_amount = CleanDecimal(TableCell('amount'), replace_dots=True)
obj__detail = Env('detail')
def obj_id(self):
try:
return Regexp(Link('./td/a', default=None), 'numMvt=(\d+)', default=None)(self)
except TypeError:
return NotAvailable
def parse(self, el):
link = Link('./td/a', default=None)(self)
page = self.page.browser.async_open(link) if link else None
self.env['detail'] = page