freight_kit 0.1.15 → 0.1.16

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +96 -43
  3. data/README.md +28 -6
  4. data/VERSION +1 -1
  5. data/configuration/carriers/abfs.yml +124 -0
  6. data/configuration/carriers/btvp.yml +84 -0
  7. data/configuration/carriers/ccyq.yml +121 -0
  8. data/configuration/carriers/clni.yml +113 -0
  9. data/configuration/carriers/cnwy.yml +113 -0
  10. data/configuration/carriers/ctbv.yml +117 -0
  11. data/configuration/carriers/dcha.yml +105 -0
  12. data/configuration/carriers/dlds.yml +111 -0
  13. data/configuration/carriers/dphe.yml +130 -0
  14. data/configuration/carriers/drrq.yml +131 -0
  15. data/configuration/carriers/fcsy.yml +102 -0
  16. data/configuration/carriers/fwda.yml +137 -0
  17. data/configuration/carriers/jfj_transportation.yml +2 -0
  18. data/configuration/carriers/mtvl.yml +12 -0
  19. data/configuration/carriers/numk.yml +14 -0
  20. data/configuration/carriers/otcl.yml +124 -0
  21. data/configuration/carriers/pens.yml +22 -0
  22. data/configuration/carriers/rdfs.yml +142 -0
  23. data/configuration/carriers/saia.yml +129 -0
  24. data/configuration/carriers/sefl.yml +115 -0
  25. data/configuration/carriers/totl.yml +111 -0
  26. data/configuration/carriers/tqyl.yml +28 -0
  27. data/configuration/carriers/wrds.yml +20 -0
  28. data/configuration/platforms/carrier_logistics.yml +25 -0
  29. data/configuration/platforms/next.yml +12 -0
  30. data/configuration/platforms/the_great_information_factory.yml +122 -0
  31. data/freight_kit.gemspec +9 -7
  32. data/lib/freight_kit/api_clients/soap_client.rb +70 -0
  33. data/lib/freight_kit/api_clients.rb +3 -0
  34. data/lib/freight_kit/carriers/abfs.rb +421 -0
  35. data/lib/freight_kit/carriers/btvp.rb +29 -0
  36. data/lib/freight_kit/carriers/ccyq.rb +317 -0
  37. data/lib/freight_kit/carriers/clni.rb +396 -0
  38. data/lib/freight_kit/carriers/cnwy.rb +327 -0
  39. data/lib/freight_kit/carriers/ctbv.rb +53 -0
  40. data/lib/freight_kit/carriers/dcha.rb +76 -0
  41. data/lib/freight_kit/carriers/dlds.rb +49 -0
  42. data/lib/freight_kit/carriers/dphe.rb +474 -0
  43. data/lib/freight_kit/carriers/drrq.rb +580 -0
  44. data/lib/freight_kit/carriers/fcsy.rb +57 -0
  45. data/lib/freight_kit/carriers/fwda.rb +744 -0
  46. data/lib/freight_kit/carriers/jfj_transportation.rb +13 -0
  47. data/lib/freight_kit/carriers/mtvl.rb +34 -0
  48. data/lib/freight_kit/carriers/numk.rb +58 -0
  49. data/lib/freight_kit/carriers/otcl.rb +528 -0
  50. data/lib/freight_kit/carriers/pens.rb +204 -0
  51. data/lib/freight_kit/carriers/rdfs.rb +521 -0
  52. data/lib/freight_kit/carriers/saia.rb +438 -0
  53. data/lib/freight_kit/carriers/sefl.rb +342 -0
  54. data/lib/freight_kit/carriers/totl.rb +172 -0
  55. data/lib/freight_kit/carriers/tqyl.rb +339 -0
  56. data/lib/freight_kit/carriers/wrds.rb +246 -0
  57. data/lib/freight_kit/carriers.rb +26 -0
  58. data/lib/freight_kit/helpers/documentable.rb +13 -0
  59. data/lib/freight_kit/helpers/pickupable.rb +39 -0
  60. data/lib/freight_kit/helpers/rateable.rb +28 -0
  61. data/lib/freight_kit/helpers/trackable.rb +25 -0
  62. data/lib/freight_kit/helpers.rb +6 -0
  63. data/lib/freight_kit/platforms/carrier_logistics.rb +450 -0
  64. data/lib/freight_kit/platforms/next.rb +101 -0
  65. data/lib/freight_kit/platforms/the_great_information_factory.rb +528 -0
  66. data/lib/freight_kit/platforms.rb +5 -0
  67. data/lib/freight_kit.rb +20 -1
  68. metadata +94 -14
