active_shipping 1.0.0.pre4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +17 -0
- data/CONTRIBUTING.md +2 -2
- data/Gemfile.activesupport32 +1 -0
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/active_shipping.gemspec +2 -2
- data/lib/active_shipping.rb +2 -3
- data/lib/active_shipping/carriers.rb +1 -0
- data/lib/active_shipping/carriers/canada_post_pws.rb +281 -266
- data/lib/active_shipping/carriers/correios.rb +285 -0
- data/lib/active_shipping/carriers/fedex.rb +205 -199
- data/lib/active_shipping/carriers/stamps.rb +218 -219
- data/lib/active_shipping/carriers/ups.rb +287 -48
- data/lib/active_shipping/carriers/usps.rb +3 -3
- data/lib/active_shipping/delivery_date_estimate.rb +21 -0
- data/lib/active_shipping/delivery_date_estimates_response.rb +11 -0
- data/lib/active_shipping/errors.rb +20 -1
- data/lib/active_shipping/location.rb +0 -5
- data/lib/active_shipping/response.rb +0 -15
- data/lib/active_shipping/version.rb +1 -1
- data/test/credentials.yml +11 -3
- data/test/fixtures/xml/correios/book_response.xml +13 -0
- data/test/fixtures/xml/correios/book_response_invalid.xml +13 -0
- data/test/fixtures/xml/correios/clothes_response.xml +43 -0
- data/test/fixtures/xml/correios/poster_response.xml +23 -0
- data/test/fixtures/xml/correios/shoes_response.xml +43 -0
- data/test/fixtures/xml/fedex/tracking_request.xml +13 -11
- data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
- data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
- data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
- data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
- data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
- data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
- data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
- data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -1
- data/test/fixtures/xml/usps/delivered_extended_tracking_response.xml +11 -0
- data/test/remote/canada_post_pws_platform_test.rb +35 -22
- data/test/remote/canada_post_pws_test.rb +32 -40
- data/test/remote/correios_test.rb +83 -0
- data/test/remote/fedex_test.rb +95 -13
- data/test/remote/stamps_test.rb +1 -0
- data/test/remote/ups_test.rb +77 -40
- data/test/remote/usps_test.rb +13 -1
- data/test/test_helper.rb +12 -2
- data/test/unit/carriers/canada_post_pws_rating_test.rb +66 -59
- data/test/unit/carriers/canada_post_pws_shipping_test.rb +34 -23
- data/test/unit/carriers/correios_test.rb +244 -0
- data/test/unit/carriers/fedex_test.rb +161 -156
- data/test/unit/carriers/ups_test.rb +193 -1
- data/test/unit/carriers/usps_test.rb +14 -0
- data/test/unit/location_test.rb +0 -10
- metadata +63 -46
- metadata.gz.sig +0 -0
- data/lib/vendor/test_helper.rb +0 -6
- data/lib/vendor/xml_node/README +0 -36
- data/lib/vendor/xml_node/Rakefile +0 -21
- data/lib/vendor/xml_node/benchmark/bench_generation.rb +0 -30
- data/lib/vendor/xml_node/init.rb +0 -1
- data/lib/vendor/xml_node/lib/xml_node.rb +0 -221
- data/lib/vendor/xml_node/test/test_generating.rb +0 -89
- data/lib/vendor/xml_node/test/test_parsing.rb +0 -40
- data/test/fixtures/xml/fedex/tracking_response.xml +0 -151
- data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +0 -76
- data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +0 -139
- data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +0 -150
- data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +0 -95
- data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +0 -71
@@ -15,7 +15,8 @@ module ActiveShipping
|
|
15
15
|
:rates => 'ups.app/xml/Rate',
|
16
16
|
:track => 'ups.app/xml/Track',
|
17
17
|
:ship_confirm => 'ups.app/xml/ShipConfirm',
|
18
|
-
:ship_accept => 'ups.app/xml/ShipAccept'
|
18
|
+
:ship_accept => 'ups.app/xml/ShipAccept',
|
19
|
+
:delivery_dates => 'ups.app/xml/TimeInTransit'
|
19
20
|
}
|
20
21
|
|
21
22
|
PICKUP_CODES = HashWithIndifferentAccess.new(
|
@@ -102,6 +103,22 @@ module ActiveShipping
|
|
102
103
|
|
103
104
|
IMPERIAL_COUNTRIES = %w(US LR MM)
|
104
105
|
|
106
|
+
DEFAULT_SERVICE_NAME_TO_CODE = Hash[UPS::DEFAULT_SERVICES.to_a.map(&:reverse)]
|
107
|
+
DEFAULT_SERVICE_NAME_TO_CODE['UPS 2nd Day Air'] = "02"
|
108
|
+
DEFAULT_SERVICE_NAME_TO_CODE['UPS 3 Day Select'] = "12"
|
109
|
+
|
110
|
+
SHIPMENT_DELIVERY_CONFIRMATION_CODES = {
|
111
|
+
delivery_confirmation_signature_required: 1,
|
112
|
+
delivery_confirmation_adult_signature_required: 2
|
113
|
+
}
|
114
|
+
|
115
|
+
PACKAGE_DELIVERY_CONFIRMATION_CODES = {
|
116
|
+
delivery_confirmation: 1,
|
117
|
+
delivery_confirmation_signature_required: 2,
|
118
|
+
delivery_confirmation_adult_signature_required: 3,
|
119
|
+
usps_delivery_confirmation: 4
|
120
|
+
}
|
121
|
+
|
105
122
|
def requirements
|
106
123
|
[:key, :login, :password]
|
107
124
|
end
|
@@ -144,8 +161,8 @@ module ActiveShipping
|
|
144
161
|
xml = parse_ship_confirm(confirm_response)
|
145
162
|
success = response_success?(xml)
|
146
163
|
message = response_message(xml)
|
147
|
-
digest = response_digest(xml)
|
148
164
|
raise message unless success
|
165
|
+
digest = response_digest(xml)
|
149
166
|
|
150
167
|
# STEP 2: Accept. Use shipment digest in first response to get the actual label.
|
151
168
|
accept_request = build_accept_request(digest, options)
|
@@ -164,6 +181,16 @@ module ActiveShipping
|
|
164
181
|
end
|
165
182
|
end
|
166
183
|
|
184
|
+
def get_delivery_date_estimates(origin, destination, packages, pickup_date=Date.current, options = {})
|
185
|
+
origin, destination = upsified_location(origin), upsified_location(destination)
|
186
|
+
options = @options.merge(options)
|
187
|
+
packages = Array(packages)
|
188
|
+
access_request = build_access_request
|
189
|
+
dates_request = build_delivery_dates_request(origin, destination, packages, pickup_date, options)
|
190
|
+
response = commit(:delivery_dates, save_request(access_request + dates_request), (options[:test] || false))
|
191
|
+
parse_delivery_dates_response(origin, destination, packages, response, options)
|
192
|
+
end
|
193
|
+
|
167
194
|
protected
|
168
195
|
|
169
196
|
def upsified_location(location)
|
@@ -182,7 +209,7 @@ module ActiveShipping
|
|
182
209
|
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
183
210
|
xml.AccessRequest do
|
184
211
|
xml.AccessLicenseNumber(@options[:key])
|
185
|
-
xml.UserId(@options[:
|
212
|
+
xml.UserId(@options[:login])
|
186
213
|
xml.Password(@options[:password])
|
187
214
|
end
|
188
215
|
end
|
@@ -194,9 +221,7 @@ module ActiveShipping
|
|
194
221
|
xml.RatingServiceSelectionRequest do
|
195
222
|
xml.Request do
|
196
223
|
xml.RequestAction('Rate')
|
197
|
-
xml.RequestOption('Shop')
|
198
|
-
# not implemented: 'Rate' RequestOption to specify a single service query
|
199
|
-
# xml.RequestOption((options[:service].nil? or options[:service] == :all) ? 'Shop' : 'Rate')
|
224
|
+
xml.RequestOption((options[:service].nil?) ? 'Shop' : 'Rate')
|
200
225
|
end
|
201
226
|
|
202
227
|
pickup_type = options[:pickup_type] || :daily_pickup
|
@@ -226,13 +251,19 @@ module ActiveShipping
|
|
226
251
|
# * Shipment/AlternateDeliveryTime element
|
227
252
|
# * Shipment/DocumentsOnly element
|
228
253
|
|
254
|
+
unless options[:service].nil?
|
255
|
+
xml.Service do
|
256
|
+
xml.Code(options[:service])
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
229
260
|
Array(packages).each do |package|
|
230
261
|
options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
|
231
262
|
build_package_node(xml, package, options)
|
232
263
|
end
|
233
264
|
|
234
265
|
# not implemented: * Shipment/ShipmentServiceOptions element
|
235
|
-
if options[:
|
266
|
+
if options[:negotiated_rates]
|
236
267
|
xml.RateInformation do
|
237
268
|
xml.NegotiatedRatesIndicator
|
238
269
|
end
|
@@ -251,18 +282,34 @@ module ActiveShipping
|
|
251
282
|
# * shipper: who is sending the package and where it should be returned
|
252
283
|
# if it is undeliverable.
|
253
284
|
# * ship_from: where the package is picked up.
|
254
|
-
# * service_code: default to '
|
255
|
-
# * service_descriptor: default to 'Next Day Air Early AM'
|
285
|
+
# * service_code: default to '03'
|
256
286
|
# * saturday_delivery: any truthy value causes this element to exist
|
257
287
|
# * optional_processing: 'validate' (blank) or 'nonvalidate' or blank
|
258
|
-
#
|
259
|
-
|
260
|
-
|
261
|
-
|
288
|
+
# * paperless_invoice: set to truthy if using paperless invoice to ship internationally
|
289
|
+
# * terms_of_shipment: used with paperless invoice to specify who pays duties and taxes
|
290
|
+
# * reference_numbers: Array of hashes with :value => a reference number value and optionally :code => reference number type
|
291
|
+
# * prepay: if truthy the shipper will be bill immediatly. Otherwise the shipper is billed when the label is used.
|
292
|
+
# * negotiated_rates: if truthy negotiated rates will be requested from ups. Only valid if shipper account has negotiated rates.
|
293
|
+
# * delivery_confirmation: Can be set to any key from SHIPMENT_DELIVERY_CONFIRMATION_CODES. Can also be set on package level via package.options
|
294
|
+
def build_shipment_request(origin, destination, packages, options={})
|
295
|
+
packages = Array(packages)
|
296
|
+
options[:international] = origin.country.name != destination.country.name
|
297
|
+
options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
|
298
|
+
|
299
|
+
if allow_package_level_reference_numbers(origin, destination)
|
300
|
+
if options[:reference_numbers]
|
301
|
+
packages.each do |package|
|
302
|
+
package.options[:reference_numbers] = options[:reference_numbers]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
options[:reference_numbers] = []
|
306
|
+
end
|
307
|
+
|
308
|
+
handle_delivery_confirmation_options(origin, destination, packages, options)
|
309
|
+
|
262
310
|
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
263
311
|
xml.ShipmentConfirmRequest do
|
264
312
|
xml.Request do
|
265
|
-
# Required element and the text must be "ShipConfirm"
|
266
313
|
xml.RequestAction('ShipConfirm')
|
267
314
|
# Required element cotnrols level of address validation.
|
268
315
|
xml.RequestOption(options[:optional_processing] || 'validate')
|
@@ -275,54 +322,96 @@ module ActiveShipping
|
|
275
322
|
end
|
276
323
|
|
277
324
|
xml.Shipment do
|
278
|
-
# Required element.
|
279
325
|
xml.Service do
|
280
|
-
xml.Code(options[:service_code] || '
|
281
|
-
xml.Description(options[:service_description] || 'Next Day Air Early AM')
|
326
|
+
xml.Code(options[:service_code] || '03')
|
282
327
|
end
|
283
328
|
|
284
|
-
|
285
|
-
build_location_node(xml, '
|
329
|
+
build_location_node(xml, 'ShipTo', destination, options)
|
330
|
+
build_location_node(xml, 'ShipFrom', origin, options)
|
286
331
|
# Required element. The company whose account is responsible for the label(s).
|
287
|
-
build_location_node(xml, 'Shipper', options[:shipper] || origin,
|
288
|
-
# Required if pickup is different different from shipper's address.
|
289
|
-
build_location_node(xml, 'ShipFrom', options[:ship_from], {}) if options[:ship_from]
|
332
|
+
build_location_node(xml, 'Shipper', options[:shipper] || origin, options)
|
290
333
|
|
291
|
-
# Optional.
|
292
334
|
if options[:saturday_delivery]
|
293
335
|
xml.ShipmentServiceOptions do
|
294
336
|
xml.SaturdayDelivery
|
295
337
|
end
|
296
338
|
end
|
297
339
|
|
298
|
-
# Optional.
|
299
340
|
if options[:origin_account]
|
300
341
|
xml.RateInformation do
|
301
342
|
xml.NegotiatedRatesIndicator
|
302
343
|
end
|
303
344
|
end
|
304
345
|
|
305
|
-
|
306
|
-
if options[:shipment] && options[:shipment][:reference_number]
|
346
|
+
Array(options[:reference_numbers]).each do |reference_num_info|
|
307
347
|
xml.ReferenceNumber do
|
308
|
-
xml.Code(
|
309
|
-
xml.Value(
|
348
|
+
xml.Code(reference_num_info[:code] || "")
|
349
|
+
xml.Value(reference_num_info[:value])
|
310
350
|
end
|
311
351
|
end
|
312
352
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
353
|
+
if options[:prepay]
|
354
|
+
xml.PaymentInformation do
|
355
|
+
xml.Prepaid do
|
356
|
+
xml.BillShipper do
|
357
|
+
xml.AccountNumber(options[:origin_account])
|
358
|
+
end
|
319
359
|
end
|
320
360
|
end
|
361
|
+
else
|
362
|
+
xml.ItemizedPaymentInformation do
|
363
|
+
xml.ShipmentCharge do
|
364
|
+
# Type '01' means 'Transportation'
|
365
|
+
# This node specifies who will be billed for transportation.
|
366
|
+
xml.Type('01')
|
367
|
+
xml.BillShipper do
|
368
|
+
xml.AccountNumber(options[:origin_account])
|
369
|
+
end
|
370
|
+
end
|
371
|
+
if options[:terms_of_shipment] == 'DDP'
|
372
|
+
# DDP stands for delivery duty paid and means the shipper will cover duties and taxes
|
373
|
+
# Otherwise UPS will charge the receiver
|
374
|
+
xml.ShipmentCharge do
|
375
|
+
xml.Type('02') # Type '02' means 'Duties and Taxes'
|
376
|
+
xml.BillShipper do
|
377
|
+
xml.AccountNumber(options[:origin_account])
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
if options[:international]
|
385
|
+
build_location_node(xml, 'SoldTo', options[:sold_to] || destination, options)
|
386
|
+
|
387
|
+
if origin.country_code(:alpha2) == 'US' && ['CA', 'PR'].include?(destination.country_code(:alpha2))
|
388
|
+
# Required for shipments from the US to Puerto Rico or Canada
|
389
|
+
xml.InvoiceLineTotal do
|
390
|
+
total_value = packages.inject(0) {|sum, package| sum + (package.value || 0)}
|
391
|
+
xml.MonetaryValue(total_value)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
contents_description = packages.map {|p| p.options[:description]}.compact.join(',')
|
396
|
+
unless contents_description.empty?
|
397
|
+
xml.Description(contents_description)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
xml.ShipmentServiceOptions do
|
402
|
+
if delivery_confirmation = options[:delivery_confirmation]
|
403
|
+
xml.DeliveryConfirmation do
|
404
|
+
xml.DCISType(SHIPMENT_DELIVERY_CONFIRMATION_CODES[delivery_confirmation])
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
if options[:international]
|
409
|
+
build_international_forms(xml, origin, destination, packages, options)
|
410
|
+
end
|
321
411
|
end
|
322
412
|
|
323
413
|
# A request may specify multiple packages.
|
324
|
-
|
325
|
-
Array(packages).each do |package|
|
414
|
+
packages.each do |package|
|
326
415
|
build_package_node(xml, package, options)
|
327
416
|
end
|
328
417
|
end
|
@@ -343,6 +432,61 @@ module ActiveShipping
|
|
343
432
|
xml_builder.to_xml
|
344
433
|
end
|
345
434
|
|
435
|
+
def build_delivery_dates_request(origin, destination, packages, pickup_date, options={})
|
436
|
+
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
437
|
+
|
438
|
+
xml.TimeInTransitRequest do
|
439
|
+
xml.Request do
|
440
|
+
xml.RequestAction('TimeInTransit')
|
441
|
+
end
|
442
|
+
|
443
|
+
build_address_artifact_format_location(xml, 'TransitFrom', origin)
|
444
|
+
build_address_artifact_format_location(xml, 'TransitTo', destination)
|
445
|
+
|
446
|
+
xml.InvoiceLineTotal do
|
447
|
+
xml.CurrencyCode('USD')
|
448
|
+
total_value = packages.inject(0) {|sum, package| sum + package.value}
|
449
|
+
xml.MonetaryValue(total_value)
|
450
|
+
end
|
451
|
+
|
452
|
+
xml.PickupDate(pickup_date.strftime('%Y%m%d'))
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
xml_builder.to_xml
|
457
|
+
end
|
458
|
+
|
459
|
+
def build_international_forms(xml, origin, destination, packages, options)
|
460
|
+
if options[:paperless_invoice]
|
461
|
+
xml.InternationalForms do
|
462
|
+
xml.FormType('01') # 01 is "Invoice"
|
463
|
+
xml.InvoiceDate(options[:invoice_date] || Date.today.strftime('%Y%m%d'))
|
464
|
+
xml.ReasonForExport(options[:reason_for_export] || 'SALE')
|
465
|
+
xml.CurrencyCode(options[:currency_code] || 'USD')
|
466
|
+
|
467
|
+
if options[:terms_of_shipment]
|
468
|
+
xml.TermsOfShipment(options[:terms_of_shipment])
|
469
|
+
end
|
470
|
+
|
471
|
+
packages.each do |package|
|
472
|
+
xml.Product do |xml|
|
473
|
+
xml.Description(package.options[:description])
|
474
|
+
xml.CommodityCode(package.options[:commodity_code])
|
475
|
+
xml.OriginCountryCode(origin.country_code(:alpha2))
|
476
|
+
xml.Unit do |xml|
|
477
|
+
xml.Value(package.value / (package.options[:item_count] || 1))
|
478
|
+
xml.Number((package.options[:item_count] || 1))
|
479
|
+
xml.UnitOfMeasurement do |xml|
|
480
|
+
# NMB = number. You can specify units in barrels, boxes, etc. Codes are in the api docs.
|
481
|
+
xml.Code(package.options[:unit_of_item_count] || 'NMB')
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
346
490
|
def build_accept_request(digest, options = {})
|
347
491
|
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
348
492
|
xml.ShipmentAcceptRequest do
|
@@ -374,8 +518,7 @@ module ActiveShipping
|
|
374
518
|
# * Shipment/(Shipper|ShipTo|ShipFrom)/AttentionName element
|
375
519
|
# * Shipment/(Shipper|ShipTo|ShipFrom)/TaxIdentificationNumber element
|
376
520
|
xml.public_send(name) do
|
377
|
-
|
378
|
-
if shipper_name = (options[:origin_name] || @options[:origin_name])
|
521
|
+
if shipper_name = (location.name || location.company_name || options[:origin_name])
|
379
522
|
xml.Name(shipper_name)
|
380
523
|
end
|
381
524
|
xml.PhoneNumber(location.phone.gsub(/[^\d]/, '')) unless location.phone.blank?
|
@@ -387,7 +530,7 @@ module ActiveShipping
|
|
387
530
|
xml.ShipperAssignedIdentificationNumber(destination_account)
|
388
531
|
end
|
389
532
|
|
390
|
-
if name = location.company_name || location.name
|
533
|
+
if name = (location.company_name || location.name || options[:origin_name])
|
391
534
|
xml.CompanyName(name)
|
392
535
|
end
|
393
536
|
|
@@ -414,6 +557,18 @@ module ActiveShipping
|
|
414
557
|
end
|
415
558
|
end
|
416
559
|
|
560
|
+
def build_address_artifact_format_location(xml, name, location)
|
561
|
+
xml.public_send(name) do
|
562
|
+
xml.AddressArtifactFormat do
|
563
|
+
xml.PoliticalDivision2(location.city)
|
564
|
+
xml.PoliticalDivision1(location.province)
|
565
|
+
xml.CountryCode(location.country_code(:alpha2))
|
566
|
+
xml.PostcodePrimaryLow(location.postal_code)
|
567
|
+
xml.ResidentialAddressIndicator(true) unless location.commercial?
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
417
572
|
def build_package_node(xml, package, options = {})
|
418
573
|
xml.Package do
|
419
574
|
|
@@ -443,15 +598,23 @@ module ActiveShipping
|
|
443
598
|
xml.Weight([value, 0.1].max)
|
444
599
|
end
|
445
600
|
|
446
|
-
|
601
|
+
|
602
|
+
Array(package.options[:reference_numbers]).each do |reference_number_info|
|
447
603
|
xml.ReferenceNumber do
|
448
|
-
xml.Code(
|
449
|
-
xml.Value(
|
604
|
+
xml.Code(reference_number_info[:code] || "")
|
605
|
+
xml.Value(reference_number_info[:value])
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
xml.PackageServiceOptions do
|
610
|
+
if delivery_confirmation = package.options[:delivery_confirmation]
|
611
|
+
xml.DeliveryConfirmation do
|
612
|
+
xml.DCISType(PACKAGE_DELIVERY_CONFIRMATION_CODES[delivery_confirmation])
|
613
|
+
end
|
450
614
|
end
|
451
615
|
end
|
452
616
|
|
453
617
|
# not implemented: * Shipment/Package/LargePackageIndicator element
|
454
|
-
# * Shipment/Package/PackageServiceOptions element
|
455
618
|
# * Shipment/Package/AdditionalHandling element
|
456
619
|
end
|
457
620
|
end
|
@@ -526,10 +689,15 @@ module ActiveShipping
|
|
526
689
|
|
527
690
|
# Get scheduled delivery date
|
528
691
|
unless status == :delivered
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
692
|
+
scheduled_delivery_date_node = first_shipment.at('ScheduledDeliveryDate')
|
693
|
+
scheduled_delivery_date_node ||= first_shipment.at('RescheduledDeliveryDate')
|
694
|
+
|
695
|
+
if scheduled_delivery_date_node
|
696
|
+
scheduled_delivery_date = parse_ups_datetime(
|
697
|
+
:date => scheduled_delivery_date_node,
|
698
|
+
:time => nil
|
699
|
+
)
|
700
|
+
end
|
533
701
|
end
|
534
702
|
|
535
703
|
activities = first_package.css('> Activity')
|
@@ -591,6 +759,30 @@ module ActiveShipping
|
|
591
759
|
:tracking_number => tracking_number)
|
592
760
|
end
|
593
761
|
|
762
|
+
def parse_delivery_dates_response(origin, destination, packages, response, options={})
|
763
|
+
xml = build_document(response, 'TimeInTransitResponse')
|
764
|
+
success = response_success?(xml)
|
765
|
+
message = response_message(xml)
|
766
|
+
delivery_estimates = []
|
767
|
+
|
768
|
+
if success
|
769
|
+
xml.css('ServiceSummary').each do |service_summary|
|
770
|
+
# Translate the Time in Transit Codes to the service codes used elsewhere
|
771
|
+
service_name = service_summary.at('Service/Description').text
|
772
|
+
service_code = UPS::DEFAULT_SERVICE_NAME_TO_CODE[service_name]
|
773
|
+
date = Date.strptime(service_summary.at('EstimatedArrival/Date').text, '%Y-%m-%d')
|
774
|
+
business_transit_days = service_summary.at('EstimatedArrival/BusinessTransitDays').text.to_i
|
775
|
+
delivery_estimates << DeliveryDateEstimate.new(origin, destination, self.class.class_variable_get(:@@name),
|
776
|
+
service_name,
|
777
|
+
:service_code => service_code,
|
778
|
+
:guaranteed => service_summary.at('Guaranteed/Code').text == 'Y',
|
779
|
+
:date => date,
|
780
|
+
:business_transit_days => business_transit_days)
|
781
|
+
end
|
782
|
+
end
|
783
|
+
response = DeliveryDateEstimatesResponse.new(success, message, Hash.from_xml(response).values.first, :delivery_estimates => delivery_estimates, :xml => response, :request => last_request)
|
784
|
+
end
|
785
|
+
|
594
786
|
def location_from_address_node(address)
|
595
787
|
return nil unless address
|
596
788
|
Location.new(
|
@@ -621,7 +813,9 @@ module ActiveShipping
|
|
621
813
|
end
|
622
814
|
|
623
815
|
def response_message(document)
|
624
|
-
document.root.at_xpath('Response/
|
816
|
+
status = document.root.at_xpath('Response/ResponseStatusDescription').try(:text)
|
817
|
+
desc = document.root.at_xpath('Response/Error/ErrorDescription').try(:text)
|
818
|
+
[status, desc].select(&:present?).join(": ").presence || "UPS could not process the request."
|
625
819
|
end
|
626
820
|
|
627
821
|
def response_digest(xml)
|
@@ -663,5 +857,50 @@ module ActiveShipping
|
|
663
857
|
name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
|
664
858
|
name || DEFAULT_SERVICES[code]
|
665
859
|
end
|
860
|
+
|
861
|
+
def allow_package_level_reference_numbers(origin, destination)
|
862
|
+
# if the package is US -> US or PR -> PR the only type of reference numbers that are allowed are package-level
|
863
|
+
# Otherwise the only type of reference numbers that are allowed are shipment-level
|
864
|
+
[['US','US'],['PR', 'PR']].include?([origin,destination].map(&:country_code))
|
865
|
+
end
|
866
|
+
|
867
|
+
def handle_delivery_confirmation_options(origin, destination, packages, options)
|
868
|
+
if package_level_delivery_confirmation?(origin, destination)
|
869
|
+
handle_package_level_delivery_confirmation(origin, destination, packages, options)
|
870
|
+
else
|
871
|
+
handle_shipment_level_delivery_confirmation(origin, destination, packages, options)
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
def handle_package_level_delivery_confirmation(origin, destination, packages, options)
|
876
|
+
packages.each do |package|
|
877
|
+
# Transfer shipment-level option to package with no specified delivery_confirmation
|
878
|
+
package.options[:delivery_confirmation] = options[:delivery_confirmation] unless package.options[:delivery_confirmation]
|
879
|
+
|
880
|
+
# Assert that option is valid
|
881
|
+
if package.options[:delivery_confirmation] && !PACKAGE_DELIVERY_CONFIRMATION_CODES[package.options[:delivery_confirmation]]
|
882
|
+
raise "Invalid delivery_confirmation option on package: '#{package.options[:delivery_confirmation]}'. Use a key from PACKAGE_DELIVERY_CONFIRMATION_CODES"
|
883
|
+
end
|
884
|
+
end
|
885
|
+
options.delete(:delivery_confirmation)
|
886
|
+
end
|
887
|
+
|
888
|
+
def handle_shipment_level_delivery_confirmation(origin, destination, packages, options)
|
889
|
+
if packages.any? { |p| p.options[:delivery_confirmation] }
|
890
|
+
raise "origin/destination pair does not support package level delivery_confirmation options"
|
891
|
+
end
|
892
|
+
|
893
|
+
if options[:delivery_confirmation] && !SHIPMENT_DELIVERY_CONFIRMATION_CODES[options[:delivery_confirmation]]
|
894
|
+
raise "Invalid delivery_confirmation option: '#{options[:delivery_confirmation]}'. Use a key from SHIPMENT_DELIVERY_CONFIRMATION_CODES"
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
# For certain origin/destination pairs, UPS allows each package in a shipment to have a specified delivery_confirmation option
|
899
|
+
# otherwise the delivery_confirmation option must be specified on the entire shipment.
|
900
|
+
# See Appendix P of UPS Shipping Package XML Developers Guide for the rules on which the logic below is based.
|
901
|
+
def package_level_delivery_confirmation?(origin, destination)
|
902
|
+
origin.country_code == destination.country_code ||
|
903
|
+
[['US','PR'], ['PR','US']].include?([origin,destination].map(&:country_code))
|
904
|
+
end
|
666
905
|
end
|
667
906
|
end
|