diff --git a/app/controllers/admin/bank_accounts_controller.rb b/app/controllers/admin/bank_accounts_controller.rb
new file mode 100644
index 00000000..a54ea798
--- /dev/null
+++ b/app/controllers/admin/bank_accounts_controller.rb
@@ -0,0 +1,41 @@
+class Admin::BankAccountsController < Admin::BaseController
+ inherit_resources
+
+ def new
+ @bank_account = BankAccount.new(params[:bank_account])
+ render :layout => false
+ end
+
+ def create
+ @bank_account = BankAccount.new(params[:bank_account])
+ if @bank_account.valid? && @bank_account.save
+ redirect_to update_bank_accounts_admin_finances_url, :status => 303
+ else
+ render :action => 'new', :layout => false
+ end
+ end
+
+ def edit
+ @bank_account = BankAccount.find(params[:id])
+ render :action => 'new', :layout => false
+ end
+
+ def update
+ @bank_account = BankAccount.find(params[:id])
+
+ if @bank_account.update_attributes(params[:bank_account])
+ redirect_to update_bank_accounts_admin_finances_url, :status => 303
+ else
+ render :action => 'new', :layout => false
+ end
+ end
+
+ def destroy
+ @bank_account = BankAccount.find(params[:id])
+ @bank_account.destroy
+ redirect_to update_bank_accounts_admin_finances_url, :status => 303
+ rescue => error
+ flash.now[:alert] = error.message
+ render template: 'shared/alert'
+ end
+end
diff --git a/app/controllers/admin/finances_controller.rb b/app/controllers/admin/finances_controller.rb
index a96a9dc2..81380fd8 100644
--- a/app/controllers/admin/finances_controller.rb
+++ b/app/controllers/admin/finances_controller.rb
@@ -2,9 +2,15 @@ class Admin::FinancesController < Admin::BaseController
inherit_resources
def index
+ @bank_accounts = BankAccount.order('name')
@financial_transaction_classes = FinancialTransactionClass.order('name ASC')
end
+ def update_bank_accounts
+ @bank_accounts = BankAccount.order('name')
+ render :layout => false
+ end
+
def update_transaction_types
@financial_transaction_classes = FinancialTransactionClass.order('name ASC')
render :layout => false
diff --git a/app/controllers/finance/bank_accounts_controller.rb b/app/controllers/finance/bank_accounts_controller.rb
new file mode 100644
index 00000000..8430143d
--- /dev/null
+++ b/app/controllers/finance/bank_accounts_controller.rb
@@ -0,0 +1,8 @@
+class Finance::BankAccountsController < Finance::BaseController
+
+ def index
+ @bank_accounts = BankAccount.order('name')
+ redirect_to finance_bank_account_transactions_url(@bank_accounts.first) if @bank_accounts.count == 1
+ end
+
+end
diff --git a/app/controllers/finance/bank_transactions_controller.rb b/app/controllers/finance/bank_transactions_controller.rb
new file mode 100644
index 00000000..7add74e7
--- /dev/null
+++ b/app/controllers/finance/bank_transactions_controller.rb
@@ -0,0 +1,28 @@
+class Finance::BankTransactionsController < ApplicationController
+ before_filter :authenticate_finance
+ inherit_resources
+
+ def index
+ if params["sort"]
+ sort = case params["sort"]
+ when "date" then "date"
+ when "amount" then "amount"
+ when "financial_link" then "financial_link_id"
+ when "date_reverse" then "date DESC"
+ when "amount_reverse" then "amount DESC"
+ when "financial_link_reverse" then "financial_link_id DESC"
+ end
+ else
+ sort = "date DESC"
+ end
+
+ @bank_account = BankAccount.find(params[:bank_account_id])
+ @bank_transactions = @bank_account.bank_transactions.order(sort)
+ @bank_transactions = @bank_transactions.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%", "%#{params[:query]}%") unless params[:query].nil?
+ @bank_transactions = @bank_transactions.page(params[:page]).per(@per_page)
+ end
+
+ def show
+ @bank_transaction = BankTransaction.find(params[:id])
+ end
+end
diff --git a/app/controllers/finance/financial_links_controller.rb b/app/controllers/finance/financial_links_controller.rb
index 7143674e..3fe00d01 100644
--- a/app/controllers/finance/financial_links_controller.rb
+++ b/app/controllers/finance/financial_links_controller.rb
@@ -3,7 +3,16 @@ class Finance::FinancialLinksController < Finance::BaseController
def show
@financial_link = FinancialLink.find(params[:id])
- @items = @financial_link.financial_transactions.map do |ft|
+ @items = @financial_link.bank_transactions.map do |bt|
+ {
+ date: bt.date,
+ type: t('activerecord.models.bank_transaction'),
+ description: bt.text,
+ amount: bt.amount,
+ link_to: finance_bank_transaction_path(bt)
+ }
+ end
+ @items += @financial_link.financial_transactions.map do |ft|
{
date: ft.created_on,
type: t('activerecord.models.financial_transaction'),
diff --git a/app/models/bank_account.rb b/app/models/bank_account.rb
new file mode 100644
index 00000000..4642afd3
--- /dev/null
+++ b/app/models/bank_account.rb
@@ -0,0 +1,12 @@
+class BankAccount < ActiveRecord::Base
+
+ has_many :bank_transactions, dependent: :destroy
+
+ normalize_attributes :name, :iban, :description
+
+ validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 }
+ validates :iban, :presence => true, :uniqueness => true
+ validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/
+ validates_numericality_of :balance, :message => I18n.t('bank_account.model.invalid_balance')
+
+end
diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb
new file mode 100644
index 00000000..83794338
--- /dev/null
+++ b/app/models/bank_transaction.rb
@@ -0,0 +1,34 @@
+class BankTransaction < ActiveRecord::Base
+
+ # @!attribute external_id
+ # @return [String] Unique Identifier of the transaction within the bank account.
+ # @!attribute date
+ # @return [Date] Date of the transaction.
+ # @!attribute amount
+ # @return [Number] Amount credited.
+ # @!attribute iban
+ # @return [String] Internation Bank Account Number of the sending/receiving account.
+ # @!attribute reference
+ # @return [String] 140 character long reference field as defined by SEPA.
+ # @!attribute text
+ # @return [String] Short description of the transaction.
+ # @!attribute receipt
+ # @return [String] Optional additional more detailed description of the transaction.
+ # @!attribute image
+ # @return [Binary] Optional PNG image for e.g. scan of paper receipt.
+
+ belongs_to :bank_account
+ belongs_to :financial_link
+ belongs_to :supplier, foreign_key: 'iban', primary_key: 'iban'
+ belongs_to :user, foreign_key: 'iban', primary_key: 'iban'
+
+ validates_presence_of :date, :amount, :bank_account_id
+ validates_numericality_of :amount
+
+ # Replace numeric seperator with database format
+ localize_input_of :amount
+
+ def image_url
+ 'data:image/png;base64,' + Base64.encode64(self.image)
+ end
+end
diff --git a/app/models/financial_link.rb b/app/models/financial_link.rb
index 4b49f6f5..62f4ff77 100644
--- a/app/models/financial_link.rb
+++ b/app/models/financial_link.rb
@@ -1,4 +1,5 @@
class FinancialLink < ActiveRecord::Base
+ has_many :bank_transactions
has_many :financial_transactions
has_many :invoices
end
diff --git a/app/views/admin/bank_accounts/_form.html.haml b/app/views/admin/bank_accounts/_form.html.haml
new file mode 100644
index 00000000..fbb43bd1
--- /dev/null
+++ b/app/views/admin/bank_accounts/_form.html.haml
@@ -0,0 +1,12 @@
+= simple_form_for [:admin, @bank_account], :validate => true, :remote => true do |f|
+ .modal-header
+ = close_button :modal
+ %h3= @bank_account.new_record? ? t('.title_new') : t('.title_edit')
+ .modal-body
+ = f.input :name
+ = f.input :iban
+ = f.input :description, as: :text
+ = f.input :balance
+ .modal-footer
+ = link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
+ = f.submit class: 'btn btn-primary'
diff --git a/app/views/admin/bank_accounts/new.js.haml b/app/views/admin/bank_accounts/new.js.haml
new file mode 100644
index 00000000..504f5527
--- /dev/null
+++ b/app/views/admin/bank_accounts/new.js.haml
@@ -0,0 +1,2 @@
+$('#modalContainer').html('#{j(render("form"))}');
+$('#modalContainer').modal();
diff --git a/app/views/admin/finances/_bank_accounts.html.haml b/app/views/admin/finances/_bank_accounts.html.haml
new file mode 100644
index 00000000..3f3e79cd
--- /dev/null
+++ b/app/views/admin/finances/_bank_accounts.html.haml
@@ -0,0 +1,17 @@
+%table.table.table-striped
+ %thead
+ %tr
+ %th= heading_helper BankAccount, :name
+ %th= heading_helper BankAccount, :iban
+ %th= heading_helper BankAccount, :description
+ %th
+ %tbody
+ - @bank_accounts.each do |bank_account|
+ %tr
+ %td= bank_account.name
+ %td= bank_account.iban
+ %td= truncate bank_account.description, length: 50
+ %td
+ = link_to t('ui.edit'), edit_admin_bank_account_path(bank_account), remote: true, class: 'btn btn-mini'
+ = link_to t('ui.delete'), [:admin, bank_account], :method => :delete, :data => {:confirm => t('ui.confirm_delete', name: bank_account.name)},
+ remote: true, class: 'btn btn-mini btn-danger'
diff --git a/app/views/admin/finances/index.html.haml b/app/views/admin/finances/index.html.haml
index 433c0351..e39ddd67 100644
--- a/app/views/admin/finances/index.html.haml
+++ b/app/views/admin/finances/index.html.haml
@@ -2,8 +2,13 @@
- content_for :actionbar do
= link_to t('.new_financial_transaction_class'), new_admin_financial_transaction_class_path, remote: true, class: 'btn btn-primary'
+ = link_to t('.new_bank_account'), new_admin_bank_account_path, remote: true, class: 'btn'
- content_for :sidebar do
%p= t('.first_paragraph').html_safe
+%h2= t('.transaction_types')
#transaction_types_table= render 'transaction_types'
+
+%h2= t('.bank_accounts')
+#bank_accounts_table= render 'bank_accounts'
diff --git a/app/views/admin/finances/update_bank_accounts.js.haml b/app/views/admin/finances/update_bank_accounts.js.haml
new file mode 100644
index 00000000..5cb19848
--- /dev/null
+++ b/app/views/admin/finances/update_bank_accounts.js.haml
@@ -0,0 +1,2 @@
+$('#bank_accounts_table').html('#{escape_javascript(render("admin/finances/bank_accounts"))}');
+$('#modalContainer').modal('hide');
diff --git a/app/views/finance/bank_accounts/import.html.haml b/app/views/finance/bank_accounts/import.html.haml
new file mode 100644
index 00000000..7b10dff4
--- /dev/null
+++ b/app/views/finance/bank_accounts/import.html.haml
@@ -0,0 +1,12 @@
+- 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)
diff --git a/app/views/finance/bank_accounts/index.html.haml b/app/views/finance/bank_accounts/index.html.haml
new file mode 100644
index 00000000..98b94df8
--- /dev/null
+++ b/app/views/finance/bank_accounts/index.html.haml
@@ -0,0 +1,16 @@
+- title t('.title')
+
+%table.table.table-striped
+ %thead
+ %tr
+ %th= heading_helper BankAccount, :name
+ %th= heading_helper BankAccount, :iban
+ %th= heading_helper BankAccount, :description
+ %th= heading_helper BankAccount, :balance
+ %tbody
+ - for account in @bank_accounts
+ %tr
+ %td= link_to account.name, finance_bank_account_transactions_path(account)
+ %td= account.iban
+ %td= truncate account.description, length: 50
+ %td.numeric{style: 'width:5em'}= format_currency account.balance
diff --git a/app/views/finance/bank_transactions/_transactions.html.haml b/app/views/finance/bank_transactions/_transactions.html.haml
new file mode 100644
index 00000000..8910256e
--- /dev/null
+++ b/app/views/finance/bank_transactions/_transactions.html.haml
@@ -0,0 +1,22 @@
+.pull-right
+ - if @bank_transactions.total_pages > 1
+ .btn-group= items_per_page wrap: false
+= pagination_links_remote @bank_transactions
+%table.table.table-striped
+ %thead
+ %tr
+ %th= sort_link_helper heading_helper(BankTransaction, :date), "date"
+ %th= heading_helper(BankTransaction, :text)
+ %th= heading_helper(BankTransaction, :reference)
+ %th= sort_link_helper heading_helper(BankTransaction, :amount), "amount"
+ %th= sort_link_helper heading_helper(BankTransaction, :financial_link), "financial_link"
+ %tbody
+ - @bank_transactions.each do |t|
+ %tr
+ %td= h link_to format_date(t.date), finance_bank_transaction_path(t)
+ %td= h(t.text).gsub("\n", "
").html_safe
+ %td= h t.reference
+ %td.numeric{style: 'width:5em'}= format_currency t.amount
+ %td
+ - if t.financial_link
+ = link_to t('ui.show'), finance_link_path(t.financial_link)
diff --git a/app/views/finance/bank_transactions/index.html.haml b/app/views/finance/bank_transactions/index.html.haml
new file mode 100644
index 00000000..a6ad44ed
--- /dev/null
+++ b/app/views/finance/bank_transactions/index.html.haml
@@ -0,0 +1,9 @@
+- title t('.title', name: @bank_account.name, balance: number_to_currency(@bank_account.balance))
+
+.well.well-small
+ = form_tag finance_bank_account_transactions_path, :method => :get, :remote => true,
+ 'data-submit-onchange' => true, class: 'form-search' do
+ = text_field_tag :query, params[:query], class: 'input-medium search-query',
+ placeholder: t('ui.search_placeholder')
+
+#transactionsTable= render 'transactions'
diff --git a/app/views/finance/bank_transactions/index.js.haml b/app/views/finance/bank_transactions/index.js.haml
new file mode 100644
index 00000000..d75e352f
--- /dev/null
+++ b/app/views/finance/bank_transactions/index.js.haml
@@ -0,0 +1 @@
+$('#transactionsTable').html('#{j(render('transactions'))}');
diff --git a/app/views/finance/bank_transactions/show.html.haml b/app/views/finance/bank_transactions/show.html.haml
new file mode 100644
index 00000000..8cd0f23a
--- /dev/null
+++ b/app/views/finance/bank_transactions/show.html.haml
@@ -0,0 +1,40 @@
+%p
+ %b= heading_helper(BankTransaction, :external_id) + ':'
+ = @bank_transaction.external_id
+%p
+ %b= heading_helper(BankTransaction, :date) + ':'
+ = @bank_transaction.date
+%p
+ %b= heading_helper(BankTransaction, :amount) + ':'
+ = number_to_currency(@bank_transaction.amount)
+%p
+ %b= heading_helper(BankTransaction, :iban) + ':'
+ = @bank_transaction.iban
+ - if user = @bank_transaction.user
+ = t('.belongs_to_user')
+ = user.display
+ - if user.ordergroup
+ = t('.in_ordergroup')
+ = link_to user.ordergroup.name, finance_ordergroup_transactions_path(user.ordergroup)
+ - elsif supplier = @bank_transaction.supplier
+ = t('.belongs_to_supplier')
+ = link_to supplier.name, supplier_path(supplier)
+%p
+ %b= heading_helper(BankTransaction, :reference) + ':'
+ = @bank_transaction.reference
+%p
+ %b= heading_helper(BankTransaction, :text) + ':'
+ = h(@bank_transaction.text).gsub("\n", "
").html_safe
+%p
+ %b= heading_helper(BankTransaction, :financial_link) + ':'
+ - if @bank_transaction.financial_link
+ = link_to t('ui.show'), finance_link_path(@bank_transaction.financial_link)
+ - else
+ = link_to t('.add_financial_link'), '/'
+
+- unless @bank_transaction.receipt.blank?
+ %pre= preserve @bank_transaction.receipt
+- if @bank_transaction.image
+ %p= image_tag @bank_transaction.image_url
+
+= link_to t('ui.or_cancel'), finance_bank_account_transactions_path(@bank_transaction.bank_account)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1dc8e890..bba845a2 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -242,6 +242,7 @@ en:
models:
article: Article
article_category: Category
+ bank_transaction: Bank transaction
delivery: Delivery
financial_transaction: Financial transaction
financial_transaction_class: Financial transaction class
@@ -902,6 +903,17 @@ en:
reload: Reload summary
with_extra_charge: 'with extra charge:'
without_extra_charge: 'without extra charge:'
+ bank_accounts:
+ index:
+ account_statement: Account statement
+ title: Bank Accounts
+ bank_transactions:
+ index:
+ title: Bank transactions for %{name} (%{balance})
+ show:
+ belongs_to_supplier: belongs to supplier
+ belongs_to_user: belongs to user
+ in_ordergroup: in ordergroup
create:
notice: Invoice was created.
financial_links:
@@ -1452,6 +1464,7 @@ en:
finances:
accounts: Manage accounts
balancing: Account orders
+ bank_accounts: Bank Accounts
home: Overview
invoices: Invoices
title: Finances
@@ -1992,6 +2005,7 @@ en:
please_wait: Please wait...
restore: Restore
save: Save
+ search_placeholder: Search ...
show: Show
views:
pagination:
diff --git a/config/navigation.rb b/config/navigation.rb
index 4b63f041..b621412a 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -36,6 +36,7 @@ SimpleNavigation::Configuration.run do |navigation|
primary.item :finance, I18n.t('navigation.finances.title'), '#', if: Proc.new { current_user.role_finance? || current_user.role_invoices? } do |subnav|
subnav.item :finance_home, I18n.t('navigation.finances.home'), finance_root_path, if: Proc.new { current_user.role_finance? }
+ subnav.item :finance_home, I18n.t('navigation.finances.bank_accounts'), finance_bank_accounts_path, if: Proc.new { current_user.role_finance? }
subnav.item :accounts, I18n.t('navigation.finances.accounts'), finance_ordergroups_path, if: Proc.new { current_user.role_finance? }
subnav.item :balancing, I18n.t('navigation.finances.balancing'), finance_order_index_path, if: Proc.new { current_user.role_finance? }
subnav.item :invoices, I18n.t('navigation.finances.invoices'), finance_invoices_path
diff --git a/config/routes.rb b/config/routes.rb
index 705e5bca..23a7b12b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -176,6 +176,13 @@ Foodsoft::Application.routes.draw do
get 'transactions/new_collection' => 'financial_transactions#new_collection', as: 'new_transaction_collection'
post 'transactions/create_collection' => 'financial_transactions#create_collection', as: 'create_transaction_collection'
+
+ resources :bank_accounts, only: [:index] do
+ resources :bank_transactions, as: :transactions
+ end
+
+ resources :bank_transactions, only: [:index, :show]
+
end
########### Administration
@@ -184,9 +191,11 @@ Foodsoft::Application.routes.draw do
root to: 'base#index'
resources :finances, only: [:index] do
+ get :update_bank_accounts, on: :collection
get :update_transaction_types, on: :collection
end
+ resources :bank_accounts
resources :financial_transaction_classes
resources :financial_transaction_types
diff --git a/db/migrate/20160222000000_create_bank_accounts_and_transactions.rb b/db/migrate/20160222000000_create_bank_accounts_and_transactions.rb
new file mode 100644
index 00000000..461f97a0
--- /dev/null
+++ b/db/migrate/20160222000000_create_bank_accounts_and_transactions.rb
@@ -0,0 +1,25 @@
+class CreateBankAccountsAndTransactions < ActiveRecord::Migration
+ def change
+ create_table :bank_accounts do |t|
+ t.string :name, null: false
+ t.string :iban
+ t.string :description
+ t.decimal :balance, precision: 12, scale: 2, default: 0, null: false
+ t.datetime :last_import
+ t.string :import_continuation_point
+ end
+
+ create_table :bank_transactions do |t|
+ t.references :bank_account, null: false
+ t.string :external_id
+ t.date :date
+ t.decimal :amount, precision: 8, scale: 2, null: false
+ t.string :iban
+ t.string :reference
+ t.text :text
+ t.text :receipt
+ t.binary :image, limit: 1.megabyte
+ t.references :financial_link, index: true
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d70ec47e..89866854 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -66,6 +66,30 @@ ActiveRecord::Schema.define(version: 20171111000000) do
add_index "assignments", ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true, using: :btree
+ create_table "bank_accounts", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "iban"
+ t.string "description"
+ t.decimal "balance", precision: 12, scale: 2, default: 0, null: false
+ t.datetime "last_import"
+ t.string "import_continuation_point"
+ end
+
+ create_table "bank_transactions", force: :cascade do |t|
+ t.integer "bank_account_id", null: false
+ t.string "external_id"
+ t.date "date"
+ t.decimal "amount", precision: 8, scale: 2, null: false
+ t.string "iban"
+ t.string "reference"
+ t.text "text"
+ t.text "receipt"
+ t.binary "image"
+ t.integer "financial_link_id"
+ end
+
+ add_index "bank_transactions", ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id", using: :btree
+
create_table "deliveries", force: :cascade do |t|
t.integer "supplier_id", limit: 4
t.date "delivered_on"