Add BankAccountConnector to implement bank import methods in plugins
This commit is contained in:
parent
d476993321
commit
5d84156bd8
11 changed files with 281 additions and 22 deletions
|
@ -15,16 +15,36 @@ class Finance::BankAccountsController < Finance::BaseController
|
|||
|
||||
def import
|
||||
@bank_account = BankAccount.find(params[:id])
|
||||
import_method = @bank_account.find_import_method
|
||||
if import_method
|
||||
count = import_method.call(@bank_account)
|
||||
redirect_to finance_bank_account_transactions_url(@bank_account), notice: t('.notice', count: count)
|
||||
importer = @bank_account.find_connector
|
||||
|
||||
if importer
|
||||
importer.load params[:state] && YAML.load(params[:state])
|
||||
|
||||
ok = importer.import params[:controls]
|
||||
|
||||
importer.finish if ok
|
||||
flash.notice = t('.notice', count: importer.count) if ok
|
||||
@auto_submit = importer.auto_submit
|
||||
@controls = importer.controls
|
||||
#TODO: encrypt state
|
||||
@state = YAML.dump importer.dump
|
||||
else
|
||||
# @todo add import for csv files as fallback
|
||||
redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('.no_import_method')
|
||||
ok = true
|
||||
flash.alert = t('.no_import_method')
|
||||
end
|
||||
|
||||
needs_redirect = ok
|
||||
rescue => error
|
||||
redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: error.message)
|
||||
flash.alert = t('errors.general_msg', msg: error.message)
|
||||
needs_redirect = true
|
||||
ensure
|
||||
return unless needs_redirect
|
||||
redirect_path = finance_bank_account_transactions_url(@bank_account)
|
||||
if request.post?
|
||||
@js_redirect = redirect_path
|
||||
else
|
||||
redirect_to redirect_path
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,7 +10,9 @@ class BankAccount < ApplicationRecord
|
|||
validates_numericality_of :balance, :message => I18n.t('bank_account.model.invalid_balance')
|
||||
|
||||
# @return [Function] Method wich can be called to import transaction from a bank or nil if unsupported
|
||||
def find_import_method
|
||||
def find_connector
|
||||
klass = BankAccountConnector.find iban
|
||||
return klass.new self if klass
|
||||
end
|
||||
|
||||
def assign_unlinked_transactions
|
||||
|
|
33
app/views/finance/bank_accounts/_import.html.haml
Normal file
33
app/views/finance/bank_accounts/_import.html.haml
Normal file
|
@ -0,0 +1,33 @@
|
|||
= form_tag import_finance_bank_account_path(@bank_account), class: 'form-horizontal',
|
||||
data: { auto_submit: @auto_submit}, id: 'import_form', method: :post, remote: true do
|
||||
|
||||
= hidden_field_tag :import_uid, @import_uid
|
||||
= hidden_field_tag :state, @state
|
||||
|
||||
- for control in @controls
|
||||
- name = control.name
|
||||
.control-group
|
||||
- if name
|
||||
- if control.type == :hidden
|
||||
= hidden_field_tag "controls[#{control.name}]", control.value
|
||||
- else
|
||||
%label(for=name class='control-label')
|
||||
= control.label + ':'
|
||||
.controls
|
||||
- if control.type == :password
|
||||
= password_field_tag "controls[#{control.name}]", control.value
|
||||
-else
|
||||
= text_field_tag "controls[#{control.name}]", control.value
|
||||
- else
|
||||
= control.text
|
||||
|
||||
- if @auto_submit
|
||||
:javascript
|
||||
var form = $('#import_form');
|
||||
setTimeout(function() {
|
||||
form.submit();
|
||||
}, form.data('auto-submit'));
|
||||
- else
|
||||
.control-group
|
||||
.controls
|
||||
= submit_tag t('.submit'), class: 'btn btn-primary'
|
|
@ -1,12 +1,4 @@
|
|||
- title t('.title', name: @bank_account.name)
|
||||
|
||||
= form_for :bank_accounts, :url => parse_upload_finance_bank_account_path(@bank_account),
|
||||
:html => { multipart: true, class: "form-horizontal" } do |f|
|
||||
|
||||
.control-group
|
||||
%label(for="bank_transactions_file")= t '.file_label'
|
||||
= f.file_field "file"
|
||||
|
||||
.form-actions
|
||||
= submit_tag t('.submit'), class: 'btn btn-primary'
|
||||
= link_to t('ui.or_cancel'), finance_bank_account_transactions_path(@bank_account)
|
||||
#import
|
||||
= render "import"
|
||||
|
|
4
app/views/finance/bank_accounts/import.js.haml
Normal file
4
app/views/finance/bank_accounts/import.js.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
- if @js_redirect
|
||||
document.location.replace('#{escape_javascript(@js_redirect)}');
|
||||
- else
|
||||
$('#import').html('#{escape_javascript(render("import"))}');
|
|
@ -516,6 +516,13 @@ de:
|
|||
text_1: 'Du kannst hier eine Tabelle hochladen, um die Artikel des Lieferanten %{supplier} zu aktualisieren. Excel (xls, xlsx) und OpenOffice (ods) Tabellen werden akzeptiert, darüber hinaus Dateien im Format "csv" (comma-separated values, mit dem Spaltentrennzeichen ";" und utf-8 Kodierung). Nur das erste Tabellenblatt wird importiert und die Spalten müssen in der folgenden Anordnung vorliegen:'
|
||||
text_2: Die hier gezeigten Spalten sind Beispiele. Ist ein "x" in der ersten Spalte, wird der Artikel aussortiert und entfernt. Das erlaubt Dir die Tabelle zu ändern und schnell viele Artikel auf ein Mal zu entfernen, zum Beispiel wenn Artkiel des Lieferanten nicht mehr verfügbar sind. Die Kategorie wird der Foodsoft Kategorie zugeordnet (durch die Kategorienamen und die Importnamen).
|
||||
title: Artikel des Lieferanten %{supplier} hochladen
|
||||
bank_account_connector:
|
||||
confirm_app: Bitte bestätige den Code %{code} in deiner App.
|
||||
fields:
|
||||
email: E-Mail
|
||||
pin: PIN
|
||||
password: Passwort
|
||||
username: Benutzername
|
||||
config:
|
||||
hints:
|
||||
applepear_url: Seite, auf der das Äpfel- und Birnensystem für Aufgaben erklärt wird.
|
||||
|
@ -781,6 +788,8 @@ de:
|
|||
import:
|
||||
notice: 'Es wurden %{count} neue Transaktionen importiert.'
|
||||
no_import_method: Für dieses Bankkonto ist keine Importmethode konfiguriert.
|
||||
submit: Abschicken
|
||||
title: Banktransaktionen für %{name} importieren
|
||||
index:
|
||||
title: Bankkonten
|
||||
bank_transactions:
|
||||
|
|
|
@ -536,6 +536,13 @@ en:
|
|||
text_1: 'Here you can upload a spreadsheet to update the articles of %{supplier}. Excel (xls, xlsx) and OpenOffice (ods) spreadsheets are accepted, as well as comma-separated files (csv, columns separated by ";" with utf-8 encoding). Only the first sheet will be imported, and columns must be in the following order:'
|
||||
text_2: The rows shown here are examples. When there is an "x" in the first column, the article is outlisted and will be removed. This allows you to edit the spreadsheet and quickly remove many articles at once, for example when articles become unavailable with the supplier. The category will be matched to your Foodsoft category list (both by category name and import names).
|
||||
title: Upload articles of %{supplier}
|
||||
bank_account_connector:
|
||||
confirm_app: Please confirum the code %{code} in your app.
|
||||
fields:
|
||||
email: E-Mail
|
||||
pin: PIN
|
||||
password: Password
|
||||
username: Username
|
||||
config:
|
||||
hints:
|
||||
applepear_url: Website where the apple and pear system for tasks is explained.
|
||||
|
@ -806,6 +813,8 @@ en:
|
|||
import:
|
||||
notice: '%{count} new transactions have been imported'
|
||||
no_import_method: For this bank account no import method is configured.
|
||||
submit: Abschicken
|
||||
title: Import bank transactions for %{name}
|
||||
index:
|
||||
title: Bank Accounts
|
||||
bank_transactions:
|
||||
|
|
|
@ -197,6 +197,7 @@ Foodsoft::Application.routes.draw do
|
|||
member do
|
||||
get :assign_unlinked_transactions
|
||||
get :import
|
||||
post :import
|
||||
end
|
||||
|
||||
resources :bank_transactions, as: :transactions
|
||||
|
|
155
lib/bank_account_connector.rb
Normal file
155
lib/bank_account_connector.rb
Normal file
|
@ -0,0 +1,155 @@
|
|||
class BankAccountConnector
|
||||
|
||||
class TextItem
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def name
|
||||
nil
|
||||
end
|
||||
|
||||
def text
|
||||
@text
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class TextField
|
||||
def initialize(name, value, label)
|
||||
@name = name
|
||||
@value = value
|
||||
@label = label
|
||||
end
|
||||
|
||||
def type
|
||||
nil
|
||||
end
|
||||
|
||||
def name
|
||||
@name
|
||||
end
|
||||
|
||||
def value
|
||||
@value
|
||||
end
|
||||
|
||||
def label
|
||||
@label || @name.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class PasswordField < TextField
|
||||
|
||||
def type
|
||||
:password
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class HiddenField < TextField
|
||||
|
||||
def type
|
||||
:hidden
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@registered_classes = Set.new
|
||||
|
||||
def self.register(klass)
|
||||
@@registered_classes.add klass
|
||||
end
|
||||
|
||||
def self.find(iban)
|
||||
@@registered_classes.each do |klass|
|
||||
return klass if klass.handles(iban)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def initialize(bank_account)
|
||||
@bank_account = bank_account
|
||||
@auto_submit = nil
|
||||
@controls = []
|
||||
@count = 0
|
||||
end
|
||||
|
||||
def iban
|
||||
@bank_account.iban
|
||||
end
|
||||
|
||||
def auto_submit
|
||||
@auto_submit
|
||||
end
|
||||
|
||||
def controls
|
||||
@controls
|
||||
end
|
||||
|
||||
def count
|
||||
@count
|
||||
end
|
||||
|
||||
def text(data)
|
||||
@controls += [TextItem.new(data)]
|
||||
end
|
||||
|
||||
def wait_with_text(auto_submit, data)
|
||||
@auto_submit = auto_submit
|
||||
@controls += [TextItem.new(data)]
|
||||
end
|
||||
|
||||
def wait_for_app(code)
|
||||
hidden_field :twofactor, code
|
||||
wait_with_text 3000, t('.confirm_app', code: code)
|
||||
nil
|
||||
end
|
||||
|
||||
def text_field(name, value=nil)
|
||||
@controls += [TextField.new(name, value, t(name))]
|
||||
end
|
||||
|
||||
def hidden_field(name, value)
|
||||
@controls += [HiddenField.new(name, value, 'HIDDEN')]
|
||||
end
|
||||
|
||||
def password_field(name, value=nil)
|
||||
@controls += [PasswordField.new(name, value, t(name))]
|
||||
end
|
||||
|
||||
|
||||
def set_balance(amount)
|
||||
@bank_account.balance = amount
|
||||
end
|
||||
|
||||
def continuation_point
|
||||
@bank_account.import_continuation_point
|
||||
end
|
||||
|
||||
def set_continuation_point(data)
|
||||
@bank_account.import_continuation_point = data
|
||||
end
|
||||
|
||||
def update_or_create_transaction(external_id, data={})
|
||||
@bank_account.bank_transactions.where(external_id: external_id).first_or_create.update(data)
|
||||
@count += 1
|
||||
end
|
||||
|
||||
def finish
|
||||
@bank_account.last_import = Time.now
|
||||
@bank_account.save!
|
||||
end
|
||||
|
||||
def load(data)
|
||||
end
|
||||
|
||||
def dump
|
||||
end
|
||||
|
||||
def t(key, args={})
|
||||
return t(".fields.#{key}") unless key.is_a? String
|
||||
I18n.t 'bank_account_connector' + key, args
|
||||
end
|
||||
end
|
31
lib/bank_account_connector_external.rb
Normal file
31
lib/bank_account_connector_external.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class BankAccountConnectorExternal < BankAccountConnector
|
||||
|
||||
def load(data)
|
||||
@connector = create_connector
|
||||
@connector.load data
|
||||
end
|
||||
|
||||
def dump
|
||||
@connector.dump
|
||||
end
|
||||
|
||||
def connector_import
|
||||
set_balance @connector.balance iban
|
||||
cp = @connector.transactions iban, continuation_point do |t|
|
||||
update_or_create_transaction t[:id], map_transaction(t)
|
||||
end
|
||||
set_continuation_point cp if cp
|
||||
end
|
||||
|
||||
def connector_logout
|
||||
@connector.logout
|
||||
end
|
||||
|
||||
def import(data)
|
||||
return false unless connector_login(data)
|
||||
connector_import
|
||||
connector_logout
|
||||
true
|
||||
end
|
||||
|
||||
end
|
|
@ -67,11 +67,14 @@ namespace :foodsoft do
|
|||
desc "Import and assign bank transactions"
|
||||
task :import_and_assign_bank_transactions => :environment do
|
||||
BankAccount.find_each do |ba|
|
||||
import_method = ba.find_import_method
|
||||
next unless import_method
|
||||
import_count = import_method.call(ba)
|
||||
importer = ba.find_connector
|
||||
next unless importer
|
||||
importer.load nil
|
||||
ok = importer.import nil
|
||||
next unless ok
|
||||
importer.finish
|
||||
assign_count = ba.assign_unlinked_transactions
|
||||
rake_say "#{ba.name}: imported #{import_count}, assigned #{assign_count}"
|
||||
rake_say "#{ba.name}: imported #{importer.count}, assigned #{assign_count}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue