class PortalScraper::Accounts::Client

Constants

CODE_TO_COUNTRY_MAPPING
IS_POSTAL_CODE_DOM
MINIMUM_AMOUNT
PROPOSITION_INDEX

Attributes

app_id[RW]
secret[RW]

Public Class Methods

new(app_id = nil, secret = nil) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 16
def initialize(app_id = nil, secret = nil)
  @app_id = app_id || config.app_id
  @secret = secret || config.secret
  raise BadRootUrl unless config.root_url =~ URI::DEFAULT_PARSER.make_regexp
end

Public Instance Methods

create_account(user) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 69
def create_account(user)
  login
  app.get(url_for('/appTiersCreation.do')) do |create_page|
    create_page.form_with(name: /(create|creation)/i) do |form|
      form.fields.each { |f| try_form_fields(form, [f.name], user[f.name.to_sym]) }
      if user[:codeInsee].present?
        form['commune']    = user[:codeInsee]
        form['libCommune'] = user[:ville]
      else
        fill_commune(app, form, user[:codePostal], parse_city_name(user[:ville]))
      end
      fill_address(form, user[:numeroVoie])
      fill_birth_place(form, user[:paysNaissance])
      form['modeContact'] = ''
      form['creerTiers']  = 'Créer'
      form.submit
      result_page = app.post(url_for('/appTiersCreation.do'), { confirmer: 'Confirmer' })
      if result_page.uri.path =~ /error.do/
        return { error: 'Missing data' }
      else
        ref = result_page.search('td.ZMSG').text.split(':')[1].strip
        return { client_ref: ref }
      end
    end
  end
  nil
rescue => e
  { error: e.message }
end
create_proposition(client_ref) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 99
def create_proposition(client_ref)
  login
  app.get(url_for('/appRecherche.do')) do |search_user_page|
    search_user_page.form_with(name: /(frmCritRech)/i) do |form|
      form['idTiers'] = client_ref
      result_page     = form.click_button
      result_page.links.find { |l| l.text.strip == client_ref }.click
      app.get(url_for('/propositionList.do'))
      return { proposition_ref: proposition }
    end
  end
  nil
end
scrap_accounts_balance() { |account_data| ... } click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 22
def scrap_accounts_balance
  login
  accounts_data = { accounts: [] }

  user_page = app.post(url_for('/appRecherche.do'), { nom: '', prenom: '', chercher: 'Chercher' })
  loop do
    user_page.links_with(href: /appTiersDetail.do/).each do |link|
      account            = link.click
      selected_link      = nil
      opened_on, balance = nil, 0.to_d

      account.links_with(css: 'table#contrat td a').each do |account_link|
        row = account_link.node.ancestors('tr')
        next unless row.at('td[1]').text.strip == 'Compte épargne rémunéré'

        row_date = parse_date(row.at('td[2]'))
        next unless opened_on.nil? || opened_on < row_date

        selected_link = account_link
        opened_on     = row_date
        balance       = parse_number(row.at('td[5]'))
      end

      next unless selected_link

      transfers = scrap_transfers(selected_link)
      account_data = {
          client_ref: find_table_value(account, 'Identifiant tiers'),
          balance:    balance,
          opened_on:  opened_on,
          transfers:  transfers,
      }
      if block_given?
        yield(account_data)
      else
        accounts_data[:accounts] << account_data
      end
    end

    break unless user_page.search('.pg_next').present?
    # Need to fetch the first page as you cannot navigate from client back to research page
    app.post(url_for('/appRecherche.do'), { nom: '', prenom: '', chercher: 'Chercher' })
    user_page = app.get(user_page.search('a.pg_next').first['href'])
  end
  accounts_data
end

Private Instance Methods

app() click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 115
def app
  @app ||= Mechanize.new
end
config() click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 189
def config
  PortalScraper.config
end
fill_address(form, address) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 171
def fill_address(form, address)
  address, additional_information = address.split("\n")
  form['numeroVoie']              = address
  if additional_information
    form['lieudit'] = additional_information
  end
end
fill_birth_place(form, country_code) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 179
def fill_birth_place(form, country_code)
  form['paysNaissance']       = country_code
  form['libPaysNaissance']    = CODE_TO_COUNTRY_MAPPING.dig(country_code, 'name')
  form['territoireNaissance'] = CODE_TO_COUNTRY_MAPPING.dig(country_code, 'category')
end
fill_commune(app, form, postal_code, city) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 160
def fill_commune(app, form, postal_code, city)
  app.post(url_for('/tiersCommunes.do'), { codPos: postal_code }).tap do |mappings|
    cities       = mappings.search('td.t').map { |c| { code: c.parent.search('a.PL_LST').text.strip, name: c.text.strip } }
    matched_city = cities.find(-> { cities.first }) { |c| c[:name].include?(city) }
    raise "City '#{city}' and postcode '#{postal_code}' not found" unless matched_city
    form['commune']    = matched_city[:code]
    form['libCommune'] = matched_city[:name]
  end
  form['territoireRes'] = IS_POSTAL_CODE_DOM.dig(postal_code[0..2], 'category') || 0
end
find_table_value(node, column_name) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 185
def find_table_value(node, column_name)
  node.search('td.CA_LABEL').find { |column| column.text.strip == column_name }.parent.search('td.CA_LIB').text.strip
end
login() click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 123
def login
  raise BadLoginUrl if config.login_url.nil?
  app.get(url_for(config.login_url)) do |login_page|
    form = login_page.form_with(name: /login/i) || login_page.forms[0]
    try_form_fields(form, %w(username login), app_id)
    try_form_fields(form, %w(password secret), secret)
    form.submit
  end
  app.get(url_for(config.token_url)) unless config.token_url.nil?
end
proposition() click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 134
def proposition
  proposition_page = app.get(url_for('/propositionDetail.do'), { idx: PROPOSITION_INDEX })
  proposition_form = proposition_page.form_with(name: /proposition/i)
  try_form_fields(proposition_form, %w(montant amount), MINIMUM_AMOUNT)
  proposition_form['regimeFiscal'] = 'I'
  proposition_form.click_button
  app.get(url_for('/propositionCreate.do'))
  confirmation_page = app.get(url_for('/propositionConfirm.do'))
  if confirmation_page.uri.path =~ /error.do/
    return { error: 'Missing data' }
  end
  text            = confirmation_page.search('td.ZMSG').text.strip
  proposition_ref = text.match('\d{9}')[0]
  app.get(url_for('/propositionImprimer.do'))
  proposition_ref
end
scrap_transfers(account_link) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 193
def scrap_transfers(account_link)
  transfers_page = account_link.click
  transfers_page.search('table#operation tbody tr').map do |transfer_row|
    {
        operation_date: parse_date(transfer_row.search('td[1]')),
        nature:         transfer_row.search('td[2]').text.strip.gsub(/[\n,\t,'']/, ''),
        value_date:     parse_date(transfer_row.search('td[3]')),
        debit:          parse_number(transfer_row.search('td[4]')),
        credit:         parse_number(transfer_row.search('td[5]')),
    }
  end
end
try_form_fields(form, fields, value) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 151
def try_form_fields(form, fields, value)
  fields.each do |field|
    if form.has_field?(field)
      form[field] = value
      break
    end
  end
end
url_for(path) click to toggle source
# File lib/portal_scraper/accounts/client.rb, line 119
def url_for(path)
  [config.root_url, path].join
end