diff --git a/Gemfile b/Gemfile index 8ac49e82..83cd4333 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ end gem 'jquery-rails' gem 'select2-rails' gem 'bootstrap-datepicker-rails' +gem 'rails-assets-listjs', '0.2.0.beta.4' # remember to maintain list.*.js plugins and template engines on update gem 'mysql2' gem 'prawn' diff --git a/Gemfile.lock b/Gemfile.lock index a9112511..83e3a43a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -202,6 +202,8 @@ GEM activesupport (= 3.2.14) bundler (~> 1.0) railties (= 3.2.14) + rails-assets-listjs (0.2.0.beta.4) + railties (>= 3.1) rails-settings-cached (0.2.4) rails (>= 3.0.0) railties (3.2.14) @@ -346,6 +348,7 @@ DEPENDENCIES prawn quiet_assets rails (~> 3.2.9) + rails-assets-listjs (= 0.2.0.beta.4) rails-settings-cached (= 0.2.4) resque rspec-core diff --git a/README.md b/README.md index d3107dae..e7be634b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ FoodSoft ========= -[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png?branch=tests-rspec)](https://travis-ci.org/foodcoops/foodsoft) +[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png)](https://travis-ci.org/foodcoops/foodsoft) [![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft) [![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft) +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/foodcoops/foodsoft/trend.png)](https://bitdeli.com/free "Bitdeli Badge") Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling). diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 81b0f62c..39b99a65 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -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 diff --git a/app/assets/javascripts/list.delay.js b/app/assets/javascripts/list.delay.js new file mode 100644 index 00000000..72ac425c --- /dev/null +++ b/app/assets/javascripts/list.delay.js @@ -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); diff --git a/app/assets/javascripts/list.reset.js b/app/assets/javascripts/list.reset.js new file mode 100644 index 00000000..9482f32c --- /dev/null +++ b/app/assets/javascripts/list.reset.js @@ -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); diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js new file mode 100644 index 00000000..b40cb98f --- /dev/null +++ b/app/assets/javascripts/list.unlist.js @@ -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); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 24a5b6c4..674a635c 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -3,4 +3,5 @@ *= require select2 *= require token-input-bootstrappy *= require bootstrap-datepicker +*= require list.unlist */ \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 408f6c65..6ad25972 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -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; +} diff --git a/app/assets/stylesheets/list.unlist.css b/app/assets/stylesheets/list.unlist.css new file mode 100644 index 00000000..9fad9603 --- /dev/null +++ b/app/assets/stylesheets/list.unlist.css @@ -0,0 +1,3 @@ +.list .unlisted:not(.no-unlist) { + display: none; +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 96b2e510..9c77fe48 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 8528e4d3..8de1444c 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -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' diff --git a/app/controllers/finance/financial_transactions_controller.rb b/app/controllers/finance/financial_transactions_controller.rb index a129a49d..8865958e 100644 --- a/app/controllers/finance/financial_transactions_controller.rb +++ b/app/controllers/finance/financial_transactions_controller.rb @@ -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 diff --git a/app/controllers/finance/group_order_articles_controller.rb b/app/controllers/finance/group_order_articles_controller.rb index 705e2b2f..3596ec40 100644 --- a/app/controllers/finance/group_order_articles_controller.rb +++ b/app/controllers/finance/group_order_articles_controller.rb @@ -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 diff --git a/app/controllers/finance/order_articles_controller.rb b/app/controllers/finance/order_articles_controller.rb index eed715eb..cc3351b0 100644 --- a/app/controllers/finance/order_articles_controller.rb +++ b/app/controllers/finance/order_articles_controller.rb @@ -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 diff --git a/app/controllers/stockit_controller.rb b/app/controllers/stockit_controller.rb index 3e3b43b2..475ac3a3 100644 --- a/app/controllers/stockit_controller.rb +++ b/app/controllers/stockit_controller.rb @@ -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 diff --git a/app/documents/order_by_articles.rb b/app/documents/order_by_articles.rb index 705393f5..a6e1b0b5 100644 --- a/app/documents/order_by_articles.rb +++ b/app/documents/order_by_articles.rb @@ -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 diff --git a/app/documents/order_by_groups.rb b/app/documents/order_by_groups.rb index 5ee66cca..746a167f 100644 --- a/app/documents/order_by_groups.rb +++ b/app/documents/order_by_groups.rb @@ -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 diff --git a/app/models/article.rb b/app/models/article.rb index 3755a4b2..a3b4af33 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -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) diff --git a/app/models/group_order.rb b/app/models/group_order.rb index 9b5eb764..2a37d970 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -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 = {} diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 11f3b447..3b37a990 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -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 diff --git a/app/models/order.rb b/app/models/order.rb index 7910dfc3..f4545d02 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -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 diff --git a/app/models/order_article.rb b/app/models/order_article.rb index 85a0da79..bd90706b 100644 --- a/app/models/order_article.rb +++ b/app/models/order_article.rb @@ -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 diff --git a/app/views/articles/sync.html.haml b/app/views/articles/sync.html.haml index d740cb6e..1039aee4 100644 --- a/app/views/articles/sync.html.haml +++ b/app/views/articles/sync.html.haml @@ -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 diff --git a/app/views/deliveries/add_stock_change.js.erb b/app/views/deliveries/add_stock_change.js.erb index 049e6233..3e233bb6 100644 --- a/app/views/deliveries/add_stock_change.js.erb +++ b/app/views/deliveries/add_stock_change.js.erb @@ -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); diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 2bd2f30b..3c6048c0 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -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'}}= '×'.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'}}= '×'.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 diff --git a/app/views/invites/new.html.haml b/app/views/invites/new.html.haml index eb9081cb..2332db00 100644 --- a/app/views/invites/new.html.haml +++ b/app/views/invites/new.html.haml @@ -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 diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml index 78cd3ca8..86d24106 100644 --- a/app/views/orders/_form.html.haml +++ b/app/views/orders/_form.html.haml @@ -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? +   + = check_box_tag 'order[ignore_warnings]' + = t '.ignore_warnings' diff --git a/app/views/shared/_articles_by_articles.html.haml b/app/views/shared/_articles_by_articles.html.haml index df4528a3..6d952363 100644 --- a/app/views/shared/_articles_by_articles.html.haml +++ b/app/views/shared/_articles_by_articles.html.haml @@ -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 diff --git a/app/views/shared/_articles_by_groups.html.haml b/app/views/shared/_articles_by_groups.html.haml index c78f5a70..9470fb49 100644 --- a/app/views/shared/_articles_by_groups.html.haml +++ b/app/views/shared/_articles_by_groups.html.haml @@ -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") diff --git a/app/views/stock_takings/_stock_change.html.haml b/app/views/stock_takings/_stock_change.html.haml index 616be369..3d27c1fa 100644 --- a/app/views/stock_takings/_stock_change.html.haml +++ b/app/views/stock_takings/_stock_change.html.haml @@ -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})" diff --git a/app/views/stock_takings/new.html.haml b/app/views/stock_takings/new.html.haml index 51239ba4..6e4526f9 100644 --- a/app/views/stock_takings/new.html.haml +++ b/app/views/stock_takings/new.html.haml @@ -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 diff --git a/app/views/stockit/history.haml b/app/views/stockit/history.haml deleted file mode 100644 index f4fe2b07..00000000 --- a/app/views/stockit/history.haml +++ /dev/null @@ -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] diff --git a/app/views/stockit/index.html.haml b/app/views/stockit/index.html.haml index 477e5816..c2899d9e 100644 --- a/app/views/stockit/index.html.haml +++ b/app/views/stockit/index.html.haml @@ -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 diff --git a/app/views/stockit/show.html.haml b/app/views/stockit/show.html.haml new file mode 100644 index 00000000..5f416bf9 --- /dev/null +++ b/app/views/stockit/show.html.haml @@ -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] diff --git a/config/initializers/resque.rb b/config/initializers/resque.rb new file mode 100644 index 00000000..28001613 --- /dev/null +++ b/config/initializers/resque.rb @@ -0,0 +1 @@ +Resque.inline = Rails.env.test? diff --git a/config/locales/de.yml b/config/locales/de.yml index c0000036..ab2c694b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -42,7 +42,10 @@ de: fc_price: Endpreis fc_share: FC-Aufschlag gross_price: Bruttopreis + name: Name + note: Notiz price: Nettopreis + supplier: Lieferantin tax: MwSt unit: Einheit unit_quantity: Gebindegröße @@ -51,6 +54,8 @@ de: note: Notiz stock_article: price: Nettopreis + quantity: Lagerbestand + quantity_available: Verfügbarer Bestand user: first_name: Vorname password: Passwort @@ -212,6 +217,11 @@ de: workgroups: members: Mitglieder name: Name + application: + controller: + error_authn: + error_denied: + error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt! article_categories: create: notice: Die Kategorie wurde gespeichert @@ -305,18 +315,20 @@ de: error_nosel: Du hast keine Artikel ausgewählt parse_upload: body:

Bitte überprufe die engelesenen Artikel.

Achtung, momentan gibt es keine Überprüfung auf doppelte Artikel.

+ title: + sync: outlist: body: ! 'Folgende Artikel wurden ausgelistet und werden gelöscht:' body_skip: Es müssen keine Artikel gelöscht werden. title: Auslisten ... price_short: Preis - submit: Alle löschen/aktualisieren + submit: Alle synchronisieren title: Artikel mit externer Datenbank synchronisieren unit_quantity_short: GebGr 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 ... - update_msg: ! 'Artikel müssen aktualisiert werden:' + update_msg: ! '%{count} Artikel müssen aktualisiert werden.' upload: body:

Die Datei muss eine Textdatei mit der Endung '.csv' sein. Die erste Zeile wird beim Einlesen ignoriert.

Die Felder müssen mit einem Semikolon (';') getrennt und der Text mit doppelten Anführungszeichen ("Text...") umklammert werden.

Als Zeichensatz wird UTF-8 erwartet. Korrekte Reihenfolge der Spalten:

fields: @@ -482,14 +494,16 @@ de: filename: Bestellung %{name}-%{date} - Artikelsortierung rows: - Bestellgruppe - - Menge + - Bestellt + - Bekommen - Preis title: ! 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}' order_by_groups: filename: Bestellung %{name}-%{date} - Gruppensortierung rows: - Artikel - - Menge + - Bestellt + - Bekommen - Preis - GebGr - Einheit @@ -505,6 +519,7 @@ de: - Gebinde - Einheit - Preis/Einheit + - Summe total: Gesamtpreis order_matrix: filename: Bestellung %{name}-%{date} - Sortiermatrix @@ -641,11 +656,13 @@ de: create: notice: Rechnung wurde erstellt. financial_transactions: - create: - notice: Die Transaktion wurde gespeichert. - create_collection: - alert: ! 'Ein Fehler ist aufgetreten: %{error}' - notice: Alle Transaktionen wurden gespeichert. + controller: + create: + notice: Die Transaktion wurde gespeichert. + create_collection: + alert: ! 'Ein Fehler ist aufgetreten: %{error}' + error_note_required: Notiz wird benötigt! + notice: Alle Transaktionen wurden gespeichert. index: balance: ! 'Kontostand: %{balance}' last_updated_at: (zuletzt aktualisiert vor %{when}) @@ -784,6 +801,8 @@ de: new_funds: Neuer Kontostand note: Notiz price: Preis + reset_article_search: Suche zurücksetzen + search_article: Artikel suchen... sum: Summe sum_amount: ! 'Gesamtbestellmenge bisher:' supplier: Lieferant @@ -968,7 +987,6 @@ de: title: Person einladen new: action: Einlading abschicken - back: oder zurück body:

Hier kannst du eine Person in die Gruppe %{group} einladen, die noch nicht Mitglied der Foodcoop ist.

success: Benutzerin wurde erfolgreich eingeladen. layouts: @@ -1299,6 +1317,7 @@ de: finish: notice: Die Bestellung wurde beendet. form: + ignore_warnings: Warnungen ignorieren name: Name note: Notiz origin: Herkunft @@ -1326,6 +1345,8 @@ de: error_starts_before_ends: muss nach dem Bestellstart liegen (oder leer bleiben) notice_close: ! 'Bestellung: %{name}, bis %{ends}' stock: Lager + warning_ordered: ! 'Warnung: Die rot markierten Artikel wurden in der laufenden Bestellung bereits bestellt. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen dieses Artikels gelöscht.' + 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: title: Neue Bestellung anlegen orders: @@ -1452,11 +1473,14 @@ de: title: Foodsoft anmelden user: Benutzerin shared: + articles: + ordered: Bestellt + ordered_desc: + received: Bekommen + received_desc: articles_by_articles: - ordered: Bestellt (Menge + Toleranz) ordergroup: Bestellgruppe price: Gesamtpreis - received: Bekommen articles_by_groups: fc_price: FC-Preis fc_price_desc: Preis incl. MwSt, Pfand und Foodcoop-Aufschlag @@ -1465,8 +1489,6 @@ de: unit: Einheit unit_quantity: GebGr unit_quantity_desc: Gebindegröße - units: Menge - units_desc: Zugeteilte Einheiten group: access: Zugriff auf activated: aktiviert @@ -1525,7 +1547,7 @@ de: message: private: Nachricht erscheint nicht im Foodsoft Posteingang order_article: - units_to_order: Anzahl gelieferter Gebinde + units_to_order: Wenn Du die Gesamtanzahl gelieferter Gebinde änderst, musst Du auch die individuelle Anzahl der einzelnen Bestellgruppen anpassen, indem Du auf den Artikelnamen klickst. Sie werden nicht automatisch neuberechnet und andernfalls werden den Bestellgruppen Artikel in Rechnung gestellt, die nicht geliefert wurden! update_current_price: Ändert auch den Preis für aktuelle Bestellungen stock_article: copy_stock_article: @@ -1718,15 +1740,6 @@ de: title: Lagerartikel bearbeiten form: price_hint: Um Chaos zu vermeiden können bis auf weiteres die Preise von angelegten Lagerartikeln nicht mehr verändert werden. - history: - change_quantity: Veränderung - datetime: Zeitpunkt - delivery: Lieferung - new_quantity: Neuer Bestand - order: Bestellung - reason: Ereignis - stock_changes: Verlauf anzeigen für »%{article_name}« - stock_taking: Inventur index: article: article: Artikel @@ -1752,6 +1765,15 @@ de: new: search_text: ! 'Suche nache Artikeln aus allen Katalogen:' title: Neuen Lagerartikel anlegen + show: + change_quantity: Veränderung + datetime: Zeitpunkt + delivery: Lieferung + new_quantity: Neuer Bestand + order: Bestellung + reason: Ereignis + stock_changes: Verlauf des Lagerbestands + stock_taking: Inventur stock_create: notice: Lagerartikel wurde gespeichert. stock_update: @@ -1876,7 +1898,6 @@ de: close: Schließen delete: Löschen edit: Bearbeiten - history: Verlauf anzeigen marks: close: ! '×' success: diff --git a/config/locales/en.yml b/config/locales/en.yml index 2fa64013..f7d04466 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -42,7 +42,10 @@ en: fc_price: FC price fc_share: FC share gross_price: gross price + name: name + note: note price: price + supplier: supplier tax: VAT unit: unit unit_quantity: unit quantity @@ -50,7 +53,9 @@ en: amount: amount note: note stock_article: - price: Price + price: price + quantity: quantity + quantity_available: available quantity user: first_name: First name password: Password @@ -212,6 +217,11 @@ en: workgroups: members: members name: name + application: + controller: + error_authn: Authentication required! + error_denied: Access denied! + error_members_only: This action is only available to members of the group! article_categories: create: notice: Category was stored @@ -305,20 +315,22 @@ en: error_nosel: You have selected no articles parse_upload: body:

Please verify the articles.

Warning, at the moment there is no check for duplicate articles.

+ title: Upload articles + sync: outlist: - body: ! 'The following articles were outlisted and deleted:' + body: ! 'The following articles were removed from the list and will be deleted:' body_skip: No articles to delete. - title: Outlist ... + title: Remove from list ... price_short: Price - submit: Delete/update all + submit: Synchronize all title: Synchronize articles with external database - unit_quantity_short: unit quantity + unit_quantity_short: Unit quantity update: - body: ! '

Every article is shown twice. The old values are gray and contain the current values.

- -

Differences with the old articles are marked yellow.

' + body: ! 'Every article is shown twice: old values are gray, while the text fields contain updated values. Differences with the old articles are marked yellow.' title: Update ... - update_msg: ! 'Articles must be updated:' + update_msg: + one: One article needs to be updated. + other: ! '%{count} articles need to be updated.' upload: body:

The file has to be a text file with the ending '.csv' The first line will be ignored when imported

The fields have to be separated with semicolons (';') and the text enclosed by double quotation marks ("text...").

As character set UTF-8 is demanded. Correct order of the column:

