Merge pull request #222 from wvengen/feature-receive
New receive screen
This commit is contained in:
commit
8b4c292ea0
52 changed files with 1168 additions and 182 deletions
BIN
app/assets/images/package-bg.png
Normal file
BIN
app/assets/images/package-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 941 B |
BIN
app/assets/images/package.png
Normal file
BIN
app/assets/images/package.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -107,6 +107,11 @@ $(function() {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Disable action of disabled buttons
|
||||||
|
$(document).on('click', 'a.disabled', function() {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
// Show and hide loader on ajax callbacks
|
// Show and hide loader on ajax callbacks
|
||||||
$('*[data-remote]').bind('ajax:beforeSend', function() {
|
$('*[data-remote]').bind('ajax:beforeSend', function() {
|
||||||
$('#loader').show();
|
$('#loader').show();
|
||||||
|
|
|
@ -30,7 +30,19 @@ body {
|
||||||
// Example:
|
// Example:
|
||||||
// @linkColor: #ff0000;
|
// @linkColor: #ff0000;
|
||||||
|
|
||||||
// Custom styles
|
|
||||||
|
// main ui colours
|
||||||
|
@mainRedColor: #ED0606;
|
||||||
|
|
||||||
|
// article status
|
||||||
|
@articleUsedColor: green;
|
||||||
|
@articleUnusedColor: red;
|
||||||
|
@articleUnavailColor: #999;
|
||||||
|
@articleUpdatedColor: #468847;
|
||||||
|
|
||||||
|
// dim colors by this amount when the information is less important
|
||||||
|
@nonessentialDim: 35%;
|
||||||
|
|
||||||
|
|
||||||
// Fix empty dd tags in horizontal dl, see https://github.com/twitter/bootstrap/issues/4062
|
// Fix empty dd tags in horizontal dl, see https://github.com/twitter/bootstrap/issues/4062
|
||||||
.dl-horizontal {
|
.dl-horizontal {
|
||||||
|
@ -39,7 +51,8 @@ body {
|
||||||
|
|
||||||
// Do not use additional margin for input in table
|
// Do not use additional margin for input in table
|
||||||
.form-horizontal .control-group.control-group-intable,
|
.form-horizontal .control-group.control-group-intable,
|
||||||
.form-horizontal .controls.controls-intable {
|
.form-horizontal .controls.controls-intable,
|
||||||
|
.input-prepend.intable {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +66,6 @@ body {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mainRedColor: #ED0606;
|
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
margin: 10px 0 0 30px;
|
margin: 10px 0 0 30px;
|
||||||
|
@ -134,11 +146,11 @@ table {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ordering
|
// ordering
|
||||||
span.used {
|
.used {
|
||||||
color: green;
|
color: @articleUsedColor;
|
||||||
}
|
}
|
||||||
span.unused {
|
.unused {
|
||||||
color: red;
|
color: @articleUnusedColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
#order-footer, .article-info {
|
#order-footer, .article-info {
|
||||||
|
@ -202,11 +214,11 @@ tr.order-article:hover .article-info {
|
||||||
// ********* Articles
|
// ********* Articles
|
||||||
|
|
||||||
tr.just-updated {
|
tr.just-updated {
|
||||||
color: #468847;
|
color: @articleUpdatedColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.unavailable {
|
tr.unavailable {
|
||||||
color: #999;
|
color: @articleUnavailColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// articles edit all
|
// articles edit all
|
||||||
|
@ -216,6 +228,16 @@ tr.unavailable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// editable article list can be more compact
|
||||||
|
.ordered-articles input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow content to appear without sudden table change (receive)
|
||||||
|
.units_delta {
|
||||||
|
min-width: 3.5em;
|
||||||
|
}
|
||||||
|
|
||||||
// ********* Tweaks & fixes
|
// ********* Tweaks & fixes
|
||||||
|
|
||||||
// Fix bootstrap dropdown menu on mobile
|
// Fix bootstrap dropdown menu on mobile
|
||||||
|
@ -258,11 +280,60 @@ table.table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it's a bit distracting
|
||||||
|
.icon-asterisk {
|
||||||
|
font-size: 80%;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-bottom: 0.4ex;
|
||||||
|
}
|
||||||
|
|
||||||
// allow buttons as input add-on (with proper height)
|
// allow buttons as input add-on (with proper height)
|
||||||
.input-append button.add-on {
|
.input-append button.add-on {
|
||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inline form elements
|
||||||
|
.inline {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show package icon after amount of wholesale units
|
||||||
|
.package-image (@align) {
|
||||||
|
background-image: url(package-bg.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: @align center;
|
||||||
|
}
|
||||||
|
input.package {
|
||||||
|
.package-image(right);
|
||||||
|
// disabled and readonly definitions though
|
||||||
|
&[disabled], &[readonly] {
|
||||||
|
background-color: @inputDisabledBackground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.package {
|
||||||
|
.package-image(left);
|
||||||
|
min-width: 18px;
|
||||||
|
min-height: 18px;
|
||||||
|
vertical-align: baseline;
|
||||||
|
font-style: normal;
|
||||||
|
padding-left: 20px;
|
||||||
|
@media (max-width: 979px) { padding-left: 0; }
|
||||||
|
}
|
||||||
|
i.package.icon-only {
|
||||||
|
padding-left: 6px;
|
||||||
|
background-position: right;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.package { color: tint(@textColor, @nonessentialDim); }
|
||||||
|
.used .package { color: tint(@articleUsedColor, @nonessentialDim); }
|
||||||
|
.unused .package { color: tint(@articleUnusedColor, @nonessentialDim); }
|
||||||
|
.unavailable .package { color: @articleUnavailColor; }
|
||||||
|
|
||||||
|
// very small inputs - need !important for responsive selectors
|
||||||
|
.input-nano {
|
||||||
|
width: 30px !important;
|
||||||
|
}
|
||||||
|
|
||||||
// get rid of extra space on bottom of dialog with form
|
// get rid of extra space on bottom of dialog with form
|
||||||
.modal form {
|
.modal form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -297,6 +368,13 @@ table.table {
|
||||||
float: none;
|
float: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// allow to add a hint for the whole line
|
||||||
|
> .help-block {
|
||||||
|
clear: both;
|
||||||
|
margin-left: 180px;
|
||||||
|
position: relative;
|
||||||
|
top: -2.5ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// allow to have indicator text instead of input with same markup
|
// allow to have indicator text instead of input with same markup
|
||||||
|
@ -304,3 +382,8 @@ table.table {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unlock button same size as warning sign
|
||||||
|
.input-prepend button.unlocker {
|
||||||
|
padding-right: 6px;
|
||||||
|
padding-left: 7px;
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ class ApplicationController < ActionController::Base
|
||||||
when "article_meta" then current_user.role_article_meta?
|
when "article_meta" then current_user.role_article_meta?
|
||||||
when "suppliers" then current_user.role_suppliers?
|
when "suppliers" then current_user.role_suppliers?
|
||||||
when "orders" then current_user.role_orders?
|
when "orders" then current_user.role_orders?
|
||||||
|
when "finance_or_orders" then (current_user.role_finance? || current_user.role_orders?)
|
||||||
when "any" then true # no role required
|
when "any" then true # no role required
|
||||||
else false # any unknown role will always fail
|
else false # any unknown role will always fail
|
||||||
end
|
end
|
||||||
|
@ -78,6 +79,10 @@ class ApplicationController < ActionController::Base
|
||||||
authenticate('orders')
|
authenticate('orders')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_finance_or_orders
|
||||||
|
authenticate('finance_or_orders')
|
||||||
|
end
|
||||||
|
|
||||||
# checks if the current_user is member of given group.
|
# checks if the current_user is member of given group.
|
||||||
# if fails the user will redirected to startpage
|
# if fails the user will redirected to startpage
|
||||||
def authenticate_membership_or_admin(group_id = params[:id])
|
def authenticate_membership_or_admin(group_id = params[:id])
|
||||||
|
|
|
@ -30,6 +30,18 @@ class Finance::BalancingController < Finance::BaseController
|
||||||
render layout: false if request.xhr?
|
render layout: false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_on_order_article_create # See publish/subscribe design pattern in /doc.
|
||||||
|
@order_article = OrderArticle.find(params[:order_article_id])
|
||||||
|
|
||||||
|
render :layout => false
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_on_order_article_update # See publish/subscribe design pattern in /doc.
|
||||||
|
@order_article = OrderArticle.find(params[:order_article_id])
|
||||||
|
|
||||||
|
render :layout => false
|
||||||
|
end
|
||||||
|
|
||||||
def update_summary
|
def update_summary
|
||||||
@order = Order.find(params[:id])
|
@order = Order.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
class Finance::OrderArticlesController < ApplicationController
|
class OrderArticlesController < ApplicationController
|
||||||
|
|
||||||
before_filter :authenticate_finance
|
before_filter :authenticate_finance_or_orders
|
||||||
|
|
||||||
layout false # We only use this controller to serve js snippets, no need for layout rendering
|
layout false # We only use this controller to serve js snippets, no need for layout rendering
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@order = Order.find(params[:order_id])
|
@order = Order.find(params[:order_id])
|
||||||
@order_article = @order.order_articles.build
|
@order_article = @order.order_articles.build(params[:order_article])
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@order = Order.find(params[:order_id])
|
@order = Order.find(params[:order_id])
|
||||||
# The article may with zero units ordered - in that case find and set amount to nonzero.
|
# The article may be ordered with zero units - in that case do not complain.
|
||||||
# If order_article is ordered and a new order_article is created, an error message will be
|
# If order_article is ordered and a new order_article is created, an error message will be
|
||||||
# given mentioning that the article already exists, which is desired.
|
# given mentioning that the article already exists, which is desired.
|
||||||
@order_article = @order.order_articles.where(:article_id => params[:order_article][:article_id]).first
|
@order_article = @order.order_articles.where(:article_id => params[:order_article][:article_id]).first
|
||||||
if @order_article and @order_article.units_to_order == 0
|
unless (@order_article and @order_article.units_to_order == 0)
|
||||||
@order_article.units_to_order = 1
|
|
||||||
else
|
|
||||||
@order_article = @order.order_articles.build(params[:order_article])
|
@order_article = @order.order_articles.build(params[:order_article])
|
||||||
end
|
end
|
||||||
unless @order_article.save
|
@order_article.save!
|
||||||
|
rescue
|
||||||
render action: :new
|
render action: :new
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@order = Order.find(params[:order_id])
|
@order = Order.find(params[:order_id])
|
|
@ -8,7 +8,8 @@ class OrdersController < ApplicationController
|
||||||
|
|
||||||
# List orders
|
# List orders
|
||||||
def index
|
def index
|
||||||
@open_orders = Order.open
|
@open_orders = Order.open.includes(:supplier)
|
||||||
|
@finished_orders = Order.finished_not_closed.includes(:supplier)
|
||||||
@per_page = 15
|
@per_page = 15
|
||||||
if params['sort']
|
if params['sort']
|
||||||
sort = case params['sort']
|
sort = case params['sort']
|
||||||
|
@ -20,7 +21,7 @@ class OrdersController < ApplicationController
|
||||||
else
|
else
|
||||||
sort = "ends DESC"
|
sort = "ends DESC"
|
||||||
end
|
end
|
||||||
@orders = Order.page(params[:page]).per(@per_page).order(sort).where("state != 'open'").includes(:supplier)
|
@orders = Order.closed.page(params[:page]).per(@per_page).includes(:supplier).order(sort)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gives a view for the results to a specific order
|
# Gives a view for the results to a specific order
|
||||||
|
@ -105,6 +106,29 @@ class OrdersController < ApplicationController
|
||||||
redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message)
|
redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def receive
|
||||||
|
@order = Order.find(params[:id])
|
||||||
|
unless request.post?
|
||||||
|
@order_articles = @order.order_articles.ordered.includes(:article)
|
||||||
|
else
|
||||||
|
s = update_order_amounts
|
||||||
|
flash[:notice] = (s ? I18n.t('orders.receive.notice', :msg => s) : I18n.t('orders.receive.notice_none'))
|
||||||
|
redirect_to @order
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Renders the fax-text-file
|
# Renders the fax-text-file
|
||||||
|
@ -131,4 +155,46 @@ class OrdersController < ApplicationController
|
||||||
end
|
end
|
||||||
text
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_order_amounts
|
||||||
|
return if not params[:order_articles]
|
||||||
|
# where to leave remainder during redistribution
|
||||||
|
rest_to = []
|
||||||
|
rest_to << :tolerance if params[:rest_to_tolerance]
|
||||||
|
rest_to << :stock if params[:rest_to_stock]
|
||||||
|
rest_to << nil
|
||||||
|
# count what happens to the articles:
|
||||||
|
# changed, rest_to_tolerance, rest_to_stock, left_over
|
||||||
|
counts = [0] * 4
|
||||||
|
cunits = [0] * 4
|
||||||
|
OrderArticle.transaction do
|
||||||
|
params[:order_articles].each do |oa_id, oa_params|
|
||||||
|
unless oa_params.blank?
|
||||||
|
oa = OrderArticle.find(oa_id)
|
||||||
|
# update attributes; don't use update_attribute because it calls save
|
||||||
|
# which makes received_changed? not work anymore
|
||||||
|
oa.attributes = oa_params
|
||||||
|
if oa.units_received_changed?
|
||||||
|
counts[0] += 1
|
||||||
|
unless oa.units_received.blank?
|
||||||
|
cunits[0] += oa.units_received * oa.article.unit_quantity
|
||||||
|
oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to
|
||||||
|
oacounts.each_with_index {|c,i| cunits[i+1]+=c; counts[i+1]+=1 if c>0 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
oa.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil if counts[0] == 0
|
||||||
|
notice = []
|
||||||
|
notice << I18n.t('orders.update_order_amounts.msg1', count: counts[0], units: cunits[0])
|
||||||
|
notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1], units: cunits[1]) if params[:rest_to_tolerance]
|
||||||
|
notice << I18n.t('orders.update_order_amounts.msg3', count: counts[2], units: cunits[2]) if params[:rest_to_stock]
|
||||||
|
if counts[3]>0 or cunits[3]>0
|
||||||
|
notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3], units: cunits[3])
|
||||||
|
end
|
||||||
|
notice.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,10 +81,10 @@ module ApplicationHelper
|
||||||
# heading, with an abbreviation title of 'foo'.
|
# heading, with an abbreviation title of 'foo'.
|
||||||
# Other options are passed through to I18n.
|
# Other options are passed through to I18n.
|
||||||
def heading_helper(model, attribute, options = {})
|
def heading_helper(model, attribute, options = {})
|
||||||
i18nopts = options.select {|a| !['short'].include?(a) }
|
i18nopts = options.select {|a| !['short'].include?(a) }.merge({count: 2})
|
||||||
s = model.human_attribute_name(attribute, i18nopts)
|
s = model.human_attribute_name(attribute, i18nopts)
|
||||||
if options[:short]
|
if options[:short]
|
||||||
sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({fallback: true, default: ''}))
|
sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({fallback: true, default: '', count: 2}))
|
||||||
s = raw "<abbr title='#{s}'>#{sshort}</abbr>" unless sshort.blank?
|
s = raw "<abbr title='#{s}'>#{sshort}</abbr>" unless sshort.blank?
|
||||||
end
|
end
|
||||||
s
|
s
|
||||||
|
|
|
@ -10,12 +10,17 @@ module DeliveriesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def articles_for_select2(supplier)
|
def articles_for_select2(articles, except = [], &block)
|
||||||
supplier.articles.undeleted.reorder('articles.name ASC').map {|a| {:id => a.id, :text => "#{a.name} (#{number_to_currency a.price}/#{a.unit})"} }
|
articles = articles.reorder('articles.name ASC')
|
||||||
|
articles.reject! {|a| not except.index(a.id).nil? } if except
|
||||||
|
block_given? or block = Proc.new {|a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" }
|
||||||
|
articles.map do |a|
|
||||||
|
{:id => a.id, :text => block.call(a)}
|
||||||
|
end.unshift({:id => '', :text => ''})
|
||||||
end
|
end
|
||||||
|
|
||||||
def stock_articles_for_table(supplier)
|
def articles_for_table(articles)
|
||||||
supplier.stock_articles.undeleted.reorder('articles.name ASC')
|
articles.undeleted.reorder('articles.name ASC')
|
||||||
end
|
end
|
||||||
|
|
||||||
def stock_change_remove_link(stock_change_form)
|
def stock_change_remove_link(stock_change_form)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module Finance::OrderArticlesHelper
|
module OrderArticlesHelper
|
||||||
|
|
||||||
def new_order_articles_collection
|
def new_order_articles_collection
|
||||||
if @order.stockit?
|
if @order.stockit?
|
|
@ -15,4 +15,67 @@ module OrdersHelper
|
||||||
options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: 0)]]
|
options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: 0)]]
|
||||||
options_for_select(options)
|
options_for_select(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def units_history_line(order_article)
|
||||||
|
if order_article.order.open?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
units_info = "#{order_article.units_to_order} #{OrderArticle.human_attribute_name :units_to_order, count: order_article.units_to_order}"
|
||||||
|
units_info += ", #{order_article.units_billed} #{OrderArticle.human_attribute_name :units_billed_short, count: order_article.units_billed}" unless order_article.units_billed.nil?
|
||||||
|
units_info += ", #{order_article.units_received} #{OrderArticle.human_attribute_name :units_received_short, count: order_article.units_received}" unless order_article.units_received.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# can be article or article_price
|
||||||
|
# icon: `false` to not show the icon
|
||||||
|
# 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 nil if article.unit_quantity == 1
|
||||||
|
uq_text = "× #{article.unit_quantity}".html_safe
|
||||||
|
uq_text = content_tag(:span, uq_text, class: 'hidden-phone') if options[:soft_uq]
|
||||||
|
if options[:icon].nil? or options[:icon]
|
||||||
|
pkg_helper_icon(uq_text)
|
||||||
|
else
|
||||||
|
pkg_helper_icon(uq_text, tag: :span)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def pkg_helper_icon(c=nil, options={})
|
||||||
|
options = {tag: 'i', class: ''}.merge(options)
|
||||||
|
if c.nil?
|
||||||
|
c = " ".html_safe
|
||||||
|
options[:class] += " icon-only"
|
||||||
|
end
|
||||||
|
content_tag(options[:tag], c, class: "package #{options[:class]}").html_safe
|
||||||
|
end
|
||||||
|
|
||||||
|
def article_price_change_hint(order_article, gross=false)
|
||||||
|
return nil if order_article.article.price == order_article.price.price
|
||||||
|
title = "#{t('helpers.orders.old_price')}: #{number_to_currency order_article.article.price}"
|
||||||
|
title += " / #{number_to_currency order_article.article.gross_price}" if gross
|
||||||
|
content_tag(:i, nil, class: 'icon-asterisk', title: j(title)).html_safe
|
||||||
|
end
|
||||||
|
|
||||||
|
def receive_input_field(form)
|
||||||
|
order_article = form.object
|
||||||
|
units_expected = (order_article.units_billed or order_article.units_to_order) *
|
||||||
|
1.0 * order_article.article.unit_quantity / order_article.article_price.unit_quantity
|
||||||
|
|
||||||
|
input_classes = 'input input-nano units_received'
|
||||||
|
input_classes += ' package' unless order_article.article_price.unit_quantity == 1
|
||||||
|
input_html = form.text_field :units_received, class: input_classes,
|
||||||
|
data: {'units-expected' => units_expected},
|
||||||
|
disabled: order_article.result_manually_changed?,
|
||||||
|
autocomplete: 'off'
|
||||||
|
|
||||||
|
if order_article.result_manually_changed?
|
||||||
|
input_html = content_tag(:span, class: 'input-prepend intable', title: t('.field_locked_title', default: '')) {
|
||||||
|
button_tag(nil, type: :button, class: 'btn unlocker') {
|
||||||
|
content_tag(:i, nil, class: 'icon icon-unlock')
|
||||||
|
} + input_html
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
input_html.html_safe
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -99,12 +99,16 @@ class GroupOrderArticle < ActiveRecord::Base
|
||||||
# Returns a hash with three keys: :quantity / :tolerance / :total
|
# Returns a hash with three keys: :quantity / :tolerance / :total
|
||||||
#
|
#
|
||||||
# See description of the ordering algorithm in the general application documentation for details.
|
# See description of the ordering algorithm in the general application documentation for details.
|
||||||
def calculate_result
|
def calculate_result(total = nil)
|
||||||
@calculate_result ||= begin
|
# return memoized result unless a total is given
|
||||||
|
return @calculate_result if total.nil? and not @calculate_result.nil?
|
||||||
|
|
||||||
quantity = tolerance = total_quantity = 0
|
quantity = tolerance = total_quantity = 0
|
||||||
|
|
||||||
# Get total
|
# Get total
|
||||||
if order_article.article.is_a?(StockArticle)
|
if not total.nil?
|
||||||
|
logger.debug "<#{order_article.article.name}> => #{total} (given)"
|
||||||
|
elsif order_article.article.is_a?(StockArticle)
|
||||||
total = order_article.article.quantity
|
total = order_article.article.quantity
|
||||||
logger.debug "<#{order_article.article.name}> (stock) => #{total}"
|
logger.debug "<#{order_article.article.name}> (stock) => #{total}"
|
||||||
else
|
else
|
||||||
|
@ -148,8 +152,10 @@ class GroupOrderArticle < ActiveRecord::Base
|
||||||
logger.debug "determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}"
|
logger.debug "determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}"
|
||||||
end
|
end
|
||||||
|
|
||||||
{:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance}
|
# memoize result unless a total is given
|
||||||
end
|
r = {:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance}
|
||||||
|
@calculate_result = r if total.nil?
|
||||||
|
r
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns order result,
|
# Returns order result,
|
||||||
|
@ -159,9 +165,11 @@ class GroupOrderArticle < ActiveRecord::Base
|
||||||
self[:result] || calculate_result[type]
|
self[:result] || calculate_result[type]
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is used during order.finish!.
|
# This is used for automatic distribution, e.g., in order.finish! or when receiving orders
|
||||||
def save_results!
|
def save_results!(article_total = nil)
|
||||||
self.update_attribute(:result, calculate_result[:total])
|
new_result = calculate_result(article_total)[:total]
|
||||||
|
self.update_attribute(:result_computed, new_result)
|
||||||
|
self.update_attribute(:result, new_result)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns total price for this individual article
|
# Returns total price for this individual article
|
||||||
|
@ -180,6 +188,10 @@ class GroupOrderArticle < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if the result deviates from the result_computed
|
||||||
|
def result_manually_changed?
|
||||||
|
result != result_computed unless result.nil?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,9 @@ class Order < ActiveRecord::Base
|
||||||
goa.save_results!
|
goa.save_results!
|
||||||
# Delete no longer required order-history (group_order_article_quantities) and
|
# Delete no longer required order-history (group_order_article_quantities) and
|
||||||
# TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?)
|
# TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?)
|
||||||
|
# A: Yes, we do - for redistributing articles when the number of articles
|
||||||
|
# delivered changes, and for statistics on popular articles. Records
|
||||||
|
# with both tolerance and quantity zero can be deleted.
|
||||||
#goa.group_order_article_quantities.clear
|
#goa.group_order_article_quantities.clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,9 @@ class OrderArticle < ActiveRecord::Base
|
||||||
validate :article_and_price_exist
|
validate :article_and_price_exist
|
||||||
validates_uniqueness_of :article_id, scope: :order_id
|
validates_uniqueness_of :article_id, scope: :order_id
|
||||||
|
|
||||||
scope :ordered, :conditions => "units_to_order > 0"
|
_ordered_sql = "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 group_order_articles.result > 0") }
|
scope :ordered, -> { where(_ordered_sql) }
|
||||||
|
scope :ordered_or_member, -> { includes(:group_order_articles).where("#{_ordered_sql} OR group_order_articles.result > 0") }
|
||||||
|
|
||||||
before_create :init_from_balancing
|
before_create :init_from_balancing
|
||||||
after_destroy :update_ordergroup_prices
|
after_destroy :update_ordergroup_prices
|
||||||
|
@ -35,6 +36,13 @@ class OrderArticle < ActiveRecord::Base
|
||||||
article_price || article
|
article_price || article
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# 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.
|
# In balancing this can differ from ordered (by supplier) quantity for this article.
|
||||||
def group_orders_sum
|
def group_orders_sum
|
||||||
|
@ -81,17 +89,70 @@ class OrderArticle < ActiveRecord::Base
|
||||||
|
|
||||||
# Calculate price for ordered quantity.
|
# Calculate price for ordered quantity.
|
||||||
def total_price
|
def total_price
|
||||||
units_to_order * price.unit_quantity * price.price
|
units * price.unit_quantity * price.price
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calculate gross price for ordered qunatity.
|
# Calculate gross price for ordered qunatity.
|
||||||
def total_gross_price
|
def total_gross_price
|
||||||
units_to_order * price.unit_quantity * price.gross_price
|
units * price.unit_quantity * price.gross_price
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_quantities_equal_to_group_orders?
|
def ordered_quantities_different_from_group_orders?(ordered_mark="!", billed_mark="?", received_mark="?")
|
||||||
# the rescue is a workaround for units_to_order not being defined in integration tests
|
if not units_received.nil?
|
||||||
(units_to_order * price.unit_quantity) == group_orders_sum[:quantity] rescue false
|
((units_received * price.unit_quantity) == group_orders_sum[:quantity]) ? false : received_mark
|
||||||
|
elsif not units_billed.nil?
|
||||||
|
((units_billed * price.unit_quantity) == group_orders_sum[:quantity]) ? false : billed_mark
|
||||||
|
elsif not units_to_order.nil?
|
||||||
|
((units_to_order * price.unit_quantity) == group_orders_sum[:quantity]) ? false : ordered_mark
|
||||||
|
else
|
||||||
|
nil # can happen in integration tests
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# redistribute articles over ordergroups
|
||||||
|
# quantity Number of units to distribute
|
||||||
|
# surplus What to do when there are more articles than ordered quantity
|
||||||
|
# :tolerance fill member orders' tolerance
|
||||||
|
# :stock move to stock
|
||||||
|
# nil nothing; for catching the remaining count
|
||||||
|
# update_totals Whether to update group_order and ordergroup totals
|
||||||
|
# returns array with counts for each surplus method
|
||||||
|
def redistribute(quantity, surplus = [:tolerance], update_totals = true)
|
||||||
|
qty_left = quantity
|
||||||
|
counts = [0] * surplus.length
|
||||||
|
|
||||||
|
if surplus.index(:tolerance).nil?
|
||||||
|
qty_for_members = [qty_left, self.quantity].min
|
||||||
|
else
|
||||||
|
qty_for_members = [qty_left, self.quantity+self.tolerance].min
|
||||||
|
counts[surplus.index(:tolerance)] = [0, qty_for_members-self.quantity].max
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recompute
|
||||||
|
group_order_articles.each {|goa| goa.save_results! qty_for_members }
|
||||||
|
qty_left -= qty_for_members
|
||||||
|
|
||||||
|
# if there's anything left, move to stock if wanted
|
||||||
|
if qty_left > 0 and surplus.index(:stock)
|
||||||
|
counts[surplus.index(:stock)] = qty_left
|
||||||
|
# 1) find existing stock article with same name, unit, price
|
||||||
|
# 2) if not found, create new stock article
|
||||||
|
# avoiding duplicate stock article names
|
||||||
|
end
|
||||||
|
if qty_left > 0 and surplus.index(nil)
|
||||||
|
counts[surplus.index(nil)] = qty_left
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update GroupOrder prices & Ordergroup stats
|
||||||
|
# TODO only affected group_orders, and once after redistributing all articles
|
||||||
|
if update_totals
|
||||||
|
update_ordergroup_prices
|
||||||
|
order.ordergroups.each(&:update_stats!)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO notifications
|
||||||
|
|
||||||
|
counts
|
||||||
end
|
end
|
||||||
|
|
||||||
# Updates order_article and belongings during balancing process
|
# Updates order_article and belongings during balancing process
|
||||||
|
@ -135,17 +196,23 @@ class OrderArticle < ActiveRecord::Base
|
||||||
units
|
units
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def article_and_price_exist
|
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?
|
errors.add(:article, I18n.t('model.order_article.error_price')) if !(article = Article.find(article_id)) || article.fc_price.nil?
|
||||||
|
rescue
|
||||||
|
errors.add(:article, I18n.t('model.order_article.error_price'))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Associate with current article price if created in a finished order
|
# Associate with current article price if created in a finished order
|
||||||
def init_from_balancing
|
def init_from_balancing
|
||||||
if order.present? and order.finished?
|
if order.present? and order.finished?
|
||||||
self.article_price = article.article_prices.first
|
self.article_price = article.article_prices.first
|
||||||
self.units_to_order = 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -153,7 +220,7 @@ class OrderArticle < ActiveRecord::Base
|
||||||
# updates prices of ALL ordergroups - these are actually too many
|
# updates prices of ALL ordergroups - these are actually too many
|
||||||
# in case of performance issues, update only ordergroups, which ordered this article
|
# 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
|
# CAUTION: in after_destroy callback related records (e.g. group_order_articles) are already non-existent
|
||||||
order.group_orders.each { |go| go.update_price! }
|
order.group_orders.each(&:update_price!)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
$('#new_stock_article').removeAttr('disabled').select2({
|
$('#new_stock_article').removeAttr('disabled').select2({
|
||||||
placeholder: '#{t '.create_stock_article'}',
|
placeholder: '#{t '.create_stock_article'}',
|
||||||
data: #{articles_for_select2(@supplier).to_json},
|
data: #{articles_for_select2(@supplier.articles).to_json},
|
||||||
createSearchChoice: function(term) {
|
createSearchChoice: function(term) {
|
||||||
return {
|
return {
|
||||||
id: 'new',
|
id: 'new',
|
||||||
|
@ -115,12 +115,12 @@
|
||||||
%tfoot
|
%tfoot
|
||||||
%tr
|
%tr
|
||||||
%th{:colspan => 5}
|
%th{:colspan => 5}
|
||||||
- if articles_for_select2(@supplier).empty?
|
- if @supplier.articles.empty?
|
||||||
= link_to t('.create_stock_article'), new_stock_article_path, :remote => true, :class => 'btn'
|
= link_to t('.create_stock_article'), new_stock_article_path, :remote => true, :class => 'btn'
|
||||||
- else
|
- else
|
||||||
%input#new_stock_article{:style => 'width: 500px;'}
|
%input#new_stock_article{:style => 'width: 500px;'}
|
||||||
%tbody
|
%tbody
|
||||||
- for article in stock_articles_for_table(@supplier)
|
- for article in articles_for_table(@supplier.stock_articles)
|
||||||
= render :partial => 'stock_article_for_adding', :locals => {:article => article}
|
= render :partial => 'stock_article_for_adding', :locals => {:article => article}
|
||||||
|
|
||||||
%h2= t '.title_fill_quantities'
|
%h2= t '.title_fill_quantities'
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
%th= sort_link_helper Article.model_name.human, "name"
|
%th= sort_link_helper Article.model_name.human, "name"
|
||||||
%th= sort_link_helper Article.human_attribute_name(:order_number_short), "order_number"
|
%th= sort_link_helper Article.human_attribute_name(:order_number_short), "order_number"
|
||||||
%th= t('.amount')
|
%th= t('.amount')
|
||||||
%th= heading_helper Article, :units
|
%th= heading_helper Article, :unit
|
||||||
%th= t('.net')
|
%th= t('.net')
|
||||||
%th= t('.gross')
|
%th= t('.gross')
|
||||||
%th= heading_helper Article, :tax
|
%th= heading_helper Article, :tax
|
||||||
%th= heading_helper Article, :deposit
|
%th= heading_helper Article, :deposit
|
||||||
%th{:colspan => "2"}
|
%th{:colspan => "2"}
|
||||||
= link_to t('.add_article'), new_finance_order_order_article_path(@order), remote: true,
|
= link_to t('.add_article'), new_order_order_article_path(@order), remote: true,
|
||||||
class: 'btn btn-small'
|
class: 'btn btn-small'
|
||||||
%tbody#result_table
|
%tbody#result_table
|
||||||
- for order_article in @articles
|
- for order_article in @articles
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
%td.closed
|
%td.closed
|
||||||
= link_to order_article.article.name, '#', 'data-toggle-this' => "#group_order_articles_#{order_article.id}"
|
= link_to order_article.article.name, '#', 'data-toggle-this' => "#group_order_articles_#{order_article.id}"
|
||||||
%td= order_article.article.order_number
|
%td= order_article.article.order_number
|
||||||
%td
|
%td{title: units_history_line(order_article)}
|
||||||
= order_article.units_to_order
|
= order_article.units
|
||||||
- unless order_article.ordered_quantities_equal_to_group_orders?
|
= pkg_helper order_article.article_price
|
||||||
%span{:style => "color:red;font-weight: bold"} !
|
- if s=order_article.ordered_quantities_different_from_group_orders?
|
||||||
%td #{order_article.price.unit_quantity} × #{order_article.article.unit}
|
%span{:style => "color:red;font-weight: bold"}= s
|
||||||
|
%td #{order_article.article.unit}
|
||||||
%td
|
%td
|
||||||
= number_to_currency(order_article.price.price, :unit => "")
|
= number_to_currency(order_article.price.price, :unit => "")
|
||||||
:plain
|
:plain
|
||||||
|
@ -19,8 +20,8 @@
|
||||||
%td #{order_article.price.tax}%
|
%td #{order_article.price.tax}%
|
||||||
%td= order_article.price.deposit
|
%td= order_article.price.deposit
|
||||||
%td
|
%td
|
||||||
= link_to t('ui.edit'), edit_finance_order_order_article_path(order_article.order, order_article), remote: true,
|
= link_to t('ui.edit'), edit_order_order_article_path(order_article.order, order_article), remote: true,
|
||||||
class: 'btn btn-mini'
|
class: 'btn btn-mini'
|
||||||
%td
|
%td
|
||||||
= link_to t('ui.delete'), finance_order_order_article_path(order_article.order, order_article), method: :delete,
|
= link_to t('ui.delete'), order_order_article_path(order_article.order, order_article), method: :delete,
|
||||||
remote: true, confirm: t('.confirm'), class: 'btn btn-danger btn-mini'
|
remote: true, confirm: t('.confirm'), class: 'btn btn-danger btn-mini'
|
||||||
|
|
|
@ -19,6 +19,11 @@
|
||||||
%td= show_user(order.updated_by)
|
%td= show_user(order.updated_by)
|
||||||
%td
|
%td
|
||||||
- unless order.closed?
|
- unless order.closed?
|
||||||
|
- if current_user.role_orders?
|
||||||
|
- unless order.stockit?
|
||||||
|
= link_to t('orders.index.action_receive'), receive_order_path(order), class: 'btn btn-mini'
|
||||||
|
- else
|
||||||
|
= link_to t('orders.index.action_receive'), '#', class: 'btn btn-mini disabled'
|
||||||
= link_to t('.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-mini btn-primary'
|
= link_to t('.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-mini btn-primary'
|
||||||
= link_to t('.close'), close_direct_finance_order_path(order),
|
= link_to t('.close'), close_direct_finance_order_path(order),
|
||||||
:confirm => t('.confirm'), :method => :put, class: 'btn btn-mini'
|
:confirm => t('.confirm'), :method => :put, class: 'btn btn-mini'
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
- content_for :javascript do
|
||||||
|
:javascript
|
||||||
|
$(function() {
|
||||||
|
// Subscribe to database changes.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
$(document).on('OrderArticle#update', function(e) {
|
||||||
|
$.ajax({
|
||||||
|
url: '#{new_on_order_article_update_finance_order_path(@order)}',
|
||||||
|
type: 'get',
|
||||||
|
data: {order_article_id: e.order_article_id},
|
||||||
|
contentType: 'application/json; charset=UTF-8'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('OrderArticle#create', function(e) {
|
||||||
|
$.ajax({
|
||||||
|
url: '#{new_on_order_article_create_finance_order_path(@order)}',
|
||||||
|
type: 'get',
|
||||||
|
data: {order_article_id: e.order_article_id},
|
||||||
|
contentType: 'application/json; charset=UTF-8'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
- title t('.title', name: @order.name)
|
- title t('.title', name: @order.name)
|
||||||
|
|
||||||
- content_for :sidebar do
|
- content_for :sidebar do
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Handle more advanced DOM update after AJAX database manipulation.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
(function(w) {
|
||||||
|
$('#order_article_<%= @order_article.id %>').remove(); // just to be sure: remove table row which is added below
|
||||||
|
|
||||||
|
$('#ordered-articles tr').removeClass('success');
|
||||||
|
|
||||||
|
var order_article_entry = $(
|
||||||
|
'<%= j render('finance/balancing/order_article_result', order_article: @order_article) %>'
|
||||||
|
).addClass('success');
|
||||||
|
|
||||||
|
$('#result_table').prepend(order_article_entry);
|
||||||
|
|
||||||
|
$('#summaryChangedWarning').show();
|
||||||
|
})(window);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Handle more advanced DOM update after AJAX database manipulation.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
(function(w) {
|
||||||
|
$('#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();
|
||||||
|
})(window);
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
$('#modalContainer').modal('hide');
|
|
||||||
$('#result_table').prepend('#{j(render('finance/balancing/order_article_result', order_article: @order_article))}');
|
|
||||||
$('#summaryChangedWarning').show();
|
|
|
@ -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,9 +1,19 @@
|
||||||
= simple_form_for [:finance, @order, @order_article], remote: true do |form|
|
= simple_form_for [@order, @order_article], remote: true do |form|
|
||||||
.modal-header
|
.modal-header
|
||||||
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
|
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
|
||||||
%h3= t '.title'
|
%h3= t '.title'
|
||||||
.modal-body
|
.modal-body
|
||||||
= form.input :units_to_order
|
- if params[:without_units]
|
||||||
|
= hidden_field_tag :without_units, true
|
||||||
|
- else
|
||||||
|
.fold-line
|
||||||
|
= form.input :units_to_order, hint: '', input_html: {class: 'input-nano'}
|
||||||
|
-#= form.input :units_billed, label: 'invoice', input_html: {class: 'input-nano'}
|
||||||
|
= form.input :units_received, input_html: {class: 'input-nano'},
|
||||||
|
label: t('activerecord.attributes.order_article.units_received_short')
|
||||||
|
%p.help-block= t 'simple_form.hints.order_article.units_to_order'
|
||||||
|
|
||||||
|
.foo{style: 'clear:both'}
|
||||||
|
|
||||||
= simple_fields_for :article, @order_article.article do |f|
|
= simple_fields_for :article, @order_article.article do |f|
|
||||||
= f.input :name
|
= f.input :name
|
|
@ -1,9 +1,9 @@
|
||||||
= simple_form_for [:finance, @order, @order_article], remote: true do |form|
|
= simple_form_for [@order, @order_article], remote: true do |form|
|
||||||
.modal-header
|
.modal-header
|
||||||
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
|
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
|
||||||
%h3= t '.title'
|
%h3= t '.title'
|
||||||
.modal-body
|
.modal-body
|
||||||
= form.input :article_id, as: :select, collection: new_order_articles_collection, :label => Article.model_name.human # Why do we need the label?
|
= form.association :article, collection: new_order_articles_collection
|
||||||
.modal-footer
|
.modal-footer
|
||||||
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
|
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
|
||||||
= form.submit class: 'btn btn-primary'
|
= form.submit class: 'btn btn-primary'
|
9
app/views/order_articles/create.js.erb
Normal file
9
app/views/order_articles/create.js.erb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Publish database changes.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
$(document).trigger({
|
||||||
|
type: 'OrderArticle#create',
|
||||||
|
order_article_id: <%= @order_article.id %>
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#modalContainer').modal('hide');
|
||||||
|
|
9
app/views/order_articles/update.js.erb
Normal file
9
app/views/order_articles/update.js.erb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Publish database changes.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
$(document).trigger({
|
||||||
|
type: 'OrderArticle#update',
|
||||||
|
order_article_id: <%= @order_article.id %>
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#modalContainer').modal('hide');
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
%th= heading_helper Article, :name
|
%th= heading_helper Article, :name
|
||||||
%th= heading_helper Article, :unit_quantity
|
%th= heading_helper Article, :unit
|
||||||
%th= t '.prices'
|
%th= t '.prices'
|
||||||
|
- if order.stockit?
|
||||||
%th= t '.units_ordered'
|
%th= t '.units_ordered'
|
||||||
- unless order.stockit?
|
- else
|
||||||
|
%th= 'Members'
|
||||||
%th= t '.units_full'
|
%th= t '.units_full'
|
||||||
- total_net, total_gross, counter = 0, 0, 0
|
- total_net, total_gross, counter = 0, 0, 0
|
||||||
%tbody
|
%tbody
|
||||||
|
@ -18,13 +20,14 @@
|
||||||
- order_articles.each do |order_article|
|
- order_articles.each do |order_article|
|
||||||
- net_price = order_article.price.price
|
- net_price = order_article.price.price
|
||||||
- gross_price = order_article.price.gross_price
|
- gross_price = order_article.price.gross_price
|
||||||
- units = order_article.units_to_order
|
|
||||||
- unit_quantity = order_article.price.unit_quantity
|
- unit_quantity = order_article.price.unit_quantity
|
||||||
|
- units = order_article.units
|
||||||
- total_net += units * unit_quantity * net_price
|
- total_net += units * unit_quantity * net_price
|
||||||
- total_gross += units * unit_quantity * gross_price
|
- total_gross += units * unit_quantity * gross_price
|
||||||
%tr{:class => cycle('even', 'odd', :name => 'articles'), :style => "color: #{units > 0 ? 'green' : 'red'}"}
|
- cssclass = (units > 0 ? 'used' : (order_article.quantity > 0 ? 'unused' : 'unavailable'))
|
||||||
|
%tr{:class => cycle('even', 'odd', :name => 'articles') + ' ' + cssclass}
|
||||||
%td=h order_article.article.name
|
%td=h order_article.article.name
|
||||||
%td= "#{unit_quantity} x #{order_article.article.unit}"
|
%td= order_article.article.unit
|
||||||
%td= "#{number_to_currency(net_price)} / #{number_to_currency(gross_price)}"
|
%td= "#{number_to_currency(net_price)} / #{number_to_currency(gross_price)}"
|
||||||
- if order.stockit?
|
- if order.stockit?
|
||||||
%td= units
|
%td= units
|
||||||
|
@ -33,7 +36,9 @@
|
||||||
%td= "#{order_article.quantity} + #{order_article.tolerance}"
|
%td= "#{order_article.quantity} + #{order_article.tolerance}"
|
||||||
- else
|
- else
|
||||||
%td= "#{order_article.quantity}"
|
%td= "#{order_article.quantity}"
|
||||||
%td= units
|
%td{title: units_history_line(order_article)}
|
||||||
|
= units
|
||||||
|
= pkg_helper order_article.price
|
||||||
%p
|
%p
|
||||||
= t '.prices_sum'
|
= t '.prices_sum'
|
||||||
= "#{number_to_currency(total_net)} / #{number_to_currency(total_gross)}"
|
= "#{number_to_currency(total_net)} / #{number_to_currency(total_gross)}"
|
||||||
|
|
27
app/views/orders/_edit_amount.html.haml
Normal file
27
app/views/orders/_edit_amount.html.haml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-# NOTE: if you modify tiny details here you must also change them in `receive_on_order_article_update.js.erb`
|
||||||
|
= fields_for 'order_articles', order_article, index: order_article.id do |form|
|
||||||
|
%tr{id: "order_article_#{order_article.id}", class: "#{cycle('even', 'odd', name: 'articles')} order-article", valign: "top"}
|
||||||
|
- order_title = []
|
||||||
|
- order_title.append Article.human_attribute_name(:manufacturer)+': ' + order_article.article.manufacturer unless order_article.article.manufacturer.to_s.empty?
|
||||||
|
- order_title.append Article.human_attribute_name(:note)+': ' + order_article.article.note unless order_article.article.note.to_s.empty?
|
||||||
|
%td= order_article.article.order_number
|
||||||
|
%td.name{title: order_title.join("\n")}= order_article.article.name
|
||||||
|
%td.unit= order_article.article.unit
|
||||||
|
%td.article_price
|
||||||
|
= number_to_currency order_article.article_price.price
|
||||||
|
= article_price_change_hint(order_article)
|
||||||
|
%td #{order_article.quantity} + #{order_article.tolerance}
|
||||||
|
%td
|
||||||
|
= order_article.units_to_order
|
||||||
|
= pkg_helper order_article.article
|
||||||
|
-#%td # TODO implement invoice screen
|
||||||
|
- unless order_article.units_billed.nil?
|
||||||
|
= order_article.units_billed
|
||||||
|
= pkg_helper order_article.article, soft_uq: true
|
||||||
|
%td.units_received_cell
|
||||||
|
= receive_input_field(form)
|
||||||
|
= pkg_helper order_article.article_price, icon: false, soft_uq: true
|
||||||
|
/ TODO add almost invisible text_field for entering single units
|
||||||
|
%td.units_delta
|
||||||
|
%td
|
||||||
|
= link_to t('ui.edit'), edit_order_order_article_path(order_article.order, order_article, without_units: true), remote: true, class: 'btn btn-small'
|
99
app/views/orders/_edit_amounts.html.haml
Normal file
99
app/views/orders/_edit_amounts.html.haml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
- new_articles = (@order.supplier.articles rescue @order.articles)
|
||||||
|
- new_article_data = articles_for_select2(new_articles, @order_articles.map(&:article_id)) {|a| "#{a.name} (#{a.unit_quantity}⨯#{a.unit})"}
|
||||||
|
- content_for :javascript do
|
||||||
|
:javascript
|
||||||
|
|
||||||
|
function update_delta(input) {
|
||||||
|
var units = $(input).val();
|
||||||
|
var expected = $(input).data('units-expected');
|
||||||
|
var delta = Math.round((units-expected)*100)/100.0;
|
||||||
|
var html;
|
||||||
|
|
||||||
|
if (units.replace(/\s/g,"")=="") {
|
||||||
|
// no value
|
||||||
|
html = '';
|
||||||
|
} else if (isNaN(units)) {
|
||||||
|
html = '<i class="icon-remove" style="color: red"></i>';
|
||||||
|
} else if (delta == 0) {
|
||||||
|
// equal value
|
||||||
|
html = '<i class="icon-ok" style="color: green"></i>';
|
||||||
|
} else {
|
||||||
|
if (delta < 0) {
|
||||||
|
html = '<span style="color: red">- '+(-delta)+'</span>';
|
||||||
|
} else /*if (units> expected)*/ {
|
||||||
|
html = '<span style="color: green">+ '+(delta)+'</span>';
|
||||||
|
}
|
||||||
|
// show package icon only if the receive field has one
|
||||||
|
if ($(input).hasClass('package')) {
|
||||||
|
html += '#{j pkg_helper_icon}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(input).closest('tr').find('.units_delta').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('change keyup', 'input[data-units-expected]', function() {
|
||||||
|
update_delta(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '#order_articles .unlocker', unlock_receive_input_field);
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$('input[data-units-expected]').each(function() {
|
||||||
|
update_delta(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
init_add_article('#add_article');
|
||||||
|
});
|
||||||
|
|
||||||
|
function init_add_article(sel) {
|
||||||
|
$(sel).removeAttr('disabled').select2({
|
||||||
|
placeholder: '#{j t('orders.receive.add_article')}',
|
||||||
|
formatNoMatches: function(term) { return '#{j t('.no_articles_available')}';}
|
||||||
|
// TODO implement adding a new article, like in deliveries
|
||||||
|
}).on('change', function(e) {
|
||||||
|
var selectedArticle = $(e.currentTarget).select2('data');
|
||||||
|
if(!selectedArticle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '#{order_order_articles_path(@order)}',
|
||||||
|
type: 'post',
|
||||||
|
data: JSON.stringify({order_article: {article_id: selectedArticle.id}}),
|
||||||
|
contentType: 'application/json; charset=UTF-8'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#add_article').select2('data', null);
|
||||||
|
}).select2('data', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlock_receive_input_field() {
|
||||||
|
$('.units_received', $(this).closest('tr')).prop('disabled', false).focus();
|
||||||
|
$(this).closest('.input-prepend').prop('title', I18n.t('orders.edit_amount.field_unlocked_title'));
|
||||||
|
$(this).replaceWith('<i class="icon icon-warning-sign add-on"></i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
%table#order_articles.ordered-articles.table.table-striped.stupidtable{style: 'margin-bottom: 0'}
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th.sort{:data => {:sort => 'string'}}= heading_helper Article, :order_number, short: true
|
||||||
|
%th.default-sort.sort{:data => {:sort => 'string'}}= heading_helper Article, :name
|
||||||
|
%th= heading_helper Article, :unit
|
||||||
|
%th= heading_helper Article, :price
|
||||||
|
%th= heading_helper OrderArticle, :quantity, short: true
|
||||||
|
%th= heading_helper OrderArticle, :units_to_order, short: true
|
||||||
|
-#%th Invoice # TODO implement invoice screen
|
||||||
|
%th= heading_helper OrderArticle, :units_received, short: true
|
||||||
|
%th
|
||||||
|
%th= t 'ui.actions'
|
||||||
|
%tbody#result_table
|
||||||
|
- @order_articles.each do |order_article|
|
||||||
|
= render :partial => 'edit_amount', :locals => {:order_article => order_article}
|
||||||
|
%tfoot
|
||||||
|
%tr
|
||||||
|
%th{:colspan => 10}
|
||||||
|
%select#add_article{:style => 'width: 500px;'}
|
||||||
|
- new_article_data.each do |option|
|
||||||
|
%option{id: "add_article_#{option[:id]}", value: option[:id]}= option[:text]
|
||||||
|
|
16
app/views/orders/add_article.js.erb
Normal file
16
app/views/orders/add_article.js.erb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
$('div.container-fluid').prepend(
|
||||||
|
'<%= j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.notice', :name => @order_article.article.name)})) %>'
|
||||||
|
);
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
$('.ordered-articles tr').removeClass('success');
|
||||||
|
|
||||||
|
var article_for_adding = $(
|
||||||
|
'<%= j(render(:partial => 'edit_amount', :locals => {:order_article => @order_article})) %>'
|
||||||
|
).addClass('success');
|
||||||
|
|
||||||
|
$('.ordered-articles tbody').append(article_for_adding);
|
||||||
|
updateSort('.ordered-articles');
|
||||||
|
|
||||||
|
$('#order_articles_<%= @order_article.id %>_units_received').focus();
|
||||||
|
})();
|
|
@ -10,8 +10,13 @@
|
||||||
%li= link_to supplier.name, new_order_path(supplier_id: supplier.id), tabindex: -1
|
%li= link_to supplier.name, new_order_path(supplier_id: supplier.id), tabindex: -1
|
||||||
|
|
||||||
.well
|
.well
|
||||||
%h2= t '.open_orders'
|
- if not @open_orders.empty?
|
||||||
- unless @open_orders.empty?
|
%h2= t '.orders_open'
|
||||||
|
- elsif not @finished_orders.empty?
|
||||||
|
%h2= t '.orders_finished'
|
||||||
|
- else
|
||||||
|
= t '.no_open_or_finished_orders'
|
||||||
|
- unless @open_orders.empty? and @finished_orders.empty?
|
||||||
%table.table.table-striped
|
%table.table.table-striped
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
@ -20,6 +25,7 @@
|
||||||
%th= heading_helper Order, :note
|
%th= heading_helper Order, :note
|
||||||
%th{colspan: "2"}
|
%th{colspan: "2"}
|
||||||
%tbody
|
%tbody
|
||||||
|
- unless @open_orders.empty?
|
||||||
- for order in @open_orders
|
- for order in @open_orders
|
||||||
- tr_class = " active" if order.expired?
|
- tr_class = " active" if order.expired?
|
||||||
%tr{class: tr_class}
|
%tr{class: tr_class}
|
||||||
|
@ -31,13 +37,32 @@
|
||||||
class: 'btn btn-small btn-success'
|
class: 'btn btn-small btn-success'
|
||||||
|
|
||||||
%td
|
%td
|
||||||
= link_to t('ui.show'), order, class: 'btn btn-small'
|
|
||||||
= link_to t('ui.edit'), edit_order_path(order), class: 'btn btn-small'
|
= link_to t('ui.edit'), edit_order_path(order), class: 'btn btn-small'
|
||||||
|
= link_to t('ui.show'), order, class: 'btn btn-small'
|
||||||
= link_to t('ui.delete'), order, confirm: t('.confirm_delete'), method: :delete,
|
= link_to t('ui.delete'), order, confirm: t('.confirm_delete'), method: :delete,
|
||||||
class: 'btn btn-small btn-danger'
|
class: 'btn btn-small btn-danger'
|
||||||
- else
|
|
||||||
= t '.no_open_orders'
|
|
||||||
|
|
||||||
%h2= t '.ended_orders'
|
- unless @finished_orders.empty?
|
||||||
|
- unless @open_orders.empty?
|
||||||
|
%tr
|
||||||
|
%td{colspan: 6}
|
||||||
|
%h2= t '.orders_finished'
|
||||||
|
- for order in @finished_orders
|
||||||
|
%tr
|
||||||
|
%td= order.name
|
||||||
|
%td= format_time(order.ends)
|
||||||
|
%td= truncate(order.note)
|
||||||
|
%td
|
||||||
|
- unless order.stockit?
|
||||||
|
-# TODO btn-success class only if not received before
|
||||||
|
= link_to t('.action_receive'), receive_order_path(order), class: 'btn btn-small btn-success'
|
||||||
|
|
||||||
|
%td
|
||||||
|
= link_to t('ui.edit'), '#', class: 'btn btn-small disabled', tabindex: -1
|
||||||
|
= link_to t('ui.show'), order, class: 'btn btn-small'
|
||||||
|
= link_to t('ui.delete'), order, confirm: t('.confirm_delete'), method: :delete,
|
||||||
|
class: 'btn btn-small btn-danger'
|
||||||
|
|
||||||
|
%h2= t '.orders_settled'
|
||||||
#orders_table
|
#orders_table
|
||||||
= render partial: 'orders'
|
= render partial: 'orders'
|
||||||
|
|
45
app/views/orders/receive.html.haml
Normal file
45
app/views/orders/receive.html.haml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
- content_for :javascript do
|
||||||
|
:javascript
|
||||||
|
$(function() {
|
||||||
|
// Subscribe to database changes.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
$(document).on('OrderArticle#update', function(e) {
|
||||||
|
$.ajax({
|
||||||
|
url: '#{receive_on_order_article_update_order_path(@order)}',
|
||||||
|
type: 'get',
|
||||||
|
data: {order_article_id: e.order_article_id},
|
||||||
|
contentType: 'application/json; charset=UTF-8'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('OrderArticle#create', function(e) {
|
||||||
|
$.ajax({
|
||||||
|
url: '#{receive_on_order_article_create_order_path(@order)}',
|
||||||
|
type: 'get',
|
||||||
|
data: {order_article_id: e.order_article_id},
|
||||||
|
contentType: 'application/json; charset=UTF-8'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
- title t('.title', order: @order.name)
|
||||||
|
|
||||||
|
= form_tag(receive_order_path(@order)) do
|
||||||
|
%fieldset#results
|
||||||
|
= render 'edit_amounts'
|
||||||
|
|
||||||
|
.form-actions
|
||||||
|
.pull-left
|
||||||
|
%b.checkbox.inline
|
||||||
|
= t '.surplus_options'
|
||||||
|
= label_tag :rest_to_tolerance, class: 'checkbox inline' do
|
||||||
|
= check_box_tag :rest_to_tolerance, 1, true
|
||||||
|
= t '.consider_member_tolerance'
|
||||||
|
= label_tag :rest_to_stock, class: 'checkbox inline' do
|
||||||
|
= check_box_tag :rest_to_stock, 1, false, disabled: true
|
||||||
|
%span{style: 'color: grey'}= t '.rest_to_stock'
|
||||||
|
.pull-right
|
||||||
|
= submit_tag t('.submit'), class: 'btn btn-primary'
|
||||||
|
= link_to t('ui.or_cancel'), order_path(@order)
|
||||||
|
|
||||||
|
%p= link_to_top
|
17
app/views/orders/receive_on_order_article_create.js.erb
Normal file
17
app/views/orders/receive_on_order_article_create.js.erb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Handle more advanced DOM update after AJAX database manipulation.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
(function(w) {
|
||||||
|
$('#order_article_<%= @order_article.id %>').remove(); // just to be sure: remove table row which is added below
|
||||||
|
|
||||||
|
$('#order_articles tr').removeClass('success');
|
||||||
|
|
||||||
|
var order_article_entry = $(
|
||||||
|
'<%= j render(partial: 'edit_amount', locals: {order_article: @order_article}) %>'
|
||||||
|
).addClass('success');
|
||||||
|
|
||||||
|
$('#order_articles tbody').append(order_article_entry);
|
||||||
|
updateSort('#order_articles');
|
||||||
|
|
||||||
|
$('#add_article_<%= @order_article.article.id %>').remove(); // remove option to add this article
|
||||||
|
})(window);
|
||||||
|
|
35
app/views/orders/receive_on_order_article_update.js.erb
Normal file
35
app/views/orders/receive_on_order_article_update.js.erb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Handle more advanced DOM update after AJAX database manipulation.
|
||||||
|
// See publish/subscribe design pattern in /doc.
|
||||||
|
(function(w) {
|
||||||
|
// get old element and update the cell which is reused
|
||||||
|
var old_order_article_entry = $('#order_article_<%= @order_article.id %>');
|
||||||
|
|
||||||
|
// update package info after input
|
||||||
|
$('td.units_received_cell span.package', old_order_article_entry).remove();
|
||||||
|
$('<%= j pkg_helper(@order_article.article_price, icon: false) %>')
|
||||||
|
.appendTo($('td.units_received_cell', old_order_article_entry));
|
||||||
|
|
||||||
|
// update package icon on input too
|
||||||
|
$('input', old_order_article_entry).toggleClass('package', <%= @order_article.article_price.unit_quantity == 1 ? 'false' : 'true' %>);
|
||||||
|
|
||||||
|
// update expected units, since unit_quantity may have been changed
|
||||||
|
$('input', old_order_article_entry).data('units-expected', <%=
|
||||||
|
(@order_article.units_billed or @order_article.units_to_order) *
|
||||||
|
1.0 * @order_article.article.unit_quantity / @order_article.article_price.unit_quantity
|
||||||
|
%>);
|
||||||
|
|
||||||
|
// render new element and inject dynamic cell
|
||||||
|
var new_order_article_entry = $(
|
||||||
|
'<%= j render(partial: 'edit_amount', locals: {order_article: @order_article}) %>'
|
||||||
|
);
|
||||||
|
|
||||||
|
$('td.units_received_cell', new_order_article_entry).replaceWith(
|
||||||
|
$('td.units_received_cell', old_order_article_entry)
|
||||||
|
);
|
||||||
|
|
||||||
|
// finally replace the OrderArticle entry
|
||||||
|
old_order_article_entry.replaceWith(new_order_article_entry);
|
||||||
|
|
||||||
|
update_delta($('input.units_received', new_order_article_entry));
|
||||||
|
})(window);
|
||||||
|
|
|
@ -31,6 +31,9 @@
|
||||||
= link_to t('ui.edit'), edit_order_path(@order), class: 'btn'
|
= link_to t('ui.edit'), edit_order_path(@order), class: 'btn'
|
||||||
= link_to t('.action_end'), finish_order_path(@order), method: :post, class: 'btn btn-success',
|
= link_to t('.action_end'), finish_order_path(@order), method: :post, class: 'btn btn-success',
|
||||||
confirm: t('.confirm_end', order: @order.name)
|
confirm: t('.confirm_end', order: @order.name)
|
||||||
|
- elsif not @order.closed? and not @order.stockit?
|
||||||
|
-# TODO btn-success class only if not received before
|
||||||
|
= link_to t('orders.index.action_receive'), receive_order_path(@order), class: 'btn btn-success'
|
||||||
- unless @order.closed?
|
- unless @order.closed?
|
||||||
= link_to t('ui.delete'), @order, confirm: t('.confirm_delete'), method: :delete,
|
= link_to t('ui.delete'), @order, confirm: t('.confirm_delete'), method: :delete,
|
||||||
class: 'btn btn-danger'
|
class: 'btn btn-danger'
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
# only serve selected strings for i18n-js to keep filesize down
|
# only serve selected strings for i18n-js to keep filesize down
|
||||||
translations:
|
translations:
|
||||||
- file: 'app/assets/javascripts/i18n/translations.js'
|
- file: 'app/assets/javascripts/i18n/translations.js'
|
||||||
only: ['*.js.*', '*.number.*', '*.date.formats.*']
|
only: [
|
||||||
|
'*.js.*',
|
||||||
|
'*.number.*',
|
||||||
|
'*.date.formats.*',
|
||||||
|
# foodsoft-specific texts to keep js with normal translations
|
||||||
|
'*.orders.edit_amount.*'
|
||||||
|
]
|
||||||
|
|
|
@ -77,10 +77,17 @@ de:
|
||||||
note: Notiz
|
note: Notiz
|
||||||
starts: Läuft vom
|
starts: Läuft vom
|
||||||
status: Status
|
status: Status
|
||||||
|
supplier: Lieferant
|
||||||
order_article:
|
order_article:
|
||||||
|
article: Artikel
|
||||||
missing_units: Fehlende Einheiten
|
missing_units: Fehlende Einheiten
|
||||||
missing_units_short: Fehlende
|
missing_units_short: Fehlend
|
||||||
units_to_order: Menge
|
quantity: Gewünschte Einheiten
|
||||||
|
quantity_short: Gewünscht
|
||||||
|
units_received: Gelieferte Gebinde
|
||||||
|
units_received_short: Geliefert
|
||||||
|
units_to_order: Bestellte Gebinde
|
||||||
|
units_to_order_short: Bestellt
|
||||||
update_current_price: Globalen Preis aktualisieren
|
update_current_price: Globalen Preis aktualisieren
|
||||||
order_comment:
|
order_comment:
|
||||||
text: Kommentiere diese Bestellung ...
|
text: Kommentiere diese Bestellung ...
|
||||||
|
@ -277,7 +284,7 @@ de:
|
||||||
error_denied: Du darfst die gewünschte Seite nicht sehen. Wenn Du denkst, dass Du dürfen solltest, frage eine Administratorin, dass sie Dir die entsprechenden Rechte einräumt. Falls Du Zugang zu mehreren Benutzerkonten hast, möchtest Du Dich vielleicht %{sign_in}.
|
error_denied: Du darfst die gewünschte Seite nicht sehen. Wenn Du denkst, dass Du dürfen solltest, frage eine Administratorin, dass sie Dir die entsprechenden Rechte einräumt. Falls Du Zugang zu mehreren Benutzerkonten hast, möchtest Du Dich vielleicht %{sign_in}.
|
||||||
error_denied_sign_in: als ein anderer Benutzer anmelden
|
error_denied_sign_in: als ein anderer Benutzer anmelden
|
||||||
error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!
|
error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!
|
||||||
error_token:
|
error_token: Zugriff verweigert (ungültiger Token)!
|
||||||
article_categories:
|
article_categories:
|
||||||
create:
|
create:
|
||||||
notice: Die Kategorie wurde gespeichert
|
notice: Die Kategorie wurde gespeichert
|
||||||
|
@ -377,7 +384,9 @@ de:
|
||||||
update:
|
update:
|
||||||
body: ! 'Jeder Artikel wird doppelt angezeigt: die alten Werte sind grau und die Textfelder sind mit den aktuellen Werten vorausgefüllt. Abweichungen zu den alten Artikeln sind gelb markiert.'
|
body: ! 'Jeder Artikel wird doppelt angezeigt: die alten Werte sind grau und die Textfelder sind mit den aktuellen Werten vorausgefüllt. Abweichungen zu den alten Artikeln sind gelb markiert.'
|
||||||
title: Aktualisieren ...
|
title: Aktualisieren ...
|
||||||
update_msg: ! '%{count} Artikel müssen aktualisiert werden.'
|
update_msg:
|
||||||
|
one: Ein Artikel muss aktualisiert werden.
|
||||||
|
other: ! '%{count} Artikel müssen aktualisiert werden.'
|
||||||
upload:
|
upload:
|
||||||
body: <p>Die Datei muss eine Textdatei mit der Endung '.csv' sein. Die erste Zeile wird beim Einlesen ignoriert.</p> <p>Die Felder müssen mit einem Semikolon (';') getrennt und der Text mit doppelten Anführungszeichen ("Text...") umklammert werden.</p> <p>Als Zeichensatz wird UTF-8 erwartet. Korrekte Reihenfolge der Spalten:</p>
|
body: <p>Die Datei muss eine Textdatei mit der Endung '.csv' sein. Die erste Zeile wird beim Einlesen ignoriert.</p> <p>Die Felder müssen mit einem Semikolon (';') getrennt und der Text mit doppelten Anführungszeichen ("Text...") umklammert werden.</p> <p>Als Zeichensatz wird UTF-8 erwartet. Korrekte Reihenfolge der Spalten:</p>
|
||||||
fields:
|
fields:
|
||||||
|
@ -469,7 +478,9 @@ de:
|
||||||
- FC-Preis
|
- FC-Preis
|
||||||
- Menge
|
- Menge
|
||||||
title: ! 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}'
|
title: ! 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}'
|
||||||
total: Insgesamt %{count} Artikel
|
total:
|
||||||
|
one: Insgesamt ein Artikel
|
||||||
|
other: ! 'Insgesamt %{count} Artikel'
|
||||||
errors:
|
errors:
|
||||||
general: Ein Problem ist aufgetreten.
|
general: Ein Problem ist aufgetreten.
|
||||||
general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen.
|
general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen.
|
||||||
|
@ -613,12 +624,6 @@ de:
|
||||||
show:
|
show:
|
||||||
back: Züruck
|
back: Züruck
|
||||||
title: Rechnung %{number}
|
title: Rechnung %{number}
|
||||||
order_articles:
|
|
||||||
edit:
|
|
||||||
stock_alert: Preise von Lagerartikeln können nicht geändert werden!
|
|
||||||
title: Artikel aktualisieren
|
|
||||||
new:
|
|
||||||
title: Neuer gelieferter Artikel die Bestellung
|
|
||||||
ordergroups:
|
ordergroups:
|
||||||
index:
|
index:
|
||||||
new_transaction: Neue Überweisungen eingeben
|
new_transaction: Neue Überweisungen eingeben
|
||||||
|
@ -741,6 +746,7 @@ de:
|
||||||
new_invoice: Rechnung anlegen
|
new_invoice: Rechnung anlegen
|
||||||
show_invoice: Rechnung anzeigen
|
show_invoice: Rechnung anzeigen
|
||||||
orders:
|
orders:
|
||||||
|
old_price: Alter Preis
|
||||||
option_choose: Lieferantin/Lager auswählen
|
option_choose: Lieferantin/Lager auswählen
|
||||||
option_stock: Lager
|
option_stock: Lager
|
||||||
order_pdf: PDF erstellen
|
order_pdf: PDF erstellen
|
||||||
|
@ -1085,6 +1091,12 @@ de:
|
||||||
model:
|
model:
|
||||||
error_single_group: ! '%{user} ist schon in einer anderen Bestellgruppe'
|
error_single_group: ! '%{user} ist schon in einer anderen Bestellgruppe'
|
||||||
invalid_balance: ist keine gültige Zahl
|
invalid_balance: ist keine gültige Zahl
|
||||||
|
order_articles:
|
||||||
|
edit:
|
||||||
|
stock_alert: Preise von Lagerartikeln können nicht geändert werden!
|
||||||
|
title: Artikel aktualisieren
|
||||||
|
new:
|
||||||
|
title: Neuer gelieferter Artikel der Bestellung
|
||||||
orders:
|
orders:
|
||||||
articles:
|
articles:
|
||||||
article_count: ! 'Bestellte Artikel:'
|
article_count: ! 'Bestellte Artikel:'
|
||||||
|
@ -1096,6 +1108,9 @@ de:
|
||||||
notice: Die Bestellung wurde erstellt.
|
notice: Die Bestellung wurde erstellt.
|
||||||
edit:
|
edit:
|
||||||
title: Bestellung bearbeiten
|
title: Bestellung bearbeiten
|
||||||
|
edit_amount:
|
||||||
|
field_locked_title: Die Verteilung dieses Artikels auf die einzelnen Bestellgruppen wurde manuell angepasst. Das Eingabefeld wurde gesperrt, um die manuellen Änderungen zu bewahren. Um den Artikel neu zu verteilen, drücke den Entsperrknopf und ändere die gelieferte Menge.
|
||||||
|
field_unlocked_title: Die Verteilung dieses Artikels auf die einzelnen Bestellgruppen wurde manuell angepasst. Wenn du die gelieferte Menge änderst, werden die vorherigen manuellen Änderungen überschrieben.
|
||||||
fax:
|
fax:
|
||||||
amount: Menge
|
amount: Menge
|
||||||
articles: Artikel
|
articles: Artikel
|
||||||
|
@ -1114,12 +1129,14 @@ de:
|
||||||
title: Artikel
|
title: Artikel
|
||||||
index:
|
index:
|
||||||
action_end: Beenden
|
action_end: Beenden
|
||||||
|
action_receive: In Empfang nehmen
|
||||||
confirm_delete: Willst Du wirklich die Bestellung löschen?
|
confirm_delete: Willst Du wirklich die Bestellung löschen?
|
||||||
confirm_end: Willst Du wirklich die Bestellung %{order} beenden? Es gibt kein zurück.
|
confirm_end: Willst Du wirklich die Bestellung %{order} beenden? Es gibt kein zurück.
|
||||||
ended_orders: Beendete Bestellungen
|
|
||||||
new_order: Neue Bestellung anlegen
|
new_order: Neue Bestellung anlegen
|
||||||
no_open_orders: Derzeit gibt es keine laufende Bestellungen.
|
no_open_or_finished_orders: Derzeit gibt es keine laufende oder beendete Bestellungen.
|
||||||
open_orders: Laufende Bestellungen
|
orders_finished: Beendet
|
||||||
|
orders_open: Laufend
|
||||||
|
orders_settled: Abgerechnet
|
||||||
title: Bestellungen verwalten
|
title: Bestellungen verwalten
|
||||||
model:
|
model:
|
||||||
error_closed: Bestellung wurde schon abgerechnet
|
error_closed: Bestellung wurde schon abgerechnet
|
||||||
|
@ -1131,6 +1148,15 @@ de:
|
||||||
warning_ordered_stock: ! 'Warnung: Die rot markierten Artikel wurden in der laufenden Lagerbestellung bereits bestellt bzw. gekauft. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen bzw. Käufe dieses Artikels gelöscht und nicht abgerechnet!'
|
warning_ordered_stock: ! 'Warnung: Die rot markierten Artikel wurden in der laufenden Lagerbestellung bereits bestellt bzw. gekauft. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen bzw. Käufe dieses Artikels gelöscht und nicht abgerechnet!'
|
||||||
new:
|
new:
|
||||||
title: Neue Bestellung anlegen
|
title: Neue Bestellung anlegen
|
||||||
|
receive:
|
||||||
|
add_article: Artikel hinzufügen
|
||||||
|
consider_member_tolerance: Toleranz berücksichtigen
|
||||||
|
notice: ! 'Bestellung in Empfang genommen: %{msg}'
|
||||||
|
notice_none: Keine neuen Artikel für den Empfang ausgewählt.
|
||||||
|
rest_to_stock: Rest ins Lager
|
||||||
|
submit: Bestellung in Empfang nehmen
|
||||||
|
surplus_options: 'Verteilungsoptionen:'
|
||||||
|
title: »%{order}« in Empfang nehmen
|
||||||
show:
|
show:
|
||||||
action_end: Beenden!
|
action_end: Beenden!
|
||||||
amounts: ! 'Netto/Bruttosumme:'
|
amounts: ! 'Netto/Bruttosumme:'
|
||||||
|
@ -1162,6 +1188,11 @@ de:
|
||||||
open: laufend
|
open: laufend
|
||||||
update:
|
update:
|
||||||
notice: Die Bestellung wurde aktualisiert.
|
notice: Die Bestellung wurde aktualisiert.
|
||||||
|
update_order_amounts:
|
||||||
|
update_order_amounts:
|
||||||
|
msg1: "%{count} Artikel (%{units} Einheiten) aktualisiert"
|
||||||
|
msg2: "%{count} (%{units}) Toleranzmenge"
|
||||||
|
msg4: "%{count} (%{units}) übrig"
|
||||||
pages:
|
pages:
|
||||||
all:
|
all:
|
||||||
new_page: Neue Seite anlegen
|
new_page: Neue Seite anlegen
|
||||||
|
|
|
@ -77,10 +77,17 @@ en:
|
||||||
note: Note
|
note: Note
|
||||||
starts: Starts at
|
starts: Starts at
|
||||||
status: Status
|
status: Status
|
||||||
|
supplier: Supplier
|
||||||
order_article:
|
order_article:
|
||||||
|
article: Article
|
||||||
missing_units: Missing units
|
missing_units: Missing units
|
||||||
missing_units_short: Missing
|
missing_units_short: Missing
|
||||||
units_to_order: Amount of units
|
quantity: Desired amount
|
||||||
|
quantity_short: Desired
|
||||||
|
units_received: Received units
|
||||||
|
units_received_short: Received
|
||||||
|
units_to_order: Ordered units
|
||||||
|
units_to_order_short: Ordered
|
||||||
update_current_price: Globally update current price
|
update_current_price: Globally update current price
|
||||||
order_comment:
|
order_comment:
|
||||||
text: Add comment to this order ...
|
text: Add comment to this order ...
|
||||||
|
@ -617,12 +624,6 @@ en:
|
||||||
show:
|
show:
|
||||||
back: Back
|
back: Back
|
||||||
title: Invoice %{number}
|
title: Invoice %{number}
|
||||||
order_articles:
|
|
||||||
edit:
|
|
||||||
stock_alert: The price of stock articles cannot be changed!
|
|
||||||
title: Update article
|
|
||||||
new:
|
|
||||||
title: Add delivered article to order
|
|
||||||
ordergroups:
|
ordergroups:
|
||||||
index:
|
index:
|
||||||
new_transaction: Add new transactions
|
new_transaction: Add new transactions
|
||||||
|
@ -745,6 +746,7 @@ en:
|
||||||
new_invoice: New invoice
|
new_invoice: New invoice
|
||||||
show_invoice: Show invoice
|
show_invoice: Show invoice
|
||||||
orders:
|
orders:
|
||||||
|
old_price: Old price
|
||||||
option_choose: Choose supplier/stock
|
option_choose: Choose supplier/stock
|
||||||
option_stock: Stock
|
option_stock: Stock
|
||||||
order_pdf: Create PDF
|
order_pdf: Create PDF
|
||||||
|
@ -1089,6 +1091,12 @@ en:
|
||||||
model:
|
model:
|
||||||
error_single_group: ! '%{user} is already a member of another ordergroup'
|
error_single_group: ! '%{user} is already a member of another ordergroup'
|
||||||
invalid_balance: is not a valid number
|
invalid_balance: is not a valid number
|
||||||
|
order_articles:
|
||||||
|
edit:
|
||||||
|
stock_alert: The price of stock articles cannot be changed!
|
||||||
|
title: Update article
|
||||||
|
new:
|
||||||
|
title: Add delivered article to order
|
||||||
orders:
|
orders:
|
||||||
articles:
|
articles:
|
||||||
article_count: ! 'Ordered articles:'
|
article_count: ! 'Ordered articles:'
|
||||||
|
@ -1100,6 +1108,9 @@ en:
|
||||||
notice: The order was created.
|
notice: The order was created.
|
||||||
edit:
|
edit:
|
||||||
title: Edit order
|
title: Edit order
|
||||||
|
edit_amount:
|
||||||
|
field_locked_title: The distribution of this article among the ordergroups was changed manually. This field is locked to protect those changes. To redistribute and overwrite those changes, press the unlock button and change the amount.
|
||||||
|
field_unlocked_title: The distribution of this article among the ordergroups was changed manually. When you change the amount, those manual changes will be overwritten.
|
||||||
fax:
|
fax:
|
||||||
amount: Amount
|
amount: Amount
|
||||||
articles: Articles
|
articles: Articles
|
||||||
|
@ -1118,12 +1129,14 @@ en:
|
||||||
title: Article
|
title: Article
|
||||||
index:
|
index:
|
||||||
action_end: Close
|
action_end: Close
|
||||||
|
action_receive: Receive
|
||||||
confirm_delete: Do you really want to delete the order?
|
confirm_delete: Do you really want to delete the order?
|
||||||
confirm_end: Do you really want to close the order %{order}? There is no going back.
|
confirm_end: Do you really want to close the order %{order}? There is no going back.
|
||||||
ended_orders: Closed orders
|
|
||||||
new_order: Create new order
|
new_order: Create new order
|
||||||
no_open_orders: There are currently no open orders.
|
no_open_or_finished_orders: There are currently no open or closed orders.
|
||||||
open_orders: Current orders
|
orders_finished: Closed
|
||||||
|
orders_open: Open
|
||||||
|
orders_settled: Settled
|
||||||
title: Manage orders
|
title: Manage orders
|
||||||
model:
|
model:
|
||||||
error_closed: Order was already settled
|
error_closed: Order was already settled
|
||||||
|
@ -1135,6 +1148,15 @@ en:
|
||||||
warning_ordered_stock: ! 'Warning: Articles marked red have already been ordered/purchased within this open stock order. If you uncheck them here, all existing orders/purchases of these articles will be deleted and it will not be accounted for them.'
|
warning_ordered_stock: ! 'Warning: Articles marked red have already been ordered/purchased within this open stock order. If you uncheck them here, all existing orders/purchases of these articles will be deleted and it will not be accounted for them.'
|
||||||
new:
|
new:
|
||||||
title: Create new order
|
title: Create new order
|
||||||
|
receive:
|
||||||
|
add_article: Add article
|
||||||
|
consider_member_tolerance: consider tolerance
|
||||||
|
notice: ! 'Order received: %{msg}'
|
||||||
|
notice_none: No new articles to receive
|
||||||
|
rest_to_stock: rest to stock
|
||||||
|
submit: Receive order
|
||||||
|
surplus_options: 'Distribution options:'
|
||||||
|
title: Receiving %{order}
|
||||||
show:
|
show:
|
||||||
action_end: Close!
|
action_end: Close!
|
||||||
amounts: ! 'Net/gross sum:'
|
amounts: ! 'Net/gross sum:'
|
||||||
|
@ -1166,6 +1188,11 @@ en:
|
||||||
open: open
|
open: open
|
||||||
update:
|
update:
|
||||||
notice: The order was updated.
|
notice: The order was updated.
|
||||||
|
update_order_amounts:
|
||||||
|
msg1: "%{count} articles (%{units} units) updated"
|
||||||
|
msg2: "%{count} (%{units}) using tolerance"
|
||||||
|
msg3: "%{count} (%{units}) go to stock if foodsoft would support that [don't translate]"
|
||||||
|
msg4: "%{count} (%{units}) left over"
|
||||||
pages:
|
pages:
|
||||||
all:
|
all:
|
||||||
new_page: Create new page
|
new_page: Create new page
|
||||||
|
|
|
@ -78,6 +78,7 @@ fr:
|
||||||
starts: Ouverture le
|
starts: Ouverture le
|
||||||
status:
|
status:
|
||||||
order_article:
|
order_article:
|
||||||
|
article: Article
|
||||||
missing_units: Unités manquantes
|
missing_units: Unités manquantes
|
||||||
missing_units_short:
|
missing_units_short:
|
||||||
units_to_order: Quantité
|
units_to_order: Quantité
|
||||||
|
@ -627,12 +628,6 @@ fr:
|
||||||
show:
|
show:
|
||||||
back: Retour
|
back: Retour
|
||||||
title: Facture %{number}
|
title: Facture %{number}
|
||||||
order_articles:
|
|
||||||
edit:
|
|
||||||
stock_alert:
|
|
||||||
title: Mettre à jour la liste des article
|
|
||||||
new:
|
|
||||||
title:
|
|
||||||
ordergroups:
|
ordergroups:
|
||||||
index:
|
index:
|
||||||
new_transaction: Saisir une nouvelle transaction
|
new_transaction: Saisir une nouvelle transaction
|
||||||
|
@ -1094,6 +1089,12 @@ fr:
|
||||||
model:
|
model:
|
||||||
error_single_group: ! '%{user} fait déjà partie d''une autre cellule'
|
error_single_group: ! '%{user} fait déjà partie d''une autre cellule'
|
||||||
invalid_balance: n'est pas un nombre valide
|
invalid_balance: n'est pas un nombre valide
|
||||||
|
order_articles:
|
||||||
|
edit:
|
||||||
|
stock_alert:
|
||||||
|
title: Mettre à jour la liste des article
|
||||||
|
new:
|
||||||
|
title:
|
||||||
orders:
|
orders:
|
||||||
articles:
|
articles:
|
||||||
article_count: ! 'Articles commandés:'
|
article_count: ! 'Articles commandés:'
|
||||||
|
@ -1125,10 +1126,11 @@ fr:
|
||||||
action_end: Terminer
|
action_end: Terminer
|
||||||
confirm_delete: Vraiment supprimer la commande?
|
confirm_delete: Vraiment supprimer la commande?
|
||||||
confirm_end: Veux tu vraiment mettre fin à la commande %{order}? Attention, il n'y aura pas d'annulation possible.
|
confirm_end: Veux tu vraiment mettre fin à la commande %{order}? Attention, il n'y aura pas d'annulation possible.
|
||||||
ended_orders: Commandes closes
|
|
||||||
new_order: Définir une nouvelle commande
|
new_order: Définir une nouvelle commande
|
||||||
no_open_orders: Il n'y a aucune commande en cours en ce moment.
|
no_open_or_finished_orders: Il n'y a aucune commande en cours en ce moment.
|
||||||
open_orders: Commandes en cours
|
orders_finished: Close
|
||||||
|
orders_open: En cours
|
||||||
|
orders_settled: Décomptée
|
||||||
title: Gestion des commandes
|
title: Gestion des commandes
|
||||||
model:
|
model:
|
||||||
error_closed: Cette commande a déjà été décomptée
|
error_closed: Cette commande a déjà été décomptée
|
||||||
|
|
|
@ -78,6 +78,7 @@ nl:
|
||||||
starts: Start op
|
starts: Start op
|
||||||
status: Status
|
status: Status
|
||||||
order_article:
|
order_article:
|
||||||
|
article: Artikel
|
||||||
missing_units: Missende eenheden
|
missing_units: Missende eenheden
|
||||||
missing_units_short: Nodig
|
missing_units_short: Nodig
|
||||||
units_to_order: Aantal eenheden
|
units_to_order: Aantal eenheden
|
||||||
|
@ -617,12 +618,6 @@ nl:
|
||||||
show:
|
show:
|
||||||
back: Terug
|
back: Terug
|
||||||
title: Factuur %{number}
|
title: Factuur %{number}
|
||||||
order_articles:
|
|
||||||
edit:
|
|
||||||
stock_alert: De prijs van voorraadartikelen kan niet aangepast worden!
|
|
||||||
title: Artikel bijwerken
|
|
||||||
new:
|
|
||||||
title: Geleverd artikel aan bestelling toevoegen
|
|
||||||
ordergroups:
|
ordergroups:
|
||||||
index:
|
index:
|
||||||
new_transaction: Nieuwe transacties toevoegen
|
new_transaction: Nieuwe transacties toevoegen
|
||||||
|
@ -1069,6 +1064,12 @@ nl:
|
||||||
model:
|
model:
|
||||||
error_single_group: ! '%{user} behoort al tot een ander huishouden'
|
error_single_group: ! '%{user} behoort al tot een ander huishouden'
|
||||||
invalid_balance: is geen geldig nummer
|
invalid_balance: is geen geldig nummer
|
||||||
|
order_articles:
|
||||||
|
edit:
|
||||||
|
stock_alert: De prijs van voorraadartikelen kan niet aangepast worden!
|
||||||
|
title: Artikel bijwerken
|
||||||
|
new:
|
||||||
|
title: Geleverd artikel aan bestelling toevoegen
|
||||||
orders:
|
orders:
|
||||||
articles:
|
articles:
|
||||||
article_count: ! 'Bestelde artikelen:'
|
article_count: ! 'Bestelde artikelen:'
|
||||||
|
@ -1100,10 +1101,11 @@ nl:
|
||||||
action_end: Sluiten
|
action_end: Sluiten
|
||||||
confirm_delete: Wil je de bestelling werkelijk verwijderen?
|
confirm_delete: Wil je de bestelling werkelijk verwijderen?
|
||||||
confirm_end: Wil je de bestelling %{order} werkelijk sluiten? Dit kun je niet ongedaan maken.
|
confirm_end: Wil je de bestelling %{order} werkelijk sluiten? Dit kun je niet ongedaan maken.
|
||||||
ended_orders: Gesloten bestellingen
|
|
||||||
new_order: Nieuwe bestelling openen
|
new_order: Nieuwe bestelling openen
|
||||||
no_open_orders: Er zijn momenteel geen lopende bestellingen.
|
no_open_or_finished_orders: Er zijn momenteel geen open of gesloten bestellingen.
|
||||||
open_orders: Lopende bestellingen
|
orders_finished: Gesloten
|
||||||
|
orders_open: Open
|
||||||
|
orders_settled: Afgerekend
|
||||||
title: Bestellingen beheren
|
title: Bestellingen beheren
|
||||||
model:
|
model:
|
||||||
error_closed: Bestelling was al afgerekend
|
error_closed: Bestelling was al afgerekend
|
||||||
|
|
|
@ -38,7 +38,15 @@ Foodsoft::Application.routes.draw do
|
||||||
member do
|
member do
|
||||||
post :finish
|
post :finish
|
||||||
post :add_comment
|
post :add_comment
|
||||||
|
|
||||||
|
get :receive
|
||||||
|
post :receive
|
||||||
|
|
||||||
|
get :receive_on_order_article_create
|
||||||
|
get :receive_on_order_article_update
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :order_articles
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :group_orders do
|
resources :group_orders do
|
||||||
|
@ -141,9 +149,10 @@ Foodsoft::Application.routes.draw do
|
||||||
get :confirm
|
get :confirm
|
||||||
put :close
|
put :close
|
||||||
put :close_direct
|
put :close_direct
|
||||||
end
|
|
||||||
|
|
||||||
resources :order_articles
|
get :new_on_order_article_create
|
||||||
|
get :new_on_order_article_update
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :group_order_articles do
|
resources :group_order_articles do
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddQuantitiesToOrderArticle < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :order_articles, :units_billed, :integer
|
||||||
|
add_column :order_articles, :units_received, :integer
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddResultComputedToGroupOrderArticles < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :group_order_articles, :result_computed,
|
||||||
|
:decimal, :precision => 8, :scale => 3
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20130920201529) do
|
ActiveRecord::Schema.define(:version => 20140102170431) do
|
||||||
|
|
||||||
create_table "article_categories", :force => true do |t|
|
create_table "article_categories", :force => true do |t|
|
||||||
t.string "name", :default => "", :null => false
|
t.string "name", :default => "", :null => false
|
||||||
|
@ -101,6 +101,7 @@ ActiveRecord::Schema.define(:version => 20130920201529) do
|
||||||
t.integer "tolerance", :default => 0, :null => false
|
t.integer "tolerance", :default => 0, :null => false
|
||||||
t.datetime "updated_on", :null => false
|
t.datetime "updated_on", :null => false
|
||||||
t.decimal "result", :precision => 8, :scale => 3
|
t.decimal "result", :precision => 8, :scale => 3
|
||||||
|
t.decimal "result_computed", :precision => 8, :scale => 3
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "group_order_articles", ["group_order_id", "order_article_id"], :name => "goa_index", :unique => true
|
add_index "group_order_articles", ["group_order_id", "order_article_id"], :name => "goa_index", :unique => true
|
||||||
|
@ -195,6 +196,8 @@ ActiveRecord::Schema.define(:version => 20130920201529) do
|
||||||
t.integer "units_to_order", :default => 0, :null => false
|
t.integer "units_to_order", :default => 0, :null => false
|
||||||
t.integer "lock_version", :default => 0, :null => false
|
t.integer "lock_version", :default => 0, :null => false
|
||||||
t.integer "article_price_id"
|
t.integer "article_price_id"
|
||||||
|
t.integer "units_billed"
|
||||||
|
t.integer "units_received"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "order_articles", ["order_id", "article_id"], :name => "index_order_articles_on_order_id_and_article_id", :unique => true
|
add_index "order_articles", ["order_id", "article_id"], :name => "index_order_articles_on_order_id_and_article_id", :unique => true
|
||||||
|
|
|
@ -24,8 +24,4 @@ FactoryGirl.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# requires order and article
|
|
||||||
factory :order_article do
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
106
spec/integration/receive_spec.rb
Normal file
106
spec/integration/receive_spec.rb
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
require_relative '../spec_helper'
|
||||||
|
|
||||||
|
describe 'receiving an order', :type => :feature do
|
||||||
|
let(:admin) { create :user, groups:[create(:workgroup, role_orders: true)] }
|
||||||
|
let(:supplier) { create :supplier }
|
||||||
|
let(:article) { create :article, supplier: supplier, unit_quantity: 3 }
|
||||||
|
let(:order) { create :order, supplier: supplier, article_ids: [article.id] } # need to ref article
|
||||||
|
let(:go1) { create :group_order, order: order }
|
||||||
|
let(:go2) { create :group_order, order: order }
|
||||||
|
let(:oa) { order.order_articles.find_by_article_id(article.id) }
|
||||||
|
let(:goa1) { create :group_order_article, group_order: go1, order_article: oa }
|
||||||
|
let(:goa2) { create :group_order_article, group_order: go2, order_article: oa }
|
||||||
|
|
||||||
|
# set quantities of group_order_articles
|
||||||
|
def set_quantities(q1, q2)
|
||||||
|
goa1.update_quantities(*q1)
|
||||||
|
goa2.update_quantities(*q2)
|
||||||
|
oa.update_results!
|
||||||
|
order.finish!(admin)
|
||||||
|
reload_articles
|
||||||
|
end
|
||||||
|
|
||||||
|
# reload all group_order_articles
|
||||||
|
def reload_articles
|
||||||
|
[goa1, goa2].map(&:reload)
|
||||||
|
oa.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_quantities(units, q1, q2)
|
||||||
|
reload_articles
|
||||||
|
expect(oa.units).to eq units
|
||||||
|
expect(goa1.result).to be_within(1e-3).of q1
|
||||||
|
expect(goa2.result).to be_within(1e-3).of q2
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
describe :type => :feature, :js => true do
|
||||||
|
before { login admin }
|
||||||
|
|
||||||
|
it 'has product ordered visible' do
|
||||||
|
set_quantities [3,0], [0,0]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
expect(page).to have_content(article.name)
|
||||||
|
expect(page).to have_selector("#order_article_#{oa.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has product not ordered invisible' do
|
||||||
|
set_quantities [0,0], [0,0]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
expect(page).to_not have_selector("#order_article_#{oa.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is not received by default' do
|
||||||
|
set_quantities [3,0], [0,0]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
expect(find("#order_articles_#{oa.id}_units_received").value).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not change anything when received is ordered' do
|
||||||
|
set_quantities [2,0], [3,2]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
fill_in "order_articles_#{oa.id}_units_received", :with => oa.units_to_order
|
||||||
|
find('input[type="submit"]').click
|
||||||
|
expect(page).to have_selector('body')
|
||||||
|
check_quantities 2, 2, 4
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redistributes properly when received is more' do
|
||||||
|
set_quantities [2,0], [3,2]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
fill_in "order_articles_#{oa.id}_units_received", :with => 3
|
||||||
|
find('input[type="submit"]').click
|
||||||
|
expect(page).to have_selector('body')
|
||||||
|
check_quantities 3, 2, 5
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redistributes properly when received is less' do
|
||||||
|
set_quantities [2,0], [3,2]
|
||||||
|
visit receive_order_path(order)
|
||||||
|
fill_in "order_articles_#{oa.id}_units_received", :with => 1
|
||||||
|
find('input[type="submit"]').click
|
||||||
|
expect(page).to have_selector('body')
|
||||||
|
check_quantities 1, 2, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a locked field when edited elsewhere' do
|
||||||
|
set_quantities [2,0], [3,2]
|
||||||
|
goa1.result = goa1.result + 1
|
||||||
|
goa1.save!
|
||||||
|
visit receive_order_path(order)
|
||||||
|
expect(find("#order_articles_#{oa.id}_units_received")).to be_disabled
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'leaves locked rows alone when submitted' do
|
||||||
|
set_quantities [2,0], [3,2]
|
||||||
|
goa1.result = goa1.result + 1
|
||||||
|
goa1.save!
|
||||||
|
visit receive_order_path(order)
|
||||||
|
find('input[type="submit"]').click
|
||||||
|
expect(page).to have_selector('body')
|
||||||
|
check_quantities 2, 3, 4
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
120
spec/models/order_article_spec.rb
Normal file
120
spec/models/order_article_spec.rb
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe OrderArticle do
|
||||||
|
let(:order) { FactoryGirl.create :order, article_count: 1 }
|
||||||
|
let(:oa) { order.order_articles.first }
|
||||||
|
|
||||||
|
it 'is not ordered by default' do
|
||||||
|
expect(OrderArticle.ordered.count).to eq 0
|
||||||
|
end
|
||||||
|
|
||||||
|
[:units_to_order, :units_billed, :units_received].each do |units|
|
||||||
|
|
||||||
|
it "is ordered when there are #{units.to_s.gsub '_', ' '}" do
|
||||||
|
oa.update_attribute units, rand(1..99)
|
||||||
|
expect(OrderArticle.ordered.count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'knows how many items there are' do
|
||||||
|
oa.units_to_order = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_to_order
|
||||||
|
oa.units_billed = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_billed
|
||||||
|
oa.units_received = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_received
|
||||||
|
|
||||||
|
oa.units_billed = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_received
|
||||||
|
oa.units_to_order = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_received
|
||||||
|
oa.units_received = rand(1..99)
|
||||||
|
expect(oa.units).to eq oa.units_received
|
||||||
|
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 }
|
||||||
|
|
||||||
|
# set quantities of group_order_articles
|
||||||
|
def set_quantities(q1, q2, q3)
|
||||||
|
goa1.update_quantities(*q1)
|
||||||
|
goa2.update_quantities(*q2)
|
||||||
|
goa3.update_quantities(*q3)
|
||||||
|
oa.update_results!
|
||||||
|
order.finish!(admin)
|
||||||
|
goa_reload
|
||||||
|
end
|
||||||
|
|
||||||
|
# reload all group_order_articles
|
||||||
|
def goa_reload
|
||||||
|
[goa1, goa2, goa3].map(&:reload)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has expected units_to_order' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.units*oa.article.unit_quantity).to eq 6
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [4, 1, 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does nothing when nothing has changed' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.redistribute 6, [:tolerance, nil]).to eq [1, 0]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result).map(&:to_i)).to eq [4, 1, 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when there is nothing to distribute' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.redistribute 0, [:tolerance, nil]).to eq [0, 0]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [0, 0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when quantity needs to be reduced' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.redistribute 4, [:tolerance, nil]).to eq [0, 0]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [3, 1, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when quantity is increased within quantity' do
|
||||||
|
set_quantities [3,0], [2,0], [2,0]
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [3, 2, 1]
|
||||||
|
expect(oa.redistribute 7, [:tolerance, nil]).to eq [0, 0]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result).map(&:to_i)).to eq [3, 2, 2]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when there is just one for the first' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.redistribute 1, [:tolerance, nil]).to eq [0, 0]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [1, 0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when there is tolerance and left-over' do
|
||||||
|
set_quantities [3,2], [1,1], [1,0]
|
||||||
|
expect(oa.redistribute 10, [:tolerance, nil]).to eq [3, 2]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [5, 2, 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works when redistributing without tolerance' do
|
||||||
|
set_quantities [3,2], [1,3], [1,0]
|
||||||
|
expect(oa.redistribute 8, [nil]).to eq [3]
|
||||||
|
goa_reload
|
||||||
|
expect([goa1, goa2, goa3].map(&:result)).to eq [3, 1, 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in a new issue