e7657b987f
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.
134 lines
5 KiB
Ruby
134 lines
5 KiB
Ruby
# encoding: utf-8
|
|
#
|
|
# Ordergroups can order, they are "children" of the class Group
|
|
#
|
|
# Ordergroup have the following attributes, in addition to Group
|
|
# * account_balance (decimal)
|
|
class Ordergroup < Group
|
|
include CustomFields
|
|
|
|
APPLE_MONTH_AGO = 6 # How many month back we will count tasks and orders sum
|
|
|
|
serialize :stats
|
|
|
|
has_many :financial_transactions
|
|
has_many :group_orders
|
|
has_many :orders, :through => :group_orders
|
|
|
|
validates_numericality_of :account_balance, :message => I18n.t('ordergroups.model.invalid_balance')
|
|
validate :uniqueness_of_name, :uniqueness_of_members
|
|
|
|
after_create :update_stats!
|
|
|
|
def contact
|
|
"#{contact_phone} (#{contact_person})"
|
|
end
|
|
def non_members
|
|
User.natural_order.all.reject { |u| (users.include?(u) || u.ordergroup) }
|
|
end
|
|
|
|
def last_user_activity
|
|
last_active_user = users.order('users.last_activity DESC').first
|
|
if last_active_user
|
|
last_active_user.last_activity
|
|
end
|
|
end
|
|
|
|
# the most recent order this ordergroup was participating in
|
|
def last_order
|
|
orders.order('orders.starts DESC').first
|
|
end
|
|
|
|
def value_of_open_orders(exclude = nil)
|
|
group_orders.in_open_orders.reject{|go| go == exclude}.collect(&:price).sum
|
|
end
|
|
|
|
def value_of_finished_orders(exclude = nil)
|
|
group_orders.in_finished_orders.reject{|go| go == exclude}.collect(&:price).sum
|
|
end
|
|
|
|
# Returns the available funds for this order group (the account_balance minus price of all non-closed GroupOrders of this group).
|
|
# * exclude (GroupOrder): exclude this GroupOrder from the calculation
|
|
def get_available_funds(exclude = nil)
|
|
account_balance - value_of_open_orders(exclude) - value_of_finished_orders(exclude)
|
|
end
|
|
|
|
# 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, transaction_type, link = nil)
|
|
transaction do
|
|
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!
|
|
# Notify only when order group had a positive balance before the last transaction:
|
|
if t.amount < 0 && self.account_balance < 0 && self.account_balance - t.amount >= 0
|
|
Resque.enqueue(UserNotifier, FoodsoftConfig.scope, 'negative_balance', self.id, t.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
def update_stats!
|
|
# Get hours for every job of each user in period
|
|
jobs = users.to_a.sum { |u| u.tasks.done.where('updated_on > ?', APPLE_MONTH_AGO.month.ago).sum(:duration) }
|
|
# Get group_order.price for every finished order in this period
|
|
orders_sum = group_orders.includes(:order).merge(Order.finished).where('orders.ends >= ?', APPLE_MONTH_AGO.month.ago).references(:orders).sum(:price)
|
|
|
|
@readonly = false # Dirty hack, avoid getting RecordReadOnly exception when called in task after_save callback. A rails bug?
|
|
update_attribute(:stats, {:jobs_size => jobs, :orders_sum => orders_sum})
|
|
end
|
|
|
|
def avg_jobs_per_euro
|
|
stats[:jobs_size].to_f / stats[:orders_sum].to_f rescue 0
|
|
end
|
|
|
|
# This is the ordergroup job per euro performance
|
|
# in comparison to the hole foodcoop average
|
|
def apples
|
|
((avg_jobs_per_euro / Ordergroup.avg_jobs_per_euro) * 100).to_i rescue 0
|
|
end
|
|
|
|
# If the the option stop_ordering_under is set, the ordergroup is only allowed to participate in an order,
|
|
# when the apples value is above the configured amount.
|
|
# The restriction can be deactivated for each ordergroup.
|
|
# Only ordergroups, which have participated in more than 5 orders in total and more than 2 orders in apple time period
|
|
def not_enough_apples?
|
|
FoodsoftConfig[:use_apple_points] &&
|
|
FoodsoftConfig[:stop_ordering_under].present? &&
|
|
!ignore_apple_restriction &&
|
|
apples < FoodsoftConfig[:stop_ordering_under] &&
|
|
group_orders.count > 5 &&
|
|
group_orders.joins(:order).merge(Order.finished).where('orders.ends >= ?', APPLE_MONTH_AGO.month.ago).count > 2
|
|
end
|
|
|
|
# Global average
|
|
def self.avg_jobs_per_euro
|
|
stats = Ordergroup.pluck(:stats)
|
|
stats.sum {|s| s[:jobs_size].to_f } / stats.sum {|s| s[:orders_sum].to_f } rescue 0
|
|
end
|
|
|
|
def account_updated
|
|
financial_transactions.last.try(:created_on) || created_on
|
|
end
|
|
|
|
private
|
|
|
|
# Make sure, that a user can only be in one ordergroup
|
|
def uniqueness_of_members
|
|
users.each do |user|
|
|
errors.add :user_tokens, I18n.t('ordergroups.model.error_single_group', :user => user.display) if user.groups.where(:type => 'Ordergroup').size > 1
|
|
end
|
|
end
|
|
|
|
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
|
|
def uniqueness_of_name
|
|
group = Ordergroup.where(name: name)
|
|
group = group.where.not(id: self.id) unless new_record?
|
|
if group.exists?
|
|
message = group.first.deleted? ? :taken_with_deleted : :taken
|
|
errors.add :name, message
|
|
end
|
|
end
|
|
|
|
end
|
|
|