Permalink
Browse files

Added support of weight limits for USPS & UPS and splitting order lin…

…e items into separate packages when it exceeds the maximum weight.
  • Loading branch information...
1 parent d1bd602 commit acf54195244dd4064ce0dbaeec5d2374451df656 @romul romul committed Oct 3, 2012
@@ -68,6 +68,12 @@ def timing(line_items)
end
+ protected
+ # weight limit in ounces or zero (if there is no limit)
+ def max_weight_for_country(country)
+ 0
+ end
+
private
def retrieve_rates(origin, destination, packages)
begin
@@ -115,20 +121,65 @@ def retrieve_timings(origin, destination, packages)
end
+
private
-
- # Generates an array of Package objects based on the quantities and weights of the variants in the line items
- def packages(order)
+
+ def convert_order_to_weights_array(order)
multiplier = Spree::ActiveShipping::Config[:unit_multiplier]
default_weight = Spree::ActiveShipping::Config[:default_weight]
+ max_weight = max_weight_for_country(order.ship_address.country)
- weight = order.line_items.inject(0) do |weight, line_item|
+ weights = order.line_items.map do |line_item|
item_weight = line_item.variant.weight.present? ? line_item.variant.weight : default_weight
- weight + (line_item.quantity * item_weight * multiplier)
+ item_weight *= multiplier
+ quantity = line_item.quantity
+ if max_weight <= 0
+ item_weight * quantity
+ else
+ if item_weight < max_weight
+ max_quantity = (max_weight/item_weight).floor
+ if quantity < max_quantity
+ item_weight * quantity
+ else
+ new_items = []
+ while quantity > 0 do
+ new_quantity = [max_quantity, quantity].min
+ new_items << (item_weight * new_quantity)
+ quantity -= new_quantity
+ end
+ new_items
+ end
+ else
+ raise Spree::ShippingError.new("#{I18n.t(:shipping_error)}: The maximum per package weight for the selected service from the selected country is #{max_weight} ounces.")
+ end
+ end
+ end
+ weights.flatten.sort
+ end
+
+ # Generates an array of Package objects based on the quantities and weights of the variants in the line items
+ def packages(order)
+ units = Spree::ActiveShipping::Config[:units].to_sym
+ packages = []
+ weights = convert_order_to_weights_array(order)
+ max_weight = max_weight_for_country(order.ship_address.country)
+
+ if max_weight <= 0
+ packages << Package.new(weights.sum, [], :units => units)
+ else
+ package_weight = 0
+ weights.each do |li_weight|
+ if package_weight + li_weight <= max_weight
+ package_weight += li_weight
+ else
+ packages << Package.new(package_weight, [], :units => units)
+ package_weight = li_weight
+ end
+ end
+ packages << Package.new(package_weight, [], :units => units) if package_weight > 0
end
- package = Package.new(weight, [], :units => Spree::ActiveShipping::Config[:units].to_sym)
- [package]
+ packages
end
def cache_key(order)
@@ -18,6 +18,12 @@ def carrier
ActiveMerchant::Shipping::UPS.new(carrier_details)
end
+
+ protected
+ # weight limit in ounces http://www.ups.com/content/us/en/resources/prepare/oversize.html
+ def max_weight_for_country(country)
+ 150*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
end
end
end
@@ -12,6 +12,12 @@ def carrier
ActiveMerchant::Shipping::USPS.new(carrier_details)
end
+
+ protected
+ # weight limit in ounces or zero (if there is no limit)
+ def max_weight_for_country(country)
+ 70*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
end
end
end
@@ -7,6 +7,12 @@ class PriorityMailInternationalLargeFlatRateBox < Calculator::Usps::Base
def self.description
I18n.t("usps.priority_mail_international_large_flat_rate_box")
end
+
+ protected
+ # weight limit in ounces http://pe.usps.com/text/imm/immc2_011.htm
+ def max_weight_for_country(country)
+ 20*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
end
end
end
@@ -7,6 +7,12 @@ class PriorityMailInternationalMediumFlatRateBox < Calculator::Usps::Base
def self.description
I18n.t("usps.priority_mail_international_medium_flat_rate_box")
end
+
+ protected
+ # weight limit in ounces http://pe.usps.com/text/imm/immc2_011.htm
+ def max_weight_for_country(country)
+ 20*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
end
end
end
@@ -7,6 +7,12 @@ class PriorityMailInternationalSmallFlatRateBox < Calculator::Usps::Base
def self.description
I18n.t("usps.priority_mail_international_small_flat_rate_box")
end
+
+ protected
+ # weight limit in ounces http://pe.usps.com/text/imm/immc2_011.htm
+ def max_weight_for_country(country)
+ 4*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
end
end
end
@@ -0,0 +1,72 @@
+require 'spec_helper'
+include ActiveMerchant::Shipping
+
+module ActiveShipping
+ describe Spree::Calculator do
+
+ let(:country) { mock_model Spree::Country, :iso => "CA", :state_name => "Quebec", :state => nil }
+ let(:address) { mock_model Spree::Address, :country => country, :state_name => country.state_name, :city => "Montreal", :zipcode => "H2B", :state => nil }
+ let(:usa) { mock_model Spree::Country, :iso => "US", :state => mock_model(Spree::State, :abbr => "MD") }
+ let(:us_address) { mock_model Spree::Address, :country => usa, :state => usa.state, :city => "Chevy Chase", :zipcode => "20815" }
+ let(:line_item_1) { mock_model(Spree::LineItem, :variant_id => 1, :quantity => 10, :variant => mock_model(Spree::Variant, :weight => 20.0)) }
+ let(:line_item_2) { mock_model(Spree::LineItem, :variant_id => 2, :quantity => 4, :variant => mock_model(Spree::Variant, :weight => 5.25)) }
+ let(:line_item_3) { mock_model(Spree::LineItem, :variant_id => 3, :quantity => 1, :variant => mock_model(Spree::Variant, :weight => 29.0)) }
+ let(:line_item_4) { mock_model(Spree::LineItem, :variant_id => 4, :quantity => 1, :variant => mock_model(Spree::Variant, :weight => 100.0)) }
+ let(:order) { mock_model Spree::Order, :number => "R12345", :ship_address => address, :line_items => [ line_item_1, line_item_2, line_item_3 ] }
+ let(:us_order) { mock_model Spree::Order, :number => "R12347", :ship_address => us_address, :line_items => [ line_item_1, line_item_2, line_item_3 ] }
+ let(:too_heavy_order) { mock_model Spree::Order, :number => "R12349", :ship_address => us_address, :line_items => [ line_item_3, line_item_4 ] }
+
+
+ let(:international_calculator) { Spree::Calculator::Usps::PriorityMailInternational.new }
+ let(:domestic_calculator) { Spree::Calculator::Usps::PriorityMail.new }
+
+ before(:each) do
+ Rails.cache.clear
+ Spree::ActiveShipping::Config.set(:units => "imperial")
+ Spree::ActiveShipping::Config.set(:unit_multiplier => 16)
+ end
+
+ describe "compute" do
+ context "for international calculators" do
+ it "should convert order line items to weights array for non-US countries" do
+ weights = international_calculator.send :convert_order_to_weights_array, order
+ weights.should == [20.0, 21.0, 29.0, 60.0, 60.0, 60.0].map{|x| x*Spree::ActiveShipping::Config[:unit_multiplier]}
+ end
+
+ it "should create array of packages" do
+ packages = international_calculator.send :packages, order
+ packages.size.should == 4
+ packages.map{|package| package.weight.amount}.should == [70.0, 60.0, 60.0, 60.0].map{|x| x*Spree::ActiveShipping::Config[:unit_multiplier]}
+ packages.map{|package| package.weight.unit}.uniq.should == [:ounces]
+ end
+
+ context "raise exception if max weight exceeded" do
+ it "should get Spree::ShippingError" do
+ expect { international_calculator.compute(too_heavy_order) }.to raise_error(Spree::ShippingError)
+ end
+ end
+ end
+
+ context "for domestic calculators" do
+ it "should not convert order line items to weights array for US" do
+ weights = domestic_calculator.send :convert_order_to_weights_array, us_order
+ weights.should == [20.0, 21.0, 29.0, 60.0, 60.0, 60.0].map{|x| x*Spree::ActiveShipping::Config[:unit_multiplier]}
+ end
+
+ it "should create array with one package for US" do
+ packages = domestic_calculator.send :packages, us_order
+ packages.size.should == 4
+ packages.map{|package| package.weight.amount}.should == [70.0, 60.0, 60.0, 60.0].map{|x| x*Spree::ActiveShipping::Config[:unit_multiplier]}
+ packages.map{|package| package.weight.unit}.uniq.should == [:ounces]
+ end
+ end
+ end
+
+ describe "weight limits" do
+ it "should be set for USPS calculators" do
+ international_calculator.send(:max_weight_for_country, country).should == 70.0*Spree::ActiveShipping::Config[:unit_multiplier]
+ domestic_calculator.send(:max_weight_for_country, country).should == 70.0*Spree::ActiveShipping::Config[:unit_multiplier]
+ end
+ end
+ end
+end

6 comments on commit acf5419

Member

iloveitaly replied Oct 9, 2012

Wow, great commit!

Contributor

jumph4x replied Oct 10, 2012

Totally. We kept experiencing the darn 'MAXIMUM WEIGHT' error from active_merchant in production for a while, finally decided enough is enough.

Contributor

romul replied Oct 10, 2012

Thanks, the algorithm of splitting into packets is not the most optimal, but much better than nothing. :-)

This breaks everything but usps, you did not add weight limits for all the shippers, so all the other shippers have a max weight of 0.

Owner

JDutil replied May 2, 2014

I don't think a change from 2 years ago is what has broken something... Could you please file a new issue with the details in the contributing guidelines.

It has been reported as an issue but not resolved in 2_2_stable. Quick link to the issue: spree#167

Please sign in to comment.