fields: @@ -430,7 +442,7 @@ en: create: notice: Delivery was created. Please don’t forget to create invoice! create_stock_article: - notice: The new stock article »%{name}« was saved. + notice: The new stock article "%{name}" was saved. destroy: notice: Delivery was deleted. edit: @@ -478,20 +490,22 @@ en: update: notice: Delivery was updated. update_stock_article: - notice: The stock article »%{name}« was updated. + notice: The stock article "%{name}" was updated. documents: order_by_articles: filename: Order %{name}-%{date} - by articles rows: - Order group - - Amount + - Ordered + - Received - Price title: ! 'Order sorted by articles: %{name}, closed at %{date}' order_by_groups: filename: Order %{name}-%{date} - by group rows: - Article - - Amount + - Ordered + - Received - Price - Unit quantity - Unit @@ -646,11 +660,13 @@ en: create: notice: Invoice was created financial_transactions: - create: - notice: The transaction was saved. - create_collection: - alert: ! 'An error occured: %{error}' - notice: All transactions were saved. + controller: + create: + notice: The transaction was saved. + create_collection: + alert: ! 'An error occured: %{error}' + error_note_required: Note is required! + notice: All transactions were saved. index: balance: ! 'Balance of account: %{balance}' last_updated_at: (last updated %{when} ago) @@ -789,6 +805,8 @@ en: new_funds: New account balance note: Note price: Price + reset_article_search: Reset search + search_article: Search for article... sum: Sum sum_amount: Current amount supplier: Supplier @@ -896,7 +914,7 @@ en: warning: Warning, if you have less then %{threshold} of apple points, you are not allowed to place an order! changes_saved: Changes saved. index: - due_date_format: ! '%A %d %b' + due_date_format: ! '%A %d %B' messages: title: Newest Messages view_all: See all messages @@ -973,7 +991,6 @@ en: title: Invite person new: action: Send invite - back: or go back body:

Here you can add a person to the group %{group}, who is not yet a member of the foodcoop.

success: User was invited successfully. layouts: @@ -1304,13 +1321,14 @@ en: finish: notice: The order has been closed. form: + ignore_warnings: Ignore warnings name: Name note: Note origin: Origin prices: Prices (net/FC) select_all: Select all stockit: In stock - supplier: Supplier + supplier: Producer title: Article unit_quantity: Unit quantity index: @@ -1331,6 +1349,8 @@ en: error_starts_before_ends: must be after the start date (or remain empty) notice_close: ! 'Order: %{name}, until %{ends}' stock: Stock + warning_ordered: ! 'Warning: Articles marked red have already been ordered within this open order. If you uncheck them here, all existing orders of these articles will be deleted.' + 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: title: Create new order orders: @@ -1390,7 +1410,7 @@ en: notice: Page was created cshow: error_noexist: Page doesn’t exist! - redirect_notice: Redirected from %{page} .. + redirect_notice: Redirected from %{page} ... destroy: notice: The page '%{page}' and all subpages have been deleted successfully. edit: @@ -1457,11 +1477,14 @@ en: title: Foodsoft login user: User shared: + articles: + ordered: Ordered + ordered_desc: Number of articles as ordered by member (amount + tolerance) + received: Received + received_desc: Number of articles that (will be) received by member articles_by_articles: - ordered: Ordered (Amount + Tolerance) ordergroup: Ordergroup price: Total price - received: Received articles_by_groups: fc_price: FC-Price fc_price_desc: Price including taxes, deposit and Foodcoop-charge @@ -1470,8 +1493,6 @@ en: unit: Unit unit_quantity: Lot quantity unit_quantity_desc: How many units per lot. - units: Amount - units_desc: Assigned units group: access: Access to activated: activated @@ -1530,7 +1551,7 @@ en: message: private: Message doesn’t show in Foodsoft mail inbox order_article: - units_to_order: Amount of delivered units + units_to_order: If you change the total amount of delivered units, you also have to change individual group amounts by clicking on the article name. They will not be automatically recalculated and so ordergroups may be accounted for articles that were not delivered! update_current_price: Also update the price of the current order stock_article: copy_stock_article: @@ -1723,15 +1744,6 @@ en: title: Edit stock articles form: price_hint: To avoid choas, it is not possible to edit the prices of already added stock articles until further notice. - history: - change_quantity: Change - datetime: Time - delivery: Delivery - new_quantity: New quantity - order: Order - reason: Reason - stock_changes: Stock quantity changes of ‘%{article_name}’ - stock_taking: Inventory index: article: article: Article @@ -1757,6 +1769,15 @@ en: new: search_text: ! 'Search for articles in all catalogues:' title: Add new stock article + show: + change_quantity: Change + datetime: Time + delivery: Delivery + new_quantity: New quantity + order: Order + reason: Reason + stock_changes: Stock quantity changes + stock_taking: Inventory stock_create: notice: Stock article was created. stock_update: @@ -1860,7 +1881,7 @@ en: title: Show task update: notice: Task has been updated - notice_converted: Task has been updated and was converted to a regular task + notice_converted: Task has been updated and was converted to a non-repeating task. user: more: Nothing to do? %{tasks_link} are tasks for sure. tasks_link: Here @@ -1881,7 +1902,6 @@ en: close: Close delete: Delete edit: Edit - history: Show history marks: close: ! '×' success: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 057acd4e..f9b71327 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -42,7 +42,10 @@ fr: fc_price: prix final fc_share: supplément boufcoop gross_price: prix brut + name: + note: price: prix net + supplier: tax: TVA unit: unité unit_quantity: unités par lot @@ -51,6 +54,8 @@ fr: note: note stock_article: price: Prix net + quantity: + quantity_available: user: first_name: Prénom password: Mot de passe @@ -212,6 +217,11 @@ fr: workgroups: members: membres name: nom + application: + controller: + error_authn: + error_denied: + error_members_only: article_categories: create: notice: La catégorie a bien été définie. @@ -307,6 +317,8 @@ fr: body: ! '

Merci de vérifier les articles importés.

Attention, les doublons ne sont pas automatiquement détectés.

.' + title: + sync: outlist: body: ! 'Les articles suivants ne sont plus dans la liste et seront donc supprimés:' body_skip: Aucun article à supprimer. @@ -316,9 +328,9 @@ fr: title: Synchroniser les articles avec la base de données extérieure unit_quantity_short: U/L update: - body:

Chaque article apparaît deux fois. Les anciennes données sont rappelées en gris, et les champs du formulaire ont été préremplis avec les nouvelles valeurs.

Les changements sont marqués en jaune.

+ body: ! 'Chaque article apparaît deux fois: les anciennes données sont rappelées en gris, et les champs du formulaire ont été préremplis avec les nouvelles valeurs. Les changements sont marqués en jaune.' title: Mettre à jour... - update_msg: ! 'Ces articles doivent être mis à jour:' + update_msg: upload: body: ! '

Le fichier doit être au format texte et son nom doit se terminer par l''extension ".csv". La première ligne sera ignorée lors de l''importation.

@@ -655,11 +667,13 @@ fr: create: notice: La facture a bien été définie. financial_transactions: - create: - notice: La transaction a été sauvegardée. - create_collection: - alert: ! 'Une erreur s''est produite: %{error}' - notice: Les transactions ont été sauvegardées. + controller: + create: + notice: La transaction a été sauvegardée. + create_collection: + alert: ! 'Une erreur s''est produite: %{error}' + error_note_required: + notice: Les transactions ont été sauvegardées. index: balance: ! 'Solde: %{balance}' last_updated_at: (dernière mise à jour il y a %{when}) @@ -802,6 +816,8 @@ fr: new_funds: Nouveau solde note: Note price: Prix + reset_article_search: + search_article: sum: Prix total sum_amount: ! 'Quantité déjà commandée:' supplier: Fourni par @@ -994,7 +1010,6 @@ fr: title: Engrainer une personne new: action: Engrainer! - back: ou revenir en arrière body:

