Created
June 6, 2010 06:34
-
-
Save psy-q/427373 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ShippingRate < ActiveRecord::Base | |
has_many :shipping_costs | |
has_many :suppliers | |
validates_presence_of :name | |
# Input: Weight in grams | |
# Output: Price for delivery according to associated ShippingCosts | |
def calculate_for_weight(weight) | |
@total_cost = 0 | |
@remaining_weight ||= weight # shouldn't really be an instance variable, but scope seems weird below otherwise? | |
while @remaining_weight > 0.to_f | |
# The weight is too high to fit into one package, we pick this supplier's highest-weight | |
# shipping rate, reduce the remaining weight and continue with the rest of the weight (this splits | |
# orders into multiple packages) | |
if @remaining_weight > self.maximum_weight | |
@remaining_weight = (@remaining_weight - self.maximum_weight) | |
@total_cost += self.shipping_costs.select{|s| s.weight_max == self.maximum_weight}.first.price | |
add_taxes(self.shipping_costs.select{|s| s.weight_max == self.maximum_weight}.first.taxes) | |
else | |
shipping_cost = self.shipping_costs.select{ |sc| @remaining_weight.between?(sc.weight_min, sc.weight_max) } | |
if shipping_cost.blank? | |
# No shipping cost was found that matches the weight of this order | |
# so we assume the order is below the lowest weight and pick the first | |
# shipping cost of this supplier | |
@total_cost += self.shipping_costs.first.net_price | |
add_taxes(self.shipping_costs.first.taxes) | |
@remaining_weight -= @remaining_weight | |
else | |
# A match was found, the weight is between two available shipping rates (shipping_cost.first is this rate) | |
@total_cost += shipping_cost.first.net_price | |
add_taxes(shipping_cost.first.taxes) | |
@remaining_weight -= @remaining_weight | |
end | |
end | |
end | |
return BigDecimal(@total_cost.to_s) | |
end | |
# Input: Weight in grams | |
# Output: Integer representing the number of packages necessary to ship | |
# items as heavy as the input weight. | |
def package_count_for_weight(weight) | |
(weight / self.maximum_weight).ceil | |
end | |
# Calculates incoming shipping cost for all the items in a document. Takes into account | |
# whether they ship directly from manufacturer to customer or whether they have to be | |
# shipped twice, once to a workshop and from there to the customer | |
def calculate_incoming(document) | |
@total_weight = 0 | |
@incoming_cost = 0 | |
@incoming_package_count = 0 | |
# On direct shipping orders, incoming cost is always 0, we can skip expensive | |
# calculations on this document. | |
unless document.direct_shipping? | |
# We do incoming shipping calculation on a flattened set of all products | |
# necessary for this order. That includes incoming supply items _and_ complete | |
# products that we order and ship directly to the customer | |
supplier_ids = document.existing_incoming_supplier_ids | |
supplier_ids.each do |sup_id| | |
this_suppliers_weight = 0 | |
this_suppliers_products_and_supply_items = document.incoming_products_and_supply_items.select{ |p| p.supplier_id == sup_id } | |
this_suppliers_products_and_supply_items.each do |p| | |
this_things_weight = document.quantity_of(p) * p.weight | |
this_suppliers_weight += this_things_weight | |
@total_weight += this_things_weight | |
end | |
@incoming_cost += Supplier.find(sup_id).shipping_rate.calculate_for_weight(this_suppliers_weight * 1000) | |
@incoming_package_count += Supplier.find(sup_id).shipping_rate.package_count_for_weight(document.weight * 1000) | |
end | |
end | |
end | |
def calculate_outgoing(document) | |
if document.direct_shipping? | |
# This is safe because Document#direct_shipping? checks to make sure there is only one | |
# supplier. | |
sr = ShippingRate.find(document.suppliers.first.shipping_rate.id) | |
else | |
# We set up an internal shipping rate that is used for outgoing | |
# calculations from our own store. This should be replaced by a configuration | |
# or a database setting that guarantees that an outgoing shipping rate is _always_ | |
# available. The database-based one would be more configurable than this, | |
# but in the first phases, we don't want to make the software dependent | |
# on specific database entries. | |
sr = ShippingRate.new | |
# Source: http://www.post.ch/post-startseite/post-privatkunden/post-versenden/post-pakete-inland/post-pakete-inland-preise.htm | |
# TODO: Extract to default shipping rate object, change prices to gross (excluding VAT) | |
sr.shipping_costs << ShippingCost.new(:weight_min => 0, :weight_max => 2000, :price => 8.0) | |
sr.shipping_costs << ShippingCost.new(:weight_min => 2001, :weight_max => 5000, :price => 10.0) | |
sr.shipping_costs << ShippingCost.new(:weight_min => 5001, :weight_max => 10000, :price => 13.0) | |
sr.shipping_costs << ShippingCost.new(:weight_min => 10001, :weight_max => 20000, :price => 19.0) | |
sr.shipping_costs << ShippingCost.new(:weight_min => 20001, :weight_max => 30000, :price => 26.0) | |
# This default tax class for shipping would need to be made configurable in the | |
# final system, especially if it is going to be released as Free Software. | |
sr.shipping_costs.each do |sc| | |
sc.tax_class = TaxClass.find_by_percentage("7.6") | |
end | |
end | |
@outgoing_cost = sr.calculate_for_weight(document.weight * 1000) | |
@outgoing_package_count = sr.package_count_for_weight(document.weight * 1000) | |
end | |
# Can calulate shipping for both orders or carts (= documents) | |
def calculate_for(document) | |
calculate_incoming(document) | |
puts "randomly outputting total taxes after inc: #{total_taxes}" | |
calculate_outgoing(document) | |
puts "randomly outputting total taxes after out: #{total_taxes}" | |
end | |
def incoming_cost | |
@incoming_cost or BigDecimal.new("0.0") | |
end | |
def outgoing_cost | |
@outgoing_cost or BigDecimal.new("0.0") | |
end | |
def incoming_package_count | |
@incoming_package_count or 0 | |
end | |
def outgoing_package_count | |
@outgoing_package_count or 0 | |
end | |
def total_cost | |
incoming_cost + outgoing_cost | |
end | |
def add_taxes(amount) | |
@total_taxes ||= 0 | |
@total_taxes += amount | |
puts "***********************************************" | |
puts "added #{amount} to total taxes: #{@total_taxes}" | |
puts "***********************************************" | |
end | |
def total_taxes | |
@total_taxes or BigDecimal.new("0.0") | |
end | |
# Maximum weight carried by this ShippingRate's ShippingCosts, in grams | |
def maximum_weight | |
self.shipping_costs.collect(&:weight_max).max | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment