freight_kit 0.1.21 → 0.1.22

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/Rakefile +1 -3
  4. data/VERSION +1 -1
  5. data/freight_kit-0.1.21.gem +0 -0
  6. data/freight_kit.gemspec +8 -19
  7. data/lib/freight_kit/address_normalizer.rb +1 -3
  8. data/lib/freight_kit/api_clients/soap_client.rb +30 -5
  9. data/lib/freight_kit/carrier.rb +6 -7
  10. data/lib/freight_kit/carriers/abf_freight_system.rb +9 -26
  11. data/lib/freight_kit/carriers/aloha_freight_forwarders.rb +1 -5
  12. data/lib/freight_kit/carriers/amado_trucking.rb +1 -5
  13. data/lib/freight_kit/carriers/clear_lane_freight_systems.rb +6 -15
  14. data/lib/freight_kit/carriers/cpl_express.rb +1 -5
  15. data/lib/freight_kit/carriers/cross_country_freight_solutions.rb +6 -17
  16. data/lib/freight_kit/carriers/dependable_highway_express.rb +6 -18
  17. data/lib/freight_kit/carriers/forward_air.rb +4 -12
  18. data/lib/freight_kit/carriers/on_trac.rb +7 -25
  19. data/lib/freight_kit/carriers/peninsula_truck_lines.rb +4 -20
  20. data/lib/freight_kit/carriers/roadrunner_transportation_services.rb +2 -8
  21. data/lib/freight_kit/carriers/saia.rb +4 -19
  22. data/lib/freight_kit/carriers/southeastern_freight_lines.rb +4 -13
  23. data/lib/freight_kit/carriers/t_force_worldwide.rb +7 -19
  24. data/lib/freight_kit/carriers/total_quality_logistics.rb +2 -1
  25. data/lib/freight_kit/carriers/western_regional_delivery_service.rb +5 -11
  26. data/lib/freight_kit/carriers/xpo_logistics.rb +5 -17
  27. data/lib/freight_kit/errors/rate_limit_error.rb +12 -0
  28. data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +1 -3
  29. data/lib/freight_kit/helpers/pickupable.rb +1 -10
  30. data/lib/freight_kit/http_client/httpi_adapter.rb +37 -0
  31. data/lib/freight_kit/http_client/safe_retries.rb +17 -1
  32. data/lib/freight_kit/http_client.rb +13 -9
  33. data/lib/freight_kit/models/credential.rb +2 -15
  34. data/lib/freight_kit/models/shipment.rb +1 -8
  35. data/lib/freight_kit/package.rb +3 -13
  36. data/lib/freight_kit/packaging.rb +1 -16
  37. data/lib/freight_kit/platforms/carrier_logistics.rb +5 -10
  38. data/lib/freight_kit/platforms/dsdc.rb +82 -0
  39. data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/quote_create.rb +32 -0
  40. data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/shipment_create.rb +29 -0
  41. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carrier.rb +37 -0
  42. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carriers.rb +45 -0
  43. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customer.rb +54 -0
  44. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customers.rb +62 -0
  45. data/lib/freight_kit/platforms/next_tms/graphql_client/query/carrier.rb +22 -0
  46. data/lib/freight_kit/platforms/next_tms/graphql_client/query/carriers.rb +30 -0
  47. data/lib/freight_kit/platforms/next_tms/graphql_client/query/customer.rb +20 -0
  48. data/lib/freight_kit/platforms/next_tms/graphql_client/query/customers.rb +28 -0
  49. data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoice.rb +31 -0
  50. data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoices.rb +39 -0
  51. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment.rb +102 -0
  52. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment_documents.rb +29 -0
  53. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipments.rb +110 -0
  54. data/lib/freight_kit/platforms/next_tms/graphql_client.rb +22 -0
  55. data/lib/freight_kit/platforms/next_tms/orm/address.rb +22 -0
  56. data/lib/freight_kit/platforms/next_tms/orm/base.rb +120 -0
  57. data/lib/freight_kit/platforms/next_tms/orm/broker_carrier.rb +18 -0
  58. data/lib/freight_kit/platforms/next_tms/orm/broker_customer.rb +22 -0
  59. data/lib/freight_kit/platforms/next_tms/orm/carrier.rb +14 -0
  60. data/lib/freight_kit/platforms/next_tms/orm/collection.rb +335 -0
  61. data/lib/freight_kit/platforms/next_tms/orm/customer.rb +12 -0
  62. data/lib/freight_kit/platforms/next_tms/orm/invoice.rb +21 -0
  63. data/lib/freight_kit/platforms/next_tms/orm/record_not_found_error.rb +9 -0
  64. data/lib/freight_kit/platforms/next_tms/orm/shipment.rb +50 -0
  65. data/lib/freight_kit/platforms/next_tms/orm.rb +38 -0
  66. data/lib/freight_kit/platforms/next_tms.rb +414 -55
  67. data/lib/freight_kit/platforms/the_great_information_factory.rb +4 -12
  68. data/lib/freight_kit.rb +19 -0
  69. data/schemas/next_tms.json +110710 -0
  70. metadata +83 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 543816b71edfd222f466c27be9e96722cb616ebb33768e3985379aea350b36d8
