friendly_shipping 0.4.14 → 0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d43e668240fd5d6edb702b48b28e7a886abf53843f3963feb44fcff17a7f498a
4
- data.tar.gz: 84e4efa1108768940d25493ba816197879c5dd9ad51ce64d5c1f5be8d2f5c92f
3
+ metadata.gz: a7033f8bf845a41a0cefa6899a465fd78a85077ff565c7c30f67d04f20708ff6
4
+ data.tar.gz: 2c5207e0139608d78b7c65b6f273b9dc222bc750bc47e587147e8f486c908b43
5
5
  SHA512:
6
- metadata.gz: d0f0c826feea1c9f9396785049f07c7635f7be2386619d3b3c217339da7f363e43842a2b3eceef2204556868be082b01f76cc96dc36b4849649cd09baa924a79
7
- data.tar.gz: 0c56d00886b43ac1d4a272c36d7dd90b3eb791315dcedcd689fe95450a6f23e0c76fb9d1e7c2c26f7a146bb418e27296c5911bcee6e698de287b53427926c05c
6
+ metadata.gz: 00d7ce8fe1d737b482d71a6ff96195a82dc70b94c7784291c9cc18eb2f71ca529dadf33900a7f5aef223a4f89a3b1297403d7bc352dd9e8255c139b11b7351c1
7
+ data.tar.gz: 7a7746f0c59dfcd03d2a330142f106cfe3c0fc7b82628859691d28c0dbd27e3d7584670c44f3509780271aaaf44fbd83551d0e1415ac14807d9ec2578891ab95
data/.circleci/config.yml CHANGED
@@ -7,7 +7,7 @@ jobs:
7
7
  build:
8
8
  docker:
9
9
  # specify the version you desire here
10
- - image: circleci/ruby:2.4
10
+ - image: circleci/ruby:2.5
11
11
 
12
12
  # Specify service dependencies here if necessary
13
13
  # CircleCI maintains a library of pre-built images
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.4
2
+ TargetRubyVersion: 2.5
3
3
 
4
4
  inherit_from:
