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"