friendly_shipping 0.4.14 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="