5
5
  - .rubocop-relaxed.yml
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.5] - 2020-01-24
8
+
9
+ ### Removed
10
+ - Drop support for Ruby 2.4 (#83)
11
+
12
+ ### Changed
13
+ - UPS/USPS Services: Use options classes for rate estimates (#82)
14
+
7
15
  ## [0.4.14] - 2020-01-21
8
16
 
9
17
  ### Changed
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_runtime_dependency "nokogiri", ">= 1.6"
29
29
  spec.add_runtime_dependency "physical", ">= 0.4.4"
30
30
  spec.add_runtime_dependency "rest-client", "~> 2.0"
31
- spec.required_ruby_version = '>= 2.4'
31
+ spec.required_ruby_version = '>= 2.5'
32
32
 
33
33
  spec.add_development_dependency "bundler"
34
34
  spec.add_development_dependency "dotenv"
@@ -20,6 +20,7 @@ require 'friendly_shipping/services/ups/parse_time_in_transit_response'
20
20
  require 'friendly_shipping/services/ups/parse_void_shipment_response'
21
21
  require 'friendly_shipping/services/ups/shipping_methods'
22
22
  require 'friendly_shipping/services/ups/label_options'
23
+ require 'friendly_shipping/services/ups/rate_estimate_options'
23
24
  require 'friendly_shipping/services/ups/timing_options'
24
25
 
25
26
  module FriendlyShipping
@@ -63,10 +64,12 @@ module FriendlyShipping
63
64
 
64
65
  # Get rates for a shipment
65
66
  # @param [Physical::Shipment] shipment The shipment we want to get rates for
67
+ # @param [FriendlyShipping::Services::Ups::RateEstimateOptions] options What options
68
+ # to use for this rate estimate call
66
69
  # @return [Result<ApiResult<Array<Rate>>>] The rates returned from UPS encoded in a
67
70
  # `FriendlyShipping::ApiResult` object.
68
- def rate_estimates(shipment, debug: false)
69
- rate_request_xml = SerializeRatingServiceSelectionRequest.call(shipment: shipment)
71
+ def rate_estimates(shipment, options:, debug: false)
72
+ rate_request_xml = SerializeRatingServiceSelectionRequest.call(shipment: shipment, options: options)
70
73
  url = base_url + RESOURCES[:rates]
71
74
  request = FriendlyShipping::Request.new(
72
75
  url: url,
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'friendly_shipping/services/ups/rate_estimate_package_options'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ # Option container for rating a shipment via UPS
8
+ #
9
+ # Required:
10
+ #
11
+ # @option shipper_number [String] The shipper number of the origin of the shipment.
12
+ #
13
+ # Optional:
14
+ #
15
+ # @option carbon_neutral [Boolean] Ship with UPS' carbon neutral program
16
+ # @option customer_context [String ] Optional element to identify transactions between client and server
17
+ # @option customer_classification [Symbol] Which kind of rates to request. See UPS docs for more details. Default: `shipper_number`
18
+ # @option negotiated_rates [Boolean] if truthy negotiated rates will be requested from ups. Only valid if
19
+ # shipper account has negotiated rates. Default: false
20
+ # @option saturday_delivery [Boolean] should we request Saturday delivery?. Default: false
21
+ # @option saturday_pickup [Boolean] should we request Saturday pickup?. Default: false
22
+ # @option shipping_method [FriendlyShipping::ShippingMethod] Request rates for a particular shipping method only?
23
+ # Default is `nil`, which translates to 'All shipping methods' (The "Shop" option in UPS parlance)
24
+ # @option with_time_in_transit [Boolean] Whether to request timing information alongside the rates
25
+ # @option package_options_class [Class] See FriendlyShipping::ShipmentOptions
26
+ #
27
+ class Ups
28
+ class RateEstimateOptions < FriendlyShipping::ShipmentOptions
29
+ PICKUP_TYPE_CODES = {
30
+ daily_pickup: "01",
31
+ customer_counter: "03",
32
+ one_time_pickup: "06",
33
+ on_call_air: "07",
34
+ suggested_retail_rates: "11",
35
+ letter_center: "19",
36
+ air_service_center: "20"
37
+ }.freeze
38
+
39
+ CUSTOMER_CLASSIFICATION_CODES = {
40
+ shipper_number: "00",
41
+ daily_rates: "01",
42
+ retail_rates: "04",
43
+ regional_rates: "05",
44
+ general_rates: "06",
45
+ standard_rates: "53"
46
+ }.freeze
47
+
48
+ attr_reader :carbon_neutral,
49
+ :customer_context,
50
+ :destination_account,
51
+ :negotiated_rates,
52
+ :saturday_delivery,
53
+ :saturday_pickup,
54
+ :shipper,
55
+ :shipper_number,
56
+ :shipping_method,
57
+ :with_time_in_transit
58
+
59
+ def initialize(
60
+ shipper_number:,
61
+ carbon_neutral: true,
62
+ customer_context: nil,
63
+ customer_classification: :daily_rates,
64
+ destination_account: nil,
65
+ negotiated_rates: false,
66
+ pickup_type: :daily_pickup,
67
+ saturday_delivery: false,
68
+ saturday_pickup: false,
69
+ shipper: nil,
70
+ shipping_method: nil,
71
+ with_time_in_transit: false,
72
+ package_options_class: FriendlyShipping::Services::Ups::RateEstimatePackageOptions,
73
+ **kwargs
74
+ )
75
+ @carbon_neutral = carbon_neutral
76
+ @customer_context = customer_context
77
+ @customer_classification = customer_classification
78
+ @destination_account = destination_account
79
+ @negotiated_rates = negotiated_rates
80
+ @shipper_number = shipper_number
81
+ @pickup_type = pickup_type
82
+ @saturday_delivery = saturday_delivery
83
+ @saturday_pickup = saturday_pickup
84
+ @shipper = shipper
85
+ @shipping_method = shipping_method
86
+ @with_time_in_transit = with_time_in_transit
87
+ super kwargs.merge(package_options_class: package_options_class)
88
+ end
89
+
90
+ def pickup_type_code
91
+ PICKUP_TYPE_CODES[@pickup_type]
92
+ end
93
+
94
+ def customer_classification_code
95
+ CUSTOMER_CLASSIFICATION_CODES[@customer_classification]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'friendly_shipping/services/ups/label_item_options'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ class Ups
8
+ # Package properties relevant for quoting a UPS package
9
+ #
10
+ # @option transmit_dimensions [Boolean] Whether to transmit the dimensions of this package, or quote only based on weight.
11
+ class RateEstimatePackageOptions < FriendlyShipping::PackageOptions
12
+ attr_reader :transmit_dimensions
13
+
14
+ def initialize(
15
+ transmit_dimensions: true,
16
+ **kwargs
17
+ )
18
+ @transmit_dimensions = transmit_dimensions
19
+ super kwargs
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -9,7 +9,8 @@ module FriendlyShipping
9
9
  package:,
10
10
  reference_numbers: {},
11
11
  delivery_confirmation_code: nil,
12
- shipper_release: false
12
+ shipper_release: false,
13
+ transmit_dimensions: true
13
14
  )
14
15
  xml.Package do
15
16
  xml.PackagingType do
@@ -20,7 +21,7 @@ module FriendlyShipping
20
21
  xml.Description(package.items.map(&:description).compact.join(', '))
21
22
  end
22
23
 
23
- if package.dimensions.all? { |dim| !dim.value.zero? && !dim.value.infinite? }
24
+ if transmit_dimensions && package.dimensions.all? { |dim| !dim.value.zero? && !dim.value.infinite? }
24
25
  xml.Dimensions do
25
26
  xml.UnitOfMeasurement do
26
27
  xml.Code('IN')
@@ -8,47 +8,30 @@ module FriendlyShipping
8
8
  module Services
9
9
  class Ups
10
10
  class SerializeRatingServiceSelectionRequest
11
- PICKUP_CODES = HashWithIndifferentAccess.new(
12
- daily_pickup: "01",
13
- customer_counter: "03",
14
- one_time_pickup: "06",
15
- on_call_air: "07",
16
- suggested_retail_rates: "11",
17
- letter_center: "19",
18
- air_service_center: "20"
19
- )
20
-
21
- CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new(
22
- shipper_number: "00",
23
- daily_rates: "01",
24
- retail_rates: "04",
25
- regional_rates: "05",
26
- general_rates: "06",
27
- standard_rates: "53"
28
- )
29
-
30
- def self.call(shipment:)
31
- shipper = shipment.options[:shipper] || shipment.origin
32
- pickup_type = PICKUP_CODES[shipment.options[:pickup_type] || :daily_pickup]
33
- customer_classification = CUSTOMER_CLASSIFICATIONS[
34
- shipment.options[:customer_classification] || :daily_rates
35
- ]
36
- origin_account = shipment.options[:origin_account]
37
- destination_account = shipment.options[:destination_account]
11
+ def self.call(shipment:, options:)
12
+ shipper = options.shipper || shipment.origin
38
13
 
39
14
  xml_builder = Nokogiri::XML::Builder.new do |xml|
40
15
  xml.RatingServiceSelectionRequest do
41
16
  xml.Request do
42
17
  xml.RequestAction('Rate')
43
- xml.RequestOption('Shop')
18
+ # If no shipping method is given, request all of them
19
+ # I one is given, omit the request option. It then becomes "Rate", the default.
20
+ xml.RequestOption('Shop') unless options.shipping_method
44
21
  xml.SubVersion('1707')
22
+ # Optional element to identify transactions between client and server.
23
+ if options.customer_context
24
+ xml.TransactionReference do
25
+ xml.CustomerContext(options.customer_context)
26
+ end
27
+ end
45
28
  end
46
29
 
47
30
  xml.PickupType do
48
- xml.Code(pickup_type)
31
+ xml.Code(options.pickup_type_code)
49
32
  end
50
33
  xml.CustomerClassification do
51
- xml.Code(customer_classification)
34
+ xml.Code(options.customer_classification_code)
52
35
  end
53
36
 
54
37
  xml.Shipment do
@@ -56,14 +39,14 @@ module FriendlyShipping
56
39
  xml.Shipper do
57
40
  SerializeAddressSnippet.call(xml: xml, location: shipper)
58
41
 
59
- xml.ShipperNumber(origin_account)
42
+ xml.ShipperNumber(options.shipper_number)
60
43
  end
61
44
 
62
45
  xml.ShipTo do
63
46
  SerializeAddressSnippet.call(xml: xml, location: shipment.destination)
64
47
 
65
- if destination_account
66
- xml.ShipperAssignedIdentificationNumber(destination_account)
48
+ if options.destination_account
49
+ xml.ShipperAssignedIdentificationNumber(options.destination_account)
67
50
  end
68
51
  end
69
52
 
@@ -74,7 +57,30 @@ module FriendlyShipping
74
57
  end
75
58
 
76
59
  shipment.packages.each do |package|
77
- SerializePackageNode.call(xml: xml, package: package)
60
+ package_options = options.options_for_package(package)
61
+ SerializePackageNode.call(
62
+ xml: xml,
63
+ package: package,
64
+ transmit_dimensions: package_options.transmit_dimensions
65
+ )
66
+ end
67
+
68
+ if options.shipping_method
69
+ xml.Service do
70
+ xml.Code options.shipping_method.service_code
71
+ end
72
+ end
73
+
74
+ xml.ShipmentServiceOptions do
75
+ xml.UPScarbonneutralIndicator if options.carbon_neutral
76
+ xml.SaturdayDelivery if options.saturday_delivery
77
+ xml.SaturdayPickup if options.saturday_pickup
78
+ end
79
+
80
+ if options.negotiated_rates
81
+ xml.RateInformation do
82
+ xml.NegotiatedRatesIndicator if options.negotiated_rates
83
+ end
78
84
  end
79
85
  end
80
86
  end
@@ -11,6 +11,7 @@ require 'friendly_shipping/services/usps/parse_city_state_lookup_response'
11
11
  require 'friendly_shipping/services/usps/parse_rate_response'
12
12
  require 'friendly_shipping/services/usps/parse_time_in_transit_response'
13
13
  require 'friendly_shipping/services/usps/timing_options'
14
+ require 'friendly_shipping/services/usps/rate_estimate_options'
14
15
 
15
16
  module FriendlyShipping
16
17
  module Services
@@ -48,25 +49,19 @@ module FriendlyShipping
48
49
 
49
50
  # Get rate estimates from USPS
50
51
  #
51
- # @param [Physical::Shipment] shipment The shipment object we're trying to get results for
52
- # USPS returns rates on a package-by-package basis, so the options for obtaining rates are
53
- # set on the [Physical::Package.container.properties] hash. The possible options are:
54
- # @property [Symbol] box_name The type of box we want to get rates for. Has to be one of the keys
55
- # of FriendlyShipping::Services::Usps::CONTAINERS.
56
- # @property [Boolean] commercial_pricing Whether we prefer commercial pricing results or retail results
57
- # @property [Boolean] hold_for_pickup Whether we want a rate with Hold For Pickup Service
58
- # @param [Physical::ShippingMethod] shipping_method The shipping method ("service" in USPS parlance) we want
59
- # to get rates for.
52
+ # @param [Physical::Shipment] shipment
53
+ # @param [FriendlyShipping::Services::Usps::RateEstimateOptions] options What options
54
+ # to use for this rate estimate call
60
55
  #
61
56
  # @return [Result<Array<FriendlyShipping::Rate>>] When successfully parsing, an array of rates in a Success Monad.
62
57
  # When the parsing is not successful or USPS can't give us rates, a Failure monad containing something that
63
58
  # can be serialized into an error message using `to_s`.
64
- def rate_estimates(shipment, shipping_method: nil, debug: false)
65
- rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login, shipping_method: shipping_method)
59
+ def rate_estimates(shipment, options: RateEstimateOptions.new, debug: false)
60
+ rate_request_xml = SerializeRateRequest.call(shipment: shipment, login: login, options: options)
66
61
  request = build_request(api: :rates, xml: rate_request_xml, debug: debug)
67
62
 
68
63
  client.post(request).bind do |response|
69
- ParseRateResponse.call(response: response, request: request, shipment: shipment)
64
+ ParseRateResponse.call(response: response, request: request, shipment: shipment, options: options)
70
65
  end
71
66
  end
72
67
 
@@ -16,19 +16,18 @@ module FriendlyShipping
16
16
  # @param [Array<FriendlyShipping::Rate>] The rates we select from
17
17
  #
18
18
  # @return [FriendlyShipping::Rate] The rate that most closely matches our package
19
- def self.call(shipping_method, package, rates)
19
+ def self.call(shipping_method, rates, package_options)
20
20
  # Keep all rates with the requested shipping method
21
21
  rates_with_this_shipping_method = rates.select { |r| r.shipping_method == shipping_method }
22
22
 
23
23
  # Keep only rates with the package type of this package
24
24
  rates_with_this_package_type = rates_with_this_shipping_method.select do |r|
25
- r.data[:box_name] == package.properties[:box_name] ||
26
- r.data[:box_name] == :flat_rate_boxes && package.properties[:box_name]&.match?(FLAT_RATE_BOX)
25
+ r.data[:box_name] == package_options.box_name
27
26
  end
28
27
 
29
28
  # Filter by our package's `hold_for_pickup` option
30
29
  rates_with_this_hold_for_pickup_option = rates_with_this_package_type.select do |r|
31
- r.data[:hold_for_pickup] == !!package.properties[:hold_for_pickup]
30
+ r.data[:hold_for_pickup] == package_options.hold_for_pickup
32
31
  end
33
32
 
34
33
  # At this point, we have one or two rates left, and they're similar enough.
@@ -57,7 +57,7 @@ module FriendlyShipping
57
57
  CURRENCY = Money::Currency.new('USD').freeze
58
58
 
59
59
  class << self
60
- def call(rate_node, package)
60
+ def call(rate_node, package, package_options)
61
61
  # "A mail class identifier for the postage returned. Not necessarily unique within a <Package/>."
62
62
  # (from the USPS docs). We save this on the data Hash, but do not use it for identifying shipping methods.
63
63
  service_code = rate_node.attributes[SERVICE_CODE_TAG].value
@@ -84,7 +84,7 @@ module FriendlyShipping
84
84
  # In these cases, return the commercial rate instead of the normal rate.
85
85
  # Some rates are available in both commercial and retail pricing - if we want the commercial pricing here,
86
86
  # we need to specify the commercial_pricing property on the `Physical::Package`.
87
- rate_value = if (package.properties[:commercial_pricing] || rate_node.at(RATE_TAG).text.to_d.zero?) && rate_node.at(COMMERCIAL_RATE_TAG)
87
+ rate_value = if (package_options.commercial_pricing || rate_node.at(RATE_TAG).text.to_d.zero?) && rate_node.at(COMMERCIAL_RATE_TAG)
88
88
  rate_node.at(COMMERCIAL_RATE_TAG).text.to_d
89
89
  else
90
90
  rate_node.at(RATE_TAG).text.to_d
@@ -107,9 +107,7 @@ module FriendlyShipping
107
107
  # We find out the box name using a bit of Regex magic using named captures. See the `BOX_REGEX`
108
108
  # constant above.
109
109
  box_name_match = service_name.match(/#{BOX_REGEX}/)
110
- box_name = if box_name_match
111
- box_name_match.named_captures.compact.keys.last.to_sym
112
- end
110
+ box_name = box_name_match ? box_name_match.named_captures.compact.keys.last.to_sym : :variable
113
111
 
114
112
  # Combine all the gathered information in a FriendlyShipping::Rate object.
115
113
  # Careful: This rate is only for one package within the shipment, and we get multiple
@@ -16,20 +16,24 @@ module FriendlyShipping
16
16
  # @param [FriendlyShipping::Request] request The request that was used to obtain this Response
17
17
  # @param [FriendlyShipping::Response] response The response that USPS returned
18
18
  # @param [Physical::Shipment] shipment The shipment object we're trying to get results for
19
+ # @param [FriendlyShipping::Services::Usps::RateEstimateOptions] options The options we sent with this request
19
20
  # @return [Result<ApiResult<Array<FriendlyShipping::Rate>>>] When successfully parsing, an array of rates in a Success Monad.
20
- def call(request:, response:, shipment:)
21
+ def call(request:, response:, shipment:, options:)
21
22
  # Filter out error responses and directly return a failure
22
23
  parsing_result = ParseXMLResponse.call(response.body, 'RateV4Response')
23
24
  rates = []
24
25
  parsing_result.fmap do |xml|
25
26
  # Get all the possible rates for each package
26
- rates_by_package = rates_from_response_node(xml, shipment)
27
+ rates_by_package = rates_from_response_node(xml, shipment, options)
27
28
 
28
29
  rates = SHIPPING_METHODS.map do |shipping_method|
29
30
  # For every package ...
30
31
  matching_rates = rates_by_package.map do |package, package_rates|
31
32
  # ... choose the rate that fits this package best.
32
- ChoosePackageRate.call(shipping_method, package, package_rates)
33
+
34
+ package_options = options.options_for_package(package)
35
+
36
+ ChoosePackageRate.call(shipping_method, package_rates, package_options)
33
37
  end.compact # Some shipping rates are not available for every shipping method.
34
38
 
35
39
  # in this case, go to the next shipping method.
@@ -62,17 +66,17 @@ module FriendlyShipping
62
66
  # @param [Physical::Shipment] shipment The shipment we're trying to get rates for
63
67
  #
64
68
  # @return [Hash<Physical::Package => Array<FriendlyShipping::Rate>>]
65
- def rates_from_response_node(xml, shipment)
69
+ def rates_from_response_node(xml, shipment, options)
66
70
  xml.xpath(PACKAGE_NODE_XPATH).each_with_object({}) do |package_node, result|
67
71
  package_id = package_node['ID']
68
72
  corresponding_package = shipment.packages[package_id.to_i]
69
-
73
+ package_options = options.options_for_package(corresponding_package)
70
74
  # There should always be a package in the original shipment that corresponds to the package ID
71
75
  # in the USPS response.
72
76
  raise BoxNotFoundError if corresponding_package.nil?
73
77
 
74
78
  result[corresponding_package] = package_node.xpath(SERVICE_NODE_NAME).map do |service_node|
75
- ParsePackageRate.call(service_node, corresponding_package)
79
+ ParsePackageRate.call(service_node, corresponding_package, package_options)
76
80
  end
77
81
  end
78
82
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'friendly_shipping/services/usps/rate_estimate_package_options'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ # Option container for rating a shipment via USPS
8
+ #
9
+ # Context: The shipment object we're trying to get results for
10
+ # USPS returns rates on a package-by-package basis, so the options for obtaining rates are
11
+ # set on the [FriendlyShipping/RateEstimateobect] hash. The possible options are:
12
+
13
+ # @param [Physical::ShippingMethod] shipping_method The shipping method ("service" in USPS parlance) we want
14
+ # to get rates for.
15
+ # @param [Boolean] commercial_pricing Whether we prefer commercial pricing results or retail results
16
+ # @param [Boolean] hold_for_pickup Whether we want a rate with Hold For Pickup Service
17
+ class Usps
18
+ class RateEstimateOptions < FriendlyShipping::ShipmentOptions
19
+ def initialize(
20
+ package_options_class: FriendlyShipping::Services::Usps::RateEstimatePackageOptions,
21
+ **kwargs
22
+ )
23
+ super kwargs.merge(package_options_class: package_options_class)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'friendly_shipping/services/ups/rate_estimate_package_options'
4
+
5
+ module FriendlyShipping
6
+ module Services
7
+ # Options for one package when rating
8
+ #
9
+ # @param [Symbol] box_name The type of box we want to get rates for. Has to be one of the keys
10
+ # of FriendlyShipping::Services::Usps::CONTAINERS.
11
+ class Usps
12
+ class RateEstimatePackageOptions < FriendlyShipping::PackageOptions
13
+ attr_reader :box_name,
14
+ :commercial_pricing,
15
+ :first_class_mail_type,
16
+ :hold_for_pickup,
17
+ :shipping_method,
18
+ :transmit_dimensions
19
+
20
+ def initialize(
21
+ box_name: :variable,
22
+ commercial_pricing: false,
23
+ first_class_mail_type: nil,
24
+ hold_for_pickup: false,
25
+ shipping_method: nil,
26
+ transmit_dimensions: true,
27
+ **kwargs
28
+ )
29
+ @box_name = CONTAINERS.key?(box_name) ? box_name : :variable
30
+ @commercial_pricing = commercial_pricing
31
+ @first_class_mail_type = first_class_mail_type
32
+ @hold_for_pickup = hold_for_pickup
33
+ @shipping_method = shipping_method
34
+ @transmit_dimensions = transmit_dimensions
35
+ super kwargs
36
+ end
37
+
38
+ def container_code
39
+ CONTAINERS.fetch(box_name)
40
+ end
41
+
42
+ def first_class_mail_type_code
43
+ FIRST_CLASS_MAIL_TYPES[first_class_mail_type]
44
+ end
45
+
46
+ def service_code
47
+ return 'ALL' unless shipping_method
48
+
49
+ if commercial_pricing
50
+ "#{shipping_method.service_code} COMMERCIAL"
51
+ else
52
+ shipping_method.service_code
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -16,24 +16,24 @@ module FriendlyShipping
16
16
  # @param [FriendlyShipping::ShippingMethod] shipping_method The shipping method we want to get rates
17
17
  # for. If empty, we get all of them
18
18
  # @return Array<[FriendlyShipping::Rate]> A set of Rates that this package may be sent with
19
- def call(shipment:, login:, shipping_method: nil)
19
+ def call(shipment:, login:, options:)
20
20
  xml_builder = Nokogiri::XML::Builder.new do |xml|
21
21
  xml.RateV4Request('USERID' => login) do
22
22
  shipment.packages.each_with_index do |package, index|
23
+ package_options = options.options_for_package(package)
23
24
  xml.Package('ID' => index) do
24
- xml.Service(service_code_by(shipping_method, package))
25
- if package.properties[:first_class_mail_type]
26
- xml.FirstClassMailType(FIRST_CLASS_MAIL_TYPES[package.properties[:first_class_mail_type]])
25
+ xml.Service(package_options.service_code)
26
+ if package_options.first_class_mail_type
27
+ xml.FirstClassMailType(package_options.first_class_mail_type_code)
27
28
  end
28
29
  xml.ZipOrigination(shipment.origin.zip)
29
30
  xml.ZipDestination(shipment.destination.zip)
30
31
  xml.Pounds(0)
31
32
  xml.Ounces(ounces_for(package))
32
33
  size_code = size_code_for(package)
33
- container = CONTAINERS[package.properties[:box_name] || :rectangular]
34
- xml.Container(container)
34
+ xml.Container(package_options.container_code)
35
35
  xml.Size(size_code)
36
- if ['RECTANGULAR', 'VARIABLE'].include?(container)
36
+ if package_options.transmit_dimensions && package_options.container_code == 'VARIABLE'
37
37
  xml.Width("%<width>0.2f" % { width: package.width.convert_to(:inches).value.to_f })
38
38
  xml.Length("%<length>0.2f" % { length: package.length.convert_to(:inches).value.to_f })
39
39
  xml.Height("%<height>0.2f" % { height: package.height.convert_to(:inches).value.to_f })
@@ -61,16 +61,6 @@ module FriendlyShipping
61
61
  end
62
62
  end
63
63
 
64
- def service_code_by(shipping_method, package)
65
- return 'ALL' unless shipping_method
66
-
67
- if package.properties[:commercial_pricing]
68
- "#{shipping_method.service_code} COMMERCIAL"
69
- else
70
- shipping_method.service_code
71
- end
72
- end
73
-
74
64
  def ounces_for(package)
75
65
  ounces = package.weight.convert_to(:ounces).value.to_f.round(2).ceil
76
66
  ounces == 16 ? 15.999 : [ounces, 1].max
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FriendlyShipping
4
- VERSION = "0.4.14"
4
+ VERSION = "0.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_shipping
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.14
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Meyerhoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-21 00:00:00.000000000 Z
11
+ date: 2020-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: data_uri
@@ -309,6 +309,8 @@ files:
309
309
  - lib/friendly_shipping/services/ups/parse_time_in_transit_response.rb
310
310
  - lib/friendly_shipping/services/ups/parse_void_shipment_response.rb
311
311
  - lib/friendly_shipping/services/ups/parse_xml_response.rb
312
+ - lib/friendly_shipping/services/ups/rate_estimate_options.rb
313
+ - lib/friendly_shipping/services/ups/rate_estimate_package_options.rb
312
314
  - lib/friendly_shipping/services/ups/serialize_access_request.rb
313
315
  - lib/friendly_shipping/services/ups/serialize_address_snippet.rb
314
316
  - lib/friendly_shipping/services/ups/serialize_address_validation_request.rb
@@ -342,6 +344,8 @@ files:
342
344
  - lib/friendly_shipping/services/usps/parse_rate_response.rb
343
345
  - lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb
344
346
  - lib/friendly_shipping/services/usps/parse_xml_response.rb
347
+ - lib/friendly_shipping/services/usps/rate_estimate_options.rb
348
+ - lib/friendly_shipping/services/usps/rate_estimate_package_options.rb
345
349
  - lib/friendly_shipping/services/usps/serialize_address_validation_request.rb
346
350
  - lib/friendly_shipping/services/usps/serialize_city_state_lookup_request.rb
347
351
  - lib/friendly_shipping/services/usps/serialize_rate_request.rb
@@ -365,7 +369,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
365
369
  requirements:
366
370
  - - ">="
367
371
  - !ruby/object:Gem::Version
368
- version: '2.4'
372
+ version: '2.5'
369
373
  required_rubygems_version: !ruby/object:Gem::Requirement
370
374
  requirements:
371
375
  - - ">="