4
- data.tar.gz: 9aba35e8079b412e35cf2a868ac3b7f0b7c7d47ac2a1b595bc2bcb06ba4e920d
3
+ metadata.gz: 170c2e1d454e583098180d4ffa29c937dcc687972ebc896c58056bd3285e02f5
4
+ data.tar.gz: c144297e62782b1f9836b4c24bc0367b2132d9319382c5b2e96668694b953247
5
5
  SHA512:
6
- metadata.gz: 363cd6ebe87b53b68dc5a8a715657ed62069e9bc55a81fdef7c4ad6d056913f1d33651787fd726167337c1ffb38194c9b2c1af84a6501002f62af6aba050ab8c
7
- data.tar.gz: eda608438300dd2581bef08ea6b8c4bb6ab0166d7f98cc1027c034da683e84a49a51b9c31d8681c6481c92c625a09c7fe673820ff63fee5b9e0f9a0e267b497a
6
+ metadata.gz: f2cadca4dbab2f033bf5b761131701a13807b7180ff5dd90f85ca79dc235bc109fa5afa7e908d9bfa19914ec5633b6ecec36653281e3bf0ae728e448b0fa9b1f
7
+ data.tar.gz: 5c6cc78939f8d056a0e9dce8686ccffc1620619a582277074619a403943f335fda9a8935831d913a21e426e4fa488eb2f40ab7cecd0c1fe749a57b8b85995ed8
data/README.md CHANGED
@@ -84,20 +84,22 @@ Debug output contains request headers and bodies, which include carrier credenti
84
84
 
85
85
  The client uses conditional requests: it sends `If-None-Match` and `If-Modified-Since` and reuses the cached body when the carrier replies `304 Not Modified`.
86
86
 
87
- By default, the client keeps cached responses in an in-process `ActiveSupport::Cache::MemoryStore`. To share the cache between processes, set your own backend at boot. The backend must respond to `read`, `write`, and `delete_matched`, like `ActiveSupport::Cache::Store`.
87
+ FreightKit keeps cached data, such as HTTP responses, in `FreightKit.cache`. By default, this is an in-process `ActiveSupport::Cache::MemoryStore`. To share the cache between processes, set your own backend at boot. The backend must respond to `read`, `write`, `fetch`, and `delete_matched`, like `ActiveSupport::Cache::Store`.
88
88
 
89
89
  In Rails, add this line to an initializer:
90
90
 
91
91
  ```ruby
92
- FreightKit::HTTPClient.cache = Rails.cache
92
+ FreightKit.cache = Rails.cache
93
93
  ```
94
94
 
95
95
  In Hanami or plain Ruby, give any compatible store:
96
96
 
97
97
  ```ruby
98
- FreightKit::HTTPClient.cache = ActiveSupport::Cache::RedisCacheStore.new(url: ENV.fetch('REDIS_URL'))
98
+ FreightKit.cache = ActiveSupport::Cache::RedisCacheStore.new(url: ENV.fetch('REDIS_URL'))
99
99
  ```
100
100
 
101
+ To use a different store for HTTP responses only, set `FreightKit::HTTPClient.cache`.
102
+
101
103
  Cache entries contain carrier response bodies. Use a backend that you trust with that data. Keys are SHA-256 digests of the method, the URL, and all request headers. Requests with different credentials thus never share an entry. To turn off the cache, set `ActiveSupport::Cache::NullStore.new`.
102
104
 
103
105
  ## Standard Usage
data/Rakefile CHANGED
@@ -1,6 +1,4 @@
1
1
  desc 'Load FreightKit'
2
- task(:environment) do
3
- require_relative 'lib/freight_kit'
4
- end
2
+ task(:environment) { require_relative 'lib/freight_kit' }
5
3
 
6
4
  Dir[File.expand_path('tasks/*.rake', __dir__)].each { |task| import(task) }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.21
1
+ 0.1.22
Binary file
data/freight_kit.gemspec CHANGED
@@ -4,27 +4,14 @@ Gem::Specification.new do |spec|
4
4
  spec.name = 'freight_kit'
5
5
  spec.version = version
6
6
 
