Slide 15
Slide 15 text
module ShippingUtilities
class ShippingException < Exception; end
# Struct classes
ShippingGroup = Struct.new( :order_line_items, :vendor, :shipping_cost, :applied_shipping_allowance ) unless const_defined?(:ShippingGroup)
ShippingDisplayOption = Struct.new( :value, :text ) unless const_defined?(:ShippingDisplayOption)
CHOOSE_COUNTRY_FIRST = [
ShippingDisplayOption.new( "", "Please choose destination country first" )
] unless const_defined?("CHOOSE_COUNTRY_FIRST")
# PO Box or Domestic
FCM_DOMESTIC_SHIPPING_OPTION = [
ShippingDisplayOption.new( "usps_fcm", "USPS First Class Mail")
] unless const_defined?("FCM_DOMESTIC_SHIPPING_OPTION")
FREE_LIGHTWEIGHT_MAIL_OPTION = [
ShippingDisplayOption.new( "free_usps_fcm", "USPS First Class Mail - FREE!")
] unless const_defined?("DISCOUNT_LIGHTWEIGHT_MAIL_OPTION")
# Domestic
DOMESTIC_COMMON_SHIPPING_OPTIONS = [
ShippingDisplayOption.new( "next_business_day", "Next Business Day" ),
ShippingDisplayOption.new( "two_day", "Second Business Day" ),
ShippingDisplayOption.new( "three_day", "Third Business Day" )
] unless const_defined?("DOMESTIC_COMMON_SHIPPING_OPTIONS")
STANDARD_GROUND_OPTION = [
ShippingDisplayOption.new( "ground", "Standard Ground" )
] unless const_defined?("STANDARD_GROUND_OPTION")
FREE_GROUND_OPTION = [
ShippingDisplayOption.new( "free_ground", "Standard Ground - FREE!" )
] unless const_defined?("FREE_GROUND_OPTION")
STANDARD_MAIL_OPTION = [
ShippingDisplayOption.new( "usps_domestic", "USPS Priority Mail")
] unless const_defined?("STANDARD_MAIL_OPTION")
DISCOUNT_MAIL_OPTION = [
ShippingDisplayOption.new( "discounted_usps_domestic", "USPS Priority Mail - DISCOUNTED!")
] unless const_defined?("DISCOUNT_MAIL_OPTION")
# LOWER 48
FS_PROMO_DOMESTIC_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
FREE_GROUND_OPTION
FS_PROMO_LIGHTWEIGHT_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
STANDARD_GROUND_OPTION +
FREE_LIGHTWEIGHT_MAIL_OPTION
STANDARD_DOMESTIC_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
STANDARD_GROUND_OPTION
LIGHTWEIGHT_DOMESTIC_SHIPPING_OPTIONS =
STANDARD_DOMESTIC_SHIPPING_OPTIONS +
FCM_DOMESTIC_SHIPPING_OPTION
PO_BOX_DOMESTIC_SHIPPING_OPTIONS =
STANDARD_MAIL_OPTION
LIGHTWEIGHT_PO_BOX_DOMESTIC_SHIPPING_OPTIONS =
STANDARD_MAIL_OPTION +
FCM_DOMESTIC_SHIPPING_OPTION
# AK/HI
FS_PROMO_AK_HI_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
STANDARD_GROUND_OPTION +
DISCOUNT_MAIL_OPTION
FS_PROMO_LIGHTWEIGHT_AK_HI_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
STANDARD_GROUND_OPTION +
STANDARD_MAIL_OPTION +
FREE_LIGHTWEIGHT_MAIL_OPTION
STANDARD_AK_HI_SHIPPING_OPTIONS =
DOMESTIC_COMMON_SHIPPING_OPTIONS +
STANDARD_GROUND_OPTION +
STANDARD_MAIL_OPTION
LIGHTWEIGHT_AK_HI_SHIPPING_OPTIONS =
STANDARD_AK_HI_SHIPPING_OPTIONS +
FCM_DOMESTIC_SHIPPING_OPTION
# TERRITORIES
FS_PROMO_TERRITORIES_SHIPPING_OPTIONS =
DISCOUNT_MAIL_OPTION
FS_PROMO_LIGHTWEIGHT_TERRITORIES_SHIPPING_OPTIONS =
STANDARD_MAIL_OPTION +
FREE_LIGHTWEIGHT_MAIL_OPTION
STANDARD_TERRITORIES_SHIPPING_OPTIONS =
STANDARD_MAIL_OPTION
LIGHTWEIGHT_TERRITORIES_SHIPPING_OPTIONS =
STANDARD_MAIL_OPTION +
FCM_DOMESTIC_SHIPPING_OPTION
# International
COMMON_INTERNATIONAL_SHIPPING_OPTIONS = [
ShippingDisplayOption.new( "ups_express", "UPS Worldwide Express [1-3 days]" ),
ShippingDisplayOption.new( "ups_expedited", "UPS Worldwide Expedited [2-5 days]" ),
ShippingDisplayOption.new( "usps_international", "USPS Express Mail International")
] unless const_defined?("INTERNATIONAL_SHIPPING_OPTIONS")
DISCOUNTED_INTERNATIONAL_PMI_OPTION = [
ShippingDisplayOption.new( "discounted_usps_int_priority", "USPS Priority Mail International - DISCOUNTED!")
] unless const_defined?("DISCOUNTED_INTERNATIONAL_PMI_OPTION")
STANDARD_INTERNATIONAL_PMI_OPTION = [
ShippingDisplayOption.new( "usps_int_priority", "USPS Priority Mail International")
] unless const_defined?("STANDARD_INTERNATIONAL_PMI_OPTION")
FS_PROMO_INTERNATIONAL_SHIPPING_OPTIONS =
COMMON_INTERNATIONAL_SHIPPING_OPTIONS +
DISCOUNTED_INTERNATIONAL_PMI_OPTION
STANDARD_INTERNATIONAL_SHIPPING_OPTIONS =
COMMON_INTERNATIONAL_SHIPPING_OPTIONS +
STANDARD_INTERNATIONAL_PMI_OPTION
ALL_SHIPPING_OPTIONS = DOMESTIC_COMMON_SHIPPING_OPTIONS + FREE_GROUND_OPTION + STANDARD_GROUND_OPTION + COMMON_INTERNATIONAL_SHIPPING_OPTIONS + DISCOUNTED_INTERNATIONAL_PMI_OPTION +
STANDARD_INTERNATIONAL_PMI_OPTION + FCM_DOMESTIC_SHIPPING_OPTION + FREE_LIGHTWEIGHT_MAIL_OPTION + DISCOUNT_MAIL_OPTION + STANDARD_MAIL_OPTION unless const_defined?("ALL_SHIPPING_OPTIONS")
INTERNATIONAL_SHIPPING_METHODS = [
ShippingMethod.new( :shipping_option => :usps_int_priority, :api => :usps, :name => "USPS Priority Mail International", :mail_class => "PriorityMailInternational", :multiplier => 1.45),
ShippingMethod.new( :shipping_option => :usps_international, :api => :usps, :name => "USPS Express Mail International", :mail_class => "ExpressMailInternational", :multiplier => 1.45),
ShippingMethod.new( :shipping_option => :ups_express, :api => :ups, :name => "UPS Worldwide Express", :service_code => "07", :multiplier => 0.7),
ShippingMethod.new( :shipping_option => :ups_expedited, :api => :ups, :name => "UPS Worldwide Expedited", :service_code => "08", :multiplier => 0.7)
] unless const_defined?("INTERNATIONAL_SHIPPING_METHODS")
DOMESTIC_FEDEX_SHIPPING_METHODS = [
ShippingMethod.new( :shipping_option => :ground, :api => :fedex, :name => "FedEx Ground", :transaction_type => "rate_ground", :service_type => "ground_service", :multiplier => 1.4 ),
ShippingMethod.new( :shipping_option => :ground_res, :api => :fedex, :name => "FedEx Home Delivery", :transaction_type => "rate_ground", :service_type => "home_delivery", :multiplier => 1.4),
ShippingMethod.new( :shipping_option => :three_day, :api => :fedex, :name => "FedEx Express Saver (3 Day)", :transaction_type => "rate_express", :service_type => "express_saver", :multiplier => 1.25 ),
ShippingMethod.new( :shipping_option => :two_day, :api => :fedex, :name => "FedEx 2 Day", :transaction_type => "rate_express", :service_type => "2day", :multiplier => 1.25 ),
ShippingMethod.new( :shipping_option => :next_business_day, :api => :fedex, :name => "FedEx Standard Overnight", :transaction_type => "rate_express", :service_type => "standard_overnight", :multiplier => 1.25 )
] unless const_defined?("DOMESTIC_FEDEX_SHIPPING_METHODS")
DOMESTIC_UPS_SHIPPING_METHODS = [
ShippingMethod.new( :shipping_option => :ground, :api => :ups, :name => "UPS Ground", :service_code => "03"),
ShippingMethod.new( :shipping_option => :three_day, :api => :ups, :name => "UPS 3-Day Select", :service_code => "12", :multiplier => 1.1),
ShippingMethod.new( :shipping_option => :two_day, :api => :ups, :name => "UPS 2nd Day Air", :service_code => "02", :multiplier => 1.1),
ShippingMethod.new( :shipping_option => :next_business_day, :api => :ups, :name => "UPS Next Day Air", :service_code => "01", :multiplier => 1.1)
] unless const_defined?("DOMESTIC_UPS_SHIPPING_METHODS")
DOMESTIC_USPS_SHIPPING_METHOD = [
ShippingMethod.new( :shipping_option => :upsmi_domestic, :api => :upsmi, :name => "UPS Mail Innovations", :service => "domestic"),
ShippingMethod.new( :shipping_option => :usps_fcm, :api => :usps, :name => "USPS First Class Mail", :mail_class => "FIRST"),
ShippingMethod.new( :shipping_option => :usps_domestic, :api => :usps, :name => "USPS Priority Mail", :mail_class => "PRIORITY")
] unless const_defined?("DOMESTIC_USPS_SHIPPING_METHOD")
ALL_SHIPPING_METHODS = INTERNATIONAL_SHIPPING_METHODS + DOMESTIC_FEDEX_SHIPPING_METHODS + DOMESTIC_UPS_SHIPPING_METHODS + DOMESTIC_USPS_SHIPPING_METHOD
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Lookup the ShippingMethod object based on the shipping code passed into
# the method here.
#
# Note that shipping_code will be one of the symbols based on the
# :shipping_option => 'XXXXX' in the above declared ShippingMethod instances.
#
# Therefore shipping_code will be something like:
# "ground", "three_day", "two_day", "next_business_day", "usps_domestic", etc.
#
#
# shipping_code will be a STRING... NOT A SYMBOL!
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def lookup_shipping_method( shipping_code, api_override = false )
# Try domestic shipping first. For domestic we need to match based on the
# shipping code ***AND*** the currently configured shipping API - which
# will be either Fedex or UPS
#RAILS_DEFAULT_LOGGER.debug "Looking up shipping method based on this code: #{shipping_code}."
# Check to see what the currently configured shipping method is - Set by an administrator
# in the admin interface. The only valid values are "ups" and "fedex"
if api_override
api = api_override
else
# Default shipping method is UPS
api = "ups"
end
case api
when "ups"
shipping_method = get_shipping_method_from_array( shipping_code, DOMESTIC_UPS_SHIPPING_METHODS)
when "fedex"
shipping_method = get_shipping_method_from_array( shipping_code, DOMESTIC_FEDEX_SHIPPING_METHODS)
end
# ...if it turns out that we found an appropriate UPS or Fedex method from above - return it...
return shipping_method unless shipping_method.nil?
# ...otherwise lets see if it is a USPS domestic
if ["usps_domestic", "upsmi_domestic", "usps_fcm"].include? shipping_code
return get_shipping_method_from_array( shipping_code, DOMESTIC_USPS_SHIPPING_METHOD )
end
# ... otherwise it must be]an international
return get_shipping_method_from_array( shipping_code, INTERNATIONAL_SHIPPING_METHODS)
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Get the generic shipping method currently selected
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def lookup_generic_shipping_method( shipping_code )
ALL_SHIPPING_OPTIONS.each do |opt|
if opt.value == shipping_code
return opt
end
end
return nil
end
def lookup_generic_shipping_method_by_name( method_name )
ALL_SHIPPING_METHODS.each do |opt|
if opt.sm_params[:name] == method_name
return opt.sm_params[:shipping_option]
end
end
return nil
end
def lookup_shipping_method_by_name( method_name )
ALL_SHIPPING_METHODS.each do |opt|
if opt.sm_params[:name] == method_name
return opt
end
end
return nil
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Get the total shipping cost for the order.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def get_shipping_cost_for_order( all_items,
receiver_zip,
receiver_country,
in_stock_shipping_method_code,
out_of_stock_shipping_method_code,
consolidate = false )
raise Exception.new( "ShippingUtilities::get_shipping_cost_for_order is deprecated! Please use new ShippingUtilities::build_shipping_groups instead." )
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Determine the shipping weight for some items
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def shipping_weight_for_items(order_line_items)
weight = 0.0
order_line_items.each { |order_line_item|
unless order_line_item.item.shipping_weight.nil? or order_line_item.item.shipping_weight == 0
weight += (order_line_item.quantity * order_line_item.item.shipping_weight)
else
weight += (order_line_item.quantity * 5)
end
}
return weight
end
private
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Get a shipping method from a narrowed down array of options
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def get_shipping_method_from_array( code, methods )
#RAILS_DEFAULT_LOGGER.debug "Code is: #{code}. Methods are: #{methods.inspect}"
methods.each do |shipping_method|
#RAILS_DEFAULT_LOGGER.debug "Shipping Method Compare: #{shipping_method.sm_params[:shipping_option]}"
if shipping_method.sm_params[:shipping_option].to_s == code
#RAILS_DEFAULT_LOGGER.debug "Returning Shipping Method: #{shipping_method.inspect}"
return shipping_method
end
end
return nil
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Technically the shipping utilities shouldn't have any awareness of what store
# to bill stuff to - but given that we need some "special" logic to determine
# what stores shipping credentials to use depending on what items are in the cart
# this seems like the best option.
#
# If the cart has any NEML products - then use NEML
# If the card is mixed up of NEMX and NEHP only - then use NEMX
# Otherwise use whatever store that all products are from
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def determine_store_for_shipping( all_items )
return Store::NEML if all_items.length < 1
stores = Store.stores_for_items( all_items.collect{ |order_line_item| order_line_item.item || order_line_item.used_item } )
if stores.length == 1
return stores.values.first
else
if stores[Store::NEML] != nil
return stores[Store::NEML]
else
return stores[Store::NEHP]
end
end
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Calculate the shipping costs for a group of products.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def calculate_shipping_costs_for_items( store, order_line_items, sender_zip, receiver_zip, receiver_country, shipping_method_code, consolidate = false )
# Before doing anything, check the validity of the zip code if we're shipping to the US.
# Raise an exception if it's bunk.
if receiver_country == "US"
ShippingCalculatorBase.state_from_zip( receiver_zip )
end
shipping_weight = shipping_weight_for_items( order_line_items )
ship_symbol = consolidate ? :order_in : order_line_items[0].item.shipping_status_symbol
# Prevent exception if sender_zip is not defined (eg. zip not in Vendor table)
if sender_zip.nil?
sender_zip = 79363
end
RAILS_DEFAULT_LOGGER.info "Shipping symbol - #{ship_symbol}"
if ship_symbol == :drop_ship
api_override = "ups"
else
api_override = false
end
RAILS_DEFAULT_LOGGER.info "Method: #{shipping_method_code}, Override: #{api_override}, Store: #{store.id}"
shipping_method = lookup_shipping_method( shipping_method_code, api_override )
RAILS_DEFAULT_LOGGER.info "We found: #{shipping_method.inspect}"
ship_api = shipping_method.sm_params[:api]
# If we don't have an explicit multiplier, we want to use the price
# returned from the API as is (so mult by 1.0)
# DEPRECATED - NEW CODE BELOW!
# shipping_method.sm_params[:multiplier] ||= 1.0
mult = Multiplier.find(:first, :conditions => ["store_id = ? and service = ?", store.id, shipping_method_code], :select => "ds, wh").send((ship_symbol == :drop_ship && receiver_country == "US") ? "ds" : "wh")
min = store.send(ship_symbol == :drop_ship ? "ds_min" : "wh_min")
ship_params = Hash.new
ship_params[:shipping_weight] = shipping_weight
ship_params[:sender_zip] = sender_zip
ship_params[:receiver_zip] = receiver_zip
ship_params[:receiver_country] = receiver_country
case ship_api
when :fedex
ship_params[:fedex_account] = store.fedex_account
ship_params[:fedex_url] = NewEnough::FEDEX_URL
ship_params[:fedex_meter] = store.fedex_meter
ship_params[:service_type] = shipping_method.sm_params[:service_type]
ship_params[:transaction_type] = shipping_method.sm_params[:transaction_type]
amt = (FedexCalculator.new.price( ship_params ) * mult).prec(2)
when :ups
ship_params[:ups_user] = NewEnough::UPS_USER
ship_params[:ups_pass] = NewEnough::UPS_PASS
ship_params[:ups_license] = NewEnough::UPS_LICENSE
ship_params[:sender_country] = store.country
ship_params[:sender_phone] = store.phone_number
ship_params[:sender_name] = store.name
ship_params[:receiver_country] = receiver_country
ship_params[:shipping_method] = shipping_method.sm_params[:service_code]
amt = (UpsCalculator.new.price( ship_params ) * mult).prec(2)
when :usps
ship_params[:mail_class] = shipping_method.sm_params[:mail_class]
ship_params[:partner_id] = NewEnough::ENDICIA_PARTNER_ID
ship_params[:account_id] = NewEnough::ENDICIA_ACCOUNT_ID
ship_params[:pass_phrase] = NewEnough::ENDICIA_PASS_PHRASE
amt = (UspsCalculator.new.price( ship_params ) * mult).prec(2)
when :upsmi
ship_params[:service] = shipping_method.sm_params[:service]
amt = (UpsmiCalculator.new.price( ship_params ) * mult).prec(2)
end
return amt > min ? amt : min
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Determine the groups in which items will be shipped. This is necessary for
# non-consolidated shipments when some items are out of stock or are being
# drop-shipped from a vendors warehouse.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def build_shipments( order_line_items, consolidate=false )
shipments = []
unless consolidate
# Build a hash of line items with associated data we can use without having to re-query the database for all the iterations.
line_items = []
order_line_items.each do |order_line_item|
item = order_line_item.item_id ? Item.find(order_line_item.item_id, :include => :product) : UsedItem.find(order_line_item.used_item_id)
RAILS_DEFAULT_LOGGER.info("Build shipments found item: #{item.inspect} from store #{item.store_id}")
ship_status_symbol = item.shipping_status_symbol_for_quantity(order_line_item.quantity)
ship_status_symbol = :in_stock if [:closeout_instock,:from_stock_only_instock].include?(ship_status_symbol)
line_items << OpenStruct.new( :ship_status_symbol => ship_status_symbol,
:store_id => item.instance_of?(Item) ? item.product.store_id : 1,
:vendor_id => item.instance_of?(Item) ? item.product.vendor_id : nil,
:line_item => order_line_item,
:drop_shippable => item.drop_shippable?,
:consolidatable => false)
end
# MAGIC GOES HERE TO DO THE CONSOLIDATION
line_items_by_sym = {}
[:in_stock,:drop_ship,:order_in].each do |ship_sym|
line_items_by_sym[ship_sym] = line_items.select{|li| li.ship_status_symbol == ship_sym}
end
if line_items_by_sym[:in_stock].length > 0 && line_items_by_sym[:drop_ship].length > 0
RAILS_DEFAULT_LOGGER.debug("Attempting to consolidate in_stocks and drop_ships.")
# If we've got us some in_stocks and some drop_ships, let's see if
# we can do some consolidating. It only makes sense to consolidate if we can completely
# get rid of in_stocks.
line_items_by_sym[:in_stock].each do |li|
matching_ds = line_items_by_sym[:drop_ship].count{|dsli| dsli.vendor_id == li.vendor_id} > 0
li.consolidatable = (matching_ds && li.drop_shippable)
end
if line_items_by_sym[:in_stock].count{|li| !li.consolidatable} < 1
RAILS_DEFAULT_LOGGER.debug("Consolidating in_stocks and drop_ships.")
#Woo-hoo! Let's consolidate
line_items.each{|li| li.ship_status_symbol = :drop_ship if li.ship_status_symbol == :in_stock}
end
end
line_items.each do |li|
create_group_if_necessary_and_insert( shipments, li.ship_status_symbol, li.store_id,
li.ship_status_symbol == :drop_ship ? li.vendor_id : li.store_id, li.line_item )
end
else
order_line_items.each do |order_line_item|
item = order_line_item.item_id ? Item.find(order_line_item.item_id, :include => :product) : UsedItem.find(order_line_item.used_item_id)
create_group_if_necessary_and_insert( shipments, :consolidated, item.store_id, item.store_id, order_line_item)
end
end
set_consolidate_flags( shipments )
RAILS_DEFAULT_LOGGER.info "====================================================="
RAILS_DEFAULT_LOGGER.info "Shipments"
RAILS_DEFAULT_LOGGER.info "====================================================="
RAILS_DEFAULT_LOGGER.info "#{shipments.inspect}"
RAILS_DEFAULT_LOGGER.info "====================================================="
return shipments
end
def set_consolidate_flags( shipments )
#RAILS_DEFAULT_LOGGER.debug shipments.to_yaml
# Now let's set appropriate consolidate flags on the shipments that are to be shipped together (NEML & NEHP)
in_stock = []
order_in = []
consol = []
shipments.each do |s|
if s.shipment_type == Shipment::IN_STOCK
in_stock << s
elsif s.shipment_type == Shipment::ORDER_IN
order_in << s
elsif s.shipment_type == Shipment::CONSOLIDATED
consol << s
end
end
# NEMX_TODO - Make this more robust if we need to combine with NEMX in the future.
groups = [in_stock, order_in, consol]
groups.each do |g|
#RAILS_DEFAULT_LOGGER.debug "================================================================"
#RAILS_DEFAULT_LOGGER.debug "Group #{g.inspect}"
#RAILS_DEFAULT_LOGGER.debug "================================================================"
if g.length == 2
# We can assume here that shipper_id == store_id always because a drop-ship from one store
# will never be combined with a drop-ship from another store. If shipment was drop-shipped
# shipper_id == vendor_id.
if g[1].shipper_id == 1
g[0].combine_flag = "combine"
g[1].combine_flag = "has_combined_shipment"
g[1].combine_with_shipment = g[0]
else
g[1].combine_flag = "combine"
g[0].combine_flag = "has_combined_shipment"
g[0].combine_with_shipment = g[1]
end
end
end
end
def create_group_if_necessary_and_insert( shipments, key, store_id, shipper_id, order_line_item)
found = false
shipments.each do |shipment|
if shipment.shipment_type == shipment.shipment_type_from_symbol( key ) && shipment.shipper_id == shipper_id && shipment.store_id == store_id
shipment.line_items << order_line_item
found = true
end
end
if found == false
shipment = Shipment.new
shipment.shipment_type = shipment.shipment_type_from_symbol( key )
shipment.shipper_id = shipper_id
shipment.store_id = store_id
shipment.line_items << order_line_item
shipments << shipment
end
end
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# If shipments will be combined, consolidate them for the public view
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def build_shipping_groups( shipments, ship_method, customer_zip, customer_country, consolidate = false )
RAILS_DEFAULT_LOGGER.debug "In build_shipping_groups, ship_method: #{ship_method}"
shipping_groups = {}
shipments.each do |shipment|
unless shipment.combine_flag == "combine"
@group_line_items = []
shipment.line_items.each {|li| @group_line_items << li }
if !shipment.combine_with_shipment.nil?
shipment.combine_with_shipment.line_items.each {|cli| @group_line_items << cli }
end
ship_symbol = consolidate ? :order_in : @group_line_items[0].item.shipping_status_symbol
shipping_weight = shipping_weight_for_items( @group_line_items )
if ship_method == 'free_ground' && !shipment.has_oversized_item?
if ship_symbol == :drop_ship
shipment.shipping_method = 'ground'
RAILS_DEFAULT_LOGGER.debug("Drop Ship")
elsif shipping_weight <= 5.0
# This is where the UPS Basic selection will kick in once we're approved.
# TODO: UPS Basic
shipment.shipping_method = 'ground'
RAILS_DEFAULT_LOGGER.debug("< 5#")
else
shipment.shipping_method = 'ground'
RAILS_DEFAULT_LOGGER.debug("Normal")
end
elsif ship_method == 'free_ground' && shipment.has_oversized_item?
shipment.shipping_method = 'ground'
elsif ship_method == 'discounted_usps_domestic'
shipment.shipping_method = 'usps_domestic'
elsif ship_method == 'discounted_usps_int_priority'
shipment.shipping_method = 'usps_int_priority'
elsif ship_method == 'free_usps_fcm'
shipment.shipping_method = 'usps_fcm'
else
shipment.shipping_method = ship_method
end
shipment.shipping_cost = calculate_shipping_costs_for_items( determine_store_for_shipping(@group_line_items), @group_line_items, shipment.ships_from.zip_code, customer_zip, customer_country, shipment.shipping_method, consolidate)
if ship_method == 'free_ground'
shipment.applied_shipping_allowance = shipment.has_oversized_item? ? 0.0 : shipment.shipping_cost
elsif ship_method == 'free_usps_fcm'
shipment.applied_shipping_allowance = shipment.shipping_cost
elsif ['discounted_usps_int_priority', 'discounted_usps_domestic'].include?(ship_method)
shipment.applied_shipping_allowance = equivalent_ship_discount_for_weight( shipping_weight )
shipment.applied_shipping_allowance = shipment.shipping_cost if shipment.applied_shipping_allowance > shipment.shipping_cost
else
shipment.applied_shipping_allowance = 0.0
end
if shipment.shipment_type == Shipment::DROP_SHIP
vendor = shipment.ships_from
key = "#{vendor.id}-#{shipment.store_id}"
else
vendor = nil
key = shipment.shipment_type_symbol
end
add_key_if_necessary_and_insert( shipping_groups, key, shipment, @group_line_items, vendor)
end # unless
end
RAILS_DEFAULT_LOGGER.info "====================================================="
RAILS_DEFAULT_LOGGER.info "Shipping Groups"
RAILS_DEFAULT_LOGGER.info "====================================================="
RAILS_DEFAULT_LOGGER.info "#{shipping_groups.inspect}"
RAILS_DEFAULT_LOGGER.info "====================================================="
return shipping_groups
end
def add_key_if_necessary_and_insert( shipping_groups, key, shipment, order_line_items, vendor )
if shipping_groups[ key ].nil?
shipping_groups[key] = ShippingGroup.new( [], vendor, shipment.shipping_cost, shipment.applied_shipping_allowance )
end
order_line_items.each do |item|
shipping_groups[key].order_line_items << item
end
end
def equivalent_ship_discount_for_weight( weight_in_pounds )
# Base of $6.53 covers shipments up to 1 lb.
# We need to figure out rounded-up weight over 1 lb.
additional_poundage = weight_in_pounds - 1
ship_discount = 6.53 + (additional_poundage * 0.29)
# Now we add in the ground multiplier so the rate is realistic
# if someone plays looky-loo with our domestic rates
mult = Multiplier.find(:first, :conditions => {:store_id => 1, :service => 'ground'}).wh
return (ship_discount * mult).prec(2)
end
end