Merge branch 'master' into updated-gems

Conflicts:
	Gemfile
	Gemfile.lock
This commit is contained in:
wvengen 2013-10-09 10:37:02 +02:00
commit bdce1b5872
64 changed files with 1039 additions and 472 deletions

View file

@ -7,6 +7,10 @@
//= require bootstrap-datepicker/locales/bootstrap-datepicker.de
//= require bootstrap-datepicker/locales/bootstrap-datepicker.nl
//= require jquery.observe_field
//= require list
//= require list.unlist
//= require list.delay
//= require list.reset
//= require rails.validations
//= require rails.validations.simple_form
//= require_self

View file

@ -0,0 +1,50 @@
// for use with listjs 0.2.0
// https://github.com/javve/list.js
(function(window, undefined) {
window.List.prototype.plugins.delay = function(locals, options) {
var list = this;
this.searchTimeout = undefined;
var init = {
start: function(options) {
this.defaults(options);
this.callbacks(options);
this.onload(options);
},
defaults: function(options) {
options.delayedSearchClass = options.delayedSearchClass || 'delayed-search';
options.delayedSearchTime = options.delayedSearchTime || 500;
},
callbacks: function(options) {
$('.' + options.delayedSearchClass, list.listContainer).keyup(list.searchDelayStart);
},
onload: function(options) {
var initialSearchTerm = $('.' + options.delayedSearchClass, list.listContainer).val();
if('' != initialSearchTerm) {
list.search(initialSearchTerm);
}
}
};
this.searchDelayStart = function(searchString, columns) {
// TODO: if keycode corresponds to 'ENTER' ? skip delay
clearTimeout(list.searchTimeout);
list.searchTimeout = window.setTimeout(
function() {list.searchDelayEnd(searchString, columns)},
options.delayedSearchTime
);
$(list.listContainer).trigger('updateComing');
};
this.searchDelayEnd = function(searchString, columns) {
list.search(searchString, columns);
};
init.start(options);
}
})(window);

View file

@ -0,0 +1,42 @@
// for use with listjs 0.2.0
// https://github.com/javve/list.js
(function(window, undefined) {
window.List.prototype.plugins.reset = function(locals, options) {
var list = this;
var init = {
start: function(options) {
this.defaults(options);
this.callbacks(options);
},
defaults: function(options) {
options.highlightClass = options.highlightClass || 'btn-primary';
options.resetSearchClass = options.resetSearchClass || 'reset-search';
options.resettableClass = options.resettableClass || 'resettable';
},
callbacks: function(options) {
$('.' + options.resetSearchClass, list.listContainer).click(list.resetSearch);
list.on('updated', list.highlightResetButton);
$(list.listContainer).on('updateComing', function() {
list.highlightResetButton(false);
});
}
};
this.highlightResetButton = function(highlightEnabled) {
highlightEnabled = (undefined === highlightEnabled) ? (list.searched) : (highlightEnabled);
$('.' + options.resetSearchClass, list.listContainer).toggleClass(options.highlightClass, highlightEnabled);
};
this.resetSearch = function() {
$('.' + options.resettableClass, list.listContainer).val('');
list.search('');
};
init.start(options);
}
})(window);

View file