Sur cette page, tu peux engrainer une personne qui ne fait pas encore partie de la Boufcoop à rejoindre la cellule %{group} success: La_le membre a été engrainéE avec succès! layouts: @@ -1288,7 +1303,7 @@ fr: article_count: ! 'Articles commandés:' name: Nom prices: Prix brut/net - prices_sum: Totaux (des prix bruts/nets) + prices_sum: ! 'Totaux (des prix bruts/nets):' unit_quantity: Unités par lots x Lots units_full: Lots complet units_ordered: Unités commandées @@ -1308,6 +1323,7 @@ fr: finish: notice: La commande a été close. form: + ignore_warnings: name: Nom note: Note origin: Origine @@ -1335,6 +1351,8 @@ fr: error_starts_before_ends: doit être postérieur à la date de début de la commande (ou bien être laissé vierge) notice_close: ! 'Commande: %{name}, jusqu''au %{ends}' stock: Stock + warning_ordered: + warning_ordered_stock: new: title: Définir une nouvelle commande orders: @@ -1459,11 +1477,14 @@ fr: title: Te connecter à Foodsoft user: Identifiant shared: + articles: + ordered: Commandé + ordered_desc: + received: Reçu + received_desc: articles_by_articles: - ordered: Commandé (Quantité + Tolérance) ordergroup: Cellul price: Prix total - received: Reçu articles_by_groups: fc_price: Prix coop fc_price_desc: Prix avec TVA, consigne et part de la coop inclus. @@ -1472,8 +1493,6 @@ fr: unit: Unité unit_quantity: U/L unit_quantity_desc: Unités par lot - units: Quantité - units_desc: Unités assignées group: access: Accès à activated: activé @@ -1532,7 +1551,7 @@ fr: message: private: Le message n'apparaîtra pas dans la boîte de réception du Foodsoft order_article: - units_to_order: Nombre de lots livrés + units_to_order: update_current_price: Modifie aussi le prix des commandes en cours stock_article: copy_stock_article: @@ -1727,15 +1746,6 @@ fr: title: Modifier l'article form: price_hint: Pour éviter que ça soit le bazar, les prix des articles en stock ne peuvent plus être modifiés. - history: - change_quantity: Modification - datetime: Temps - delivery: Réapprovisionnement - new_quantity: Nouveau stock - order: Commande - reason: Raison - stock_changes: Afficher l'historique pour "%{article_name}" - stock_taking: Inventaire index: article: article: Article @@ -1761,6 +1771,15 @@ fr: new: search_text: ! 'Rechercher des articles dans tous les catalogues:' title: Ajouter un article au stock + show: + change_quantity: Modification + datetime: Temps + delivery: Réapprovisionnement + new_quantity: Nouveau stock + order: Commande + reason: Raison + stock_changes: Afficher l'historique + stock_taking: Inventaire stock_create: notice: L'article a été sauvegardé. stock_update: @@ -1893,7 +1912,6 @@ fr: close: Fermer delete: Supprimer edit: Modifier - history: Afficher l'historique marks: close: ! '×' success: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 628bd0e8..2bef7df0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -39,10 +39,13 @@ nl: article_category: categorie availability: Artikel leverbaar? deposit: statiegeld - fc_price: - fc_share: + fc_price: prijs foodcoop + fc_share: marge foodcoop gross_price: bruto prijs + name: + note: price: netto prijs + supplier: tax: BTW unit: eenheid unit_quantity: groothandelseenheid @@ -51,6 +54,8 @@ nl: note: notitie stock_article: price: prijs + quantity: + quantity_available: user: first_name: Voornaam password: Wachtwoord @@ -87,7 +92,7 @@ nl: task: attributes: done: - exclusion: + exclusion: gedane taken kunnen niet herhaald worden template: body: ! 'Controleer de volgende velden:' header: @@ -145,7 +150,7 @@ nl: first_paragraph: Hier kun je %{url} toevoegen, bewerken en verwijderen. new_ordergroup: Nieuw huishouden toevoegen new_ordergroups: nieuwe huishoudens - second_paragraph: + second_paragraph: ! 'Bedenk het onderscheid tussen werkgroep en huishouden: een huishouden heeft een rekening en kan bestellen. in een %{url} (bijv. sorteergroep) werken leden samen om taken te vervullen. Leden kunnen slechts lid zijn van éen huishouden, maar van meerdere werkgroepen.' title: Huishoudens workgroup: werkgroep new: @@ -212,6 +217,11 @@ nl: workgroups: members: leden name: naam + application: + controller: + error_authn: Inloggen vereist. + error_denied: Geen toegang. + error_members_only: Deze actie is alleen beschikbaar voor leden van de groep! article_categories: create: notice: Categorie is opgeslagen @@ -263,7 +273,7 @@ nl: notice: Alle artikelen en prijzen zijn bijgewerkt. destroy_active_article: drop: verwijderen - note: + note: ! '%{article} is deel van een lopende bestelling en kan niet verwijderd worden. Het artikel graag eerst uit de bestelling(en) %{drop_link}.' edit_all: note: ! 'Verplichte velden zijn: Naam, eenheid, (netto) prijs en bestellingsnummer.' submit: Alle artikelen bijwerken @@ -305,18 +315,22 @@ nl: error_nosel: Je hebt geen artikelen geselecteerd parse_upload: body:

Ingelezen artikelen graag controleren.

Let op, momenteel vind er geen controle op dubbele artikelen plaats.

+ title: + sync: outlist: - body: ! 'De volgende artikelen werden uit de lijst gehaald en worden gewist:' - body_skip: Er zijn geen artikelen om te wissen. + body: ! 'De volgende artikelen zijn uit de lijst gehaald en worden verwijderd:' + body_skip: Er zijn geen artikelen om te verwijderen. title: Uit de lijst halen ... price_short: prijs - submit: Alle wissen/bijwerken + submit: Alles synchroniseren title: Artikelen met externe database synchroniseren - unit_quantity_short: + unit_quantity_short: Gr.Eenh. update: - body: - title: - update_msg: + body: ! 'Ieder artikel wordt tweemaal getoond: oude waarden zijn grijs, en de tekstvelden bevatten de nieuwe waarden. Verschillen met de oude artikelen zijn geel gemarkeerd.' + title: Bijwerken ... + update_msg: + one: Er moet éen artikel bijgewerkt worden. + other: Er moeten %{count} artikelen bijgewerkt worden. upload: body: fields: @@ -426,11 +440,11 @@ nl: add_stock_change: how_many_units: create: - notice: + notice: Levering is aangemaakt. Vergeet niet een factuur te maken! create_stock_article: - notice: + notice: Nieuw voorraadsartikel "%{name}" gemaakt. destroy: - notice: + notice: Levering is verwijdered. edit: title: form: @@ -474,7 +488,7 @@ nl: remove_article: suppliers_overview: update: - notice: + notice: Levering is bijgewerkt. update_stock_article: notice: documents: @@ -498,33 +512,38 @@ nl: title: ! 'Huishoudenslijst van bestelling: %{name}, gesloten op %{date}' order_fax: filename: Bestelling %{name}-%{date} - Fax - rows: ! '[]' + rows: total: Totaal order_matrix: filename: Bestelling %{name}-%{date} - Sorteermatrix heading: Artikeloverzicht - rows: + rows: + - Artikel + - Eenheid + - Gr.Eenh. + - Foodcoop-prijs + - Aantal title: ! 'Sorteermatrix van bestelling: %{name}, gesloten op %{date}' total: one: In totaal éen artikel other: In totaal %{count} artikelen errors: - format: + format: ! '%{attribute} %{message}' general: Er is een probleem opgetreden. - general_again: + general_again: Er is een fout opgetreden. Probeer het opnieuw. general_msg: ! 'Er is een probleem opgetreden: %{msg}' messages: accepted: moet geaccepteerd worden blank: moet ingevuld worden - confirmation: + confirmation: komt niet overeen met de bevestiging empty: moet ingevuld worden equal_to: moet precies %{count} zijn - even: + even: moet even zijn exclusion: moet even zijn greater_than: moet groter dan %{count} zijn - greater_than_or_equal_to: - inclusion: - invalid: + greater_than_or_equal_to: moet groter dan of gelijk zijn aan %{count} + inclusion: geen geldige waarde + invalid: is ongeldig less_than: moet kleiner dan %{count} zijn less_than_or_equal_to: moet groter of gelijk aan %{count} zijn not_a_number: is geen getal @@ -533,17 +552,25 @@ nl: record_invalid: taken: is al in gebruik taken_with_deleted: is al in gebruik (verwijderde groep) - too_long: - too_short: - wrong_length: + too_long: + one: is te lang (niet meer dan éen teken) + other: is te lang (niet meer dan %{count} tekens) + too_short: + one: is te kort (niet minder dan éen teken) + other: is te kort (niet minder dan %{count} tekens) + wrong_length: + one: heeft de verkeerde lengte (moet precies éen zijn) + other: heeft de verkeerde lengte (moet precies %{count} tekens hebben) template: - body: - header: + body: ! 'Controleer alsjeblieft de volgende velden:' + header: + one: ! 'Kon %{model} niet opslaan: éen fout gevonden.' + other: ! 'Kon %{model} niet opslaan: %{count} fouten gevonden.' feedback: create: - notice: + notice: Bericht verstuurd. Vriendelijk bedankt! new: - first_paragraph: + first_paragraph: Probleem gevonden? Voorstel? Idee? Verbeterpunt? We horen graag je feedback. second_paragraph: send: title: @@ -630,11 +657,13 @@ nl: create: notice: Rekening is gemaakt financial_transactions: - create: - notice: De transactie is opgeslagen. - create_collection: - alert: - notice: Alle transacties zijn opgeslagen. + controller: + create: + notice: De transactie is opgeslagen. + create_collection: + alert: + error_note_required: Notitie ontbreekt. + notice: Alle transacties zijn opgeslagen. index: balance: ! 'Tegoed: %{balance}' last_updated_at: (laatst bijgewerkt %{when} geleden) @@ -653,8 +682,8 @@ nl: sidebar: title: ordergroup: - remove: - remove_group: + remove: Verwijderen + remove_group: Huishouden verwijderen transactions: amount: Bedrag date: Datum @@ -708,7 +737,7 @@ nl: title: Tegoeden beheren ordergroups: account_balance: Tegoed - account_statement: + account_statement: Rekeningafschrift contact: name: Naam new_transaction: Nieuwe transactie @@ -717,31 +746,31 @@ nl: foodcoop: ordergroups: index: - name: - only_active: - only_active_desc: - title: + name: Naam ... + only_active: Alleen actieve + only_active_desc: (minstens eenmaal in de laatste 3 maanden besteld) + title: Huishoudens ordergroups: - last_ordered: - name: - user: + last_ordered: laatste besteld + name: Naam + user: Leden users: index: - body: - ph_name: - ph_ordergroup: - profile_link: - title: + body:

