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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d67ea13d5af138aa5a66c9cdf708edae95282d94
|
4
|
+
data.tar.gz: 061f00b789943cfb9def0ca495315a6d0dd482a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a426a84ca1c081e64b1bcffa792ccfbecc594648f14272ec6c00385fe4ce6fbab54c9cc9ec1505059dc7cca8b0604cb2faaf9bbbc55a647c728239137e95fa0d
|
7
|
+
data.tar.gz: 46d1e8c45a4c5fc7594626c1bf8000db652cf5a88cbd49e992323b0de9c2b597d62988bfb93991da6a90a09fae36e1f5c27aef224d038a4f02464b428eb0e5a7
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# ActiveShipping CHANGELOG
|
2
2
|
|
3
|
+
### v1.0.0
|
4
|
+
|
5
|
+
- **BREAKING CHANGE:** Change namespace from `ActiveMerchant::Shipping` to `ActiveShipping`
|
6
|
+
- Drop support for Ruby 1.8 and Ruby 1.9.
|
7
|
+
- Drop support for ActiveSupport < 3.2, support up to ActiveSupport 4.2.
|
8
|
+
- Updated Fedex to use latest API version for tracking
|
9
|
+
- Various improvements to UPS carrier implementation.
|
10
|
+
- Small bugfixes in USPS carrier implementation.
|
11
|
+
- Various small bugfixes in XML handling for several carriers.
|
12
|
+
- Rewite all carriers to use nokogiri for XML parsing and generating.
|
13
|
+
- Bump `active_utils` dependency to require 3.x to avoid conflicts with `ActiveMerchant`.
|
14
|
+
- Extracted `quantified` into separate gem.
|
15
|
+
- Removed vendored `XmlNode` library.
|
16
|
+
- Removed `builder` dependency.
|
17
|
+
- Improved test setup that allows running functional tests on CI.
|
18
|
+
- Improved documentation of the abstraction API.
|
19
|
+
|
3
20
|
### v0.10.1
|
4
21
|
|
5
22
|
- Canada Post PWS: Makes wrapper act more consistently with the rest of the API [jnormore]
|
data/CONTRIBUTING.md
CHANGED
@@ -9,7 +9,7 @@ Please use clean, concise code that follows Ruby community standards. For exampl
|
|
9
9
|
- Be consistent
|
10
10
|
- Don't use too much white space
|
11
11
|
- Use 2 space indent, no tabs.
|
12
|
-
- No spaces after (
|
12
|
+
- No spaces after `(`, `[` and before `]`, `)`
|
13
13
|
- Nor too little
|
14
14
|
- Use spaces around operators and after commas, colons and semicolons
|
15
15
|
- Indent when as deep as case
|
@@ -19,7 +19,7 @@ Please use clean, concise code that follows Ruby community standards. For exampl
|
|
19
19
|
|
20
20
|
- Add unit tests, and remote tests to make sure we won't introduce regressions to your code later on.
|
21
21
|
- Make sure CI passes for all Ruby versions and dependency versions we support.
|
22
|
-
- XML handling: use `
|
22
|
+
- XML handling: use `Nokogiri.XML` for parsing XML, and `Nokogiri::XML::Builder` to generate it.
|
23
23
|
- JSON: use the JSON module that is included in Rubys standard ibrary
|
24
24
|
- HTTP: use `ActiveUtils`'s `PostsData`.
|
25
25
|
- Do not add new gem dependencies.
|
data/Gemfile.activesupport32
CHANGED
data/README.md
CHANGED
@@ -97,7 +97,7 @@ Active Shipping is currently being used and improved in a production environment
|
|
97
97
|
|
98
98
|
## Running the tests
|
99
99
|
|
100
|
-
After installing dependencies with `bundle install`, you can run the unit tests with `rake test:units` and the remote tests with `rake test:remote`. The unit tests mock out requests and responses so that everything runs locally, while the remote tests actually hit the carrier servers. For the remote tests, you'll need valid test credentials for any carriers' tests you want to run. The credentials should go in ~/.active_shipping/credentials.yml, and the format of that file can be seen in the included [credentials.yml](https://github.com/Shopify/active_shipping/blob/master/test/credentials.yml).
|
100
|
+
After installing dependencies with `bundle install`, you can run the unit tests with `rake test:units` and the remote tests with `rake test:remote`. The unit tests mock out requests and responses so that everything runs locally, while the remote tests actually hit the carrier servers. For the remote tests, you'll need valid test credentials for any carriers' tests you want to run. The credentials should go in ~/.active_shipping/credentials.yml, and the format of that file can be seen in the included [credentials.yml](https://github.com/Shopify/active_shipping/blob/master/test/credentials.yml). For some carriers, we have public credentials you can use for testing: see `.travis.yml`.
|
101
101
|
|
102
102
|
## Development
|
103
103
|
|
data/Rakefile
CHANGED
data/active_shipping.gemspec
CHANGED
@@ -14,16 +14,16 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.description = "Get rates and tracking info from various shipping carriers. Extracted from Shopify."
|
15
15
|
s.license = 'MIT'
|
16
16
|
|
17
|
-
s.add_dependency('quantified', '~> 1.0')
|
17
|
+
s.add_dependency('quantified', '~> 1.0.1')
|
18
18
|
s.add_dependency('activesupport', '>= 3.2', '< 5.0.0')
|
19
19
|
s.add_dependency('active_utils', '~> 3.0.0')
|
20
|
-
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
|
21
20
|
s.add_dependency('nokogiri', '>= 1.6')
|
22
21
|
|
23
22
|
s.add_development_dependency('minitest')
|
24
23
|
s.add_development_dependency('rake')
|
25
24
|
s.add_development_dependency('mocha', '~> 1')
|
26
25
|
s.add_development_dependency('timecop')
|
26
|
+
s.add_development_dependency('business_time')
|
27
27
|
|
28
28
|
s.files = `git ls-files`.split($/)
|
29
29
|
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/lib/active_shipping.rb
CHANGED
@@ -27,20 +27,19 @@ require 'active_support/core_ext/enumerable'
|
|
27
27
|
require 'active_support/core_ext/class/attribute_accessors'
|
28
28
|
require 'active_utils'
|
29
29
|
|
30
|
-
require 'rexml/document'
|
31
30
|
require 'nokogiri'
|
32
31
|
require 'quantified'
|
33
32
|
|
34
|
-
require 'vendor/xml_node/lib/xml_node'
|
35
|
-
|
36
33
|
require 'active_shipping/response'
|
37
34
|
require 'active_shipping/rate_response'
|
38
35
|
require 'active_shipping/tracking_response'
|
36
|
+
require 'active_shipping/delivery_date_estimates_response'
|
39
37
|
require 'active_shipping/shipping_response'
|
40
38
|
require 'active_shipping/label_response'
|
41
39
|
require 'active_shipping/package'
|
42
40
|
require 'active_shipping/location'
|
43
41
|
require 'active_shipping/rate_estimate'
|
42
|
+
require 'active_shipping/delivery_date_estimate'
|
44
43
|
require 'active_shipping/shipment_event'
|
45
44
|
require 'active_shipping/shipment_packer'
|
46
45
|
require 'active_shipping/carrier'
|
@@ -31,3 +31,4 @@ ActiveShipping::Carriers.register :CanadaPost, 'active_shipping/carriers/c
|
|
31
31
|
ActiveShipping::Carriers.register :NewZealandPost, 'active_shipping/carriers/new_zealand_post'
|
32
32
|
ActiveShipping::Carriers.register :CanadaPostPWS, 'active_shipping/carriers/canada_post_pws'
|
33
33
|
ActiveShipping::Carriers.register :Stamps, 'active_shipping/carriers/stamps'
|
34
|
+
ActiveShipping::Carriers.register :Correios, 'active_shipping/carriers/correios'
|
@@ -158,13 +158,14 @@ module ActiveShipping
|
|
158
158
|
# service discovery
|
159
159
|
|
160
160
|
def parse_services_response(response)
|
161
|
-
doc =
|
162
|
-
|
161
|
+
doc = Nokogiri.XML(response)
|
162
|
+
doc.remove_namespaces!
|
163
|
+
service_nodes = doc.xpath('services/service')
|
163
164
|
service_nodes.inject({}) do |result, node|
|
164
|
-
service_code = node.
|
165
|
-
service_name = node.
|
166
|
-
service_link = node.
|
167
|
-
service_link_media_type = node.
|
165
|
+
service_code = node.at("service-code").text
|
166
|
+
service_name = node.at("service-name").text
|
167
|
+
service_link = node.at("link").attributes['href'].value
|
168
|
+
service_link_media_type = node.at("link").attributes['media-type'].value
|
168
169
|
result[service_code] = {
|
169
170
|
:name => service_name,
|
170
171
|
:link => service_link,
|
@@ -175,36 +176,35 @@ module ActiveShipping
|
|
175
176
|
end
|
176
177
|
|
177
178
|
def parse_service_options_response(response)
|
178
|
-
doc =
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
result
|
195
|
-
end
|
179
|
+
doc = Nokogiri.XML(response)
|
180
|
+
doc.remove_namespaces!
|
181
|
+
|
182
|
+
service_code = doc.root.at("service-code").text
|
183
|
+
service_name = doc.root.at("service-name").text
|
184
|
+
|
185
|
+
option_nodes = doc.root.xpath('options/option')
|
186
|
+
options = option_nodes.map do |node|
|
187
|
+
option = {
|
188
|
+
:code => node.at("option-code").text,
|
189
|
+
:name => node.at("option-name").text,
|
190
|
+
:required => node.at("mandatory").text != "false",
|
191
|
+
:qualifier_required => node.at("qualifier-required").text != "false",
|
192
|
+
}
|
193
|
+
option[:qualifier_max] = node.at("qualifier-max").text.to_i if node.at("qualifier-max")
|
194
|
+
option
|
196
195
|
end
|
197
|
-
|
198
|
-
|
196
|
+
|
197
|
+
restrictions_node = doc.root.at('restrictions')
|
198
|
+
dimensions_node = restrictions_node.at('dimensional-restrictions')
|
199
199
|
restrictions = {
|
200
|
-
:min_weight => restrictions_node.
|
201
|
-
:max_weight => restrictions_node.
|
202
|
-
:min_length => dimensions_node.
|
203
|
-
:max_length => dimensions_node.
|
204
|
-
:min_height => dimensions_node.
|
205
|
-
:max_height => dimensions_node.
|
206
|
-
:min_width => dimensions_node.
|
207
|
-
:max_width => dimensions_node.
|
200
|
+
:min_weight => restrictions_node.at("weight-restriction").attributes['min'].value.to_i,
|
201
|
+
:max_weight => restrictions_node.at("weight-restriction").attributes['max'].value.to_i,
|
202
|
+
:min_length => dimensions_node.at("length").attributes['min'].value.to_f,
|
203
|
+
:max_length => dimensions_node.at("length").attributes['max'].value.to_f,
|
204
|
+
:min_height => dimensions_node.at("height").attributes['min'].value.to_f,
|
205
|
+
:max_height => dimensions_node.at("height").attributes['max'].value.to_f,
|
206
|
+
:min_width => dimensions_node.at("width").attributes['min'].value.to_f,
|
207
|
+
:max_width => dimensions_node.at("width").attributes['max'].value.to_f
|
208
208
|
}
|
209
209
|
|
210
210
|
{
|
@@ -216,21 +216,22 @@ module ActiveShipping
|
|
216
216
|
end
|
217
217
|
|
218
218
|
def parse_option_response(response)
|
219
|
-
doc =
|
220
|
-
|
221
|
-
|
222
|
-
|
219
|
+
doc = Nokogiri.XML(response)
|
220
|
+
doc.remove_namespaces!
|
221
|
+
|
222
|
+
conflicts = doc.root.xpath('conflicting-options/option-code').map(&:text)
|
223
|
+
prereqs = doc.root.xpath('prerequisite-options/option-code').map(&:text)
|
223
224
|
option = {
|
224
|
-
:code =>
|
225
|
-
:name =>
|
226
|
-
:class =>
|
227
|
-
:prints_on_label =>
|
228
|
-
:qualifier_required =>
|
225
|
+
:code => doc.root.at('option-code').text,
|
226
|
+
:name => doc.root.at('option-name').text,
|
227
|
+
:class => doc.root.at('option-class').text,
|
228
|
+
:prints_on_label => doc.root.at('prints-on-label').text != "false",
|
229
|
+
:qualifier_required => doc.root.at('qualifier-required').text != "false",
|
229
230
|
}
|
230
231
|
option[:conflicting_options] = conflicts if conflicts
|
231
232
|
option[:prerequisite_options] = prereqs if prereqs
|
232
233
|
|
233
|
-
option[:qualifier_max] =
|
234
|
+
option[:qualifier_max] = doc.root.at("qualifier-max").text.to_i if doc.root.at("qualifier-max")
|
234
235
|
option
|
235
236
|
end
|
236
237
|
|
@@ -238,30 +239,32 @@ module ActiveShipping
|
|
238
239
|
|
239
240
|
def build_rates_request(origin, destination, line_items = [], options = {}, package = nil, services = [])
|
240
241
|
line_items = Array(line_items)
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
242
|
+
|
243
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
244
|
+
xml.public_send('mailing-scenario', :xmlns => "http://www.canadapost.ca/ws/ship/rate") do
|
245
|
+
customer_number_node(xml, options)
|
246
|
+
contract_id_node(xml, options)
|
247
|
+
quote_type_node(xml, options)
|
248
|
+
expected_mailing_date_node(xml, shipping_date(options)) if options[:shipping_date]
|
249
|
+
shipping_options_node(xml, RATES_OPTIONS, options)
|
250
|
+
parcel_node(xml, line_items, package)
|
251
|
+
origin_node(xml, origin)
|
252
|
+
destination_node(xml, destination)
|
253
|
+
services_node(xml, services) unless services.blank?
|
254
|
+
end
|
252
255
|
end
|
253
|
-
|
256
|
+
builder.to_xml
|
254
257
|
end
|
255
258
|
|
256
259
|
def parse_rates_response(response, origin, destination)
|
257
|
-
doc =
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
rates =
|
262
|
-
service_name = node.
|
263
|
-
service_code = node.
|
264
|
-
total_price = node.
|
260
|
+
doc = Nokogiri.XML(response)
|
261
|
+
doc.remove_namespaces!
|
262
|
+
raise ActiveShipping::ResponseError, "No Quotes" unless doc.at('price-quotes')
|
263
|
+
|
264
|
+
rates = doc.root.xpath('price-quote').map do |node|
|
265
|
+
service_name = node.at("service-name").text
|
266
|
+
service_code = node.at("service-code").text
|
267
|
+
total_price = node.at('price-details/due').text
|
265
268
|
expected_date = expected_date_from_node(node)
|
266
269
|
options = {
|
267
270
|
:service_code => service_code,
|
@@ -277,29 +280,30 @@ module ActiveShipping
|
|
277
280
|
# tracking
|
278
281
|
|
279
282
|
def parse_tracking_response(response)
|
280
|
-
doc =
|
281
|
-
|
283
|
+
doc = Nokogiri.XML(response)
|
284
|
+
doc.remove_namespaces!
|
285
|
+
raise ActiveShipping::ResponseError, "No Tracking" unless doc.at('tracking-detail')
|
282
286
|
|
283
|
-
events =
|
287
|
+
events = doc.root.xpath('significant-events/occurrence')
|
284
288
|
|
285
289
|
shipment_events = build_tracking_events(events)
|
286
|
-
change_date =
|
287
|
-
expected_date =
|
288
|
-
dest_postal_code =
|
290
|
+
change_date = doc.root.at('changed-expected-date').text
|
291
|
+
expected_date = doc.root.at('expected-delivery-date').text
|
292
|
+
dest_postal_code = doc.root.at('destination-postal-id').text
|
289
293
|
destination = Location.new(:postal_code => dest_postal_code)
|
290
|
-
origin = Location.new(origin_hash_for(
|
294
|
+
origin = Location.new(origin_hash_for(doc.root))
|
291
295
|
options = {
|
292
296
|
:carrier => @@name,
|
293
|
-
:service_name =>
|
297
|
+
:service_name => doc.root.at('service-name').text,
|
294
298
|
:expected_date => expected_date.blank? ? nil : Date.parse(expected_date),
|
295
299
|
:changed_date => change_date.blank? ? nil : Date.parse(change_date),
|
296
|
-
:change_reason =>
|
297
|
-
:destination_postal_code =>
|
300
|
+
:change_reason => doc.root.at('changed-expected-delivery-reason').text.strip,
|
301
|
+
:destination_postal_code => doc.root.at('destination-postal-id').text,
|
298
302
|
:shipment_events => shipment_events,
|
299
|
-
:tracking_number =>
|
303
|
+
:tracking_number => doc.root.at('pin').text,
|
300
304
|
:origin => origin,
|
301
305
|
:destination => destination,
|
302
|
-
:customer_number =>
|
306
|
+
:customer_number => doc.root.at('mailed-by-customer-number').text
|
303
307
|
}
|
304
308
|
|
305
309
|
CPPWSTrackingResponse.new(true, "", {}, options)
|
@@ -307,14 +311,15 @@ module ActiveShipping
|
|
307
311
|
|
308
312
|
def build_tracking_events(events)
|
309
313
|
events.map do |event|
|
310
|
-
date = event.
|
311
|
-
time = event.
|
312
|
-
zone = event.
|
314
|
+
date = event.at('event-date').text
|
315
|
+
time = event.at('event-time').text
|
316
|
+
zone = event.at('event-time-zone').text
|
313
317
|
timestamp = DateTime.parse("#{date} #{time} #{zone}")
|
314
318
|
time = Time.utc(timestamp.utc.year, timestamp.utc.month, timestamp.utc.day, timestamp.utc.hour, timestamp.utc.min, timestamp.utc.sec)
|
315
|
-
message = event.
|
316
|
-
location = [event.
|
317
|
-
|
319
|
+
message = event.at('event-description').text
|
320
|
+
location = [event.at('event-retail-name'), event.at('event-site'), event.at('event-province')].
|
321
|
+
reject { |e| e.nil? || e.text.empty? }.join(", ")
|
322
|
+
name = event.at('event-identifier').text
|
318
323
|
ShipmentEvent.new(name, time, location, message)
|
319
324
|
end
|
320
325
|
end
|
@@ -332,109 +337,111 @@ module ActiveShipping
|
|
332
337
|
origin = sanitize_location(origin)
|
333
338
|
destination = sanitize_location(destination)
|
334
339
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
340
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
341
|
+
xml.public_send('non-contract-shipment', :xmlns => "http://www.canadapost.ca/ws/ncshipment") do
|
342
|
+
xml.public_send('delivery-spec') do
|
343
|
+
shipment_service_code_node(xml, options)
|
344
|
+
shipment_sender_node(xml, origin, options)
|
345
|
+
shipment_destination_node(xml, destination, options)
|
346
|
+
shipment_options_node(xml, options)
|
347
|
+
shipment_parcel_node(xml, package)
|
348
|
+
shipment_notification_node(xml, options)
|
349
|
+
shipment_preferences_node(xml, options)
|
350
|
+
references_node(xml, options) # optional > user defined custom notes
|
351
|
+
shipment_customs_node(xml, destination, line_items, options)
|
352
|
+
# COD Remittance defaults to sender
|
353
|
+
end
|
348
354
|
end
|
349
355
|
end
|
350
|
-
|
356
|
+
builder.to_xml
|
351
357
|
end
|
352
358
|
|
353
|
-
def shipment_service_code_node(options)
|
354
|
-
|
359
|
+
def shipment_service_code_node(xml, options)
|
360
|
+
xml.public_send('service-code', options[:service])
|
355
361
|
end
|
356
362
|
|
357
|
-
def shipment_sender_node(location, options)
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
#
|
368
|
-
|
363
|
+
def shipment_sender_node(xml, location, options)
|
364
|
+
xml.public_send('sender') do
|
365
|
+
xml.public_send('name', location.name)
|
366
|
+
xml.public_send('company', location.company) if location.company.present?
|
367
|
+
xml.public_send('contact-phone', location.phone)
|
368
|
+
xml.public_send('address-details') do
|
369
|
+
xml.public_send('address-line-1', location.address1)
|
370
|
+
xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
|
371
|
+
xml.public_send('city', location.city)
|
372
|
+
xml.public_send('prov-state', location.province)
|
373
|
+
# xml.public_send('country-code', location.country_code)
|
374
|
+
xml.public_send('postal-zip-code', location.postal_code)
|
369
375
|
end
|
370
376
|
end
|
371
377
|
end
|
372
378
|
|
373
|
-
def shipment_destination_node(location, options)
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
379
|
+
def shipment_destination_node(xml, location, options)
|
380
|
+
xml.public_send('destination') do
|
381
|
+
xml.public_send('name', location.name)
|
382
|
+
xml.public_send('company', location.company) if location.company.present?
|
383
|
+
xml.public_send('client-voice-number', location.phone)
|
384
|
+
xml.public_send('address-details') do
|
385
|
+
xml.public_send('address-line-1', location.address1)
|
386
|
+
xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
|
387
|
+
xml.public_send('city', location.city)
|
388
|
+
xml.public_send('prov-state', location.province) unless location.province.blank?
|
389
|
+
xml.public_send('country-code', location.country_code)
|
390
|
+
xml.public_send('postal-zip-code', location.postal_code)
|
385
391
|
end
|
386
392
|
end
|
387
393
|
end
|
388
394
|
|
389
|
-
def shipment_options_node(options)
|
390
|
-
|
395
|
+
def shipment_options_node(xml, options)
|
396
|
+
shipping_options_node(xml, SHIPPING_OPTIONS, options)
|
391
397
|
end
|
392
398
|
|
393
|
-
def shipment_notification_node(options)
|
399
|
+
def shipment_notification_node(xml, options)
|
394
400
|
return unless options[:notification_email]
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
401
|
+
xml.public_send('notification') do
|
402
|
+
xml.public_send('email', options[:notification_email])
|
403
|
+
xml.public_send('on-shipment', true)
|
404
|
+
xml.public_send('on-exception', true)
|
405
|
+
xml.public_send('on-delivery', true)
|
400
406
|
end
|
401
407
|
end
|
402
408
|
|
403
|
-
def shipment_preferences_node(options)
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
409
|
+
def shipment_preferences_node(xml, options)
|
410
|
+
xml.public_send('preferences') do
|
411
|
+
xml.public_send('show-packing-instructions', options[:packing_instructions] || true)
|
412
|
+
xml.public_send('show-postage-rate', options[:show_postage_rate] || false)
|
413
|
+
xml.public_send('show-insured-value', true)
|
408
414
|
end
|
409
415
|
end
|
410
416
|
|
411
|
-
def references_node(options)
|
417
|
+
def references_node(xml, options)
|
412
418
|
# custom values
|
413
|
-
#
|
419
|
+
# xml.public_send('references') do
|
414
420
|
# end
|
415
421
|
end
|
416
422
|
|
417
|
-
def shipment_customs_node(destination, line_items, options)
|
423
|
+
def shipment_customs_node(xml, destination, line_items, options)
|
418
424
|
return unless destination.country_code != 'CA'
|
419
425
|
|
420
|
-
|
426
|
+
xml.public_send('customs') do
|
421
427
|
currency = options[:currency] || "CAD"
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
+
xml.public_send('currency', currency)
|
429
|
+
xml.public_send('conversion-from-cad', options[:conversion_from_cad].to_s) if currency != 'CAD' && options[:conversion_from_cad]
|
430
|
+
xml.public_send('reason-for-export', 'SOG') # SOG - Sale of Goods
|
431
|
+
xml.public_send('other-reason', options[:customs_other_reason]) if options[:customs_reason_for_export] && options[:customs_other_reason]
|
432
|
+
xml.public_send('additional-customs-info', options[:customs_addition_info]) if options[:customs_addition_info]
|
433
|
+
xml.public_send('sku-list') do
|
428
434
|
line_items.each do |line_item|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
435
|
+
kg = '%#2.3f' % [sanitize_weight_kg(line_item.kg)]
|
436
|
+
xml.public_send('item') do
|
437
|
+
xml.public_send('hs-tariff-code', line_item.hs_code) if line_item.hs_code && !line_item.hs_code.empty?
|
438
|
+
xml.public_send('sku', line_item.sku) if line_item.sku && !line_item.sku.empty?
|
439
|
+
xml.public_send('customs-description', line_item.name.slice(0, 44))
|
440
|
+
xml.public_send('unit-weight', kg)
|
441
|
+
xml.public_send('customs-value-per-unit', '%.2f' % sanitize_price_from_cents(line_item.value))
|
442
|
+
xml.public_send('customs-number-of-units', line_item.quantity)
|
443
|
+
xml.public_send('country-of-origin', line_item.options[:country_of_origin]) if line_item.options && line_item.options[:country_of_origin] && !line_item.options[:country_of_origin].empty?
|
444
|
+
xml.public_send('province-of-origin', line_item.options[:province_of_origin]) if line_item.options && line_item.options[:province_of_origin] && !line_item.options[:province_of_origin].empty?
|
438
445
|
end
|
439
446
|
end
|
440
447
|
end
|
@@ -442,99 +449,107 @@ module ActiveShipping
|
|
442
449
|
end
|
443
450
|
end
|
444
451
|
|
445
|
-
def shipment_parcel_node(package, options = {})
|
452
|
+
def shipment_parcel_node(xml, package, options = {})
|
446
453
|
weight = sanitize_weight_kg(package.kilograms.to_f)
|
447
|
-
|
448
|
-
|
454
|
+
xml.public_send('parcel-characteristics') do
|
455
|
+
xml.public_send('weight', "%#2.3f" % weight)
|
449
456
|
pkg_dim = package.cm
|
450
457
|
if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
458
|
+
xml.public_send('dimensions') do
|
459
|
+
xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
|
460
|
+
xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
|
461
|
+
xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
|
455
462
|
end
|
463
|
+
xml.public_send('document', false)
|
464
|
+
else
|
465
|
+
xml.public_send('document', true)
|
456
466
|
end
|
457
|
-
|
458
|
-
|
459
|
-
|
467
|
+
|
468
|
+
xml.public_send('mailing-tube', package.tube?)
|
469
|
+
xml.public_send('unpackaged', package.unpackaged?)
|
460
470
|
end
|
461
471
|
end
|
462
472
|
|
463
473
|
def parse_shipment_response(response)
|
464
|
-
doc =
|
465
|
-
|
474
|
+
doc = Nokogiri.XML(response)
|
475
|
+
doc.remove_namespaces!
|
476
|
+
raise ActiveShipping::ResponseError, "No Shipping" unless doc.at('non-contract-shipment-info')
|
466
477
|
options = {
|
467
|
-
:shipping_id =>
|
468
|
-
:tracking_number =>
|
469
|
-
:details_url =>
|
470
|
-
:label_url =>
|
471
|
-
:receipt_url =>
|
478
|
+
:shipping_id => doc.root.at('shipment-id').text,
|
479
|
+
:tracking_number => doc.root.at('tracking-pin').text,
|
480
|
+
:details_url => doc.root.at_xpath("links/link[@rel='details']")['href'],
|
481
|
+
:label_url => doc.root.at_xpath("links/link[@rel='label']")['href'],
|
482
|
+
:receipt_url => doc.root.at_xpath("links/link[@rel='receipt']")['href'],
|
472
483
|
}
|
473
484
|
CPPWSShippingResponse.new(true, "", {}, options)
|
474
485
|
end
|
475
486
|
|
476
487
|
def parse_register_token_response(response)
|
477
|
-
doc =
|
478
|
-
|
488
|
+
doc = Nokogiri.XML(response)
|
489
|
+
doc.remove_namespaces!
|
490
|
+
raise ActiveShipping::ResponseError, "No Registration Token" unless doc.at('token')
|
479
491
|
options = {
|
480
|
-
:token_id =>
|
492
|
+
:token_id => doc.root.at('token-id').text
|
481
493
|
}
|
482
494
|
CPPWSRegisterResponse.new(true, "", {}, options)
|
483
495
|
end
|
484
496
|
|
485
497
|
def parse_merchant_details_response(response)
|
486
|
-
doc =
|
487
|
-
|
488
|
-
raise "No Merchant Info"
|
498
|
+
doc = Nokogiri.XML(response)
|
499
|
+
doc.remove_namespaces!
|
500
|
+
raise "No Merchant Info" unless doc.at('merchant-info')
|
501
|
+
raise "No Merchant Info" if doc.root.at('customer-number').blank?
|
489
502
|
options = {
|
490
|
-
:customer_number =>
|
491
|
-
:contract_number =>
|
492
|
-
:username =>
|
493
|
-
:password =>
|
494
|
-
:has_default_credit_card =>
|
503
|
+
:customer_number => doc.root.at('customer-number').text,
|
504
|
+
:contract_number => doc.root.at('contract-number').text,
|
505
|
+
:username => doc.root.at('merchant-username').text,
|
506
|
+
:password => doc.root.at('merchant-password').text,
|
507
|
+
:has_default_credit_card => doc.root.at('has-default-credit-card').text == 'true'
|
495
508
|
}
|
496
509
|
CPPWSMerchantDetailsResponse.new(true, "", {}, options)
|
497
510
|
end
|
498
511
|
|
499
512
|
def parse_shipment_receipt_response(response)
|
500
|
-
doc =
|
501
|
-
|
502
|
-
|
503
|
-
|
513
|
+
doc = Nokogiri.XML(response)
|
514
|
+
doc.remove_namespaces!
|
515
|
+
root = doc.at('non-contract-shipment-receipt')
|
516
|
+
cc_details_node = root.at('cc-receipt-details')
|
517
|
+
service_standard_node = root.at('service-standard')
|
504
518
|
receipt = {
|
505
|
-
:final_shipping_point => root.
|
506
|
-
:shipping_point_name => root.
|
507
|
-
:service_code => root.
|
508
|
-
:rated_weight => root.
|
509
|
-
:base_amount => root.
|
510
|
-
:pre_tax_amount => root.
|
511
|
-
:gst_amount => root.
|
512
|
-
:pst_amount => root.
|
513
|
-
:hst_amount => root.
|
514
|
-
:charge_amount => cc_details_node.
|
515
|
-
:currency => cc_details_node.
|
516
|
-
:expected_transit_days => service_standard_node.
|
517
|
-
:expected_delivery_date => service_standard_node.
|
519
|
+
:final_shipping_point => root.at("final-shipping-point").text,
|
520
|
+
:shipping_point_name => root.at("shipping-point-name").text,
|
521
|
+
:service_code => root.at("service-code").text,
|
522
|
+
:rated_weight => root.at("rated-weight").text.to_f,
|
523
|
+
:base_amount => root.at("base-amount").text.to_f,
|
524
|
+
:pre_tax_amount => root.at("pre-tax-amount").text.to_f,
|
525
|
+
:gst_amount => root.at("gst-amount").text.to_f,
|
526
|
+
:pst_amount => root.at("pst-amount").text.to_f,
|
527
|
+
:hst_amount => root.at("hst-amount").text.to_f,
|
528
|
+
:charge_amount => cc_details_node.at("charge-amount").text.to_f,
|
529
|
+
:currency => cc_details_node.at("currency").text,
|
530
|
+
:expected_transit_days => service_standard_node.at("expected-transit-time").text.to_i,
|
531
|
+
:expected_delivery_date => service_standard_node.at("expected-delivery-date").text
|
518
532
|
}
|
519
|
-
option_nodes = root.
|
533
|
+
option_nodes = root.xpath('priced-options/priced-option')
|
520
534
|
|
521
|
-
receipt[:priced_options] = if option_nodes
|
522
|
-
|
523
|
-
result[node.
|
535
|
+
receipt[:priced_options] = if option_nodes.length > 0
|
536
|
+
option_nodes.inject({}) do |result, node|
|
537
|
+
result[node.at("option-code").text] = node.at("option-price").text.to_f
|
524
538
|
result
|
525
539
|
end
|
526
540
|
else
|
527
|
-
|
541
|
+
{}
|
528
542
|
end
|
529
543
|
|
530
544
|
receipt
|
531
545
|
end
|
532
546
|
|
533
547
|
def error_response(response, response_klass)
|
534
|
-
doc =
|
535
|
-
|
536
|
-
|
537
|
-
|
548
|
+
doc = Nokogiri.XML(response)
|
549
|
+
doc.remove_namespaces!
|
550
|
+
messages = doc.xpath('messages/message')
|
551
|
+
message = messages.map { |m| m.at('description').text }.join(", ")
|
552
|
+
code = messages.map { |m| m.at('code').text }.join(", ")
|
538
553
|
response_klass.new(false, message, {}, :carrier => @@name, :code => code)
|
539
554
|
end
|
540
555
|
|
@@ -612,110 +627,110 @@ module ActiveShipping
|
|
612
627
|
headers
|
613
628
|
end
|
614
629
|
|
615
|
-
def customer_number_node(options)
|
616
|
-
|
630
|
+
def customer_number_node(xml, options)
|
631
|
+
xml.public_send("customer-number", options[:customer_number] || customer_number)
|
617
632
|
end
|
618
633
|
|
619
|
-
def contract_id_node(options)
|
620
|
-
|
634
|
+
def contract_id_node(xml, options)
|
635
|
+
xml.public_send("contract-id", options[:contract_id]) if options[:contract_id]
|
621
636
|
end
|
622
637
|
|
623
|
-
def quote_type_node(options)
|
624
|
-
|
638
|
+
def quote_type_node(xml, options)
|
639
|
+
xml.public_send("quote-type", 'commercial')
|
625
640
|
end
|
626
641
|
|
627
|
-
def expected_mailing_date_node(date_as_string)
|
628
|
-
|
642
|
+
def expected_mailing_date_node(xml, date_as_string)
|
643
|
+
xml.public_send("expected-mailing-date", date_as_string)
|
629
644
|
end
|
630
645
|
|
631
|
-
def parcel_node(line_items, package = nil, options = {})
|
646
|
+
def parcel_node(xml, line_items, package = nil, options = {})
|
632
647
|
weight = sanitize_weight_kg(package && !package.kilograms.zero? ? package.kilograms.to_f : line_items.sum(&:kilograms).to_f)
|
633
|
-
|
634
|
-
|
648
|
+
xml.public_send('parcel-characteristics') do
|
649
|
+
xml.public_send('weight', "%#2.3f" % weight)
|
635
650
|
if package
|
636
651
|
pkg_dim = package.cm
|
637
652
|
if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
653
|
+
xml.public_send('dimensions') do
|
654
|
+
xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
|
655
|
+
xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
|
656
|
+
xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
|
642
657
|
end
|
643
658
|
end
|
644
659
|
end
|
645
|
-
|
646
|
-
|
647
|
-
|
660
|
+
xml.public_send('mailing-tube', line_items.any?(&:tube?))
|
661
|
+
xml.public_send('oversized', true) if line_items.any?(&:oversized?)
|
662
|
+
xml.public_send('unpackaged', line_items.any?(&:unpackaged?))
|
648
663
|
end
|
649
664
|
end
|
650
665
|
|
651
|
-
def origin_node(location)
|
666
|
+
def origin_node(xml, location)
|
652
667
|
origin = sanitize_location(location)
|
653
|
-
|
668
|
+
xml.public_send("origin-postal-code", origin.zip)
|
654
669
|
end
|
655
670
|
|
656
|
-
def destination_node(location)
|
671
|
+
def destination_node(xml, location)
|
657
672
|
destination = sanitize_location(location)
|
658
673
|
case destination.country_code
|
659
674
|
when 'CA'
|
660
|
-
|
661
|
-
|
662
|
-
|
675
|
+
xml.public_send('destination') do
|
676
|
+
xml.public_send('domestic') do
|
677
|
+
xml.public_send('postal-code', destination.postal_code)
|
663
678
|
end
|
664
679
|
end
|
665
680
|
|
666
681
|
when 'US'
|
667
|
-
|
668
|
-
|
669
|
-
|
682
|
+
xml.public_send('destination') do
|
683
|
+
xml.public_send('united-states') do
|
684
|
+
xml.public_send('zip-code', destination.postal_code)
|
670
685
|
end
|
671
686
|
end
|
672
687
|
|
673
688
|
else
|
674
|
-
|
675
|
-
|
676
|
-
|
689
|
+
xml.public_send('destination') do
|
690
|
+
xml.public_send('international') do
|
691
|
+
xml.public_send('country-code', destination.country_code)
|
677
692
|
end
|
678
693
|
end
|
679
694
|
end
|
680
695
|
end
|
681
696
|
|
682
|
-
def services_node(services)
|
683
|
-
|
684
|
-
services.each { |code|
|
697
|
+
def services_node(xml, services)
|
698
|
+
xml.public_send('services') do
|
699
|
+
services.each { |code| xml.public_send('service-code', code) }
|
685
700
|
end
|
686
701
|
end
|
687
702
|
|
688
|
-
def shipping_options_node(available_options, options = {})
|
703
|
+
def shipping_options_node(xml, available_options, options = {})
|
689
704
|
return if (options.symbolize_keys.keys & available_options).empty?
|
690
|
-
|
705
|
+
xml.public_send('options') do
|
691
706
|
|
692
707
|
if options[:cod] && options[:cod_amount]
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
708
|
+
xml.public_send('option') do
|
709
|
+
xml.public_send('option-code', 'COD')
|
710
|
+
xml.public_send('option-amount', options[:cod_amount])
|
711
|
+
xml.public_send('option-qualifier-1', options[:cod_includes_shipping]) unless options[:cod_includes_shipping].blank?
|
712
|
+
xml.public_send('option-qualifier-2', options[:cod_method_of_payment]) unless options[:cod_method_of_payment].blank?
|
698
713
|
end
|
699
714
|
end
|
700
715
|
|
701
716
|
if options[:cov]
|
702
|
-
|
703
|
-
|
704
|
-
|
717
|
+
xml.public_send('option') do
|
718
|
+
xml.public_send('option-code', 'COV')
|
719
|
+
xml.public_send('option-amount', options[:cov_amount]) unless options[:cov_amount].blank?
|
705
720
|
end
|
706
721
|
end
|
707
722
|
|
708
723
|
if options[:d2po]
|
709
|
-
|
710
|
-
|
711
|
-
|
724
|
+
xml.public_send('option') do
|
725
|
+
xml.public_send('option-code', 'D2PO')
|
726
|
+
xml.public_send('option-qualifier-2'. options[:d2po_office_id]) unless options[:d2po_office_id].blank?
|
712
727
|
end
|
713
728
|
end
|
714
729
|
|
715
730
|
[:so, :dc, :pa18, :pa19, :hfp, :dns, :lad, :rase, :rts, :aban].each do |code|
|
716
731
|
if options[code]
|
717
|
-
|
718
|
-
|
732
|
+
xml.public_send('option') do
|
733
|
+
xml.public_send('option-code', code.to_s.upcase)
|
719
734
|
end
|
720
735
|
end
|
721
736
|
end
|
@@ -723,8 +738,8 @@ module ActiveShipping
|
|
723
738
|
end
|
724
739
|
|
725
740
|
def expected_date_from_node(node)
|
726
|
-
if service = node.
|
727
|
-
expected_date = service.
|
741
|
+
if service = node.at('service-standard/expected-delivery-date')
|
742
|
+
expected_date = service.text
|
728
743
|
else
|
729
744
|
expected_date = nil
|
730
745
|
end
|
@@ -756,22 +771,22 @@ module ActiveShipping
|
|
756
771
|
value == 0 ? 0.01 : value.round / 100.0
|
757
772
|
end
|
758
773
|
|
759
|
-
def origin_hash_for(
|
760
|
-
occurrences =
|
774
|
+
def origin_hash_for(root)
|
775
|
+
occurrences = root.xpath('significant-events/occurrence')
|
761
776
|
earliest = occurrences.sort_by { |occurrence| time_of_occurrence(occurrence) }.first
|
762
777
|
|
763
778
|
{
|
764
|
-
city: earliest.
|
765
|
-
province: earliest.
|
766
|
-
address_1: earliest.
|
779
|
+
city: earliest.at('event-site').text,
|
780
|
+
province: earliest.at('event-province').text,
|
781
|
+
address_1: earliest.at('event-retail-location-id').text,
|
767
782
|
country: 'Canada'
|
768
783
|
}
|
769
784
|
end
|
770
785
|
|
771
786
|
def time_of_occurrence(occurrence)
|
772
|
-
time = occurrence.
|
773
|
-
date = occurrence.
|
774
|
-
time_zone = occurrence.
|
787
|
+
time = occurrence.at('event-time').text
|
788
|
+
date = occurrence.at('event-date').text
|
789
|
+
time_zone = occurrence.at('event-time-zone').text
|
775
790
|
DateTime.parse "#{date} #{time} #{time_zone}"
|
776
791
|
end
|
777
792
|
end
|