@ -0,0 +1,124 @@
// for use with listjs 0.2.0
// https://github.com/javve/list.js
/*******************************************************************************
********************************************************************************
The following code is a modification of list.js. It was created by copy-pasting
the original code with the copyright notice below.
********************************************************************************
*******************************************************************************/
/*******************************************************************************
Begin copyright notice of the original code
*******************************************************************************/
/*
ListJS Beta 0.2.0
By Jonny Strömberg (www.jonnystromberg.com, www.listjs.com)
OBS. The API is not frozen. It MAY change!
License (MIT)
Copyright (c) 2011 Jonny Strömberg http://jonnystromberg.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
/*******************************************************************************
End copyright notice of the original code
*******************************************************************************/
(function(w, undefined) {
/*******************************************************************************
Begin copy-pasted and modified code
*******************************************************************************/
// * template engine which adds class 'unlisted' instead of removing from DOM
// * especially useful in case of formulars
// * uses jQuery's $
w.List.prototype.templateEngines.unlist = function(list, settings) {
var h = w.ListJsHelpers;
// start with standard engine, override specific methods afterwards
this.superClass = w.List.prototype.templateEngines.standard;
this.superClass(list, settings);
// todo refer to listjs code instead of copy-pasting
var listSource = h.getByClass(settings.listClass, list.listContainer, true);
var templater = this;
var ensure = {
created: function(item) {
if(item.elm === undefined) {
templater.create(item);
}
}
};
var init = {
start: function(options) {
this.defaults(options);
this.callbacks(options);
},
defaults: function(options) {
options.listHeadingsClass = options.listHeadingsClass || 'list-heading';
},
callbacks: function(options) {
list.on('updated', templater.updateListHeadings);
}
};
this.show = function(item) {
ensure.created(item);
listSource.appendChild(item.elm); // append item (or move it to the end)
$(item.elm).removeClass('unlisted');
};
this.hide = function(item) {
ensure.created(item);
$(item.elm).addClass('unlisted');
listSource.appendChild(item.elm);
};
this.clear = function() {
$(listSource.childNodes).addClass('unlisted');
};
this.updateListHeadings = function() {
var headSel = '.' + settings.listHeadingsClass;
$(headSel, listSource).each(function() {
var listedCount = $(this).nextUntil(headSel, ':not(.unlisted)').length;
$(this).toggleClass('unlisted', 0==listedCount);
});
};
init.start(settings);
};
/*******************************************************************************
End copy-pasted and modified code
*******************************************************************************/
})(window);

View file

@ -3,4 +3,5 @@
*= require select2
*= require token-input-bootstrappy
*= require bootstrap-datepicker
*= require list.unlist
*/

View file

@ -237,3 +237,8 @@ tr.unavailable {
margin-bottom: 15px
}
}
// allow buttons as input add-on (with proper height)
.input-append button.add-on {
height: inherit;
}

View file

@ -0,0 +1,3 @@
.list .unlisted:not(.no-unlist) {
display: none;
}

View file

@ -26,7 +26,7 @@ class ApplicationController < ActionController::Base
def deny_access
session[:return_to] = request.original_url
redirect_to login_url, :alert => 'Access denied!'
redirect_to login_url, :alert => I18n.t('application.controller.error_denied')
end
private
@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base
# No user at all: redirect to login page.
session[:user_id] = nil
session[:return_to] = request.original_url
redirect_to login_url, :alert => 'Authentication required!'
redirect_to login_url, :alert => I18n.t('application.controller.error_authn')
else
# We have an authenticated user, now check role...
# Roles gets the user through his memberships.
@ -83,7 +83,7 @@ class ApplicationController < ActionController::Base
def authenticate_membership_or_admin
@group = Group.find(params[:id])
unless @group.member?(@current_user) or @current_user.role_admin?
redirect_to root_path, alert: "Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!"
redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
end
end

View file