@@ -0,0 +1,528 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FreightKit
4
+ class TheGreatInformationFactory < Platform
5
+ class << self
6
+ def required_credential_types
7
+ %i[api]
8
+ end
9
+
10
+ def requirements
11
+ %i[credentials tariff]
12
+ end
13
+
14
+ def overlength_fees_require_tariff?
15
+ true
16
+ end
17
+ end
18
+
19
+ REACTIVE_FREIGHT_PLATFORM = true
20
+
21
+ include FreightKit::Rateable
22
+ include FreightKit::Trackable
23
+ include FreightKit::Pickupable
24
+
25
+ protected
26
+
27
+ def wrap_request(request)
28
+ { 'arg0' => request }
29
+ end
30
+
31
+ def build_soap_header
32
+ api_credentials = fetch_credential(:api)
33
+
34
+ { username: api_credentials.username, password: api_credentials.password }
35
+ end
36
+
37
+ def commit(action, request)
38
+ client_args = {
39
+ wsdl: build_url(action),
40
+ convert_request_keys_to: :upcase,
41
+ env_namespace: :soapenv
42
+ }
43
+
44
+ call_args = {
45
+ headers: { 'SOAPAction' => '""' },
46
+ soap_action: false,
47
+ message: request
48
+ }
49
+
50
+ ::FreightKit::SoapClient.new(
51
+ carrier: self,
52
+ action:,
53
+ client_args:,
54
+ call_args:,
55
+ soap_operation: @conf.dig(:api, :actions, action),
56
+ ).call
57
+ end
58
+
59
+ def parse_api_date(date)
60
+ return if date.blank?
61
+
62
+ local_date = ::Date.strptime(date, '%m/%d/%Y')
63
+ ::FreightKit::DateTime.new(local_date:)
64
+ end
65
+
66
+ def parse_api_date_time(date_time, location)
67
+ return if date_time.blank?
68
+
69
+ format = date_time.include?('-') ? '%Y-%m-%d %H:%M' : '%m/%d/%Y %H:%M'
70
+
71
+ local_date_time = ::Time.strptime(date_time, format).to_fs(:db)
72
+ ::FreightKit::DateTime.new(local_date_time:, location:)
73
+ end
74
+
75
+ def build_url(action)
76
+ scheme = @conf.dig(:api, :use_ssl, action) ? 'https://' : 'http://'
77
+ domain = @conf.dig(:api, :domains, action).presence || @conf.dig(:api, :domain)
78
+ port = @conf.dig(:api, :ports, action)
79
+ return [scheme, domain, @conf.dig(:api, :endpoints, action)].join unless port
80
+
81
+ "#{scheme}#{domain}:#{port}#{@conf.dig(:api, :endpoints, action)}"
82
+ end
83
+
84
+ def strip_date(str)
85
+ str ? str.split(/[A|P]M /)[1] : nil
86
+ end
87
+
88
+ # Rates
89
+
90
+ def build_calculated_accessorials(packages)
91
+ []
92
+ end
93
+
94
+ def build_rate_request(shipment:)
95
+ accessorials = []
96
+
97
+ if shipment.accessorials.present?
98
+ serviceable_accessorials?(shipment.accessorials)
99
+
100
+ accessorials = shipment
101
+ .accessorials
102
+ .select { |accessorial| @conf.dig(:accessorials, :unserviceable).exclude?(accessorial) }
103
+ .map { |accessorial| @conf.dig(:accessorials, :mappable, accessorial) }
104
+ end
105
+
106
+ accessorials += build_calculated_accessorials(shipment.packages)
107
+ accessorials.uniq!
108
+
109
+ item = shipment.packages.map do |package|
110
+ {
111
+ _class: package.freight_class,
112
+ description: (package.description || 'Freight')[..8].upcase,
113
+ haz: (package.hazmat? ? 'Y' : ''),
114
+ pallets: (package.packaging.pallet? ? package.quantity : 0),
115
+ pieces: package.quantity,
116
+ weight: package.pounds(:total).ceil
117
+ }
118
+ end
119
+
120
+ request = {
121
+ securityinfo: build_soap_header,
122
+ quote: {
123
+ iam: 'D', # S for shipper, C for consignee, D for third party
124
+ shipper: {
125
+ city: shipment.origin.city.upcase,
126
+ state: shipment.origin.province.upcase,
127
+ zip: shipment.origin.postal_code.gsub(/\s+/, '').upcase
128
+ },
129
+ consignee: {
130
+ city: shipment.destination.city.upcase,
131
+ state: shipment.destination.province.upcase,
132
+ zip: shipment.destination.postal_code.gsub(/\s+/, '').upcase
133
+ },
134
+ accessorialcount: accessorials.count,
135
+ accessorial: accessorials.map { |code| { code: } },
136
+ ppdcol: 'P', # Prepaid
137
+ itemcount: shipment.packages.sum(&:quantity),
138
+ item:
139
+ }
140
+ }
141
+
142
+ request = wrap_request(request)
143
+ save_request(request)
144
+
145
+ request
146
+ end
147
+
148
+ def rate_item_description(rate_item)
149
+ description = rate_item[:description] || ''
150
+ description = description.gsub('-', '')
151
+ description = description.squish
152
+ description = description.sub('disc.on', 'discount on')
153
+ description = description.capitalize
154
+ description = description.sub('zip code', 'ZIP code')
155
+ description.sub('Zip code', 'ZIP code')
156
+ end
157
+
158
+ def parse_rate_response(shipment:, response:)
159
+ rate_response = RateResponse.new(request: last_request, response:)
160
+
161
+ if response.blank?
162
+ rate_response.error = ResponseError.new('Unknown response')
163
+ return rate_response
164
+ end
165
+
166
+ error_code = response.dig(:getquote_response, :return, :rating, :errorcode)
167
+ if error_code
168
+ rate_response.error = parse_error_response(error_code)
169
+ return rate_response
170
+ end
171
+
172
+ total_cents = response.dig(:getquote_response, :return, :rating, :amount)
173
+
174
+ raise FreightKit::ResponseError, 'API Error: Cost is empty' if total_cents.blank?
175
+
176
+ rate_items = response.dig(:getquote_response, :return, :rateitem)
177
+ total_cents = (total_cents.to_f * 100).to_i
178
+
179
+ prices = []
180
+
181
+ # Confusing API sometimes returns lines of freight with high costs and then later includes includes lines that
182
+ # override the high cost without adding a discount, etc
183
+ rate_items.each do |rate_item|
184
+ next if ['Sub Total', 'GrandTotal'].include?(rate_item[:acccode])
185
+
186
+ # Exclude lines that are just the packages repeated back to us
187
+ next unless rate_item[:pallets] == '0' && rate_item[:pieces] == '0'
188
+
189
+ cents = (rate_item[:amount].to_f * 100).to_i
190
+ description = rate_item_description(rate_item)
191
+
192
+ prices << Price.new(blame: :api, cents:, description:)
193
+ end
194
+
195
+ # Since we expected the low-cost overriding lines earlier, we need to handle situations where those lines do not
196
+ # appear
197
+ if prices.sum(&:cents) < total_cents
198
+ prices = [
199
+ Price.new(
200
+ blame: :api,
201
+ cents: total_cents - prices.sum(&:cents),
202
+ description: 'Freight',
203
+ ),
204
+ ] + prices
205
+ end
206
+
207
+ if self.class.overlength_fees_require_tariff?
208
+ shipment.packages.each do |package|
209
+ cents = overlength_fee(tariff, package)
210
+ next unless cents.positive?
211
+
212
+ prices << Price.new(
213
+ blame: :tariff,
214
+ cents:,
215
+ description: 'Overlength fee',
216
+ )
217
+ end
218
+ end
219
+
220
+ transit_days = response.dig(
221
+ :getquote_response,
222
+ :return,
223
+ :service,
224
+ :days,
225
+ ).to_i
226
+
227
+ # Calculate real transit time based on information we have about the destination service days
228
+ %i[mon tue wed thu fri].each do |weekday|
229
+ transit_days += 1 if response.dig(:getquote_response, :return, :service, :destination, weekday) == 'N'
230
+ end
231
+
232
+ estimate_reference = response.dig(
233
+ :getquote_response,
234
+ :return,
235
+ :rating,
236
+ :quotenumber,
237
+ )
238
+
239
+ rate = Rate.new(
240
+ carrier_name: self.class.name,
241
+ carrier: self,
242
+ currency: 'USD',
243
+ estimate_reference:,
244
+ prices:,
245
+ scac: self.class.scac.upcase,
246
+ service_name: :standard,
247
+ shipment:,
248
+ transit_days:,
249
+ with_excessive_length_fees: @conf.dig(:attributes, :rates, :with_excessive_length_fees),
250
+ )
251
+
252
+ rate_response.rates = [rate]
253
+ rate_response
254
+ end
255
+
256
+ # Tracking
257
+
258
+ def build_tracking_request(tracking_number)
259
+ request = { pronumber: tracking_number, securityinfo: build_soap_header }
260
+
261
+ request = wrap_request(request)
262
+ save_request(request)
263
+
264
+ request
265
+ end
266
+
267
+ def parse_location(code)
268
+ country = ActiveUtils::Country.find('USA')
269
+ return Location.new(country:) unless code
270
+
271
+ location = @conf.dig(:events, :locations, code.to_sym)
272
+
273
+ if location
274
+ Location.new(
275
+ city: location[:city],
276
+ province: location[:state],
277
+ country:,
278
+ )
279
+ else
280
+ Location.new(city: code, country:)
281
+ end
282
+ end
283
+
284
+ def parse_error_response(error_code)
285
+ case error_code
286
+ when 'BADUSRPWD' then InvalidCredentialsError.new
287
+ when 'NOSVC' then UnserviceableError.new('Origin or destination has no service available')
288
+ when 'BADCONZIP' then UnserviceableError.new('Invalid destination ZIP code')
289
+ else
290
+ ResponseError.new("API error code #{error_code}")
291
+ end
292
+ end
293
+
294
+ def build_location(city, province)
295
+ Location.new(city: city.titleize, province: province.upcase, country: ActiveUtils::Country.find('USA'))
296
+ end
297
+
298
+ def parse_tracking_response(response)
299
+ tracking_response = TrackingResponse.new(carrier: self, request: last_request, response:)
300
+ mapped_response = response.dig(:tracktrace_response, :return, :currentstatus)
301
+
302
+ if mapped_response[:errorcode]
303
+ tracking_response.error = parse_error_response(mapped_response[:errorcode])
304
+ return tracking_response
305
+ end
306
+
307
+ receiver_location = build_location(
308
+ mapped_response.dig(:consignee, :city),
309
+ mapped_response.dig(:consignee, :state),
310
+ )
311
+ shipper_location = build_location(mapped_response.dig(:shipper, :city), mapped_response.dig(:shipper, :state))
312
+
313
+ actual_delivery_date = mapped_response[:deliverydate]
314
+
315
+ if actual_delivery_date.present?
316
+ comment = mapped_response[:status].downcase
317
+
318
+ if comment.starts_with?('delivered')
319
+ api_date = comment.downcase.split('signed')[0].split('on')[1].strip.sub('at ', '')
320
+ actual_delivery_date = parse_api_date(api_date)
321
+ end
322
+ end
323
+
324
+ shipment_events = []
325
+
326
+ ship_time = parse_api_date(mapped_response[:shipdate])
327
+ # Leave this open for modification later
328
+ picked_up_event = ShipmentEvent.new(location: shipper_location, date_time: ship_time, type_code: :picked_up)
329
+
330
+ scheduled_delivery_date = parse_api_date(mapped_response[:estdeliverydate])
331
+ tracking_number = response.dig(:tracktrace_response, :return, :pronumber)
332
+
333
+ api_events = response.dig(:tracktrace_response, :return, :history)
334
+ api_events = [api_events] if api_events.is_a?(Hash)
335
+
336
+ last_location = nil
337
+
338
+ api_events.each_with_index do |api_event, index|
339
+ next if api_event[:description].blank?
340
+
341
+ event = nil
342
+ @conf.dig(:events, :types).each do |key, val|
343
+ next if api_event[:description].downcase.exclude?(val)
344
+
345
+ event = key
346
+ break
347
+ end
348
+ next if event.blank?
349
+
350
+ location = if api_event[:location].blank?
351
+ case event
352
+ when :departed then last_location
353
+ when :picked_up, :pickup_information_sent_to_carrier then shipper_location
354
+ when :delivered, :out_for_delivery then receiver_location
355
+ end
356
+ else
357
+ parse_location(api_event[:location])
358
+ end
359
+
360
+ api_date_time = "#{api_event[:date]} #{api_event[:time]}"
361
+ date_time = api_event[:date].present? ? parse_api_date_time(api_date_time, location) : nil
362
+
363
+ case event
364
+ when :arrived_at_terminal
365
+ # Duplicate event occurs without location data from API
366
+ break if api_event[:location].blank?
367
+ when :delivered
368
+ actual_delivery_date = date_time
369
+ when :out_for_delivery
370
+ # Do not consider out for delivery when out for delivery and interlined dates match
371
+ next_api_event = api_events[index + 1]
372
+
373
+ break if next_api_event.blank?
374
+
375
+ if next_api_event[:description].include?('INTERLINE') && next_api_event[:date] == api_event[:date]
376
+ shipment_events << ShipmentEvent.new(date_time:, location:, type_code: :departed)
377
+ next
378
+ end
379
+ when :pickup_information_sent_to_carrier
380
+ # Pickup event appears after carrier information sent, let's fix that
381
+ picked_up_event.date_time = date_time.dup
382
+ end
383
+
384
+ last_location = location
385
+
386
+ shipment_events << ShipmentEvent.new(date_time:, location:, type_code: event)
387
+ end
388
+
389
+ shipment_events << picked_up_event
390
+
391
+ if shipment_events.collect(&:date_time).none?(nil)
392
+ shipment_events = shipment_events.sort_by do |shipment_event|
393
+ d = shipment_event.date_time
394
+ d&.local_date_time || d.date_time_with_zone&.to_fs(:db) || d.local_date&.to_fs(:db)
395
+ end
396
+ end
397
+
398
+ status = shipment_events.last&.type_code
399
+
400
+ # Workarounds for false status on certain events when timestamps are in wrong order
401
+ status = :out_for_delivery if shipment_events.find do |shipment_event|
402
+ shipment_event.type_code == :out_for_delivery
403
+ end
404
+ status = :delivered if shipment_events.find { |shipment_event| shipment_event.type_code == :delivered }
405
+
406
+ tracking_response.assign_attributes(
407
+ actual_delivery_date:,
408
+ destination: receiver_location,
409
+ origin: shipper_location,
410
+ scheduled_delivery_date:,
411
+ ship_time:,
412
+ shipment_events:,
413
+ status:,
414
+ tracking_number:,
415
+ )
416
+
417
+ tracking_response
418
+ end
419
+
420
+ def parse_pickup_response(response)
421
+ pickup_response = PickupResponse.new(request: last_request, response:)
422
+
423
+ result = response.dig(:requestpickup_response, :return, :results)
424
+ if result[:errorcode]
425
+ pickup_response.error = FreightKit::ResponseError.new("API Error: #{result[:errorcode]}")
426
+ return pickup_response
427
+ end
428
+
429
+ pickup_number = result[:pickupnumber]
430
+
431
+ if pickup_number == '0'
432
+ pickup_response.error = FreightKit::ResponseError.new('Unknown Error')
433
+ return pickup_response
434
+ end
435
+
436
+ pickup_response.pickup_number = pickup_number
437
+ pickup_response
438
+ end
439
+
440
+ def build_pickup_request(
441
+ delivery_from:,
442
+ delivery_to:,
443
+ dispatcher:,
444
+ pickup_from:,
445
+ pickup_to:,
446
+ scac:,
447
+ service:,
448
+ shipment:
449
+ )
450
+
451
+ shipper_phone = shipment.origin.contact.phone.gsub(/\s+/, '').gsub(/[()-+.]/, '')
452
+ shipper_phone = shipper_phone[1..] if shipper_phone.length == 11
453
+
454
+ request = {
455
+ securityinfo: build_soap_header,
456
+ shipperinfo: {
457
+ name: shipment.origin.contact.company_name,
458
+ contact: shipment.origin.contact.name,
459
+ phonenumber: shipper_phone,
460
+ address1: shipment.origin.address1,
461
+ address2: shipment.origin.address2,
462
+ city: shipment.origin.city,
463
+ state: shipment.origin.province,
464
+ ReadyDate: pickup_from.strftime('%m/%d/%Y'),
465
+ ReadyTime: pickup_from.strftime('%H%M').to_i,
466
+ CloseTime: pickup_to.strftime('%H%M').to_i,
467
+ zip: shipment.origin.postal_code,
468
+ SpecialInstructions: ''
469
+ },
470
+ ShipmentCount: 1,
471
+ shipments: [
472
+ {
473
+ DestZip: shipment.destination.postal_code,
474
+ Pieces: shipment.packages.sum(&:quantity),
475
+ Pallets: shipment.packages.select { |p| p.packaging.pallet? }.sum(&:quantity),
476
+ Weight: shipment.packages.sum { |p| p.pounds(:total).ceil },
477
+ HAZ: shipment.packages.any?(&:hazmat?) ? 'Y' : 'N',
478
+ dblStack: 'N',
479
+ SortSeg: 'N',
480
+ Pro: shipment.pro,
481
+ Liftgate: shipment.accessorials.include?(:liftgate_pickup) ? 'Y' : 'N'
482
+ },
483
+ ]
484
+ }
485
+
486
+ request = wrap_request(request)
487
+ save_request(request)
488
+
489
+ request
490
+ end
491
+
492
+ def parse_document_response(type, tracking_number)
493
+ base_url = build_url(type)
494
+ website_credentials = fetch_credential(:api)
495
+ query_parameter = "&username=#{website_credentials.username}&" \
496
+ "password=#{website_credentials.password}&" \
497
+ "pronumber=#{tracking_number}&" \
498
+ 'format=PDF'
499
+
500
+ url = [base_url, query_parameter].join
501
+
502
+ response = HTTParty.get(url)
503
+
504
+ image = response.deep_symbolize_keys.dig(:ImageRequest, :Image)
505
+ image = image.last if image.is_a?(Array)
506
+ # ImageRequest[:Image] sometimes return an Array and both images are identical
507
+
508
+ document_response = DocumentResponse.new(request: url)
509
+
510
+ unless image
511
+ document_response.error = DocumentNotFoundError.new
512
+ return document_response
513
+ end
514
+
515
+ base64_document_data = image.dig(:ImageData, :__content__)
516
+
517
+ unless base64_document_data
518
+ document_response.error = DocumentNotFoundError.new
519
+ return document_response
520
+ end
521
+
522
+ decoded_pdf_data = Base64.decode64(base64_document_data)
523
+ document_response.assign_attributes(content_type: 'application/pdf', data: decoded_pdf_data)
524
+
525
+ document_response
526
+ end
527
+ end
528
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'freight_kit/platforms/carrier_logistics'
4
+ require 'freight_kit/platforms/next'
5
+ require 'freight_kit/platforms/the_great_information_factory'
data/lib/freight_kit.rb CHANGED
@@ -4,7 +4,6 @@ require 'active_model'
4
4
  require 'active_support/all'
