2012-04-16 08:48:01 +02:00
#
2009-01-14 12:46:01 +01:00
# Ordergroups can order, they are "children" of the class Group
2021-03-01 15:27:26 +01:00
#
2009-01-14 12:46:01 +01:00
# Ordergroup have the following attributes, in addition to Group
2009-01-06 11:49:19 +01:00
# * account_balance (decimal)
2009-01-14 12:46:01 +01:00
class Ordergroup < Group
2017-10-12 20:50:40 +02:00
include CustomFields
2012-12-16 19:03:04 +01:00
2021-03-01 15:27:26 +01:00
APPLE_MONTH_AGO = 6 # How many month back we will count tasks and orders sum
2012-12-16 19:03:04 +01:00
2023-11-03 16:37:43 +01:00
attr_accessor :sepa_account_holder_user_id
2009-08-01 13:41:22 +02:00
serialize :stats
2009-01-29 01:57:51 +01:00
2012-11-12 13:13:01 +01:00
has_many :financial_transactions
2009-01-29 01:57:51 +01:00
has_many :group_orders
2018-10-13 16:21:37 +02:00
has_many :orders , through : :group_orders
has_many :group_order_articles , through : :group_orders
2009-01-06 11:49:19 +01:00
2023-05-12 13:01:12 +02:00
validates :account_balance , numericality : { message : I18n . t ( 'ordergroups.model.invalid_balance' ) }
2013-02-24 23:26:16 +01:00
validate :uniqueness_of_name , :uniqueness_of_members
2009-01-29 01:57:51 +01:00
2009-08-01 13:41:22 +02:00
after_create :update_stats!
2022-03-08 00:11:06 +01:00
scope :active , - > { joins ( :orders ) . where ( orders : { starts : ( Time . now . months_ago ( 3 ) .. Time . now ) } ) . group ( :id ) }
2021-03-17 13:12:13 +01:00
2009-02-02 16:35:43 +01:00
def contact
" #{ contact_phone } ( #{ contact_person } ) "
end
2021-03-01 15:27:26 +01:00
2009-02-01 20:56:23 +01:00
def non_members
2013-09-22 14:38:56 +02:00
User . natural_order . all . reject { | u | ( users . include? ( u ) || u . ordergroup ) }
2009-02-01 20:56:23 +01:00
end
2009-01-29 01:57:51 +01:00
2017-03-04 14:15:39 +01:00
def self . include_transaction_class_sum
columns = [ 'groups.*' ]
2023-05-12 13:01:12 +02:00
FinancialTransactionClass . all . find_each do | c |
2017-03-04 14:15:39 +01:00
columns << " sum(CASE financial_transaction_types.financial_transaction_class_id WHEN #{ c . id } THEN financial_transactions.amount ELSE 0 END) AS sum_of_class_ #{ c . id } "
end
select ( columns . join ( ', ' ) )
. joins ( 'LEFT JOIN financial_transactions ON groups.id = financial_transactions.ordergroup_id' )
. joins ( 'LEFT JOIN financial_transaction_types ON financial_transaction_types.id = financial_transactions.financial_transaction_type_id' )
. group ( 'groups.id' )
end
2018-12-21 01:12:43 +01:00
def self . custom_fields
fields = FoodsoftConfig [ :custom_fields ] && FoodsoftConfig [ :custom_fields ] [ :ordergroup ]
return [ ] unless fields
2021-03-01 15:27:26 +01:00
2018-12-21 01:12:43 +01:00
fields . map ( & :deep_symbolize_keys )
end
2016-02-18 00:00:55 +01:00
def last_user_activity
last_active_user = users . order ( 'users.last_activity DESC' ) . first
2023-05-12 13:01:12 +02:00
return unless last_active_user
last_active_user . last_activity
2016-02-18 00:00:55 +01:00
end
2013-12-14 13:15:47 +01:00
# the most recent order this ordergroup was participating in
def last_order
orders . order ( 'orders.starts DESC' ) . first
end
2009-01-29 01:57:51 +01:00
def value_of_open_orders ( exclude = nil )
2021-03-01 15:27:26 +01:00
group_orders . in_open_orders . reject { | go | go == exclude } . collect ( & :price ) . sum
2009-01-06 11:49:19 +01:00
end
2021-03-01 15:27:26 +01:00
2009-01-29 01:57:51 +01:00
def value_of_finished_orders ( exclude = nil )
2021-03-01 15:27:26 +01:00
group_orders . in_finished_orders . reject { | go | go == exclude } . collect ( & :price ) . sum
2009-01-29 01:57:51 +01:00
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
2018-12-30 03:43:48 +01:00
def financial_transaction_class_balance ( klass )
financial_transactions
. joins ( :financial_transaction_type )
2021-03-01 15:27:26 +01:00
. where ( financial_transaction_types : { financial_transaction_class_id : klass } )
2018-12-30 03:43:48 +01:00
. sum ( :amount )
end
2009-01-14 12:46:01 +01:00
# Creates a new FinancialTransaction for this Ordergroup and updates the account_balance accordingly.
2009-01-06 11:49:19 +01:00
# Throws an exception if it fails.
2019-11-04 04:33:43 +01:00
def add_financial_transaction! ( amount , note , user , transaction_type , link = nil , group_order = nil )
2017-10-28 20:54:08 +02:00
transaction do
2023-05-12 13:01:12 +02:00
t = FinancialTransaction . new ( ordergroup : self , amount : amount , note : note , user : user ,
financial_transaction_type : transaction_type , financial_link : link , group_order : group_order )
2012-08-24 14:48:53 +02:00
t . save!
2019-11-01 19:30:23 +01:00
update_balance!
2012-08-24 14:48:53 +02:00
# Notify only when order group had a positive balance before the last transaction:
2023-05-12 13:01:12 +02:00
if t . amount < 0 && account_balance < 0 && account_balance - t . amount > = 0
NotifyNegativeBalanceJob . perform_later ( self ,
t )
2012-08-24 14:48:53 +02:00
end
2021-02-18 12:37:22 +01:00
t
2009-01-06 11:49:19 +01:00
end
end
2009-02-04 16:41:01 +01:00
2009-08-01 13:41:22 +02:00
def update_stats!
2012-12-16 13:48:15 +01:00
# Get hours for every job of each user in period
2014-02-20 15:04:53 +01:00
jobs = users . to_a . sum { | u | u . tasks . done . where ( 'updated_on > ?' , APPLE_MONTH_AGO . month . ago ) . sum ( :duration ) }
2012-12-16 13:48:15 +01:00
# Get group_order.price for every finished order in this period
2023-05-12 13:01:12 +02:00
orders_sum = group_orders . includes ( :order ) . merge ( Order . finished ) . where ( 'orders.ends >= ?' ,
APPLE_MONTH_AGO . month . ago ) . references ( :orders ) . sum ( :price )
2012-12-16 13:48:15 +01:00
2012-12-16 14:13:54 +01:00
@readonly = false # Dirty hack, avoid getting RecordReadOnly exception when called in task after_save callback. A rails bug?
2023-05-12 13:01:12 +02:00
update_attribute ( :stats , { jobs_size : jobs , orders_sum : orders_sum } )
2009-08-01 13:41:22 +02:00
end
2019-11-01 19:30:23 +01:00
def update_balance!
2021-02-08 02:40:19 +01:00
new_account_balance = financial_transactions
2021-03-01 15:27:26 +01:00
. joins ( financial_transaction_type : [ :financial_transaction_class ] )
. where ( { financial_transaction_classes : { ignore_for_account_balance : false } } )
. sum ( :amount )
2021-02-08 02:40:19 +01:00
update_attribute :account_balance , new_account_balance
2019-11-01 19:30:23 +01:00
end
2009-08-01 13:41:22 +02:00
def avg_jobs_per_euro
2023-05-12 13:01:12 +02:00
stats [ :jobs_size ] . to_f / stats [ :orders_sum ] . to_f
rescue StandardError
0
2009-08-01 13:41:22 +02:00
end
2021-03-01 15:27:26 +01:00
# This is the ordergroup job per euro performance
2012-06-24 21:43:36 +02:00
# in comparison to the hole foodcoop average
def apples
2023-05-12 13:01:12 +02:00
( ( avg_jobs_per_euro / Ordergroup . avg_jobs_per_euro ) * 100 ) . to_i
rescue StandardError
0
2012-06-24 21:43:36 +02:00
end
2012-09-29 17:52:25 +02:00
# 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.
2012-09-30 13:34:16 +02:00
# The restriction can be deactivated for each ordergroup.
2012-12-16 19:03:04 +01:00
# Only ordergroups, which have participated in more than 5 orders in total and more than 2 orders in apple time period
2012-09-29 17:52:25 +02:00
def not_enough_apples?
2015-01-14 21:15:08 +01:00
FoodsoftConfig [ :use_apple_points ] &&
2021-03-01 15:27:26 +01:00
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
2012-09-29 17:52:25 +02:00
end
2009-08-01 13:41:22 +02:00
# Global average
def self . avg_jobs_per_euro
2012-12-16 13:56:47 +01:00
stats = Ordergroup . pluck ( :stats )
2023-05-12 13:01:12 +02:00
begin
stats . sum { | s | s [ :jobs_size ] . to_f } / stats . sum { | s | s [ :orders_sum ] . to_f }
rescue StandardError
0
end
2009-08-01 13:41:22 +02:00
end
2012-12-30 15:31:37 +01:00
def account_updated
financial_transactions . last . try ( :created_on ) || created_on
end
2021-03-01 15:27:26 +01:00
2022-05-27 17:06:25 +02:00
def self . sort_by_param ( param )
2023-05-12 13:01:12 +02:00
param || = 'name'
2022-05-27 17:06:25 +02:00
sort_param_map = {
2023-05-12 13:01:12 +02:00
'name' = > 'name' ,
'name_reverse' = > 'name DESC' ,
'members_count' = > 'count(users.id)' ,
'members_count_reverse' = > 'count(users.id) DESC' ,
'last_user_activity' = > 'max(users.last_activity)' ,
'last_user_activity_reverse' = > 'max(users.last_activity) DESC' ,
'last_order' = > 'max(orders.starts)' ,
'last_order_reverse' = > 'max(orders.starts) DESC'
2022-05-27 17:06:25 +02:00
}
result = self
2023-05-12 13:01:12 +02:00
result = result . left_joins ( :users ) . group ( 'groups.id' ) if param . starts_with? ( 'members_count' , 'last_user_activity' )
result = result . left_joins ( :orders ) . group ( 'groups.id' ) if param . starts_with? ( 'last_order' )
2022-05-27 17:06:25 +02:00
# Never pass user input data to Arel.sql() because of SQL Injection vulnerabilities.
# This case here is okay, as param is mapped to the actual order string.
result . order ( Arel . sql ( sort_param_map [ param ] ) )
end
2023-11-03 16:37:43 +01:00
def sepa_possible?
sepa_account_holder & . all_fields_present? || false
end
2009-02-04 16:41:01 +01:00
private
2011-06-10 13:22:15 +02:00
# Make sure, that a user can only be in one ordergroup
def uniqueness_of_members
users . each do | user |
2023-05-12 13:01:12 +02:00
next unless user . groups . where ( type : 'Ordergroup' ) . size > 1
errors . add :user_tokens ,
I18n . t ( 'ordergroups.model.error_single_group' ,
user : user . display )
2011-06-10 13:22:15 +02:00
end
end
2013-02-24 23:26:16 +01:00
2013-02-24 23:26:16 +01:00
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
def uniqueness_of_name
2014-02-20 15:04:53 +01:00
group = Ordergroup . where ( name : name )
2023-05-12 13:01:12 +02:00
group = group . where . not ( id : id ) unless new_record?
return unless group . exists?
message = group . first . deleted? ? :taken_with_deleted : :taken
errors . add :name , message
2013-02-24 23:26:16 +01:00
end
2009-01-06 11:49:19 +01:00
end