@ -10,7 +10,7 @@ class Finance::BalancingController < Finance::BaseController
flash.now.alert = t('finance.balancing.new.alert') if @order.closed?
@comments = @order.comments
@articles = @order.order_articles.ordered.includes(:article, :article_price,
@articles = @order.order_articles.ordered_or_member.includes(:article, :article_price,
group_order_articles: {group_order: :ordergroup})
sort_param = params['sort'] || 'name'

View file

@ -34,7 +34,7 @@ class Finance::FinancialTransactionsController < ApplicationController
@financial_transaction = FinancialTransaction.new(params[:financial_transaction])
@financial_transaction.user = current_user
@financial_transaction.add_transaction!
redirect_to finance_ordergroup_transactions_url(@ordergroup), notice: t('finance.financial_transactions.create.notice')
redirect_to finance_ordergroup_transactions_url(@ordergroup), notice: I18n.t('finance.financial_transactions.controller.create.notice')
rescue ActiveRecord::RecordInvalid => error
flash.now[:alert] = error.message
render :action => :new
@ -44,16 +44,16 @@ class Finance::FinancialTransactionsController < ApplicationController
end
def create_collection
raise "Notiz wird benötigt!" if params[:note].blank?
raise I18n.t('finance.financial_transactions.controller.create_collection.error_note_required') if params[:note].blank?
params[:financial_transactions].each do |trans|
# ignore empty amount fields ...
unless trans[:amount].blank?
Ordergroup.find(trans[:ordergroup_id]).add_financial_transaction!(trans[:amount], params[:note], @current_user)
end
end
redirect_to finance_ordergroups_url, notice: t('finance.create_collection.create.notice')
redirect_to finance_ordergroups_url, notice: I18n.t('finance.financial_transactions.controller.create_collection.notice')
rescue => error
redirect_to finance_new_transaction_collection_url, alert: t('finance.create_collection.create.alert', error: error.to_s)
redirect_to finance_new_transaction_collection_url, alert: I18n.t('finance.financial_transactions.controller.create_collection.alert', error: error.to_s)
end
protected

View file

@ -65,7 +65,13 @@ class Finance::GroupOrderArticlesController < ApplicationController
def destroy
group_order_article = GroupOrderArticle.find(params[:id])
group_order_article.destroy
# only destroy if quantity and tolerance was zero already, so that we don't
# lose what the user ordered, if any
if group_order_article.quantity > 0 or group_order_article.tolerance >0
group_order_article.update_attribute(:result, 0)
else
group_order_article.destroy
end
update_summaries(group_order_article)
@order_article = group_order_article.order_article

View file

@ -42,6 +42,14 @@ class Finance::OrderArticlesController < ApplicationController
def destroy
@order_article = OrderArticle.find(params[:id])
@order_article.destroy
# only destroy if there are no associated GroupOrders; if we would, the requested
# quantity and tolerance would be gone. Instead of destroying, we set all result
# quantities to zero.
if @order_article.group_order_articles.count == 0
@order_article.destroy
else
@order_article.group_order_articles.each { |goa| goa.update_attribute(:result, 0) }
@order_article.update_results!
end
end
end

View file

@ -31,6 +31,11 @@ class StockitController < ApplicationController
end
end
def show
@stock_article = StockArticle.find(params[:id])
@stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC')
end
def destroy
@article = StockArticle.find(params[:id])
@article.mark_as_deleted
@ -55,9 +60,4 @@ class StockitController < ApplicationController
render :partial => 'form', :locals => {:stock_article => stock_article}
end
def history
@stock_article = StockArticle.undeleted.find(params[:stock_article_id])
@stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC').each {|s| s.readonly!}
end
end

View file

@ -12,20 +12,29 @@ class OrderByArticles < OrderPdf
def body
@order.order_articles.ordered.each do |order_article|
text "#{order_article.article.name} (#{order_article.article.unit} | #{order_article.price.unit_quantity.to_s} | #{number_with_precision(order_article.price.fc_price, precision: 2)})",
style: :bold, size: 10
rows = []
rows << I18n.t('documents.order_by_articles.rows')
for goa in order_article.group_order_articles
dimrows = []
for goa in order_article.group_order_articles.ordered
rows << [goa.group_order.ordergroup.name,
"#{goa.quantity} + #{goa.tolerance}",
goa.result,
number_with_precision(order_article.price.fc_price * goa.result, precision: 2)]
dimrows << rows.length if goa.result == 0
end
next if rows.length == 0
rows.unshift I18n.t('documents.order_by_articles.rows') # table header
table rows, column_widths: [200,40,40], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.columns(1..2).align = :right
text "#{order_article.article.name} (#{order_article.article.unit} | #{order_article.price.unit_quantity.to_s} | #{number_with_precision(order_article.price.fc_price, precision: 2)})",
style: :bold, size: 10
table rows, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.column(0).width = 200
table.columns(1..3).align = :right
table.column(2).font_style = :bold
table.cells.border_width = 1
table.cells.border_color = '666666'
table.rows(0).border_bottom_width = 2
# dim rows which were ordered but not received
dimrows.each { |ri| table.row(ri).text_color = '999999' }
end
move_down 10
end

View file

@ -12,12 +12,10 @@ class OrderByGroups < OrderPdf
def body
# Start rendering
@order.group_orders.each do |group_order|
text group_order.ordergroup.name, size: 9, style: :bold
@order.group_orders.ordered.each do |group_order|
total = 0
rows = []
rows << I18n.t('documents.order_by_groups.rows') # Table Header
dimrows = []
group_order_articles = group_order.group_order_articles.ordered
group_order_articles.each do |goa|
@ -25,15 +23,20 @@ class OrderByGroups < OrderPdf
sub_total = price * goa.result
total += sub_total
rows << [goa.order_article.article.name,
"#{goa.quantity} + #{goa.tolerance}",
goa.result,
number_with_precision(price, precision: 2),
goa.order_article.price.unit_quantity,
goa.order_article.article.unit,
number_with_precision(sub_total, precision: 2)]
dimrows << rows.length if goa.result == 0
end
rows << [ I18n.t('documents.order_by_groups.sum'), nil, nil, nil, nil, number_with_precision(total, precision: 2)]
next if rows.length == 0
rows << [ I18n.t('documents.order_by_groups.sum'), nil, nil, nil, nil, nil, number_with_precision(total, precision: 2)]
rows.unshift I18n.t('documents.order_by_groups.rows') # Table Header
table rows, column_widths: [250,50,50,50,50,50], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
text group_order.ordergroup.name, size: 9, style: :bold
table rows, width: 500, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
# borders
table.cells.borders = []
table.row(0).borders = [:bottom]
@ -41,8 +44,14 @@ class OrderByGroups < OrderPdf
table.cells.border_width = 1
table.cells.border_color = '666666'
table.columns(1..3).align = :right
table.columns(5).align = :right
table.column(0).width = 240
table.column(2).font_style = :bold
table.columns(1..4).align = :right
table.column(6).align = :right
table.column(6).font_style = :bold
# dim rows which were ordered but not received
dimrows.each { |ri| table.row(ri).text_color = '999999' }
end
move_down 15

View file

@ -1,6 +1,5 @@
# encoding: utf-8
class Article < ActiveRecord::Base
extend ActiveSupport::Memoizable # Ability to cache method results. Use memoize :expensive_method
# Replace numeric seperator with database format
localize_input_of :price, :tax, :deposit
@ -44,11 +43,12 @@ class Article < ActiveRecord::Base
# If the article is used in an open Order, the Order will be returned.
def in_open_order
order_articles = OrderArticle.all(:conditions => ['order_id IN (?)', Order.open.collect(&:id)])
order_article = order_articles.detect {|oa| oa.article_id == id }
order_article ? order_article.order : nil
@in_open_order ||= begin
order_articles = OrderArticle.all(:conditions => ['order_id IN (?)', Order.open.collect(&:id)])
order_article = order_articles.detect {|oa| oa.article_id == id }
order_article ? order_article.order : nil
end
end
memoize :in_open_order
# Returns true if the article has been ordered in the given order at least once
def ordered_in_order?(order)

View file

@ -17,6 +17,8 @@ class GroupOrder < ActiveRecord::Base
scope :in_open_orders, joins(:order).merge(Order.open)
scope :in_finished_orders, joins(:order).merge(Order.finished_not_closed)
scope :ordered, :include => :ordergroup, :order => 'groups.name'
# Generate some data for the javascript methods in ordering view
def load_data
data = {}

View file

@ -2,7 +2,6 @@
# The chronologically order of the Ordergroup - activity are stored in GroupOrderArticleQuantity
#
class GroupOrderArticle < ActiveRecord::Base
extend ActiveSupport::Memoizable # Ability to cache method results. Use memoize :expensive_method
belongs_to :group_order
belongs_to :order_article
@ -14,7 +13,7 @@ class GroupOrderArticle < ActiveRecord::Base
validates_inclusion_of :tolerance, :in => 0..99
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
scope :ordered, :conditions => 'result > 0'
scope :ordered, :conditions => 'group_order_articles.result > 0 OR group_order_articles.quantity > 0 OR group_order_articles.tolerance > 0', :include => {:group_order => :ordergroup}, :order => 'groups.name'
localize_input_of :result
@ -101,54 +100,55 @@ class GroupOrderArticle < ActiveRecord::Base
#
# See description of the ordering algorithm in the general application documentation for details.
def calculate_result
quantity = tolerance = 0
stockit = order_article.article.is_a?(StockArticle)
@calculate_result ||= begin
quantity = tolerance = 0
stockit = order_article.article.is_a?(StockArticle)
# Get total
total = stockit ? order_article.article.quantity : order_article.units_to_order * order_article.price.unit_quantity
logger.debug("<#{order_article.article.name}>.unitsToOrder => items ordered: #{order_article.units_to_order} => #{total}")
# Get total
total = stockit ? order_article.article.quantity : order_article.units_to_order * order_article.price.unit_quantity
logger.debug("<#{order_article.article.name}>.unitsToOrder => items ordered: #{order_article.units_to_order} => #{total}")
if (total > 0)
# In total there are enough units ordered. Now check the individual result for the ordergroup (group_order).
#
# Get all GroupOrderArticleQuantities for this OrderArticle...
order_quantities = GroupOrderArticleQuantity.all(
:conditions => ["group_order_article_id IN (?)", order_article.group_order_article_ids], :order => 'created_on')
logger.debug("GroupOrderArticleQuantity records found: #{order_quantities.size}")
if (total > 0)
# In total there are enough units ordered. Now check the individual result for the ordergroup (group_order).
#
# Get all GroupOrderArticleQuantities for this OrderArticle...
order_quantities = GroupOrderArticleQuantity.all(
:conditions => ["group_order_article_id IN (?)", order_article.group_order_article_ids], :order => 'created_on')
logger.debug("GroupOrderArticleQuantity records found: #{order_quantities.size}")
# Determine quantities to be ordered...
total_quantity = i = 0
while (i < order_quantities.size && total_quantity < total)
q = (order_quantities[i].quantity <= total - total_quantity ? order_quantities[i].quantity : total - total_quantity)
total_quantity += q
if (order_quantities[i].group_order_article_id == self.id)
logger.debug("increasing quantity by #{q}")
quantity += q
end
i += 1
end
# Determine tolerance to be ordered...
if (total_quantity < total)
logger.debug("determining additional items to be ordered from tolerance")
i = 0
# Determine quantities to be ordered...
total_quantity = i = 0
while (i < order_quantities.size && total_quantity < total)
q = (order_quantities[i].tolerance <= total - total_quantity ? order_quantities[i].tolerance : total - total_quantity)
q = (order_quantities[i].quantity <= total - total_quantity ? order_quantities[i].quantity : total - total_quantity)
total_quantity += q
if (order_quantities[i].group_order_article_id == self.id)
logger.debug("increasing tolerance by #{q}")
tolerance += q
logger.debug("increasing quantity by #{q}")
quantity += q
end
i += 1
end
# Determine tolerance to be ordered...
if (total_quantity < total)
logger.debug("determining additional items to be ordered from tolerance")
i = 0
while (i < order_quantities.size && total_quantity < total)
q = (order_quantities[i].tolerance <= total - total_quantity ? order_quantities[i].tolerance : total - total_quantity)
total_quantity += q
if (order_quantities[i].group_order_article_id == self.id)
logger.debug("increasing tolerance by #{q}")
tolerance += q
end
i += 1
end
end
logger.debug("determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}")
end
logger.debug("determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}")
{:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance}
end
{:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance}
end
memoize :calculate_result
# Returns order result,
# either calcualted on the fly or fetched from result attribute
@ -167,7 +167,7 @@ class GroupOrderArticle < ActiveRecord::Base
# the minimum price depending on configuration. When the order is finished it
# will be the value depending of the article results.
def total_price(order_article = self.order_article)
unless order_article.order.finished?
if order_article.order.open?
if FoodsoftConfig[:tolerance_is_costly]
order_article.article.fc_price * (quantity + tolerance)
else

View file

@ -2,6 +2,8 @@
#
class Order < ActiveRecord::Base
attr_accessor :ignore_warnings
# Associations
has_many :order_articles, :dependent => :destroy
has_many :articles, :through => :order_articles
@ -17,6 +19,7 @@ class Order < ActiveRecord::Base
# Validations
validates_presence_of :starts
validate :starts_before_ends, :include_articles
validate :keep_ordered_articles
# Callbacks
after_save :save_order_articles, :update_price_of_group_orders
@ -55,7 +58,12 @@ class Order < ActiveRecord::Base
end
def article_ids
@article_ids ||= order_articles.map(&:article_id)
@article_ids ||= order_articles.map { |a| a.article_id.to_s }
end
# Returns an array of article ids that lead to a validation error.
def erroneous_article_ids
@erroneous_article_ids ||= []
end
def open?
@ -209,24 +217,24 @@ class Order < ActiveRecord::Base
protected
def starts_before_ends
errors.add(:ends, I18n.t('articles.model.error_starts_before_ends')) if (ends && starts && ends <= starts)
errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if (ends && starts && ends <= starts)
end
def include_articles
errors.add(:articles, I18n.t('articles.model.error_nosel')) if article_ids.empty?
errors.add(:articles, I18n.t('orders.model.error_nosel')) if article_ids.empty?
end
def keep_ordered_articles
chosen_order_articles = order_articles.find_all_by_article_id(article_ids)
to_be_removed = order_articles - chosen_order_articles
to_be_removed_but_ordered = to_be_removed.select { |a| a.quantity > 0 or a.tolerance > 0 }
unless to_be_removed_but_ordered.empty? or ignore_warnings
errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered'))
@erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id }
end
end
def save_order_articles
#self.articles = Article.find(article_ids) # This doesn't deletes the group_order_articles, belonging to order_articles,
# # see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
#
## Ensure to delete also the group_order_articles, belonging to order_articles
## This case is relevant, when removing articles from a running order
#goa_ids = GroupOrderArticle.where(group_order_id: group_order_ids).includes(:order_article).
# select { |goa| goa.order_article.nil? }.map(&:id)
#GroupOrderArticle.delete_all(id: goa_ids) unless goa_ids.empty?
# fetch selected articles
articles_list = Article.find(article_ids)
# create new order_articles

