\d+)', CleanText(li)(self))[-1]
- yield link, parent_id
-
- def iter_idelcos(self):
- # Use a set because it is possible to see several times the same link.
- idelcos = set()
- for link, parent_id in self._iter_idelcos_ids():
- if link.startswith('javascript:'):
- m = re.search(r"javascript:fwkPUAvancerForm\('Cartes','(\w+)'\)", link)
- if m:
- idelcos.add((m.group(1), parent_id))
- else:
- m = re.search('IDELCO=(\d+)&', link)
- if m:
- idelcos.add((m.group(1), parent_id))
- return idelcos
-
- def get_idelco(self, account_idelco):
- for link, parent_id in self._iter_idelcos_ids():
- if link.startswith('javascript:'):
- # no need to fetch a "full" link
- return self.get_form(name=account_idelco)
- elif 'IDELCO=%s&' % account_idelco in link:
- return link
-
- def submit_card(self, name):
- form = name
- form['fwkaction'] = 'Cartes'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def check_perimeters(self):
- return len(self.doc.xpath('//a[@title="Espace Autres Comptes"]'))
-
-
-class PerimeterPage(MyLoggedPage, BasePage):
- def check_multiple_perimeters(self):
- self.browser.perimeters = list()
- self.get_current()
- if self.browser.current_perimeter is None:
- return
- self.browser.perimeters.append(self.browser.current_perimeter)
- multiple = self.doc.xpath(u'//p[span/a[contains(text(), "Accès")]]')
- if not multiple:
- if not len(self.doc.xpath(u'//div[contains(text(), "Périmètre en cours de chargement. Merci de patienter quelques secondes.")]')):
- self.logger.debug('Possible error on this page.')
- # We change perimeter in this case to add the second one.
- self.browser.location(self.browser.chg_perimeter_url.format(self.browser.sag))
- if self.browser.page.get_error() is not None:
- self.browser.broken_perimeters.append('the other perimeter is broken')
- self.browser.do_login()
- else:
- for table in self.doc.xpath('//table[@class]'):
- space = CleanText().filter(table.find('caption').text.lower())
- for perim in table.xpath('.//label'):
- perim = CleanText().filter(perim.text.lower())
- self.browser.perimeters.append(u'%s : %s' % (space, perim))
-
- def get_perimeter_link(self, perimeter):
- caption = perimeter.split(' : ')[0].title()
- perim = perimeter.split(' : ')[1]
- for table in self.doc.xpath('//table[@class and caption[contains(text(), $caption)]]', caption=caption):
- for p in table.xpath(u'.//p[span/a[contains(text(), "Accès")]]'):
- if perim in CleanText().filter(p.find('label').text.lower()):
- link = p.xpath('./span/a')[0].attrib['href']
- return link
-
-
-class ChgPerimeterPage(PerimeterPage):
- def on_load(self):
- if self.get_error() is not None:
- self.logger.debug('Error on ChgPerimeterPage')
- return
- self.get_current()
-
- # sometimes the perimeter use " & " and sometimes " et "
- # and can also be "&" instead of "et" (example: "metms" et "m&ms")
- if not (self.browser.current_perimeter in self.browser.perimeters or
- self.browser.current_perimeter.replace('et', '&') in self.browser.perimeters):
- assert len(self.browser.perimeters) == 1
- self.browser.perimeters.append(self.browser.current_perimeter)
-
-
-class CardElement(ItemElement):
- obj_coming = MyDecimal('.//tr[@class="ligne-paire"]/td[@class="cel-num"]', replace_dots=True, default=Decimal('0'))
-
- obj__form = None
-
- obj_balance = Decimal('0')
-
- def obj_currency(self):
- cur = self.page.doc.xpath('//table/caption//span/text()[starts-with(.,"Montants en ")]')[0].replace("Montants en ", "")
- return Account.get_currency(cur)
-
- def obj__idelco(self):
- m = re.search('IDELCO=(\d+)&', self.page.url)
- if m:
- idelco = m.group(1)
- else:
- idelco = None
- return idelco
-
- def obj__perimeter(self):
- return self.page.browser.current_perimeter
-
-
-class CardsPage(MyLoggedPage, BasePage):
- # cragr sends us this shit:
- # Msft *
- def build_doc(self, content):
- content = re.sub(br'\* tag in columns.
- if col_text.find('font') is not None:
- col_text = col_text.find('font')
-
- t.category = unicode(col_text.text.strip())
- t.label = re.sub('(.*) (.*)', r'\2', t.category).strip()
-
- br = col_text.find('br')
- if br is not None:
- sub_label = br.tail
- if br is not None and sub_label is not None:
- junk_ratio = len(re.findall('[^\w\s]', sub_label)) / float(len(sub_label))
- nums_ratio = len(re.findall('\d', t.label)) / float(len(t.label))
- if len(t.label) < 3 or t.label == t.category or junk_ratio < nums_ratio:
- t.label = unicode(sub_label.strip())
- # Sometimes, the category contains the label, even if there is another line with it again.
- t.category = re.sub('(.*) .*', r'\1', t.category).strip()
-
- t.type = self.TYPES.get(t.category, t.TYPE_UNKNOWN)
-
- # Sometimes, the deffered card summary transaction can be only identified from the label
- if 'Depenses Carte' in t.label:
- t.type = t.TYPE_CARD_SUMMARY
-
- # Parse operation date in label (for card transactions for example)
- m = re.match('(?P.*) (?P[0-3]\d)/(?P[0-1]\d)$', t.label)
- if not m:
- m = re.match('^(?P[0-3]\d)/(?P[0-1]\d) (?P.*)$', t.label)
- if m:
- if t.type in (t.TYPE_CARD, t.TYPE_WITHDRAWAL):
- t.rdate = date_guesser.guess_date(int(m.groupdict()['dd']), int(m.groupdict()['mm']), change_current_date=False)
- t.label = m.groupdict()['text'].strip()
-
- # Strip city or other useless information from label.
- t.label = re.sub('(.*) .*', r'\1', t.label).strip()
- t.set_amount(credit, debit)
- yield t
-
-
-class HistoryPostPage(CollectePageMixin, TransactionsPage):
- IS_HERE_TEXT = ('Consultation des comptes', 'Relevé')
-
-
-class NoFixedDepositPage(MyLoggedPage, BasePage):
- def is_here(self):
- return 'Vous ne disposez pas de DAT' in CleanText('//div[@class="blc-choix-wrap-valid"]/table/tr/td')(self.doc)
-
-
-class UnavailablePage(CollectePageMixin, BasePage):
- def is_here(self):
- return bool(self.get_error())
-
-
-class AutoEncodingMixin(object):
- def build_doc(self, data):
- try:
- data.decode('utf-8')
- self.forced_encoding = 'utf-8'
- except UnicodeDecodeError:
- self.forced_encoding = 'iso8859-15'
- return super(AutoEncodingMixin, self).build_doc(data)
-
-
-class MarketPage(MyLoggedPage, AutoEncodingMixin, BasePage):
- COL_ID = 1
- COL_QUANTITY = 2
- COL_UNITVALUE = 3
- COL_VALUATION = 4
- COL_UNITPRICE = 5
- COL_DIFF = 6
-
- def iter_investment(self):
- for line in self.doc.xpath('//table[contains(@class, "ca-data-table")]/descendant::tr[count(td)>=7]'):
- for sub in line.xpath('./td[@class="info-produit"]'):
- sub.drop_tree()
- cells = line.findall('td')
-
- if cells[self.COL_ID].find('div/a') is None:
- continue
- inv = Investment()
- inv.label = unicode(cells[self.COL_ID].find('div/a').text.strip())
- inv.code = cells[self.COL_ID].find('div/br').tail.strip().split(' ')[0].split(u'\xa0')[0].split(u'\xc2\xa0')[0]
- inv.quantity = self.parse_decimal(cells[self.COL_QUANTITY].find('span').text)
- inv.valuation = self.parse_decimal(cells[self.COL_VALUATION].text)
- inv.diff = self.parse_decimal(cells[self.COL_DIFF].text_content())
- if "%" in cells[self.COL_UNITPRICE].text and "%" in cells[self.COL_UNITVALUE].text:
- inv.unitvalue = inv.valuation / inv.quantity
- inv.unitprice = (inv.valuation - inv.diff) / inv.quantity
- else:
- inv.unitprice = self.parse_decimal(re.search('([^(]+)', cells[self.COL_UNITPRICE].text).group(1))
- inv.unitvalue = self.parse_decimal(cells[self.COL_UNITVALUE].text)
- date = cells[self.COL_UNITVALUE].find('span').text
- if ':' in date:
- inv.vdate = ddate.today()
- else:
- day, month = [int(x) for x in date.split('/')][:2]
- date_guesser = LinearDateGuesser()
- inv.vdate = date_guesser.guess_date(day, month)
-
- yield inv
-
- def parse_decimal(self, value):
- v = value.strip()
- if v == '-' or v == '' or v == '_':
- return NotAvailable
- if '.' in value and not ',' in value:
- return CleanDecimal().filter(value)
- return MyDecimal().filter(value)
-
- # This method is used to fetch the PEA balance without liquidities present on the DAV PEA:
- def get_pea_balance(self):
- return CleanDecimal('//tr[td[text()="Valorisation titres"]]/td/span', replace_dots=True)(self.doc)
-
-
-class MarketHomePage(MarketPage):
- COL_ID_LABEL = 1
- COL_VALUATION = 5
-
- def on_load(self):
- action_needed_msg = CleanText('//div[contains(text(), "Afin de finaliser le paramétrage de votre environnement Bourse")]', replace=[('Enregistrer', '')])(self.doc)
- if action_needed_msg:
- raise ActionNeeded(action_needed_msg)
-
- @method
- class get_list(TableElement):
- item_xpath = '//table[has-class("tableau_comptes_details")]//tr[td[2]]'
- head_xpath = '//table[has-class("tableau_comptes_details")]//tr/th'
-
- col_label = u'Comptes'
- col_balance = re.compile(u'Valorisation')
-
- class item(ItemElement):
- klass = Account
-
- condition = lambda self: Field('id')(self)
-
- def obj_id(self):
- return CleanText(default=None).filter(TableCell('label')(self)[0].xpath('./div[2]'))
-
- def obj_label(self):
- return CleanText(default=None).filter(TableCell('label')(self)[0].xpath('./div/b'))
-
- obj_balance = MyDecimal(TableCell('balance'))
-
-
-class LifeInsurancePage(MarketPage):
- COL_ID = 0
- COL_QUANTITY = 3
- COL_UNITVALUE = 1
- COL_VDATE = 2
- COL_VALUATION = 4
- COL_PSHARE = 5
-
- def go_on_detail(self, account_id):
- # Sometimes this page is a synthesis, so we need to go on detail.
- if len(self.doc.xpath(u'//h1[contains(text(), "Synthèse de vos contrats d\'assurance vie, de capitalisation et de prévoyance")]')) == 1:
- form = self.get_form(name='frm_fwk')
- form['ID_CNT_CAR'] = account_id
- form['fwkaction'] = 'Enchainer'
- form['fwkcodeaction'] = 'Executer'
- form['puCible'] = 'SEPPU'
- form.submit()
- self.browser.location('https://assurance-personnes.credit-agricole.fr/filiale/entreeBam?sessionSAG=%s&stbpg=pagePU&act=SEPPU&stbzn=bnt&actCrt=SEPPU' % self.browser.sag)
-
- def has_liquidities(self):
- return bool(self.doc.xpath('//span[contains(text(), "de versement restant")]'))
-
- def get_liquidities(self):
- return CleanDecimal(Regexp(CleanText('//span[contains(text(), "de versement restant")]'),
- "dont (.*) de versement restant"), replace_dots=True)(self.doc)
-
- def iter_investment(self):
-
- for line in self.doc.xpath('//table[@summary and count(descendant::td) > 1]/tbody/tr'):
- cells = line.findall('td')
- if len(cells) < 5:
- continue
-
- inv = Investment()
- inv.label = unicode(cells[self.COL_ID].text_content().strip())
- a = cells[self.COL_ID].find('a')
- if a is not None:
- try:
- inv.code = a.attrib['id'].split(' ')[0].split(u'\xa0')[0].split(u'\xc2\xa0')[0]
- except KeyError:
- #For "Mandat d'arbitrage" which is a recapitulatif of more investement
- continue
- else:
- inv.code = NotAvailable
- inv.quantity = self.parse_decimal(cells[self.COL_QUANTITY].text_content()) or NotAvailable
- inv.unitvalue = self.parse_decimal(cells[self.COL_UNITVALUE].text_content()) or NotAvailable
- inv.valuation = self.parse_decimal(cells[self.COL_VALUATION].text_content())
- inv.unitprice = NotAvailable
- inv.diff = NotAvailable
- # clean text before filtering the date
- inv.vdate = Date(CleanText('.'), dayfirst=True, default=NotAvailable)(cells[self.COL_VDATE])
- inv.portfolio_share = self.parse_decimal(cells[self.COL_PSHARE].text_content()) / 100
-
- yield inv
-
-
-class AdvisorPage(MyLoggedPage, BasePage):
- def get_codeperimetre(self):
- script = CleanText('//script[contains(text(), "codePerimetre")]', default=None)(self.doc)
- if script:
- return Regexp(pattern=r'codePerimetre.+?(\d+).+?codeAgence.+?(\d+)', template='\\1-\\2').filter(script)
-
- @method
- class get_advisor(ItemElement):
- klass = Advisor
-
- obj_name = CleanText('//span[@class="c1"]')
- obj_email = CleanText('//span[@id="emailCons"]')
- obj_phone = Regexp(CleanText('//span[@class="c2"]', symbols=' ', default=""), '(\d+)', default=NotAvailable)
- obj_agency = CleanText('//span[@class="a1"]')
-
- def iter_numbers(self):
- for p in self.doc.xpath(u'//fieldset/div/p[not(contains(text(), "TTC"))]'):
- phone = None
- if p.find('b') is not None:
- phone = ''.join(p.find('b').text_content().split('.'))
- else:
- m = re.search('(?=\d)([\d\s.]+)', p.text_content().strip())
- if m:
- phone = ''.join(m.group(1).split())
- if not phone or len(phone) != 10:
- continue
-
- adv = Advisor()
- adv.name = unicode(re.search('([^:]+)', p.find('span').text).group(1).strip())
- if adv.name.startswith('Pour toute '):
- adv.name = u"Fil général"
- adv.phone = unicode(phone)
- if "bourse" in adv.name:
- adv.role = u"wealth"
- [setattr(adv, k, NotAvailable) for k in ['email', 'mobile', 'fax', 'agency', 'address']]
- yield adv
-
-
-class ProfilePage(MyLoggedPage, BasePage):
- def get_error(self):
- return CleanText('//div[@id="trPagePu"]//h1[text()="Fonctionnalité Indisponible"]/following::p')(self.doc)
-
- @method
- class get_profile(ItemElement):
- klass = Profile
-
- obj_email = Regexp(CleanText('//font/b/script[contains(text(), "Mail")]', default=""), '\'([^\']+)', default=NotAvailable)
-
- def obj_address(self):
- address = ""
- for tr in self.page.doc.xpath('//tr[td[contains(text(), "Adresse")]]/following-sibling::tr[position() < 4]'):
- address += " " + CleanText('./td[last()]')(tr).strip()
- return ' '.join(address.split()) or NotAvailable
-
- def obj_name(self):
- name = CleanText('//span[contains(text(), "Espace Titulaire")]', default=None)(self)
- if name and not "particuliers" in name:
- return ''.join(name.split(':')[1:]).strip()
- pattern = u'//td[contains(text(), "%s")]/following-sibling::td[1]'
- return Format('%s %s', CleanText(pattern % "Nom"), CleanText(pattern % "Prénom"))(self)
-
-
-class BGPIPage(MarketPage):
- COL_ID = 0
- COL_QUANTITY = 1
- COL_UNITPRICE = 2
- COL_UNITVALUE = 3
- COL_VALUATION = 4
- COL_PORTFOLIO = 5
- COL_DIFF = 6
-
- def iter_investment(self):
- for line in self.doc.xpath('//table[contains(@class, "PuTableauLarge")]/tr[contains(@class, "PuLigne")]'):
- cells = line.findall('td')
- inv = Investment()
-
- inv.label = unicode(cells[self.COL_ID].find('span').text_content().strip())
- a = cells[self.COL_ID].find('a')
- if a is not None:
- inv.code = unicode(a.text_content().strip())
- else:
- inv.code = NotAvailable
-
- inv.quantity = self.parse_decimal(cells[self.COL_QUANTITY].text_content())
- if 5 <= len(cells) <= 7:
- inv.unitvalue = self.parse_decimal(cells[2].text_content())
- inv.valuation = self.parse_decimal(cells[3].text_content())
- inv.portfolio_share = self.parse_decimal(cells[4].text_content())/100
- else:
- inv.unitvalue = self.parse_decimal(cells[self.COL_UNITVALUE].text_content())
- inv.valuation = self.parse_decimal(cells[self.COL_VALUATION].text_content())
- inv.unitprice = self.parse_decimal(cells[self.COL_UNITPRICE].text_content())
- inv.diff = self.parse_decimal(cells[self.COL_DIFF].text_content())
- inv.portfolio_share = self.parse_decimal(cells[self.COL_PORTFOLIO].text_content())/100
-
- yield inv
-
- def get_li_details(self, _id):
- label = CleanText('//tr[td[text()="%s"]]/td/a' % _id)(self.doc)
- balance = CleanDecimal('//tr[td[text()="%s"]]/td[@class="cel-num cel-important"]' % _id, replace_dots=True)(self.doc)
- return label, balance
-
- def go_on(self, link):
- origin = urlparse(self.url)
- self.browser.location('https://%s%s' % (origin.netloc, link))
- return True
-
- def go_detail(self):
- link = self.doc.xpath('.//a[contains(text(), "Détail")]')
- return self.go_on(link[0].attrib['href']) if link else False
-
- def get_params(self, _id):
- option = Attr('//select[@class="BntSelectOption"]/option[text()="%s"]' % _id, 'value', default=None)(self.doc)
- if option:
- params = {'id': option}
- return params
- return None
-
- def go_back(self):
- self.go_on(self.doc.xpath(u'.//a[contains(text(), "Retour à mes comptes")]')[0].attrib['href'])
- form = self.browser.page.get_form(name='formulaire')
- form.submit()
-
- def cgu_needed(self):
- return bool(CleanText(u'//h1[contains(text(), "Conditions Générales d\'utilisation des Services en Ligne")]')(self.doc))
-
-
-class PredicaRedirectPage(MyLoggedPage, BasePage):
- pass
-
-
-class TransferInit(MyLoggedPage, BasePage):
- def iter_emitters(self):
- items = self.doc.xpath('//select[@name="VIR_VIR1_FR3_LE"]/option')
- return self.parse_recipients(items, assume_internal=True)
-
- def iter_recipients(self):
- items = self.doc.xpath('//select[@name="VIR_VIR1_FR3_LB"]/option')
- return self.parse_recipients(items)
-
- def parse_recipients(self, items, assume_internal=False):
- for opt in items:
- lines = get_text_lines(opt)
-
- if opt.attrib['value'].startswith('I') or assume_internal:
- for n, line in enumerate(lines):
- if line.strip().startswith('n°'):
- rcpt = Recipient()
- rcpt._index = opt.attrib['value']
- rcpt._raw_label = ' '.join(lines)
- rcpt.category = 'Interne'
- rcpt.id = CleanText().filter(line[2:].strip())
- # we don't have iban here, use account number
- rcpt.label = ' '.join(lines[:n])
- rcpt.currency = Currency.get_currency(lines[-1])
- rcpt.enabled_at = datetime.now().replace(microsecond=0)
- yield rcpt
- break
- elif opt.attrib['value'].startswith('E'):
- if len(lines) > 1:
- # In some cases we observed beneficiaries without label, we skip them
- rcpt = Recipient()
- rcpt._index = opt.attrib['value']
- rcpt._raw_label = ' '.join(lines)
- rcpt.category = 'Externe'
- rcpt.label = lines[0]
- rcpt.iban = lines[1].upper()
- rcpt.id = rcpt.iban
- rcpt.enabled_at = datetime.now().replace(microsecond=0)
- yield rcpt
- else:
- self.logger.warning('The recipient associated with the iban %s has got no label' % lines[0])
-
- def submit_accounts(self, account_id, recipient_id, amount, currency):
- emitters = [rcpt for rcpt in self.iter_emitters() if rcpt.id == account_id and not rcpt.iban]
- if len(emitters) != 1:
- raise TransferError('Could not find emitter %r' % account_id)
- recipients = [rcpt for rcpt in self.iter_recipients() if rcpt.id and rcpt.id == recipient_id]
- # for recipient with same IBAN, first matched recipient is the default value
- if len(recipients) < 1:
- raise TransferError('Could not find recipient %r' % recipient_id)
-
- form = self.get_form(name='frm_fwk')
- assert amount > 0
- amount = str(amount.quantize(Decimal('0.00')))
- form['T3SEF_MTT_EURO'], form['T3SEF_MTT_CENT'] = amount.split('.')
- form['VIR_VIR1_FR3_LE'] = emitters[0]._index
- form['VIR_VIR1_FR3_LB'] = recipients[0]._index
- form['DEVISE'] = currency or emitters[0].currency
- form['VIR_VIR1_FR3_LE_HID'] = emitters[0]._raw_label
- form['VIR_VIR1_FR3_LB_HID'] = recipients[0]._raw_label
- form['fwkaction'] = 'Confirmer' # mandatory
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def url_list_recipients(self):
- return CleanText(u'(//a[contains(text(),"Liste des bénéficiaires")])[1]/@href')(self.doc)
-
- def add_recipient_is_allowed(self):
- return bool(self.doc.xpath('//a[text()="+ Saisir un autre compte bénéficiaire"]') or self.doc.xpath('//a[contains(text(),"Liste des bénéficiaires")]'))
-
- def url_add_recipient(self):
- link = Link('//a[text()="+ Saisir un autre compte bénéficiaire"]')(self.doc)
- return link + '&IDENT=LI_VIR_RIB1&VIR_VIR1_FR3_LE=0&T3SEF_MTT_EURO=&T3SEF_MTT_CENT=&VICrt_REFERENCE='
-
-
-class RecipientListPage(MyLoggedPage, BasePage):
- def url_add_recipient(self):
- return CleanText(u'//a[contains(text(),"Ajouter un compte destinataire")]/@href')(self.doc)
-
-
-class RecipientAddingMixin(object):
- def submit_recipient(self, label, iban):
- try:
- form = self.get_form(name='frm_fwk')
- except FormNotFound:
- assert False, 'An error occurred before sending recipient'
-
- form['NOM_BENEF'] = label
- for i in range(9):
- form['CIBAN%d' % (i + 1)] = iban[i * 4:(i + 1) * 4]
- form['fwkaction'] = 'VerifCodeIBAN'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
-
-class TransferPage(RecipientAddingMixin, CollectePageMixin, MyLoggedPage, BasePage):
- IS_HERE_TEXT = 'Virement'
-
- ### for transfers
- def get_step(self):
- return CleanText('//div[@id="etapes"]//li[has-class("encours")]')(self.doc)
-
- def is_sent(self):
- return self.get_step().startswith('Récapitulatif')
-
- def is_confirm(self):
- return self.get_step().startswith('Confirmation')
-
- def is_reason(self):
- return self.get_step().startswith('Informations complémentaires')
-
- def get_transfer(self):
- transfer = Transfer()
-
- # FIXME all will probably fail if an account has a user-chosen label with "IBAN :" or "n°"
-
- amount_xpath = '//fieldset//p[has-class("montant")]'
- transfer.amount = MyDecimal(amount_xpath)(self.doc)
- transfer.currency = CleanCurrency(amount_xpath)(self.doc)
-
- if self.is_sent():
- transfer.account_id = Regexp(CleanText('//p[@class="nomarge"][span[contains(text(),'
- '"Compte émetteur")]]/text()'),
- r'n°(\d+)')(self.doc)
-
- base = CleanText('//fieldset//table[.//span[contains(text(), "Compte bénéficiaire")]]'
- '//td[contains(text(),"n°") or contains(text(),"IBAN :")]//text()', newlines=False)(self.doc)
- transfer.recipient_id = Regexp(None, r'IBAN : ([^\n]+)|n°(\d+)').filter(base)
- transfer.recipient_id = transfer.recipient_id.replace(' ', '')
- if 'IBAN' in base:
- transfer.recipient_iban = transfer.recipient_id
-
- transfer.exec_date = MyDate(CleanText('//p[@class="nomarge"][span[contains(text(), "Date de l\'ordre")]]/text()'))(self.doc)
- else:
- transfer.account_id = Regexp(CleanText('//fieldset[.//h3[contains(text(), "Compte émetteur")]]//p'),
- r'n°(\d+)')(self.doc)
-
- base = CleanText('//fieldset[.//h3[contains(text(), "Compte bénéficiaire")]]//text()',
- newlines=False)(self.doc)
- transfer.recipient_id = Regexp(None, r'IBAN : ([^\n]+)|n°(\d+)').filter(base)
- transfer.recipient_id = transfer.recipient_id.replace(' ', '')
- if 'IBAN' in base:
- transfer.recipient_iban = transfer.recipient_id
-
- transfer.exec_date = MyDate(CleanText('//fieldset//p[span[contains(text(), "Virement unique le :")]]/text()'))(self.doc)
-
- transfer.label = CleanText('//fieldset//p[span[contains(text(), "Référence opération")]]')(self.doc)
- transfer.label = re.sub(r'^Référence opération(?:\s*):', '', transfer.label).strip()
-
- return transfer
-
- def submit_more(self, label, date=None):
- if date is None:
- date = ddate.today()
-
- form = self.get_form(name='frm_fwk')
- form['VICrt_CDDOOR'] = label
- form['VICrtU_DATEVRT_JJ'] = date.strftime('%d')
- form['VICrtU_DATEVRT_MM'] = date.strftime('%m')
- form['VICrtU_DATEVRT_AAAA'] = date.strftime('%Y')
- form['DATEC'] = date.strftime('%d/%m/%Y')
- form['PERIODE'] = 'U'
- form['fwkaction'] = 'Confirmer'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def submit_confirm(self):
- form = self.get_form(name='frm_fwk')
- form['fwkaction'] = 'Confirmer'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def on_load(self):
- super(TransferPage, self).on_load()
- # warning: the "service indisponible" message (not catched here) is not a real BrowserUnavailable
- err = CleanText('//form//div[has-class("blc-choix-erreur")]//p', default='')(self.doc)
- if err:
- raise TransferBankError(message=err)
-
- ### add a recipient by faking a transfer
- def confirm_recipient(self):
- # pretend to make a transfer
- form = self.get_form(name='frm_fwk')
- form['AJOUT_BENEF_CHECK'] = 'on'
- form['fwkcodeaction'] = 'Executer'
- form['fwkaction'] = 'Suite'
- form['T3SEF_MTT_EURO'] = '1'
- form['DEVISE'] = 'EUR'
- form.submit()
-
- def check_error(self):
- # this is for transfer error, it's not a `AddRecipientBankError` but a `TransferBankError`
-
- msg = CleanText('//tr[@bgcolor="#C74545"]', default='')(self.doc) # there is no id, class or anything...
- if msg:
- raise TransferBankError(message=msg)
-
- def check_recipient_error(self):
- # this is a copy-paste from RecipientMiscPage, i can't test if it works on this page...
- # this is for add recipient by initiate transfer
-
- msg = CleanText('//tr[@bgcolor="#C74545"]', default='')(self.doc) # there is no id, class or anything...
- if msg:
- raise AddRecipientBankError(message=msg)
-
-
-class RecipientMiscPage(RecipientAddingMixin, CollectePageMixin, MyLoggedPage, BasePage):
- IS_HERE_TEXT = 'Liste des comptes bénéficiaires'
-
- ### for adding recipients
- def send_sms(self):
- form = self.get_form(name='frm_fwk')
-
- assert 'code' not in form
- form['fwkaction'] = 'DemandeCodeSMSVerifID'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def get_sms_error(self):
- return CleanText('//div[@class="blc-choix-wrap-erreur"]')(self.doc)
-
- def confirm_recipient(self):
- try:
- form = self.get_form(name='frm_fwk')
- except FormNotFound:
- assert False, 'An error occurred before finishing adding recipient'
-
- form['fwkaction'] = 'ConfirmerAjout'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def check_recipient_error(self):
- msg = CleanText('//tr[@bgcolor="#C74545"]', default='')(self.doc) # there is no id, class or anything...
- if msg:
- raise AddRecipientBankError(message=msg)
-
- def get_iban_col(self):
- for index, td in enumerate(self.doc.xpath('//table[starts-with(@summary,"Nom et IBAN")]//th')):
- if 'Numéro de compte' in CleanText('.')(td):
- # index start at 0
- return index + 1
-
- def find_recipient(self, iban):
- iban = iban.upper()
- iban_col = self.get_iban_col()
-
- for tr in self.doc.xpath('//table[starts-with(@summary,"Nom et IBAN")]/tbody/tr'):
- iban_text = re.sub(r'\s', '', CleanText('./td[%s]' % iban_col)(tr))
- if iban_text.upper() == 'IBAN:%s' % iban:
- res = Recipient()
- res.iban = iban
- res.id = iban
- res.label = CleanText('./td[%s]' % (iban_col-1))(tr)
- return res
-
-
-class RecipientPage(MyLoggedPage, BasePage):
- def can_send_code(self):
- form = self.get_form(name='frm_fwk')
- return 'code' in form
-
- def send_sms(self):
- form = self.get_form(name='frm_fwk')
-
- if 'code' in form:
- # a code is still pending, ask a new one
- form['fwkaction'] = 'NouvelleDemandeCodeSMS'
- form['fwkcodeaction'] = 'Executer'
- new_page = form.submit().page
- assert isinstance(new_page, TransferPage) or isinstance(new_page, SendSMSPage)
- return new_page.send_sms()
- else:
- form['fwkaction'] = 'DemandeCodeSMSVerifID'
-
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
- def submit_code(self, code):
- form = self.get_form(name='frm_fwk')
- form['fwkaction'] = 'Confirmer'
- form['fwkcodeaction'] = 'Executer'
- form['code'] = code
- form.submit()
-
-
-class SendSMSPage(MyLoggedPage, CollectePageMixin, BasePage):
- IS_HERE_TEXT = 'Authentification par sms - demande'
-
- def on_load(self):
- # if the otp is incorrect
- error_msg = CleanText('//div[has-class("blc-choix-erreur")]//span')(self.doc)
- if error_msg:
- raise AddRecipientBankError(message=error_msg)
-
- def send_sms(self):
- # when a code is still pending
- # resend sms to validate recipient
- form = self.get_form(name='frm_fwk')
- form['fwkaction'] = 'DemandeCodeSMSVerifID'
- form['fwkcodeaction'] = 'Executer'
- form.submit()
-
-
-def get_text_lines(el):
- lines = [re.sub(r'\s+', ' ', line).strip() for line in el.text_content().split('\n')]
- return [l for l in lines if l]
-
-
-class DeferredCardsPage(CollectePageMixin, CardsPage):
- IS_HERE_TEXT = (u'Cartes - détail', 'Cartes')
|