freight_kit 0.1.0

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.
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