View file

@ -12,7 +12,8 @@ class OrderArticle < ActiveRecord::Base
validate :article_and_price_exist
validates_uniqueness_of :article_id, scope: :order_id
scope :ordered, :conditions => "units_to_order >= 1"
scope :ordered, :conditions => "units_to_order > 0"
scope :ordered_or_member, -> { includes(:group_order_articles).where("units_to_order > 0 OR group_order_articles.result > 0") }
before_create :init_from_balancing
after_destroy :update_ordergroup_prices

View file

@ -1,4 +1,4 @@
- title 'Artikel mit externer Datenbank synchronisieren'
- title t('.title')
= form_tag update_synchronized_supplier_articles_path(@supplier) do
%h2= t '.outlist.title'
@ -19,9 +19,8 @@
%h2= t '.update.title'
%p
%i
%b= @updated_articles.size
= t '.update.update_msg'
= t('.update.body').html_safe
= t '.update.update_msg', count: @updated_articles.size
= t '.update.body'
%table.table
%thead
%tr

View file

@ -5,21 +5,19 @@
$('#stock_changes tr').removeClass('success');
var quantity = w.prompt('<%= j(t('.how_many_units', :unit => @stock_change.stock_article.unit, :name => @stock_change.stock_article.name)) %>');
if(null === quantity) {
return false;
}
var stock_change = $(
'<%= j(render(:partial => 'stock_change', :locals => {:stock_change => @stock_change})) %>'
).addClass('success');
enablePriceTooltips(stock_change);
$('input.stock-change-quantity', stock_change).val(quantity);
$('#stock_changes').append(stock_change);
mark_article_for_delivery(<%= @stock_change.stock_article.id %>);
updateSort('#stock_changes');
var quantity = w.prompt('<%= j(t('.how_many_units', :unit => @stock_change.stock_article.unit, :name => @stock_change.stock_article.name)) %>'); <%# how to properly escape here? %>
if(null === quantity) {
stock_change.remove();
mark_article_for_delivery(<%= @stock_change.stock_article.id %>);
return false;
}
$('input.stock-change-quantity', stock_change).val(quantity);
})(window);

