Add optional boxfill phase to orders

This commit is contained in:
wvengen 2015-09-23 22:38:20 +02:00
parent c1413ff817
commit a03789e048
13 changed files with 201 additions and 50 deletions

View file

@ -28,7 +28,7 @@ class GroupOrder < ActiveRecord::Base
data[:order_articles] = {}
order.articles_grouped_by_category.each do |article_category, order_articles|
order_articles.each do |order_article|
# Get the result of last time ordering, if possible
goa = group_order_articles.detect { |goa| goa.order_article_id == order_article.id }
@ -58,8 +58,10 @@ class GroupOrder < ActiveRecord::Base
group_order_article = group_order_articles.where(order_article_id: order_article.id).first_or_create
# Get ordered quantities and update group_order_articles/_quantities...
quantities = group_order_articles_attributes.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0})
group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i)
if group_order_articles_attributes
quantities = group_order_articles_attributes.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0})
group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i)
end
# Also update results for the order_article
logger.debug "[save_group_order_articles] update order_article.results!"
@ -86,4 +88,3 @@ class GroupOrder < ActiveRecord::Base
end
end

View file

@ -27,9 +27,9 @@ class GroupOrderArticle < ActiveRecord::Base
group_order.try!(:ordergroup_id)
end
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
# and the associated GroupOrderArticleQuantities chronologically.
#
#
# See description of the ordering algorithm in the general application documentation for details.
def update_quantities(quantity, tolerance)
logger.debug("GroupOrderArticle[#{id}].update_quantities(#{quantity}, #{tolerance})")
@ -104,7 +104,7 @@ class GroupOrderArticle < ActiveRecord::Base
# Determines how many items of this article the Ordergroup receives.
# Returns a hash with three keys: :quantity / :tolerance / :total
#
#
# See description of the ordering algorithm in the general application documentation for details.
def calculate_result(total = nil)
# return memoized result unless a total is given
@ -199,5 +199,3 @@ class GroupOrderArticle < ActiveRecord::Base
result != result_computed unless result.nil?
end
end

View file

@ -35,7 +35,7 @@ class Order < ActiveRecord::Base
# Allow separate inputs for date and time
# with workaround for https://github.com/einzige/date_time_attribute/issues/14
include DateTimeAttributeValidate
date_time_attribute :starts, :ends
date_time_attribute :starts, :boxfill, :ends
def stockit?
supplier_id == 0
@ -92,8 +92,16 @@ class Order < ActiveRecord::Base
state == "closed"
end
def boxfill?
FoodsoftConfig[:use_boxfill] && open? && boxfill.present? && boxfill < Time.now
end
def is_boxfill_useful?
FoodsoftConfig[:use_boxfill] && supplier.try(:has_tolerance?)
end
def expired?
!ends.nil? && ends < Time.now
ends.present? && ends < Time.now
end
# sets up first guess of dates when initializing a new object
@ -105,7 +113,8 @@ class Order < ActiveRecord::Base
last = (DateTime.parse(FoodsoftConfig[:order_schedule][:initial]) rescue nil)
last ||= Order.finished.reorder(:starts).first.try(:starts)
last ||= self.starts
# adjust end date
# adjust boxfill and end date
self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:boxfill] if is_boxfill_useful?
self.ends ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:ends]
end
self
@ -251,7 +260,9 @@ class Order < ActiveRecord::Base
def starts_before_ends
delta = Rails.env.test? ? 1 : 0 # since Rails 4.2 tests appear to have time differences, with this validation failing
errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if (ends && starts && ends <= (starts-delta))
errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if ends && starts && ends <= (starts-delta)
errors.add(:ends, I18n.t('orders.model.error_boxfill_before_ends')) if ends && boxfill && ends <= (boxfill-delta)
errors.add(:boxfill, I18n.t('orders.model.error_starts_before_boxfill')) if boxfill && starts && boxfill <= (starts-delta)
end
def include_articles
@ -288,4 +299,3 @@ class Order < ActiveRecord::Base
end
end

View file

@ -32,7 +32,7 @@ class OrderArticle < ActiveRecord::Base
units_to_order
end
# Count quantities of belonging group_orders.
# Count quantities of belonging group_orders.
# In balancing this can differ from ordered (by supplier) quantity for this article.
def group_orders_sum
quantity = group_order_articles.collect(&:result).sum
@ -42,10 +42,11 @@ class OrderArticle < ActiveRecord::Base
# Update quantity/tolerance/units_to_order from group_order_articles
def update_results!
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))
self.quantity = group_order_articles.collect(&:quantity).sum
self.tolerance = group_order_articles.collect(&:tolerance).sum
self.units_to_order = calculate_units_to_order(quantity, tolerance)
enforce_boxfill if order.boxfill?
save!
elsif order.finished?
update_attribute(:units_to_order, group_order_articles.collect(&:result).sum)
end
@ -186,19 +187,20 @@ class OrderArticle < ActiveRecord::Base
# @return [Number] Units missing for the last +unit_quantity+ of the article.
def missing_units
units = price.unit_quantity - ((quantity % price.unit_quantity) + tolerance)
units = 0 if units < 0
units = 0 if units == price.unit_quantity
units
_missing_units(price.unit_quantity, quantity, tolerance)
end
def missing_units_was
_missing_units(price.unit_quantity, quantity_was, tolerance_was)
end
# Check if the result of any associated GroupOrderArticle was overridden manually
def result_manually_changed?
group_order_articles.any? {|goa| goa.result_manually_changed?}
end
private
def article_and_price_exist
errors.add(:article, I18n.t('model.order_article.error_price')) if !(article = Article.find(article_id)) || article.fc_price.nil?
rescue
@ -219,5 +221,26 @@ class OrderArticle < ActiveRecord::Base
order.group_orders.each(&:update_price!)
end
end
# Throws an exception when the changed article decreases the amount of filled boxes.
def enforce_boxfill
# Either nothing changes, or
# missing_units becomes less and the amount doesn't decrease, or
# tolerance was moved to quantity. Only then are changes allowed in the boxfill phase.
delta_q = quantity - quantity_was
delta_t = tolerance - tolerance_was
delta_mis = missing_units - missing_units_was
delta_box = units_to_order - units_to_order_was
unless (delta_q == 0 && delta_t == 0) ||
(delta_mis < 0 && delta_box >= 0 && delta_t >= 0) ||
(delta_q > 0 && delta_q == -delta_t)
raise ActiveRecord::RecordNotSaved.new("Change not acceptable in boxfill phase, sorry.", self)
end
end
def _missing_units(unit_quantity, quantity, tolerance)
units = unit_quantity - ((quantity % unit_quantity) + tolerance)
units = 0 if units < 0
units = 0 if units == unit_quantity
units
end
end

View file

@ -116,6 +116,11 @@ class Supplier < ActiveRecord::Base
end
end
# @return [Boolean] Whether there are articles that would use tolerance (unit_quantity > 1)
def has_tolerance?
articles.where('articles.unit_quantity > 1').any?
end
protected
# make sure the shared_sync_method is allowed for the shared supplier