Permalink
Browse files

Add ShipmentPacker class to handle packaging items

  • Loading branch information...
1 parent 7ca800d commit 10690671e706399dbcfbe0438cb977d4c0dd8d88 Denis Odorcic committed Nov 14, 2011
Showing with 150 additions and 0 deletions.
  1. +1 −0 lib/active_shipping.rb
  2. +49 −0 lib/active_shipping/shipping/shipment_packer.rb
  3. +100 −0 test/unit/shipment_packer_test.rb
View
@@ -45,5 +45,6 @@
require 'active_shipping/shipping/location'
require 'active_shipping/shipping/rate_estimate'
require 'active_shipping/shipping/shipment_event'
+require 'active_shipping/shipping/shipment_packer'
require 'active_shipping/shipping/carrier'
require 'active_shipping/shipping/carriers'
@@ -0,0 +1,49 @@
+module ActiveMerchant
+ module Shipping
+ class ShipmentPacker
+ class OverweightItem < StandardError
+ end
+
+ # items - array of hashes containing quantity, grams and price.
+ # ex. [{:quantity => 2, :price => 1.0, :grams => 50}]
+ # dimensions - [5.0, 15.0, 30.0]
+ # maximum_weight - maximum weight in grams
+ # currency - ISO currency code
+ def self.pack(items, dimensions, maximum_weight, currency)
+ items.each(&:symbolize_keys!)
+ items = items.collect{ |item| [item] * item[:quantity] }.flatten
+ packages = []
+ state = :package_empty
+
+ while state != :packing_finished
+ case state
+ when :package_empty
+ package_weight, package_value = 0, 0
+ state = :filling_package
+ when :filling_package
+ item = items.shift
+ item_weight, item_price = item[:grams], Package.cents_from(item[:price])
+
+ if item_weight > maximum_weight
+ raise OverweightItem, "The item with weight of #{item_weight}g is heavier than the allowable package weight of #{maximum_weight}g"
+ end
+
+ if (package_weight + item_weight) <= maximum_weight
+ package_weight += item_weight
+ package_value += item_price
+ state = :package_full if items.empty?
+ else
+ items.unshift(item)
+ state = :package_full
+ end
+ when :package_full
+ packages << ActiveMerchant::Shipping::Package.new(package_weight, dimensions, :value => package_value, :currency => currency)
+ state = items.any? ? :package_empty : :packing_finished
+ end
+ end
+
+ packages
+ end
+ end
+ end
+end
@@ -0,0 +1,100 @@
+require 'test_helper'
+
+class ShipmentPackerTest < Test::Unit::TestCase
+ def setup
+ @dimensions = [5.1, 15.2, 30.5]
+ end
+
+ def test_pack_divide_order_into_a_single_package
+ items = [ {:grams => 1, :quantity => 1, :price => 1.0} ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 1, 'USD')
+ assert_equal 1, packages.size
+
+ package = packages.first
+ assert_equal 1, package.weight
+ end
+
+ def test_divide_order_with_multiple_lines_into_a_single_package
+ items = [ {:grams => 1, :quantity => 2, :price => 1.0} ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 2, 'USD')
+ assert_equal 1, packages.size
+
+ package = packages.first
+ assert_equal 2, package.weight
+ end
+
+ def test_divide_order_with_single_line_into_two_packages
+ items = [ {:grams => 1, :quantity => 2, :price => 1.0} ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 1, 'USD')
+ assert_equal 2, packages.size
+
+ packages.each do |package|
+ assert_equal 1, package.weight
+ end
+ end
+
+ def test_divide_order_with_multiple_lines_into_two_packages
+ items = [
+ {:grams => 1, :quantity => 1, :price => 1.0},
+ {:grams => 1, :quantity => 1, :price => 1.0}
+ ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 1, 'USD')
+ assert_equal 2, packages.size
+
+ packages.each do |package|
+ assert_equal 1, package.weight
+ end
+ end
+
+ def test_divide_order_into_two_packages_mixing_line_items
+ items = [
+ {:grams => 1, :quantity => 1, :price => 1.0},
+ {:grams => 1, :quantity => 1, :price => 1.0},
+ {:grams => 1, :quantity => 1, :price => 1.0}
+ ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 2, 'USD')
+ assert_equal 2, packages.size
+
+ assert_equal 2, packages[0].weight
+ assert_equal 1, packages[1].weight
+ end
+
+ def test_raise_overweight_exception_when_a_single_item_exceeds_the_maximum_weight_of_a_package
+ assert_raises(ShipmentPacker::OverweightItem) do
+ items = [{:grams => 2, :quantity => 1, :price => 1.0}]
+ ShipmentPacker.pack(items, @dimensions, 1, 'USD')
+ end
+ end
+
+ def test_add_summarized_prices_for_all_items_and_currency_to_package
+ items = [
+ {:grams => 1, :quantity => 3, :price => 1.0},
+ {:grams => 2, :quantity => 1, :price => 2.0}
+ ]
+ packages = ShipmentPacker.pack(items, @dimensions, 5, 'USD')
+ assert_equal 1, packages.size
+ assert_equal 500, packages.first.value
+ assert_equal 'USD', packages.first.currency
+ end
+
+ def test_divide_items_and_prices_accordingly_when_splitting_into_two_packages
+ items = [
+ {:grams => 1, :quantity => 1, :price => 1.0},
+ {:grams => 1, :quantity => 1, :price => 1.0},
+ {:grams => 1, :quantity => 1, :price => 1.0}
+ ]
+
+ packages = ShipmentPacker.pack(items, @dimensions, 2, 'USD')
+ assert_equal 2, packages.size
+
+ assert_equal 200, packages[0].value
+ assert_equal 100, packages[1].value
+ assert_equal 'USD', packages[0].currency
+ assert_equal 'USD', packages[1].currency
+ end
+end

0 comments on commit 1069067

Please sign in to comment.