View file

@ -7,12 +7,17 @@
setMinimumBalance(#{FoodsoftConfig[:minimum_balance] or 0});
setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]});
setStockit(#{@order.stockit?});
// create List for search-feature (using list.js, http://listjs.com)
var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}];
var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}];
new List(document.body, { valueNames: ['name'], engine: 'unlist', plugins: [listjsResetPlugin, listjsDelayPlugin] });
});
- title t('.title'), false
.row-fluid
.well.pull-left
%button{type: "button", class: "close", data: {dismiss: 'alert'}}= '&times;'.html_safe
%h2= @order.name
%dl.dl-horizontal
- unless @order.note.blank?
@ -35,8 +40,17 @@
%dd= number_to_currency(@ordering_data[:available_funds])
.well.pull-right
%button{type: "button", class: "close", data: {dismiss: 'alert'}}= '&times;'.html_safe
= render 'switch_order', current_order: @order
.row-fluid
.well.clear
.form-search
.input-append
= text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search resettable'
%button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')}
%i.icon.icon-remove
= form_for @group_order do |f|
= f.hidden_field :lock_version
= f.hidden_field :order_id
@ -59,9 +73,9 @@
%th(style="width:20px")= t '.available'
%th#col_required= t '.amount'
%th{style: "width:15px;"}= t '.sum'
%tbody
%tbody.list
- @order.articles_grouped_by_category.each do |category, order_articles|
%tr.article-category
%tr.list-heading.article-category
%td
= category
%i.icon-tag

