Permalink
Browse files

Allow volume as sum of product's variants.

  • Loading branch information...
Adam Wróbel
Adam Wróbel committed Apr 12, 2011
1 parent d66d65a commit 07fe92e4316e744ea364e72f860929fd181cb788
@@ -1,4 +1,4 @@
require "render_inheritable"
Admin::BaseController.class_eval do
render_inheritable
-end
+end unless Admin::BaseController.included_modules.include? RenderInheritable
@@ -10,4 +10,4 @@ def volume_prices
render :edit
end
end
-end
+end unless Admin::ProductsController.instance_methods.include? :volume_prices
@@ -0,0 +1,8 @@
+ProductsHelper.module_eval do
+ def variant_price_diff_with_volume_discount variant
+ return if variant.product.variants_use_master_discount
+ variant_price_diff_without_volume_discount variant
+ end
+ alias_method_chain :variant_price_diff, :volume_discount
+end unless ProductsHelper.instance_methods.include? \
+ :variant_price_diff_with_volume_discount
@@ -1,18 +1,20 @@
LineItem.class_eval do
- before_save :check_update_volume_discount
-
def update_volume_discount updated_order = nil
self.volume_discount = 0
+ self.order = updated_order if updated_order
- return if self.quantity < 1 || variant.volume_prices.empty?
+ if variant.uses_master_discount? && new_record?
+ self.price = variant.volume_prices_source.price
+ end
- self.order = updated_order if updated_order
- starting_quantity = order.variant_starting_quantity(variant)
+ return if quantity < 1 || !variant.uses_volume_pricing?
+
+ unless variant.requires_per_product_discount?
+ starting_quantity = order.variants_starting_quantity(variant_id)
- self.volume_discount = if variant.progressive_volume_discount
- progressive_price_strategy starting_quantity
- else
- uniform_price_strategy starting_quantity
+ volume_cost = variant.volume_prices_source.total_volume_cost \
+ starting_quantity, quantity
+ self.volume_discount = volume_cost - quantity * self.price
end
end
@@ -23,44 +25,18 @@ def amount_with_volume_discount
alias_method_chain :amount, :volume_discount
alias total amount
- private
- def check_update_volume_discount
- update_volume_discount if price_changed? || quantity_changed?
- end
-
- def uniform_price_strategy starting_quantity
- total_quantity = self.quantity + starting_quantity
- final_price = default_price = self.price
-
- variant.volume_prices.each do |vp|
- break if vp.starting_quantity > total_quantity
- final_price = vp.price
+ def update_order_with_volume_discount
+ if quantity > 0 && variant.requires_per_product_discount?
+ order.update_product_volume_discount variant.product
end
- self.quantity * (final_price - default_price)
+ update_order_without_volume_discount
end
+ alias_method_chain :update_order, :volume_discount
- def progressive_price_strategy units_processed
- discount = 0
- total_quantity = self.quantity + units_processed
- current_price = default_price = self.price
-
- variant.volume_prices.each do |vp|
- if vp.starting_quantity - 1 > units_processed
- last_unit_for_this_price = [total_quantity, vp.starting_quantity - 1].min
- items_count = last_unit_for_this_price - units_processed
- discount += items_count * (current_price - default_price)
- units_processed = last_unit_for_this_price
- end
- break if vp.starting_quantity > total_quantity
- current_price = vp.price
- end
-
- if total_quantity > units_processed
- items_count = total_quantity - units_processed
- discount += items_count * (current_price - default_price)
- end
-
- discount
+ private
+ before_save :check_update_volume_discount
+ def check_update_volume_discount
+ update_volume_discount if price_changed? || quantity_changed?
end
-end
+end unless LineItem.instance_methods.include? :amount_with_volume_discount
@@ -3,24 +3,68 @@ def volume_discount
line_items.map(&:volume_discount).sum
end
+ def products_line_items product
+ line_items.all.select {|i| product.all_variant_ids.include? i.variant_id}
+ end
+
# By default volume price is calculated based only on quantity of the current
# order. If you want to have "Volume Customers" - people who purchase at
# volume prices, but making multiple orders of smaller quantities you can
- # overwrite this method. You could return the total quantity of given variant
+ # overwrite this method. You could return the total quantity of given variants
# the customer bought in the last month. The line items volume price
# calculation will be adjusted by that number. Like this:
# variant_starting_quantity + current order quantity
- def variant_starting_quantity variant
+ def variants_starting_quantity *variant_ids
0
end
+ def update_totals_with_volume_discount
+ # we need to refresh the items
+ line_items true
+ update_totals_without_volume_discount
+ end
+ alias_method_chain :update_totals, :volume_discount
+
# This is required for Volume Customers
# It updates item_total when user logs in
def update_totals_on_user_association
return unless user_id_changed? || email_changed?
- line_items.each {|li| li.update_volume_discount self}
+ products_to_update = []
+
+ line_items.each do |li|
+ if li.variant.requires_per_product_discount?
+ products_to_update << li.variant.product
+ else
+ li.update_volume_discount self
+ li.save
+ end
+ end
+
+ products_to_update.uniq.each {|p| update_product_volume_discount p}
+
update_totals
end
before_save :update_totals_on_user_association
-end
+
+ def update_product_volume_discount product
+ line_items true
+ items = products_line_items(product).sort_by &:variant_id
+
+ return if items.blank?
+
+ starting_quantity = variants_starting_quantity *product.all_variant_ids
+ quantity = items.map(&:quantity).sum
+ volume_cost = product.master.total_volume_cost starting_quantity, quantity
+ regular_cost = items.map(&:amount_without_volume_discount).sum
+ volume_discount = volume_cost - regular_cost
+
+ # only one item stores the discount to avoid division problems
+ items.shift.update_attributes_without_callbacks \
+ :volume_discount => volume_discount
+
+ items.each do |i|
+ i.update_attributes_without_callbacks :volume_discount => 0.0
+ end
+ end
+end unless Order.instance_methods.include? :update_totals_on_user_association
@@ -3,16 +3,28 @@
:volume_prices_attributes=,
:progressive_volume_discount
+ def uses_volume_pricing?
+ if variants_use_master_discount
+ !master.volume_prices.empty?
+ else
+ !Product.where(:id => id).joins(:variants => :volume_prices).empty?
+ end
+ end
+
def save_master
return unless master && (master.changed? || master.new_record? || master.changed_for_autosave?)
raise ActiveRecord::Rollback unless master.save
end
+ def all_variant_ids
+ @all_variant_ids ||= Variant.where(:product_id => id).map &:id
+ end
+
private
def duplicate_extra original
return unless original
self.master.volume_prices = original.master.volume_prices.map do |vp|
VolumePrice.new vp.attributes.slice('starting_quantity', 'price')
end
end
-end
+end unless Product.instance_methods.include? :all_variant_ids
@@ -9,6 +9,34 @@
after_create :copy_master_volume_prices
+ def volume_prices_source
+ if !is_master && product.variants_use_master_discount
+ product.master
+ else
+ self
+ end
+ end
+
+ def uses_volume_pricing?
+ volume_prices_source.volume_prices.present?
+ end
+
+ def uses_master_discount?
+ product.variants_use_master_discount
+ end
+
+ def requires_per_product_discount?
+ uses_master_discount? && product.variants.present?
+ end
+
+ def total_volume_cost starting_quantity, quantity
+ if progressive_volume_discount
+ progressive_total_cost starting_quantity, quantity
+ else
+ uniform_total_cost starting_quantity, quantity
+ end
+ end
+
def blank_volume_price attributes
attributes['starting_quantity'].blank? && attributes['price'].blank?
end
@@ -21,4 +49,40 @@ def copy_master_volume_prices
VolumePrice.new vp.attributes.slice('starting_quantity', 'price')
end
end
-end
+
+ def uniform_total_cost starting_quantity, quantity
+ total_quantity = quantity + starting_quantity
+ final_price = default_price = self.price
+
+ volume_prices.each do |vp|
+ break if vp.starting_quantity > total_quantity
+ final_price = vp.price
+ end
+
+ quantity * final_price
+ end
+
+ def progressive_total_cost units_processed, quantity
+ total_cost = 0
+ total_quantity = quantity + units_processed
+ current_price = default_price = self.price
+
+ volume_prices.each do |vp|
+ if vp.starting_quantity - 1 > units_processed
+ last_unit_for_this_price = [total_quantity, vp.starting_quantity - 1].min
+ items_count = last_unit_for_this_price - units_processed
+ total_cost += items_count * current_price
+ units_processed = last_unit_for_this_price
+ end
+ break if vp.starting_quantity > total_quantity
+ current_price = vp.price
+ end
+
+ if total_quantity > units_processed
+ items_count = total_quantity - units_processed
+ total_cost += items_count * current_price
+ end
+
+ total_cost
+ end
+end unless Variant.instance_methods.include? :volume_prices_source
@@ -0,0 +1,9 @@
+<% if @order.volume_discount != 0.0 %>
+ <tbody id='volume-discount'>
+ <tr class="total" id="volume-discount-row">
+ <td colspan="3"><b><%= t('volume_discount') %>:</b></td>
+ <td class="total"><span><%= number_to_currency @order.volume_discount %></span></td>
+ <td></td>
+ </tr>
+ </tbody>
+<% end %>
@@ -8,12 +8,22 @@
:html => { :method => :put } do |f| %>
<h3><%= t("volume_prices") %></h3>
<style>
- #progressive_discount_info {
+ #progressive_discount_info,
+ #variants_use_master_discount_info {
display: inline-block;
margin-left: 20pt;
font-size: 0.8em;
}
</style>
+ <%= fields_for @product do |pf| %>
+ <p>
+ <%= pf.check_box :variants_use_master_discount %>
+ <%= pf.label :variants_use_master_discount %>
+ <span id="variants_use_master_discount_info">
+ <%= t 'variants_use_master_discount_info' %>
+ </span>
+ </p>
+ <% end %>
<p>
<%= f.check_box :progressive_volume_discount %>
<%= f.label :progressive_volume_discount %>
@@ -1,33 +1,40 @@
-<%= fields_for @variant do |f| %>
- <h3><%= t("volume_prices") %></h3>
- <style>
- #progressive_discount_info {
- display: inline-block;
- margin-left: 20pt;
- font-size: 0.8em;
- }
- </style>
- <p>
- <%= f.check_box :progressive_volume_discount %>
- <%= f.label :progressive_volume_discount %>
- <span id="progressive_discount_info">
- <%= t 'progressive_discount_info' %>
- </span>
- </p>
- <table class="index">
- <thead>
- <tr>
- <th><%= t("starting_from") %></th>
- <th><%= t("price") %></th>
- <th><%= t("action") %></th>
- </tr>
- </thead>
- <tbody id="volume_prices">
- <%= f.fields_for :volume_prices do |vp_form| %>
- <%= render "volume_price_fields", :f => vp_form -%>
- <% end %>
- </tbody>
- </table>
- <%= link_to_add_fields icon('add') + ' ' + t("add_volume_price"), "tbody#volume_prices", f, :volume_prices %>
- <br/><br/>
+<h3><%= t("volume_prices") %></h3>
+<% if @variant.product.variants_use_master_discount %>
+ <script>
+ $('p:has(input[id=variant_price])').hide();
+ </script>
+ <%= t :variant_prices_ignored_info %>
+<% else %>
+ <%= fields_for @variant do |f| %>
+ <style>
+ #progressive_discount_info {
+ display: inline-block;
+ margin-left: 20pt;
+ font-size: 0.8em;
+ }
+ </style>
+ <p>
+ <%= f.check_box :progressive_volume_discount %>
+ <%= f.label :progressive_volume_discount %>
+ <span id="progressive_discount_info">
+ <%= t 'progressive_discount_info' %>
+ </span>
+ </p>
+ <table class="index">
+ <thead>
+ <tr>
+ <th><%= t("starting_from") %></th>
+ <th><%= t("price") %></th>
+ <th><%= t("action") %></th>
+ </tr>
+ </thead>
+ <tbody id="volume_prices">
+ <%= f.fields_for :volume_prices do |vp_form| %>
+ <%= render "volume_price_fields", :f => vp_form -%>
+ <% end %>
+ </tbody>
+ </table>
+ <%= link_to_add_fields icon('add') + ' ' + t("add_volume_price"), "tbody#volume_prices", f, :volume_prices %>
+ <br/><br/>
+ <% end %>
<% end %>
Oops, something went wrong.

0 comments on commit 07fe92e

Please sign in to comment.