7
- spec.authors = [
8
- 'Third Party Transportation Systems LLC',
9
- 'Brody Hoskins',
10
- 'Sub Pop Records',
11
- 'Shopify'
12
- ]
13
- spec.email = [
14
- 'hello@next-tms.com',
15
- 'brody@brody.digital',
16
- 'webmaster@subpop.com',
17
- 'integrations-team@shopify.com'
18
- ]
7
+ spec.authors = ['Third Party Transportation Systems LLC', 'Brody Hoskins', 'Sub Pop Records', 'Shopify']
8
+ spec.email = ['hello@next-tms.com', 'brody@brody.digital', 'webmaster@subpop.com', 'integrations-team@shopify.com']
19
9
 
20
10
  spec.description = 'Freight carrier API and website abstraction library for transportation management systems (TMS)'
21
11
  spec.homepage = 'https://github.com/next-tms/freight_kit'
22
12
  spec.summary = spec.description
23
13
 
24
- spec.files = Dir['[A-Z]*'] +
25
- Dir['configuration/*/*.yml'] +
26
- Dir['lib/**/*'] +
27
- Dir['test/**/*']
14
+ spec.files = Dir['[A-Z]*'] + Dir['configuration/*/*.yml'] + Dir['lib/**/*'] + Dir['schemas/*'] + Dir['test/**/*']
28
15
  spec.require_paths = ['lib']
29
16
 
30
17
  spec.add_development_dependency('faker', '~> 3.8.0')
@@ -41,14 +28,16 @@ Gem::Specification.new do |spec|
41
28
  spec.add_dependency('brotli', '>= 0.9.2', '< 1') # CVE-2026-111497
42
29
  spec.add_dependency('business_time', '~> 0.13.0')
43
30
  spec.add_dependency('countries', '~> 8.1')
44
- spec.add_dependency('httpx', '>= 1.9.0', '< 2')
31
+ spec.add_dependency('graphql', '>= 2.0', '< 3') # Next TMS
32
+ spec.add_dependency('graphql-client', '>= 0.23.0', '< 1') # Next TMS
33
+ spec.add_dependency('httpx', '>= 1.9.0', '!= 1.9.1', '< 2') # 1.9.1: https://github.com/HoneyryderChuck/httpx/issues/162
45
34
  spec.add_dependency('json', '< 3') # measured
46
- spec.add_dependency('measured', '>= 2.0', '< 3.2.2')
35
+ spec.add_dependency('measured', '>= 2.0', '< 3.2.3')
47
36
  spec.add_dependency('mimemagic', '~> 0.4.3')
48
37
  spec.add_dependency('multi_xml', '>= 0.9.1', '< 1')
49
38
  spec.add_dependency('nokogiri', '>= 1.6', '< 1.20')
50
39
  spec.add_dependency('place_kit', '~> 0.0.6')
51
- spec.add_dependency('savon', '>= 2.17', '< 2.18')
40
+ spec.add_dependency('savon', '>= 2.17', '< 2.19')
52
41
  spec.add_dependency('StreetAddress', '~> 2.0')
53
42
  spec.add_dependency('tzinfo-data', '~> 1.2023', '>= 1.2023.3')
54
43
  spec.add_dependency('watir', '>= 7.0', '< 7.4')
@@ -17,9 +17,7 @@ module FreightKit
17
17
  words = words.take_while { |word| UNIT_DESIGNATORS.exclude?(word) }
18
18
  return unless words.first&.match?(/\d/)
19
19
 
20
- words.map do |word|
21
- StreetAddress::US::DIRECTIONAL[word]&.downcase || StreetAddress::US::STREET_TYPES[word] || word
22
- end.join(' ')
20
+ words.map { |word| StreetAddress::US::DIRECTIONAL[word]&.downcase || StreetAddress::US::STREET_TYPES[word] || word }.join(' ')
23
21
  end
24
22
  end
25
23
  end
@@ -1,14 +1,18 @@
1
1
  module FreightKit
2
2
  class SoapClient
3
3
  API_EXCEPTIONS = [
4
- Savon::HTTPError,
5
- Net::ReadTimeout,
6
- Net::OpenTimeout,
7
4
  Errno::ECONNREFUSED,
8
5
  Errno::ECONNRESET,
9
- Errno::EHOSTUNREACH
6
+ Errno::EHOSTUNREACH,
7
+ HTTPX::ConnectionError,
8
+ HTTPX::TimeoutError,
9
+ Savon::HTTPError
10
10
  ].freeze
11
11
 
12
+ WSDL_CACHE_HEADERS = ['cache-control', 'etag', 'expires', 'last-modified'].freeze
13
+ WSDL_EXPIRES_IN = 1.day
14
+ WSDL_NAMESPACE = 'freight_kit/soap_client/wsdl'.freeze
15
+
12
16
  def initialize(carrier:, action:, client_args:, call_args:, soap_operation:)
13
17
  @carrier = carrier
14
18
  @action = action
@@ -26,7 +30,7 @@ module FreightKit
26
30
  # For testing and debugging