View file

@ -4,4 +4,4 @@
= form.hidden_field :group_id
= form.input :email
= form.submit t('.action')
= link_to t('.back'), :back
= link_to t('ui.or_cancel'), :back

View file

@ -27,10 +27,14 @@
= category_name
%i.icon-tag
- for article in articles
/ check if the article is selected
- included = @order.article_ids.include?(article.id)
- included_class = included ? ' selected' : ''
%tr{:class => included_class, :id => article.id.to_s }
/ check if the article is selected or has an error
- included = @order.article_ids.include?(article.id.to_s)
- row_class = ''
- if included
- row_class = 'selected'
- elsif @order.erroneous_article_ids.include?(article.id)
- row_class = 'error'
%tr{class: row_class, id: article.id}
%td= check_box_tag "order[article_ids][]", article.id, included, :id => "checkbox_#{article.id}"
%td.click-me{'data-check-this' => "#checkbox_#{article.id}"}= article.name
%td=h truncate article.note, :length => 25
@ -52,3 +56,7 @@
.form-actions
= f.submit class: 'btn'
= link_to t('ui.or_cancel'), orders_path
- unless @order.erroneous_article_ids.empty?
&nbsp;
= check_box_tag 'order[ignore_warnings]'
= t '.ignore_warnings'

View file

