friendly_shipping 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.env.template +3 -0
- data/.env.test.local.template +6 -0
- data/.gitignore +1 -0
- data/.rubocop-relaxed.yml +7 -23
- data/.rubocop.yml +11 -2
- data/.rubocop_todo.yml +21 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +17 -0
- data/friendly_shipping.gemspec +4 -13
- data/lib/friendly_shipping/api_error.rb +6 -4
- data/lib/friendly_shipping/api_error_handler.rb +10 -6
- data/lib/friendly_shipping/api_failure.rb +4 -0
- data/lib/friendly_shipping/api_result.rb +16 -4
- data/lib/friendly_shipping/carrier.rb +28 -8
- data/lib/friendly_shipping/http_client.rb +25 -2
- data/lib/friendly_shipping/inflections.rb +10 -0
- data/lib/friendly_shipping/item_options.rb +3 -0
- data/lib/friendly_shipping/label.rb +41 -20
- data/lib/friendly_shipping/package_options.rb +21 -2
- data/lib/friendly_shipping/rate.rb +50 -15
- data/lib/friendly_shipping/request.rb +23 -7
- data/lib/friendly_shipping/response.rb +21 -6
- data/lib/friendly_shipping/services/rl/api_error.rb +33 -0
- data/lib/friendly_shipping/services/rl/bol_options.rb +107 -0
- data/lib/friendly_shipping/services/rl/bol_packages_serializer.rb +32 -0
- data/lib/friendly_shipping/services/rl/bol_structures_serializer.rb +31 -0
- data/lib/friendly_shipping/services/rl/item_options.rb +38 -0
- data/lib/friendly_shipping/services/rl/package_options.rb +40 -0
- data/lib/friendly_shipping/services/rl/parse_create_bol_response.rb +46 -0
- data/lib/friendly_shipping/services/rl/parse_invoice_response.rb +50 -0
- data/lib/friendly_shipping/services/rl/parse_print_bol_response.rb +47 -0
- data/lib/friendly_shipping/services/rl/parse_print_shipping_labels_response.rb +47 -0
- data/lib/friendly_shipping/services/rl/parse_rate_quote_response.rb +66 -0
- data/lib/friendly_shipping/services/rl/parse_transit_times_response.rb +76 -0
- data/lib/friendly_shipping/services/rl/rate_quote_options.rb +86 -0
- data/lib/friendly_shipping/services/rl/rate_quote_packages_serializer.rb +54 -0
- data/lib/friendly_shipping/services/rl/rate_quote_structures_serializer.rb +53 -0
- data/lib/friendly_shipping/services/rl/serialize_create_bol_request.rb +86 -0
- data/lib/friendly_shipping/services/rl/serialize_location.rb +46 -0
- data/lib/friendly_shipping/services/rl/serialize_rate_quote_request.rb +69 -0
- data/lib/friendly_shipping/services/rl/serialize_transit_times_request.rb +38 -0
- data/lib/friendly_shipping/services/rl/shipment_document.rb +40 -0
- data/lib/friendly_shipping/services/rl/shipment_information.rb +41 -0
- data/lib/friendly_shipping/services/rl/shipment_options.rb +50 -0
- data/lib/friendly_shipping/services/rl/shipping_methods.rb +28 -0
- data/lib/friendly_shipping/services/rl/structure_options.rb +13 -0
- data/lib/friendly_shipping/services/rl.rb +204 -0
- data/lib/friendly_shipping/services/ship_engine/api_error.rb +33 -0
- data/lib/friendly_shipping/services/ship_engine/customs_items_serializer.rb +36 -0
- data/lib/friendly_shipping/services/ship_engine/label_customs_options.rb +10 -7
- data/lib/friendly_shipping/services/ship_engine/label_item_options.rb +10 -5
- data/lib/friendly_shipping/services/ship_engine/label_options.rb +31 -14
- data/lib/friendly_shipping/services/ship_engine/label_package_options.rb +18 -11
- data/lib/friendly_shipping/services/ship_engine/parse_address_validation_response.rb +77 -0
- data/lib/friendly_shipping/services/ship_engine/parse_carrier_response.rb +9 -0
- data/lib/friendly_shipping/services/ship_engine/parse_label_response.rb +4 -0
- data/lib/friendly_shipping/services/ship_engine/{parse_rate_estimate_response.rb → parse_rate_estimates_response.rb} +26 -6
- data/lib/friendly_shipping/services/ship_engine/parse_rates_response.rb +101 -0
- data/lib/friendly_shipping/services/ship_engine/parse_void_response.rb +4 -0
- data/lib/friendly_shipping/services/ship_engine/rate_estimates_options.rb +17 -4
- data/lib/friendly_shipping/services/ship_engine/rates_item_options.rb +28 -0
- data/lib/friendly_shipping/services/ship_engine/rates_options.rb +61 -0
- data/lib/friendly_shipping/services/ship_engine/rates_package_options.rb +20 -0
- data/lib/friendly_shipping/services/ship_engine/serialize_address_residential_indicator.rb +27 -0
- data/lib/friendly_shipping/services/ship_engine/serialize_address_validation_request.rb +31 -0
- data/lib/friendly_shipping/services/ship_engine/serialize_label_shipment.rb +22 -27
- data/lib/friendly_shipping/services/ship_engine/serialize_rate_estimate_request.rb +41 -16
- data/lib/friendly_shipping/services/ship_engine/serialize_rates_request.rb +126 -0
- data/lib/friendly_shipping/services/ship_engine.rb +94 -21
- data/lib/friendly_shipping/services/ship_engine_ltl/api_error.rb +12 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/item_options.rb +24 -5
- data/lib/friendly_shipping/services/ship_engine_ltl/package_options.rb +39 -4
- data/lib/friendly_shipping/services/ship_engine_ltl/parse_carrier_response.rb +4 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/parse_quote_response.rb +10 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/quote_options.rb +29 -11
- data/lib/friendly_shipping/services/ship_engine_ltl/serialize_packages.rb +7 -2
- data/lib/friendly_shipping/services/ship_engine_ltl/serialize_quote_request.rb +50 -16
- data/lib/friendly_shipping/services/ship_engine_ltl/serialize_structures.rb +42 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/shipment_options.rb +47 -0
- data/lib/friendly_shipping/services/ship_engine_ltl/structure_options.rb +17 -0
- data/lib/friendly_shipping/services/ship_engine_ltl.rb +50 -30
- data/lib/friendly_shipping/services/tforce_freight/access_token.rb +43 -0
- data/lib/friendly_shipping/services/tforce_freight/api_error.rb +42 -0
- data/lib/friendly_shipping/services/tforce_freight/bol_options.rb +180 -0
- data/lib/friendly_shipping/services/tforce_freight/document_options.rb +100 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_commodity_information.rb +92 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_create_bol_request_hash.rb +165 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_document_options_hash.rb +36 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_handling_units_hash.rb +51 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_location_hash.rb +25 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_pickup_request_hash.rb +113 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_rates_request_hash.rb +65 -0
- data/lib/friendly_shipping/services/tforce_freight/generate_reference_hash.rb +28 -0
- data/lib/friendly_shipping/services/tforce_freight/item_options.rb +93 -0
- data/lib/friendly_shipping/services/tforce_freight/package_options.rb +121 -0
- data/lib/friendly_shipping/services/tforce_freight/parse_create_bol_response.rb +94 -0
- data/lib/friendly_shipping/services/tforce_freight/parse_pickup_response.rb +45 -0
- data/lib/friendly_shipping/services/tforce_freight/parse_rates_response.rb +58 -0
- data/lib/friendly_shipping/services/tforce_freight/parse_shipment_document.rb +31 -0
- data/lib/friendly_shipping/services/tforce_freight/pickup_options.rb +82 -0
- data/lib/friendly_shipping/services/tforce_freight/rates_item_options.rb +12 -0
- data/lib/friendly_shipping/services/tforce_freight/rates_options.rb +162 -0
- data/lib/friendly_shipping/services/tforce_freight/rates_package_options.rb +12 -0
- data/lib/friendly_shipping/services/tforce_freight/shipment_document.rb +38 -0
- data/lib/friendly_shipping/services/tforce_freight/shipment_information.rb +104 -0
- data/lib/friendly_shipping/services/tforce_freight/shipment_options.rb +49 -0
- data/lib/friendly_shipping/services/tforce_freight/shipping_methods.rb +25 -0
- data/lib/friendly_shipping/services/tforce_freight/structure_options.rb +44 -0
- data/lib/friendly_shipping/services/tforce_freight.rb +202 -0
- data/lib/friendly_shipping/services/ups/label_options.rb +14 -2
- data/lib/friendly_shipping/services/ups/label_package_options.rb +1 -1
- data/lib/friendly_shipping/services/ups/parse_modifier_element.rb +29 -0
- data/lib/friendly_shipping/services/ups/parse_rate_response.rb +12 -0
- data/lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb +1 -1
- data/lib/friendly_shipping/services/ups/rate_estimate_options.rb +14 -1
- data/lib/friendly_shipping/services/ups/serialize_rating_service_selection_request.rb +10 -1
- data/lib/friendly_shipping/services/ups/serialize_shipment_accept_request.rb +1 -1
- data/lib/friendly_shipping/services/ups/serialize_shipment_confirm_request.rb +2 -1
- data/lib/friendly_shipping/services/ups/shipping_methods.rb +1 -1
- data/lib/friendly_shipping/services/ups.rb +1 -1
- data/lib/friendly_shipping/services/ups_freight/api_error.rb +8 -5
- data/lib/friendly_shipping/services/ups_freight/generate_commodity_information.rb +65 -19
- data/lib/friendly_shipping/services/ups_freight/generate_freight_rate_request_hash.rb +3 -20
- data/lib/friendly_shipping/services/ups_freight/generate_freight_ship_request_hash.rb +2 -20
- data/lib/friendly_shipping/services/ups_freight/generate_handling_units_hash.rb +54 -0
- data/lib/friendly_shipping/services/ups_freight/label_options.rb +36 -10
- data/lib/friendly_shipping/services/ups_freight/label_structure_options.rb +13 -0
- data/lib/friendly_shipping/services/ups_freight/parse_freight_label_response.rb +2 -2
- data/lib/friendly_shipping/services/ups_freight/parse_shipment_document.rb +1 -1
- data/lib/friendly_shipping/services/ups_freight/rates_item_options.rb +18 -9
- data/lib/friendly_shipping/services/ups_freight/rates_options.rb +31 -21
- data/lib/friendly_shipping/services/ups_freight/rates_package_options.rb +79 -8
- data/lib/friendly_shipping/services/ups_freight/rates_structure_options.rb +46 -0
- data/lib/friendly_shipping/services/ups_freight/shipment_information.rb +7 -3
- data/lib/friendly_shipping/services/ups_freight/shipment_options.rb +49 -0
- data/lib/friendly_shipping/services/ups_freight.rb +3 -1
- data/lib/friendly_shipping/services/ups_json/access_token.rb +29 -0
- data/lib/friendly_shipping/services/ups_json/api_error.rb +29 -0
- data/lib/friendly_shipping/services/ups_json/generate_address_classification_payload.rb +29 -0
- data/lib/friendly_shipping/services/ups_json/generate_address_hash.rb +30 -0
- data/lib/friendly_shipping/services/ups_json/generate_labels_payload.rb +211 -0
- data/lib/friendly_shipping/services/ups_json/generate_package_hash.rb +76 -0
- data/lib/friendly_shipping/services/ups_json/generate_rates_payload.rb +86 -0
- data/lib/friendly_shipping/services/ups_json/generate_timings_payload.rb +44 -0
- data/lib/friendly_shipping/services/ups_json/label.rb +20 -0
- data/lib/friendly_shipping/services/ups_json/label_billing_options.rb +41 -0
- data/lib/friendly_shipping/services/ups_json/label_item_options.rb +77 -0
- data/lib/friendly_shipping/services/ups_json/label_options.rb +177 -0
- data/lib/friendly_shipping/services/ups_json/label_package_options.rb +51 -0
- data/lib/friendly_shipping/services/ups_json/parse_address_classification_response.rb +31 -0
- data/lib/friendly_shipping/services/ups_json/parse_json_response.rb +44 -0
- data/lib/friendly_shipping/services/ups_json/parse_labels_response.rb +71 -0
- data/lib/friendly_shipping/services/ups_json/parse_money_hash.rb +128 -0
- data/lib/friendly_shipping/services/ups_json/parse_rate_modifier_hash.rb +28 -0
- data/lib/friendly_shipping/services/ups_json/parse_rates_response.rb +105 -0
- data/lib/friendly_shipping/services/ups_json/parse_timings_response.rb +56 -0
- data/lib/friendly_shipping/services/ups_json/parse_void_response.rb +32 -0
- data/lib/friendly_shipping/services/ups_json/rates_item_options.rb +22 -0
- data/lib/friendly_shipping/services/ups_json/rates_options.rb +113 -0
- data/lib/friendly_shipping/services/ups_json/rates_package_options.rb +17 -0
- data/lib/friendly_shipping/services/ups_json/shipping_methods.rb +111 -0
- data/lib/friendly_shipping/services/ups_json/timings_options.rb +33 -0
- data/lib/friendly_shipping/services/ups_json.rb +216 -0
- data/lib/friendly_shipping/services/usps/choose_package_rate.rb +3 -3
- data/lib/friendly_shipping/services/usps/machinable_package.rb +1 -1
- data/lib/friendly_shipping/services/usps/parse_package_rate.rb +6 -6
- data/lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb +6 -6
- data/lib/friendly_shipping/services/usps/rate_estimate_options.rb +1 -1
- data/lib/friendly_shipping/services/usps/rate_estimate_package_options.rb +6 -1
- data/lib/friendly_shipping/services/usps/serialize_rate_request.rb +2 -2
- data/lib/friendly_shipping/services/usps/shipping_methods.rb +4 -3
- data/lib/friendly_shipping/services/usps.rb +1 -1
- data/lib/friendly_shipping/services/usps_international/parse_package_rate.rb +3 -3
- data/lib/friendly_shipping/services/usps_international/rate_estimate_options.rb +1 -1
- data/lib/friendly_shipping/services/usps_international/serialize_rate_request.rb +1 -1
- data/lib/friendly_shipping/services/usps_international.rb +1 -1
- data/lib/friendly_shipping/services/usps_ship/access_token.rb +37 -0
- data/lib/friendly_shipping/services/usps_ship/api_error.rb +29 -0
- data/lib/friendly_shipping/services/usps_ship/machinable_package.rb +53 -0
- data/lib/friendly_shipping/services/usps_ship/parse_rate_estimates_response.rb +80 -0
- data/lib/friendly_shipping/services/usps_ship/parse_timings_response.rb +80 -0
- data/lib/friendly_shipping/services/usps_ship/rate_estimate_options.rb +45 -0
- data/lib/friendly_shipping/services/usps_ship/rate_estimate_package_options.rb +124 -0
- data/lib/friendly_shipping/services/usps_ship/serialize_rate_estimates_request.rb +55 -0
- data/lib/friendly_shipping/services/usps_ship/shipping_methods.rb +38 -0
- data/lib/friendly_shipping/services/{ship_engine_ltl/bad_request.rb → usps_ship/timing_options.rb} +2 -2
- data/lib/friendly_shipping/services/usps_ship.rb +199 -0
- data/lib/friendly_shipping/shipment_options.rb +13 -1
- data/lib/friendly_shipping/shipping_method.rb +38 -11
- data/lib/friendly_shipping/structure_options.rb +38 -0
- data/lib/friendly_shipping/timing.rb +42 -7
- data/lib/friendly_shipping/version.rb +1 -1
- data/lib/friendly_shipping.rb +6 -0
- metadata +140 -174
- data/lib/friendly_shipping/services/ship_engine/bad_request.rb +0 -29
- data/lib/friendly_shipping/services/ship_engine/bad_request_handler.rb +0 -33
- data/lib/friendly_shipping/services/ship_engine_ltl/bad_request_handler.rb +0 -33
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Options for getting documents (BOLs, labels, etc.) from the API.
|
7
|
+
class DocumentOptions
|
8
|
+
# @return [Symbol] the document type (see {DOCUMENT_TYPES})
|
9
|
+
attr_reader :type
|
10
|
+
|
11
|
+
# @return [Symbol] the document format (see {DOCUMENT_FORMATS})
|
12
|
+
attr_reader :format
|
13
|
+
|
14
|
+
# @return [Boolean] whether or not the label is for thermal printers (DEPRECATED: use `label_type` instead)
|
15
|
+
attr_reader :thermal
|
16
|
+
|
17
|
+
# @return [String] the type of label
|
18
|
+
attr_reader :label_type
|
19
|
+
|
20
|
+
# @return [Integer] the start position of the sticker
|
21
|
+
attr_reader :start_position
|
22
|
+
|
23
|
+
# @return [Integer] the number of stickers
|
24
|
+
attr_reader :number_of_stickers
|
25
|
+
|
26
|
+
# Maps friendly names to document types.
|
27
|
+
DOCUMENT_TYPES = {
|
28
|
+
label: "30",
|
29
|
+
tforce_bol: "20",
|
30
|
+
vics_bol: "21"
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
# Maps friendly names to document formats.
|
34
|
+
DOCUMENT_FORMATS = {
|
35
|
+
pdf: "01"
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
# Maps booleans to thermal codes.
|
39
|
+
THERMAL_CODE = {
|
40
|
+
false => "01",
|
41
|
+
true => "02"
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
# Maps friendly names to label types.
|
45
|
+
LABEL_TYPE_CODES = {
|
46
|
+
address_labels_with_pro_nums: "01",
|
47
|
+
address_labels_without_pro_nums: "02",
|
48
|
+
pro_stickers: "03",
|
49
|
+
address_labels_1x1: "04",
|
50
|
+
address_labels_2x1: "05",
|
51
|
+
address_labels_2x2: "06",
|
52
|
+
thermal_labels_4x6: "07",
|
53
|
+
thermal_labels_4x8: "08"
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
# @param type [Symbol] the document type (see [DOCUMENT_TYPES])
|
57
|
+
# @param format [Symbol] the document format (see [DOCUMENT_FORMATS])
|
58
|
+
# @param thermal [Boolean] whether or not the label is for thermal printers (DEPRECATED: use `label_type` instead)
|
59
|
+
# @param start_position [Integer] the start position of the sticker
|
60
|
+
# @param number_of_stickers [Integer] the number of stickers
|
61
|
+
def initialize(
|
62
|
+
type: :label,
|
63
|
+
format: :pdf,
|
64
|
+
thermal: false,
|
65
|
+
label_type: :thermal_labels_4x6,
|
66
|
+
start_position: 1,
|
67
|
+
number_of_stickers: 1
|
68
|
+
)
|
69
|
+
@type = type
|
70
|
+
@format = format
|
71
|
+
@thermal = thermal
|
72
|
+
@label_type = label_type
|
73
|
+
@start_position = start_position
|
74
|
+
@number_of_stickers = number_of_stickers
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [String]
|
78
|
+
def format_code
|
79
|
+
DOCUMENT_FORMATS.fetch(format)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
def document_type_code
|
84
|
+
DOCUMENT_TYPES.fetch(type)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String]
|
88
|
+
# @deprecated Use `label_type` instead.
|
89
|
+
def thermal_code
|
90
|
+
THERMAL_CODE.fetch(thermal)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [String]
|
94
|
+
def label_type_code
|
95
|
+
LABEL_TYPE_CODES.fetch(label_type)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Generates a commodity information hash for JSON serialization.
|
7
|
+
class GenerateCommodityInformation
|
8
|
+
class << self
|
9
|
+
# @param shipment [Physical::Shipment] the shipment with commodities to serialize
|
10
|
+
# @param options [#options_for_structure, #options_for_package] the options to serialize
|
11
|
+
# @return [Array<Hash>] commodities hash suitable for JSON request
|
12
|
+
def call(shipment:, options:)
|
13
|
+
if shipment.structures.any?
|
14
|
+
serialize_structures(shipment.structures, options)
|
15
|
+
else
|
16
|
+
warn "[DEPRECATION] `packages` is deprecated. Please use `structures` instead."
|
17
|
+
serialize_packages(shipment.packages, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @param structures [Array<Physical::Structure>]
|
24
|
+
# @param options [#options_for_structure] the options to serialize
|
25
|
+
# @return [Array<Hash>]
|
26
|
+
def serialize_structures(structures, options)
|
27
|
+
structures.flat_map do |structure|
|
28
|
+
structure_options = options.options_for_structure(structure)
|
29
|
+
structure.packages.map do |package|
|
30
|
+
package_options = structure_options.options_for_package(package)
|
31
|
+
{
|
32
|
+
description: package.description.presence || "Commodities",
|
33
|
+
class: package_options.freight_class,
|
34
|
+
nmfc: {
|
35
|
+
prime: package_options.nmfc_primary_code,
|
36
|
+
sub: package_options.nmfc_sub_code
|
37
|
+
}.compact.presence,
|
38
|
+
pieces: 1, # We don't support grouping yet
|
39
|
+
weight: {
|
40
|
+
weight: package.weight.convert_to(:pounds).value.to_f.round(2),
|
41
|
+
weightUnit: "LBS"
|
42
|
+
},
|
43
|
+
packagingType: package_options.packaging_code,
|
44
|
+
dangerousGoods: package_options.hazardous,
|
45
|
+
dimensions: {
|
46
|
+
length: package.length.convert_to(:inches).value.to_f.round(2),
|
47
|
+
width: package.width.convert_to(:inches).value.to_f.round(2),
|
48
|
+
height: package.height.convert_to(:inches).value.to_f.round(2),
|
49
|
+
unit: "IN"
|
50
|
+
}
|
51
|
+
}.compact
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param packages [Array<Physical::Package>]
|
57
|
+
# @param options [#options_for_package] the options to serialize
|
58
|
+
# @return [Array<Hash>]
|
59
|
+
def serialize_packages(packages, options)
|
60
|
+
packages.flat_map do |package|
|
61
|
+
package_options = options.options_for_package(package)
|
62
|
+
package.items.map do |item|
|
63
|
+
item_options = package_options.options_for_item(item)
|
64
|
+
{
|
65
|
+
description: item.description.presence || "Commodities",
|
66
|
+
class: item_options.freight_class,
|
67
|
+
nmfc: {
|
68
|
+
prime: item_options.nmfc_primary_code,
|
69
|
+
sub: item_options.nmfc_sub_code
|
70
|
+
}.compact.presence,
|
71
|
+
pieces: 1, # We don't support grouping yet
|
72
|
+
weight: {
|
73
|
+
weight: item.weight.convert_to(:pounds).value.to_f.round(2),
|
74
|
+
weightUnit: "LBS"
|
75
|
+
},
|
76
|
+
packagingType: item_options.packaging_code,
|
77
|
+
dangerousGoods: item_options.hazardous,
|
78
|
+
dimensions: {
|
79
|
+
length: item.length.convert_to(:inches).value.to_f.round(2),
|
80
|
+
width: item.width.convert_to(:inches).value.to_f.round(2),
|
81
|
+
height: item.height.convert_to(:inches).value.to_f.round(2),
|
82
|
+
unit: "IN"
|
83
|
+
}
|
84
|
+
}.compact
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/tforce_freight/generate_location_hash'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class TForceFreight
|
8
|
+
# Generates a Bill of Lading (BOL) request hash for JSON serialization.
|
9
|
+
class GenerateCreateBOLRequestHash
|
10
|
+
class << self
|
11
|
+
# @param shipment [Physical::Shipment] the shipment for which we want to create a BOL
|
12
|
+
# @param options [BOLOptions] options for the BOL
|
13
|
+
# @return [Hash] BOL request hash
|
14
|
+
def call(shipment:, options:)
|
15
|
+
origin = shipment.origin
|
16
|
+
destination = shipment.destination
|
17
|
+
{
|
18
|
+
requestOptions: request_options(options),
|
19
|
+
shipFrom: location(origin).merge(isResidential: origin.residential?),
|
20
|
+
shipTo: location(destination).merge(isResidential: destination.residential?),
|
21
|
+
payment: payment(origin, options),
|
22
|
+
commodities: options.commodity_information_generator.call(shipment: shipment, options: options),
|
23
|
+
instructions: instructions(options),
|
24
|
+
serviceOptions: service_options(options),
|
25
|
+
pickupRequest: pickup_request(origin, options),
|
26
|
+
documents: { image: documents(options.document_options) }
|
27
|
+
}.compact.
|
28
|
+
merge(GenerateHandlingUnitsHash.call(shipment: shipment, options: options)).
|
29
|
+
merge(GenerateReferenceHash.call(reference_numbers: options.reference_numbers))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param options [BOLOptions]
|
35
|
+
# @return [Hash]
|
36
|
+
def request_options(options)
|
37
|
+
{
|
38
|
+
serviceCode: options.service_code,
|
39
|
+
pickupDate: options.pickup_at&.strftime("%Y-%m-%d"),
|
40
|
+
previewRate: options.preview_rate,
|
41
|
+
timeInTransit: options.time_in_transit
|
42
|
+
}.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param location [Physical::Location]
|
46
|
+
# @return [Hash]
|
47
|
+
def location(location)
|
48
|
+
{
|
49
|
+
name: clean_company_name(location.company_name.presence || location.name),
|
50
|
+
contact: truncate(location.name),
|
51
|
+
email: truncate(location.email, length: 50),
|
52
|
+
phone: {
|
53
|
+
number: truncate(location.phone, length: 15)
|
54
|
+
}.compact,
|
55
|
+
address: {
|
56
|
+
addressLine: truncate(address_line(location)),
|
57
|
+
city: location.city,
|
58
|
+
stateProvinceCode: location.region&.code,
|
59
|
+
postalCode: truncate(location.zip, length: 6),
|
60
|
+
country: location.country&.code
|
61
|
+
}.compact
|
62
|
+
}.compact_blank
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param location [Physical::Location]
|
66
|
+
# @param options [BOLOptions]
|
67
|
+
# @return [Hash]
|
68
|
+
def payment(location, options)
|
69
|
+
{
|
70
|
+
payer: location(location),
|
71
|
+
billingCode: options.billing_code
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param location [Physical::Location]
|
76
|
+
# @return [String, nil]
|
77
|
+
def address_line(location)
|
78
|
+
[
|
79
|
+
location.address1,
|
80
|
+
location.address2,
|
81
|
+
location.address3
|
82
|
+
].compact.reject(&:blank?).join(" ").presence
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param options [BOLOptions]
|
86
|
+
# @return [Hash]
|
87
|
+
def instructions(options)
|
88
|
+
{
|
89
|
+
pickup: options.pickup_instructions,
|
90
|
+
handling: options.handling_instructions,
|
91
|
+
delivery: options.delivery_instructions
|
92
|
+
}.compact
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param options [BOLOptions]
|
96
|
+
# @return [Hash]
|
97
|
+
def service_options(options)
|
98
|
+
{
|
99
|
+
pickup: options.pickup_options,
|
100
|
+
delivery: options.delivery_options
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param location [Physical::Location]
|
105
|
+
# @param options [PickupOptions]
|
106
|
+
# @return [Hash]
|
107
|
+
def pickup_request(location, options)
|
108
|
+
{
|
109
|
+
pickup: pickup(options),
|
110
|
+
requester: requester(location),
|
111
|
+
pomIndicator: false # We don't support this yet
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param options [PickupOptions]
|
116
|
+
# @return [Hash]
|
117
|
+
def pickup(options)
|
118
|
+
{
|
119
|
+
date: options.pickup_at&.strftime("%Y-%m-%d"),
|
120
|
+
time: options.pickup_at&.strftime("%H:%M:%S"),
|
121
|
+
openTime: options.pickup_time_window&.begin&.strftime("%H:%M:%S"),
|
122
|
+
closeTime: options.pickup_time_window&.end&.strftime("%H:%M:%S")
|
123
|
+
}.compact
|
124
|
+
end
|
125
|
+
|
126
|
+
# @param location [Physical::Location]
|
127
|
+
# @return [Hash]
|
128
|
+
def requester(location)
|
129
|
+
{
|
130
|
+
companyName: clean_company_name(location.company_name.presence || location.name),
|
131
|
+
contactName: truncate(location.name),
|
132
|
+
email: truncate(location.email, length: 50),
|
133
|
+
phone: {
|
134
|
+
number: truncate(location.phone, length: 15)
|
135
|
+
}.compact
|
136
|
+
}.compact
|
137
|
+
end
|
138
|
+
|
139
|
+
# @param document_options [Array<DocumentOptions>]
|
140
|
+
# @return [Array<Hash>]
|
141
|
+
def documents(document_options)
|
142
|
+
document_options.map { GenerateDocumentOptionsHash.call(document_options: _1) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# @param value [String]
|
146
|
+
# @param length [Integer]
|
147
|
+
# @return [String]
|
148
|
+
def truncate(value, length: 35)
|
149
|
+
value && value[0..(length - 1)].strip
|
150
|
+
end
|
151
|
+
|
152
|
+
# TForce does not support special characters like &, <, or > in company names.
|
153
|
+
#
|
154
|
+
# @param name [String]
|
155
|
+
# @return [String]
|
156
|
+
def clean_company_name(name)
|
157
|
+
return if name.nil?
|
158
|
+
|
159
|
+
truncate(CGI.escapeHTML(name).strip)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Generates a document options hash for JSON serialization.
|
7
|
+
class GenerateDocumentOptionsHash
|
8
|
+
class << self
|
9
|
+
# @param document_options [DocumentOptions] the document options
|
10
|
+
# @return [Hash] document options hash suitable for JSON request
|
11
|
+
def call(document_options:)
|
12
|
+
{
|
13
|
+
type: document_options.document_type_code,
|
14
|
+
format: document_options.format_code,
|
15
|
+
label: label(document_options)
|
16
|
+
}.compact
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @param document_options [DocumentOptions] the document options
|
22
|
+
# @return [Hash, nil] label hash or nil if this is a BOL
|
23
|
+
def label(document_options)
|
24
|
+
return unless document_options.type == :label
|
25
|
+
|
26
|
+
{
|
27
|
+
type: document_options.label_type_code,
|
28
|
+
startPosition: document_options.start_position,
|
29
|
+
numberOfStickers: document_options.number_of_stickers
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Generates a handling units hash for JSON serialization.
|
7
|
+
class GenerateHandlingUnitsHash
|
8
|
+
class << self
|
9
|
+
# @param shipment [Physical::Shipment]
|
10
|
+
# @param options [FriendlyShipping::ShipmentOptions]
|
11
|
+
# @return [Hash] handling units hash suitable for JSON request
|
12
|
+
def call(shipment:, options:)
|
13
|
+
handling_units(shipment, options).reduce(&:merge)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# @param shipment [Physical::Shipment]
|
19
|
+
# @param options [FriendlyShipping::ShipmentOptions]
|
20
|
+
# @return [Array<Hash>]
|
21
|
+
def handling_units(shipment, options)
|
22
|
+
if shipment.packages.any?
|
23
|
+
warn "[DEPRECATION] `packages` is deprecated. Please use `structures` instead."
|
24
|
+
all_package_options = shipment.packages.map { |package| options.options_for_package(package) }
|
25
|
+
all_package_options.group_by(&:handling_unit_code).map do |_handling_unit_code, options_group|
|
26
|
+
[options_group.first, options_group.length]
|
27
|
+
end.map { |package_options, quantity| handling_unit_hash(package_options, quantity) }
|
28
|
+
else
|
29
|
+
all_structure_options = shipment.structures.map { |structure| options.options_for_structure(structure) }
|
30
|
+
all_structure_options.group_by(&:handling_unit_code).map do |_handling_unit_code, options_group|
|
31
|
+
[options_group.first, options_group.length]
|
32
|
+
end.map { |structure_options, quantity| handling_unit_hash(structure_options, quantity) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param options [PackageOptions, StructureOptions]
|
37
|
+
# @param quantity [Integer]
|
38
|
+
# @return [Hash]
|
39
|
+
def handling_unit_hash(options, quantity)
|
40
|
+
{
|
41
|
+
options.handling_unit_tag.to_sym => {
|
42
|
+
quantity: quantity,
|
43
|
+
typeCode: options.handling_unit_code
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Generates a location hash for JSON serialization.
|
7
|
+
class GenerateLocationHash
|
8
|
+
class << self
|
9
|
+
# @param location [Physical::Location] the location
|
10
|
+
# @return [Hash] location hash suitable for JSON request
|
11
|
+
def call(location:)
|
12
|
+
{
|
13
|
+
address: {
|
14
|
+
city: location.city,
|
15
|
+
stateProvinceCode: location.region&.code,
|
16
|
+
postalCode: location.zip&.strip&.[](0..4),
|
17
|
+
country: location.country&.code
|
18
|
+
}.compact
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/tforce_freight/generate_location_hash'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class TForceFreight
|
8
|
+
# Generates a pickup request hash for JSON serialization.
|
9
|
+
class GeneratePickupRequestHash
|
10
|
+
class << self
|
11
|
+
# @param shipment [Physical::Shipment] the shipment for which we want to create a pickup request
|
12
|
+
# @param options [PickupOptions] options for the pickup request
|
13
|
+
# @return [Hash] pickup request hash
|
14
|
+
def call(shipment:, options:)
|
15
|
+
{
|
16
|
+
pickup: pickup(options),
|
17
|
+
requester: requester(shipment.origin),
|
18
|
+
origin: origin(shipment.origin),
|
19
|
+
destination: destination(shipment.destination),
|
20
|
+
services: options.service_options,
|
21
|
+
lineItems: line_items(shipment.packages, options),
|
22
|
+
instructions: instructions(options),
|
23
|
+
pomIndicator: false # We don't support this yet
|
24
|
+
}.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# @param options [PickupOptions]
|
30
|
+
# @return [Hash]
|
31
|
+
def pickup(options)
|
32
|
+
{
|
33
|
+
date: options.pickup_at&.strftime("%Y-%m-%d"),
|
34
|
+
time: options.pickup_at&.strftime("%H:%M:%S"),
|
35
|
+
openTime: options.pickup_time_window&.begin&.strftime("%H:%M:%S"),
|
36
|
+
closeTime: options.pickup_time_window&.end&.strftime("%H:%M:%S")
|
37
|
+
}.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param location [Physical::Location]
|
41
|
+
# @return [Hash]
|
42
|
+
def requester(location)
|
43
|
+
{
|
44
|
+
companyName: location.company_name.presence || location.name,
|
45
|
+
contactName: location.name,
|
46
|
+
email: location.email,
|
47
|
+
phone: { number: location.phone }.compact
|
48
|
+
}.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param location [Physical::Location]
|
52
|
+
# @return [Hash]
|
53
|
+
def origin(location)
|
54
|
+
{
|
55
|
+
companyName: location.company_name.presence || location.name,
|
56
|
+
contactName: location.name,
|
57
|
+
email: location.email,
|
58
|
+
phone: { number: location.phone },
|
59
|
+
address: {
|
60
|
+
address1: location.address1,
|
61
|
+
address2: location.address2,
|
62
|
+
address3: location.address3,
|
63
|
+
city: location.city,
|
64
|
+
stateProvinceCode: location.region&.code,
|
65
|
+
postalCode: location.zip,
|
66
|
+
country: location.country&.code
|
67
|
+
}.compact
|
68
|
+
}.compact
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param location [Physical::Location]
|
72
|
+
# @return [Hash]
|
73
|
+
def destination(location)
|
74
|
+
{
|
75
|
+
postalCode: location.zip,
|
76
|
+
country: location.country&.code
|
77
|
+
}.compact
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param packages [Array<Physical::Package>]
|
81
|
+
# @param options [Physical::Location]
|
82
|
+
# @return [Array<Hash>]
|
83
|
+
def line_items(packages, options)
|
84
|
+
packages.flat_map do |package|
|
85
|
+
package_options = options.options_for_package(package)
|
86
|
+
package.items.map do |item|
|
87
|
+
item_options = package_options.options_for_item(item)
|
88
|
+
{
|
89
|
+
description: item.description.presence || "Commodities",
|
90
|
+
weight: item.weight.convert_to(:pounds).value.to_f.ceil,
|
91
|
+
weightUnit: "LBS",
|
92
|
+
pieces: 1,
|
93
|
+
packagingType: item_options.packaging_code,
|
94
|
+
hazardous: item_options.hazardous
|
95
|
+
}.compact
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# @param options [PickupOptions]
|
101
|
+
# @return [Hash]
|
102
|
+
def instructions(options)
|
103
|
+
{
|
104
|
+
pickup: options.pickup_instructions,
|
105
|
+
handling: options.handling_instructions,
|
106
|
+
delivery: options.delivery_instructions
|
107
|
+
}.compact
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'friendly_shipping/services/tforce_freight/generate_location_hash'
|
4
|
+
|
5
|
+
module FriendlyShipping
|
6
|
+
module Services
|
7
|
+
class TForceFreight
|
8
|
+
# Generates a rates request hash for JSON serialization.
|
9
|
+
class GenerateRatesRequestHash
|
10
|
+
class << self
|
11
|
+
# @param shipment [Physical::Shipment] the shipment for which we want to get rates
|
12
|
+
# @param options [RatesOptions] options for obtaining rates for this shipment
|
13
|
+
# @return [Hash] rates request hash
|
14
|
+
def call(shipment:, options:)
|
15
|
+
{
|
16
|
+
requestOptions: request_options(options),
|
17
|
+
shipFrom: GenerateLocationHash.call(location: shipment.origin),
|
18
|
+
shipTo: GenerateLocationHash.call(location: shipment.destination),
|
19
|
+
payment: payment(options),
|
20
|
+
serviceOptions: service_options(options),
|
21
|
+
commodities: options.commodity_information_generator.call(shipment: shipment, options: options)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @param options [RatesOptions]
|
28
|
+
# @return [Hash]
|
29
|
+
def request_options(options)
|
30
|
+
{
|
31
|
+
serviceCode: options.shipping_method.service_code,
|
32
|
+
pickupDate: options.pickup_date.strftime('%Y-%m-%d'),
|
33
|
+
type: options.type_code,
|
34
|
+
densityEligible: options.density_eligible,
|
35
|
+
gfpOptions: {
|
36
|
+
accessorialRate: options.accessorial_rate,
|
37
|
+
},
|
38
|
+
timeInTransit: options.time_in_transit,
|
39
|
+
quoteNumber: options.quote_number,
|
40
|
+
customerContext: options.customer_context
|
41
|
+
}.compact
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param options [RatesOptions]
|
45
|
+
# @return [Hash]
|
46
|
+
def payment(options)
|
47
|
+
{
|
48
|
+
payer: GenerateLocationHash.call(location: options.billing_address),
|
49
|
+
billingCode: options.billing_code
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param options [RatesOptions]
|
54
|
+
# @return [Hash]
|
55
|
+
def service_options(options)
|
56
|
+
{
|
57
|
+
pickup: options.pickup_options,
|
58
|
+
delivery: options.delivery_options
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FriendlyShipping
|
4
|
+
module Services
|
5
|
+
class TForceFreight
|
6
|
+
# Generates a reference hash for JSON serialization.
|
7
|
+
class GenerateReferenceHash
|
8
|
+
class << self
|
9
|
+
# @param reference_numbers [Array] reference numbers for the Bill of Lading
|
10
|
+
# @return [Hash] reference hash suitable for JSON request
|
11
|
+
def call(reference_numbers:)
|
12
|
+
return {} unless reference_numbers
|
13
|
+
|
14
|
+
references = reference_numbers.map do |reference_number|
|
15
|
+
{
|
16
|
+
number: reference_number[:value],
|
17
|
+
type: reference_number[:code],
|
18
|
+
quantity: reference_number[:quantity],
|
19
|
+
weight: reference_number[:weight]
|
20
|
+
}.compact
|
21
|
+
end
|
22
|
+
references.any? ? { references: references } : {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|