allow to edit GroupOrderArticle result from orders screen
Conflicts: app/assets/javascripts/application.js
This commit is contained in:
parent
f9d2c20aaa
commit
60826ceedc
34 changed files with 393 additions and 220 deletions
|
@ -7,7 +7,6 @@
|
|||
//= require bootstrap-datepicker/locales/bootstrap-datepicker.de
|
||||
//= require bootstrap-datepicker/locales/bootstrap-datepicker.nl
|
||||
//= require bootstrap-datepicker/locales/bootstrap-datepicker.fr
|
||||
//= require jquery.observe_field
|
||||
//= require list
|
||||
//= require list.unlist
|
||||
//= require list.delay
|
||||
|
@ -20,6 +19,7 @@
|
|||
//= require ordering
|
||||
//= require stupidtable
|
||||
//= require touchclick
|
||||
//= require delta_input
|
||||
|
||||
// Load following statements, when DOM is ready
|
||||
$(function() {
|
||||
|
@ -69,17 +69,32 @@ $(function() {
|
|||
return false;
|
||||
});
|
||||
|
||||
// Submit form when changing text of an input field
|
||||
// Use jquery observe_field plugin
|
||||
$('form[data-submit-onchange] input[type=text]').each(function() {
|
||||
$(this).observe_field(1, function() {
|
||||
// Submit form when clicking on checkbox
|
||||
$(document).on('click', 'form[data-submit-onchange] input[type=checkbox]:not(input[data-ignore-onchange])', function() {
|
||||
$(this).parents('form').submit();
|
||||
});
|
||||
});
|
||||
|
||||
// Submit form when clicking on checkbox
|
||||
$('form[data-submit-onchange] input[type=checkbox]:not(input[data-ignore-onchange])').click(function() {
|
||||
$(this).parents('form').submit();
|
||||
// Submit form when changing text of an input field.
|
||||
// Wubmission will be done after 500ms of not typed, unless data-submit-onchange=changed,
|
||||
// in which case it happens when the input box loses its focus ('changed' event).
|
||||
$(document).on('changed keyup focusin', 'form[data-submit-onchange] input[type=text]', function(e) {
|
||||
var input = $(this);
|
||||
// when form has data-submit-onchange=changed, don't do updates while typing
|
||||
if (e.type!='changed' && input.parents('form[data-submit-onchange=changed]')) {
|
||||
return true;
|
||||
}
|
||||
// remember old value when it's getting the focus
|
||||
if (e.type=='focusin') {
|
||||
input.data('old-value', input.val());
|
||||
return true;
|
||||
}
|
||||
// trigger timeout to submit form when value was changed
|
||||
clearTimeout(input.data('submit-timeout-id'));
|
||||
input.data('submit-timeout-id', setTimeout(function() {
|
||||
if (input.val() != input.data('old-value')) input.parents('form').submit();
|
||||
input.removeData('submit-timeout-id');
|
||||
input.removeData('old-value');
|
||||
}, 500));
|
||||
});
|
||||
|
||||
$('[data-redirect-to]').bind('change', function() {
|
||||
|
|
49
app/assets/javascripts/delta_input.js
Normal file
49
app/assets/javascripts/delta_input.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
$(function() {
|
||||
$(document).on('click', 'button[data-increment]', function() {
|
||||
data_delta_update($('#'+$(this).data('increment')), +1);
|
||||
});
|
||||
$(document).on('click', 'button[data-decrement]', function() {
|
||||
data_delta_update($('#'+$(this).data('decrement')), -1);
|
||||
});
|
||||
$(document).on('change keyup', 'input[type="text"][data-delta]', function() {
|
||||
data_delta_update(this, 0);
|
||||
});
|
||||
});
|
||||
|
||||
function data_delta_update(el, direction) {
|
||||
var id = $(el).attr('id');
|
||||
|
||||
var min = $(el).data('min');
|
||||
var max = $(el).data('max');
|
||||
var delta = $(el).data('delta');
|
||||
var granularity = $(el).data('granularity');
|
||||
|
||||
var val = $(el).val();
|
||||
var oldval = $.isNumeric(val) ? Number(val) : 0;
|
||||
var newval = oldval + delta*direction;
|
||||
|
||||
if (newval < min) newval = min;
|
||||
if (newval > max) newval = max;
|
||||
|
||||
// disable buttons when min/max reached
|
||||
$('button[data-decrement='+id+']').attr('disabled', newval<=min ? 'disabled' : null);
|
||||
$('button[data-increment='+id+']').attr('disabled', newval>=max ? 'disabled' : null);
|
||||
|
||||
// warn when what was entered is not a number
|
||||
$(el).toggleClass('error', val!='' && val!='.' && (!$.isNumeric(val) || val < 0));
|
||||
|
||||
// update field, unless the user is typing
|
||||
if (!$(el).is(':focus')) {
|
||||
$(el).val(round_float(newval, granularity));
|
||||
$(el).trigger('changed');
|
||||
}
|
||||
}
|
||||
|
||||
// truncate numbers because of tiny floating point deviations
|
||||
// if we don't do this, 1.0 might be shown as 0.99999999
|
||||
function round_float(s, granularity) {
|
||||
var e = granularity ? 1/granularity : 1000;
|
||||
return Math.round(Number(s)*e) / e;
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
@import "twitter/bootstrap/bootstrap";
|
||||
@import "twitter/bootstrap/responsive";
|
||||
@import "delta_input";
|
||||
|
||||
body {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
@ -253,6 +255,15 @@ td.symbol, th.symbol {
|
|||
.unused .symbol { color: tint(@articleUnusedColor, @nonessentialDim); }
|
||||
.unavailable .symbol { color: @articleUnavailColor; }
|
||||
|
||||
// hide symbols completely on small screens to save space
|
||||
@media (max-width: 768px) {
|
||||
.symbol {
|
||||
font-size: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ********* Tweaks & fixes
|
||||
|
||||
|
|
33
app/assets/stylesheets/delta_input.less
Normal file
33
app/assets/stylesheets/delta_input.less
Normal file
|
@ -0,0 +1,33 @@
|
|||
// needs @import "twitter/bootstrap/bootstrap";
|
||||
|
||||
form.delta-input, .delta-input form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.delta-input {
|
||||
|
||||
.btn {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[data-delta] {
|
||||
text-align: center;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
// handle error class outside of bootstrap controls
|
||||
input[data-delta].error {
|
||||
// relevant bootstrap portion of: .formFieldState(@errorText, @errorText, @errorBackground);
|
||||
border-color: @errorText;
|
||||
.box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
|
||||
&:focus {
|
||||
border-color: darken(@errorText, 10%);
|
||||
@shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@errorText, 20%);
|
||||
.box-shadow(@shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
class Finance::GroupOrderArticlesController < ApplicationController
|
||||
class GroupOrderArticlesController < ApplicationController
|
||||
|
||||
before_filter :authenticate_finance
|
||||
before_filter :find_group_order_article, except: [:new, :create]
|
||||
|
||||
layout false # We only use this controller to server js snippets, no need for layout rendering
|
||||
|
||||
|
@ -10,6 +11,8 @@ class Finance::GroupOrderArticlesController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
# XXX when ordergroup_id appears before order_article_id in the parameters, you
|
||||
# can get `NoMethodError - undefined method 'order_id' for nil:NilClass`
|
||||
@group_order_article = GroupOrderArticle.new(params[:group_order_article])
|
||||
@order_article = @group_order_article.order_article
|
||||
|
||||
|
@ -21,59 +24,38 @@ class Finance::GroupOrderArticlesController < ApplicationController
|
|||
@group_order_article = goa
|
||||
|
||||
update_summaries(@group_order_article)
|
||||
render :update
|
||||
render :create
|
||||
|
||||
elsif @group_order_article.save
|
||||
update_summaries(@group_order_article)
|
||||
render :update
|
||||
render :create
|
||||
|
||||
else # Validation failed, show form
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@group_order_article = GroupOrderArticle.find(params[:id])
|
||||
@order_article = @group_order_article.order_article
|
||||
end
|
||||
|
||||
def update
|
||||
@group_order_article = GroupOrderArticle.find(params[:id])
|
||||
@order_article = @group_order_article.order_article
|
||||
|
||||
if @group_order_article.update_attributes(params[:group_order_article])
|
||||
update_summaries(@group_order_article)
|
||||
if params[:delta]
|
||||
@group_order_article.update_attribute :result, [@group_order_article.result + params[:delta].to_f, 0].max
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
@group_order_article.update_attributes(params[:group_order_article])
|
||||
end
|
||||
|
||||
def update_result
|
||||
group_order_article = GroupOrderArticle.find(params[:id])
|
||||
@order_article = group_order_article.order_article
|
||||
|
||||
if params[:modifier] == '-'
|
||||
group_order_article.update_attribute :result, group_order_article.result - 1
|
||||
elsif params[:modifier] == '+'
|
||||
group_order_article.update_attribute :result, group_order_article.result + 1
|
||||
end
|
||||
|
||||
update_summaries(group_order_article)
|
||||
update_summaries(@group_order_article)
|
||||
|
||||
render :update
|
||||
end
|
||||
|
||||
def destroy
|
||||
group_order_article = GroupOrderArticle.find(params[:id])
|
||||
# only destroy if quantity and tolerance was zero already, so that we don't
|
||||
# lose what the user ordered, if any
|
||||
if group_order_article.quantity > 0 or group_order_article.tolerance >0
|
||||
group_order_article.update_attribute(:result, 0)
|
||||
if @group_order_article.quantity > 0 or @group_order_article.tolerance >0
|
||||
@group_order_article.update_attribute(:result, 0)
|
||||
else
|
||||
group_order_article.destroy
|
||||
@group_order_article.destroy
|
||||
end
|
||||
update_summaries(group_order_article)
|
||||
@order_article = group_order_article.order_article
|
||||
update_summaries(@group_order_article)
|
||||
|
||||
render :update
|
||||
end
|
||||
|
@ -86,4 +68,8 @@ class Finance::GroupOrderArticlesController < ApplicationController
|
|||
# Update units_to_order of order_article
|
||||
group_order_article.order_article.update_results! if group_order_article.order_article.article.is_a?(StockArticle)
|
||||
end
|
||||
|
||||
def find_group_order_article
|
||||
@group_order_article = GroupOrderArticle.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -31,8 +31,8 @@ class OrdersController < ApplicationController
|
|||
@view = (params[:view] or 'default').gsub(/[^-_a-zA-Z0-9]/, '')
|
||||
@partial = case @view
|
||||
when 'default' then 'articles'
|
||||
when 'groups'then 'shared/articles_by_groups'
|
||||
when 'articles'then 'shared/articles_by_articles'
|
||||
when 'groups' then 'shared/articles_by/groups'
|
||||
when 'articles' then 'shared/articles_by/articles'
|
||||
else 'articles'
|
||||
end
|
||||
|
||||
|
@ -120,13 +120,11 @@ class OrdersController < ApplicationController
|
|||
|
||||
def receive_on_order_article_create # See publish/subscribe design pattern in /doc.
|
||||
@order_article = OrderArticle.find(params[:order_article_id])
|
||||
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
def receive_on_order_article_update # See publish/subscribe design pattern in /doc.
|
||||
@order_article = OrderArticle.find(params[:order_article_id])
|
||||
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
|
|
@ -83,12 +83,14 @@ module ApplicationHelper
|
|||
# be overridden by the option 'desc'.
|
||||
# Other options are passed through to I18n.
|
||||
def heading_helper(model, attribute, options = {})
|
||||
i18nopts = options.select {|a| !['short', 'desc'].include?(a) }.merge({count: 2})
|
||||
i18nopts = {count: 2}.merge(options.select {|a| !['short', 'desc'].include?(a) })
|
||||
s = model.human_attribute_name(attribute, i18nopts)
|
||||
if options[:short]
|
||||
desc = (options[:desc] or model.human_attribute_name("#{attribute}_desc".to_sym, options.merge({fallback: true, default: '', count: 2})))
|
||||
desc = options[:desc]
|
||||
desc ||= model.human_attribute_name("#{attribute}_desc".to_sym, options.merge({fallback: true, default: '', count: 2}))
|
||||
desc.blank? and desc = s
|
||||
sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({fallback: true, default: '', count: 2}))
|
||||
s = raw "<abbr title='#{desc or s}'>#{sshort}</abbr>" unless sshort.blank?
|
||||
s = raw "<abbr title='#{desc}'>#{sshort}</abbr>" unless sshort.blank?
|
||||
end
|
||||
s
|
||||
end
|
||||
|
|
|
@ -5,9 +5,9 @@ module Finance::BalancingHelper
|
|||
when 'edit_results' then
|
||||
'edit_results_by_articles'
|
||||
when 'groups_overview' then
|
||||
'shared/articles_by_groups'
|
||||
'shared/articles_by/groups'
|
||||
when 'articles_overview' then
|
||||
'shared/articles_by_articles'
|
||||
'shared/articles_by/articles'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
app/helpers/group_order_articles_helper.rb
Normal file
14
app/helpers/group_order_articles_helper.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module GroupOrderArticlesHelper
|
||||
|
||||
# return an edit field for a GroupOrderArticle result
|
||||
def group_order_article_edit_result(goa)
|
||||
unless goa.group_order.order.finished? and current_user.role_finance?
|
||||
goa.result
|
||||
else
|
||||
simple_form_for goa, remote: true, html: {'data-submit-onchange' => 'changed', class: 'delta-input'} do |f|
|
||||
f.input_field :result, as: :delta, class: 'input-nano', data: {min: 0}, id: "r_#{goa.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -18,7 +18,7 @@ module OrdersHelper
|
|||
options_for_select(options)
|
||||
end
|
||||
|
||||
# "1 ordered units, 2 billed, 2 received"
|
||||
# "1×2 ordered, 2×2 billed, 2×2 received"
|
||||
def units_history_line(order_article, options={})
|
||||
if order_article.order.open?
|
||||
nil
|
||||
|
@ -26,10 +26,9 @@ module OrdersHelper
|
|||
units_info = []
|
||||
[:units_to_order, :units_billed, :units_received].map do |unit|
|
||||
if n = order_article.send(unit)
|
||||
i18nkey = if units_info.empty? and options[:plain] then unit else "#{unit}_short" end
|
||||
line = n.to_s + ' '
|
||||
line += pkg_helper(order_article.price) + ' ' unless options[:plain] or n == 0
|
||||
line += OrderArticle.human_attribute_name(i18nkey, count: n)
|
||||
line += pkg_helper(order_article.price, options) + ' ' unless n == 0
|
||||
line += OrderArticle.human_attribute_name("#{unit}_short", count: n)
|
||||
units_info << line
|
||||
end
|
||||
end
|
||||
|
@ -39,13 +38,16 @@ module OrdersHelper
|
|||
|
||||
# can be article or article_price
|
||||
# icon: `false` to not show the icon
|
||||
# plain: `true` to not use html (implies icon: false)
|
||||
# soft_uq: `true` to hide unit quantity specifier on small screens
|
||||
# sensible in tables with multiple columns calling `pkg_helper`
|
||||
def pkg_helper(article, options={})
|
||||
return '' if not article or article.unit_quantity == 1
|
||||
uq_text = "× #{article.unit_quantity}".html_safe
|
||||
uq_text = "× #{article.unit_quantity}"
|
||||
uq_text = content_tag(:span, uq_text, class: 'hidden-phone') if options[:soft_uq]
|
||||
if options[:icon].nil? or options[:icon]
|
||||
if options[:plain]
|
||||
uq_text
|
||||
elsif options[:icon].nil? or options[:icon]
|
||||
pkg_helper_icon(uq_text)
|
||||
else
|
||||
pkg_helper_icon(uq_text, tag: :span)
|
||||
|
|
24
app/inputs/delta_input.rb
Normal file
24
app/inputs/delta_input.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# encoding: utf-8
|
||||
|
||||
class DeltaInput < SimpleForm::Inputs::StringInput
|
||||
# for now, need to pass id or it won't work
|
||||
def input
|
||||
@input_html_options[:data] ||= {}
|
||||
@input_html_options[:data][:delta] ||= 1
|
||||
@input_html_options[:autocomplete] ||= 'off'
|
||||
# TODO get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option
|
||||
|
||||
template.content_tag :div, class: 'delta-input input-prepend input-append' do
|
||||
delta_button('−', -1) + super + delta_button('+', 1)
|
||||
end
|
||||
end
|
||||
#template.button_tag('−', type: :submit, data: {decrement: @input_html_options[:id]}, tabindex: -1, class: 'btn') +
|
||||
|
||||
private
|
||||
|
||||
def delta_button(title, direction)
|
||||
data = { (direction>0 ? 'increment' : 'decrement') => @input_html_options[:id] }
|
||||
delta = direction * @input_html_options[:data][:delta]
|
||||
template.button_tag(title, type: :button, name: 'delta', value: delta, data: data, tabindex: -1, class: 'btn')
|
||||
end
|
||||
end
|
|
@ -4,38 +4,31 @@
|
|||
%tr
|
||||
%td
|
||||
%td{:style => "width:8em"}= Ordergroup.model_name.human
|
||||
%td= t('.units')
|
||||
-#%td.center= t('.units')
|
||||
%td.center
|
||||
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
|
||||
%td= t('.total')
|
||||
%td{:colspan => "3",:style => "width:14em"}
|
||||
= link_to t('.add_group'), new_finance_group_order_article_path(order_article_id: order_article.id),
|
||||
= link_to t('.add_group'), new_group_order_article_path(order_article_id: order_article.id),
|
||||
remote: true, class: 'btn btn-mini'
|
||||
%tbody
|
||||
- totals = {result: 0}
|
||||
- for group_order_article in order_article.group_order_articles.select { |goa| goa.result > 0 }
|
||||
%tr[group_order_article]
|
||||
%td
|
||||
%td{:style=>"width:50%"}
|
||||
= group_order_article.group_order.ordergroup.name
|
||||
%td{:id => "group_order_article_#{group_order_article.id}_quantity", :style => "white-space:nowrap"}
|
||||
= group_order_article.result
|
||||
= link_to "+", update_result_finance_group_order_article_path(group_order_article, modifier: '+'),
|
||||
method: :put, remote: true, class: 'btn btn-mini'
|
||||
= link_to "-", update_result_finance_group_order_article_path(group_order_article, modifier: '-'),
|
||||
method: :put, remote: true, class: 'btn btn-mini'
|
||||
%td.numeric
|
||||
= number_to_currency(group_order_article.order_article.price.fc_price * group_order_article.result)
|
||||
%td.center= group_order_article_edit_result(group_order_article)
|
||||
%td.numeric= number_to_currency(group_order_article.order_article.price.fc_price * group_order_article.result)
|
||||
%td.actions{:style=>"width:1em"}
|
||||
= link_to t('ui.edit'), edit_finance_group_order_article_path(group_order_article), remote: true,
|
||||
class: 'btn btn-mini'
|
||||
%td.actions{:style=>"width:1em"}
|
||||
= link_to t('ui.delete'), finance_group_order_article_path(group_order_article), method: :delete,
|
||||
= link_to t('ui.delete'), group_order_article_path(group_order_article), method: :delete,
|
||||
remote: true, class: 'btn btn-mini btn-danger'
|
||||
%td
|
||||
- totals[:result] += group_order_article.result
|
||||
%tfoot
|
||||
%tr
|
||||
%td
|
||||
%td{:style => "width:8em"}= t('.total_fc')
|
||||
%td{:id => "group_orders_sum_quantity_#{order_article.id}"}
|
||||
= order_article.group_orders_sum[:quantity]
|
||||
%td.numeric{:id => "group_orders_sum_price_#{order_article.id}"}
|
||||
= number_to_currency(order_article.group_orders_sum[:price])
|
||||
%td.center= totals[:result]
|
||||
%td.numeric= number_to_currency(order_article.group_orders_sum[:price])
|
||||
%td{:colspan => "3"}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
$(function() {
|
||||
// Subscribe to database changes.
|
||||
// See publish/subscribe design pattern in /doc.
|
||||
$(document).on('OrderArticle#update', function(e) {
|
||||
$(document).on('OrderArticle#update GroupOrderArticle#create GroupOrderArticle#update', function(e) {
|
||||
$.ajax({
|
||||
url: '#{new_on_order_article_update_finance_order_path(@order)}',
|
||||
type: 'get',
|
||||
|
@ -21,6 +21,7 @@
|
|||
});
|
||||
});
|
||||
});
|
||||
= render 'shared/articles_by/common', order: @order
|
||||
|
||||
- title t('.title', name: @order.name)
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
$('#modalContainer').html('#{j(render("form"))}');
|
||||
$('#modalContainer').modal();
|
|
@ -1,2 +0,0 @@
|
|||
$('#modalContainer').html('#{j(render("form"))}');
|
||||
$('#modalContainer').modal();
|
|
@ -1,4 +0,0 @@
|
|||
$('#modalContainer').modal('hide');
|
||||
$('#order_article_#{@order_article.id}').html('#{j(render('finance/balancing/order_article', order_article: @order_article))}');
|
||||
$('#group_order_articles_#{@order_article.id}').html('#{j(render('finance/balancing/group_order_articles', order_article: @order_article))}');
|
||||
$('#summaryChangedWarning').show();
|
|
@ -1,11 +1,11 @@
|
|||
= simple_form_for [:finance, @group_order_article], remote: true do |form|
|
||||
= simple_form_for @group_order_article, remote: true do |form|
|
||||
= form.hidden_field :order_article_id
|
||||
.modal-header
|
||||
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
|
||||
%h3= t('.amount_change_for', article: @order_article.article.name)
|
||||
.modal-body
|
||||
= form.input :ordergroup_id, as: :select, collection: Ordergroup.all.map { |g| [g.name, g.id] }
|
||||
= form.input :result, hint: I18n.t('finance.group_order_articles.form.result_hint', unit: @order_article.article.unit) # Why do we need the full prefix?
|
||||
= form.input :result, hint: I18n.t('group_order_articles.form.result_hint', unit: @order_article.article.unit) # Why do we need the full prefix?
|
||||
.modal-footer
|
||||
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
|
||||
= form.submit t('ui.save'), class: 'btn btn-primary'
|
10
app/views/group_order_articles/create.js.erb
Normal file
10
app/views/group_order_articles/create.js.erb
Normal file
|
@ -0,0 +1,10 @@
|
|||
$('#modalContainer').modal('hide');
|
||||
|
||||
// trigger hooks for views
|
||||
$(document).trigger({
|
||||
type: 'GroupOrderArticle#create',
|
||||
order_article_id: <%= @group_order_article.order_article_id %>,
|
||||
group_order_id: <%= @group_order_article.group_order_id %>,
|
||||
group_order_article_id: <%= @group_order_article.id %>,
|
||||
group_order_article_price: <%= @group_order_article.total_price %>
|
||||
});
|
2
app/views/group_order_articles/new.js.erb
Normal file
2
app/views/group_order_articles/new.js.erb
Normal file
|
@ -0,0 +1,2 @@
|
|||
$('#modalContainer').html('<%= j render("form") %>');
|
||||
$('#modalContainer').modal();
|
8
app/views/group_order_articles/update.js.erb
Normal file
8
app/views/group_order_articles/update.js.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
// and trigger hooks for views including this
|
||||
$(document).trigger({
|
||||
type: 'GroupOrderArticle#update',
|
||||
order_article_id: <%= @group_order_article.order_article_id %>,
|
||||
group_order_id: <%= @group_order_article.group_order_id %>,
|
||||
group_order_article_id: <%= @group_order_article.id %>,
|
||||
group_order_article_price: <%= @group_order_article.total_price %>
|
||||
});
|
|
@ -98,3 +98,5 @@
|
|||
$(function() {
|
||||
activate_search('#{j @view}', '#{j t(".search_placeholder.#{@view}")}');
|
||||
});
|
||||
|
||||
= render 'shared/articles_by/common', order: @order
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
%table.table.table-hover.list
|
||||
%thead.list-heading
|
||||
%tr
|
||||
%th{:style => 'width:70%'}= t '.ordergroup'
|
||||
%th
|
||||
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
|
||||
%th
|
||||
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
|
||||
%th= t '.price'
|
||||
|
||||
- for order_article in order.order_articles.ordered.all(:include => [:article, :article_price])
|
||||
%tbody
|
||||
%tr
|
||||
%th.name{:colspan => "4"}
|
||||
= order_article.article.name
|
||||
= "(#{order_article.article.unit} | #{order_article.price.unit_quantity} | #{number_to_currency(order_article.price.gross_price)})"
|
||||
- for goa in order_article.group_order_articles.ordered
|
||||
%tr{:class => [cycle('even', 'odd', :name => 'groups'), if goa.result == 0 then 'unavailable' end]}
|
||||
%td{:style => "width:70%"}=h goa.group_order.ordergroup.name
|
||||
%td= "#{goa.quantity} + #{goa.tolerance}"
|
||||
%td
|
||||
%b= goa.result
|
||||
%td= number_to_currency(order_article.price.fc_price * goa.result)
|
||||
%tr
|
||||
%td(colspan="4" )
|
||||
- reset_cycle('groups')
|
|
@ -1,41 +0,0 @@
|
|||
%table.table.table-hover.list
|
||||
%thead.list-heading
|
||||
%tr
|
||||
%th{:style => "width:40%"}= heading_helper Article, :name
|
||||
%th= heading_helper Article, :unit
|
||||
%th.center
|
||||
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
|
||||
%th.center{colspan: 2}
|
||||
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
|
||||
%th{colspan: 2}= heading_helper Article, :fc_price, short: true
|
||||
%th= t '.price'
|
||||
%th= #heading_helper Article, :unit_quantity, short: true
|
||||
|
||||
- for group_order in order.group_orders.ordered
|
||||
%tbody
|
||||
%tr.list-heading
|
||||
%th{:colspan => "9"}
|
||||
%h4.name= group_order.ordergroup.name
|
||||
- total = 0
|
||||
- for goa in group_order.group_order_articles.ordered.all(:include => :order_article)
|
||||
- fc_price = goa.order_article.price.fc_price
|
||||
- subTotal = fc_price * goa.result
|
||||
- total += subTotal
|
||||
%tr{:class => [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end]}
|
||||
%td.name{:style => "width:40%"}=h goa.order_article.article.name
|
||||
%td= goa.order_article.article.unit
|
||||
%td.center= "#{goa.quantity} + #{goa.tolerance}"
|
||||
%td.center
|
||||
%b= goa.result
|
||||
%td.symbol ×
|
||||
%td= number_to_currency(fc_price)
|
||||
%td.symbol =
|
||||
%td= number_to_currency(subTotal)
|
||||
%td= pkg_helper goa.order_article.price
|
||||
%tr{:class => cycle('even', 'odd', :name => 'articles')}
|
||||
%th{:colspan => "7"}= t '.price_sum'
|
||||
%th= number_to_currency(total)
|
||||
%th
|
||||
%tr
|
||||
%th(colspan="9")
|
||||
- reset_cycle("articles")
|
11
app/views/shared/articles_by/_article_single.html.haml
Normal file
11
app/views/shared/articles_by/_article_single.html.haml
Normal file
|
@ -0,0 +1,11 @@
|
|||
%tbody{id: "oa_#{order_article.id}"}
|
||||
- if not defined?(heading) or heading
|
||||
%tr.list-heading
|
||||
%th.name{:colspan => "4"}>
|
||||
= order_article.article.name + ' '
|
||||
= "(#{order_article.article.unit}, #{number_to_currency order_article.price.fc_price}"
|
||||
- pkg_info = pkg_helper(order_article.price)
|
||||
= ", #{pkg_info}".html_safe unless pkg_info.blank?
|
||||
)
|
||||
- for goa in order_article.group_order_articles.ordered
|
||||
= render 'shared/articles_by/article_single_goa', goa: goa, edit: (edit rescue nil)
|
|
@ -0,0 +1,5 @@
|
|||
%tr{class: if goa.result == 0 then 'unavailable' end, id: "goa_#{goa.id}"}
|
||||
%td{:style => "width:70%"}= goa.group_order.ordergroup.name
|
||||
%td.center= "#{goa.quantity} + #{goa.tolerance}"
|
||||
%td.center.input-delta= (edit or true rescue true) ? group_order_article_edit_result(goa) : goa.result
|
||||
%td.price{data: {value: goa.total_price}}= number_to_currency(goa.total_price)
|
14
app/views/shared/articles_by/_articles.html.haml
Normal file
14
app/views/shared/articles_by/_articles.html.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
%table.table.table-hover.list#articles_by_articles
|
||||
%thead.list-heading
|
||||
%tr
|
||||
%th{:style => 'width:70%'}= Ordergroup.model_name.human
|
||||
%th.center
|
||||
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
|
||||
%th.center
|
||||
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
|
||||
%th= t 'shared.articles_by.price'
|
||||
|
||||
- for order_article in order.order_articles.ordered.all(:include => [:article, :article_price])
|
||||
= render 'shared/articles_by/article_single', order_article: order_article, edit: (edit rescue nil)
|
||||
%tr
|
||||
%td{colspan: 4}
|
30
app/views/shared/articles_by/_common.html.haml
Normal file
30
app/views/shared/articles_by/_common.html.haml
Normal file
|
@ -0,0 +1,30 @@
|
|||
-# common javascript for updating articles_by views
|
||||
-# include this in all pages that use articles_by views (directly or via ajax)
|
||||
= content_for :javascript do
|
||||
:javascript
|
||||
$(document).on('GroupOrderArticle#update', function(e) {
|
||||
|
||||
var el_goa = $('#goa_'+e.group_order_article_id);
|
||||
|
||||
// update total price of group_order_article
|
||||
// show localised value, store raw number in data attribute
|
||||
var el_price = $('.price', el_goa);
|
||||
var old_price = el_price.data('value');
|
||||
if (el_price.length) {
|
||||
el_price.text(I18n.l('currency', e.group_order_article_price));
|
||||
el_price.data('value', e.group_order_article_price);
|
||||
}
|
||||
|
||||
// group_order_article is greyed when result==0
|
||||
el_goa.toggleClass('unavailable', $('input#r_'+e.group_order_article_id, el_goa).val()==0);
|
||||
|
||||
// update total price of group_order, order_article and/or ordergroup, when present
|
||||
var el_sum = $('#group_order_'+e.group_order_id+', #single_ordergroup_total, #single_order_article_total');
|
||||
var el_price_sum = $('.price_sum', el_sum);
|
||||
if (el_price_sum.length) {
|
||||
var old_price_sum = el_price_sum.data('value');
|
||||
var new_price_sum = old_price_sum - old_price + e.group_order_article_price;
|
||||
el_price_sum.text(I18n.l('currency', new_price_sum));
|
||||
el_price_sum.data('value', new_price_sum);
|
||||
}
|
||||
});
|
15
app/views/shared/articles_by/_group_single.html.haml
Normal file
15
app/views/shared/articles_by/_group_single.html.haml
Normal file
|
@ -0,0 +1,15 @@
|
|||
%tbody{id: "group_order_#{group_order.id}"}
|
||||
- if not defined?(heading) or heading
|
||||
%tr.list-heading
|
||||
%th{colspan: 9}
|
||||
%h4.name= group_order.ordergroup.name
|
||||
- total = 0
|
||||
- for goa in group_order.group_order_articles.ordered.all(:include => :order_article)
|
||||
- total += goa.total_price
|
||||
= render 'shared/articles_by/group_single_goa', goa: goa, edit: (edit rescue nil)
|
||||
%tr{class: cycle('even', 'odd', :name => 'articles')}
|
||||
%th{colspan: 7}= t 'shared.articles_by.price_sum'
|
||||
%th.price_sum{colspan: 2, data: {value: total}}= number_to_currency(total)
|
||||
%tr
|
||||
%th{colspan: 9}
|
||||
- reset_cycle("articles")
|
11
app/views/shared/articles_by/_group_single_goa.html.haml
Normal file
11
app/views/shared/articles_by/_group_single_goa.html.haml
Normal file
|
@ -0,0 +1,11 @@
|
|||
%tr{class: [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end], id: "goa_#{goa.id}"}
|
||||
%td.name= goa.order_article.article.name
|
||||
%td= goa.order_article.article.unit
|
||||
%td.center= "#{goa.quantity} + #{goa.tolerance}"
|
||||
%td.center.input-delta= (edit or true rescue true) ? group_order_article_edit_result(goa) : goa.result
|
||||
%td.symbol ×
|
||||
%td= number_to_currency(goa.order_article.price.fc_price)
|
||||
%td.symbol =
|
||||
%td.price{data: {value: goa.total_price}}= number_to_currency(goa.total_price)
|
||||
%td= pkg_helper goa.order_article.price
|
||||
|
17
app/views/shared/articles_by/_groups.html.haml
Normal file
17
app/views/shared/articles_by/_groups.html.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%table.table.table-hover.list#articles_by_groups
|
||||
%thead.list-heading
|
||||
%tr
|
||||
%th{:style => "width:40%"}= heading_helper Article, :name
|
||||
%th= heading_helper Article, :unit
|
||||
%th.center
|
||||
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
|
||||
%th.center
|
||||
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
|
||||
%th.symbol
|
||||
%th= heading_helper Article, :fc_price, short: true
|
||||
%th.symbol
|
||||
%th= t 'shared.articles_by.price'
|
||||
%th= #heading_helper Article, :unit_quantity, short: true
|
||||
|
||||
- for group_order in order.group_orders.ordered
|
||||
= render 'shared/articles_by/group_single', group_order: group_order, edit: (edit rescue nil)
|
|
@ -600,10 +600,6 @@ en:
|
|||
ordergroup:
|
||||
remove: Remove
|
||||
remove_group: Remove group
|
||||
group_order_articles:
|
||||
form:
|
||||
amount_change_for: Change amount for %{article}
|
||||
result_hint: ! 'Unit: %{unit}'
|
||||
index:
|
||||
amount_fc: Amount(FC)
|
||||
end: End
|
||||
|
@ -737,6 +733,10 @@ en:
|
|||
error_general: The order couldn’t be updated due to a bug.
|
||||
error_stale: Someone else has ordered in the meantime, couldn't update the order.
|
||||
notice: The order was saved.
|
||||
group_order_articles:
|
||||
form:
|
||||
amount_change_for: Change amount for %{article}
|
||||
result_hint: ! 'Unit: %{unit}'
|
||||
helpers:
|
||||
application:
|
||||
edit_user: Edit user
|
||||
|
@ -1294,14 +1294,9 @@ en:
|
|||
ordered_desc: Number of articles as ordered by member (amount + tolerance)
|
||||
received: Received
|
||||
received_desc: Number of articles that (will be) received by member
|
||||
articles_by_articles:
|
||||
ordergroup: Ordergroup
|
||||
price: Total price
|
||||
articles_by_groups:
|
||||
articles_by:
|
||||
price: Total price
|
||||
price_sum: Sum
|
||||
unit_quantity: Lot quantity
|
||||
unit_quantity_desc: How many units per lot.
|
||||
group:
|
||||
access: Access to
|
||||
activated: activated
|
||||
|
|
|
@ -53,6 +53,8 @@ Foodsoft::Application.routes.draw do
|
|||
get :archive, :on => :collection
|
||||
end
|
||||
|
||||
resources :group_order_articles
|
||||
|
||||
resources :order_comments, :only => [:new, :create]
|
||||
|
||||
############ Foodcoop orga
|
||||
|
@ -155,12 +157,6 @@ Foodsoft::Application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :group_order_articles do
|
||||
member do
|
||||
put :update_result
|
||||
end
|
||||
end
|
||||
|
||||
resources :invoices
|
||||
|
||||
resources :ordergroups, :only => [:index] do
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative '../spec_helper'
|
|||
|
||||
describe 'settling an order', :type => :feature do
|
||||
let(:admin) { create :user, groups:[create(:workgroup, role_finance: true)] }
|
||||
let(:user) { create :user, groups:[create(:ordergroup)] }
|
||||
let(:supplier) { create :supplier }
|
||||
let(:article) { create :article, supplier: supplier, unit_quantity: 1 }
|
||||
let(:order) { create :order, supplier: supplier, article_ids: [article.id] } # need to ref article
|
||||
|
@ -43,10 +44,10 @@ describe 'settling an order', :type => :feature do
|
|||
expect(page).to have_content(go1.ordergroup.name)
|
||||
expect(page).to have_content(go2.ordergroup.name)
|
||||
# and that their order results match what we expect
|
||||
expect(page).to have_selector("#group_order_article_#{goa1.id}_quantity")
|
||||
expect(find("#group_order_article_#{goa1.id}_quantity").text.to_f).to eq(3)
|
||||
expect(page).to have_selector("#group_order_article_#{goa2.id}_quantity")
|
||||
expect(find("#group_order_article_#{goa2.id}_quantity").text.to_f).to eq(1)
|
||||
expect(page).to have_selector("#r_#{goa1.id}")
|
||||
expect(find("#r_#{goa1.id}").value.to_f).to eq(3)
|
||||
expect(page).to have_selector("#r_#{goa2.id}")
|
||||
expect(find("#r_#{goa2.id}").value.to_f).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -120,6 +121,48 @@ describe 'settling an order', :type => :feature do
|
|||
expect(oa.units_to_order).to eq(0)
|
||||
end
|
||||
|
||||
it 'can add an ordergroup to an order article' do
|
||||
user # need to reference user before "new article" dialog is loaded
|
||||
click_link article.name
|
||||
within("#group_order_articles_#{oa.id}") do
|
||||
click_link I18n.t('finance.balancing.group_order_articles.add_group')
|
||||
end
|
||||
expect(page).to have_selector('form#new_group_order_article')
|
||||
within('#new_group_order_article') do
|
||||
select user.ordergroup.name, :from => 'group_order_article_ordergroup_id'
|
||||
fill_in 'group_order_article_result', :with => 8
|
||||
find('input[type="submit"]').click
|
||||
end
|
||||
expect(page).to have_content(user.ordergroup.name)
|
||||
goa = GroupOrderArticle.last
|
||||
expect(goa).to_not be_nil
|
||||
expect(goa.result).to eq 8
|
||||
expect(page).to have_selector("#group_order_article_#{goa.id}")
|
||||
expect(find("#r_#{goa.id}").value.to_f).to eq 8
|
||||
end
|
||||
|
||||
it 'can modify an ordergroup result' do
|
||||
click_link article.name
|
||||
within("#group_order_articles_#{oa.id}") do
|
||||
fill_in "r_#{goa1.id}", :with => 5
|
||||
# leave input box and wait a bit so that update is sent using ajax
|
||||
find("#r_#{goa1.id}").native.send_keys :tab
|
||||
sleep 1
|
||||
end
|
||||
expect(goa1.reload.result).to eq 5
|
||||
expect(find("#group_order_articles_#{oa.id} tfoot td:nth-child(3)").text.to_f).to eq 6
|
||||
end
|
||||
|
||||
it 'can modify an ordergroup result using the + button' do
|
||||
click_link article.name
|
||||
within("#group_order_article_#{goa1.id}") do
|
||||
4.times { find('button[data-increment]').click }
|
||||
sleep 1
|
||||
end
|
||||
expect(goa1.reload.result).to eq 7
|
||||
expect(find("#group_order_articles_#{oa.id} tfoot td:nth-child(3)").text.to_f).to eq 8
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// jquery.observe_field.js
|
||||
|
||||
|
||||
(function( $ ){
|
||||
|
||||
jQuery.fn.observe_field = function(frequency, callback) {
|
||||
|
||||
frequency = frequency * 1000; // translate to milliseconds
|
||||
|
||||
return this.each(function(){
|
||||
var $this = $(this);
|
||||
var prev = $this.val();
|
||||
|
||||
var check = function() {
|
||||
var val = $this.val();
|
||||
if(prev != val){
|
||||
prev = val;
|
||||
$this.map(callback); // invokes the callback on $this
|
||||
}
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
if(ti){
|
||||
clearInterval(ti);
|
||||
ti = setInterval(check, frequency);
|
||||
}
|
||||
};
|
||||
|
||||
check();
|
||||
var ti = setInterval(check, frequency); // invoke check periodically
|
||||
|
||||
// reset counter after user interaction
|
||||
$this.bind('keyup click mousemove', reset); //mousemove is for selects
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
})( jQuery );
|
||||
|
Loading…
Reference in a new issue