@ -2,8 +2,10 @@
%thead
%tr
%th{:style => 'width:70%'}= t '.ordergroup'
%th= t '.ordered'
%th= t '.received'
%th
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
%th
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
%th= t '.price'
- for order_article in order.order_articles.ordered.all(:include => [:article, :article_price])
@ -13,8 +15,8 @@
= order_article.article.name
= "(#{order_article.article.unit} | #{order_article.price.unit_quantity} | #{number_to_currency(order_article.price.gross_price)})"
%tbody
- for goa in order_article.group_order_articles
%tr{:class => cycle('even', 'odd', :name => 'groups')}
- for goa in order_article.group_order_articles.ordered
%tr{:class => [cycle('even', 'odd', :name => 'groups'), if goa.result == 0 then 'unavailable' end]}
%td{:style => "width:70%"}=h goa.group_order.ordergroup.name
%td= "#{goa.quantity} + #{goa.tolerance}"
%td

View file

@ -3,7 +3,9 @@
%tr
%th{:style => "width:40%"}= t '.name'
%th
%acronym{:title => t('.units_desc')}= t '.units'
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
%th
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
%th
%acronym{:title => t('.fc_price_desc')}= t '.fc_price'
%th
@ -11,10 +13,10 @@
%th= t '.unit'
%th= t '.price'
- for group_order in order.group_orders.all
- for group_order in order.group_orders.ordered
%thead
%tr
%th{:colspan => "6"}
%th{:colspan => "7"}
%h4= group_order.ordergroup.name
%tbody
- total = 0
@ -22,17 +24,19 @@
- fc_price = goa.order_article.price.fc_price
- subTotal = fc_price * goa.result
- total += subTotal
%tr{:class => cycle('even', 'odd', :name => 'articles')}
%tr{:class => [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end]}
%td{:style => "width:40%"}=h goa.order_article.article.name
%td= goa.result
%td= "#{goa.quantity} + #{goa.tolerance}"
%td
%b= goa.result
%td= number_to_currency(fc_price)
%td= goa.order_article.price.unit_quantity
%td= goa.order_article.article.unit
%td= number_to_currency(subTotal)
%tr{:class => cycle('even', 'odd', :name => 'articles')}
%th{:colspan => "5"} Summe
%th{:colspan => "6"} Summe
%th= number_to_currency(total)
%tr
%th(colspan="6")
%th(colspan="7")
- reset_cycle("articles")

View file

