From 497c9e0cf1ba60ce709aa69c39c60bfd71ae206f Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 2 Jul 2013 12:01:25 +0200 Subject: [PATCH 01/50] fix closed group_order totals --- app/models/group_order_article.rb | 2 +- .../20130702113610_update_group_order_totals.rb | 12 ++++++++++++ db/schema.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20130702113610_update_group_order_totals.rb diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 11f3b447..36e67828 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -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/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb new file mode 100644 index 00000000..4e45749f --- /dev/null +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -0,0 +1,12 @@ +class UpdateGroupOrderTotals < ActiveRecord::Migration + def self.up + # The group_order total was updated to the total ordered amount instead of + # the amount received. Now this is fixed, the totals need to be updated. + GroupOrder.all.each do |go| + go.order.closed? and go.update_price! + end + end + + def self.down + end +end diff --git a/db/schema.rb b/db/schema.rb index 188ea83b..59f35c66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20121230142516) do +ActiveRecord::Schema.define(:version => 20130715233410) do create_table "article_categories", :force => true do |t| t.string "name", :default => "", :null => false From 1c9fad0a7b8de75e56f93d429bf52cb8ee991497 Mon Sep 17 00:00:00 2001 From: Robert Waltemath Date: Tue, 3 Sep 2013 12:09:33 +0200 Subject: [PATCH 02/50] Added validation for removed but ordered articles. --- app/models/order.rb | 25 ++++++++++++++----------- app/views/orders/_form.html.haml | 11 ++++++++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 7910dfc3..35224856 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -17,6 +17,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 +56,7 @@ 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 def open? @@ -206,6 +207,12 @@ class Order < ActiveRecord::Base update_attributes! state: 'closed', updated_by: user end + def articles_to_be_removed_and_ordered + chosen_order_articles = order_articles.find_all_by_article_id(article_ids) + to_be_removed = order_articles - chosen_order_articles + to_be_removed.select { |a| a.quantity > 0 or a.tolerance > 0 } + end + protected def starts_before_ends @@ -216,17 +223,13 @@ class Order < ActiveRecord::Base errors.add(:articles, I18n.t('articles.model.error_nosel')) if article_ids.empty? end + def keep_ordered_articles + unless articles_to_be_removed_and_ordered.empty? + errors.add(:articles, "Die markierten Artikel wurden in der laufenden Bestellung bereits bestellt. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen dieses Artikels gelöscht. Bei Lagerbestellungen kann dies je nach Verwendung bedeuten, dass bereits gekaufte Artikel nicht abgerechnet werden!") + 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/views/orders/_form.html.haml b/app/views/orders/_form.html.haml index 78cd3ca8..cb138d4b 100644 --- a/app/views/orders/_form.html.haml +++ b/app/views/orders/_form.html.haml @@ -27,9 +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' : '' + / check if the article is selected or has an error + - included = @order.article_ids.include?(article.id.to_s) + - if included + - included_class = 'selected' + - elsif @order.errors.has_key?(:articles) and @order.articles_to_be_removed_and_ordered.map{|a| a.article_id}.include?(article.id) + - included_class = 'error' + - else + - included_class = '' %tr{:class => included_class, :id => article.id.to_s } %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 From bb25bdc6eb0a376387a862f2011daffc2047b574 Mon Sep 17 00:00:00 2001 From: Robert Waltemath Date: Tue, 3 Sep 2013 17:37:49 +0200 Subject: [PATCH 03/50] Improved error data passing. --- app/models/order.rb | 17 ++++++++++------- app/views/orders/_form.html.haml | 11 +++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 35224856..b7fdf1d5 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -59,6 +59,11 @@ class Order < ActiveRecord::Base @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? state == "open" end @@ -207,12 +212,6 @@ class Order < ActiveRecord::Base update_attributes! state: 'closed', updated_by: user end - def articles_to_be_removed_and_ordered - chosen_order_articles = order_articles.find_all_by_article_id(article_ids) - to_be_removed = order_articles - chosen_order_articles - to_be_removed.select { |a| a.quantity > 0 or a.tolerance > 0 } - end - protected def starts_before_ends @@ -224,8 +223,12 @@ class Order < ActiveRecord::Base end def keep_ordered_articles - unless articles_to_be_removed_and_ordered.empty? + 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? errors.add(:articles, "Die markierten Artikel wurden in der laufenden Bestellung bereits bestellt. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen dieses Artikels gelöscht. Bei Lagerbestellungen kann dies je nach Verwendung bedeuten, dass bereits gekaufte Artikel nicht abgerechnet werden!") + @erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id } end end diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml index cb138d4b..e5bdf07e 100644 --- a/app/views/orders/_form.html.haml +++ b/app/views/orders/_form.html.haml @@ -29,13 +29,12 @@ - for article in articles / check if the article is selected or has an error - included = @order.article_ids.include?(article.id.to_s) + - row_class = '' - if included - - included_class = 'selected' - - elsif @order.errors.has_key?(:articles) and @order.articles_to_be_removed_and_ordered.map{|a| a.article_id}.include?(article.id) - - included_class = 'error' - - else - - included_class = '' - %tr{:class => included_class, :id => article.id.to_s } + - 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 From 805071f3fb1494420f552a5d818a7ae4d43b23a2 Mon Sep 17 00:00:00 2001 From: Robert Waltemath Date: Wed, 4 Sep 2013 10:52:14 +0200 Subject: [PATCH 04/50] Added checkbox to ignore warnings. Added translations. --- app/models/order.rb | 10 ++++++---- app/views/orders/_form.html.haml | 4 ++++ config/locales/de.yml | 2 ++ config/locales/en.yml | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index b7fdf1d5..9d67214a 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 @@ -215,19 +217,19 @@ 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? - errors.add(:articles, "Die markierten Artikel wurden in der laufenden Bestellung bereits bestellt. Wenn Du sie hier abwählst, werden alle bestehenden Bestellungen dieses Artikels gelöscht. Bei Lagerbestellungen kann dies je nach Verwendung bedeuten, dass bereits gekaufte Artikel nicht abgerechnet werden!") + unless to_be_removed_but_ordered.empty? or ignore_warnings + errors.add(:articles, I18n.t('orders.model.warning_ordered')) @erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id } end end diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml index e5bdf07e..86d24106 100644 --- a/app/views/orders/_form.html.haml +++ b/app/views/orders/_form.html.haml @@ -56,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/config/locales/de.yml b/config/locales/de.yml index 11abe693..90d47e37 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1297,6 +1297,7 @@ de: finish: notice: Die Bestellung wurde beendet. form: + ignore_warnings: Warnungen ignorieren name: Name note: Notiz origin: Herkunft @@ -1324,6 +1325,7 @@ 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. Bei Lagerbestellungen kann dies je nach Verwendung bedeuten, dass bereits gekaufte Artikel nicht abgerechnet werden!' new: title: Neue Bestellung anlegen orders: diff --git a/config/locales/en.yml b/config/locales/en.yml index c9eecb77..e6014639 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1299,6 +1299,7 @@ en: finish: notice: The order has been closed. form: + ignore_warnings: Ignore warnings name: Name note: Note origin: Origin @@ -1326,6 +1327,7 @@ 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. In case of stock orders this might mean that already bought articles will not be accounted for!' new: title: Create new order orders: From 1bb257c41baacb4b072d63c9851a0cb5fe2a5acc Mon Sep 17 00:00:00 2001 From: wvengen Date: Fri, 13 Sep 2013 15:37:30 +0200 Subject: [PATCH 05/50] remember member order when order article is deleted + test --- .../finance/order_articles_controller.rb | 10 ++++++- spec/integration/balancing_spec.rb | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) 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/spec/integration/balancing_spec.rb b/spec/integration/balancing_spec.rb index 75133131..63245849 100644 --- a/spec/integration/balancing_spec.rb +++ b/spec/integration/balancing_spec.rb @@ -50,6 +50,32 @@ 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 + end end From 8ca95f396fe1afa539e6a3b332728375ab987c00 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 16 Sep 2013 23:52:58 +0200 Subject: [PATCH 06/50] make both ordered and received products visible in order screens and pdfs --- app/documents/order_by_articles.rb | 19 +++++++++++---- app/documents/order_by_groups.rb | 23 +++++++++++++------ app/models/group_order_article.rb | 2 +- .../shared/_articles_by_articles.html.haml | 8 ++++--- .../shared/_articles_by_groups.html.haml | 16 ++++++++----- config/locales/de.yml | 7 ++++-- config/locales/en.yml | 15 +++++++----- 7 files changed, 60 insertions(+), 30 deletions(-) diff --git a/app/documents/order_by_articles.rb b/app/documents/order_by_articles.rb index 705393f5..99a3704b 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') + dimrows = [] for goa in order_article.group_order_articles 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..2162e049 100644 --- a/app/documents/order_by_groups.rb +++ b/app/documents/order_by_groups.rb @@ -13,11 +13,9 @@ class OrderByGroups < OrderPdf def body # Start rendering @order.group_orders.each do |group_order| - text group_order.ordergroup.name, size: 9, style: :bold - 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/group_order_article.rb b/app/models/group_order_article.rb index 11f3b447..ecf3df32 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -14,7 +14,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 => 'result > 0 OR quantity > 0 OR tolerance > 0' localize_input_of :result diff --git a/app/views/shared/_articles_by_articles.html.haml b/app/views/shared/_articles_by_articles.html.haml index df4528a3..c8522794 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]) @@ -14,7 +16,7 @@ = "(#{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')} + %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..5ca1b3e5 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 @@ -14,7 +16,7 @@ - for group_order in order.group_orders.all %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/config/locales/de.yml b/config/locales/de.yml index c0000036..c77f4dcb 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -482,14 +482,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 +507,7 @@ de: - Gebinde - Einheit - Preis/Einheit + - Summe total: Gesamtpreis order_matrix: filename: Bestellung %{name}-%{date} - Sortiermatrix diff --git a/config/locales/en.yml b/config/locales/en.yml index 2fa64013..4bde07ca 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -484,14 +484,16 @@ en: 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 @@ -1457,11 +1459,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 +1475,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 From b11706f30a9a58f77f4e4e09828a4ddd7ae6d469 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 17 Sep 2013 00:25:38 +0200 Subject: [PATCH 07/50] sort ordergroups in order screen and pdfs --- app/documents/order_by_articles.rb | 2 +- app/documents/order_by_groups.rb | 2 +- app/models/group_order.rb | 2 ++ app/models/group_order_article.rb | 2 +- app/views/shared/_articles_by_articles.html.haml | 2 +- app/views/shared/_articles_by_groups.html.haml | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/documents/order_by_articles.rb b/app/documents/order_by_articles.rb index 99a3704b..a6e1b0b5 100644 --- a/app/documents/order_by_articles.rb +++ b/app/documents/order_by_articles.rb @@ -14,7 +14,7 @@ class OrderByArticles < OrderPdf @order.order_articles.ordered.each do |order_article| rows = [] dimrows = [] - for goa in order_article.group_order_articles + for goa in order_article.group_order_articles.ordered rows << [goa.group_order.ordergroup.name, "#{goa.quantity} + #{goa.tolerance}", goa.result, diff --git a/app/documents/order_by_groups.rb b/app/documents/order_by_groups.rb index 2162e049..746a167f 100644 --- a/app/documents/order_by_groups.rb +++ b/app/documents/order_by_groups.rb @@ -12,7 +12,7 @@ class OrderByGroups < OrderPdf def body # Start rendering - @order.group_orders.each do |group_order| + @order.group_orders.ordered.each do |group_order| total = 0 rows = [] dimrows = [] 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 ecf3df32..a8d0a688 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -14,7 +14,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 OR quantity > 0 OR tolerance > 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 diff --git a/app/views/shared/_articles_by_articles.html.haml b/app/views/shared/_articles_by_articles.html.haml index c8522794..6d952363 100644 --- a/app/views/shared/_articles_by_articles.html.haml +++ b/app/views/shared/_articles_by_articles.html.haml @@ -15,7 +15,7 @@ = 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 + - 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}" diff --git a/app/views/shared/_articles_by_groups.html.haml b/app/views/shared/_articles_by_groups.html.haml index 5ca1b3e5..9470fb49 100644 --- a/app/views/shared/_articles_by_groups.html.haml +++ b/app/views/shared/_articles_by_groups.html.haml @@ -13,7 +13,7 @@ %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 => "7"} From f224735718f1ac7d25bebffd91edc40c6286d7e6 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 17 Sep 2013 14:19:46 +0200 Subject: [PATCH 08/50] remember what member ordered when deleted in balancing screen --- .../group_order_articles_controller.rb | 8 +++++- spec/integration/balancing_spec.rb | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) 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/spec/integration/balancing_spec.rb b/spec/integration/balancing_spec.rb index 63245849..8e2321ac 100644 --- a/spec/integration/balancing_spec.rb +++ b/spec/integration/balancing_spec.rb @@ -76,6 +76,33 @@ describe 'settling an order', :type => :feature do 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 + end end From dbb0bb4506892ff681fb4ef25fdc9ec353f4f534 Mon Sep 17 00:00:00 2001 From: Robert Waltemath Date: Wed, 18 Sep 2013 10:07:50 +0200 Subject: [PATCH 09/50] Added hint when changing total delivered amount. --- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index c0000036..a74f95b5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1525,7 +1525,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: diff --git a/config/locales/en.yml b/config/locales/en.yml index 2fa64013..d33f02cd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1530,7 +1530,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 otherwise ordergroups will be accounted for undelivered articles! update_current_price: Also update the price of the current order stock_article: copy_stock_article: From 142de28adef1bdd714b67202c0c34ca62f1f9e0b Mon Sep 17 00:00:00 2001 From: Robert Waltemath Date: Wed, 18 Sep 2013 10:50:56 +0200 Subject: [PATCH 10/50] Split up messages for stock/ supplier orders. --- app/models/order.rb | 2 +- config/locales/de.yml | 3 ++- config/locales/en.yml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 9d67214a..f4545d02 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -229,7 +229,7 @@ class Order < ActiveRecord::Base 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('orders.model.warning_ordered')) + 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 diff --git a/config/locales/de.yml b/config/locales/de.yml index b603d28f..c11305ae 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1327,7 +1327,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. Bei Lagerbestellungen kann dies je nach Verwendung bedeuten, dass bereits gekaufte Artikel nicht abgerechnet werden!' + 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: diff --git a/config/locales/en.yml b/config/locales/en.yml index 5c507ac3..ecc41870 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1332,7 +1332,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. In case of stock orders this might mean that already bought articles will not be accounted for!' + 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: From f462e70e495b15fa7d731452573f913a4efae6f5 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Wed, 18 Sep 2013 12:44:41 +0200 Subject: [PATCH 11/50] uses FactoryGirl helpers --- spec/factories/article.rb | 2 +- spec/factories/group_order.rb | 2 +- spec/factories/order.rb | 2 +- spec/factories/supplier.rb | 2 +- spec/factories/user.rb | 2 +- spec/integration/balancing_spec.rb | 18 +++++++++--------- .../product_distribution_example_spec.rb | 14 +++++++------- spec/integration/session_spec.rb | 4 ++-- spec/integration/supplier_spec.rb | 12 ++++++------ spec/models/article_spec.rb | 7 +++---- spec/models/group_order_article_spec.rb | 15 +++++++-------- spec/models/group_order_spec.rb | 8 ++++---- spec/models/order_spec.rb | 12 ++++++------ spec/models/supplier_spec.rb | 8 ++++---- spec/models/user_spec.rb | 14 +++++++------- spec/spec_helper.rb | 2 +- spec/support/factory_girl.rb | 4 ++++ 17 files changed, 65 insertions(+), 63 deletions(-) create mode 100644 spec/support/factory_girl.rb 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..83ed29bb 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) 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..056b8dd6 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 @@ -43,5 +43,4 @@ describe Article do article.save! expect(article.article_prices.all.map(&:price)).to eq([article.price, oldprice]) 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 From b515d5b2e85937b6375afcef27142a3f11410959 Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Wed, 18 Sep 2013 14:52:23 +0200 Subject: [PATCH 12/50] Fixed whenever cron config. --- config/schedule.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 From 5444c3a1c0eeaae71afa33b96b30c894b1ad44c4 Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Wed, 18 Sep 2013 17:48:24 +0200 Subject: [PATCH 13/50] Merged updates from localeapp. --- config/locales/en.yml | 6 ++- config/locales/nl.yml | 121 ++++++++++++++++++++++++------------------ 2 files changed, 73 insertions(+), 54 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4bde07ca..3618f4bc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -314,7 +314,7 @@ en: title: Synchronize articles with external database unit_quantity_short: unit quantity update: - body: ! '

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

