freight_kit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +201 -0
  4. data/MIT-LICENSE +31 -0
  5. data/README.md +153 -0
  6. data/VERSION +1 -0
  7. data/accessorial_symbols.txt +95 -0
  8. data/freight_kit.gemspec +58 -0
  9. data/lib/freight_kit/carrier.rb +473 -0
  10. data/lib/freight_kit/carriers.rb +24 -0
  11. data/lib/freight_kit/contact.rb +17 -0
  12. data/lib/freight_kit/error.rb +5 -0
  13. data/lib/freight_kit/errors/document_not_found_error.rb +5 -0
  14. data/lib/freight_kit/errors/expired_credentials_error.rb +5 -0
  15. data/lib/freight_kit/errors/http_error.rb +25 -0
  16. data/lib/freight_kit/errors/invalid_credentials_error.rb +5 -0
  17. data/lib/freight_kit/errors/response_error.rb +16 -0
  18. data/lib/freight_kit/errors/shipment_not_found_error.rb +5 -0
  19. data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +17 -0
  20. data/lib/freight_kit/errors/unserviceable_error.rb +5 -0
  21. data/lib/freight_kit/errors.rb +10 -0
  22. data/lib/freight_kit/model.rb +17 -0
  23. data/lib/freight_kit/models/credential.rb +117 -0
  24. data/lib/freight_kit/models/date_time.rb +37 -0
  25. data/lib/freight_kit/models/document_response.rb +17 -0
  26. data/lib/freight_kit/models/label.rb +13 -0
  27. data/lib/freight_kit/models/location.rb +108 -0
  28. data/lib/freight_kit/models/pickup_response.rb +19 -0
  29. data/lib/freight_kit/models/price.rb +38 -0
  30. data/lib/freight_kit/models/rate.rb +81 -0
  31. data/lib/freight_kit/models/rate_response.rb +15 -0
  32. data/lib/freight_kit/models/response.rb +21 -0
  33. data/lib/freight_kit/models/shipment.rb +66 -0
  34. data/lib/freight_kit/models/shipment_event.rb +38 -0
  35. data/lib/freight_kit/models/tracking_response.rb +75 -0
  36. data/lib/freight_kit/models.rb +17 -0
  37. data/lib/freight_kit/package.rb +313 -0
  38. data/lib/freight_kit/package_item.rb +65 -0
  39. data/lib/freight_kit/packaging.rb +52 -0
  40. data/lib/freight_kit/platform.rb +36 -0
  41. data/lib/freight_kit/shipment_packer.rb +116 -0
  42. data/lib/freight_kit/tariff.rb +29 -0
  43. data/lib/freight_kit/version.rb +5 -0
  44. data/lib/freight_kit.rb +34 -0
  45. data/service_type_symbols.txt +4 -0
  46. data/shipment_event_symbols.txt +17 -0
  47. metadata +453 -0
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit # :nodoc:
4
+ class Package
5
+ class << self
6
+ def cents_from(money)
7
+ return if money.nil?
8
+
9
+ if money.respond_to?(:cents)
10
+ money.cents
11
+ else
12
+ case money
13
+ when Float
14
+ (money * 100).round
15
+ when String
16
+ money =~ /\./ ? (money.to_f * 100).round : money.to_i
17
+ else
18
+ money.to_i
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ VALID_FREIGHT_CLASSES = [55, 60, 65, 70, 77.5, 85, 92.5, 100, 110, 125, 150, 175, 200, 250, 300, 400].freeze
25
+
26
+ cattr_accessor :default_options
27
+ attr_accessor :description, :hazmat, :nmfc, :quantity
28
+ attr_reader :currency, :options, :packaging, :value
29
+ attr_writer :declared_freight_class
30
+
31
+ # Package.new(100, [10, 20, 30], 'pallet', :units => :metric)
32
+ # Package.new(Measured::Weight.new(100, :g), 'box', [10, 20, 30].map {|m| Length.new(m, :centimetres)})
33
+ # Package.new(100.grams, [10, 20, 30].map(&:centimetres))
34
+ def initialize(total_grams_or_ounces, dimensions, packaging_type, options = {})
35
+ options = @@default_options.update(options) if @@default_options
36
+ options.symbolize_keys!
37
+ @options = options
38
+
39
+ raise ArgumentError, 'Package#new: packaging_type is required' unless packaging_type
40
+ raise ArgumentError, 'Package#new: quantity is required' unless options[:quantity]
41
+
42
+ # For backward compatibility
43
+ if dimensions.is_a?(Array)
44
+ @dimensions = [dimensions].flatten.reject(&:nil?)
45
+ else
46
+ @dimensions = [dimensions.dig(:height), dimensions.dig(:width), dimensions.dig(:length)]
47
+ @dimensions = [@dimensions].flatten.reject(&:nil?)
48
+ end
49
+
50
+ @description = options[:description]
51
+ @hazmat = options[:hazmat] == true
52
+ @nmfc = options[:nmfc].presence
53
+
54
+ imperial = (options[:units] == :imperial)
55
+
56
+ weight_imperial = dimensions_imperial = imperial if options.include?(:units)
57
+
58
+ weight_imperial = (options[:weight_units] == :imperial) if options.include?(:weight_units)
59
+
60
+ dimensions_imperial = (options[:dim_units] == :imperial) if options.include?(:dim_units)
61
+
62
+ @weight_unit_system = weight_imperial ? :imperial : :metric
63
+ @dimensions_unit_system = dimensions_imperial ? :imperial : :metric
64
+
65
+ @quantity = options[:quantity] || 1
66
+
67
+ @total_weight = attribute_from_metric_or_imperial(
68
+ total_grams_or_ounces,
69
+ Measured::Weight,
70
+ @weight_unit_system,
71
+ :grams,
72
+ :ounces,
73
+ )
74
+
75
+ @each_weight = attribute_from_metric_or_imperial(
76
+ total_grams_or_ounces / @quantity.to_f,
77
+ Measured::Weight,
78
+ @weight_unit_system,
79
+ :grams,
80
+ :ounces,
81
+ )
82
+
83
+ if @dimensions.blank?
84
+ zero_length = Measured::Length.new(0, (dimensions_imperial ? :inches : :centimetres))
85
+ @dimensions = [zero_length] * 3
86
+ else
87
+ # Overriding ReactiveShipping's protected process_dimensions which sorts
88
+ # them making it confusing for ReactiveFreight carrier API's that expect
89
+ # the H x W x L order. Since H x W x L is nonstandard in the freight
90
+ # industry ReactiveFreight introduces explicit functions for each
91
+ @dimensions = @dimensions.map do |l|
92
+ attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches)
93
+ end
94
+ 2.downto(@dimensions.length) do |_n|
95
+ @dimensions.unshift(@dimensions[0])
96
+ end
97
+ end
98
+
99
+ @value = Package.cents_from(options[:value])
100
+ @currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
101
+ @cylinder = options[:cylinder] || options[:tube] ? true : false
102
+ @gift = options[:gift] ? true : false
103
+ @oversized = options[:oversized] ? true : false
104
+ @unpackaged = options[:unpackaged] ? true : false
105
+ @packaging = Packaging.new(packaging_type)
106
+ end
107
+
108
+ def cubic_ft(each_or_total)
109
+ q = case each_or_total
110
+ when :each then 1
111
+ when :total then @quantity
112
+ else
113
+ raise ArgumentError, 'each_or_total must be one of :each, :total'
114
+ end
115
+
116
+ return unless inches[..2].all?(&:present?)
117
+
118
+ cubic_ft = (inches[0] * inches[1] * inches[2]).to_f / 1728
119
+ cubic_ft *= q
120
+
121
+ format('%0.2f', cubic_ft).to_f
122
+ end
123
+
124
+ def density
125
+ return unless inches[..2].all?(&:present?) && pounds(:each)
126
+
127
+ density = pounds(:each).to_f / cubic_ft(:each)
128
+ format('%0.2f', density).to_f
129
+ end
130
+
131
+ def calculated_freight_class
132
+ sanitized_freight_class(density_to_freight_class(density))
133
+ end
134
+
135
+ def declared_freight_class
136
+ @declared_freight_class || @options[:declared_freight_class]
137
+ end
138
+
139
+ def freight_class
140
+ (declared_freight_class.presence || calculated_freight_class)
141
+ end
142
+
143
+ def length(unit)
144
+ @dimensions[2].convert_to(unit).value.to_f
145
+ end
146
+
147
+ def width(unit)
148
+ @dimensions[1].convert_to(unit).value.to_f
149
+ end
150
+
151
+ def height(unit)
152
+ @dimensions[0].convert_to(unit).value.to_f
153
+ end
154
+
155
+ def cylinder?
156
+ @cylinder
157
+ end
158
+
159
+ def oversized?
160
+ @oversized
161
+ end
162
+
163
+ def unpackaged?
164
+ @unpackaged
165
+ end
166
+
167
+ alias_method :tube?, :cylinder?
168
+
169
+ def gift?
170
+ @gift
171
+ end
172
+
173
+ def hazmat?
174
+ @hazmat
175
+ end
176
+
177
+ def ounces(options = {})
178
+ weight(options).convert_to(:oz).value.to_f
179
+ end
180
+ alias_method :oz, :ounces
181
+
182
+ def grams(options = {})
183
+ weight(options).convert_to(:g).value.to_f
184
+ end
185
+ alias_method :g, :grams
186
+
187
+ def pounds(args)
188
+ weight(*args).convert_to(:lb).value.to_f
189
+ end
190
+ alias_method :lb, :pounds
191
+ alias_method :lbs, :pounds
192
+
193
+ def kilograms(options = {})
194
+ weight(options).convert_to(:kg).value.to_f
195
+ end
196
+ alias_method :kg, :kilograms
197
+ alias_method :kgs, :kilograms
198
+
199
+ def inches(measurement = nil)
200
+ @inches ||= @dimensions.map { |m| m.convert_to(:in).value.to_f }
201
+ measurement.nil? ? @inches : measure(measurement, @inches)
202
+ end
203
+ alias_method :in, :inches
204
+
205
+ def centimetres(measurement = nil)
206
+ @centimetres ||= @dimensions.map { |m| m.convert_to(:cm).value.to_f }
207
+ measurement.nil? ? @centimetres : measure(measurement, @centimetres)
208
+ end
209
+ alias_method :cm, :centimetres
210
+
211
+ def dim_weight
212
+ return if inches(:length).blank? || inches(:width).blank? || inches(:height).blank? || pounds(:each).blank?
213
+
214
+ @dim_weight ||= (inches(:length).ceil * inches(:width).ceil * inches(:height).ceil).to_f / 139
215
+ end
216
+
217
+ def each_weight(options = {})
218
+ weight(@each_weight, options)
219
+ end
220
+
221
+ def total_weight(options = {})
222
+ weight(@total_weight, options)
223
+ end
224
+
225
+ private
226
+
227
+ def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit)
228
+ if obj.is_a?(klass)
229
+ obj
230
+ else
231
+ klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit))
232
+ end
233
+ end
234
+
235
+ def density_to_freight_class(density)
236
+ return unless density
237
+ return 400 if density < 1
238
+ return 60 if density > 30
239
+
240
+ density_table = [
241
+ [1, 2, 300],
242
+ [2, 4, 250],
243
+ [4, 6, 175],
244
+ [6, 8, 125],
245
+ [8, 10, 100],
246
+ [10, 12, 92.5],
247
+ [12, 15, 85],
248
+ [15, 22.5, 70],
249
+ [22.5, 30, 65],
250
+ [30, 35, 60],
251
+ ]
252
+ density_table.each do |density_row|
253
+ return density_row[2] if (density >= density_row[0]) && (density < density_row[1])
254
+ end
255
+ end
256
+
257
+ def sanitized_freight_class(freight_class)
258
+ return if freight_class.blank?
259
+
260
+ if VALID_FREIGHT_CLASSES.include?(freight_class)
261
+ return freight_class.to_i == freight_class ? freight_class.to_i : freight_class
262
+ end
263
+
264
+ nil
265
+ end
266
+
267
+ def measure(measurement, ary)
268
+ case measurement
269
+ when Integer then ary[measurement]
270
+ when :x, :max, :length, :long then ary[2]
271
+ when :y, :mid, :width, :wide then ary[1]
272
+ when :z, :min, :height, :depth, :high, :deep then ary[0]
273
+ when :girth, :around, :circumference
274
+ cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
275
+ when :volume then cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary)
276
+ when :box_volume then ary[0] * ary[1] * ary[2]
277
+ end
278
+ end
279
+
280
+ def process_dimensions
281
+ @dimensions = @dimensions.map do |l|
282
+ attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches)
283
+ end.sort
284
+ # [1,2] => [1,1,2]
285
+ # [5] => [5,5,5]
286
+ # etc..
287
+ 2.downto(@dimensions.length) do |_n|
288
+ @dimensions.unshift(@dimensions[0])
289
+ end
290
+ end
291
+
292
+ def weight(which_weight, options = {})
293
+ weight = case which_weight
294
+ when :each then @each_weight
295
+ when :total then @total_weight
296
+ else
297
+ raise ArgumentError, 'which_weight must be one of :each, :total'
298
+ end
299
+
300
+ case options[:type]
301
+ when nil, :actual
302
+ weight
303
+ when :volumetric, :dimensional
304
+ @volumetric_weight ||= begin
305
+ m = Measured::Weight.new((centimetres(:box_volume) / 6.0), :grams)
306
+ @weight_unit_system == :imperial ? m.convert_to(:oz) : m
307
+ end
308
+ when :billable
309
+ [weight, weight(weight, type: :volumetric)].max
310
+ end
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit # :nodoc:
4
+ class PackageItem
5
+ attr_reader :sku, :hs_code, :value, :name, :quantity, :options
6
+
7
+ def initialize(name, grams_or_ounces, value, quantity, options = {})
8
+ @name = name
9
+
10
+ imperial = (options[:units] == :imperial)
11
+
12
+ @unit_system = imperial ? :imperial : :metric
13
+
14
+ @weight = grams_or_ounces
15
+ @weight = Measured::Weight.new(
16
+ grams_or_ounces,
17
+ (@unit_system == :imperial ? :oz : :g),
18
+ ) unless @weight.is_a?(Measured::Weight)
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 = Measured::Weight.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).convert_to(:oz).value
45
+ end
46
+ alias_method :oz, :ounces
47
+
48
+ def grams(options = {})
49
+ weight(options).convert_to(:g).value
50
+ end
51
+ alias_method :g, :grams
52
+
53
+ def pounds(options = {})
54
+ weight(options).convert_to(:lb).value
55
+ end
56
+ alias_method :lb, :pounds
57
+ alias_method :lbs, :pounds
58
+
59
+ def kilograms(options = {})
60
+ weight(options).convert_to(:kg).value
61
+ end
62
+ alias_method :kg, :kilograms
63
+ alias_method :kgs, :kilograms
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ class Packaging
5
+ VALID_TYPES = %i[
6
+ box
7
+ bundle
8
+ container
9
+ crate
10
+ cylinder
11
+ drum
12
+ luggage
13
+ pail
14
+ pallet
15
+ piece
16
+ roll
17
+ tote
18
+ truckload
19
+ tote
20
+ ].freeze
21
+
22
+ PALLET_TYPES = %i[crate drum pallet tote].freeze
23
+
24
+ attr_accessor :type
25
+
26
+ # Packaging.new(:pallet)
27
+ def initialize(type, options = {})
28
+ options.symbolize_keys!
29
+ @options = options
30
+
31
+ unless VALID_TYPES.include?(type)
32
+ raise ArgumentError, "Package#new: `type` should be one of #{VALID_TYPES.join(", ")}"
33
+ end
34
+
35
+ @type = type
36
+ end
37
+
38
+ def box?
39
+ @box ||= BOX_TYPES.include?(@type)
40
+ end
41
+
42
+ def pallet?
43
+ @pallet ||= PALLET_TYPES.include?(@type)
44
+ end
45
+
46
+ def box_or_pallet_type
47
+ return :pallet if pallet?
48
+
49
+ box? ? :box : nil
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ class Platform < Carrier
5
+ # Credentials should be a `Credential` or `Array` of `Credential`
6
+ def initialize(credentials, customer_location: nil, tariff: nil)
7
+ super
8
+
9
+ # Use #superclass instead of using #ancestors to fetch the parent class which the carrier class is inheriting from
10
+ # (#ancestors returns an array including the parent class and all the modules that were included)
11
+ parent_class_name = self.class.superclass.name.demodulize.underscore
12
+
13
+ conf_path = File
14
+ .join(
15
+ File.expand_path(
16
+ '../../../../configuration/platforms',
17
+ self.class.const_source_location(:REACTIVE_FREIGHT_PLATFORM).first,
18
+ ),
19
+ "#{parent_class_name}.yml",
20
+ )
21
+ @conf = YAML.safe_load(File.read(conf_path), permitted_classes: [Symbol])
22
+
23
+ conf_path = File
24
+ .join(
25
+ File.expand_path(
26
+ '../../../../configuration/carriers',
27
+ self.class.const_source_location(:REACTIVE_FREIGHT_CARRIER).first,
28
+ ),
29
+ "#{self.class.to_s.demodulize.underscore}.yml",
30
+ )
31
+ @conf = @conf.deep_merge(YAML.safe_load(File.read(conf_path), permitted_classes: [Symbol]))
32
+
33
+ @rates_with_excessive_length_fees = @conf.dig(:attributes, :rates, :with_excessive_length_fees)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ class ShipmentPacker
5
+ class OverweightItem < StandardError
6
+ end
7
+
8
+ EXCESS_PACKAGE_QUANTITY_THRESHOLD = 10_000
9
+ class ExcessPackageQuantity < StandardError; end
10
+
11
+ # items - array of hashes containing quantity, grams and price.
12
+ # ex. `[{:quantity => 2, :price => 1.0, :grams => 50}]`
13
+ # dimensions - `[5.0, 15.0, 30.0]`
14
+ # maximum_weight - maximum weight in grams
15
+ # currency - ISO currency code
16
+
17
+ class << self
18
+ def pack(items, dimensions, maximum_weight, currency)
19
+ return [] if items.empty?
20
+
21
+ packages = []
22
+ items.map!(&:symbolize_keys)
23
+
24
+ # Naive in that it assumes weight is equally distributed across all items
25
+ # Should raise early enough in most cases
26
+ validate_total_weight(items, maximum_weight)
27
+ items_to_pack = items.map(&:dup).sort_by! { |i| i[:grams].to_i }
28
+
29
+ state = :package_empty
30
+ while state != :packing_finished
31
+ case state
32
+ when :package_empty
33
+ package_weight = 0
34
+ package_value = 0
35
+ state = :filling_package
36
+ when :filling_package
37
+ validate_package_quantity(packages.count)
38
+
39
+ items_to_pack.each do |item|
40
+ quantity = determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
41
+ package_weight += item_weight(quantity, item[:grams])
42
+ package_value += item_value(quantity, item[:price])
43
+ item[:quantity] = item[:quantity].to_i - quantity
44
+ end
45
+
46
+ items_to_pack.reject! { |i| i[:quantity].to_i == 0 }
47
+ state = :package_full
48
+ when :package_full
49
+ packages << FreightKit::Package.new(package_weight, dimensions, value: package_value, currency: currency)
50
+ state = items_to_pack.any? ? :package_empty : :packing_finished
51
+ end
52
+ end
53
+
54
+ packages
55
+ end
56
+
57
+ private
58
+
59
+ def validate_total_weight(items, maximum_weight)
60
+ total_weight = 0
61
+ items.each do |item|
62
+ total_weight += item[:quantity].to_i * item[:grams].to_i
63
+
64
+ if overweight_item?(item[:grams], maximum_weight)
65
+ message = <<~MESSAGE.squish
66
+ The item with weight of #{item[:grams]}g is heavier than the allowable package weight of
67
+ #{maximum_weight}g
68
+ MESSAGE
69
+ raise OverweightItem, message
70
+ end
71
+
72
+ raise_excess_quantity_error if maybe_excess_package_quantity?(total_weight, maximum_weight)
73
+ end
74
+ end
75
+
76
+ def validate_package_quantity(number_of_packages)
77
+ raise_excess_quantity_error if number_of_packages >= EXCESS_PACKAGE_QUANTITY_THRESHOLD
78
+ end
79
+
80
+ def raise_excess_quantity_error
81
+ raise ExcessPackageQuantity, "Unable to pack more than #{EXCESS_PACKAGE_QUANTITY_THRESHOLD} packages"
82
+ end
83
+
84
+ def overweight_item?(grams, maximum_weight)
85
+ grams.to_i > maximum_weight
86
+ end
87
+
88
+ def maybe_excess_package_quantity?(total_weight, maximum_weight)
89
+ total_weight > (maximum_weight * EXCESS_PACKAGE_QUANTITY_THRESHOLD)
90
+ end
91
+
92
+ def determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
93
+ item_grams = item[:grams].to_i
94
+ item_quantity = item[:quantity].to_i
95
+
96
+ if item_grams <= 0
97
+ item_quantity
98
+ else
99
+ # Grab the max amount of this item we can fit into this package
100
+ # Or, if there are fewer than the max for this item, put
101
+ # what is left into this package
102
+ available_grams = (maximum_weight - package_weight).to_i
103
+ [available_grams / item_grams, item_quantity].min
104
+ end
105
+ end
106
+
107
+ def item_weight(quantity, grams)
108
+ quantity * grams.to_i
109
+ end
110
+
111
+ def item_value(quantity, price)
112
+ quantity * Package.cents_from(price)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ class Tariff
5
+ attr_accessor :overlength_rules
6
+
7
+ def initialize(options = {})
8
+ options.symbolize_keys!
9
+ @options = options
10
+
11
+ @options[:overlength_rules] = (@options[:overlength_rules].presence || [])
12
+ raise ArgumentError, 'overlength_rules must be an Array' unless @options[:overlength_rules].is_a?(Array)
13
+
14
+ @options[:overlength_rules].each do |overlength_rule|
15
+ if !overlength_rule[:min_length].is_a?(Measured::Length)
16
+ raise ArgumentError, 'overlength_rule[:min_length] must be a Measured::Length'
17
+ elsif ![Measured::Length, NilClass].include?(overlength_rule[:max_length].class)
18
+ raise ArgumentError, 'overlength_rule[:max_length] must be one of Measured::Length, NilClass'
19
+ end
20
+
21
+ unless overlength_rule[:fee_cents].is_a?(Integer)
22
+ raise ArgumentError, 'overlength_rule[:fee_cents] must be an Integer'
23
+ end
24
+ end
25
+
26
+ @overlength_rules = @options[:overlength_rules]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ VERSION = File.read(File.expand_path('../../VERSION', __dir__)).strip.freeze
5
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+ require 'active_support/all'
5
+ require 'active_utils'
6
+
7
+ require 'cgi'
8
+ require 'yaml'
9
+
10
+ require 'httparty'
11
+ require 'measured'
12
+ require 'mimemagic'
13
+ require 'nokogiri'
14
+ require 'open-uri'
15
+ require 'place_kit'
16
+ require 'savon'
17
+ require 'watir'
18
+
19
+ require 'freight_kit/error'
20
+ require 'freight_kit/errors'
21
+
22
+ require 'freight_kit/model'
23
+ require 'freight_kit/models'
24
+
25
+ require 'freight_kit/carrier'
26
+ require 'freight_kit/carriers'
27
+ require 'freight_kit/contact'
28
+ require 'freight_kit/package_item'
29
+ require 'freight_kit/package'
30
+ require 'freight_kit/packaging'
31
+ require 'freight_kit/platform'
32
+ require 'freight_kit/shipment_packer'
33
+ require 'freight_kit/tariff'
34
+ require 'freight_kit/version'
@@ -0,0 +1,4 @@
1
+ :standard
2
+ :guaranteed_delivery
3
+ :guaranteed_delivery_am
4
+ :guaranteed_delivery_pm
@@ -0,0 +1,17 @@
1
+ :arrived_at_terminal
2
+ :delayed_due_to_weather
3
+ :delivered
4
+ :delivery_appointment_scheduled
5
+ :departed
6
+ :found
7
+ :located
8
+ :lost
9
+ :out_for_delivery
10
+ :pending_delivery_appointment
11
+ :picked_up
12
+ :pickup_driver_assigned
13
+ :pickup_information_received_by_carrier
14
+ :pickup_information_sent_to_carrier
15
+ :sailed
16
+ :trailer_closed
17
+ :trailer_unloaded