From eb4705857b68ab36413ec9355434bc373d97265c Mon Sep 17 00:00:00 2001 From: benni Date: Sun, 19 Jun 2011 15:30:33 +0200 Subject: [PATCH] Ordering refactored: Made everything RESTful. --- app/controllers/ordering_controller.rb | 158 +++++------------- app/helpers/ordering_helper.rb | 29 ++++ app/models/group_order.rb | 83 +++++++-- app/models/group_order_article.rb | 65 ++++--- app/models/order.rb | 8 +- app/views/layouts/_main_tabnav.html.erb | 2 +- app/views/ordering/_data.html.erb | 4 +- app/views/ordering/_footer.html.haml | 23 +++ app/views/ordering/_form.html.haml | 90 ++++++++++ app/views/ordering/_order_head.haml | 2 +- .../{myOrders.haml => archive.html.haml} | 2 +- app/views/ordering/edit.html.haml | 1 + .../ordering/{index.haml => index.html.haml} | 9 +- app/views/ordering/new.html.haml | 1 + .../{my_order_result.haml => show.html.haml} | 46 ++--- app/views/shared/_open_orders.html.haml | 2 +- config/initializers/load_app_config.rb | 6 +- config/routes.rb | 4 +- public/javascripts/ordering.js | 27 ++- 19 files changed, 340 insertions(+), 222 deletions(-) create mode 100644 app/helpers/ordering_helper.rb create mode 100644 app/views/ordering/_footer.html.haml create mode 100644 app/views/ordering/_form.html.haml rename app/views/ordering/{myOrders.haml => archive.html.haml} (89%) create mode 100644 app/views/ordering/edit.html.haml rename app/views/ordering/{index.haml => index.html.haml} (85%) create mode 100644 app/views/ordering/new.html.haml rename app/views/ordering/{my_order_result.haml => show.html.haml} (69%) diff --git a/app/controllers/ordering_controller.rb b/app/controllers/ordering_controller.rb index 54224744..4c57dcda 100644 --- a/app/controllers/ordering_controller.rb +++ b/app/controllers/ordering_controller.rb @@ -3,71 +3,51 @@ class OrderingController < ApplicationController # Security before_filter :ensure_ordergroup_member - before_filter :ensure_open_order, :only => [:order, :stock_order, :saveOrder] + before_filter :ensure_open_order, :only => [:new, :create, :edit, :update, :order, :stock_order, :saveOrder] # Index page. def index end - # Edit a current order. - def order - redirect_to :action => 'stock_order', :id => @order if @order.stockit? + def new + @group_order = @order.group_orders.build(:ordergroup => @ordergroup, :updated_by => current_user) + @ordering_data = @group_order.load_data + end - # Load order article data... - @articles_grouped_by_category = @order.articles_grouped_by_category - # save results of earlier orders in array - ordered_articles = Array.new - @group_order = @order.group_orders.find(:first, - :conditions => "ordergroup_id = #{@ordergroup.id}", :include => :group_order_articles) - - if @group_order - # Group has already ordered, so get the results... - for goa in @group_order.group_order_articles - ordered_articles[goa.order_article_id] = {:quantity => goa.quantity, - :tolerance => goa.tolerance, - :quantity_result => goa.result(:quantity), - :tolerance_result => goa.result(:tolerance)} - end - @version = @group_order.lock_version - @availableFunds = @ordergroup.get_available_funds(@group_order) - else - @version = 0 - @availableFunds = @ordergroup.get_available_funds + def create + @group_order = GroupOrder.new(params[:group_order]) + begin + @group_order.save_ordering! + redirect_to group_order_url(@group_order), :notice => 'Die Bestellung wurde gespeichert.' + rescue ActiveRecord::StaleObjectError + redirect_to group_orders_url, :alert => 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.' + rescue => exception + logger.error('Failed to update order: ' + exception.message) + redirect_to group_orders_url, :alert => 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.' end + end - # load prices .... - @price = Array.new; @unit = Array.new; - @others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new - @others_tolerance = Array.new; @tolerance = Array.new; @tolerance_result = Array.new; @used_tolerance = Array.new; @unused_tolerance = Array.new - i = 0; - @articles_grouped_by_category.each do |category_name, order_articles| - for order_article in order_articles - # price/unit size - @price[i] = order_article.article.fc_price - @unit[i] = order_article.article.unit_quantity - # quantity - @quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity] : 0) - @others_quantity[i] = order_article.quantity - @quantity[i] - @used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity_result] : 0) - # tolerance - @tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:tolerance] : 0) - @others_tolerance[i] = order_article.tolerance - @tolerance[i] - @used_tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:tolerance_result] : 0) - i += 1 - end - end + def show + @group_order = GroupOrder.find(params[:id]) + @order= @group_order.order + end - @add_data_to_js = [] - if Foodsoft.config[:tolerance_is_costly] - for i in 0...@price.size - @add_data_to_js << [@price[i], @unit[i], @price[i] * (@tolerance[i] + @quantity[i]), @others_quantity[i], - @others_tolerance[i], @used_quantity[i], 0] - end - else - for j in 0...@price.size - @add_data_to_js << [@price[j], @unit[j], @price[j] * @quantity[j], @others_quantity[j], - @others_tolerance[j], @used_quantity[j], 0] - end + def edit + @group_order = GroupOrder.find(params[:id]) + @ordering_data = @group_order.load_data + end + + def update + @group_order = GroupOrder.find(params[:id]) + @group_order.attributes = params[:group_order] + begin + @group_order.save_ordering! + redirect_to group_order_url(@group_order), :notice => 'Die Bestellung wurde gespeichert.' + rescue ActiveRecord::StaleObjectError + redirect_to group_orders_url, :alert => 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.' + rescue => exception + logger.error('Failed to update order: ' + exception.message) + redirect_to group_orders_url, :alert => 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.' end end @@ -112,68 +92,9 @@ class OrderingController < ApplicationController end end - # Update changes to a current order. - def saveOrder - if (params[:total_balance].to_i < 0) #TODO: Better use a real test on sufficiant funds - flash[:error] = 'Der Bestellwert übersteigt das verfügbare Guthaben.' - redirect_to :action => 'order' - elsif (ordered = params[:ordered]) - begin - Order.transaction do - # Try to find group_order - group_order = @order.group_orders.first :conditions => "ordergroup_id = #{@ordergroup.id}", - :include => [:group_order_articles] - # Create group order if necessary... - unless group_order.nil? - # check for conflicts well ahead - if (params[:version].to_i != group_order.lock_version) - raise ActiveRecord::StaleObjectError - end - else - group_order = @ordergroup.group_orders.create!(:order => @order, :updated_by => @current_user, :price => 0) - end - - # Create/update group_order_articles... - for order_article in @order.order_articles - - # Find the group_order_article, create a new one if necessary... - group_order_article = group_order.group_order_articles.detect { |v| v.order_article_id == order_article.id } - if group_order_article.nil? - group_order_article = group_order.group_order_articles.create(:order_article_id => order_article.id) - end - - # Get ordered quantities and update group_order_articles/_quantities... - quantities = ordered.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0}) - group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i) - - # Also update results for the order_article - order_article.update_results! - end - - group_order.update_price! - group_order.update_attribute(:updated_by, @current_user) - end - flash[:notice] = 'Die Bestellung wurde gespeichert.' - rescue ActiveRecord::StaleObjectError - flash[:error] = 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.' - rescue => exception - logger.error('Failed to update order: ' + exception.message) - flash[:error] = 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.' - end - redirect_to :action => 'my_order_result', :id => @order - end - end - - # Shows the Result for the Ordergroup the current user belongs to - # this method decides between finished and unfinished orders - def my_order_result - @order= Order.find(params[:id]) - @group_order = @order.group_order(@ordergroup) - end - # Shows all Orders of the Ordergroup # if selected, it shows all orders of the foodcoop - def myOrders + def archive # get only orders belonging to the ordergroup @closed_orders = Order.paginate :page => params[:page], :per_page => 10, :conditions => { :state => 'closed' }, :order => "orders.ends DESC" @@ -194,7 +115,7 @@ class OrderingController < ApplicationController else flash[:error] = "Kommentar konnte nicht erstellt werden. Leerer Kommentar?" end - redirect_to :action => 'my_order_result', :id => order + redirect_to :action => 'show', :id => order end private @@ -209,7 +130,8 @@ class OrderingController < ApplicationController end def ensure_open_order - @order = Order.find(params[:id], :include => [:supplier, :order_articles]) + @order = Order.find((params[:order_id] || params[:group_order][:order_id]), + :include => [:supplier, :order_articles]) unless @order.open? flash[:notice] = 'Diese Bestellung ist bereits abgeschlossen.' redirect_to :action => 'index' diff --git a/app/helpers/ordering_helper.rb b/app/helpers/ordering_helper.rb new file mode 100644 index 00000000..3e0c9494 --- /dev/null +++ b/app/helpers/ordering_helper.rb @@ -0,0 +1,29 @@ +module OrderingHelper + def data_to_js(ordering_data) + ordering_data[:order_articles].map do |id, data| + if Foodsoft.config[:tolerance_is_costly] + [id, data[:price], data[:unit], data[:price] * (data[:tolerance] + data[:quantity]), data[:others_quantity], data[:others_tolerance], data[:used_quantity], 0] + else + [id, data[:price], data[:unit], data[:price] * data[:quantity], data[:others_quantity], data[:others_tolerance], data[:used_quantity], 0] + end + end + end + + def link_to_ordering(order) + path = if group_order = order.group_order(current_user.ordergroup) + edit_group_order_path(group_order, :order_id => order.id) + else + new_group_order_path(:order_id => order.id) + end + link_to order.name, path + end + + # Return css class names for order result table + def order_article_class_name(quantity, tolerance, result) + if (quantity + tolerance > 0) + result > 0 ? 'success' : 'failed' + else + 'ignored' + end + end +end \ No newline at end of file diff --git a/app/models/group_order.rb b/app/models/group_order.rb index 90b4ede8..b3af1b28 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -1,6 +1,8 @@ # A GroupOrder represents an Order placed by an Ordergroup. class GroupOrder < ActiveRecord::Base - + + attr_accessor :group_order_articles_attributes + belongs_to :order belongs_to :ordergroup has_many :group_order_articles, :dependent => :destroy @@ -14,27 +16,74 @@ class GroupOrder < ActiveRecord::Base scope :open, lambda { {:conditions => ["order_id IN (?)", Order.open.collect(&:id)]} } scope :finished, lambda { {:conditions => ["order_id IN (?)", Order.finished_not_closed.collect(&:id)]} } - - # Updates the "price" attribute. - # Until the order is finished this will be the maximum price or - # the minimum price depending on configuration. When the order is finished it - # will be the value depending of the article results. - def update_price! - total = 0 - for article in group_order_articles.find(:all, :include => :order_article) - unless order.finished? - if Foodsoft.config[:tolerance_is_costly] - total += article.order_article.article.fc_price * (article.quantity + article.tolerance) - else - total += article.order_article.article.fc_price * article.quantity - end - else - total += article.order_article.price.fc_price * article.result + + # Generate some data for the javascript methods in ordering view + def load_data + data = {} + data[:available_funds] = ordergroup.get_available_funds(self) + + unless new_record? + # Group has already ordered, so get the results... + goas = {} + group_order_articles.all.each do |goa| + goas[goa.order_article_id] = {:quantity => goa.quantity, + :tolerance => goa.tolerance, + :quantity_result => goa.result(:quantity), + :tolerance_result => goa.result(:tolerance)} end end + + # load prices .... + data[:order_articles] = {} + order.order_articles.each do |order_article| + data[:order_articles][order_article.id] = { + :price => order_article.article.fc_price, + :unit => order_article.article.unit_quantity, + :quantity => (new_record? ? 0 : goas[order_article.id][:quantity]), + :others_quantity => order_article.quantity - (new_record? ? 0 : goas[order_article.id][:quantity]), + :used_quantity => (new_record? ? 0 : goas[order_article.id][:quantity_result]), + :tolerance => (new_record? ? 0 : goas[order_article.id][:tolerance]), + :others_tolerance => order_article.tolerance - (new_record? ? 0 : goas[order_article.id][:tolerance]), + :used_tolerance => (new_record? ? 0 : goas[order_article.id][:tolerance_result]) + } + end + + data + end + + def save_group_order_articles + for order_article in order.order_articles + # Find the group_order_article, create a new one if necessary... + group_order_article = group_order_articles.find_or_create_by_order_article_id(order_article.id) + + # Get ordered quantities and update group_order_articles/_quantities... + quantities = group_order_articles_attributes.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0}) + group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i) + + # Also update results for the order_article + logger.debug "[save_group_order_articles] update order_article.results!" + order_article.update_results! + end + + # set attributes to nil to avoid and infinite loop of + end + + # Updates the "price" attribute. + def update_price! + total = group_order_articles.includes(:order_article => :article).all.map(&:total_price).sum update_attribute(:price, total) end + + # Save GroupOrder and updates group_order_articles/quantities accordingly + def save_ordering! + transaction do + save! + save_group_order_articles + update_price! + end + end + end # == Schema Information diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 9f680551..76255347 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -14,15 +14,10 @@ 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 - attr_accessor :ordergroup_id # To create an new GroupOrder if neccessary - scope :ordered, :conditions => 'result > 0' - # Custom attribute setter that accepts decimal numbers using localized decimal separator. - def result=(result) - self[:result] = String.delocalized_decimal(result) - end - + localize_input_of :result + # Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties # and the associated GroupOrderArticleQuantities chronologically. # @@ -30,17 +25,17 @@ class GroupOrderArticle < ActiveRecord::Base def update_quantities(quantity, tolerance) logger.debug("GroupOrderArticle[#{id}].update_quantities(#{quantity}, #{tolerance})") logger.debug("Current quantity = #{self.quantity}, tolerance = #{self.tolerance}") - + # Get quantities ordered with the newest item first. quantities = group_order_article_quantities.find(:all, :order => 'created_on desc') logger.debug("GroupOrderArticleQuantity items found: #{quantities.size}") - if (quantities.size == 0) + if (quantities.size == 0) # There is no GroupOrderArticleQuantity item yet, just insert with desired quantities... logger.debug("No quantities entry at all, inserting a new one with the desired quantities") quantities.push(GroupOrderArticleQuantity.new(:group_order_article => self, :quantity => quantity, :tolerance => tolerance)) - self.quantity, self.tolerance = quantity, tolerance - else + self.quantity, self.tolerance = quantity, tolerance + else # Decrease quantity/tolerance if necessary by going through the existing items and decreasing their values... i = 0 while (i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance)) @@ -50,31 +45,31 @@ class GroupOrderArticle < ActiveRecord::Base delta = (delta > quantities[i].quantity ? quantities[i].quantity : delta) logger.debug("Decreasing quantity by #{delta}") quantities[i].quantity -= delta - self.quantity -= delta + self.quantity -= delta end if (tolerance < self.tolerance && quantities[i].tolerance > 0) delta = self.tolerance - tolerance delta = (delta > quantities[i].tolerance ? quantities[i].tolerance : delta) logger.debug("Decreasing tolerance by #{delta}") quantities[i].tolerance -= delta - self.tolerance -= delta + self.tolerance -= delta end i += 1 - end + end # If there is at least one increased value: insert a new GroupOrderArticleQuantity object if (quantity > self.quantity || tolerance > self.tolerance) logger.debug("Inserting a new GroupOrderArticleQuantity") quantities.insert(0, GroupOrderArticleQuantity.new( - :group_order_article => self, - :quantity => (quantity > self.quantity ? quantity - self.quantity : 0), - :tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0) + :group_order_article => self, + :quantity => (quantity > self.quantity ? quantity - self.quantity : 0), + :tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0) )) # Recalc totals: self.quantity += quantities[0].quantity - self.tolerance += quantities[0].tolerance + self.tolerance += quantities[0].tolerance end end - + # Check if something went terribly wrong and quantites have not been adjusted as desired. if (self.quantity != quantity || self.tolerance != tolerance) raise 'Invalid state: unable to update GroupOrderArticle/-Quantities to desired quantities!' @@ -82,7 +77,7 @@ class GroupOrderArticle < ActiveRecord::Base # Remove zero-only items. quantities = quantities.reject { | q | q.quantity == 0 && q.tolerance == 0} - + # Save transaction do quantities.each { | i | i.save! } @@ -90,7 +85,7 @@ class GroupOrderArticle < ActiveRecord::Base save! end end - + # Determines how many items of this article the Ordergroup receives. # Returns a hash with three keys: :quantity / :tolerance / :total # @@ -102,15 +97,15 @@ class GroupOrderArticle < ActiveRecord::Base # 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') + :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) @@ -137,10 +132,10 @@ class GroupOrderArticle < ActiveRecord::Base i += 1 end end - + logger.debug("determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}") end - + {:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance} end memoize :calculate_result @@ -156,7 +151,23 @@ class GroupOrderArticle < ActiveRecord::Base def save_results! self.update_attribute(:result, calculate_result[:total]) end - + + # Returns total price for this individual article + # Until the order is finished this will be the maximum price or + # the minimum price depending on configuration. When the order is finished it + # will be the value depending of the article results. + def total_price + unless order_article.order.finished? + if Foodsoft.config[:tolerance_is_costly] + order_article.article.fc_price * (quantity + tolerance) + else + order_article.article.fc_price * quantity + end + else + order_article.price.fc_price * result + end + end + end diff --git a/app/models/order.rb b/app/models/order.rb index cee06608..93b61bee 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -85,16 +85,16 @@ class Order < ActiveRecord::Base # search GroupOrder of given Ordergroup def group_order(ordergroup) - group_orders.first :conditions => { :ordergroup_id => ordergroup.id } + group_orders.where(:ordergroup_id => ordergroup.id).first end # Returns OrderArticles in a nested Array, grouped by category and ordered by article name. # The array has the following form: # e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]] def articles_grouped_by_category - order_articles.all(:include => [:article, :article_price], :order => 'articles.name').group_by { |a| - a.article.article_category.name - }.sort { |a, b| a[0] <=> b[0] } + order_articles.includes(:article, :article_price).order('articles.name'). + group_by { |a| a.article.article_category.name }. + sort { |a, b| a[0] <=> b[0] } end memoize :articles_grouped_by_category diff --git a/app/views/layouts/_main_tabnav.html.erb b/app/views/layouts/_main_tabnav.html.erb index 1778582a..d109256f 100644 --- a/app/views/layouts/_main_tabnav.html.erb +++ b/app/views/layouts/_main_tabnav.html.erb @@ -28,7 +28,7 @@ :active => ["orders", "ordering"], :subnav => [ { :name => "Bestellen!", :url => ordering_path }, - { :name => "Meine Bestellungen", :url => my_orders_path }, + { :name => "Meine Bestellungen", :url => archive_group_orders_path }, { :name => "Bestellverwaltung", :url => orders_path, :access_denied? => (!u.role_orders?) } ] }, diff --git a/app/views/ordering/_data.html.erb b/app/views/ordering/_data.html.erb index 724477da..2bab910d 100644 --- a/app/views/ordering/_data.html.erb +++ b/app/views/ordering/_data.html.erb @@ -1,10 +1,10 @@