+ body: ! '

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

Differences with the old articles are marked yellow.

' title: Update ... @@ -1467,6 +1467,8 @@ en: articles_by_articles: ordergroup: Ordergroup price: Total price + ordered: Ordered (Amount + Tolerance) + received: Received articles_by_groups: fc_price: FC-Price fc_price_desc: Price including taxes, deposit and Foodcoop-charge @@ -1475,6 +1477,8 @@ 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 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 628bd0e8..4b632cc5 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -39,8 +39,8 @@ 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 price: netto prijs tax: BTW @@ -87,7 +87,7 @@ nl: task: attributes: done: - exclusion: + exclusion: gedane taken kunnen niet herhaald worden template: body: ! 'Controleer de volgende velden:' header: @@ -145,7 +145,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: @@ -263,7 +263,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 @@ -312,11 +312,13 @@ nl: price_short: prijs submit: Alle wissen/bijwerken 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. De oude waarden zijn grijs, en de tekstvelden bevatten de huidige waarden.

+ +

Verschillen met de oude artikelen zijn geel gemarkeerd.

' + title: Bijwerken ... + update_msg: ! 'De volgende artikelen moeten bijgewerkt worden: ' upload: body: fields: @@ -498,33 +500,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 +540,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: @@ -708,7 +723,7 @@ nl: title: Tegoeden beheren ordergroups: account_balance: Tegoed - account_statement: + account_statement: Rekeningafschrift contact: name: Naam new_transaction: Nieuwe transactie @@ -717,31 +732,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: @@ -791,9 +806,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 @@ -889,12 +904,12 @@ nl: last_update: title: 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: @@ -930,7 +945,7 @@ nl: start_nav: admin: finances: - accounts: + accounts: Tegoeden bijwerken settle: title: Financiën foodcoop: Foodcoop From 5681553ca085188f58cfa29bf771ef8bb6d704a9 Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Wed, 18 Sep 2013 18:04:40 +0200 Subject: [PATCH 14/50] Avoid connecting to redis backend in test environment. This makes it also possible to test if certain background task are doing what they should do. --- config/initializers/resque.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/initializers/resque.rb 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? From 4c27a98e0620ebea422325e04618f1876fd73ffd Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Wed, 18 Sep 2013 18:19:41 +0200 Subject: [PATCH 15/50] Fixed failing asset precompiling in production mode. --- lib/tasks/rspec.rake | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 From 2d549fc1daf85f81acb3c9b5a8d54bd13641d7db Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 18:33:49 +0200 Subject: [PATCH 16/50] do not recalculate GroupOrder totals, but show message to do so on migrations --- .../20130702113610_update_group_order_totals.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index 4e45749f..ca9aab67 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -1,10 +1,16 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration def self.up - # The group_order total was updated to the total ordered amount instead of - # the amount received. Now this is fixed, the totals need to be updated. - GroupOrder.all.each do |go| - go.order.closed? and go.update_price! - end + 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 From f1f7e8d7098d6fd72f539fcf2064ac3b03fc252e Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 19:14:48 +0200 Subject: [PATCH 17/50] localeapp roundtrip --- config/locales/de.yml | 13 +++++++------ config/locales/en.yml | 8 ++------ config/locales/fr.yml | 12 ++++++++---- config/locales/nl.yml | 12 ++++++++---- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index abee707b..07a7f2f9 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1330,8 +1330,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!' + 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: @@ -1458,11 +1458,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 @@ -1471,8 +1474,6 @@ de: unit: Einheit unit_quantity: GebGr unit_quantity_desc: Gebindegröße - units: Menge - units_desc: Zugeteilte Einheiten group: access: Zugriff auf activated: aktiviert diff --git a/config/locales/en.yml b/config/locales/en.yml index 5608b458..d21665ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1334,8 +1334,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.' + 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: @@ -1470,8 +1470,6 @@ en: articles_by_articles: ordergroup: Ordergroup price: Total price - ordered: Ordered (Amount + Tolerance) - received: Received articles_by_groups: fc_price: FC-Price fc_price_desc: Price including taxes, deposit and Foodcoop-charge @@ -1480,8 +1478,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 diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 057acd4e..fe52e77e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1308,6 +1308,7 @@ fr: finish: notice: La commande a été close. form: + ignore_warnings: name: Nom note: Note origin: Origine @@ -1335,6 +1336,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 +1462,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 +1478,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é diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 4b632cc5..c2f0ee5b 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1218,6 +1218,7 @@ nl: finish: notice: De bestelling is gesloten. form: + ignore_warnings: name: note: origin: @@ -1245,6 +1246,8 @@ nl: error_starts_before_ends: notice_close: stock: Voorraad + warning_ordered: + warning_ordered_stock: new: title: orders: @@ -1369,11 +1372,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 @@ -1382,8 +1388,6 @@ 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 From 50a8cce9fd3d5c06743fc82d56e0e2bcdd7bd140 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 19:21:54 +0200 Subject: [PATCH 18/50] do not update localeapp from translation instance, since it may run a slightly older version [ci skip] --- script/heroku_deploy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6b0146eb9587ccb9c5c05d6ae2cf24e6ab3375ae Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 22:27:53 +0200 Subject: [PATCH 19/50] add spec for article#in_open_order --- spec/models/article_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 056b8dd6..e3bab788 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -43,4 +43,13 @@ describe Article do article.save! 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 From c7f28a3b5c89c8dee9e197bf16a29a29baa20900 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 22:45:57 +0200 Subject: [PATCH 20/50] use plain Ruby memoization to fix deprecation (closes #121) --- app/models/article.rb | 10 ++--- app/models/group_order_article.rb | 72 +++++++++++++++---------------- 2 files changed, 41 insertions(+), 41 deletions(-) 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_article.rb b/app/models/group_order_article.rb index a8d0a688..50c1aa0e 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 @@ -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 From 14d4d2f12d8c5ac68eeaa4fad8f5f5b591f75f62 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 18 Sep 2013 23:15:21 +0200 Subject: [PATCH 21/50] Add list.js for filtering articles (another try for foodcoops#143) --- app/assets/javascripts/application.js | 2 + app/assets/javascripts/list.unlist.js | 150 +++++ app/assets/stylesheets/application.css | 1 + app/assets/stylesheets/list.unlist.css | 3 + app/views/group_orders/_form.html.haml | 10 +- config/locales/de.yml | 1 + config/locales/en.yml | 1 + vendor/assets/javascripts/list.js | 775 +++++++++++++++++++++++++ 8 files changed, 941 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/list.unlist.js create mode 100644 app/assets/stylesheets/list.unlist.css create mode 100644 vendor/assets/javascripts/list.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c6d6e36a..09771539 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -8,6 +8,8 @@ //= require bootstrap-datepicker/locales/bootstrap-datepicker.de //= require bootstrap-datepicker/locales/bootstrap-datepicker.nl //= require jquery.observe_field +//= require list +//= require list.unlist //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js new file mode 100644 index 00000000..f649369f --- /dev/null +++ b/app/assets/javascripts/list.unlist.js @@ -0,0 +1,150 @@ +/******************************************************************************* +******************************************************************************** + +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 +*******************************************************************************/ + + + +/******************************************************************************* +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 $ +List.prototype.templateEngines.unlist = function(list, settings) { + var listSource = ListJsHelpers.getByClass(settings.listClass, list.listContainer, true), + itemSource = getItemSource(settings.item), + templater = this; + + function getItemSource(item) { + if (item === undefined) { + var nodes = listSource.childNodes, + items = []; + + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + return nodes[i]; + } + } + return null; + } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! + var div = document.createElement('div'); + div.innerHTML = item; + return div.firstChild; + } else { + return document.getElementById(settings.item); + } + } + + var ensure = { + created: function(item) { + if (item.elm === undefined) { + templater.create(item); + } + } + }; + + /* Get values from element */ + this.get = function(item, valueNames) { + ensure.created(item); + var values = {}; + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm = ListJsHelpers.getByClass(valueNames[i], item.elm, true); + values[valueNames[i]] = elm ? elm.innerHTML : ""; + } + return values; + }; + + /* Sets values at element */ + this.set = function(item, values) { + ensure.created(item); + for(var v in values) { + if (values.hasOwnProperty(v)) { + // TODO speed up if possible + var elm = ListJsHelpers.getByClass(v, item.elm, true); + if (elm) { + elm.innerHTML = values[v]; + } + } + } + }; + + this.create = function(item) { + if (item.elm !== undefined) { + return; + } + /* If item source does not exists, use the first item in list as + source for new items */ + var newItem = itemSource.cloneNode(true); + newItem.id = ""; + item.elm = newItem; + templater.set(item, item.values()); + }; + this.remove = function(item) { + listSource.removeChild(item.elm); + }; + 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) { + if (item.elm !== undefined) { + $(item.elm).addClass('unlisted'); + } + }; + this.clear = function() { + $(listSource.childNodes).addClass('unlisted'); + }; +}; + +/******************************************************************************* +End copy-pasted and modified code +*******************************************************************************/ 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/list.unlist.css b/app/assets/stylesheets/list.unlist.css new file mode 100644 index 00000000..b0ed4600 --- /dev/null +++ b/app/assets/stylesheets/list.unlist.css @@ -0,0 +1,3 @@ +.list .unlisted { + display: none; +} \ No newline at end of file diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 2bd2f30b..78bccce5 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -7,6 +7,8 @@ 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) + new List(document.body, { valueNames: ['name'], engine: 'unlist' }); }); - title t('.title'), false @@ -37,6 +39,10 @@ .well.pull-right = render 'switch_order', current_order: @order +.row-fluid + .well.clear + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large search' + = form_for @group_order do |f| = f.hidden_field :lock_version = f.hidden_field :order_id @@ -59,10 +65,10 @@ %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 - %td + %td.name = category %i.icon-tag %td{colspan: "9"} diff --git a/config/locales/de.yml b/config/locales/de.yml index 07a7f2f9..ab08d6dc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -787,6 +787,7 @@ de: new_funds: Neuer Kontostand note: Notiz price: Preis + search_article: Artikel suchen... sum: Summe sum_amount: ! 'Gesamtbestellmenge bisher:' supplier: Lieferant diff --git a/config/locales/en.yml b/config/locales/en.yml index d21665ec..18356d73 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -791,6 +791,7 @@ en: new_funds: New account balance note: Note price: Price + search_article: Search for article... sum: Sum sum_amount: Current amount supplier: Supplier diff --git a/vendor/assets/javascripts/list.js b/vendor/assets/javascripts/list.js new file mode 100644 index 00000000..44ce6879 --- /dev/null +++ b/vendor/assets/javascripts/list.js @@ -0,0 +1,775 @@ +/* +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. +*/ +(function( window, undefined ) { +"use strict"; +var document = window.document, + h; + +var List = function(id, options, values) { + var self = this, + templater, + init, + initialItems, + Item, + Templater, + sortButtons, + events = { + 'updated': [] + }; + this.listContainer = (typeof(id) == 'string') ? document.getElementById(id) : id; + // Check if the container exists. If not return instead of breaking the javascript + if (!this.listContainer) + return; + + this.items = []; + this.visibleItems = []; // These are the items currently visible + this.matchingItems = []; // These are the items currently matching filters and search, regadlessof visible count + this.searched = false; + this.filtered = false; + + this.list = null; + this.templateEngines = {}; + + this.page = options.page || 200; + this.i = options.i || 1; + + init = { + start: function(values, options) { + options.plugins = options.plugins || {}; + this.classes(options); + templater = new Templater(self, options); + this.callbacks(options); + this.items.start(values, options); + self.update(); + this.plugins(options.plugins); + }, + classes: function(options) { + options.listClass = options.listClass || 'list'; + options.searchClass = options.searchClass || 'search'; + options.sortClass = options.sortClass || 'sort'; + }, + callbacks: function(options) { + self.list = h.getByClass(options.listClass, self.listContainer, true); + h.addEvent(h.getByClass(options.searchClass, self.listContainer), 'keyup', self.search); + sortButtons = h.getByClass(options.sortClass, self.listContainer); + h.addEvent(sortButtons, 'click', self.sort); + }, + items: { + start: function(values, options) { + if (options.valueNames) { + var itemsToIndex = this.get(), + valueNames = options.valueNames; + if (options.indexAsync) { + this.indexAsync(itemsToIndex, valueNames); + } else { + this.index(itemsToIndex, valueNames); + } + } + if (values !== undefined) { + self.add(values); + } + }, + get: function() { + // return h.getByClass('item', self.list); + var nodes = self.list.childNodes, + items = []; + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + items.push(nodes[i]); + } + } + return items; + }, + index: function(itemElements, valueNames) { + for (var i = 0, il = itemElements.length; i < il; i++) { + self.items.push(new Item(valueNames, itemElements[i])); + } + }, + indexAsync: function(itemElements, valueNames) { + var itemsToIndex = itemElements.splice(0, 100); // TODO: If < 100 items, what happens in IE etc? + this.index(itemsToIndex, valueNames); + if (itemElements.length > 0) { + setTimeout(function() { + init.items.indexAsync(itemElements, valueNames); + }, + 10); + } else { + self.update(); + // TODO: Add indexed callback + } + } + }, + plugins: function(plugins) { + var locals = { + templater: templater, + init: init, + initialItems: initialItems, + Item: Item, + Templater: Templater, + sortButtons: sortButtons, + events: events, + reset: reset + }; + for (var i = 0; i < plugins.length; i++) { + plugins[i][1] = plugins[i][1] || {}; + var pluginName = plugins[i][1].name || plugins[i][0]; + self[pluginName] = self.plugins[plugins[i][0]].call(self, locals, plugins[i][1]); + } + } + }; + + + /* + * Add object to list + */ + this.add = function(values, callback) { + if (callback) { + addAsync(values, callback); + } + var added = [], + notCreate = false; + if (values[0] === undefined){ + values = [values]; + } + for (var i = 0, il = values.length; i < il; i++) { + var item = null; + if (values[i] instanceof Item) { + item = values[i]; + item.reload(); + } else { + notCreate = (self.items.length > self.page) ? true : false; + item = new Item(values[i], undefined, notCreate); + } + self.items.push(item); + added.push(item); + } + self.update(); + return added; + }; + + /* + * Adds items asynchronous to the list, good for adding huge amount of + * data. Defaults to add 100 items a time + */ + var addAsync = function(values, callback, items) { + var valuesToAdd = values.splice(0, 100); + items = items || []; + items = items.concat(self.add(valuesToAdd)); + if (values.length > 0) { + setTimeout(function() { + addAsync(values, callback, items); + }, 10); + } else { + self.update(); + callback(items); + } + }; + + this.show = function(i, page) { + this.i = i; + this.page = page; + self.update(); + }; + + /* Removes object from list. + * Loops through the list and removes objects where + * property "valuename" === value + */ + this.remove = function(valueName, value, options) { + var found = 0; + for (var i = 0, il = self.items.length; i < il; i++) { + if (self.items[i].values()[valueName] == value) { + templater.remove(self.items[i], options); + self.items.splice(i,1); + il--; + found++; + } + } + self.update(); + return found; + }; + + /* Gets the objects in the list which + * property "valueName" === value + */ + this.get = function(valueName, value) { + var matchedItems = []; + for (var i = 0, il = self.items.length; i < il; i++) { + var item = self.items[i]; + if (item.values()[valueName] == value) { + matchedItems.push(item); + } + } + if (matchedItems.length == 0) { + return null; + } else if (matchedItems.length == 1) { + return matchedItems[0]; + } else { + return matchedItems; + } + }; + + /* Sorts the list. + * @valueOrEvent Either a JavaScript event object or a valueName + * @sortFunction (optional) Define if natural sorting does not fullfill your needs + */ + this.sort = function(valueName, options) { + var length = self.items.length, + value = null, + target = valueName.target || valueName.srcElement, /* IE have srcElement */ + sorting = '', + isAsc = false, + asc = 'asc', + desc = 'desc', + options = options || {}; + + if (target === undefined) { + value = valueName; + isAsc = options.asc || false; + } else { + value = h.getAttribute(target, 'data-sort'); + isAsc = h.hasClass(target, asc) ? false : true; + } + for (var i = 0, il = sortButtons.length; i < il; i++) { + h.removeClass(sortButtons[i], asc); + h.removeClass(sortButtons[i], desc); + } + if (isAsc) { + if (target !== undefined) { + h.addClass(target, asc); + } + isAsc = true; + } else { + if (target !== undefined) { + h.addClass(target, desc); + } + isAsc = false; + } + + if (options.sortFunction) { + options.sortFunction = options.sortFunction; + } else { + options.sortFunction = function(a, b) { + return h.sorter.alphanum(a.values()[value].toLowerCase(), b.values()[value].toLowerCase(), isAsc); + }; + } + self.items.sort(options.sortFunction); + self.update(); + }; + + /* + * Searches the list after values with content "searchStringOrEvent". + * The columns parameter defines if all values should be included in the search, + * defaults to undefined which means "all". + */ + this.search = function(searchString, columns) { + self.i = 1; // Reset paging + var matching = [], + found, + item, + text, + values, + is, + columns = (columns === undefined) ? self.items[0].values() : columns, + searchString = (searchString === undefined) ? "" : searchString, + target = searchString.target || searchString.srcElement; /* IE have srcElement */ + + searchString = (target === undefined) ? (""+searchString).toLowerCase() : ""+target.value.toLowerCase(); + is = self.items; + // Escape regular expression characters + searchString = searchString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + + templater.clear(); + if (searchString === "" ) { + reset.search(); + self.searched = false; + self.update(); + } else { + self.searched = true; + + for (var k = 0, kl = is.length; k < kl; k++) { + found = false; + item = is[k]; + values = item.values(); + + for(var j in columns) { + if(values.hasOwnProperty(j) && columns[j] !== null) { + text = (values[j] != null) ? values[j].toString().toLowerCase() : ""; + if ((searchString !== "") && (text.search(searchString) > -1)) { + found = true; + } + } + } + if (found) { + item.found = true; + matching.push(item); + } else { + item.found = false; + } + } + self.update(); + } + return self.visibleItems; + }; + + /* + * Filters the list. If filterFunction() returns False hides the Item. + * if filterFunction == false are the filter removed + */ + this.filter = function(filterFunction) { + self.i = 1; // Reset paging + reset.filter(); + if (filterFunction === undefined) { + self.filtered = false; + } else { + self.filtered = true; + var is = self.items; + for (var i = 0, il = is.length; i < il; i++) { + var item = is[i]; + if (filterFunction(item)) { + item.filtered = true; + } else { + item.filtered = false; + } + } + } + self.update(); + return self.visibleItems; + }; + + /* + * Get size of the list + */ + this.size = function() { + return self.items.length; + }; + + /* + * Removes all items from the list + */ + this.clear = function() { + templater.clear(); + self.items = []; + }; + + this.on = function(event, callback) { + events[event].push(callback); + }; + + var trigger = function(event) { + var i = events[event].length; + while(i--) { + events[event][i](); + } + }; + + var reset = { + filter: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].filtered = false; + } + }, + search: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].found = false; + } + } + }; + + + this.update = function() { + var is = self.items, + il = is.length; + + self.visibleItems = []; + self.matchingItems = []; + templater.clear(); + for (var i = 0; i < il; i++) { + if (is[i].matching() && ((self.matchingItems.length+1) >= self.i && self.visibleItems.length < self.page)) { + is[i].show(); + self.visibleItems.push(is[i]); + self.matchingItems.push(is[i]); + } else if (is[i].matching()) { + self.matchingItems.push(is[i]); + is[i].hide(); + } else { + is[i].hide(); + } + } + trigger('updated'); + }; + + Item = function(initValues, element, notCreate) { + var item = this, + values = {}; + + this.found = false; // Show if list.searched == true and this.found == true + this.filtered = false;// Show if list.filtered == true and this.filtered == true + + var init = function(initValues, element, notCreate) { + if (element === undefined) { + if (notCreate) { + item.values(initValues, notCreate); + } else { + item.values(initValues); + } + } else { + item.elm = element; + var values = templater.get(item, initValues); + item.values(values); + } + }; + this.values = function(newValues, notCreate) { + if (newValues !== undefined) { + for(var name in newValues) { + values[name] = newValues[name]; + } + if (notCreate !== true) { + templater.set(item, item.values()); + } + } else { + return values; + } + }; + this.show = function() { + templater.show(item); + }; + this.hide = function() { + templater.hide(item); + }; + this.matching = function() { + return ( + (self.filtered && self.searched && item.found && item.filtered) || + (self.filtered && !self.searched && item.filtered) || + (!self.filtered && self.searched && item.found) || + (!self.filtered && !self.searched) + ); + }; + this.visible = function() { + return (item.elm.parentNode) ? true : false; + }; + init(initValues, element, notCreate); + }; + + /* Templater with different kinds of template engines. + * All engines have these methods + * - reload(item) + * - remove(item) + */ + Templater = function(list, settings) { + if (settings.engine === undefined) { + settings.engine = "standard"; + } else { + settings.engine = settings.engine.toLowerCase(); + } + return new self.constructor.prototype.templateEngines[settings.engine](list, settings); + }; + + init.start(values, options); +}; + +List.prototype.templateEngines = {}; +List.prototype.plugins = {}; + +List.prototype.templateEngines.standard = function(list, settings) { + var listSource = h.getByClass(settings.listClass, list.listContainer, true), + itemSource = getItemSource(settings.item), + templater = this; + + function getItemSource(item) { + if (item === undefined) { + var nodes = listSource.childNodes, + items = []; + + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + return nodes[i]; + } + } + return null; + } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! + var div = document.createElement('div'); + div.innerHTML = item; + return div.firstChild; + } else { + return document.getElementById(settings.item); + } + } + + var ensure = { + created: function(item) { + if (item.elm === undefined) { + templater.create(item); + } + } + }; + + /* Get values from element */ + this.get = function(item, valueNames) { + ensure.created(item); + var values = {}; + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm = h.getByClass(valueNames[i], item.elm, true); + values[valueNames[i]] = elm ? elm.innerHTML : ""; + } + return values; + }; + + /* Sets values at element */ + this.set = function(item, values) { + ensure.created(item); + for(var v in values) { + if (values.hasOwnProperty(v)) { + // TODO speed up if possible + var elm = h.getByClass(v, item.elm, true); + if (elm) { + elm.innerHTML = values[v]; + } + } + } + }; + + this.create = function(item) { + if (item.elm !== undefined) { + return; + } + /* If item source does not exists, use the first item in list as + source for new items */ + var newItem = itemSource.cloneNode(true); + newItem.id = ""; + item.elm = newItem; + templater.set(item, item.values()); + }; + this.remove = function(item) { + listSource.removeChild(item.elm); + }; + this.show = function(item) { + ensure.created(item); + listSource.appendChild(item.elm); + }; + this.hide = function(item) { + if (item.elm !== undefined && item.elm.parentNode === listSource) { + listSource.removeChild(item.elm); + } + }; + this.clear = function() { + /* .innerHTML = ''; fucks up IE */ + if (listSource.hasChildNodes()) { + while (listSource.childNodes.length >= 1) + { + listSource.removeChild(listSource.firstChild); + } + } + }; +}; + + +/* +* These helper functions are not written by List.js author Jonny (they may have been +* adjusted, thought). +*/ +h = { + /* + * Cross browser getElementsByClassName, which uses native + * if it exists. Modified version of Dustin Diaz function: + * http://www.dustindiaz.com/getelementsbyclass + */ + getByClass: (function() { + if (document.getElementsByClassName) { + return function(searchClass,node,single) { + if (single) { + return node.getElementsByClassName(searchClass)[0]; + } else { + return node.getElementsByClassName(searchClass); + } + }; + } else { + return function(searchClass,node,single) { + var classElements = [], + tag = '*'; + if (node == null) { + node = document; + } + var els = node.getElementsByTagName(tag); + var elsLen = els.length; + var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)"); + for (var i = 0, j = 0; i < elsLen; i++) { + if ( pattern.test(els[i].className) ) { + if (single) { + return els[i]; + } else { + classElements[j] = els[i]; + j++; + } + } + } + return classElements; + }; + } + })(), + /* (elm, 'event' callback) Source: http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/ */ + addEvent: (function( window, document ) { + if ( document.addEventListener ) { + return function( elem, type, cb ) { + if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { + elem.addEventListener(type, cb, false ); + } else if ( elem && elem[0] !== undefined ) { + var len = elem.length; + for ( var i = 0; i < len; i++ ) { + h.addEvent(elem[i], type, cb); + } + } + }; + } + else if ( document.attachEvent ) { + return function ( elem, type, cb ) { + if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { + elem.attachEvent( 'on' + type, function() { return cb.call(elem, window.event); } ); + } else if ( elem && elem[0] !== undefined ) { + var len = elem.length; + for ( var i = 0; i < len; i++ ) { + h.addEvent( elem[i], type, cb ); + } + } + }; + } + })(this, document), + /* (elm, attribute) Source: http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method */ + getAttribute: function(ele, attr) { + var result = (ele.getAttribute && ele.getAttribute(attr)) || null; + if( !result ) { + var attrs = ele.attributes; + var length = attrs.length; + for(var i = 0; i < length; i++) { + if (attr[i] !== undefined) { + if(attr[i].nodeName === attr) { + result = attr[i].nodeValue; + } + } + } + } + return result; + }, + /* http://stackoverflow.com/questions/7238177/detect-htmlcollection-nodelist-in-javascript */ + isNodeList: function(nodes) { + var result = Object.prototype.toString.call(nodes); + if (typeof nodes === 'object' && /^\[object (HTMLCollection|NodeList|Object)\]$/.test(result) && (nodes.length == 0 || (typeof nodes[0] === "object" && nodes[0].nodeType > 0))) { + return true; + } + return false; + }, + hasClass: function(ele, classN) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + return (classes.search(classN) > -1); + }, + addClass: function(ele, classN) { + if (!this.hasClass(ele, classN)) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + classes = classes + ' ' + classN + ' '; + classes = classes.replace(/\s{2,}/g, ' '); + ele.setAttribute('class', classes); + } + }, + removeClass: function(ele, classN) { + if (this.hasClass(ele, classN)) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + classes = classes.replace(classN, ''); + ele.setAttribute('class', classes); + } + }, + /* + * The sort function. From http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + */ + sorter: { + alphanum: function(a,b,asc) { + if (a === undefined || a === null) { + a = ""; + } + if (b === undefined || b === null) { + b = ""; + } + a = a.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ + return (p1 == "lt")? "<" : ">"; + }); + a = a.replace(/<\/?[^>]+(>|$)/g, ""); + + b = b.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ + return (p1 == "lt")? "<" : ">"; + }); + b = b.replace(/<\/?[^>]+(>|$)/g, ""); + var aa = this.chunkify(a); + var bb = this.chunkify(b); + + for (var x = 0; aa[x] && bb[x]; x++) { + if (aa[x] !== bb[x]) { + var c = Number(aa[x]), d = Number(bb[x]); + if (asc) { + if (c == aa[x] && d == bb[x]) { + return c - d; + } else { + return (aa[x] > bb[x]) ? 1 : -1; + } + } else { + if (c == aa[x] && d == bb[x]) { + return d-c;//c - d; + } else { + return (aa[x] > bb[x]) ? -1 : 1; //(aa[x] > bb[x]) ? 1 : -1; + } + } + } + } + return aa.length - bb.length; + }, + chunkify: function(t) { + var tz = [], x = 0, y = -1, n = 0, i, j; + + while (i = (j = t.charAt(x++)).charCodeAt(0)) { + var m = (i == 45 || i == 46 || (i >=48 && i <= 57)); + if (m !== n) { + tz[++y] = ""; + n = m; + } + tz[y] += j; + } + return tz; + } + } +}; + +window.List = List; +window.ListJsHelpers = h; +})(window); From 2008bb75bff158f4ca91d21472f00dfc7e2175c3 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 18 Sep 2013 23:34:55 +0200 Subject: [PATCH 22/50] update travis badge after tests merge [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3107dae..ecf7099d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 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) From fa99a0a85244111a9583062a8a99ad36d230768e Mon Sep 17 00:00:00 2001 From: wvengen Date: Fri, 20 Sep 2013 18:24:40 +0200 Subject: [PATCH 23/50] add bitdeli badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ecf7099d..e7be634b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ 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). From 891293af8495ddd2a467aecdcc6958579c7f0f67 Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 21:50:59 +0200 Subject: [PATCH 24/50] Add delay to list.js search --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/list.customized.js | 43 +++++++++++++++++++++++ app/views/group_orders/_form.html.haml | 4 +-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/list.customized.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 09771539..c684cf31 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,6 +10,7 @@ //= require jquery.observe_field //= require list //= require list.unlist +//= require list.customized //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js new file mode 100644 index 00000000..f95a0e0c --- /dev/null +++ b/app/assets/javascripts/list.customized.js @@ -0,0 +1,43 @@ +(function(window, undefined) { + +var CustomizedList = function(id, options, values) { + var self = this; + var h = window.ListJsHelpers; + + this.searchTimeout = undefined; + + var init = { + start: function(id, options, values) { + this.defaults(options); + this.list(id, options, values); + this.callbacks(options); + }, + defaults: function(options) { + options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; + options.delayedSearchTime = options.delayedSearchTime || 500; + }, + list: function(id, options, values) { + self.list = new window.List(id, options, values); + }, + callbacks: function(options) { + h.addEvent(h.getByClass(options.delayedSearchClass, self.list.listContainer), 'keyup', self.searchDelayStart); + } + }; + + this.searchDelayStart = function(searchString, columns) { + // TODO: if keycode corresponds to 'ENTER' ? skip delay + // TODO: if search is about to be cleared (empty searchString) ? skip delay + clearTimeout(self.searchTimeout); + self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); + } + + this.searchDelayEnd = function(searchString, columns) { + self.list.search(searchString, columns); + } + + init.start(id, options, values); +} + +window.CustomizedList = CustomizedList; + +})(window); diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 78bccce5..dfac473f 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -8,7 +8,7 @@ setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]}); setStockit(#{@order.stockit?}); // create List for search-feature (using list.js, http://listjs.com) - new List(document.body, { valueNames: ['name'], engine: 'unlist' }); + new CustomizedList(document.body, { valueNames: ['name'], engine: 'unlist' }); }); - title t('.title'), false @@ -41,7 +41,7 @@ .row-fluid .well.clear - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large search' + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large delayed-search' = form_for @group_order do |f| = f.hidden_field :lock_version From 6ec81ace18ba8d72350599cb81bd38850eaa49cf Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 22:04:29 +0200 Subject: [PATCH 25/50] Always show article category headers (do not unlist them on list.js search) --- app/assets/javascripts/list.unlist.js | 6 +++--- app/assets/stylesheets/list.unlist.css | 2 +- app/views/group_orders/_form.html.haml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index f649369f..dc7101ed 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -136,9 +136,9 @@ List.prototype.templateEngines.unlist = function(list, settings) { $(item.elm).removeClass('unlisted'); }; this.hide = function(item) { - if (item.elm !== undefined) { - $(item.elm).addClass('unlisted'); - } + ensure.created(item); + $(item.elm).addClass('unlisted'); + listSource.appendChild(item.elm); }; this.clear = function() { $(listSource.childNodes).addClass('unlisted'); diff --git a/app/assets/stylesheets/list.unlist.css b/app/assets/stylesheets/list.unlist.css index b0ed4600..9fad9603 100644 --- a/app/assets/stylesheets/list.unlist.css +++ b/app/assets/stylesheets/list.unlist.css @@ -1,3 +1,3 @@ -.list .unlisted { +.list .unlisted:not(.no-unlist) { display: none; } \ No newline at end of file diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index dfac473f..64e88453 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -67,8 +67,8 @@ %th{style: "width:15px;"}= t '.sum' %tbody.list - @order.articles_grouped_by_category.each do |category, order_articles| - %tr.article-category - %td.name + %tr.no-unlist.article-category + %td = category %i.icon-tag %td{colspan: "9"} From 3c8f56b24f8c8176c2bc0d0bd509177a69e4bde3 Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 22:07:34 +0200 Subject: [PATCH 26/50] Add dismiss button to sidebars in group_order form (idea stolen from foodcoops#143) --- app/views/group_orders/_form.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 64e88453..9d087687 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -15,6 +15,7 @@ .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? @@ -37,6 +38,7 @@ %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 From 9f083dd4d065ec12d38d7ade793a97514a0f92de Mon Sep 17 00:00:00 2001 From: Julius Date: Mon, 23 Sep 2013 21:31:54 +0200 Subject: [PATCH 27/50] Add button to clear article search (list.js customization) --- app/assets/javascripts/list.customized.js | 30 +++++++++++++++++-- .../bootstrap_and_overrides.css.less | 5 ++++ app/views/group_orders/_form.html.haml | 6 +++- config/locales/de.yml | 1 + config/locales/en.yml | 1 + 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js index f95a0e0c..96ca9793 100644 --- a/app/assets/javascripts/list.customized.js +++ b/app/assets/javascripts/list.customized.js @@ -11,30 +11,56 @@ var CustomizedList = function(id, options, values) { this.defaults(options); this.list(id, options, values); this.callbacks(options); + this.onload(options); }, defaults: function(options) { options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; options.delayedSearchTime = options.delayedSearchTime || 500; + options.highlightClass = options.highlightClass || 'btn-primary'; + options.resetSearchClass = options.resetSearchClass || 'reset-search'; }, list: function(id, options, values) { self.list = new window.List(id, options, values); }, callbacks: function(options) { - h.addEvent(h.getByClass(options.delayedSearchClass, self.list.listContainer), 'keyup', self.searchDelayStart); + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).click(self.resetSearch); + self.list.on('updated', self.highlightResetButton); + + var delayedSearchInput = h.getByClass(options.delayedSearchClass, self.list.listContainer); + $(delayedSearchInput).keyup(self.searchDelayStart); + }, + onload: function(options) { + var initialSearchTerm = $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(); + if('' != initialSearchTerm) { + self.list.search(initialSearchTerm); + } } }; this.searchDelayStart = function(searchString, columns) { // TODO: if keycode corresponds to 'ENTER' ? skip delay - // TODO: if search is about to be cleared (empty searchString) ? skip delay clearTimeout(self.searchTimeout); self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); + + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).removeClass(options.highlightClass); } this.searchDelayEnd = function(searchString, columns) { self.list.search(searchString, columns); } + this.highlightResetButton = function() { + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); + } + + this.resetSearch = function() { + $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); + self.list.search(''); + } + init.start(id, options, values); } diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 5d595bc2..cce03029 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -238,3 +238,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/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 9d087687..bab7cad7 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -43,7 +43,11 @@ .row-fluid .well.clear - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large delayed-search' + .form-search + .input-append + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search' + %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 diff --git a/config/locales/de.yml b/config/locales/de.yml index ab08d6dc..7e8f67bc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -787,6 +787,7 @@ 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:' diff --git a/config/locales/en.yml b/config/locales/en.yml index 18356d73..63d33cfd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -791,6 +791,7 @@ 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 From a87ced2646d2fba206cd1b6b5d5ffdd3c6235fbf Mon Sep 17 00:00:00 2001 From: Julius Date: Tue, 24 Sep 2013 21:21:08 +0200 Subject: [PATCH 28/50] Hide article category if no article matches the search --- app/assets/javascripts/list.customized.js | 8 ++++---- app/assets/javascripts/list.unlist.js | 25 +++++++++++++++++++++++ app/views/group_orders/_form.html.haml | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js index 96ca9793..da79cbe1 100644 --- a/app/assets/javascripts/list.customized.js +++ b/app/assets/javascripts/list.customized.js @@ -45,21 +45,21 @@ var CustomizedList = function(id, options, values) { var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); $(resetSearchButton).removeClass(options.highlightClass); - } + }; this.searchDelayEnd = function(searchString, columns) { self.list.search(searchString, columns); - } + }; this.highlightResetButton = function() { var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); - } + }; this.resetSearch = function() { $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); self.list.search(''); - } + }; init.start(id, options, values); } diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index dc7101ed..16600077 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -62,6 +62,19 @@ List.prototype.templateEngines.unlist = function(list, settings) { itemSource = getItemSource(settings.item), templater = this; + 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); + } + }; + function getItemSource(item) { if (item === undefined) { var nodes = listSource.childNodes, @@ -143,6 +156,18 @@ List.prototype.templateEngines.unlist = function(list, settings) { this.clear = function() { $(listSource.childNodes).addClass('unlisted'); }; + + this.updateListHeadings = function() { + var headSel = '.' + settings.listHeadingsClass; + + $(headSel, listSource).each(function() { + if( $(this).nextUntil(headSel, ':not(.unlisted)').length ) { + $(this).removeClass('unlisted'); + } + }); + }; + + init.start(settings); }; /******************************************************************************* diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index bab7cad7..fbb4da00 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -73,7 +73,7 @@ %th{style: "width:15px;"}= t '.sum' %tbody.list - @order.articles_grouped_by_category.each do |category, order_articles| - %tr.no-unlist.article-category + %tr.list-heading.article-category %td = category %i.icon-tag From 975bcdec3a484960259e88c5d0b4d33c0e60da30 Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Wed, 25 Sep 2013 11:57:22 +0200 Subject: [PATCH 29/50] Avoid send messages so STDOUT in rake silen mode. --- lib/tasks/foodsoft.rake | 11 ++++++++--- lib/tasks/multicoops.rake | 10 ++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/tasks/foodsoft.rake b/lib/tasks/foodsoft.rake index 04db66f9..df05c1fd 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 .." + 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}.." + 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}" + say "deliver aborted for #{user.email}" end end end @@ -47,3 +47,8 @@ namespace :foodsoft do end end end + +# Helper +def say(message) + puts message unless Rake.application.options.silent +end diff --git a/lib/tasks/multicoops.rake b/lib/tasks/multicoops.rake index 621a0d54..130daf40 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}" + 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']}" + say "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" Rake::Task[task_to_run].execute end end + + +# Helper +def say(message) + puts message unless Rake.application.options.silent +end From c1ac95e4400b7ba2ef4eac7c75d9f770a3d21310 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 25 Sep 2013 20:58:53 +0200 Subject: [PATCH 30/50] list.unlist.js: Use jQuery's toggleClass instead of native if-else (just for briefness) --- app/assets/javascripts/list.unlist.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index 16600077..1bbc12a7 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -161,9 +161,8 @@ List.prototype.templateEngines.unlist = function(list, settings) { var headSel = '.' + settings.listHeadingsClass; $(headSel, listSource).each(function() { - if( $(this).nextUntil(headSel, ':not(.unlisted)').length ) { - $(this).removeClass('unlisted'); - } + var listedCount = $(this).nextUntil(headSel, ':not(.unlisted)').length; + $(this).toggleClass('unlisted', 0==listedCount); }); }; From 53f24cd885169d4bb0adf2253257fdaabc3dd054 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 25 Sep 2013 21:22:34 +0200 Subject: [PATCH 31/50] Fix migrate problem caused by output formating(?) --- db/migrate/20130702113610_update_group_order_totals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index ca9aab67..f0052ee5 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -7,7 +7,7 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration 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 "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }" 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." From 6632f5ed7568eb8fdceb0b6510e0dab6bdb409d9 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 30 Sep 2013 10:57:54 +0200 Subject: [PATCH 32/50] workaround migration bug introduced by 975bcdec3a484960259e88c5d0b4d33c0e60da30 --- lib/tasks/foodsoft.rake | 8 ++++---- lib/tasks/multicoops.rake | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/tasks/foodsoft.rake b/lib/tasks/foodsoft.rake index df05c1fd..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 - say "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 - say "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 - say "deliver aborted for #{user.email}" + rake_say "deliver aborted for #{user.email}" end end end @@ -49,6 +49,6 @@ namespace :foodsoft do end # Helper -def say(message) +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 130daf40..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| - say "Run '#{task_to_run}' for #{coop}" + rake_say "Run '#{task_to_run}' for #{coop}" Rake::Task[task_to_run].execute end end @@ -17,7 +17,7 @@ namespace :multicoops do task :run_single => :environment do task_to_run = ENV['TASK'] FoodsoftConfig.select_foodcoop ENV['FOODCOOP'] - say "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" + rake_say "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" Rake::Task[task_to_run].execute end @@ -25,6 +25,6 @@ end # Helper -def say(message) - puts message unless Rake.application.options.silent +def rake_say(message) + puts message unless Rake.application.options.silent end From d36f2dd4acc1ff21f2e54b5c3e8e01b018a5e0e5 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 25 Sep 2013 23:06:53 +0200 Subject: [PATCH 33/50] Fix minor delivery bug --- app/views/deliveries/add_stock_change.js.erb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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); From 0bb62332c04b280e9325e09d9cfbe30920dd0901 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 1 Oct 2013 00:05:46 +0200 Subject: [PATCH 34/50] localeapp roundtrip --- config/locales/en.yml | 12 ++-- config/locales/fr.yml | 4 +- config/locales/nl.yml | 142 +++++++++++++++++++++--------------------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 02a5bb1f..c3dcfadf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -430,7 +430,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,7 +478,7 @@ 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 @@ -1313,7 +1313,7 @@ en: prices: Prices (net/FC) select_all: Select all stockit: In stock - supplier: Supplier + supplier: Producer title: Article unit_quantity: Unit quantity index: @@ -1395,7 +1395,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: @@ -1536,7 +1536,7 @@ en: message: private: Message doesn’t show in Foodsoft mail inbox order_article: - 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 otherwise ordergroups will be accounted for undelivered articles! + 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: @@ -1866,7 +1866,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 diff --git a/config/locales/fr.yml b/config/locales/fr.yml index fe52e77e..7ba4c3c7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1288,7 +1288,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 @@ -1536,7 +1536,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: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index c2f0ee5b..f6e29a95 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -428,11 +428,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: @@ -476,7 +476,7 @@ nl: remove_article: suppliers_overview: update: - notice: + notice: Levering is bijgewerkt. update_stock_article: notice: documents: @@ -1061,7 +1061,7 @@ nl: text1: messages: create: - notice: + notice: Bericht is opgeslagen en wordt verzonden. index: new: title: @@ -1078,9 +1078,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: @@ -1091,15 +1091,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 @@ -1187,46 +1187,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: - ignore_warnings: - 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: @@ -1244,7 +1244,7 @@ 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: @@ -1288,7 +1288,7 @@ nl: finished: gesloten open: lopend update: - notice: + notice: De bestelling is bijgewerkt. pages: all: new_page: @@ -1302,12 +1302,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: @@ -1351,7 +1351,7 @@ nl: versions: title: update: - notice: + notice: Pagina is bijgewerkt. version: author: date_format: @@ -1394,11 +1394,11 @@ nl: 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 @@ -1407,8 +1407,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 @@ -1436,7 +1436,7 @@ nl: total_sum: Totaalsom who_ordered: Wie heeft besteld? workgroup_members: - title: + title: Groepsleden simple_form: error_notification: default_message: @@ -1550,7 +1550,7 @@ nl: address: contact_person: customer_number: - delivery_days: + delivery_days: Bezorgdagen email: fax: is_subscribed: @@ -1601,7 +1601,7 @@ nl: 'yes': Ja stock_takings: create: - notice: + notice: Inventarisatie is aangelegd. edit: title: index: @@ -1629,12 +1629,12 @@ nl: date: note: update: - notice: + notice: Inventarisatie is bijgewerkt. stockit: check: not_empty: destroy: - notice: + notice: Artikel %{name} is verwijdered. edit: title: form: @@ -1674,14 +1674,14 @@ nl: search_text: title: 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: @@ -1707,7 +1707,7 @@ nl: new_delivery: show_deliveries: update: - notice: + notice: Leverancier is bijgewerkt support: array: last_word_connector: @@ -1715,7 +1715,7 @@ nl: words_connector: tasks: accept: - notice: + notice: Je hebt de taak geaccepteerd archive: title: archive_tasks: @@ -1724,9 +1724,9 @@ nl: task_format: who: create: - notice: + notice: Taak is aangemaakt destroy: - notice: + notice: Taak is verwijderd edit: title: warning_periodic: @@ -1764,7 +1764,7 @@ nl: title: repeated: set_done: - notice: + notice: De status van de taak is aangepast show: accept_task: confirm_delete_group: @@ -1775,8 +1775,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: From 9ac9d1ea50413f32169f7c98edeadad11bc7d248 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 16:32:01 +0200 Subject: [PATCH 35/50] add spec for foodcoops/foodsoft#179 --- spec/integration/balancing_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/integration/balancing_spec.rb b/spec/integration/balancing_spec.rb index 2bf860be..eba7973e 100644 --- a/spec/integration/balancing_spec.rb +++ b/spec/integration/balancing_spec.rb @@ -103,6 +103,23 @@ describe 'settling an order', :type => :feature do 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 From 6cfaa4979a6e76a7f2987ecaef413bdbafcf80fc Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 16:32:29 +0200 Subject: [PATCH 36/50] show lines with zero units but with member orders in balancing screen (closes foodcoops/foodsoft#179) --- app/controllers/finance/balancing_controller.rb | 2 +- app/models/order_article.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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/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 From 1d8fefcfb74a50d57108a0a2dc7078098ac83c7b Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 17:08:24 +0200 Subject: [PATCH 37/50] small i18n update --- app/views/invites/new.html.haml | 2 +- config/locales/de.yml | 1 - config/locales/en.yml | 1 - config/locales/fr.yml | 1 - config/locales/nl.yml | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) 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/config/locales/de.yml b/config/locales/de.yml index 1663cc07..d46ea8d3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -971,7 +971,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: diff --git a/config/locales/en.yml b/config/locales/en.yml index c3dcfadf..bdb867c3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -975,7 +975,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: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7ba4c3c7..f4d809c4 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -994,7 +994,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: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index f6e29a95..abf19a21 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -972,7 +972,6 @@ nl: title: new: action: - back: body: success: layouts: From a8ace3bc79309b9df163458e6716cff863084656 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 17:13:49 +0200 Subject: [PATCH 38/50] add missing i18n --- app/controllers/application_controller.rb | 6 +++--- config/locales/de.yml | 5 +++++ config/locales/en.yml | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) 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/config/locales/de.yml b/config/locales/de.yml index d46ea8d3..f562bf87 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -212,6 +212,11 @@ de: workgroups: members: Mitglieder name: Name + application: + controller: + error_denied: + error_authn: + error_members_only: 'Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!' article_categories: create: notice: Die Kategorie wurde gespeichert diff --git a/config/locales/en.yml b/config/locales/en.yml index bdb867c3..e42480e3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -212,6 +212,11 @@ en: workgroups: members: members name: name + application: + controller: + error_denied: 'Access denied!' + error_authn: 'Authentication required!' + error_members_only: 'This action is only available to members of the group!' article_categories: create: notice: Category was stored From 17bbb0705b960cc80aefa54aa15e585ff111fa4a Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 17:23:49 +0200 Subject: [PATCH 39/50] fix i18n --- .../finance/financial_transactions_controller.rb | 8 ++++---- config/locales/de.yml | 12 +++++++----- config/locales/en.yml | 12 +++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) 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/config/locales/de.yml b/config/locales/de.yml index f562bf87..b956b3f3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -649,11 +649,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}) diff --git a/config/locales/en.yml b/config/locales/en.yml index e42480e3..2c89b7f1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -653,11 +653,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) From 3574f239c6fa7e309a43937383b9bf29c274fc4e Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 2 Oct 2013 17:33:37 +0200 Subject: [PATCH 40/50] localeapp roundtrip --- config/locales/de.yml | 8 ++--- config/locales/en.yml | 10 +++--- config/locales/fr.yml | 17 +++++++--- config/locales/nl.yml | 77 ++++++++++++++++++++++++++++--------------- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index b956b3f3..a0a0e3a7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -214,9 +214,9 @@ de: name: Name application: controller: - error_denied: - error_authn: - error_members_only: 'Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!' + 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 @@ -654,7 +654,7 @@ de: notice: Die Transaktion wurde gespeichert. create_collection: alert: ! 'Ein Fehler ist aufgetreten: %{error}' - error_note_required: ! 'Notiz wird benötigt!' + error_note_required: Notiz wird benötigt! notice: Alle Transaktionen wurden gespeichert. index: balance: ! 'Kontostand: %{balance}' diff --git a/config/locales/en.yml b/config/locales/en.yml index 2c89b7f1..b13f4707 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -214,9 +214,9 @@ en: name: name application: controller: - error_denied: 'Access denied!' - error_authn: 'Authentication required!' - error_members_only: 'This action is only available to members of the group!' + 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 @@ -658,7 +658,7 @@ en: notice: The transaction was saved. create_collection: alert: ! 'An error occured: %{error}' - error_note_required: ! 'Note is required!' + error_note_required: Note is required! notice: All transactions were saved. index: balance: ! 'Balance of account: %{balance}' @@ -905,7 +905,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 diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f4d809c4..18e2cfea 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -212,6 +212,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. @@ -655,11 +660,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}) diff --git a/config/locales/nl.yml b/config/locales/nl.yml index abf19a21..221d9373 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -212,6 +212,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 @@ -645,11 +650,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) @@ -668,8 +675,8 @@ nl: sidebar: title: ordergroup: - remove: - remove_group: + remove: Verwijderen + remove_group: Huishouden verwijderen transactions: amount: Bedrag date: Datum @@ -856,7 +863,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: @@ -890,19 +897,19 @@ 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: Bedrag note: Notitie @@ -913,13 +920,13 @@ nl: 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. @@ -946,7 +953,7 @@ nl: admin: finances: accounts: Tegoeden bijwerken - settle: + settle: Bestelling afrekenen title: Financiën foodcoop: Foodcoop members: Leden @@ -1051,13 +1058,31 @@ 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: Bericht is opgeslagen en wordt verzonden. From 1674dcf851db372ad236925fdd19e156c6003630 Mon Sep 17 00:00:00 2001 From: wvengen Date: Thu, 3 Oct 2013 14:07:03 +0200 Subject: [PATCH 41/50] fix i18n of article sync --- app/views/articles/sync.html.haml | 7 +++---- config/locales/de.yml | 8 +++++--- config/locales/en.yml | 18 ++++++++++-------- config/locales/fr.yml | 6 ++++-- config/locales/nl.yml | 16 +++++++++------- 5 files changed, 31 insertions(+), 24 deletions(-) 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/config/locales/de.yml b/config/locales/de.yml index a0a0e3a7..893739d6 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -310,18 +310,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: diff --git a/config/locales/en.yml b/config/locales/en.yml index b13f4707..915f4341 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -310,20 +310,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 the text fields 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: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 18e2cfea..2c176d31 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -312,6 +312,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. @@ -321,9 +323,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.

diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 221d9373..05b3ea5e 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -310,20 +310,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: Gr.Eenh. update: - body: ! '

Ieder artikel wordt tweemaal getoond. De oude waarden zijn grijs, en de tekstvelden bevatten de huidige waarden.

- -

Verschillen met de oude artikelen zijn geel gemarkeerd.

' + 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: ! 'De volgende artikelen moeten bijgewerkt worden: ' + update_msg: + one: Er moet éen artikel bijgewerkt worden. + other: Er moeten %{count} artikelen bijgewerkt worden. upload: body: fields: From 26e207aeb106f35465025cbcfcbf5754793ce0a8 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 16:10:18 +0200 Subject: [PATCH 42/50] Reuse some list.js standard template functions (thanks to wvengen https://gist.github.com/wvengen/6784056) --- app/assets/javascripts/list.unlist.js | 122 ++++++++------------------ 1 file changed, 36 insertions(+), 86 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index 1bbc12a7..b40cb98f 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -1,3 +1,6 @@ +// for use with listjs 0.2.0 +// https://github.com/javve/list.js + /******************************************************************************* ******************************************************************************** @@ -50,6 +53,7 @@ End copyright notice of the original code +(function(w, undefined) { /******************************************************************************* Begin copy-pasted and modified code *******************************************************************************/ @@ -57,10 +61,23 @@ 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 $ -List.prototype.templateEngines.unlist = function(list, settings) { - var listSource = ListJsHelpers.getByClass(settings.listClass, list.listContainer, true), - itemSource = getItemSource(settings.item), - templater = this; +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) { @@ -74,88 +91,20 @@ List.prototype.templateEngines.unlist = function(list, settings) { list.on('updated', templater.updateListHeadings); } }; - - function getItemSource(item) { - if (item === undefined) { - var nodes = listSource.childNodes, - items = []; - - for (var i = 0, il = nodes.length; i < il; i++) { - // Only textnodes have a data attribute - if (nodes[i].data === undefined) { - return nodes[i]; - } - } - return null; - } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! - var div = document.createElement('div'); - div.innerHTML = item; - return div.firstChild; - } else { - return document.getElementById(settings.item); - } - } - - var ensure = { - created: function(item) { - if (item.elm === undefined) { - templater.create(item); - } - } - }; - - /* Get values from element */ - this.get = function(item, valueNames) { - ensure.created(item); - var values = {}; - for(var i = 0, il = valueNames.length; i < il; i++) { - var elm = ListJsHelpers.getByClass(valueNames[i], item.elm, true); - values[valueNames[i]] = elm ? elm.innerHTML : ""; - } - return values; - }; - - /* Sets values at element */ - this.set = function(item, values) { - ensure.created(item); - for(var v in values) { - if (values.hasOwnProperty(v)) { - // TODO speed up if possible - var elm = ListJsHelpers.getByClass(v, item.elm, true); - if (elm) { - elm.innerHTML = values[v]; - } - } - } - }; - - this.create = function(item) { - if (item.elm !== undefined) { - return; - } - /* If item source does not exists, use the first item in list as - source for new items */ - var newItem = itemSource.cloneNode(true); - newItem.id = ""; - item.elm = newItem; - templater.set(item, item.values()); - }; - this.remove = function(item) { - listSource.removeChild(item.elm); - }; - 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.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; @@ -172,3 +121,4 @@ List.prototype.templateEngines.unlist = function(list, settings) { /******************************************************************************* End copy-pasted and modified code *******************************************************************************/ +})(window); From 3f822dc396ef6a2157d791058591116451c7608c Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:03:14 +0200 Subject: [PATCH 43/50] Revert "Fix migrate problem caused by output formating(?)" This reverts commit 53f24cd885169d4bb0adf2253257fdaabc3dd054. --- db/migrate/20130702113610_update_group_order_totals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index f0052ee5..ca9aab67 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -7,7 +7,7 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration 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! }" + 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." From a3e82f5304739f109d40883d442c087405180e90 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:14:41 +0200 Subject: [PATCH 44/50] Add article price tooltip to StockTaking form --- app/views/stock_takings/_stock_change.html.haml | 5 +++-- app/views/stock_takings/new.html.haml | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) 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..c46406f3 100644 --- a/app/views/stock_takings/new.html.haml +++ b/app/views/stock_takings/new.html.haml @@ -1,5 +1,19 @@ - title t('.title') +- content_for :javascript do + :javascript + $(function() { + enablePriceTooltips(); + }); + + function enablePriceTooltips(context) { + $('[data-toggle~="tooltip"]', context).tooltip({ + animation: false, + html: true, + placement: 'left' + }); + } + - content_for :sidebar do %p %i= t('.text_deviations', inv_link: link_to(t('.temp_inventory'), stock_articles_path)).html_safe From fef8c6d99f7f457c77363cd44b54d160677beb78 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:24:21 +0200 Subject: [PATCH 45/50] Move StockArticle#history to StockArticle#show --- app/controllers/stockit_controller.rb | 10 +++++----- app/views/stockit/index.html.haml | 3 +-- .../stockit/{history.haml => show.html.haml} | 0 config/locales/de.yml | 19 +++++++++---------- config/locales/en.yml | 19 +++++++++---------- config/locales/fr.yml | 19 +++++++++---------- config/locales/nl.yml | 19 +++++++++---------- config/routes.rb | 2 -- 8 files changed, 42 insertions(+), 49 deletions(-) rename app/views/stockit/{history.haml => show.html.haml} (100%) 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/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/history.haml b/app/views/stockit/show.html.haml similarity index 100% rename from app/views/stockit/history.haml rename to app/views/stockit/show.html.haml diff --git a/config/locales/de.yml b/config/locales/de.yml index a0a0e3a7..8971e53d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1731,15 +1731,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 @@ -1765,6 +1756,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 anzeigen für »%{article_name}« + stock_taking: Inventur stock_create: notice: Lagerartikel wurde gespeichert. stock_update: @@ -1889,7 +1889,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 b13f4707..50121ec5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1735,15 +1735,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 @@ -1769,6 +1760,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 of ‘%{article_name}’ + stock_taking: Inventory stock_create: notice: Stock article was created. stock_update: @@ -1893,7 +1893,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 18e2cfea..30f446c2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1737,15 +1737,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 @@ -1771,6 +1762,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 pour "%{article_name}" + stock_taking: Inventaire stock_create: notice: L'article a été sauvegardé. stock_update: @@ -1903,7 +1903,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 221d9373..50fdf702 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1663,15 +1663,6 @@ nl: title: form: price_hint: - history: - change_quantity: - datetime: - delivery: - new_quantity: - order: - reason: - stock_changes: - stock_taking: index: article: article: @@ -1697,6 +1688,15 @@ nl: new: search_text: title: + show: + change_quantity: + datetime: + delivery: + new_quantity: + order: + reason: + stock_changes: + stock_taking: stock_create: notice: Voorraadsartikel is opgeslagen. stock_update: @@ -1821,7 +1821,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 From 9a9a782bc40266d881129c37d8b01a6b971a2247 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 23:21:53 +0200 Subject: [PATCH 46/50] Complete StockArticle#show view --- app/views/stockit/show.html.haml | 62 +++++++++++++++++++++++--------- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/fr.yml | 2 +- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/app/views/stockit/show.html.haml b/app/views/stockit/show.html.haml index f4fe2b07..5f416bf9 100644 --- a/app/views/stockit/show.html.haml +++ b/app/views/stockit/show.html.haml @@ -1,17 +1,47 @@ -- title t('.stock_changes', :article_name => @stock_article.name) +- title @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] +.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/locales/de.yml b/config/locales/de.yml index 8971e53d..ba339ee8 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1763,7 +1763,7 @@ de: new_quantity: Neuer Bestand order: Bestellung reason: Ereignis - stock_changes: Verlauf anzeigen für »%{article_name}« + stock_changes: Verlauf des Lagerbestands stock_taking: Inventur stock_create: notice: Lagerartikel wurde gespeichert. diff --git a/config/locales/en.yml b/config/locales/en.yml index 50121ec5..6c3fbea8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1767,7 +1767,7 @@ en: new_quantity: New quantity order: Order reason: Reason - stock_changes: Stock quantity changes of ‘%{article_name}’ + stock_changes: Stock quantity changes stock_taking: Inventory stock_create: notice: Stock article was created. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 30f446c2..8d16c076 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1769,7 +1769,7 @@ fr: new_quantity: Nouveau stock order: Commande reason: Raison - stock_changes: Afficher l'historique pour "%{article_name}" + stock_changes: Afficher l'historique stock_taking: Inventaire stock_create: notice: L'article a été sauvegardé. From 3a948e9b7396738981dd37efbb5bbe6371636952 Mon Sep 17 00:00:00 2001 From: Julius Date: Fri, 4 Oct 2013 18:28:45 +0200 Subject: [PATCH 47/50] Move list.js extensions to plugins --- Gemfile | 2 +- Gemfile.lock | 3 + app/assets/javascripts/application.js | 3 +- app/assets/javascripts/list.customized.js | 69 -- app/assets/javascripts/list.delay.js | 50 ++ app/assets/javascripts/list.reset.js | 42 ++ app/views/group_orders/_form.html.haml | 6 +- vendor/assets/javascripts/list.js | 775 ---------------------- 8 files changed, 102 insertions(+), 848 deletions(-) delete mode 100644 app/assets/javascripts/list.customized.js create mode 100644 app/assets/javascripts/list.delay.js create mode 100644 app/assets/javascripts/list.reset.js delete mode 100644 vendor/assets/javascripts/list.js diff --git a/Gemfile b/Gemfile index e80314ed..f2dbd228 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,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 c816b8c4..61e2fc6c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -192,6 +192,8 @@ GEM activesupport (= 3.2.13) bundler (~> 1.0) railties (= 3.2.13) + rails-assets-listjs (0.2.0.beta.4) + railties (>= 3.1) rails-settings-cached (0.2.4) rails (>= 3.0.0) railties (3.2.13) @@ -332,6 +334,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/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c684cf31..b8dc87e8 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,7 +10,8 @@ //= require jquery.observe_field //= require list //= require list.unlist -//= require list.customized +//= require list.delay +//= require list.reset //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js deleted file mode 100644 index da79cbe1..00000000 --- a/app/assets/javascripts/list.customized.js +++ /dev/null @@ -1,69 +0,0 @@ -(function(window, undefined) { - -var CustomizedList = function(id, options, values) { - var self = this; - var h = window.ListJsHelpers; - - this.searchTimeout = undefined; - - var init = { - start: function(id, options, values) { - this.defaults(options); - this.list(id, options, values); - this.callbacks(options); - this.onload(options); - }, - defaults: function(options) { - options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; - options.delayedSearchTime = options.delayedSearchTime || 500; - options.highlightClass = options.highlightClass || 'btn-primary'; - options.resetSearchClass = options.resetSearchClass || 'reset-search'; - }, - list: function(id, options, values) { - self.list = new window.List(id, options, values); - }, - callbacks: function(options) { - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).click(self.resetSearch); - self.list.on('updated', self.highlightResetButton); - - var delayedSearchInput = h.getByClass(options.delayedSearchClass, self.list.listContainer); - $(delayedSearchInput).keyup(self.searchDelayStart); - }, - onload: function(options) { - var initialSearchTerm = $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(); - if('' != initialSearchTerm) { - self.list.search(initialSearchTerm); - } - } - }; - - this.searchDelayStart = function(searchString, columns) { - // TODO: if keycode corresponds to 'ENTER' ? skip delay - clearTimeout(self.searchTimeout); - self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); - - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).removeClass(options.highlightClass); - }; - - this.searchDelayEnd = function(searchString, columns) { - self.list.search(searchString, columns); - }; - - this.highlightResetButton = function() { - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); - }; - - this.resetSearch = function() { - $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); - self.list.search(''); - }; - - init.start(id, options, values); -} - -window.CustomizedList = CustomizedList; - -})(window); 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/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index fbb4da00..3c6048c0 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -8,7 +8,9 @@ setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]}); setStockit(#{@order.stockit?}); // create List for search-feature (using list.js, http://listjs.com) - new CustomizedList(document.body, { valueNames: ['name'], engine: 'unlist' }); + 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 @@ -45,7 +47,7 @@ .well.clear .form-search .input-append - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search' + = 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 diff --git a/vendor/assets/javascripts/list.js b/vendor/assets/javascripts/list.js deleted file mode 100644 index 44ce6879..00000000 --- a/vendor/assets/javascripts/list.js +++ /dev/null @@ -1,775 +0,0 @@ -/* -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. -*/ -(function( window, undefined ) { -"use strict"; -var document = window.document, - h; - -var List = function(id, options, values) { - var self = this, - templater, - init, - initialItems, - Item, - Templater, - sortButtons, - events = { - 'updated': [] - }; - this.listContainer = (typeof(id) == 'string') ? document.getElementById(id) : id; - // Check if the container exists. If not return instead of breaking the javascript - if (!this.listContainer) - return; - - this.items = []; - this.visibleItems = []; // These are the items currently visible - this.matchingItems = []; // These are the items currently matching filters and search, regadlessof visible count - this.searched = false; - this.filtered = false; - - this.list = null; - this.templateEngines = {}; - - this.page = options.page || 200; - this.i = options.i || 1; - - init = { - start: function(values, options) { - options.plugins = options.plugins || {}; - this.classes(options); - templater = new Templater(self, options); - this.callbacks(options); - this.items.start(values, options); - self.update(); - this.plugins(options.plugins); - }, - classes: function(options) { - options.listClass = options.listClass || 'list'; - options.searchClass = options.searchClass || 'search'; - options.sortClass = options.sortClass || 'sort'; - }, - callbacks: function(options) { - self.list = h.getByClass(options.listClass, self.listContainer, true); - h.addEvent(h.getByClass(options.searchClass, self.listContainer), 'keyup', self.search); - sortButtons = h.getByClass(options.sortClass, self.listContainer); - h.addEvent(sortButtons, 'click', self.sort); - }, - items: { - start: function(values, options) { - if (options.valueNames) { - var itemsToIndex = this.get(), - valueNames = options.valueNames; - if (options.indexAsync) { - this.indexAsync(itemsToIndex, valueNames); - } else { - this.index(itemsToIndex, valueNames); - } - } - if (values !== undefined) { - self.add(values); - } - }, - get: function() { - // return h.getByClass('item', self.list); - var nodes = self.list.childNodes, - items = []; - for (var i = 0, il = nodes.length; i < il; i++) { - // Only textnodes have a data attribute - if (nodes[i].data === undefined) { - items.push(nodes[i]); - } - } - return items; - }, - index: function(itemElements, valueNames) { - for (var i = 0, il = itemElements.length; i < il; i++) { - self.items.push(new Item(valueNames, itemElements[i])); - } - }, - indexAsync: function(itemElements, valueNames) { - var itemsToIndex = itemElements.splice(0, 100); // TODO: If < 100 items, what happens in IE etc? - this.index(itemsToIndex, valueNames); - if (itemElements.length > 0) { - setTimeout(function() { - init.items.indexAsync(itemElements, valueNames); - }, - 10); - } else { - self.update(); - // TODO: Add indexed callback - } - } - }, - plugins: function(plugins) { - var locals = { - templater: templater, - init: init, - initialItems: initialItems, - Item: Item, - Templater: Templater, - sortButtons: sortButtons, - events: events, - reset: reset - }; - for (var i = 0; i < plugins.length; i++) { - plugins[i][1] = plugins[i][1] || {}; - var pluginName = plugins[i][1].name || plugins[i][0]; - self[pluginName] = self.plugins[plugins[i][0]].call(self, locals, plugins[i][1]); - } - } - }; - - - /* - * Add object to list - */ - this.add = function(values, callback) { - if (callback) { - addAsync(values, callback); - } - var added = [], - notCreate = false; - if (values[0] === undefined){ - values = [values]; - } - for (var i = 0, il = values.length; i < il; i++) { - var item = null; - if (values[i] instanceof Item) { - item = values[i]; - item.reload(); - } else { - notCreate = (self.items.length > self.page) ? true : false; - item = new Item(values[i], undefined, notCreate); - } - self.items.push(item); - added.push(item); - } - self.update(); - return added; - }; - - /* - * Adds items asynchronous to the list, good for adding huge amount of - * data. Defaults to add 100 items a time - */ - var addAsync = function(values, callback, items) { - var valuesToAdd = values.splice(0, 100); - items = items || []; - items = items.concat(self.add(valuesToAdd)); - if (values.length > 0) { - setTimeout(function() { - addAsync(values, callback, items); - }, 10); - } else { - self.update(); - callback(items); - } - }; - - this.show = function(i, page) { - this.i = i; - this.page = page; - self.update(); - }; - - /* Removes object from list. - * Loops through the list and removes objects where - * property "valuename" === value - */ - this.remove = function(valueName, value, options) { - var found = 0; - for (var i = 0, il = self.items.length; i < il; i++) { - if (self.items[i].values()[valueName] == value) { - templater.remove(self.items[i], options); - self.items.splice(i,1); - il--; - found++; - } - } - self.update(); - return found; - }; - - /* Gets the objects in the list which - * property "valueName" === value - */ - this.get = function(valueName, value) { - var matchedItems = []; - for (var i = 0, il = self.items.length; i < il; i++) { - var item = self.items[i]; - if (item.values()[valueName] == value) { - matchedItems.push(item); - } - } - if (matchedItems.length == 0) { - return null; - } else if (matchedItems.length == 1) { - return matchedItems[0]; - } else { - return matchedItems; - } - }; - - /* Sorts the list. - * @valueOrEvent Either a JavaScript event object or a valueName - * @sortFunction (optional) Define if natural sorting does not fullfill your needs - */ - this.sort = function(valueName, options) { - var length = self.items.length, - value = null, - target = valueName.target || valueName.srcElement, /* IE have srcElement */ - sorting = '', - isAsc = false, - asc = 'asc', - desc = 'desc', - options = options || {}; - - if (target === undefined) { - value = valueName; - isAsc = options.asc || false; - } else { - value = h.getAttribute(target, 'data-sort'); - isAsc = h.hasClass(target, asc) ? false : true; - } - for (var i = 0, il = sortButtons.length; i < il; i++) { - h.removeClass(sortButtons[i], asc); - h.removeClass(sortButtons[i], desc); - } - if (isAsc) { - if (target !== undefined) { - h.addClass(target, asc); - } - isAsc = true; - } else { - if (target !== undefined) { - h.addClass(target, desc); - } - isAsc = false; - } - - if (options.sortFunction) { - options.sortFunction = options.sortFunction; - } else { - options.sortFunction = function(a, b) { - return h.sorter.alphanum(a.values()[value].toLowerCase(), b.values()[value].toLowerCase(), isAsc); - }; - } - self.items.sort(options.sortFunction); - self.update(); - }; - - /* - * Searches the list after values with content "searchStringOrEvent". - * The columns parameter defines if all values should be included in the search, - * defaults to undefined which means "all". - */ - this.search = function(searchString, columns) { - self.i = 1; // Reset paging - var matching = [], - found, - item, - text, - values, - is, - columns = (columns === undefined) ? self.items[0].values() : columns, - searchString = (searchString === undefined) ? "" : searchString, - target = searchString.target || searchString.srcElement; /* IE have srcElement */ - - searchString = (target === undefined) ? (""+searchString).toLowerCase() : ""+target.value.toLowerCase(); - is = self.items; - // Escape regular expression characters - searchString = searchString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - - templater.clear(); - if (searchString === "" ) { - reset.search(); - self.searched = false; - self.update(); - } else { - self.searched = true; - - for (var k = 0, kl = is.length; k < kl; k++) { - found = false; - item = is[k]; - values = item.values(); - - for(var j in columns) { - if(values.hasOwnProperty(j) && columns[j] !== null) { - text = (values[j] != null) ? values[j].toString().toLowerCase() : ""; - if ((searchString !== "") && (text.search(searchString) > -1)) { - found = true; - } - } - } - if (found) { - item.found = true; - matching.push(item); - } else { - item.found = false; - } - } - self.update(); - } - return self.visibleItems; - }; - - /* - * Filters the list. If filterFunction() returns False hides the Item. - * if filterFunction == false are the filter removed - */ - this.filter = function(filterFunction) { - self.i = 1; // Reset paging - reset.filter(); - if (filterFunction === undefined) { - self.filtered = false; - } else { - self.filtered = true; - var is = self.items; - for (var i = 0, il = is.length; i < il; i++) { - var item = is[i]; - if (filterFunction(item)) { - item.filtered = true; - } else { - item.filtered = false; - } - } - } - self.update(); - return self.visibleItems; - }; - - /* - * Get size of the list - */ - this.size = function() { - return self.items.length; - }; - - /* - * Removes all items from the list - */ - this.clear = function() { - templater.clear(); - self.items = []; - }; - - this.on = function(event, callback) { - events[event].push(callback); - }; - - var trigger = function(event) { - var i = events[event].length; - while(i--) { - events[event][i](); - } - }; - - var reset = { - filter: function() { - var is = self.items, - il = is.length; - while (il--) { - is[il].filtered = false; - } - }, - search: function() { - var is = self.items, - il = is.length; - while (il--) { - is[il].found = false; - } - } - }; - - - this.update = function() { - var is = self.items, - il = is.length; - - self.visibleItems = []; - self.matchingItems = []; - templater.clear(); - for (var i = 0; i < il; i++) { - if (is[i].matching() && ((self.matchingItems.length+1) >= self.i && self.visibleItems.length < self.page)) { - is[i].show(); - self.visibleItems.push(is[i]); - self.matchingItems.push(is[i]); - } else if (is[i].matching()) { - self.matchingItems.push(is[i]); - is[i].hide(); - } else { - is[i].hide(); - } - } - trigger('updated'); - }; - - Item = function(initValues, element, notCreate) { - var item = this, - values = {}; - - this.found = false; // Show if list.searched == true and this.found == true - this.filtered = false;// Show if list.filtered == true and this.filtered == true - - var init = function(initValues, element, notCreate) { - if (element === undefined) { - if (notCreate) { - item.values(initValues, notCreate); - } else { - item.values(initValues); - } - } else { - item.elm = element; - var values = templater.get(item, initValues); - item.values(values); - } - }; - this.values = function(newValues, notCreate) { - if (newValues !== undefined) { - for(var name in newValues) { - values[name] = newValues[name]; - } - if (notCreate !== true) { - templater.set(item, item.values()); - } - } else { - return values; - } - }; - this.show = function() { - templater.show(item); - }; - this.hide = function() { - templater.hide(item); - }; - this.matching = function() { - return ( - (self.filtered && self.searched && item.found && item.filtered) || - (self.filtered && !self.searched && item.filtered) || - (!self.filtered && self.searched && item.found) || - (!self.filtered && !self.searched) - ); - }; - this.visible = function() { - return (item.elm.parentNode) ? true : false; - }; - init(initValues, element, notCreate); - }; - - /* Templater with different kinds of template engines. - * All engines have these methods - * - reload(item) - * - remove(item) - */ - Templater = function(list, settings) { - if (settings.engine === undefined) { - settings.engine = "standard"; - } else { - settings.engine = settings.engine.toLowerCase(); - } - return new self.constructor.prototype.templateEngines[settings.engine](list, settings); - }; - - init.start(values, options); -}; - -List.prototype.templateEngines = {}; -List.prototype.plugins = {}; - -List.prototype.templateEngines.standard = function(list, settings) { - var listSource = h.getByClass(settings.listClass, list.listContainer, true), - itemSource = getItemSource(settings.item), - templater = this; - - function getItemSource(item) { - if (item === undefined) { - var nodes = listSource.childNodes, - items = []; - - for (var i = 0, il = nodes.length; i < il; i++) { - // Only textnodes have a data attribute - if (nodes[i].data === undefined) { - return nodes[i]; - } - } - return null; - } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! - var div = document.createElement('div'); - div.innerHTML = item; - return div.firstChild; - } else { - return document.getElementById(settings.item); - } - } - - var ensure = { - created: function(item) { - if (item.elm === undefined) { - templater.create(item); - } - } - }; - - /* Get values from element */ - this.get = function(item, valueNames) { - ensure.created(item); - var values = {}; - for(var i = 0, il = valueNames.length; i < il; i++) { - var elm = h.getByClass(valueNames[i], item.elm, true); - values[valueNames[i]] = elm ? elm.innerHTML : ""; - } - return values; - }; - - /* Sets values at element */ - this.set = function(item, values) { - ensure.created(item); - for(var v in values) { - if (values.hasOwnProperty(v)) { - // TODO speed up if possible - var elm = h.getByClass(v, item.elm, true); - if (elm) { - elm.innerHTML = values[v]; - } - } - } - }; - - this.create = function(item) { - if (item.elm !== undefined) { - return; - } - /* If item source does not exists, use the first item in list as - source for new items */ - var newItem = itemSource.cloneNode(true); - newItem.id = ""; - item.elm = newItem; - templater.set(item, item.values()); - }; - this.remove = function(item) { - listSource.removeChild(item.elm); - }; - this.show = function(item) { - ensure.created(item); - listSource.appendChild(item.elm); - }; - this.hide = function(item) { - if (item.elm !== undefined && item.elm.parentNode === listSource) { - listSource.removeChild(item.elm); - } - }; - this.clear = function() { - /* .innerHTML = ''; fucks up IE */ - if (listSource.hasChildNodes()) { - while (listSource.childNodes.length >= 1) - { - listSource.removeChild(listSource.firstChild); - } - } - }; -}; - - -/* -* These helper functions are not written by List.js author Jonny (they may have been -* adjusted, thought). -*/ -h = { - /* - * Cross browser getElementsByClassName, which uses native - * if it exists. Modified version of Dustin Diaz function: - * http://www.dustindiaz.com/getelementsbyclass - */ - getByClass: (function() { - if (document.getElementsByClassName) { - return function(searchClass,node,single) { - if (single) { - return node.getElementsByClassName(searchClass)[0]; - } else { - return node.getElementsByClassName(searchClass); - } - }; - } else { - return function(searchClass,node,single) { - var classElements = [], - tag = '*'; - if (node == null) { - node = document; - } - var els = node.getElementsByTagName(tag); - var elsLen = els.length; - var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)"); - for (var i = 0, j = 0; i < elsLen; i++) { - if ( pattern.test(els[i].className) ) { - if (single) { - return els[i]; - } else { - classElements[j] = els[i]; - j++; - } - } - } - return classElements; - }; - } - })(), - /* (elm, 'event' callback) Source: http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/ */ - addEvent: (function( window, document ) { - if ( document.addEventListener ) { - return function( elem, type, cb ) { - if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { - elem.addEventListener(type, cb, false ); - } else if ( elem && elem[0] !== undefined ) { - var len = elem.length; - for ( var i = 0; i < len; i++ ) { - h.addEvent(elem[i], type, cb); - } - } - }; - } - else if ( document.attachEvent ) { - return function ( elem, type, cb ) { - if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { - elem.attachEvent( 'on' + type, function() { return cb.call(elem, window.event); } ); - } else if ( elem && elem[0] !== undefined ) { - var len = elem.length; - for ( var i = 0; i < len; i++ ) { - h.addEvent( elem[i], type, cb ); - } - } - }; - } - })(this, document), - /* (elm, attribute) Source: http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method */ - getAttribute: function(ele, attr) { - var result = (ele.getAttribute && ele.getAttribute(attr)) || null; - if( !result ) { - var attrs = ele.attributes; - var length = attrs.length; - for(var i = 0; i < length; i++) { - if (attr[i] !== undefined) { - if(attr[i].nodeName === attr) { - result = attr[i].nodeValue; - } - } - } - } - return result; - }, - /* http://stackoverflow.com/questions/7238177/detect-htmlcollection-nodelist-in-javascript */ - isNodeList: function(nodes) { - var result = Object.prototype.toString.call(nodes); - if (typeof nodes === 'object' && /^\[object (HTMLCollection|NodeList|Object)\]$/.test(result) && (nodes.length == 0 || (typeof nodes[0] === "object" && nodes[0].nodeType > 0))) { - return true; - } - return false; - }, - hasClass: function(ele, classN) { - var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; - return (classes.search(classN) > -1); - }, - addClass: function(ele, classN) { - if (!this.hasClass(ele, classN)) { - var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; - classes = classes + ' ' + classN + ' '; - classes = classes.replace(/\s{2,}/g, ' '); - ele.setAttribute('class', classes); - } - }, - removeClass: function(ele, classN) { - if (this.hasClass(ele, classN)) { - var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; - classes = classes.replace(classN, ''); - ele.setAttribute('class', classes); - } - }, - /* - * The sort function. From http://my.opera.com/GreyWyvern/blog/show.dml/1671288 - */ - sorter: { - alphanum: function(a,b,asc) { - if (a === undefined || a === null) { - a = ""; - } - if (b === undefined || b === null) { - b = ""; - } - a = a.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ - return (p1 == "lt")? "<" : ">"; - }); - a = a.replace(/<\/?[^>]+(>|$)/g, ""); - - b = b.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ - return (p1 == "lt")? "<" : ">"; - }); - b = b.replace(/<\/?[^>]+(>|$)/g, ""); - var aa = this.chunkify(a); - var bb = this.chunkify(b); - - for (var x = 0; aa[x] && bb[x]; x++) { - if (aa[x] !== bb[x]) { - var c = Number(aa[x]), d = Number(bb[x]); - if (asc) { - if (c == aa[x] && d == bb[x]) { - return c - d; - } else { - return (aa[x] > bb[x]) ? 1 : -1; - } - } else { - if (c == aa[x] && d == bb[x]) { - return d-c;//c - d; - } else { - return (aa[x] > bb[x]) ? -1 : 1; //(aa[x] > bb[x]) ? 1 : -1; - } - } - } - } - return aa.length - bb.length; - }, - chunkify: function(t) { - var tz = [], x = 0, y = -1, n = 0, i, j; - - while (i = (j = t.charAt(x++)).charCodeAt(0)) { - var m = (i == 45 || i == 46 || (i >=48 && i <= 57)); - if (m !== n) { - tz[++y] = ""; - n = m; - } - tz[y] += j; - } - return tz; - } - } -}; - -window.List = List; -window.ListJsHelpers = h; -})(window); From 10d1615cd878f33ea4175bfab76e7cca83bd1785 Mon Sep 17 00:00:00 2001 From: Julius Date: Sat, 5 Oct 2013 22:28:04 +0200 Subject: [PATCH 48/50] Prepare tooltip for next bootstrap version (as in 1fed9834b35dbee8b329305b2a62ac1ddf8e0eb7) --- app/views/stock_takings/new.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/stock_takings/new.html.haml b/app/views/stock_takings/new.html.haml index c46406f3..6e4526f9 100644 --- a/app/views/stock_takings/new.html.haml +++ b/app/views/stock_takings/new.html.haml @@ -10,7 +10,8 @@ $('[data-toggle~="tooltip"]', context).tooltip({ animation: false, html: true, - placement: 'left' + placement: 'left', + container: 'body' }); } From 9041a350d5b15279974241b58515ec3c663ed00f Mon Sep 17 00:00:00 2001 From: Julius Date: Sat, 5 Oct 2013 22:37:17 +0200 Subject: [PATCH 49/50] Add some locales for article attributes --- config/locales/de.yml | 5 +++++ config/locales/en.yml | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index bed1f8b9..ea3d9bf8 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 diff --git a/config/locales/en.yml b/config/locales/en.yml index cebb1c8e..d54afc8a 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 From 1b49a08c5e4084ee4c77f3594bb2a533b32b9876 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 8 Oct 2013 11:42:23 +0200 Subject: [PATCH 50/50] localeapp roundtrip --- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/fr.yml | 7 +++++++ config/locales/nl.yml | 7 +++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 8b2c4bb2..ab2c694b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -802,7 +802,7 @@ de: note: Notiz price: Preis reset_article_search: Suche zurücksetzen - search_article: Artikel suchen... + search_article: Artikel suchen... sum: Summe sum_amount: ! 'Gesamtbestellmenge bisher:' supplier: Lieferant diff --git a/config/locales/en.yml b/config/locales/en.yml index 9b514e31..f7d04466 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -806,7 +806,7 @@ en: note: Note price: Price reset_article_search: Reset search - search_article: Search for article... + search_article: Search for article... sum: Sum sum_amount: Current amount supplier: Supplier diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 835bbdfe..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 @@ -811,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 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 5a3d4f99..2bef7df0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -42,7 +42,10 @@ nl: 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 @@ -797,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