5
5
  require 'active_utils'
6
6
  require 'business_time'
7
- require 'cgi'
8
7
  require 'httparty'
9
8
  require 'measured'
10
9
  require 'mimemagic'
@@ -35,6 +34,26 @@ loader = Zeitwerk::Loader.for_gem
35
34
  loader.collapse("#{__dir__}/freight_kit/errors")
36
35
  loader.collapse("#{__dir__}/freight_kit/models")
37
36
 
37
+ # Carriers, platforms, helpers, and api_clients define top-level constants
38
+ # under FreightKit:: (e.g. FreightKit::ABFS, FreightKit::Rateable) rather
39
+ # than nested under the directory name, so they're loaded manually via the
40
+ # explicit requires below.
41
+ loader.ignore("#{__dir__}/freight_kit/api_clients.rb")
42
+ loader.ignore("#{__dir__}/freight_kit/api_clients")
43
+ loader.ignore("#{__dir__}/freight_kit/carriers.rb")
44
+ loader.ignore("#{__dir__}/freight_kit/carriers")
45
+ loader.ignore("#{__dir__}/freight_kit/helpers.rb")
46
+ loader.ignore("#{__dir__}/freight_kit/helpers")
47
+ loader.ignore("#{__dir__}/freight_kit/platforms.rb")
48
+ loader.ignore("#{__dir__}/freight_kit/platforms")
49
+
38
50
  loader.inflector = FreightKit::Inflector.new
39
51
 
40
52
  loader.setup
53
+
54
+ require 'rmagick'
55
+
56
+ require 'freight_kit/api_clients'
57
+ require 'freight_kit/helpers'
58
+ require 'freight_kit/platforms'
59
+ require 'freight_kit/carriers'