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.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/Rakefile +1 -3
- data/VERSION +1 -1
- data/freight_kit-0.1.21.gem +0 -0
- data/freight_kit.gemspec +8 -19
- data/lib/freight_kit/address_normalizer.rb +1 -3
- data/lib/freight_kit/api_clients/soap_client.rb +30 -5
- data/lib/freight_kit/carrier.rb +6 -7
- data/lib/freight_kit/carriers/abf_freight_system.rb +9 -26
- data/lib/freight_kit/carriers/aloha_freight_forwarders.rb +1 -5
- data/lib/freight_kit/carriers/amado_trucking.rb +1 -5
- data/lib/freight_kit/carriers/clear_lane_freight_systems.rb +6 -15
- data/lib/freight_kit/carriers/cpl_express.rb +1 -5
- data/lib/freight_kit/carriers/cross_country_freight_solutions.rb +6 -17
- data/lib/freight_kit/carriers/dependable_highway_express.rb +6 -18
- data/lib/freight_kit/carriers/forward_air.rb +4 -12
- data/lib/freight_kit/carriers/on_trac.rb +7 -25
- data/lib/freight_kit/carriers/peninsula_truck_lines.rb +4 -20
- data/lib/freight_kit/carriers/roadrunner_transportation_services.rb +2 -8
- data/lib/freight_kit/carriers/saia.rb +4 -19
- data/lib/freight_kit/carriers/southeastern_freight_lines.rb +4 -13
- data/lib/freight_kit/carriers/t_force_worldwide.rb +7 -19
- data/lib/freight_kit/carriers/total_quality_logistics.rb +2 -1
- data/lib/freight_kit/carriers/western_regional_delivery_service.rb +5 -11
- data/lib/freight_kit/carriers/xpo_logistics.rb +5 -17
- data/lib/freight_kit/errors/rate_limit_error.rb +12 -0
- data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +1 -3
- data/lib/freight_kit/helpers/pickupable.rb +1 -10
- data/lib/freight_kit/http_client/httpi_adapter.rb +37 -0
- data/lib/freight_kit/http_client/safe_retries.rb +17 -1
- data/lib/freight_kit/http_client.rb +13 -9
- data/lib/freight_kit/models/credential.rb +2 -15
- data/lib/freight_kit/models/shipment.rb +1 -8
- data/lib/freight_kit/package.rb +3 -13
- data/lib/freight_kit/packaging.rb +1 -16
- data/lib/freight_kit/platforms/carrier_logistics.rb +5 -10
- data/lib/freight_kit/platforms/dsdc.rb +82 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/quote_create.rb +32 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/shipment_create.rb +29 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carrier.rb +37 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carriers.rb +45 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customer.rb +54 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customers.rb +62 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/carrier.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/carriers.rb +30 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/customer.rb +20 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/customers.rb +28 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoice.rb +31 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoices.rb +39 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment.rb +102 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment_documents.rb +29 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipments.rb +110 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/address.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/base.rb +120 -0
- data/lib/freight_kit/platforms/next_tms/orm/broker_carrier.rb +18 -0
- data/lib/freight_kit/platforms/next_tms/orm/broker_customer.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/carrier.rb +14 -0
- data/lib/freight_kit/platforms/next_tms/orm/collection.rb +335 -0
- data/lib/freight_kit/platforms/next_tms/orm/customer.rb +12 -0
- data/lib/freight_kit/platforms/next_tms/orm/invoice.rb +21 -0
- data/lib/freight_kit/platforms/next_tms/orm/record_not_found_error.rb +9 -0
- data/lib/freight_kit/platforms/next_tms/orm/shipment.rb +50 -0
- data/lib/freight_kit/platforms/next_tms/orm.rb +38 -0
- data/lib/freight_kit/platforms/next_tms.rb +414 -55
- data/lib/freight_kit/platforms/the_great_information_factory.rb +4 -12
- data/lib/freight_kit.rb +19 -0
- data/schemas/next_tms.json +110710 -0
- metadata +83 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 170c2e1d454e583098180d4ffa29c937dcc687972ebc896c58056bd3285e02f5
|
|
4
|
+
data.tar.gz: c144297e62782b1f9836b4c24bc0367b2132d9319382c5b2e96668694b953247
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
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
|
-
|
|
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('
|
|
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.
|
|
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.
|
|
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
|
|
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(**
|
|
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
|
data/lib/freight_kit/carrier.rb
CHANGED
|
@@ -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?
|
|
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
|
|
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
|
|
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
|
|
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?
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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,
|