@ -3,5 +3,6 @@
= form.hidden_field :stock_article_id
= "Menge (#{stock_change.stock_article.quantity_available})"
= form.text_field :quantity, :size => 5, :autocomplete => 'off'
%b= stock_change.stock_article.name
= "(#{number_to_currency(stock_change.stock_article.price)} / #{stock_change.stock_article.unit})"
%span{:data => {:toggle => :tooltip, :title => render(:partial => 'shared/article_price_info', :locals => {:article => stock_change.stock_article})}}
%b= stock_change.stock_article.name
= "(#{number_to_currency(stock_change.stock_article.price)} / #{stock_change.stock_article.unit})"

View file

@ -1,5 +1,20 @@
- title t('.title')
- content_for :javascript do
:javascript
$(function() {
enablePriceTooltips();
});
function enablePriceTooltips(context) {
$('[data-toggle~="tooltip"]', context).tooltip({
animation: false,
html: true,
placement: 'left',
container: 'body'
});
}
- content_for :sidebar do
%p
%i= t('.text_deviations', inv_link: link_to(t('.temp_inventory'), stock_articles_path)).html_safe

View file

@ -1,17 +0,0 @@
- title t('.stock_changes', :article_name => @stock_article.name)
%table.table.table-hover#stock_changes
%thead
%tr
%th= t '.datetime'
%th= t '.reason'
%th= t '.change_quantity'
%th= t '.new_quantity'
%tbody
- reversed_history = @stock_article.quantity_history.reverse
- @stock_changes.each_with_index do |stock_change, index|
%tr
%td= l stock_change.created_at
%td= link_to_stock_change_reason(stock_change)
%td= stock_change.quantity
%td= reversed_history[index]

View file

@ -45,7 +45,7 @@
%tbody
- for article in @stock_articles
%tr{:class => stock_article_classes(article), :id => "stockArticle-#{article.id}"}
%td=h article.name
%td= link_to article.name, article
%td= article.quantity
%td= article.quantity - article.quantity_available
%th= article.quantity_available
@ -56,7 +56,6 @@
%td= article.article_category.name
%td
= link_to t('ui.edit'), edit_stock_article_path(article), class: 'btn btn-mini'
= link_to t('ui.history'), stock_article_history_path(article), class: 'btn btn-mini'
= link_to t('ui.delete'), article, :method => :delete, :confirm => t('.confirm_delete'),
class: 'btn btn-mini btn-danger', :remote => true
%p

View file

@ -0,0 +1,47 @@
- title @stock_article.name
.row-fluid
.span6
%dl.dl-horizontal
%dt= StockArticle.human_attribute_name 'supplier'
%dd= link_to @stock_article.supplier.name, @stock_article.supplier
%dt= StockArticle.human_attribute_name 'name'
%dd= @stock_article.name
%dt= StockArticle.human_attribute_name 'unit'
%dd= @stock_article.unit
%dt= StockArticle.human_attribute_name 'price'
%dd= number_to_currency @stock_article.price
%dt= StockArticle.human_attribute_name 'tax'
%dd= number_to_percentage @stock_article.tax
%dt= StockArticle.human_attribute_name 'deposit'
%dd= number_to_currency @stock_article.deposit
%dt= StockArticle.human_attribute_name 'fc_price'
%dd= number_to_currency @stock_article.fc_price
%dt= StockArticle.human_attribute_name 'article_category'
%dd= @stock_article.article_category.name
%dt= StockArticle.human_attribute_name 'note'
%dd= @stock_article.note
%dt= StockArticle.human_attribute_name 'quantity'
%dd= @stock_article.quantity
%dt= StockArticle.human_attribute_name 'quantity_available'
%dd= @stock_article.quantity_available
.form-actions
= link_to t('ui.edit'), edit_stock_article_path(@stock_article), class: 'btn'
.span6
%h2= t('.stock_changes')
%table.table.table-hover#stock_changes
%thead
%tr
%th= t '.datetime'
%th= t '.reason'
%th= t '.change_quantity'
%th= t '.new_quantity'
%tbody
- reversed_history = @stock_article.quantity_history.reverse
- @stock_changes.each_with_index do |stock_change, index|
%tr
%td= l stock_change.created_at
%td= link_to_stock_change_reason(stock_change)
%td= stock_change.quantity
%td= reversed_history[index]