active_shipping 0.12.6 → 1.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -0
- data.tar.gz.sig +0 -0
- data/{CHANGELOG → CHANGELOG.md} +6 -2
- data/CONTRIBUTING.md +32 -0
- data/{README.markdown → README.md} +45 -61
- data/lib/active_shipping.rb +20 -28
- data/lib/active_shipping/carrier.rb +82 -0
- data/lib/active_shipping/carriers.rb +33 -0
- data/lib/active_shipping/carriers/benchmark_carrier.rb +31 -0
- data/lib/active_shipping/carriers/bogus_carrier.rb +12 -0
- data/lib/active_shipping/carriers/canada_post.rb +253 -0
- data/lib/active_shipping/carriers/canada_post_pws.rb +870 -0
- data/lib/active_shipping/carriers/fedex.rb +579 -0
- data/lib/active_shipping/carriers/kunaki.rb +164 -0
- data/lib/active_shipping/carriers/new_zealand_post.rb +262 -0
- data/lib/active_shipping/carriers/shipwire.rb +181 -0
- data/lib/active_shipping/carriers/stamps.rb +861 -0
- data/lib/active_shipping/carriers/ups.rb +648 -0
- data/lib/active_shipping/carriers/usps.rb +642 -0
- data/lib/active_shipping/errors.rb +7 -0
- data/lib/active_shipping/label_response.rb +23 -0
- data/lib/active_shipping/location.rb +149 -0
- data/lib/active_shipping/package.rb +241 -0
- data/lib/active_shipping/rate_estimate.rb +64 -0
- data/lib/active_shipping/rate_response.rb +13 -0
- data/lib/active_shipping/response.rb +41 -0
- data/lib/active_shipping/shipment_event.rb +17 -0
- data/lib/active_shipping/shipment_packer.rb +73 -0
- data/lib/active_shipping/shipping_response.rb +12 -0
- data/lib/active_shipping/tracking_response.rb +52 -0
- data/lib/active_shipping/version.rb +1 -1
- data/lib/vendor/quantified/test/length_test.rb +2 -2
- data/lib/vendor/xml_node/test/test_parsing.rb +1 -1
- metadata +58 -36
- metadata.gz.sig +0 -0
- data/lib/active_shipping/shipping/base.rb +0 -13
- data/lib/active_shipping/shipping/carrier.rb +0 -84
- data/lib/active_shipping/shipping/carriers.rb +0 -23
- data/lib/active_shipping/shipping/carriers/benchmark_carrier.rb +0 -33
- data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +0 -14
- data/lib/active_shipping/shipping/carriers/canada_post.rb +0 -257
- data/lib/active_shipping/shipping/carriers/canada_post_pws.rb +0 -874
- data/lib/active_shipping/shipping/carriers/fedex.rb +0 -581
- data/lib/active_shipping/shipping/carriers/kunaki.rb +0 -166
- data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +0 -262
- data/lib/active_shipping/shipping/carriers/shipwire.rb +0 -184
- data/lib/active_shipping/shipping/carriers/stamps.rb +0 -864
- data/lib/active_shipping/shipping/carriers/ups.rb +0 -650
- data/lib/active_shipping/shipping/carriers/usps.rb +0 -649
- data/lib/active_shipping/shipping/errors.rb +0 -9
- data/lib/active_shipping/shipping/label_response.rb +0 -25
- data/lib/active_shipping/shipping/location.rb +0 -152
- data/lib/active_shipping/shipping/package.rb +0 -243
- data/lib/active_shipping/shipping/rate_estimate.rb +0 -66
- data/lib/active_shipping/shipping/rate_response.rb +0 -15
- data/lib/active_shipping/shipping/response.rb +0 -43
- data/lib/active_shipping/shipping/shipment_event.rb +0 -19
- data/lib/active_shipping/shipping/shipment_packer.rb +0 -75
- data/lib/active_shipping/shipping/shipping_response.rb +0 -14
- data/lib/active_shipping/shipping/tracking_response.rb +0 -54
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveShipping
|
2
|
+
# This is UPS specific for now; the hash is not at all generic
|
3
|
+
# or common between carriers.
|
4
|
+
|
5
|
+
class LabelResponse < Response
|
6
|
+
attr :params # maybe?
|
7
|
+
|
8
|
+
def initialize(success, message, params = {}, options = {})
|
9
|
+
@params = params
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def labels
|
14
|
+
return @labels if @labels
|
15
|
+
packages = params["ShipmentResults"]["PackageResults"]
|
16
|
+
packages = [packages] if Hash === packages
|
17
|
+
@labels = packages.map do |package|
|
18
|
+
{ :tracking_number => package["TrackingNumber"],
|
19
|
+
:image => package["LabelImage"] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module ActiveShipping #:nodoc:
|
2
|
+
class Location
|
3
|
+
ADDRESS_TYPES = %w(residential commercial po_box)
|
4
|
+
|
5
|
+
attr_reader :options,
|
6
|
+
:country,
|
7
|
+
:postal_code,
|
8
|
+
:province,
|
9
|
+
:city,
|
10
|
+
:name,
|
11
|
+
:address1,
|
12
|
+
:address2,
|
13
|
+
:address3,
|
14
|
+
:phone,
|
15
|
+
:fax,
|
16
|
+
:address_type,
|
17
|
+
:company_name
|
18
|
+
|
19
|
+
alias_method :zip, :postal_code
|
20
|
+
alias_method :postal, :postal_code
|
21
|
+
alias_method :state, :province
|
22
|
+
alias_method :territory, :province
|
23
|
+
alias_method :region, :province
|
24
|
+
alias_method :company, :company_name
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@country = (options[:country].nil? or options[:country].is_a?(ActiveUtils::Country)) ?
|
28
|
+
options[:country] :
|
29
|
+
ActiveUtils::Country.find(options[:country])
|
30
|
+
@postal_code = options[:postal_code] || options[:postal] || options[:zip]
|
31
|
+
@province = options[:province] || options[:state] || options[:territory] || options[:region]
|
32
|
+
@city = options[:city]
|
33
|
+
@name = options[:name]
|
34
|
+
@address1 = options[:address1]
|
35
|
+
@address2 = options[:address2]
|
36
|
+
@address3 = options[:address3]
|
37
|
+
@phone = options[:phone]
|
38
|
+
@fax = options[:fax]
|
39
|
+
@company_name = options[:company_name] || options[:company]
|
40
|
+
|
41
|
+
self.address_type = options[:address_type]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.from(object, options = {})
|
45
|
+
return object if object.is_a? ActiveShipping::Location
|
46
|
+
attr_mappings = {
|
47
|
+
:name => [:name],
|
48
|
+
:country => [:country_code, :country],
|
49
|
+
:postal_code => [:postal_code, :zip, :postal],
|
50
|
+
:province => [:province_code, :state_code, :territory_code, :region_code, :province, :state, :territory, :region],
|
51
|
+
:city => [:city, :town],
|
52
|
+
:address1 => [:address1, :address, :street],
|
53
|
+
:address2 => [:address2],
|
54
|
+
:address3 => [:address3],
|
55
|
+
:phone => [:phone, :phone_number],
|
56
|
+
:fax => [:fax, :fax_number],
|
57
|
+
:address_type => [:address_type],
|
58
|
+
:company_name => [:company, :company_name]
|
59
|
+
}
|
60
|
+
attributes = {}
|
61
|
+
hash_access = begin
|
62
|
+
object[:some_symbol]
|
63
|
+
true
|
64
|
+
rescue
|
65
|
+
false
|
66
|
+
end
|
67
|
+
attr_mappings.each do |pair|
|
68
|
+
pair[1].each do |sym|
|
69
|
+
if value = (object[sym] if hash_access) || (object.send(sym) if object.respond_to?(sym) && (!hash_access || !Hash.public_instance_methods.include?(sym.to_s)))
|
70
|
+
attributes[pair[0]] = value
|
71
|
+
break
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
attributes.delete(:address_type) unless ADDRESS_TYPES.include?(attributes[:address_type].to_s)
|
76
|
+
new(attributes.update(options))
|
77
|
+
end
|
78
|
+
|
79
|
+
def country_code(format = :alpha2)
|
80
|
+
@country.nil? ? nil : @country.code(format).value
|
81
|
+
end
|
82
|
+
|
83
|
+
def residential?; @address_type == 'residential' end
|
84
|
+
def commercial?; @address_type == 'commercial' end
|
85
|
+
def po_box?; @address_type == 'po_box' end
|
86
|
+
def unknown?; country_code == 'ZZ' end
|
87
|
+
|
88
|
+
def address_type=(value)
|
89
|
+
return unless value.present?
|
90
|
+
raise ArgumentError.new("address_type must be one of #{ADDRESS_TYPES.join(', ')}") unless ADDRESS_TYPES.include?(value.to_s)
|
91
|
+
@address_type = value.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_hash
|
95
|
+
{
|
96
|
+
:country => country_code,
|
97
|
+
:postal_code => postal_code,
|
98
|
+
:province => province,
|
99
|
+
:city => city,
|
100
|
+
:name => name,
|
101
|
+
:address1 => address1,
|
102
|
+
:address2 => address2,
|
103
|
+
:address3 => address3,
|
104
|
+
:phone => phone,
|
105
|
+
:fax => fax,
|
106
|
+
:address_type => address_type,
|
107
|
+
:company_name => company_name
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_xml(options = {})
|
112
|
+
options[:root] ||= "location"
|
113
|
+
to_hash.to_xml(options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_s
|
117
|
+
prettyprint.gsub(/\n/, ' ')
|
118
|
+
end
|
119
|
+
|
120
|
+
def prettyprint
|
121
|
+
chunks = [@name, @address1, @address2, @address3]
|
122
|
+
chunks << [@city, @province, @postal_code].reject(&:blank?).join(', ')
|
123
|
+
chunks << @country
|
124
|
+
chunks.reject(&:blank?).join("\n")
|
125
|
+
end
|
126
|
+
|
127
|
+
def inspect
|
128
|
+
string = prettyprint
|
129
|
+
string << "\nPhone: #{@phone}" unless @phone.blank?
|
130
|
+
string << "\nFax: #{@fax}" unless @fax.blank?
|
131
|
+
string
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns the postal code as a properly formatted Zip+4 code, e.g. "77095-2233"
|
135
|
+
def zip_plus_4
|
136
|
+
if /(\d{5})(\d{4})/ =~ @postal_code
|
137
|
+
return "#{$1}-#{$2}"
|
138
|
+
elsif /\d{5}-\d{4}/ =~ @postal_code
|
139
|
+
return @postal_code
|
140
|
+
else
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def address2_and_3
|
146
|
+
[address2, address3].reject(&:blank?).join(", ")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module ActiveShipping #:nodoc:
|
2
|
+
# A package item is a unique item(s) that is physically in a package.
|
3
|
+
# A single package can have many items. This is only required
|
4
|
+
# for shipping methods (label creation) right now.
|
5
|
+
class PackageItem
|
6
|
+
include Quantified
|
7
|
+
|
8
|
+
attr_reader :sku, :hs_code, :value, :name, :weight, :quantity, :options
|
9
|
+
|
10
|
+
def initialize(name, grams_or_ounces, value, quantity, options = {})
|
11
|
+
@name = name
|
12
|
+
|
13
|
+
imperial = (options[:units] == :imperial) ||
|
14
|
+
(grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
|
15
|
+
|
16
|
+
@unit_system = imperial ? :imperial : :metric
|
17
|
+
|
18
|
+
@weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, :grams, :ounces)
|
19
|
+
|
20
|
+
@value = Package.cents_from(value)
|
21
|
+
@quantity = quantity > 0 ? quantity : 1
|
22
|
+
|
23
|
+
@sku = options[:sku]
|
24
|
+
@hs_code = options[:hs_code]
|
25
|
+
@options = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def weight(options = {})
|
29
|
+
case options[:type]
|
30
|
+
when nil, :actual
|
31
|
+
@weight
|
32
|
+
when :volumetric, :dimensional
|
33
|
+
@volumetric_weight ||= begin
|
34
|
+
m = Mass.new((centimetres(:box_volume) / 6.0), :grams)
|
35
|
+
@unit_system == :imperial ? m.in_ounces : m
|
36
|
+
end
|
37
|
+
when :billable
|
38
|
+
[weight, weight(:type => :volumetric)].max
|
39
|
+
end
|
40
|
+
end
|
41
|
+
alias_method :mass, :weight
|
42
|
+
|
43
|
+
def ounces(options = {})
|
44
|
+
weight(options).in_ounces.amount
|
45
|
+
end
|
46
|
+
alias_method :oz, :ounces
|
47
|
+
|
48
|
+
def grams(options = {})
|
49
|
+
weight(options).in_grams.amount
|
50
|
+
end
|
51
|
+
alias_method :g, :grams
|
52
|
+
|
53
|
+
def pounds(options = {})
|
54
|
+
weight(options).in_pounds.amount
|
55
|
+
end
|
56
|
+
alias_method :lb, :pounds
|
57
|
+
alias_method :lbs, :pounds
|
58
|
+
|
59
|
+
def kilograms(options = {})
|
60
|
+
weight(options).in_kilograms.amount
|
61
|
+
end
|
62
|
+
alias_method :kg, :kilograms
|
63
|
+
alias_method :kgs, :kilograms
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def attribute_from_metric_or_imperial(obj, klass, metric_unit, imperial_unit)
|
68
|
+
if obj.is_a?(klass)
|
69
|
+
return value
|
70
|
+
else
|
71
|
+
return klass.new(obj, (@unit_system == :imperial ? imperial_unit : metric_unit))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Package
|
77
|
+
include Quantified
|
78
|
+
|
79
|
+
cattr_accessor :default_options
|
80
|
+
attr_reader :options, :value, :currency
|
81
|
+
|
82
|
+
# Package.new(100, [10, 20, 30], :units => :metric)
|
83
|
+
# Package.new(Mass.new(100, :grams), [10, 20, 30].map {|m| Length.new(m, :centimetres)})
|
84
|
+
# Package.new(100.grams, [10, 20, 30].map(&:centimetres))
|
85
|
+
def initialize(grams_or_ounces, dimensions, options = {})
|
86
|
+
options = @@default_options.update(options) if @@default_options
|
87
|
+
options.symbolize_keys!
|
88
|
+
@options = options
|
89
|
+
|
90
|
+
@dimensions = [dimensions].flatten.reject(&:nil?)
|
91
|
+
|
92
|
+
imperial = (options[:units] == :imperial) ||
|
93
|
+
([grams_or_ounces, *dimensions].all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
|
94
|
+
|
95
|
+
weight_imperial = dimensions_imperial = imperial if options.include?(:units)
|
96
|
+
|
97
|
+
if options.include?(:weight_units)
|
98
|
+
weight_imperial = (options[:weight_units] == :imperial) ||
|
99
|
+
(grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
|
100
|
+
end
|
101
|
+
|
102
|
+
if options.include?(:dim_units)
|
103
|
+
dimensions_imperial = (options[:dim_units] == :imperial) ||
|
104
|
+
(dimensions && dimensions.all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
|
105
|
+
end
|
106
|
+
|
107
|
+
@weight_unit_system = weight_imperial ? :imperial : :metric
|
108
|
+
@dimensions_unit_system = dimensions_imperial ? :imperial : :metric
|
109
|
+
|
110
|
+
@weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, @weight_unit_system, :grams, :ounces)
|
111
|
+
|
112
|
+
if @dimensions.blank?
|
113
|
+
@dimensions = [Length.new(0, (dimensions_imperial ? :inches : :centimetres))] * 3
|
114
|
+
else
|
115
|
+
process_dimensions
|
116
|
+
end
|
117
|
+
|
118
|
+
@value = Package.cents_from(options[:value])
|
119
|
+
@currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
|
120
|
+
@cylinder = (options[:cylinder] || options[:tube]) ? true : false
|
121
|
+
@gift = options[:gift] ? true : false
|
122
|
+
@oversized = options[:oversized] ? true : false
|
123
|
+
@unpackaged = options[:unpackaged] ? true : false
|
124
|
+
end
|
125
|
+
|
126
|
+
def unpackaged?
|
127
|
+
@unpackaged
|
128
|
+
end
|
129
|
+
|
130
|
+
def oversized?
|
131
|
+
@oversized
|
132
|
+
end
|
133
|
+
|
134
|
+
def cylinder?
|
135
|
+
@cylinder
|
136
|
+
end
|
137
|
+
alias_method :tube?, :cylinder?
|
138
|
+
|
139
|
+
def gift?; @gift end
|
140
|
+
|
141
|
+
def ounces(options = {})
|
142
|
+
weight(options).in_ounces.amount
|
143
|
+
end
|
144
|
+
alias_method :oz, :ounces
|
145
|
+
|
146
|
+
def grams(options = {})
|
147
|
+
weight(options).in_grams.amount
|
148
|
+
end
|
149
|
+
alias_method :g, :grams
|
150
|
+
|
151
|
+
def pounds(options = {})
|
152
|
+
weight(options).in_pounds.amount
|
153
|
+
end
|
154
|
+
alias_method :lb, :pounds
|
155
|
+
alias_method :lbs, :pounds
|
156
|
+
|
157
|
+
def kilograms(options = {})
|
158
|
+
weight(options).in_kilograms.amount
|
159
|
+
end
|
160
|
+
alias_method :kg, :kilograms
|
161
|
+
alias_method :kgs, :kilograms
|
162
|
+
|
163
|
+
def inches(measurement = nil)
|
164
|
+
@inches ||= @dimensions.map { |m| m.in_inches.amount }
|
165
|
+
measurement.nil? ? @inches : measure(measurement, @inches)
|
166
|
+
end
|
167
|
+
alias_method :in, :inches
|
168
|
+
|
169
|
+
def centimetres(measurement = nil)
|
170
|
+
@centimetres ||= @dimensions.map { |m| m.in_centimetres.amount }
|
171
|
+
measurement.nil? ? @centimetres : measure(measurement, @centimetres)
|
172
|
+
end
|
173
|
+
alias_method :cm, :centimetres
|
174
|
+
|
175
|
+
def weight(options = {})
|
176
|
+
case options[:type]
|
177
|
+
when nil, :actual
|
178
|
+
@weight
|
179
|
+
when :volumetric, :dimensional
|
180
|
+
@volumetric_weight ||= begin
|
181
|
+
m = Mass.new((centimetres(:box_volume) / 6.0), :grams)
|
182
|
+
@weight_unit_system == :imperial ? m.in_ounces : m
|
183
|
+
end
|
184
|
+
when :billable
|
185
|
+
[weight, weight(:type => :volumetric)].max
|
186
|
+
end
|
187
|
+
end
|
188
|
+
alias_method :mass, :weight
|
189
|
+
|
190
|
+
def self.cents_from(money)
|
191
|
+
return nil if money.nil?
|
192
|
+
if money.respond_to?(:cents)
|
193
|
+
return money.cents
|
194
|
+
else
|
195
|
+
case money
|
196
|
+
when Float
|
197
|
+
(money * 100).round
|
198
|
+
when String
|
199
|
+
money =~ /\./ ? (money.to_f * 100).round : money.to_i
|
200
|
+
else
|
201
|
+
money.to_i
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit)
|
209
|
+
if obj.is_a?(klass)
|
210
|
+
return obj
|
211
|
+
else
|
212
|
+
return klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def measure(measurement, ary)
|
217
|
+
case measurement
|
218
|
+
when Fixnum then ary[measurement]
|
219
|
+
when :x, :max, :length, :long then ary[2]
|
220
|
+
when :y, :mid, :width, :wide then ary[1]
|
221
|
+
when :z, :min, :height, :depth, :high, :deep then ary[0]
|
222
|
+
when :girth, :around, :circumference
|
223
|
+
self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
|
224
|
+
when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary)
|
225
|
+
when :box_volume then ary[0] * ary[1] * ary[2]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def process_dimensions
|
230
|
+
@dimensions = @dimensions.map do |l|
|
231
|
+
attribute_from_metric_or_imperial(l, Length, @dimensions_unit_system, :centimetres, :inches)
|
232
|
+
end.sort
|
233
|
+
# [1,2] => [1,1,2]
|
234
|
+
# [5] => [5,5,5]
|
235
|
+
# etc..
|
236
|
+
2.downto(@dimensions.length) do |_n|
|
237
|
+
@dimensions.unshift(@dimensions[0])
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActiveShipping #:nodoc:
|
2
|
+
class RateEstimate
|
3
|
+
attr_reader :origin # Location objects
|
4
|
+
attr_reader :destination
|
5
|
+
attr_reader :package_rates # array of hashes in the form of {:package => <Package>, :rate => 500}
|
6
|
+
attr_reader :carrier # Carrier.name ('USPS', 'FedEx', etc.)
|
7
|
+
attr_reader :service_name # name of service ("First Class Ground", etc.)
|
8
|
+
attr_reader :service_code
|
9
|
+
attr_reader :currency # 'USD', 'CAD', etc.
|
10
|
+
# http://en.wikipedia.org/wiki/ISO_4217
|
11
|
+
attr_reader :shipping_date
|
12
|
+
attr_reader :delivery_date # Usually only available for express shipments
|
13
|
+
attr_reader :delivery_range # Min and max delivery estimate in days
|
14
|
+
attr_reader :negotiated_rate
|
15
|
+
attr_reader :insurance_price
|
16
|
+
|
17
|
+
def initialize(origin, destination, carrier, service_name, options = {})
|
18
|
+
@origin, @destination, @carrier, @service_name = origin, destination, carrier, service_name
|
19
|
+
@service_code = options[:service_code]
|
20
|
+
if options[:package_rates]
|
21
|
+
@package_rates = options[:package_rates].map { |p| p.update(:rate => Package.cents_from(p[:rate])) }
|
22
|
+
else
|
23
|
+
@package_rates = Array(options[:packages]).map { |p| {:package => p} }
|
24
|
+
end
|
25
|
+
@total_price = Package.cents_from(options[:total_price])
|
26
|
+
@negotiated_rate = options[:negotiated_rate] ? Package.cents_from(options[:negotiated_rate]) : nil
|
27
|
+
@currency = ActiveUtils::CurrencyCode.standardize(options[:currency])
|
28
|
+
@delivery_range = options[:delivery_range] ? options[:delivery_range].map { |date| date_for(date) }.compact : []
|
29
|
+
@shipping_date = date_for(options[:shipping_date])
|
30
|
+
@delivery_date = @delivery_range.last
|
31
|
+
@insurance_price = Package.cents_from(options[:insurance_price])
|
32
|
+
end
|
33
|
+
|
34
|
+
def total_price
|
35
|
+
@total_price || @package_rates.sum { |p| p[:rate] }
|
36
|
+
rescue NoMethodError
|
37
|
+
raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
|
38
|
+
end
|
39
|
+
alias_method :price, :total_price
|
40
|
+
|
41
|
+
def add(package, rate = nil)
|
42
|
+
cents = Package.cents_from(rate)
|
43
|
+
raise ArgumentError.new("New packages must have valid rate information since this RateEstimate has no total_price set.") if cents.nil? and total_price.nil?
|
44
|
+
@package_rates << {:package => package, :rate => cents}
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def packages
|
49
|
+
package_rates.map { |p| p[:package] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def package_count
|
53
|
+
package_rates.length
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def date_for(date)
|
59
|
+
date && DateTime.strptime(date.to_s, "%Y-%m-%d")
|
60
|
+
rescue ArgumentError
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|