27
31
  # client_args.merge!(log: true, pretty_print_xml: true)
28
32
 
29
- Savon.client(**client_args).call(soap_operation, **call_args).body
33
+ Savon.client(**savon_args).call(soap_operation, **call_args).body
30
34
  rescue *api_exceptions => e
31
35
  response = build_response_class(action:)
32
36
  response.error = ResponseError.new("Network Error: #{e}")
@@ -64,5 +68,26 @@ module FreightKit
64
68
  when :pickup then PickupResponse.new(request:, response: nil)
65
69
  end
66
70
  end
71
+
72
+ def fetch_wsdl(url)
73
+ key = "#{WSDL_NAMESPACE}/#{Digest::SHA256.hexdigest(url)}"
74
+ cached = FreightKit.cache.read(key)
75
+ return cached if cached
76
+
77
+ response = HTTPClient.get(url)
78
+ raise Wasabi::Resolver::HTTPError.new("Error: #{response.code} for url #{url}", response) unless (200..299).cover?(response.code)
79
+
80
+ if WSDL_CACHE_HEADERS.none? { |field| response.headers.key?(field) }
81
+ FreightKit.cache.write(key, response.body, expires_in: WSDL_EXPIRES_IN)
82
+ end
83
+
84
+ response.body
85
+ end
86
+
87
+ def savon_args
88
+ args = client_args.merge(adapter: HTTPClient::HTTPIAdapter::NAME)
89
+ args[:wsdl] = fetch_wsdl(args[:wsdl]) if args[:wsdl].to_s.match?(%r{\Ahttps?://})
90
+ args
91
+ end
67
92
  end
68
93
  end
@@ -72,10 +72,7 @@ module FreightKit
72
72
  # @param package [FreightKit::Package]
73
73
  # @return [Measured::Length]
74
74
  def longest_dimension(package)
75
- [
76
- Measured::Length.new(package.length(:inches), :inches),
77
- Measured::Length.new(package.width(:inches), :inches)
78
- ].max
75
+ [Measured::Length.new(package.length(:inches), :inches), Measured::Length.new(package.width(:inches), :inches)].max
79
76
  end
80
77
 
81
78
  # FreightKit accessorials the carrier prices, mapped to the carrier's codes.
@@ -254,9 +251,7 @@ module FreightKit
254
251
  [package.height(:inches), package.length(:inches), package.width(:inches)].any?(&:zero?)
255
252
  end
256
253
 
257
- uncovered_overlength = packages.any? do |package|
258
- overlength?(package) && overlength_rule_for(tariff, package).nil?
259
- end
254
+ uncovered_overlength = packages.any? { |package| overlength?(package) && overlength_rule_for(tariff, package).nil? }
260
255
 
261
256
  if missing_dimensions
262
257
  message << 'item dimensions are required'
@@ -432,6 +427,10 @@ module FreightKit
432
427
 
433
428
  protected
434
429
 
430
+ # Proxy for HTTP requests, from the optional `:api_proxy` credential.
431
+ # @return [String|NilClass]
432
+ def proxy_url = fetch_credential(:api_proxy)&.proxy_url
433
+
435
434
  # Use after building the request to save for later inspection.
436
435
  # @return [void]
437
436
  def save_request(request) = @last_request = request
@@ -32,9 +32,7 @@ module FreightKit
32
32
  def scac = 'ABFS'
33
33
 
34
34
  def serviceable_accessorials?(accessorials)
35
- unsupported_accessorials = unserviceable_accessorials.select do |key|
36
- accessorials.include?(key)
37
- end.sort!
35
+ unsupported_accessorials = unserviceable_accessorials.select { |key| accessorials.include?(key) }.sort!
38
36
 
39
37
  message = "#{unsupported_accessorials.join(', ')} unserviceable"
40
38
  raise FreightKit::UnserviceableError, message if unsupported_accessorials.any?
@@ -105,9 +103,7 @@ module FreightKit
105
103
  # query params
106
104
  raise FreightKit::UnserviceableError, 'Too many packages' if packages.count > 15
107
105
 
108
- unsupported_packaging_types = packages.map(&:packaging).map(&:type).select do |type|
109
- PACKAGE_TYPES.keys.exclude?(type)
110
- end.sort!
106
+ unsupported_packaging_types = packages.map(&:packaging).map(&:type).select { |type| PACKAGE_TYPES.keys.exclude?(type) }.sort!
111
107
 
112
108
  message = "#{unsupported_packaging_types.join(', ').upcase_first} unserviceable"
113
109
  raise FreightKit::UnserviceableError, message if unsupported_packaging_types.any?
@@ -305,9 +301,7 @@ module FreightKit
305
301
  end
306
302
 
307
303
  other_options = {}.tap do |builder|
308
- alpha2_codes = [shipment.destination.country, shipment.origin.country].map do |country|
309
- country.code(:alpha2).value
310
- end
304
+ alpha2_codes = [shipment.destination.country, shipment.origin.country].map { |country| country.code(:alpha2).value }
311
305
 
312
306
  builder['Acc_ARR'] = 'Y' if shipment.accessorials.include?(:appointment_delivery)
313
307
 
@@ -329,25 +323,15 @@ module FreightKit
329
323
  builder['Acc_HAZ'] = 'Y' if shipment.packages.any?(&:hazmat?)
330
324
  builder['Acc_PALLET'] = 'Y' if shipment.packages.map(&:packaging).all?(&:pallet?)
331
325
 
332
- unless shipment.accessorials.intersect?(%i[
333
- church_pickup
334
- inside_pickup
335
- liftgate_pickup
336
- residential_pickup
337
- restaurant_pickup
338
- ])
326
+ unless shipment.accessorials.intersect?(%i[church_pickup inside_pickup liftgate_pickup residential_pickup restaurant_pickup])
339
327
  builder['Acc_SL'] = 'Y'
340
328
  end
341
329
 
342
330
  builder['Acc_SS'] = 'Y'
343
331
 
344
- longest_dimension_in = if shipment.packages.all? do |package|
345
- package.length(:inch).present? && package.width(:inch).present?
346
- end
332
+ longest_dimension_in = if shipment.packages.all? { |package| package.length(:inch).present? && package.width(:inch).present? }
347
333
 
348
- shipment.packages.map do |package|
349
- [package.length(:in), package.width(:in)].max
350
- end.max.ceil
334
+ shipment.packages.map { |package| [package.length(:in), package.width(:in)].max }.max.ceil
351
335
  end
352
336
 
353
337
  if longest_dimension_in.present?
@@ -367,8 +351,7 @@ module FreightKit
367
351
  end
368
352
  end
369
353
 
370
- params = [account, commodities, consignee, delivery, other_options, pickup, shipper, specifics, third_party]
371
- .reduce({}, :merge)
354
+ params = [account, commodities, consignee, delivery, other_options, pickup, shipper, specifics, third_party].reduce({}, :merge)
372
355
 
373
356
  build_request(:rates, { params: })
374
357
  end
@@ -394,9 +377,9 @@ module FreightKit
394
377
  url = request[:url]
395
378
 
396
379
  response = if method == :post
397
- HTTPClient.post(url, headers:, body:)
380
+ HTTPClient.post(url, headers:, body:, proxy_url:)
398
381
  else
399
- HTTPClient.get(url, headers:)
382
+ HTTPClient.get(url, headers:, proxy_url:)
400
383
  end
401
384
 
402
385
  response.deep_transform_keys! { |key| key.downcase.to_sym }[:abf] if response&.parsed_response
@@ -17,11 +17,7 @@ module FreightKit
17
17
 
18
18
  def contacts
19
19
  [
20
- Contact.new(
21
- department: 'Rating',
22
- email: 'rates@alohafreight.com',
23
- id: '5de89baa-e0df-4374-a3be-018251dd6f4e'
24
- )
20
+ Contact.new(department: 'Rating', email: 'rates@alohafreight.com', id: '5de89baa-e0df-4374-a3be-018251dd6f4e')
25
21
  ]
26
22
  end
27
23
 
@@ -4,11 +4,7 @@ module FreightKit
4
4
  class << self
5
5
  def contacts
6
6
  [
7
- Contact.new(
8
- department: 'Dispatch',
9
- email: 'la.dispatch@amadotrucking.com',
10
- id: 'c5dab2fd-b178-41f7-bc2b-3f53f8717c01'
11
- )
7
+ Contact.new(department: 'Dispatch', email: 'la.dispatch@amadotrucking.com', id: 'c5dab2fd-b178-41f7-bc2b-3f53f8717c01')
12
8
  ]
13
9
  end
14
10
 
@@ -274,9 +274,7 @@ module FreightKit
274
274
  end
275
275
 
276
276
  def parse_amount(amount)
277
- ['$', ','].each do |char|
278
- amount = amount.sub(char, '')
279
- end
277
+ ['$', ','].each { |char| amount = amount.sub(char, '') }
280
278
 
281
279
  return 0 if amount.blank?
282
280
 
@@ -312,13 +310,10 @@ module FreightKit
312
310
 
313
311
  # Hack to get around JavaScript messing up our input
314
312
  sleep(1)
315
- from.chars.each do |char|
316
- browser.text_field(name: 'txtFromDate').append(char)
317
- end
313
+ from.chars.each { |char| browser.text_field(name: 'txtFromDate').append(char) }
318
314
 
319
315
  browser.text_field(name: 'txtToDate').click
320
- browser.element(xpath: '/html/body/form/div[3]/div[4]/div[3]/div[2]/div/div/div[3]/div').wait_until(&:present?)
321
- .click
316
+ browser.element(xpath: '/html/body/form/div[3]/div[4]/div[3]/div[2]/div/div/div[3]/div').wait_until(&:present?).click
322
317
 
323
318
  browser.button(name: 'btnSubmit').click
324
319
 
@@ -335,9 +330,7 @@ module FreightKit
335
330
  return document_response
336
331
  end
337
332
 
338
- browser.element(xpath: '/html/body/form/div[3]/div[4]/div[8]/div/table/tbody/tr/td[12]/a')
339
- .wait_until(&:present?)
340
- .click
333
+ browser.element(xpath: '/html/body/form/div[3]/div[4]/div[8]/div/table/tbody/tr/td[12]/a').wait_until(&:present?).click
341
334
 
342
335
  browser.switch_window
343
336
 
@@ -376,7 +369,7 @@ module FreightKit
376
369
  document_response.request = URI.parse(url)
377
370
 
378
371
  begin
379
- response = HTTPClient.get(url)
372
+ response = HTTPClient.get(url, proxy_url:)
380
373
  rescue StandardError => e
381
374
  document_response.error = e
382
375
  return document_response
@@ -409,9 +402,7 @@ module FreightKit
409
402
 
410
403
  if error.present?
411
404
  if error.include?('do not service this lane')
412
- rate_response.error = UnserviceableError.new(
413
- 'Incorrect ZIP code or no service available at origin and/or destination'
414
- )
405
+ rate_response.error = UnserviceableError.new('Incorrect ZIP code or no service available at origin and/or destination')
415
406
  return rate_response
416
407
  end
417
408
 
@@ -4,11 +4,7 @@ module FreightKit
4
4
  class << self
5
5
  def contacts
6
6
  [
7
- Contact.new(
8
- department: 'Dispatch',
9
- email: 'dispatch@cplexpress.com',
10
- id: 'ba43e014-2e59-43d6-8f39-acb80128b8c9'
11
- ),
7
+ Contact.new(department: 'Dispatch', email: 'dispatch@cplexpress.com', id: 'ba43e014-2e59-43d6-8f39-acb80128b8c9'),
12
8
  Contact.new(department: 'Main', id: 'a7daa1e5-4abb-48de-9893-493e91806a83', phone: '+13104189008')
13
9
  ]
14
10
  end
@@ -128,7 +128,7 @@ module FreightKit
128
128
  def minimum_length_for_overlength_fees = Measured::Length.new(6, :feet)
129
129
  def name = 'CrossCountry Freight Solutions'
130
130
  def overlength_fees_require_tariff? = false
131
- def required_credential_types = %i[api_key api_proxy]
131
+ def required_credential_types = %i[api_key]
132
132
  def requirements = %i[credentials]
133
133
  def scac = 'CCYQ'
134
134
  def unquotable_accessorials = %i[athletic_facility_delivery dock_dropoff dock_pickup]
@@ -174,9 +174,7 @@ module FreightKit
174
174
 
175
175
  next if longest_dimension < 96
176
176
 
177
- package.quantity.times do
178
- accessorials << { Code: 'EXLEN', Factor: longest_dimension }
179
- end
177
+ package.quantity.times { accessorials << { Code: 'EXLEN', Factor: longest_dimension } }
180
178
  end
181
179
 
182
180
  shipment.accessorials.map do |accessorial|
@@ -266,16 +264,8 @@ module FreightKit
266
264
 
267
265
  def build_request(action, body: {}, query: {})
268
266
  api_key = fetch_credential(:api_key).api_key
269
- proxy_url = fetch_credential(:api_proxy).proxy_url
270
-
271
- request = {
272
- url: build_url(action),
273
- headers: { APIKEY: api_key },
274
- method: API.dig(:methods, action),
275
- body:,
276
- query:,
277
- proxy_url:
278
- }.compact
267
+
268
+ request = { url: build_url(action), headers: { APIKEY: api_key }, method: API.dig(:methods, action), body:, query: }.compact
279
269
 
280
270
  save_request(request)
281
271
  request
@@ -291,7 +281,7 @@ module FreightKit
291
281
  headers: request[:headers].merge(JSON_HEADERS),
292
282
  body: request[:body],
293
283
  query: request[:query],
294
- proxy: request[:proxy_url]
284
+ proxy_url:
295
285
  )
296
286
 
297
287
  unless response.code == 200
@@ -377,8 +367,7 @@ module FreightKit
377
367
  expires_at = ::Time.iso8601(response['QuoteExpiryDate'])
378
368
 
379
369
  transit_days = (
380
- ::Time.iso8601(response['EarliestDeliveryDate']).to_date -
381
- ::Time.iso8601(response['PickupDate']).to_date
370
+ ::Time.iso8601(response['EarliestDeliveryDate']).to_date - ::Time.iso8601(response['PickupDate']).to_date
382
371
  ).to_i
383
372
 
384
373
  prices = []
@@ -155,10 +155,7 @@ module FreightKit
155
155
  },
156
156
  use_ssl: { rates: false, track: false }
157
157
  }.freeze
158
- CITY_STATES = {
159
- ca: ['Los Angeles', 'Mammoth Lakes', 'Sacramento', 'Redding', 'West Sacramento'],
160
- nv: ['Reno', 'Sparks']
161
- }.freeze
158
+ CITY_STATES = { ca: ['Los Angeles', 'Mammoth Lakes', 'Sacramento', 'Redding', 'West Sacramento'], nv: ['Reno', 'Sparks'] }.freeze
162
159
  EVENTS = {
163
160
  types: {
164
161
  arrived_at_terminal: 'linehaul arrived',
@@ -180,8 +177,7 @@ module FreightKit
180
177
  country_codes = [shipment.destination.country.code(:alpha2).value, shipment.origin.country.code(:alpha2).value]
181
178
 
182
179
  if country_codes.reject { |c| c.upcase == 'US' }.any?
183
- raise UnserviceableError,
184
- "No service from #{shipment.origin.postal_code} to #{shipment.destination.postal_code}"
180
+ raise UnserviceableError, "No service from #{shipment.origin.postal_code} to #{shipment.destination.postal_code}"
185
181
  end
186
182
 
187
183
  accessorials = []
@@ -262,9 +258,7 @@ module FreightKit
262
258
  def parse_amount(amount)
263
259
  negative = amount.include?('(') && amount.include?(')')
264
260
 
265
- ['$', ',', '(', ')'].each do |char|
266
- amount = amount.sub(char, '')
267
- end
261
+ ['$', ',', '(', ')'].each { |char| amount = amount.sub(char, '') }
268
262
 
269
263
  return 0 if amount.blank?
270
264
 
@@ -333,8 +327,7 @@ module FreightKit
333
327
  browser.button(name: 'ctl00$ContentPlaceHolder1$btnSubmit').click
334
328
 
335
329
  begin
336
- browser.element(xpath: '//*[@id="ContentPlaceHolder1_GridView1"]/tbody/tr[2]/td[2]/a')
337
- .click
330
+ browser.element(xpath: '//*[@id="ContentPlaceHolder1_GridView1"]/tbody/tr[2]/td[2]/a').click
338
331
  rescue Watir::Exception::UnknownObjectException
339
332
  document_response.error = DocumentNotFoundError.new
340
333
  return document_response
@@ -373,7 +366,7 @@ module FreightKit
373
366
  document_response.request = URI.parse(url)
374
367
 
375
368
  begin
376
- response = HTTPClient.get(url)
369
+ response = HTTPClient.get(url, proxy_url:)
377
370
  rescue StandardError => e
378
371
  document_response.error = e
379
372
  return document_response
@@ -438,12 +431,7 @@ module FreightKit
438
431
 
439
432
  prices = []
440
433
 
441
- shipment_details = response.dig(
442
- :get_rates_response,
443
- :get_rates_result,
444
- :shipment_detail_response,
445
- :shipment_detail_row
446
- )
434
+ shipment_details = response.dig(:get_rates_response, :get_rates_result, :shipment_detail_response, :shipment_detail_row)
447
435
 
448
436
  shipment_details.each do |shipment_detail|
449
437
  next if shipment_detail[:charge].blank?
@@ -316,22 +316,14 @@ module FreightKit
316
316
  end
317
317
 
318
318
  def build_document_request(doc_id:, tracking_number:)
319
- request = {
320
- url: build_url(:document, doc_id:, tracking_number:),
321
- headers: build_headers,
322
- method: API.dig(:methods, :documents)
323
- }
319
+ request = { url: build_url(:document, doc_id:, tracking_number:), headers: build_headers, method: API.dig(:methods, :documents) }
324
320
 
325
321
  save_request(request)
326
322
  request
327
323
  end
328
324
 
329
325
  def build_documents_request(tracking_number)
330
- request = {
331
- url: build_url(:documents, tracking_number:),
332
- headers: build_headers,
333
- method: API.dig(:methods, :documents)
334
- }
326
+ request = { url: build_url(:documents, tracking_number:), headers: build_headers, method: API.dig(:methods, :documents) }
335
327
 
336
328
  save_request(request)
337
329
  request
@@ -570,9 +562,9 @@ module FreightKit
570
562
 
571
563
  response = case method
572
564
  when :post
573
- HTTPClient.post(url, headers:, body:)
565
+ HTTPClient.post(url, headers:, body:, proxy_url:)
574
566
  else
575
- HTTPClient.get(url, headers:)
567
+ HTTPClient.get(url, headers:, proxy_url:)
576
568
  end
577
569
 
578
570
  if (200..299).exclude?(response.code)
@@ -155,29 +155,11 @@ module FreightKit
155
155
  service:,
156
156
  shipment:
157
157
  )
158
- request = build_shipment_request(
159
- delivery_from:,
160
- delivery_to:,
161
- dispatcher:,
162
- pickup_from:,
163
- pickup_to:,
164
- scac:,
165
- service:,
166
- shipment:
167
- )
158
+ request = build_shipment_request(delivery_from:, delivery_to:, dispatcher:, pickup_from:, pickup_to:, scac:, service:, shipment:)
168
159
 
169
160
  labels = parse_shipment_response(commit(request))
170
161
 
171
- request = build_pickup_request(
172
- delivery_from:,
173
- delivery_to:,
174
- dispatcher:,
175
- pickup_from:,
176
- pickup_to:,
177
- scac:,
178
- service:,
179
- shipment:
180
- )
162
+ request = build_pickup_request(delivery_from:, delivery_to:, dispatcher:, pickup_from:, pickup_to:, scac:, service:, shipment:)
181
163
 
182
164
  parse_pickup_response(response: commit(request), labels:)
183
165
  end
@@ -417,9 +399,9 @@ module FreightKit
417
399
 
418
400
  response = case method
419
401
  when :post
420
- HTTPClient.post(url, headers:, body:)
402
+ HTTPClient.post(url, headers:, body:, proxy_url:)
421
403
  else
422
- HTTPClient.get(url, headers:)
404
+ HTTPClient.get(url, headers:, proxy_url:)
423
405
  end
424
406
 
425
407
  response.parsed_response if response&.parsed_response
@@ -487,8 +469,8 @@ module FreightKit
487
469
  prices = []
488
470
  transit_days = nil
489
471
 
490
- api_shipments = response.dig('OnTracRateResponse', 'Shipments', 'Shipment')
491
- api_shipments = [api_shipments] unless api_shipments.is_a?(Array)
472
+ api_shipments = Array.wrap(response.dig('OnTracRateResponse', 'Shipments', 'Shipment'))
473
+ return rate_response.tap { |item| item.error = ResponseError.new('API Error: Blank shipment') } if api_shipments.empty?
492
474
 
493
475
  if api_shipments.any? { |api_shipment| api_shipment['Error']&.downcase&.include?('not serviced') }
494
476
  rate_response.error = UnserviceableError.new
@@ -546,7 +528,7 @@ module FreightKit
546
528
  end
547
529
 
548
530
  api_shipments = response.dig('OnTracShipmentResponse', 'Shipments', 'Shipment')
549
- api_shipments = [api_shipments] unless api_shipments.is_a?(Array)
531
+ api_shipments = Array.wrap(api_shipments)
550
532
 
551
533
  base64_labels = api_shipments&.map { |s| s['Label'] }
552
534
  raise FreightKit::ResponseError, 'API Error: Blank label' if base64_labels.blank?
@@ -100,21 +100,13 @@ module FreightKit
100
100
 
101
101
  call_args = { message: request }
102
102
 
103
- ::FreightKit::SoapClient.new(
104
- carrier: self,
105
- action:,
106
- client_args:,
107
- call_args:,
108
- soap_operation: API.dig(:actions, action)
109
- ).call
103
+ ::FreightKit::SoapClient.new(carrier: self, action:, client_args:, call_args:, soap_operation: API.dig(:actions, action)).call
110
104
  end
111
105
 
112
106
  def parse_amount(amount)
113
107
  negative = amount.start_with?('-$') || amount.start_with?('-')
114
108
 
115
- ['$', '-', ','].each do |char|
116
- amount = amount.sub(char, '')
117
- end
109
+ ['$', '-', ','].each { |char| amount = amount.sub(char, '') }
118
110
 
119
111
  return 0 if amount.blank?
120
112
 
@@ -182,17 +174,9 @@ module FreightKit
182
174
  )
183
175
  end
184
176
 
185
- prices << Price.new(
186
- blame: :api,
187
- cents: parse_amount(result.dig(:quote, :discount_amount)),
188
- description: 'Discount'
189
- )
177
+ prices << Price.new(blame: :api, cents: parse_amount(result.dig(:quote, :discount_amount)), description: 'Discount')
190
178
 
191
- prices << Price.new(
192
- blame: :api,
193
- cents: parse_amount(result.dig(:quote, :fsc_amount)),
194
- description: 'Fuel surcharge'
195
- )
179
+ prices << Price.new(blame: :api, cents: parse_amount(result.dig(:quote, :fsc_amount)), description: 'Fuel surcharge')
196
180
 
197
181
  rate = Rate.new(
198
182
  carrier: self,