2009-01-08 16:33:27 +01:00
# An OrderArticle represents a single Article that is part of an Order.
2009-01-06 11:49:19 +01:00
class OrderArticle < ActiveRecord :: Base
2009-01-06 15:45:19 +01:00
2012-12-11 10:32:59 +01:00
attr_reader :update_current_price
2009-01-06 11:49:19 +01:00
belongs_to :order
2013-03-16 17:53:24 +01:00
belongs_to :article
2009-01-29 01:57:51 +01:00
belongs_to :article_price
2009-01-06 11:49:19 +01:00
has_many :group_order_articles , :dependent = > :destroy
2009-02-09 20:12:56 +01:00
validates_presence_of :order_id , :article_id
2009-01-29 21:28:22 +01:00
validate :article_and_price_exist
2012-06-21 17:19:00 +02:00
validates_uniqueness_of :article_id , scope : :order_id
2009-01-06 11:49:19 +01:00
2013-11-25 13:48:54 +01:00
scope :ordered , - > { where ( " units_to_order > 0 OR units_billed > 0 OR units_received > 0 " ) }
scope :ordered_or_member , - > { includes ( :group_order_articles ) . where ( " units_to_order > 0 OR units_billed > 0 OR units_received > 0 OR group_order_articles.result > 0 " ) }
2009-01-29 01:57:51 +01:00
2012-06-21 17:19:00 +02:00
before_create :init_from_balancing
after_destroy :update_ordergroup_prices
2009-01-29 21:28:22 +01:00
2013-03-12 18:54:51 +01:00
def self . sort_by_name ( order_articles )
order_articles . sort { | a , b | a . article . name < = > b . article . name }
end
def self . sort_by_order_number ( order_articles )
order_articles . sort do | a , b |
a . article . order_number . to_s . gsub ( / [^[:digit:]] / , " " ) . to_i < = >
b . article . order_number . to_s . gsub ( / [^[:digit:]] / , " " ) . to_i
end
end
2009-02-03 21:14:48 +01:00
# This method returns either the ArticlePrice or the Article
# The first will be set, when the the order is finished
2009-01-29 01:57:51 +01:00
def price
article_price || article
end
2013-11-25 13:48:54 +01:00
# latest information on available units
def units
return units_received unless units_received . nil?
return units_billed unless units_billed . nil?
units_to_order
end
2009-01-29 21:28:22 +01:00
# Count quantities of belonging group_orders.
# In balancing this can differ from ordered (by supplier) quantity for this article.
def group_orders_sum
2009-02-04 16:41:01 +01:00
quantity = group_order_articles . collect ( & :result ) . sum
2009-01-29 21:28:22 +01:00
{ :quantity = > quantity , :price = > quantity * price . fc_price }
end
2009-02-03 21:14:48 +01:00
# Update quantity/tolerance/units_to_order from group_order_articles
def update_results!
2009-02-06 16:26:35 +01:00
if order . open?
quantity = group_order_articles . collect ( & :quantity ) . sum
tolerance = group_order_articles . collect ( & :tolerance ) . sum
update_attributes ( :quantity = > quantity , :tolerance = > tolerance ,
:units_to_order = > calculate_units_to_order ( quantity , tolerance ) )
elsif order . finished?
update_attribute ( :units_to_order , group_order_articles . collect ( & :result ) . sum )
end
2009-02-03 21:14:48 +01:00
end
# Returns how many units of the belonging article need to be ordered given the specified order quantity and tolerance.
# This is determined by calculating how many units can be ordered from the given order quantity, using
# the tolerance to order an additional unit if the order quantity is not quiet sufficient.
# There must always be at least one item in a unit that is an ordered quantity (no units are ever entirely
# filled by tolerance items only).
#
# Example:
#
# unit_quantity | quantity | tolerance | calculate_units_to_order
# --------------+----------+-----------+-----------------------
# 4 | 0 | 2 | 0
# 4 | 0 | 5 | 0
# 4 | 2 | 2 | 1
# 4 | 4 | 2 | 1
# 4 | 4 | 4 | 1
# 4 | 5 | 3 | 2
# 4 | 5 | 4 | 2
#
def calculate_units_to_order ( quantity , tolerance = 0 )
unit_size = price . unit_quantity
units = quantity / unit_size
remainder = quantity % unit_size
units += ( ( remainder > 0 ) && ( remainder + tolerance > = unit_size ) ? 1 : 0 )
end
2009-03-22 11:58:01 +01:00
# Calculate price for ordered quantity.
def total_price
2009-04-18 18:08:33 +02:00
units_to_order * price . unit_quantity * price . price
2009-03-22 11:58:01 +01:00
end
# Calculate gross price for ordered qunatity.
def total_gross_price
2009-04-18 18:08:33 +02:00
units_to_order * price . unit_quantity * price . gross_price
2009-03-22 11:58:01 +01:00
end
2009-02-04 16:41:01 +01:00
def ordered_quantities_equal_to_group_orders?
2013-11-01 13:30:02 +01:00
# the rescue is a workaround for units_to_order not being defined in integration tests
( units_to_order * price . unit_quantity ) == group_orders_sum [ :quantity ] rescue false
2009-02-04 16:41:01 +01:00
end
2013-11-25 13:48:54 +01:00
def redistribute ( quantity )
# recompute
group_order_articles . each { | goa | goa . save_results! quantity }
# Update GroupOrder prices & Ordergroup stats
# TODO only affected group_orders, and once after redistributing all articles
order . group_orders . each ( & :update_price! )
order . ordergroups . each ( & :update_stats! )
# TODO notifications
end
2009-02-09 20:12:56 +01:00
# Updates order_article and belongings during balancing process
2013-03-17 18:33:04 +01:00
def update_article_and_price! ( order_article_attributes , article_attributes , price_attributes = nil )
2009-02-09 20:12:56 +01:00
OrderArticle . transaction do
2012-12-11 10:32:59 +01:00
# Updates self
self . update_attributes! ( order_article_attributes )
2009-02-09 20:12:56 +01:00
# Updates article
article . update_attributes! ( article_attributes )
2012-12-11 10:32:59 +01:00
# Updates article_price belonging to current order article
2013-03-17 18:33:04 +01:00
if price_attributes . present?
article_price . attributes = price_attributes
if article_price . changed?
# Updates also price attributes of article if update_current_price is selected
if update_current_price
article . update_attributes! ( price_attributes )
self . article_price = article . article_prices . first # Assign new created article price to order article
else
# Creates a new article_price if neccessary
# Set created_at timestamp to order ends, to make sure the current article price isn't changed
create_article_price! ( price_attributes . merge ( created_at : order . ends ) ) and save
end
# Updates ordergroup values
update_ordergroup_prices
2012-12-11 10:32:59 +01:00
end
2009-02-09 20:12:56 +01:00
end
end
end
2012-12-11 10:32:59 +01:00
def update_current_price = ( value )
@update_current_price = ( value == true or value == '1' ) ? true : false
end
2011-06-19 19:56:04 +02:00
# Units missing for the next full unit_quantity of the article
def missing_units
units = article . unit_quantity - ( ( quantity % article . unit_quantity ) + tolerance )
units = 0 if units < 0
units
end
2009-01-06 11:49:19 +01:00
private
2009-01-29 21:28:22 +01:00
def article_and_price_exist
2013-03-22 01:21:44 +01:00
errors . add ( :article , I18n . t ( 'model.order_article.error_price' ) ) if ! ( article = Article . find ( article_id ) ) || article . fc_price . nil?
2009-01-29 21:28:22 +01:00
end
2012-06-21 17:19:00 +02:00
# Associate with current article price if created in a finished order
def init_from_balancing
if order . present? and order . finished?
self . article_price = article . article_prices . first
self . units_to_order = 1
end
end
def update_ordergroup_prices
2013-05-30 10:54:22 +02:00
# updates prices of ALL ordergroups - these are actually too many
# in case of performance issues, update only ordergroups, which ordered this article
# CAUTION: in after_destroy callback related records (e.g. group_order_articles) are already non-existent
order . group_orders . each { | go | go . update_price! }
2012-06-21 17:19:00 +02:00
end
2009-01-06 11:49:19 +01:00
end
2011-05-07 21:55:24 +02:00