Merge pull request #387 from foodcoops/feature/boxfills
Add optional boxfill phase to orders (+ bootstrap buttons)
This commit is contained in:
commit
227ca0dd84
16 changed files with 271 additions and 76 deletions
|
@ -5,11 +5,11 @@
|
|||
//
|
||||
// Call setDecimalSeparator(char) to overwrite the default character "." with a localized value.
|
||||
|
||||
var modified = false // indicates if anything has been clicked on this page
|
||||
var modified = false; // indicates if anything has been clicked on this page
|
||||
var groupBalance = 0; // available group money
|
||||
var minimumBalance = 0; // minimum group balance for the order to be succesful
|
||||
var toleranceIsCostly = true; // default tolerance behaviour
|
||||
var isStockit = false; // Wheter the order is from stock oder normal supplier
|
||||
var isStockit = false; // Whether the order is from stock oder normal supplier
|
||||
|
||||
// Article data arrays:
|
||||
var price = new Array();
|
||||
|
@ -48,27 +48,37 @@ function addData(orderArticleId, itemPrice, itemUnit, itemSubtotal, itemQuantity
|
|||
}
|
||||
|
||||
function increaseQuantity(item) {
|
||||
var value = Number($('#q_' + item).val()) + 1;
|
||||
var $el = $('#q_' + item),
|
||||
value = Number($el.val()) + 1,
|
||||
max = $el.data('max');
|
||||
if (value > max) { value = max; }
|
||||
if (!isStockit || (value <= (quantityAvailable[item] + itemsAllocated[item]))) {
|
||||
update(item, value, $('#t_' + item).val());
|
||||
}
|
||||
}
|
||||
|
||||
function decreaseQuantity(item) {
|
||||
var value = Number($('#q_' + item).val()) - 1;
|
||||
if (value >= 0) {
|
||||
var $el = $('#q_' + item),
|
||||
value = Number($el.val()) - 1,
|
||||
min = $el.data('min') || 0;
|
||||
if (value >= min) {
|
||||
update(item, value, $('#t_' + item).val());
|
||||
}
|
||||
}
|
||||
|
||||
function increaseTolerance(item) {
|
||||
var value = Number($('#t_' + item).val()) + 1;
|
||||
var $el = $('#t_' + item),
|
||||
value = Number($el.val()) + 1;
|
||||
max = $el.data('max');
|
||||
if (value > max) { value = max; }
|
||||
update(item, $('#q_' + item).val(), value);
|
||||
}
|
||||
|
||||
function decreaseTolerance(item) {
|
||||
var value = Number($('#t_' + item).val()) - 1;
|
||||
if (value >= 0) {
|
||||
var $el = $('#t_' + item),
|
||||
value = Number($el.val()) - 1,
|
||||
min = $el.data('min') || 0;
|
||||
if (value >= min) {
|
||||
update(item, $('#q_' + item).val(), value);
|
||||
}
|
||||
}
|
||||
|
@ -139,9 +149,8 @@ function update(item, quantity, tolerance) {
|
|||
.removeClass('missing-many missing-few missing-none')
|
||||
.addClass(missing_units_css);
|
||||
|
||||
|
||||
// update balance
|
||||
updateBalance();
|
||||
updateButtons($('#q_'+item).closest('tr'));
|
||||
}
|
||||
|
||||
function calcUnits(unitSize, quantity, tolerance) {
|
||||
|
@ -184,21 +193,43 @@ function updateBalance() {
|
|||
}
|
||||
}
|
||||
|
||||
function updateButtons($el) {
|
||||
// enable/disable buttons depending on min/max vs. value
|
||||
$el.find('a[data-increase_quantity]').each(function() {
|
||||
var $q = $el.find('#q_'+$(this).data('increase_quantity'));
|
||||
$(this).toggleClass('disabled', $q.val() >= $q.data('max'));
|
||||
});
|
||||
$el.find('a[data-decrease_quantity]').each(function() {
|
||||
var $q = $el.find('#q_'+$(this).data('decrease_quantity'));
|
||||
$(this).toggleClass('disabled', $q.val() <= ($q.data('min')||0));
|
||||
});
|
||||
$el.find('a[data-increase_tolerance]').each(function() {
|
||||
var $t = $el.find('#t_'+$(this).data('increase_tolerance'));
|
||||
$(this).toggleClass('disabled', $t.val() >= $t.data('max'));
|
||||
});
|
||||
$el.find('a[data-decrease_tolerance]').each(function() {
|
||||
var $t = $el.find('#t_'+$(this).data('decrease_tolerance'));
|
||||
$(this).toggleClass('disabled', $t.val() <= ($t.data('min')||0));
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('input[data-increase_quantity]').on('touchclick', function() {
|
||||
$('a[data-increase_quantity]').on('touchclick', function() {
|
||||
increaseQuantity($(this).data('increase_quantity'));
|
||||
});
|
||||
$('input[data-decrease_quantity]').on('touchclick', function() {
|
||||
$('a[data-decrease_quantity]').on('touchclick', function() {
|
||||
decreaseQuantity($(this).data('decrease_quantity'));
|
||||
});
|
||||
$('input[data-increase_tolerance]').on('touchclick', function() {
|
||||
$('a[data-increase_tolerance]').on('touchclick', function() {
|
||||
increaseTolerance($(this).data('increase_tolerance'));
|
||||
});
|
||||
$('input[data-decrease_tolerance]').on('touchclick', function() {
|
||||
$('a[data-decrease_tolerance]').on('touchclick', function() {
|
||||
decreaseTolerance($(this).data('decrease_tolerance'));
|
||||
});
|
||||
|
||||
$('a[data-confirm_switch_order]').on('touchclick', function() {
|
||||
return (!modified || confirm(I18n.t('js.ordering.confirm_change')));
|
||||
});
|
||||
|
||||
updateButtons($(document));
|
||||
});
|
||||
|
|
|
@ -197,10 +197,17 @@ table {
|
|||
}
|
||||
.unused {
|
||||
color: @articleUnusedColor;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.partused {
|
||||
color: @articlePartusedColor;
|
||||
}
|
||||
.btn-ordering, .btn-group .btn-ordering {
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
padding: 4px 8px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
#order-footer, .article-info {
|
||||
text-align: left;
|
||||
|
|
|
@ -40,7 +40,7 @@ class Admin::ConfigsController < Admin::BaseController
|
|||
# turn recurring rules into something palatable
|
||||
def parse_recurring_selects!(config)
|
||||
if config
|
||||
for k in [:pickup, :ends] do
|
||||
for k in [:pickup, :boxfill, :ends] do
|
||||
if config[k]
|
||||
# allow clearing it using dummy value '{}' ('' would break recurring_select)
|
||||
if config[k][:recurr].present? && config[k][:recurr] != '{}'
|
||||
|
|
|
@ -54,7 +54,8 @@ module Admin::ConfigsHelper
|
|||
checked_value = options.delete(:checked_value) || 'true'
|
||||
unchecked_value = options.delete(:unchecked_value) || 'false'
|
||||
options[:checked] = 'checked' if v=options.delete(:value) && v!='false'
|
||||
form.hidden_field(key, value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false)
|
||||
# different key for hidden field so that allow clocking on label focuses the control
|
||||
form.hidden_field(key, id: "#{key}_", value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false)
|
||||
elsif options[:as] == :select_recurring
|
||||
options[:value] = FoodsoftDateUtil.rule_from(options[:value])
|
||||
options[:rules] ||= []
|
||||
|
@ -111,6 +112,13 @@ module Admin::ConfigsHelper
|
|||
end
|
||||
end
|
||||
|
||||
# @return [String] Tooltip element (span)
|
||||
# @param form [ActionView::Helpers::FormBuilder] Form object.
|
||||
# @param key [Symbol, String] Configuration key of a boolean (e.g. +use_messages+).
|
||||
def config_tooltip(form, key, options={}, &block)
|
||||
content_tag :span, config_input_tooltip_options(form, key, options), &block
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def config_input_tooltip_options(form, key, options)
|
||||
|
|
|
@ -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...
|
||||
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
|
||||
|
||||
|
|
|
@ -199,5 +199,3 @@ class GroupOrderArticle < ActiveRecord::Base
|
|||
result != result_computed unless result.nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,10 +187,11 @@ 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
|
||||
|
@ -219,5 +221,26 @@ class OrderArticle < ActiveRecord::Base
|
|||
order.group_orders.each(&:update_price!)
|
||||
end
|
||||
|
||||
# Throws an exception when the changed article decreases the amount of filled boxes.
|
||||
def enforce_boxfill
|
||||
# Either nothing changes, or the tolerance increases,
|
||||
# missing_units decreases 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 for '#{article.name}', 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,8 +11,18 @@
|
|||
%span.add-on= t 'number.currency.format.unit'
|
||||
= config_input_field form, :minimum_balance, as: :decimal, class: 'input-small'
|
||||
|
||||
%h4= t '.schedule_title'
|
||||
= form.simple_fields_for :order_schedule do |fields|
|
||||
#boxfill-schedule.collapse{class: ('in' if FoodsoftConfig[:use_boxfill])}
|
||||
= fields.simple_fields_for 'boxfill' do |fields|
|
||||
.fold-line
|
||||
= config_input fields, 'recurr', as: :select_recurring, input_html: {class: 'input-xlarge'}
|
||||
= config_input fields, 'time', input_html: {class: 'input-mini'}
|
||||
= fields.simple_fields_for 'ends' do |fields|
|
||||
.fold-line
|
||||
= config_input fields, 'recurr', as: :select_recurring, input_html: {class: 'input-xlarge'}, allow_blank: true
|
||||
= config_input fields, 'time', input_html: {class: 'input-mini'}
|
||||
-# can't use collapse and tooltip on same element :/
|
||||
= config_input form, :use_boxfill, as: :boolean do
|
||||
= config_tooltip form, :use_boxfill do
|
||||
= config_input_field form, :use_boxfill, as: :boolean, title: '', data: {toggle: 'collapse', target: '#boxfill-schedule'}
|
||||
|
|
|
@ -102,21 +102,27 @@
|
|||
%span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units]
|
||||
|
||||
%td.quantity
|
||||
%input{id: "q_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", size: "2", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:quantity]}/
|
||||
%input{id: "q_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:quantity], 'data-min' => (@ordering_data[:order_articles][order_article.id][:quantity] if @order.boxfill?), 'data-max' => (@ordering_data[:order_articles][order_article.id][:quantity]+@ordering_data[:order_articles][order_article.id][:missing_units] if @order.boxfill?)}/
|
||||
%span.used{id: "q_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_quantity]
|
||||
+
|
||||
%span.unused{id: "q_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] - @ordering_data[:order_articles][order_article.id][:used_quantity]
|
||||
%input{type: 'button', value: '+', 'data-increase_quantity' => order_article.id}
|
||||
%input{type: 'button', value: '-', 'data-decrease_quantity' => order_article.id}
|
||||
.btn-group
|
||||
%a.btn.btn-ordering{'data-increase_quantity' => order_article.id}
|
||||
%i.icon-plus
|
||||
%a.btn.btn-ordering{'data-decrease_quantity' => order_article.id}
|
||||
%i.icon-minus
|
||||
|
||||
%td.tolerance{style: ('display:none' if @order.stockit?)}
|
||||
%input{id: "t_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", size: "2", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:tolerance]}/
|
||||
%input{id: "t_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:tolerance], 'data-min' => (@ordering_data[:order_articles][order_article.id][:tolerance] if @order.boxfill?)}/
|
||||
- if (@ordering_data[:order_articles][order_article.id][:unit] > 1)
|
||||
%span.used{id: "t_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_tolerance]
|
||||
+
|
||||
%span.unused{id: "t_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] - @ordering_data[:order_articles][order_article.id][:used_tolerance]
|
||||
%input{type: 'button', value: '+', 'data-increase_tolerance' => order_article.id}
|
||||
%input{type: 'button', value: '-', 'data-decrease_tolerance' => order_article.id}
|
||||
.btn-group
|
||||
%a.btn.btn-ordering{'data-increase_tolerance' => order_article.id}
|
||||
%i.icon-plus
|
||||
%a.btn.btn-ordering{'data-decrease_tolerance' => order_article.id}
|
||||
%i.icon-minus
|
||||
|
||||
%td{id: "td_price_#{order_article.id}", style: "text-align:right; padding-right:10px; width:4em"}
|
||||
%span{id: "price_#{order_article.id}_display"}= number_to_currency(@ordering_data[:order_articles][order_article.id][:total_price])
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
= f.hidden_field :supplier_id
|
||||
.fold-line
|
||||
= f.input :starts, as: :date_picker_time
|
||||
= f.input :boxfill, as: :date_picker_time if @order.is_boxfill_useful?
|
||||
= f.input :ends, as: :date_picker_time
|
||||
= f.input :note, input_html: {rows: 2, class: 'input-xxlarge'}
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ en:
|
|||
sent_to_all: Send to all members
|
||||
subject: Subject
|
||||
order:
|
||||
boxfill: Fill boxes after
|
||||
closed_by: Settled by
|
||||
created_by: Created by
|
||||
ends: Ends at
|
||||
|
@ -237,6 +238,8 @@ en:
|
|||
pdf_title: PDF documents
|
||||
tab_messages:
|
||||
emails_title: Sending email
|
||||
tab_payment:
|
||||
schedule_title: Ordering schedule
|
||||
tab_tasks:
|
||||
periodic_title: Periodic tasks
|
||||
tabs:
|
||||
|
@ -478,6 +481,9 @@ en:
|
|||
ends:
|
||||
recurr: Schedule for default order closing date.
|
||||
time: Default time when orders are closed.
|
||||
boxfill:
|
||||
recurr: Schedule for when the box-fill phase starts by default.
|
||||
time: Default time when the box-fill phase of the ordering starts.
|
||||
initial: Schedule starts at this date.
|
||||
page_footer: Shown on each page at the bottom. Enter "blank" to disable the footer completely.
|
||||
pdf_add_page_breaks:
|
||||
|
@ -492,6 +498,7 @@ en:
|
|||
tax_default: Default VAT percentage for new articles.
|
||||
tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order.
|
||||
use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering.
|
||||
use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders.
|
||||
use_messages: Allow members to communicate with each other within Foodsoft.
|
||||
use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname.
|
||||
use_wiki: Enable editable wiki pages.
|
||||
|
@ -523,6 +530,9 @@ en:
|
|||
ends:
|
||||
recurr: Order ends
|
||||
time: time
|
||||
boxfill:
|
||||
recurr: Box fill after
|
||||
time: time
|
||||
initial: Schedule start
|
||||
page_footer: Page footer
|
||||
pdf_add_page_breaks: Page breaks
|
||||
|
@ -536,6 +546,7 @@ en:
|
|||
time_zone: Time zone
|
||||
tolerance_is_costly: Tolerance is costly
|
||||
use_apple_points: Apple points
|
||||
use_boxfill: Box-fill phase
|
||||
use_messages: Messages
|
||||
use_nick: Use nicknames
|
||||
use_wiki: Enable wiki
|
||||
|
@ -1279,6 +1290,8 @@ en:
|
|||
close_direct_message: Order settled without charging member accounts.
|
||||
error_closed: Order was already settled
|
||||
error_nosel: At least one article must be selected. You may want to delete the order instead?
|
||||
error_boxfill_before_ends: must be after the box-fill date (or remain empty)
|
||||
error_starts_before_boxfill: must be after the start date (or remain empty)
|
||||
error_starts_before_ends: must be after the start date (or remain empty)
|
||||
notice_close: 'Order: %{name}, until %{ends}'
|
||||
stock: Stock
|
||||
|
|
5
db/migrate/20150923190747_add_boxfill_to_order.rb
Normal file
5
db/migrate/20150923190747_add_boxfill_to_order.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddBoxfillToOrder < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :orders, :boxfill, :datetime
|
||||
end
|
||||
end
|
23
db/schema.rb
23
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20150301000000) do
|
||||
ActiveRecord::Schema.define(version: 20150923190747) do
|
||||
|
||||
create_table "article_categories", force: :cascade do |t|
|
||||
t.string "name", limit: 255, default: "", null: false
|
||||
|
@ -37,7 +37,7 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
t.integer "article_category_id", limit: 4, default: 0, null: false
|
||||
t.string "unit", limit: 255, default: "", null: false
|
||||
t.string "note", limit: 255
|
||||
t.boolean "availability", limit: 1, default: true, null: false
|
||||
t.boolean "availability", default: true, null: false
|
||||
t.string "manufacturer", limit: 255
|
||||
t.string "origin", limit: 255
|
||||
t.datetime "shared_updated_on"
|
||||
|
@ -61,7 +61,7 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
create_table "assignments", force: :cascade do |t|
|
||||
t.integer "user_id", limit: 4, default: 0, null: false
|
||||
t.integer "task_id", limit: 4, default: 0, null: false
|
||||
t.boolean "accepted", limit: 1, default: false
|
||||
t.boolean "accepted", default: false
|
||||
end
|
||||
|
||||
add_index "assignments", ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true, using: :btree
|
||||
|
@ -127,18 +127,18 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
t.string "description", limit: 255
|
||||
t.decimal "account_balance", precision: 12, scale: 2, default: 0, null: false
|
||||
t.datetime "created_on", null: false
|
||||
t.boolean "role_admin", limit: 1, default: false, null: false
|
||||
t.boolean "role_suppliers", limit: 1, default: false, null: false
|
||||
t.boolean "role_article_meta", limit: 1, default: false, null: false
|
||||
t.boolean "role_finance", limit: 1, default: false, null: false
|
||||
t.boolean "role_orders", limit: 1, default: false, null: false
|
||||
t.boolean "role_admin", default: false, null: false
|
||||
t.boolean "role_suppliers", default: false, null: false
|
||||
t.boolean "role_article_meta", default: false, null: false
|
||||
t.boolean "role_finance", default: false, null: false
|
||||
t.boolean "role_orders", default: false, null: false
|
||||
t.datetime "deleted_at"
|
||||
t.string "contact_person", limit: 255
|
||||
t.string "contact_phone", limit: 255
|
||||
t.string "contact_address", limit: 255
|
||||
t.text "stats", limit: 65535
|
||||
t.integer "next_weekly_tasks_number", limit: 4, default: 8
|
||||
t.boolean "ignore_apple_restriction", limit: 1, default: false
|
||||
t.boolean "ignore_apple_restriction", default: false
|
||||
end
|
||||
|
||||
add_index "groups", ["name"], name: "index_groups_on_name", unique: true, using: :btree
|
||||
|
@ -184,7 +184,7 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
t.string "subject", limit: 255, null: false
|
||||
t.text "body", limit: 65535
|
||||
t.integer "email_state", limit: 4, default: 0, null: false
|
||||
t.boolean "private", limit: 1, default: false
|
||||
t.boolean "private", default: false
|
||||
t.datetime "created_at"
|
||||
t.integer "reply_to", limit: 4
|
||||
t.integer "group_id", limit: 4
|
||||
|
@ -224,6 +224,7 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
t.integer "updated_by_user_id", limit: 4
|
||||
t.decimal "foodcoop_result", precision: 8, scale: 2
|
||||
t.integer "created_by_user_id", limit: 4
|
||||
t.datetime "boxfill"
|
||||
end
|
||||
|
||||
add_index "orders", ["state"], name: "index_orders_on_state", using: :btree
|
||||
|
@ -316,7 +317,7 @@ ActiveRecord::Schema.define(version: 20150301000000) do
|
|||
t.string "name", limit: 255, default: "", null: false
|
||||
t.string "description", limit: 255
|
||||
t.date "due_date"
|
||||
t.boolean "done", limit: 1, default: false
|
||||
t.boolean "done", default: false
|
||||
t.integer "workgroup_id", limit: 4
|
||||
t.datetime "created_on", null: false
|
||||
t.datetime "updated_on", null: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe OrderArticle do
|
||||
let(:order) { FactoryGirl.create :order, article_count: 1 }
|
||||
let(:order) { create :order, article_count: 1 }
|
||||
let(:oa) { order.order_articles.first }
|
||||
|
||||
it 'is not ordered by default' do
|
||||
|
@ -34,15 +34,15 @@ describe OrderArticle do
|
|||
end
|
||||
|
||||
describe 'redistribution' do
|
||||
let(:admin) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_finance: true)] }
|
||||
let(:article) { FactoryGirl.create :article, unit_quantity: 3 }
|
||||
let(:order) { FactoryGirl.create :order, article_ids: [article.id] }
|
||||
let(:go1) { FactoryGirl.create :group_order, order: order }
|
||||
let(:go2) { FactoryGirl.create :group_order, order: order }
|
||||
let(:go3) { FactoryGirl.create :group_order, order: order }
|
||||
let(:goa1) { FactoryGirl.create :group_order_article, group_order: go1, order_article: oa }
|
||||
let(:goa2) { FactoryGirl.create :group_order_article, group_order: go2, order_article: oa }
|
||||
let(:goa3) { FactoryGirl.create :group_order_article, group_order: go3, order_article: oa }
|
||||
let(:admin) { create :user, groups:[create(:workgroup, role_finance: true)] }
|
||||
let(:article) { create :article, unit_quantity: 3 }
|
||||
let(:order) { create :order, article_ids: [article.id] }
|
||||
let(:go1) { create :group_order, order: order }
|
||||
let(:go2) { create :group_order, order: order }
|
||||
let(:go3) { create :group_order, order: order }
|
||||
let(:goa1) { create :group_order_article, group_order: go1, order_article: oa }
|
||||
let(:goa2) { create :group_order_article, group_order: go2, order_article: oa }
|
||||
let(:goa3) { create :group_order_article, group_order: go3, order_article: oa }
|
||||
|
||||
# set quantities of group_order_articles
|
||||
def set_quantities(q1, q2, q3)
|
||||
|
@ -117,4 +117,80 @@ describe OrderArticle do
|
|||
|
||||
end
|
||||
|
||||
describe 'boxfill' do
|
||||
before { FoodsoftConfig[:use_boxfill] = true }
|
||||
let(:article) { create :article, unit_quantity: 6 }
|
||||
let(:order) { create :order, article_ids: [article.id], starts: 1.week.ago }
|
||||
let(:oa) { order.order_articles.first }
|
||||
let(:go) { create :group_order, order: order }
|
||||
let(:goa) { create :group_order_article, group_order: go, order_article: oa }
|
||||
|
||||
shared_examples "boxfill" do |success, q|
|
||||
# initial situation
|
||||
before do
|
||||
goa.update_quantities *q.keys[0]
|
||||
oa.update_results!; oa.reload
|
||||
end
|
||||
|
||||
# check starting condition
|
||||
it '(before)' do
|
||||
expect([oa.quantity, oa.tolerance, oa.missing_units]).to eq q.keys[1]
|
||||
end
|
||||
|
||||
# actual test
|
||||
it (success ? 'succeeds' : 'fails') do
|
||||
order.update_attributes(boxfill: boxfill_from)
|
||||
|
||||
r = proc {
|
||||
goa.update_quantities *q.values[0]
|
||||
oa.update_results!
|
||||
}
|
||||
if success
|
||||
r.call
|
||||
else
|
||||
expect(r).to raise_error(ActiveRecord::RecordNotSaved)
|
||||
end
|
||||
|
||||
oa.reload
|
||||
expect([oa.quantity, oa.tolerance, oa.missing_units]).to eq q.values[1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'before the date' do
|
||||
let(:boxfill_from) { 1.hour.from_now }
|
||||
context 'decreasing the missing units' do
|
||||
include_examples "boxfill", true, [6,0]=>[5,0], [6,0,0]=>[5,0,1]
|
||||
end
|
||||
context 'decreasing the tolerance' do
|
||||
include_examples "boxfill", true, [1,2]=>[1,1], [1,2,3]=>[1,1,4]
|
||||
end
|
||||
end
|
||||
|
||||
context 'after the date' do
|
||||
let(:boxfill_from) { 1.second.ago }
|
||||
context 'changing nothing in particular' do
|
||||
include_examples "boxfill", true, [4,1]=>[4,1], [4,1,1]=>[4,1,1]
|
||||
end
|
||||
context 'increasing missing units' do
|
||||
include_examples "boxfill", false, [3,0]=>[2,0], [3,0,3]=>[3,0,3]
|
||||
end
|
||||
context 'increasing tolerance' do
|
||||
include_examples "boxfill", true, [2,1]=>[2,2], [2,1,3]=>[2,2,2]
|
||||
end
|
||||
context 'decreasing quantity to fix missing units' do
|
||||
include_examples "boxfill", true, [7,0]=>[6,0], [7,0,5]=>[6,0,0]
|
||||
end
|
||||
context 'decreasing quantity keeping missing units equal' do
|
||||
include_examples "boxfill", false, [7,0]=>[1,0], [7,0,5]=>[7,0,5]
|
||||
end
|
||||
context 'moving tolerance to quantity' do
|
||||
include_examples "boxfill", true, [4,2]=>[6,0], [4,2,0]=>[6,0,0]
|
||||
end
|
||||
# @todo enable test when tolerance doesn't count in missing_units
|
||||
#context 'decreasing tolerance' do
|
||||
# include_examples "boxfill", false, [0,2]=>[0,0], [0,2,0]=>[0,2,0]
|
||||
#end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue