friendly_shipping 0.7.3 → 0.8.1
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/.env.template +4 -0
- data/.env.test +4 -0
- data/CHANGELOG.md +23 -0
- data/friendly_shipping.gemspec +1 -1
- data/lib/friendly_shipping/package_options.rb +4 -0
- data/lib/friendly_shipping/rate.rb +1 -1
- data/lib/friendly_shipping/services/ship_engine/label_customs_options.rb +25 -0
- data/lib/friendly_shipping/services/ship_engine/label_item_options.rb +27 -0
- data/lib/friendly_shipping/services/ship_engine/label_options.rb +5 -1
- data/lib/friendly_shipping/services/ship_engine/label_package_options.rb +2 -1
- data/lib/friendly_shipping/services/ship_engine/serialize_label_shipment.rb +37 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/bad_request.rb +9 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/bad_request_handler.rb +33 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/item_options.rb +31 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/package_options.rb +15 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/parse_carrier_response.rb +49 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/parse_quote_response.rb +72 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/quote_options.rb +34 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/serialize_packages.rb +41 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/serialize_quote_request.rb +109 -0
- data/lib/friendly_shipping/services/ship_engine_ltl.rb +133 -0
- data/lib/friendly_shipping/services/ups/label_item_options.rb +4 -1
- data/lib/friendly_shipping/services/ups/label_package_options.rb +7 -3
- data/lib/friendly_shipping/services/ups/parse_rate_response.rb +3 -3
- data/lib/friendly_shipping/services/ups/parse_shipment_accept_response.rb +1 -1
- data/lib/friendly_shipping/services/ups/serialize_address_snippet.rb +5 -2
- data/lib/friendly_shipping/services/ups/serialize_package_node.rb +11 -1
- data/lib/friendly_shipping/services/ups/serialize_shipment_confirm_request.rb +11 -7
- data/lib/friendly_shipping/services/ups_freight/api_error.rb +2 -0
- data/lib/friendly_shipping/services/ups_freight/generate_location_hash.rb +10 -6
- data/lib/friendly_shipping/services/usps/parse_package_rate.rb +2 -1
- data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +6 -2
- data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +4 -8
- data/lib/friendly_shipping/services/usps/shipping_methods.rb +4 -2
- data/lib/friendly_shipping/services/usps_international/parse_package_rate.rb +81 -0
- data/lib/friendly_shipping/services/usps_international/parse_rate_response.rb +86 -0
- data/lib/friendly_shipping/services/usps_international/rate_estimate_options.rb +28 -0
- data/lib/friendly_shipping/services/usps_international/rate_estimate_package_options.rb +45 -0
- data/lib/friendly_shipping/services/usps_international/serialize_rate_request.rb +75 -0
- data/lib/friendly_shipping/services/usps_international/shipping_methods.rb +38 -0
- data/lib/friendly_shipping/services/usps_international.rb +80 -0
- data/lib/friendly_shipping/version.rb +1 -1
- data/lib/friendly_shipping.rb +2 -0
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bc054d44e12f0d44f1240a8c831918fa7eec158f8cc66ffb104586a2a2f4180
|
4
|
+
data.tar.gz: 900e8bb08f94858c0b147d886f9eaeae72c3db74efe04b7cbe8a3d9e7184f4fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db80927fd83629f29a503512672dd39566e758b20e16d29d92332427e9eabd22aef9f9f03d286468fd36c78ea74dd06906a63b9e631bd7304f3f4cfa372e8718
|
7
|
+
data.tar.gz: 99930e58a990bc2e4a50027cbb5a4a379062f86ca21a897edb3761d594843444f54de3b1ac09b65b4b454c46f52d75941cee688f09f86e8230cb97ae73d0d76a
|
data/.env.template
CHANGED
@@ -5,8 +5,12 @@
|
|
5
5
|
SHIPENGINE_API_KEY=ShipEngine API key
|
6
6
|
SHIPENGINE_CARRIER_ID=Carrier ID from your ShipEngine account to run test labels with
|
7
7
|
|
8
|
+
SHIPENGINE_LTL_CARRIER_ID=LTL carrier ID from your ShipEngine account to run tests with
|
9
|
+
SHIPENGINE_LTL_CARRIER_SCAC=Standard Carrier Alpha Code from your ShipEngine account to run tests with
|
10
|
+
|
8
11
|
UPS_KEY=UPS API access key
|
9
12
|
UPS_LOGIN=UPS login name
|
10
13
|
UPS_PASSWORD=UPS login password
|
14
|
+
UPS_SHIPPER_NUMBER=UPS shipper number
|
11
15
|
|
12
16
|
USPS_LOGIN=USPS login name
|
data/.env.test
ADDED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.8.1] - 2023-08-03
|
8
|
+
- USPS Service: Fix international ounces remainder (#166)
|
9
|
+
- UPS Service: Fix bug causing inflated international product costs (#167)
|
10
|
+
- UPS Service: Add declared value to UPS package serializer (#168)
|
11
|
+
- UPS Service: Add declared value to UPS label package options (#169)
|
12
|
+
- TForce Service: Truncate long values in UPS Freight label request (#170)
|
13
|
+
- USPS Service: Add new USPS Ground Advantage shipping method (#171)
|
14
|
+
- ShipEngine Service: Basic ShipEngine LTL service class (#172)
|
15
|
+
- UPS Service: Add new billing options for Non-Resident Importer (#174)
|
16
|
+
- ShipEngine Service: Request quotes from ShipEngine LTL API (#175)
|
17
|
+
|
18
|
+
## [0.8.0] - 2023-04-18
|
19
|
+
- Rails 7 support: Fix deprecation warning about ActiveSupport#sum (#164)
|
20
|
+
- UPS Service: Truncate product descriptions (#163)
|
21
|
+
- TForce Service: Handle Timeouts gracefully (#162)
|
22
|
+
- UPS Service: Support per-item origin countries for paperless invoices (#161)
|
23
|
+
- USPS Service: Fix for currency formatting when shipping internationally (#160)
|
24
|
+
- ShipEngine Service: Add support for customs information (#159)
|
25
|
+
- UPS Service: Require both name and attention name for international shipping (#158)
|
26
|
+
- UPS Service: Allow third-party billing for taxes and fees (#156)
|
27
|
+
- USPS: New service for international shipping (#155)
|
28
|
+
- UPS Service: Parse missing package charges (#154)
|
29
|
+
|
7
30
|
## [0.7.3] - 2023-01-24
|
8
31
|
- UPS Service: Record USPS tracking code (#153)
|
9
32
|
|
data/friendly_shipping.gemspec
CHANGED
@@ -7,7 +7,7 @@ require "friendly_shipping/version"
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = "friendly_shipping"
|
9
9
|
spec.version = FriendlyShipping::VERSION
|
10
|
-
spec.authors = ["Martin Meyerhoff"]
|
10
|
+
spec.authors = ["Martin Meyerhoff", "Matthew Bass"]
|
11
11
|
spec.email = ["mamhoff@gmail.com"]
|
12
12
|
|
13
13
|
spec.summary = "An integration layer for shipping services"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class ShipEngine
|
6
|
+
# Represents customs options for obtaining international shipment labels.
|
7
|
+
# @option contents [String] The contents of the shipment.
|
8
|
+
# Valid values are: gift, merchandise, returned_goods, documents, sample
|
9
|
+
# @option non_delivery [String] Indicates what should be done if the shipment cannot be delivered.
|
10
|
+
# Valid values are: treat_as_abandoned, return_to_sender
|
11
|
+
class LabelCustomsOptions
|
12
|
+
attr_reader :contents,
|
13
|
+
:non_delivery
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
contents: "merchandise",
|
17
|
+
non_delivery: "return_to_sender"
|
18
|
+
)
|
19
|
+
@contents = contents
|
20
|
+
@non_delivery = non_delivery
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/item_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngine
|
8
|
+
# Represents item options for obtaining shipment labels.
|
9
|
+
# @option commodity_code [String] This item's HS or NMFC code for international shipments.
|
10
|
+
# @option country_of_origin [String] This item's country of origin for international shipments.
|
11
|
+
class LabelItemOptions < FriendlyShipping::ItemOptions
|
12
|
+
attr_reader :commodity_code,
|
13
|
+
:country_of_origin
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
commodity_code: nil,
|
17
|
+
country_of_origin: nil,
|
18
|
+
**kwargs
|
19
|
+
)
|
20
|
+
@commodity_code = commodity_code
|
21
|
+
@country_of_origin = country_of_origin
|
22
|
+
super(**kwargs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'friendly_shipping/shipment_options'
|
4
|
+
require 'friendly_shipping/services/ship_engine/label_customs_options'
|
4
5
|
|
5
6
|
module FriendlyShipping
|
6
7
|
module Services
|
@@ -17,19 +18,22 @@ module FriendlyShipping
|
|
17
18
|
attr_reader :shipping_method,
|
18
19
|
:label_download_type,
|
19
20
|
:label_format,
|
20
|
-
:label_image_id
|
21
|
+
:label_image_id,
|
22
|
+
:customs_options
|
21
23
|
|
22
24
|
def initialize(
|
23
25
|
shipping_method:,
|
24
26
|
label_download_type: :url,
|
25
27
|
label_format: :pdf,
|
26
28
|
label_image_id: nil,
|
29
|
+
customs_options: LabelCustomsOptions.new,
|
27
30
|
**kwargs
|
28
31
|
)
|
29
32
|
@shipping_method = shipping_method
|
30
33
|
@label_download_type = label_download_type
|
31
34
|
@label_format = label_format
|
32
35
|
@label_image_id = label_image_id
|
36
|
+
@customs_options = customs_options
|
33
37
|
super(**kwargs.merge(package_options_class: LabelPackageOptions))
|
34
38
|
end
|
35
39
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'friendly_shipping/package_options'
|
4
|
+
require 'friendly_shipping/services/ship_engine/label_item_options'
|
4
5
|
|
5
6
|
module FriendlyShipping
|
6
7
|
module Services
|
@@ -20,7 +21,7 @@ module FriendlyShipping
|
|
20
21
|
def initialize(package_code: nil, messages: [], **kwargs)
|
21
22
|
@package_code = package_code
|
22
23
|
@messages = messages
|
23
|
-
super(**kwargs)
|
24
|
+
super(**kwargs.merge(item_options_class: LabelItemOptions))
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
@@ -21,6 +21,10 @@ module FriendlyShipping
|
|
21
21
|
shipment_hash[:shipment][:carrier_id] = options.shipping_method.carrier.id
|
22
22
|
end
|
23
23
|
|
24
|
+
if international?(shipment)
|
25
|
+
shipment_hash[:shipment][:customs] = serialize_customs(shipment.packages, options)
|
26
|
+
end
|
27
|
+
|
24
28
|
if test
|
25
29
|
shipment_hash[:test_label] = true
|
26
30
|
end
|
@@ -68,6 +72,35 @@ module FriendlyShipping
|
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
75
|
+
def serialize_customs(packages, options)
|
76
|
+
{
|
77
|
+
contents: options.customs_options.contents,
|
78
|
+
non_delivery: options.customs_options.non_delivery,
|
79
|
+
customs_items: serialize_customs_items(packages, options)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def serialize_customs_items(packages, options)
|
84
|
+
packages.map do |package|
|
85
|
+
package.items.group_by(&:sku).map do |sku, items|
|
86
|
+
reference_item = items.first
|
87
|
+
package_options = options.options_for_package(package)
|
88
|
+
item_options = package_options.options_for_item(reference_item)
|
89
|
+
{
|
90
|
+
sku: sku,
|
91
|
+
description: reference_item.description,
|
92
|
+
quantity: items.count,
|
93
|
+
value: {
|
94
|
+
amount: reference_item.cost.to_d,
|
95
|
+
currency: reference_item.cost.currency.to_s
|
96
|
+
},
|
97
|
+
harmonized_tariff_code: item_options.commodity_code,
|
98
|
+
country_of_origin: item_options.country_of_origin
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end.flatten
|
102
|
+
end
|
103
|
+
|
71
104
|
def serialize_weight(weight)
|
72
105
|
ounces = weight.convert_to(:ounce).value.to_f
|
73
106
|
{
|
@@ -78,6 +111,10 @@ module FriendlyShipping
|
|
78
111
|
}
|
79
112
|
}
|
80
113
|
end
|
114
|
+
|
115
|
+
def international?(shipment)
|
116
|
+
shipment.origin.country != shipment.destination.country
|
117
|
+
end
|
81
118
|
end
|
82
119
|
end
|
83
120
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ship_engine_ltl/bad_request'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngineLTL
|
8
|
+
class BadRequestHandler
|
9
|
+
extend Dry::Monads::Result::Mixin
|
10
|
+
|
11
|
+
def self.call(error, original_request: nil, original_response: nil)
|
12
|
+
if error.http_code == 400
|
13
|
+
Failure(
|
14
|
+
ApiFailure.new(
|
15
|
+
BadRequest.new(error),
|
16
|
+
original_request: original_request,
|
17
|
+
original_response: original_response
|
18
|
+
)
|
19
|
+
)
|
20
|
+
else
|
21
|
+
Failure(
|
22
|
+
ApiFailure.new(
|
23
|
+
error,
|
24
|
+
original_request: original_request,
|
25
|
+
original_response: original_response
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class ShipEngineLTL
|
6
|
+
class ItemOptions < FriendlyShipping::ItemOptions
|
7
|
+
attr_reader :packaging_code,
|
8
|
+
:freight_class,
|
9
|
+
:nmfc_code,
|
10
|
+
:stackable,
|
11
|
+
:hazardous_materials
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
packaging_code: nil,
|
15
|
+
freight_class: nil,
|
16
|
+
nmfc_code: nil,
|
17
|
+
stackable: true,
|
18
|
+
hazardous_materials: false,
|
19
|
+
**kwargs
|
20
|
+
)
|
21
|
+
@packaging_code = packaging_code
|
22
|
+
@freight_class = freight_class
|
23
|
+
@nmfc_code = nmfc_code
|
24
|
+
@stackable = stackable
|
25
|
+
@hazardous_materials = hazardous_materials
|
26
|
+
super(**kwargs)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/ups/label_item_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngineLTL
|
8
|
+
class PackageOptions < FriendlyShipping::PackageOptions
|
9
|
+
def initialize(**kwargs)
|
10
|
+
super(**kwargs.merge(item_options_class: ItemOptions))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngineLTL
|
8
|
+
class ParseCarrierResponse
|
9
|
+
extend Dry::Monads::Result::Mixin
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def call(request:, response:)
|
13
|
+
parsed_json = JSON.parse(response.body)
|
14
|
+
carriers = parsed_json.fetch('carriers', []).map do |carrier_data|
|
15
|
+
FriendlyShipping::Carrier.new(
|
16
|
+
id: carrier_data['carrier_id'],
|
17
|
+
name: carrier_data['name'],
|
18
|
+
data: {
|
19
|
+
countries: carrier_data['countries'],
|
20
|
+
features: carrier_data['features'],
|
21
|
+
scac: carrier_data['scac']
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
if carriers.any?
|
27
|
+
Success(
|
28
|
+
ApiResult.new(
|
29
|
+
carriers,
|
30
|
+
original_request: request,
|
31
|
+
original_response: response
|
32
|
+
)
|
33
|
+
)
|
34
|
+
else
|
35
|
+
errors = parsed_json.fetch('errors', [{ 'message' => 'Unknown error' }])
|
36
|
+
Failure(
|
37
|
+
ApiResult.new(
|
38
|
+
errors.map { |e| e['message'] },
|
39
|
+
original_request: request,
|
40
|
+
original_response: response
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngineLTL
|
8
|
+
class ParseQuoteResponse
|
9
|
+
extend Dry::Monads::Result::Mixin
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def call(request:, response:)
|
13
|
+
parsed_json = JSON.parse(response.body)
|
14
|
+
rates = build_rates(parsed_json)
|
15
|
+
if rates.any?
|
16
|
+
Success(
|
17
|
+
ApiResult.new(
|
18
|
+
rates,
|
19
|
+
original_request: request,
|
20
|
+
original_response: response
|
21
|
+
)
|
22
|
+
)
|
23
|
+
else
|
24
|
+
errors = parsed_json.fetch('errors', [{ 'message' => 'Unknown error' }])
|
25
|
+
Failure(
|
26
|
+
ApiResult.new(
|
27
|
+
errors.map { |e| e['message'] },
|
28
|
+
original_request: request,
|
29
|
+
original_response: response
|
30
|
+
)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_rates(parsed_json)
|
38
|
+
total = build_total(parsed_json)
|
39
|
+
return [] unless total.positive?
|
40
|
+
|
41
|
+
[
|
42
|
+
FriendlyShipping::Rate.new(
|
43
|
+
shipping_method: build_shipping_method(parsed_json),
|
44
|
+
amounts: { total: total }
|
45
|
+
)
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_shipping_method(parsed_json)
|
50
|
+
description = parsed_json.dig("service", "carrier_description")
|
51
|
+
code = parsed_json.dig("service", "code")
|
52
|
+
|
53
|
+
FriendlyShipping::ShippingMethod.new(
|
54
|
+
name: description,
|
55
|
+
service_code: code,
|
56
|
+
multi_package: true
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_total(parsed_json)
|
61
|
+
total_charges = parsed_json.fetch("charges", []).detect { |e| e['type'] == "total" }
|
62
|
+
return 0 unless total_charges
|
63
|
+
|
64
|
+
currency = Money::Currency.new(total_charges.dig("amount", "currency"))
|
65
|
+
value = total_charges.dig("amount", "value")
|
66
|
+
Money.new(value * currency.subunit_to_unit, currency)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/shipment_options'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class ShipEngineLTL
|
8
|
+
class QuoteOptions < ShipmentOptions
|
9
|
+
attr_reader :service_code,
|
10
|
+
:pickup_date,
|
11
|
+
:accessorial_service_codes,
|
12
|
+
:packages_serializer_class
|
13
|
+
|
14
|
+
# @param [String] service_code
|
15
|
+
# @param [Time] pickup_date
|
16
|
+
# @param [Array<String>] accessorial_service_codes
|
17
|
+
# @param [Class] packages_serializer_class
|
18
|
+
def initialize(
|
19
|
+
service_code: nil,
|
20
|
+
pickup_date: nil,
|
21
|
+
accessorial_service_codes: [],
|
22
|
+
packages_serializer_class: SerializePackages,
|
23
|
+
**kwargs
|
24
|
+
)
|
25
|
+
@service_code = service_code
|
26
|
+
@pickup_date = pickup_date
|
27
|
+
@accessorial_service_codes = accessorial_service_codes
|
28
|
+
@packages_serializer_class = packages_serializer_class
|
29
|
+
super(**kwargs.merge(package_options_class: PackageOptions))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class ShipEngineLTL
|
6
|
+
class SerializePackages
|
7
|
+
class << self
|
8
|
+
# @param [Array<Physical::Package>] packages
|
9
|
+
# @param [FriendlyShipping::Services::ShipEngineLTL::QuoteOptions] options
|
10
|
+
def call(packages:, options:)
|
11
|
+
packages.flat_map do |package|
|
12
|
+
package_options = options.options_for_package(package)
|
13
|
+
package.items.map do |item|
|
14
|
+
item_options = package_options.options_for_item(item)
|
15
|
+
{
|
16
|
+
code: item_options.packaging_code,
|
17
|
+
freight_class: item_options.freight_class,
|
18
|
+
nmfc_code: item_options.nmfc_code,
|
19
|
+
description: item.description || "Commodities",
|
20
|
+
dimensions: {
|
21
|
+
width: item.width.convert_to(:inches).value.ceil,
|
22
|
+
height: item.height.convert_to(:inches).value.ceil,
|
23
|
+
length: item.length.convert_to(:inches).value.ceil,
|
24
|
+
unit: "inches"
|
25
|
+
},
|
26
|
+
weight: {
|
27
|
+
value: item.weight.convert_to(:pounds).value.ceil,
|
28
|
+
unit: "pounds"
|
29
|
+
},
|
30
|
+
quantity: 1, # we don't support this yet
|
31
|
+
stackable: item_options.stackable,
|
32
|
+
hazardous_materials: item_options.hazardous_materials
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class ShipEngineLTL
|
6
|
+
class SerializeQuoteRequest
|
7
|
+
class << self
|
8
|
+
# @param [Physical::Shipment] shipment
|
9
|
+
# @param [FriendlyShipping::Services::ShipEngineLTL::QuoteOptions] options
|
10
|
+
def call(shipment:, options:)
|
11
|
+
{
|
12
|
+
shipment: {
|
13
|
+
service_code: options.service_code,
|
14
|
+
pickup_date: options.pickup_date.strftime('%Y-%m-%d'),
|
15
|
+
packages: options.packages_serializer_class.call(packages: shipment.packages, options: options),
|
16
|
+
options: serialize_options(options),
|
17
|
+
ship_from: serialize_ship_address(shipment.origin),
|
18
|
+
ship_to: serialize_ship_address(shipment.destination),
|
19
|
+
bill_to: serialize_bill_address(shipment.origin),
|
20
|
+
requested_by: serialize_requested_by(shipment.origin),
|
21
|
+
}.compact,
|
22
|
+
shipment_measurements: serialize_shipment_measurements(shipment.packages)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# @param [FriendlyShipping::Services::ShipEngineLTL::QuoteOptions] options
|
29
|
+
def serialize_options(options)
|
30
|
+
options.accessorial_service_codes.map do |code|
|
31
|
+
{ code: code }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Physical::Location] location
|
36
|
+
def serialize_ship_address(location)
|
37
|
+
{
|
38
|
+
account: location.properties.with_indifferent_access['account_number'],
|
39
|
+
address: serialize_address(location),
|
40
|
+
contact: serialize_contact(location)
|
41
|
+
}.compact
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [Physical::Location] location
|
45
|
+
def serialize_bill_address(location)
|
46
|
+
{
|
47
|
+
type: "shipper",
|
48
|
+
payment_terms: "prepaid",
|
49
|
+
account: location.properties.with_indifferent_access['account_number'],
|
50
|
+
address: serialize_address(location),
|
51
|
+
contact: serialize_contact(location)
|
52
|
+
}.compact
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [Physical::Location] location
|
56
|
+
def serialize_address(location)
|
57
|
+
{
|
58
|
+
company_name: location.company_name,
|
59
|
+
address_line1: location.address1,
|
60
|
+
city_locality: location.city,
|
61
|
+
state_province: location.region.code,
|
62
|
+
postal_code: location.zip,
|
63
|
+
country_code: location.country.code
|
64
|
+
}.compact
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param [Physical::Location] location
|
68
|
+
def serialize_contact(location)
|
69
|
+
{
|
70
|
+
name: location.name,
|
71
|
+
phone_number: location.phone,
|
72
|
+
email: location.email
|
73
|
+
}.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param [Physical::Location] location
|
77
|
+
def serialize_requested_by(location)
|
78
|
+
{
|
79
|
+
company_name: location.company_name,
|
80
|
+
contact: serialize_contact(location)
|
81
|
+
}.compact
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [Array<Physical::Package>] packages
|
85
|
+
def serialize_shipment_measurements(packages)
|
86
|
+
{
|
87
|
+
total_linear_length: {
|
88
|
+
value: packages.sum(&:length).convert_to(:inches).value.ceil,
|
89
|
+
unit: "inches"
|
90
|
+
},
|
91
|
+
total_width: {
|
92
|
+
value: packages.map(&:width).max.convert_to(:inches).value.ceil,
|
93
|
+
unit: "inches"
|
94
|
+
},
|
95
|
+
total_height: {
|
96
|
+
value: packages.map(&:height).max.convert_to(:inches).value.ceil,
|
97
|
+
unit: "inches"
|
98
|
+
},
|
99
|
+
total_weight: {
|
100
|
+
value: +packages.sum(&:weight).convert_to(:pounds).value.ceil,
|
101
|
+
unit: "pounds"
|
102
|
+
}
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|