active_shipping 1.0.0.pre4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|