Hier kun je leden van deze foodcoop een bericht sturen.

Om je eigen contactgegevens te laten zien, moet je die vrijgeven op %{profile_link}.

+ ph_name: Naam ... + ph_ordergroup: Huishouden ... + profile_link: Instellingen + title: Leden workgroups: edit: - invite_link: - invite_new: - title: + invite_link: hier + invite_new: Nieuwe leden kun je %{invite_link} uitnodigen. + title: Groep bewerken index: - body: - title: + body:

De groep kan alleen aangepast worden door leden ervan.
Als je lid wilt worden van een groep, stuur dan een bericht aan een van de leden.

+ title: Werkgroepen workgroup: - edit: + edit: Groep bewerken show_tasks: group_orders: archive: @@ -773,6 +802,8 @@ nl: new_funds: Nieuw tegoed note: Notitie price: Prijs + reset_article_search: + search_article: sum: Som sum_amount: ! 'Huidig totaalbedrag:' supplier: Leverancier @@ -791,9 +822,9 @@ nl: title: Afgerekende bestellingen finished_orders: title: Niet afgerekende bestellingen - total_sum: + total_sum: Totaal funds: - account_balance: + account_balance: Tegoed available_funds: finished_orders: niet afgerekende bestellingen open_orders: Lopende bestellingen @@ -841,7 +872,7 @@ nl: title: Lopende bestellingen update: error_general: - error_stale: + error_stale: In de tussentijd heeft iemand anders ook bestelt, daarom kon je bestelling niet opgeslagen worden. Sorry! notice: De bestelling is opgeslagen. helpers: application: @@ -875,36 +906,36 @@ nl: home: apple_bar: desc: - more_info: + more_info: Meer informatie points: warning: changes_saved: Wijzigingen opgeslagen. index: - due_date_format: + due_date_format: ! '%A %d %B' messages: title: view_all: my_ordergroup: - funds: - last_update: - title: + funds: ! '| Beschikbaar tegoed:' + last_update: Laatst gewijzigd %{when} geleden + title: Mijn huishouden transactions: - amount: - note: - title: - view: - when: - where: + amount: Bedrag + note: Notitie + title: Laatste transacties + view: Rekeningafschrift tonen + when: Wanneer + where: Wie ordergroup: title: tasks_move: - action: - desc: - title: + action: Taken op je nemen/annuleren + desc: Je bent voor de volgende taken verantwoordelijk. + title: Taken op je nemen tasks_open: - action: - desc: - title: + action: open taken + desc: Er zijn %{size} + title: open taken title: Beginpagina your_tasks: Jouw taken no_ordergroups: Jammergenoeg ben je niet aangesloten bij een huishouden. @@ -930,8 +961,8 @@ nl: start_nav: admin: finances: - accounts: - settle: + accounts: Tegoeden bijwerken + settle: Bestelling afrekenen title: Financiën foodcoop: Foodcoop members: Leden @@ -957,7 +988,6 @@ nl: title: new: action: - back: body: success: layouts: @@ -1037,16 +1067,34 @@ nl: Vriendelijke groet van %{foodcoop}.' reset_password: - subject: - text: + subject: Nieuw wachtwoord voor %{username} + text: ! 'Beste %{user}, + + + Jij (of iemand anders) heeft een nieuw wachtwoord aangevraagd voor het foodcoop ordersysteem. + + Ga naar de volgende pagina om een nieuw wachtwoord in te voeren: %{link} + + Dit kan slechts éenmaal gedaan worden, en op zijn laatst op %{expires}. + + Wanneer je je wachtwoord niet wilt veranderen, hoef je niets te doen; dan blijft je huidige wachtwoord geldig. + + + Groeten van je foodcoop!' upcoming_tasks: - nextweek: - subject: - text0: - text1: + nextweek: ! 'Taken voor komende week:' + subject: Er is een taak te doen! + text0: ! 'Beste %{user}, + + + Je bent opgegeven voor "%{task}". Deze taak is morgen te vervullen (%{when})!' + text1: ! 'Mijn taken: %{user_tasks_url} + + + Groeten van %{foodcoop}.' messages: create: - notice: + notice: Bericht is opgeslagen en wordt verzonden. index: new: title: @@ -1063,9 +1111,9 @@ nl: subscribe: subscribe_msg: wiki: - no_user_found: - search: - search_user: + no_user_found: Geen gebruiker gevonden + search: Zoeken ... + search_user: Gebruiker zoeken title: show: all_messages: @@ -1076,15 +1124,15 @@ nl: title: model: delivery: - each_stock_article_must_be_unique: + each_stock_article_must_be_unique: In een levering mag ieder voorraadsartikel maar een keer voorkomen. membership: - no_admin_delete: + no_admin_delete: Lidmaatschap kan niet beeindigd worden. Je bent de laatste administrator. order_article: - error_price: + error_price: moet ingevuld worden en een huidige prijs hebben page: - redirect: + redirect: Doorverwijzing naar [[%{title}]]... user: - no_ordergroup: + no_ordergroup: geen huishouden navigation: admin: home: Overzicht @@ -1172,45 +1220,46 @@ nl: delimiter: ordergroups: edit: - title: + title: Huidhouden bewerken index: - title: + title: Huishoudens model: - error_single_group: - invalid_balance: + error_single_group: ! '%{user} behoort al tot een ander huishouden' + invalid_balance: is geen geldig nummer orders: articles: - article_count: - name: - prices: - prices_sum: - unit_quantity: - units_full: - units_ordered: + article_count: ! 'Bestelde artikelen:' + name: Naam + prices: Netto/bruto prijs + prices_sum: ! 'Totaal (netto/bruto prijs):' + unit_quantity: Groothandelseenheid + units_full: Volle eenheden + units_ordered: Bestelde eenheden create: - notice: + notice: De bestelling is aangemaakt. edit: - title: + title: Bestelling aanpassen fax: - amount: - articles: - customer_number: - delivery_day: - heading: - name: - number: - to_address: + amount: Aantal + articles: Artikelen + customer_number: Klantnummer + delivery_day: Bezorgdag + heading: Bestelling voor %{name} + name: Naam + number: Nummer + to_address: Verzendadres finish: notice: De bestelling is gesloten. form: - name: - note: - origin: - prices: - select_all: - stockit: - supplier: - title: + ignore_warnings: Waarschuwingen negeren + name: Naam + note: Notitie + origin: Herkomst + prices: Prijs (netto/FC) + select_all: Alles selecteren + stockit: Beschikbaar + supplier: Producent + title: Artikel unit_quantity: index: action_end: @@ -1228,8 +1277,10 @@ nl: error_closed: Bestelling was al afgerekend error_nosel: error_starts_before_ends: - notice_close: + notice_close: ! 'Bestelling: %{name}, tot %{ends}' stock: Voorraad + warning_ordered: + warning_ordered_stock: new: title: orders: @@ -1270,7 +1321,7 @@ nl: finished: gesloten open: lopend update: - notice: + notice: De bestelling is bijgewerkt. pages: all: new_page: @@ -1284,12 +1335,12 @@ nl: body: title_toc: create: - notice: + notice: Pagina is gemaakt. cshow: error_noexist: - redirect_notice: + redirect_notice: Doorverwezen van %{page} ... destroy: - notice: + notice: De pagina '%{page}' en alle subpagina's zijn verwijderd. edit: title: error_stale_object: @@ -1333,7 +1384,7 @@ nl: versions: title: update: - notice: + notice: Pagina is bijgewerkt. version: author: date_format: @@ -1354,11 +1405,14 @@ nl: title: Foodsoft aanmelden user: Gebruiker shared: + articles: + ordered: Besteld + ordered_desc: Hoeveel artikelen het lid besteld heeft (aantal + tolerantie) + received: Ontvangen + received_desc: Hoeveel artikelen het lid is toegewezen danwel heeft gekregen articles_by_articles: - ordered: Besteld (Hoeveelheid + Tolerantie) ordergroup: Huishouden price: Totaalprijs - received: Ontvangen articles_by_groups: fc_price: FC-Prijs fc_price_desc: Prijs inclusief belasting, borg en foodcoop-toeslag @@ -1367,19 +1421,17 @@ nl: unit: Eenheid unit_quantity: Gr.Eenh. unit_quantity_desc: Hoeveel eenheden per groothandelsverpakking - units: Aantal - units_desc: Toegewezen eenheden group: access: Toegang tot activated: actief address: Adres apple_limit: contact: Contact - deactivated: - description: + deactivated: inactief + description: Beschrijving members: Leden - no_weekly_job: - weekly_job: + no_weekly_job: geen wekelijkse taak ingesteld + weekly_job: wekelijkse taak group_form_fields: search: Zoeken ... search_user: Gebruiker zoeken @@ -1388,8 +1440,8 @@ nl: loginInfo: edit_profile: Profiel aanpassen feedback: - desc: - title: + desc: Fout gevonden? Opmerking? Idee? + title: Feedback help: Help homepage_title: Foodcoop startpagina bezoeken logout: Uitloggen @@ -1417,7 +1469,7 @@ nl: total_sum: Totaalsom who_ordered: Wie heeft besteld? workgroup_members: - title: + title: Groepsleden simple_form: error_notification: default_message: @@ -1531,7 +1583,7 @@ nl: address: contact_person: customer_number: - delivery_days: + delivery_days: Bezorgdagen email: fax: is_subscribed: @@ -1582,7 +1634,7 @@ nl: 'yes': Ja stock_takings: create: - notice: + notice: Inventarisatie is aangelegd. edit: title: index: @@ -1610,25 +1662,16 @@ nl: date: note: update: - notice: + notice: Inventarisatie is bijgewerkt. stockit: check: not_empty: destroy: - notice: + notice: Artikel %{name} is verwijdered. edit: title: form: price_hint: - history: - change_quantity: - datetime: - delivery: - new_quantity: - order: - reason: - stock_changes: - stock_taking: index: article: article: @@ -1654,15 +1697,24 @@ nl: new: search_text: title: + show: + change_quantity: + datetime: + delivery: + new_quantity: + order: + reason: + stock_changes: + stock_taking: stock_create: - notice: + notice: Voorraadsartikel is opgeslagen. stock_update: - notice: + notice: Voorraadsartikel is bijgewerkt. suppliers: create: - notice: + notice: Leverancier is aangemaakt. destroy: - notice: + notice: Leverancier is verwijderd edit: title: index: @@ -1688,7 +1740,7 @@ nl: new_delivery: show_deliveries: update: - notice: + notice: Leverancier is bijgewerkt support: array: last_word_connector: @@ -1696,7 +1748,7 @@ nl: words_connector: tasks: accept: - notice: + notice: Je hebt de taak geaccepteerd archive: title: archive_tasks: @@ -1705,9 +1757,9 @@ nl: task_format: who: create: - notice: + notice: Taak is aangemaakt destroy: - notice: + notice: Taak is verwijderd edit: title: warning_periodic: @@ -1745,7 +1797,7 @@ nl: title: repeated: set_done: - notice: + notice: De status van de taak is aangepast show: accept_task: confirm_delete_group: @@ -1756,8 +1808,8 @@ nl: reject_task: title: update: - notice: - notice_converted: + notice: Taak is bijgewerkt + notice_converted: Taak is bijgewerkt en omgezet naar een eenmalige taak. user: more: tasks_link: @@ -1778,7 +1830,6 @@ nl: close: Sluiten delete: Verwijder edit: Bewerk - history: marks: close: ! '×' success: diff --git a/config/routes.rb b/config/routes.rb index 0f96d7fa..533d2d97 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,8 +97,6 @@ Foodsoft::Application.routes.draw do get :articles_search get :fill_new_stock_article_form end - - get :history end resources :suppliers do diff --git a/config/schedule.rb b/config/schedule.rb index 631e3d28..26e2d1f2 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -3,13 +3,11 @@ # Upcoming tasks notifier every :day, :at => '7:20 am' do - rake "multicoops:run foodsoft:notify_upcoming_tasks" + rake "multicoops:run TASK=foodsoft:notify_upcoming_tasks" end # Weekly taks every :sunday, :at => '7:14 am' do - rake "multicoops:run foodsoft:create_upcoming_weekly_tasks" - rake "multicoops:run foodsoft:notify_users_of_weekly_task" -end - - + rake "multicoops:run TASK=foodsoft:create_upcoming_periodic_tasks" + rake "multicoops:run TASK=foodsoft:notify_users_of_weekly_task" +end \ No newline at end of file diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb new file mode 100644 index 00000000..ca9aab67 --- /dev/null +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -0,0 +1,18 @@ +class UpdateGroupOrderTotals < ActiveRecord::Migration + def self.up + say "If you have ever modified an order after it was settled, the group_order's " + + "price may be calculated incorrectly. This can take a lot of time on a " + + "large database." + + say "If you do want to update the ordergroup totals, open the rails console " + + "(by running `rails c`), and enter:" + + say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }", subitem: true + + say "You may want to check first that no undesired accounting issues are introduced. " + + "It may be wise to discuss this with those responsible for the ordering finances." + end + + def self.down + end +end diff --git a/lib/tasks/foodsoft.rake b/lib/tasks/foodsoft.rake index 04db66f9..1fdd2259 100644 --- a/lib/tasks/foodsoft.rake +++ b/lib/tasks/foodsoft.rake @@ -5,12 +5,12 @@ namespace :foodsoft do task :notify_upcoming_tasks => :environment do tasks = Task.where(done: false, due_date: 1.day.from_now.to_date) for task in tasks - puts "Send notifications for #{task.name} to .." + rake_say "Send notifications for #{task.name} to .." for user in task.users begin Mailer.upcoming_tasks(user, task).deliver if user.settings.notify['upcoming_tasks'] == 1 rescue - puts "deliver aborted for #{user.email}.." + rake_say "deliver aborted for #{user.email}.." end end end @@ -27,7 +27,7 @@ namespace :foodsoft do begin Mailer.not_enough_users_assigned(task, user).deliver rescue - puts "deliver aborted for #{user.email}" + rake_say "deliver aborted for #{user.email}" end end end @@ -47,3 +47,8 @@ namespace :foodsoft do end end end + +# Helper +def rake_say(message) + puts message unless Rake.application.options.silent +end diff --git a/lib/tasks/multicoops.rake b/lib/tasks/multicoops.rake index 621a0d54..a14020b7 100644 --- a/lib/tasks/multicoops.rake +++ b/lib/tasks/multicoops.rake @@ -8,7 +8,7 @@ namespace :multicoops do task :run => :environment do task_to_run = ENV['TASK'] FoodsoftConfig.each_coop do |coop| - puts "Run '#{task_to_run}' for #{coop}" + rake_say "Run '#{task_to_run}' for #{coop}" Rake::Task[task_to_run].execute end end @@ -17,8 +17,14 @@ namespace :multicoops do task :run_single => :environment do task_to_run = ENV['TASK'] FoodsoftConfig.select_foodcoop ENV['FOODCOOP'] - puts "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" + rake_say "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" Rake::Task[task_to_run].execute end end + + +# Helper +def rake_say(message) + puts message unless Rake.application.options.silent +end diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake index 45a78604..b6b58452 100644 --- a/lib/tasks/rspec.rake +++ b/lib/tasks/rspec.rake @@ -1,3 +1,6 @@ -require 'rspec/core/rake_task' -RSpec::Core::RakeTask.new(:spec) -task :default => :spec +begin + require 'rspec/core/rake_task' + RSpec::Core::RakeTask.new(:spec) + task :default => :spec +rescue LoadError +end diff --git a/script/heroku_deploy b/script/heroku_deploy index df7da72d..7f55fecf 100755 --- a/script/heroku_deploy +++ b/script/heroku_deploy @@ -92,7 +92,7 @@ require 'localeapp/rails' Localeapp.configure do |config| config.api_key = '$LOCALEAPP_KEY' - config.sending_environments = ['$RAILS_ENV'] + config.sending_environments = [] config.polling_environments = ['$RAILS_ENV'] end EOF diff --git a/spec/factories/article.rb b/spec/factories/article.rb index 19f41529..d79535fc 100644 --- a/spec/factories/article.rb +++ b/spec/factories/article.rb @@ -10,7 +10,7 @@ FactoryGirl.define do deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample } unit_quantity { rand(5) < 3 ? 1 : rand(1..20) } #supplier_id - article_category { FactoryGirl.create :article_category } + article_category { create :article_category } end factory :article_category do diff --git a/spec/factories/group_order.rb b/spec/factories/group_order.rb index 0b9983e6..1a665ca4 100644 --- a/spec/factories/group_order.rb +++ b/spec/factories/group_order.rb @@ -4,7 +4,7 @@ FactoryGirl.define do # requires order factory :group_order do - ordergroup { FactoryGirl.create(:user, groups: [FactoryGirl.create(:ordergroup)]).ordergroup } + ordergroup { create(:user, groups: [FactoryGirl.create(:ordergroup)]).ordergroup } end end diff --git a/spec/factories/order.rb b/spec/factories/order.rb index f3bf1bde..4bf63b00 100644 --- a/spec/factories/order.rb +++ b/spec/factories/order.rb @@ -4,7 +4,7 @@ FactoryGirl.define do factory :order do starts { Time.now } - supplier { FactoryGirl.create :supplier, article_count: (article_count.nil? ? true : article_count) } + supplier { create :supplier, article_count: (article_count.nil? ? true : article_count) } article_ids { supplier.articles.map(&:id) unless supplier.nil? } ignore do diff --git a/spec/factories/supplier.rb b/spec/factories/supplier.rb index e4a1c18b..4f67d91c 100644 --- a/spec/factories/supplier.rb +++ b/spec/factories/supplier.rb @@ -14,7 +14,7 @@ FactoryGirl.define do after :create do |supplier, evaluator| article_count = evaluator.article_count article_count = rand(1..99) if article_count == true - FactoryGirl.create_list :article, article_count, supplier: supplier + create_list :article, article_count, supplier: supplier end end diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 6f7e9e4d..f70c48c8 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -12,7 +12,7 @@ FactoryGirl.define do sequence(:nick) { |n| "admin#{n}" } first_name 'Administrator' after :create do |user, evaluator| - FactoryGirl.create :workgroup, role_admin: true, user_ids: [user.id] + create :workgroup, role_admin: true, user_ids: [user.id] end end end diff --git a/spec/integration/balancing_spec.rb b/spec/integration/balancing_spec.rb index 75133131..eba7973e 100644 --- a/spec/integration/balancing_spec.rb +++ b/spec/integration/balancing_spec.rb @@ -1,15 +1,15 @@ -require 'spec_helper' +require_relative '../spec_helper' describe 'settling an order', :type => :feature do - let(:admin) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_finance: true)] } - let(:supplier) { FactoryGirl.create :supplier } - let(:article) { FactoryGirl.create :article, supplier: supplier, unit_quantity: 1 } - let(:order) { FactoryGirl.create :order, supplier: supplier, article_ids: [article.id] } # need to ref article - let(:go1) { FactoryGirl.create :group_order, order: order } - let(:go2) { FactoryGirl.create :group_order, order: order } + let(:admin) { create :user, groups:[create(:workgroup, role_finance: true)] } + let(:supplier) { create :supplier } + let(:article) { create :article, supplier: supplier, unit_quantity: 1 } + let(:order) { create :order, supplier: supplier, article_ids: [article.id] } # need to ref article + 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) { 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(:goa1) { create :group_order_article, group_order: go1, order_article: oa } + let(:goa2) { create :group_order_article, group_order: go2, order_article: oa } before do goa1.update_quantities(3, 0) goa2.update_quantities(1, 0) @@ -50,6 +50,76 @@ describe 'settling an order', :type => :feature do end end + it 'keeps ordered quantities when article is deleted from resulting order' do + within("#order_article_#{oa.id}") do + click_link I18n.t('ui.delete') + page.driver.browser.switch_to.alert.accept + end + expect(page).to_not have_selector("#order_article_#{oa.id}") + expect(OrderArticle.exists?(oa.id)).to be_true + oa.reload + expect(oa.quantity).to eq(4) + expect(oa.tolerance).to eq(0) + expect(oa.units_to_order).to eq(0) + expect(goa1.reload.result).to eq(0) + expect(goa2.reload.result).to eq(0) + end + + it 'deletes an OrderArticle with no GroupOrderArticles' do + goa1.destroy + goa2.destroy + within("#order_article_#{oa.id}") do + click_link I18n.t('ui.delete') + page.driver.browser.switch_to.alert.accept + end + expect(page).to_not have_selector("#order_article_#{oa.id}") + expect(OrderArticle.exists?(oa.id)).to be_false + end + + it 'keeps ordered quantities when GroupOrderArticle is deleted from resulting order' do + click_link article.name + expect(page).to have_selector("#group_order_article_#{goa1.id}") + within("#group_order_article_#{goa1.id}") do + click_link I18n.t('ui.delete') + end + expect(page).to_not have_selector("#group_order_article_#{goa1.id}") + expect(OrderArticle.exists?(oa.id)).to be_true + expect(GroupOrderArticle.exists?(goa1.id)).to be_true + goa1.reload + expect(goa1.result).to eq(0) + expect(goa1.quantity).to eq(3) + expect(goa1.tolerance).to eq(0) + end + + it 'deletes a GroupOrderArticle with no ordered amounts' do + goa1.update_attributes({:quantity => 0, :tolerance => 0}) + click_link article.name + expect(page).to have_selector("#group_order_article_#{goa1.id}") + within("#group_order_article_#{goa1.id}") do + click_link I18n.t('ui.delete') + end + expect(page).to_not have_selector("#group_order_article_#{goa1.id}") + expect(OrderArticle.exists?(oa.id)).to be_true + expect(GroupOrderArticle.exists?(goa1.id)).to be_false + end + + it 'keeps product when amount is set to zero' do + within("#order_article_#{oa.id}") do + click_link I18n.t('ui.edit') + end + within("#edit_order_article_#{oa.id}") do + fill_in :order_article_units_to_order, :with => 0 + find('input[type="submit"]').click + end + expect(page).to have_selector("#order_article_#{oa.id}") + # make sure it still works after reloading + visit new_finance_order_path(order_id: order.id) + expect(page).to have_selector("#order_article_#{oa.id}") + expect(OrderArticle.exists?(oa.id)).to be_true + oa.reload + expect(oa.units_to_order).to eq(0) + end + end end diff --git a/spec/integration/product_distribution_example_spec.rb b/spec/integration/product_distribution_example_spec.rb index 12fd84f5..2d8cc229 100644 --- a/spec/integration/product_distribution_example_spec.rb +++ b/spec/integration/product_distribution_example_spec.rb @@ -1,12 +1,12 @@ -require 'spec_helper' +require_relative '../spec_helper' describe 'product distribution', :type => :feature do - let(:admin) { FactoryGirl.create :admin } - let(:user_a) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] } - let(:user_b) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] } - let(:supplier) { FactoryGirl.create :supplier } - let(:article) { FactoryGirl.create :article, supplier: supplier, unit_quantity: 5 } - let(:order) { FactoryGirl.create(:order, supplier: supplier, article_ids: [article.id]) } + let(:admin) { create :admin } + let(:user_a) { create :user, groups: [create(:ordergroup)] } + let(:user_b) { create :user, groups: [create(:ordergroup)] } + let(:supplier) { create :supplier } + let(:article) { create :article, supplier: supplier, unit_quantity: 5 } + let(:order) { create(:order, supplier: supplier, article_ids: [article.id]) } let(:oa) { order.order_articles.first } describe :type => :feature do diff --git a/spec/integration/session_spec.rb b/spec/integration/session_spec.rb index f86c2e59..d6942e94 100644 --- a/spec/integration/session_spec.rb +++ b/spec/integration/session_spec.rb @@ -1,7 +1,7 @@ -require 'spec_helper' +require_relative '../spec_helper' describe 'the session', :type => :feature do - let(:user) { FactoryGirl.create :user } + let(:user) { create :user } describe 'login page', :type => :feature do it 'is accesible' do diff --git a/spec/integration/supplier_spec.rb b/spec/integration/supplier_spec.rb index c93a810c..e50a8e25 100644 --- a/spec/integration/supplier_spec.rb +++ b/spec/integration/supplier_spec.rb @@ -1,16 +1,16 @@ -require 'spec_helper' +require_relative '../spec_helper' describe 'supplier', :type => :feature do - let(:supplier) { FactoryGirl.create :supplier } + let(:supplier) { create :supplier } describe :type => :feature, :js => true do - let(:user) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_suppliers: true)] } + let(:user) { create :user, groups:[create(:workgroup, role_suppliers: true)] } before { login user } it 'can be created' do visit suppliers_path click_on I18n.t('suppliers.index.action_new') - supplier = FactoryGirl.build :supplier + supplier = build :supplier within('#new_supplier') do fill_in 'supplier_name', :with => supplier.name fill_in 'supplier_address', :with => supplier.address @@ -28,8 +28,8 @@ describe 'supplier', :type => :feature do end describe :type => :feature, :js => true do - let(:article_category) { FactoryGirl.create :article_category } - let(:user) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_article_meta: true)] } + let(:article_category) { create :article_category } + let(:user) { create :user, groups:[create(:workgroup, role_article_meta: true)] } before { login user } it 'can visit supplier articles path' do diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 45b802b4..e3bab788 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -1,8 +1,8 @@ -require 'spec_helper' +require_relative '../spec_helper' describe Article do - let(:supplier) { FactoryGirl.create :supplier } - let(:article) { FactoryGirl.create :article, supplier: supplier } + let(:supplier) { create :supplier } + let(:article) { create :article, supplier: supplier } it 'has a unique name' do article2 = FactoryGirl.build :article, supplier: supplier, name: article.name @@ -44,4 +44,12 @@ describe Article do expect(article.article_prices.all.map(&:price)).to eq([article.price, oldprice]) end + it 'is not in an open order by default' do + expect(article.in_open_order).to be_nil + end + + it 'is knows its open order' do + order = create :order, supplier: supplier, article_ids: [article.id] + expect(article.in_open_order).to eq(order) + end end diff --git a/spec/models/group_order_article_spec.rb b/spec/models/group_order_article_spec.rb index 160b312f..3c332429 100644 --- a/spec/models/group_order_article_spec.rb +++ b/spec/models/group_order_article_spec.rb @@ -1,10 +1,10 @@ -require 'spec_helper' +require_relative '../spec_helper' describe GroupOrderArticle do - let(:user) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] } - let(:order) { FactoryGirl.create(:order).reload } - let(:go) { FactoryGirl.create :group_order, order: order, ordergroup: user.ordergroup } - let(:goa) { FactoryGirl.create :group_order_article, group_order: go, order_article: order.order_articles.first } + let(:user) { create :user, groups: [create(:ordergroup)] } + let(:order) { create(:order) } + let(:go) { create :group_order, order: order, ordergroup: user.ordergroup } + let(:goa) { create :group_order_article, group_order: go, order_article: order.order_articles.first } it 'has zero quantity by default' do expect(goa.quantity).to eq(0) end it 'has zero tolerance by default' do expect(goa.tolerance).to eq(0) end @@ -13,9 +13,9 @@ describe GroupOrderArticle do it 'has zero total price by default' do expect(goa.total_price).to eq(0) end describe do - let(:article) { FactoryGirl.create :article, supplier: order.supplier, unit_quantity: 1 } + let(:article) { create :article, supplier: order.supplier, unit_quantity: 1 } let(:oa) { order.order_articles.create(:article => article) } - let(:goa) { FactoryGirl.create :group_order_article, group_order: go, order_article: oa } + let(:goa) { create :group_order_article, group_order: go, order_article: oa } it 'can be ordered by piece' do goa.update_quantities(1, 0) @@ -42,7 +42,6 @@ describe GroupOrderArticle do expect(goa.quantity).to eq(0) expect(goa.tolerance).to eq(0) end - end end diff --git a/spec/models/group_order_spec.rb b/spec/models/group_order_spec.rb index 04f69c33..1dab91ed 100644 --- a/spec/models/group_order_spec.rb +++ b/spec/models/group_order_spec.rb @@ -1,8 +1,8 @@ -require 'spec_helper' +require_relative '../spec_helper' describe GroupOrder do - let(:user) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] } - let(:order) { FactoryGirl.create :order } + let(:user) { create :user, groups: [create(:ordergroup)] } + let(:order) { create :order } # the following two tests are currently disabled - https://github.com/foodcoops/foodsoft/issues/158 @@ -15,7 +15,7 @@ describe GroupOrder do #end describe do - let(:go) { FactoryGirl.create :group_order, order: order, ordergroup: user.ordergroup } + let(:go) { create :group_order, order: order, ordergroup: user.ordergroup } it 'has zero price initially' do expect(go.price).to eq(0) diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 4a8b7ebc..81c58bd5 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -1,22 +1,22 @@ -require 'spec_helper' +require_relative '../spec_helper' describe Order do it 'needs a supplier' do - expect(FactoryGirl.build(:order, supplier: nil)).to be_invalid + expect(build(:order, supplier: nil)).to be_invalid end it 'needs order articles' do - supplier = FactoryGirl.create :supplier, article_count: 0 - expect(FactoryGirl.build(:order, supplier: supplier)).to be_invalid + supplier = create :supplier, article_count: 0 + expect(build(:order, supplier: supplier)).to be_invalid end it 'can be created' do - expect(FactoryGirl.build(:order, article_count: 1)).to be_valid + expect(build(:order, article_count: 1)).to be_valid end describe 'with articles' do - let(:order) { FactoryGirl.create :order } + let(:order) { create :order } it 'is open by default' do expect(order).to be_open end it 'is not finished by default' do expect(order).to_not be_finished end diff --git a/spec/models/supplier_spec.rb b/spec/models/supplier_spec.rb index db01b148..b3536105 100644 --- a/spec/models/supplier_spec.rb +++ b/spec/models/supplier_spec.rb @@ -1,15 +1,15 @@ -require 'spec_helper' +require_relative '../spec_helper' describe Supplier do - let(:supplier) { FactoryGirl.create :supplier } + let(:supplier) { create :supplier } it 'has a unique name' do - supplier2 = FactoryGirl.build :supplier, name: supplier.name + supplier2 = build :supplier, name: supplier.name expect(supplier2).to be_invalid end it 'has valid articles' do - supplier = FactoryGirl.create :supplier, article_count: true + supplier = create :supplier, article_count: true supplier.articles.all.each {|a| expect(a).to be_valid } end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d679bea8..bc90fa01 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,9 +1,9 @@ -require 'spec_helper' +require_relative '../spec_helper' describe User do it 'is correctly created' do - user = FactoryGirl.create :user, + user = create :user, nick: 'johnnydoe', first_name: 'Johnny', last_name: 'DoeBar', email: 'johnnydoe@foodcoop.test', phone: '+1234567890' expect(user.nick).to eq('johnnydoe') @@ -15,7 +15,7 @@ describe User do end describe 'does not have the role' do - let(:user) { FactoryGirl.create :user } + let(:user) { create :user } it 'admin' do expect(user.role_admin?).to be_false end it 'finance' do expect(user.role_finance?).to be_false end it 'article_meta' do expect(user.role_article_meta?).to be_false end @@ -24,7 +24,7 @@ describe User do end describe do - let(:user) { FactoryGirl.create :user, password: 'blahblah' } + let(:user) { create :user, password: 'blahblah' } it 'can authenticate with correct password' do expect(User.authenticate(user.nick, 'blahblah')).to be_true @@ -44,15 +44,15 @@ describe User do end it 'has a unique nick' do - expect(FactoryGirl.build(:user, nick: user.nick, email: "x-#{user.email}")).to be_invalid + expect(build(:user, nick: user.nick, email: "x-#{user.email}")).to be_invalid end it 'has a unique email' do - expect(FactoryGirl.build(:user, email: "#{user.email}")).to be_invalid + expect(build(:user, email: "#{user.email}")).to be_invalid end end describe 'admin' do - let(:user) { FactoryGirl.create :admin } + let(:user) { create :admin } it 'default admin role' do expect(user.role_admin?).to be_true end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1324abca..adbc4248 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,6 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' -require 'support/coverage' # needs to be first +require_relative 'support/coverage' # needs to be first require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 00000000..34b6e657 --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,4 @@ +RSpec.configure do |config| + # load FactoryGirl shortcuts create(), etc. + config.include FactoryGirl::Syntax::Methods +end \ No newline at end of file