Update model to support financial transactions #367
This change introduces two new data types to group the financial transactions. Now every transaction has a "type", which itself belongs to a "class". Types should be used add structured information to an transaction, instead of writing it into the notice textfield. E.g. this could be used to have different types depending on the source of money (cash vs. bank transfer). Classes are shown as different columns in the tables and will be uses to group transactions of specific types. They should be used if not the whole amount of ordergroup should be used to order food. E.g. if there is a deposit or membership fee, which is independent of the normal credit. This will allow us to implement additional features based on classes in the future. E.g. the sum of transactions in the "membership fee" class must be positive to allow food orders or show a big warning if it is bellow a certain value.
This commit is contained in:
parent
dc94e98138
commit
e7657b987f
21 changed files with 156 additions and 34 deletions
|
@ -68,7 +68,8 @@ class Finance::BalancingController < Finance::BaseController
|
|||
# Balances the Order, Update of the Ordergroup.account_balances
|
||||
def close
|
||||
@order = Order.find(params[:id])
|
||||
@order.close!(@current_user)
|
||||
@type = FinancialTransactionType.find_by_id(params.permit(:type))
|
||||
@order.close!(@current_user, @type)
|
||||
redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice')
|
||||
|
||||
rescue => error
|
||||
|
|
|
@ -55,10 +55,11 @@ class Finance::FinancialTransactionsController < ApplicationController
|
|||
|
||||
def create_collection
|
||||
raise I18n.t('finance.financial_transactions.controller.create_collection.error_note_required') if params[:note].blank?
|
||||
type = FinancialTransactionType.find_by_id(params.permit(:type))
|
||||
params[:financial_transactions].each do |trans|
|
||||
# ignore empty amount fields ...
|
||||
unless trans[:amount].blank?
|
||||
Ordergroup.find(trans[:ordergroup_id]).add_financial_transaction!(trans[:amount], params[:note], @current_user)
|
||||
Ordergroup.find(trans[:ordergroup_id]).add_financial_transaction!(trans[:amount], params[:note], @current_user, type)
|
||||
end
|
||||
end
|
||||
redirect_to finance_ordergroups_url, notice: I18n.t('finance.financial_transactions.controller.create_collection.notice')
|
||||
|
|
|
@ -4,6 +4,7 @@ class FinancialTransaction < ActiveRecord::Base
|
|||
belongs_to :ordergroup
|
||||
belongs_to :user
|
||||
belongs_to :financial_link
|
||||
belongs_to :financial_transaction_type
|
||||
|
||||
validates_presence_of :amount, :note, :user_id, :ordergroup_id
|
||||
validates_numericality_of :amount, greater_then: -100_000,
|
||||
|
@ -11,8 +12,18 @@ class FinancialTransaction < ActiveRecord::Base
|
|||
|
||||
localize_input_of :amount
|
||||
|
||||
after_initialize do
|
||||
initialize_financial_transaction_type
|
||||
end
|
||||
|
||||
# Use this save method instead of simple save and after callback
|
||||
def add_transaction!
|
||||
ordergroup.add_financial_transaction! amount, note, user
|
||||
ordergroup.add_financial_transaction! amount, note, user, financial_transaction_type
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def initialize_financial_transaction_type
|
||||
self.financial_transaction_type ||= FinancialTransactionType.default
|
||||
end
|
||||
end
|
||||
|
|
6
app/models/financial_transaction_class.rb
Normal file
6
app/models/financial_transaction_class.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class FinancialTransactionClass < ActiveRecord::Base
|
||||
has_many :financial_transaction_types, dependent: :destroy
|
||||
|
||||
validates :name, presence: true
|
||||
validates_uniqueness_of :name
|
||||
end
|
25
app/models/financial_transaction_type.rb
Normal file
25
app/models/financial_transaction_type.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class FinancialTransactionType < ActiveRecord::Base
|
||||
belongs_to :financial_transaction_class
|
||||
has_many :financial_transactions, dependent: :restrict_with_exception
|
||||
|
||||
validates :name, presence: true
|
||||
validates_uniqueness_of :name
|
||||
validates :financial_transaction_class, presence: true
|
||||
|
||||
before_destroy :restrict_deleting_last_financial_transaction_type
|
||||
|
||||
def self.default
|
||||
first
|
||||
end
|
||||
|
||||
def self.has_multiple_types
|
||||
self.count > 1
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# check if this is the last financial transaction type and deny
|
||||
def restrict_deleting_last_financial_transaction_type
|
||||
raise I18n.t('model.financial_transaction_type.no_delete_last') if FinancialTransactionType.count == 1
|
||||
end
|
||||
end
|
|
@ -231,7 +231,7 @@ class Order < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Sets order.status to 'close' and updates all Ordergroup.account_balances
|
||||
def close!(user)
|
||||
def close!(user, transaction_type = nil)
|
||||
raise I18n.t('orders.model.error_closed') if closed?
|
||||
transaction_note = I18n.t('orders.model.notice_close', :name => name,
|
||||
:ends => ends.strftime(I18n.t('date.formats.default')))
|
||||
|
@ -243,7 +243,7 @@ class Order < ActiveRecord::Base
|
|||
for group_order in gos
|
||||
if group_order.ordergroup
|
||||
price = group_order.price * -1 # decrease! account balance
|
||||
group_order.ordergroup.add_financial_transaction!(price, transaction_note, user)
|
||||
group_order.ordergroup.add_financial_transaction!(price, transaction_note, user, transaction_type)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ class Ordergroup < Group
|
|||
|
||||
# Creates a new FinancialTransaction for this Ordergroup and updates the account_balance accordingly.
|
||||
# Throws an exception if it fails.
|
||||
def add_financial_transaction!(amount, note, user, link = nil)
|
||||
def add_financial_transaction!(amount, note, user, transaction_type, link = nil)
|
||||
transaction do
|
||||
t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user, financial_link: link)
|
||||
t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user, financial_transaction_type: transaction_type, financial_link: link)
|
||||
t.save!
|
||||
self.account_balance = financial_transactions.sum('amount')
|
||||
save!
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
-title t('.title')
|
||||
= form_tag close_finance_order_path(@order) do
|
||||
%p!= t('.first_paragraph')
|
||||
- if FinancialTransactionType.has_multiple_types
|
||||
%p
|
||||
%b= heading_helper FinancialTransaction, :financial_transaction_type
|
||||
= select_tag :type, options_for_select(FinancialTransactionType.order(:name).map { |t| [ t.name, t.id ] })
|
||||
%table.table.table-striped{:style => "width:35em"}
|
||||
- for group_order in @order.group_orders
|
||||
%tr{:class => cycle('even', 'odd')}
|
||||
%td= group_order.ordergroup_name
|
||||
%td.numeric= number_to_currency(group_order.price)
|
||||
.form-actions
|
||||
= link_to t('.clear'), close_finance_order_path(@order), method: :patch, class: 'btn btn-primary'
|
||||
= submit_tag t('.clear'), class: 'btn btn-primary'
|
||||
= link_to t('.or_cancel'), new_finance_order_path(order_id: @order.id)
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
= simple_form_for @financial_transaction, :url => finance_ordergroup_transactions_path(@ordergroup),
|
||||
:validate => true do |f|
|
||||
= f.hidden_field :ordergroup_id
|
||||
- if FinancialTransactionType.has_multiple_types
|
||||
= f.association :financial_transaction_type, :as => :radio_buttons
|
||||
= f.input :amount
|
||||
= f.input :note, :as => :text
|
||||
.form-actions
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
.well.well-small= t('.sidebar')
|
||||
|
||||
= form_tag finance_create_transaction_collection_path do
|
||||
- if FinancialTransactionType.has_multiple_types
|
||||
%p
|
||||
%b= heading_helper FinancialTransaction, :financial_transaction_type
|
||||
= select_tag :type, options_for_select(FinancialTransactionType.order(:name).map { |t| [ t.name, t.id ] })
|
||||
%p
|
||||
%b= heading_helper FinancialTransaction, :note
|
||||
= text_field_tag :note, params[:note], class: 'input-xlarge', required: 'required'
|
||||
|
|
|
@ -1401,6 +1401,8 @@ de:
|
|||
model:
|
||||
delivery:
|
||||
each_stock_article_must_be_unique: Lieferung darf jeden Lagerartikel höchstens einmal auflisten.
|
||||
financial_transaction_type:
|
||||
no_delete_last: Es muss mindestens ein Finanztransaktionstyp existieren.
|
||||
group_order:
|
||||
stock_ordergroup_name: Lager (%{user})
|
||||
invoice:
|
||||
|
|
|
@ -1411,6 +1411,8 @@ en:
|
|||
model:
|
||||
delivery:
|
||||
each_stock_article_must_be_unique: Each stock article must not be listed more than once.
|
||||
financial_transaction_type:
|
||||
no_delete_last: At least one financial transaction type must exist.
|
||||
group_order:
|
||||
stock_ordergroup_name: Stock (%{user})
|
||||
invoice:
|
||||
|
|
|
@ -149,7 +149,7 @@ Foodsoft::Application.routes.draw do
|
|||
put :update_note
|
||||
|
||||
get :confirm
|
||||
patch :close
|
||||
post :close
|
||||
patch :close_direct
|
||||
|
||||
get :new_on_order_article_create
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
class CreateFinancialTransactionClassAndTypes < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :financial_transaction_classes do |t|
|
||||
t.string :name, :null => false
|
||||
end
|
||||
|
||||
create_table :financial_transaction_types do |t|
|
||||
t.string :name, :null => false
|
||||
t.references :financial_transaction_class, :null => false
|
||||
end
|
||||
|
||||
change_table :financial_transactions do |t|
|
||||
t.references :financial_transaction_type
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "INSERT INTO financial_transaction_classes (id, name) VALUES (1, 'Standard')"
|
||||
execute "INSERT INTO financial_transaction_types (id, name, financial_transaction_class_id) VALUES (1, 'Foodsoft', 1)"
|
||||
execute "UPDATE financial_transactions SET financial_transaction_type_id = 1"
|
||||
end
|
||||
end
|
||||
|
||||
change_column_null :financial_transactions, :financial_transaction_type_id, false
|
||||
end
|
||||
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -92,6 +92,15 @@ ActiveRecord::Schema.define(version: 20171110000000) do
|
|||
t.text "note"
|
||||
end
|
||||
|
||||
create_table "financial_transaction_classes", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
end
|
||||
|
||||
create_table "financial_transaction_types", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "financial_transaction_class_id", null: false
|
||||
end
|
||||
|
||||
create_table "financial_transactions", force: :cascade do |t|
|
||||
t.integer "ordergroup_id", limit: 4, default: 0, null: false
|
||||
t.decimal "amount", precision: 8, scale: 2, default: 0, null: false
|
||||
|
@ -99,6 +108,7 @@ ActiveRecord::Schema.define(version: 20171110000000) do
|
|||
t.integer "user_id", limit: 4, default: 0, null: false
|
||||
t.datetime "created_on", null: false
|
||||
t.integer "financial_link_id"
|
||||
t.integer "financial_transaction_type_id", null: false
|
||||
end
|
||||
|
||||
add_index "financial_transactions", ["ordergroup_id"], name: "index_financial_transactions_on_ordergroup_id", using: :btree
|
||||
|
|
|
@ -21,5 +21,9 @@ User.create(
|
|||
:groups => [administrators]
|
||||
)
|
||||
|
||||
# First entry for financial transaction types
|
||||
financial_transaction_class = FinancialTransactionClass.create(:name => "Other")
|
||||
FinancialTransactionType.create(:name => "Foodcoop", :financial_transaction_class_id => financial_transaction_class.id)
|
||||
|
||||
# First entry for article categories
|
||||
ArticleCategory.create(:name => "Other", :description => "other, misc, unknown")
|
||||
|
|
|
@ -178,8 +178,12 @@ seed_group_orders
|
|||
|
||||
## Finances
|
||||
|
||||
FinancialTransaction.create(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "iDEAL payment", :user_id => 1, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00')
|
||||
FinancialTransactionClass.create(:id => 1, :name => "Other")
|
||||
|
||||
FinancialTransactionType.create(:id => 1, :name => "Foodcoop", :financial_transaction_class_id => 1)
|
||||
|
||||
FinancialTransaction.create(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "iDEAL payment", :user_id => 1, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
|
|
|
@ -178,8 +178,12 @@ seed_group_orders
|
|||
|
||||
## Finances
|
||||
|
||||
FinancialTransaction.create(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "iDEAL payment", :user_id => 1, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00')
|
||||
FinancialTransaction.create(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00')
|
||||
FinancialTransactionClass.create(:id => 1, :name => "Other")
|
||||
|
||||
FinancialTransactionType.create(:id => 1, :name => "Foodcoop", :financial_transaction_class_id => 1)
|
||||
|
||||
FinancialTransaction.create(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 0, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "iDEAL payment", :user_id => 1, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
FinancialTransaction.create(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1)
|
||||
|
|
14
spec/factories/financial_transaction_type.rb
Normal file
14
spec/factories/financial_transaction_type.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
require 'factory_bot'
|
||||
|
||||
FactoryBot.define do
|
||||
|
||||
factory :financial_transaction_class do
|
||||
sequence(:name) { |n| Faker::Lorem.characters(rand(2..12)) + " ##{n}" }
|
||||
end
|
||||
|
||||
factory :financial_transaction_type do
|
||||
financial_transaction_class
|
||||
sequence(:name) { |n| Faker::Lorem.words(rand(2..4)).join(' ') + " ##{n}" }
|
||||
end
|
||||
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
require_relative '../spec_helper'
|
||||
|
||||
feature 'settling an order', js: true do
|
||||
let(:ftt) { create :financial_transaction_type }
|
||||
let(:admin) { create :user, groups:[create(:workgroup, role_finance: true)] }
|
||||
let(:user) { create :user, groups:[create(:ordergroup)] }
|
||||
let(:supplier) { create :supplier }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require_relative '../spec_helper'
|
||||
|
||||
feature 'product distribution', js: true do
|
||||
let(:ftt) { create :financial_transaction_type }
|
||||
let(:admin) { create :admin }
|
||||
let(:user_a) { create :user, groups: [create(:ordergroup)] }
|
||||
let(:user_b) { create :user, groups: [create(:ordergroup)] }
|
||||
|
@ -13,7 +14,7 @@ feature 'product distribution', js: true do
|
|||
# make sure users have enough money to order
|
||||
[user_a, user_b].each do |user|
|
||||
ordergroup = Ordergroup.find(user.ordergroup.id)
|
||||
ordergroup.add_financial_transaction! 5000, 'for ordering', admin
|
||||
ordergroup.add_financial_transaction! 5000, 'for ordering', admin, ftt
|
||||
end
|
||||
order # make sure order is referenced
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue