trackerific 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Gemfile.lock +43 -1
- data/README.rdoc +58 -60
- data/lib/trackerific.rb +17 -20
- data/lib/trackerific/builders/base/soap.rb +21 -0
- data/lib/trackerific/builders/base/xml.rb +38 -0
- data/lib/trackerific/builders/fedex.rb +46 -0
- data/lib/trackerific/builders/ups.rb +32 -0
- data/lib/trackerific/builders/usps.rb +19 -0
- data/lib/trackerific/environment.rb +11 -0
- data/lib/trackerific/error.rb +3 -1
- data/lib/trackerific/parsers/base.rb +35 -0
- data/lib/trackerific/parsers/fedex.rb +59 -0
- data/lib/trackerific/parsers/ups.rb +66 -0
- data/lib/trackerific/parsers/usps.rb +51 -0
- data/lib/trackerific/services/base.rb +23 -10
- data/lib/trackerific/services/concerns/soap.rb +45 -0
- data/lib/trackerific/services/concerns/xml.rb +44 -0
- data/lib/trackerific/services/fedex.rb +9 -79
- data/lib/trackerific/services/mock_service.rb +4 -12
- data/lib/trackerific/services/ups.rb +11 -95
- data/lib/trackerific/services/usps.rb +21 -181
- data/lib/trackerific/soap/wsdl.rb +17 -0
- data/lib/trackerific/version.rb +1 -1
- data/spec/fixtures/fedex/error.xml +1 -10
- data/spec/fixtures/fedex/success.xml +1 -74
- data/spec/fixtures/ups/malformed.xml +10 -0
- data/spec/fixtures/ups/request.xml +1 -0
- data/spec/fixtures/usps/malformed.xml +2 -0
- data/spec/fixtures/usps/request.xml +1 -0
- data/spec/lib/trackerific/builders/base/soap_spec.rb +29 -0
- data/spec/lib/trackerific/builders/base/xml_spec.rb +35 -0
- data/spec/lib/trackerific/builders/fedex_spec.rb +70 -0
- data/spec/lib/trackerific/builders/ups_spec.rb +11 -0
- data/spec/lib/trackerific/builders/usps_spec.rb +11 -0
- data/spec/lib/trackerific/environment_spec.rb +45 -0
- data/spec/lib/trackerific/parsers/base_spec.rb +62 -0
- data/spec/lib/trackerific/parsers/ups_spec.rb +71 -0
- data/spec/lib/trackerific/parsers/usps_spec.rb +77 -0
- data/spec/lib/trackerific/services/base_spec.rb +56 -0
- data/spec/lib/trackerific/services/concerns/soap_spec.rb +71 -0
- data/spec/lib/trackerific/services/concerns/xml_spec.rb +65 -0
- data/spec/lib/trackerific/services/fedex_spec.rb +43 -45
- data/spec/lib/trackerific/services/ups_spec.rb +23 -2
- data/spec/lib/trackerific/services/usps_spec.rb +44 -34
- data/spec/lib/trackerific/services_spec.rb +7 -0
- data/spec/lib/trackerific/soap/wsdl_spec.rb +29 -0
- data/spec/lib/trackerific/version_spec.rb +1 -1
- data/spec/lib/trackerific_spec.rb +2 -11
- data/spec/spec_helper.rb +17 -1
- data/spec/support/reload.rb +6 -0
- data/spec/support/trackerific.rb +7 -0
- data/trackerific.gemspec +2 -0
- data/vendor/wsdl/fedex/TrackService_v8.wsdl +2284 -0
- metadata +81 -7
- data/lib/trackerific/configuration.rb +0 -7
- data/spec/fixtures/usps/city_state_lookup.xml +0 -8
- data/spec/lib/trackerific/configuration_spec.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1310f1dbf143518a58178390a664b23b697cadba
|
4
|
+
data.tar.gz: 709ab9cdaa8df43975eff61b797b98586fab4e34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b398a2bfaab2602d8cb0624d48f7936750abb13bdfb36828a27cd4ec1f4b0a5a0763996de30edf2aa38a4270fecb31e5158a77a2d242542a3c3be6d1094b0158
|
7
|
+
data.tar.gz: dfe45ddfe2c76098a2e9b6c917f6b825fb91d993f92236f9b5b28595f539b48b13d758bae1dbe1fd6ede6928ea202b95638d010ac0cf6e5b06ef81e930781ded
|
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
trackerific (0.7.
|
4
|
+
trackerific (0.7.2)
|
5
5
|
activesupport
|
6
6
|
builder (>= 3.1.4)
|
7
7
|
httparty (>= 0.12.0)
|
8
|
+
savon (~> 2.3.0)
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: https://rubygems.org/
|
@@ -15,19 +16,39 @@ GEM
|
|
15
16
|
multi_json (~> 1.3)
|
16
17
|
thread_safe (~> 0.1)
|
17
18
|
tzinfo (~> 0.3.37)
|
19
|
+
akami (1.2.0)
|
20
|
+
gyoku (>= 0.4.0)
|
21
|
+
nokogiri (>= 1.4.0)
|
18
22
|
atomic (1.1.14)
|
19
23
|
builder (3.2.2)
|
24
|
+
coveralls (0.7.0)
|
25
|
+
multi_json (~> 1.3)
|
26
|
+
rest-client
|
27
|
+
simplecov (>= 0.7)
|
28
|
+
term-ansicolor
|
29
|
+
thor
|
20
30
|
diff-lcs (1.1.3)
|
21
31
|
fakeweb (1.3.0)
|
32
|
+
gyoku (1.1.0)
|
33
|
+
builder (>= 2.1.2)
|
22
34
|
httparty (0.12.0)
|
23
35
|
json (~> 1.8)
|
24
36
|
multi_xml (>= 0.5.2)
|
37
|
+
httpi (2.1.0)
|
38
|
+
rack
|
39
|
+
rubyntlm (~> 0.3.2)
|
25
40
|
i18n (0.6.5)
|
26
41
|
json (1.8.0)
|
42
|
+
mime-types (1.25)
|
27
43
|
minitest (4.7.5)
|
28
44
|
multi_json (1.8.2)
|
29
45
|
multi_xml (0.5.5)
|
46
|
+
nokogiri (1.5.10)
|
47
|
+
nori (2.3.0)
|
48
|
+
rack (1.5.2)
|
30
49
|
rake (10.1.0)
|
50
|
+
rest-client (1.6.7)
|
51
|
+
mime-types (>= 1.16)
|
31
52
|
rspec (2.6.0)
|
32
53
|
rspec-core (~> 2.6.0)
|
33
54
|
rspec-expectations (~> 2.6.0)
|
@@ -36,15 +57,36 @@ GEM
|
|
36
57
|
rspec-expectations (2.6.0)
|
37
58
|
diff-lcs (~> 1.1.2)
|
38
59
|
rspec-mocks (2.6.0)
|
60
|
+
rubyntlm (0.3.4)
|
61
|
+
savon (2.3.0)
|
62
|
+
akami (~> 1.2.0)
|
63
|
+
builder (>= 2.1.2)
|
64
|
+
gyoku (~> 1.1.0)
|
65
|
+
httpi (~> 2.1.0)
|
66
|
+
nokogiri (>= 1.4.0, < 1.6)
|
67
|
+
nori (~> 2.3.0)
|
68
|
+
wasabi (~> 3.2.0)
|
69
|
+
simplecov (0.7.1)
|
70
|
+
multi_json (~> 1.0)
|
71
|
+
simplecov-html (~> 0.7.1)
|
72
|
+
simplecov-html (0.7.1)
|
73
|
+
term-ansicolor (1.2.2)
|
74
|
+
tins (~> 0.8)
|
75
|
+
thor (0.18.1)
|
39
76
|
thread_safe (0.1.3)
|
40
77
|
atomic
|
78
|
+
tins (0.12.0)
|
41
79
|
tzinfo (0.3.38)
|
80
|
+
wasabi (3.2.0)
|
81
|
+
httpi (~> 2.0)
|
82
|
+
nokogiri (>= 1.4.0, < 1.6)
|
42
83
|
|
43
84
|
PLATFORMS
|
44
85
|
ruby
|
45
86
|
|
46
87
|
DEPENDENCIES
|
47
88
|
bundler (>= 1.3.5)
|
89
|
+
coveralls (~> 0.7.0)
|
48
90
|
fakeweb (~> 1.3.0)
|
49
91
|
rake
|
50
92
|
rspec (~> 2.6.0)
|
data/README.rdoc
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
= Trackerific {<img src="https://badge.fury.io/rb/trackerific.png" alt="Gem Version" />}[http://badge.fury.io/rb/trackerific] {<img src="https://travis-ci.org/travishaynes/trackerific.png" />}[https://travis-ci.org/travishaynes/trackerific] {<img src="https://coveralls.io/repos/travishaynes/trackerific/badge.png" alt="Coverage Status" />}[https://coveralls.io/r/travishaynes/trackerific]
|
2
|
+
|
3
|
+
Package tracking in a gem. Currently supported carriers are:
|
4
|
+
|
5
|
+
* FedEx
|
6
|
+
* UPS
|
7
|
+
* USPS
|
8
|
+
|
9
|
+
== Prerequisites
|
10
|
+
|
11
|
+
You will need to sign up to each carrier you wish to utilize. They each have
|
12
|
+
their own requirements and license agreements, so it's up to you to make sure
|
13
|
+
you follow their terms of agreement. For example, at the time this was written
|
14
|
+
UPS requires that you include their logo on your website when you use their
|
15
|
+
API.
|
16
|
+
|
17
|
+
You'll also need the proper credentials from each carrier, which requires
|
18
|
+
signing up through their individual websites, and is not always free, depending
|
19
|
+
on which portion of their API you plan on using. Trackerific focuses primarily
|
20
|
+
on the tracking APIs, so that's all you'll need to use this gem, unless you
|
21
|
+
plan on implementing additional features.
|
22
|
+
|
1
23
|
== Installation
|
2
24
|
|
3
25
|
To use this gem, add this line to your Gemfile
|
@@ -7,24 +29,15 @@ and then run
|
|
7
29
|
|
8
30
|
=== Configuration
|
9
31
|
|
10
|
-
You will need to configure
|
32
|
+
You will need to configure the credentials for each service you're utilizing.
|
33
|
+
Services without credentials will not be accessed by the gem.
|
11
34
|
|
12
35
|
Trackerific.configure do |config|
|
13
|
-
config.fedex :
|
14
|
-
|
15
|
-
|
16
|
-
config.ups :key => 'key',
|
17
|
-
:user_id => 'userid',
|
18
|
-
:password => 'secret'
|
19
|
-
|
20
|
-
config.usps :user_id => 'userid',
|
21
|
-
:use_city_state_lookup => true
|
36
|
+
config.fedex = { account: 'account', meter: '123456789' }
|
37
|
+
config.ups = { key: 'key', user_id: 'userid', password: 'secret' }
|
38
|
+
config.usps = { user_id: 'userid' }
|
22
39
|
end
|
23
40
|
|
24
|
-
For USPS packages, the option :use_city_state_lookup defaults to false, and will
|
25
|
-
only work if you have access to USPS's CityStateLookup API. If you can enable
|
26
|
-
it, this feature will provide the location for USPS package events.
|
27
|
-
|
28
41
|
=== Tracking with Automatic Service Discovery
|
29
42
|
|
30
43
|
Once you configured the services, tracking a package is as easy as
|
@@ -36,64 +49,47 @@ Once you configured the services, tracking a package is as easy as
|
|
36
49
|
If you do not know the tracking service provider of a package id you can use
|
37
50
|
Trackerific::Services.find_by_tracking_id:
|
38
51
|
|
39
|
-
Trackerific::Services.find_by_tracking_id("
|
40
|
-
Trackerific::Services.find_by_tracking_id("1Z12345E0291980793")
|
41
|
-
Trackerific::Services.find_by_tracking_id("EJ958083578US")
|
42
|
-
|
52
|
+
Trackerific::Services.find_by_tracking_id("123456789012")
|
53
|
+
Trackerific::Services.find_by_tracking_id("1Z12345E0291980793")
|
54
|
+
Trackerific::Services.find_by_tracking_id("EJ958083578US")
|
55
|
+
|
56
|
+
Each of the above examples will return an Array of
|
57
|
+
Trackerific::Service::Base subclasses that are capable of tracking the given
|
58
|
+
package ID.
|
43
59
|
|
44
60
|
=== Tracking a Package with a Specific Service
|
45
61
|
|
46
62
|
Use this method if you need to specify exactly which service to track a package.
|
47
63
|
|
48
|
-
|
49
|
-
details = Trackerific::Services::FedEx.track('183689015000001')
|
50
|
-
|
51
|
-
# Track a USPS package:
|
52
|
-
details = Trackerific::Services::USPS.track('EJ958083578US')
|
64
|
+
details = Trackerific::Services::FedEx.track('123456789012')
|
53
65
|
|
54
|
-
|
55
|
-
|
66
|
+
Using #track will automatically read the credentials from Trackerific.config.
|
67
|
+
If you need to assign them manually, use #new:
|
56
68
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
69
|
+
fedex = Trackerific::Services::FedEx.new(
|
70
|
+
:account => "account",
|
71
|
+
:meter => "123456789"
|
72
|
+
)
|
61
73
|
details = fedex.track('183689015000001')
|
62
74
|
|
63
75
|
=== Tracking Details
|
64
76
|
|
65
77
|
The tracking information is returned in a Trackerific::Details instance.
|
66
78
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
You can easily print out the tracking events just by doing:
|
71
|
-
|
72
|
-
puts details.events # for all the events
|
73
|
-
puts details.events.first # for just one event
|
74
|
-
|
75
|
-
Or, if you need specific information about an event:
|
76
|
-
|
77
|
-
details.events.last.date # => the date the package was shipped
|
78
|
-
details.events.first.date # => the last date the package was updated
|
79
|
-
details.events.first.description # => a description of an event
|
80
|
-
details.events.first.location # => the location of the package during that event
|
81
|
-
|
82
|
-
location will not work for USPS packages, because USPS does not provide that
|
83
|
-
information seperately from the description. So for USPS packages, the location
|
84
|
-
will always be at the end of the description.
|
79
|
+
details.summary - Summary of the tracking events
|
80
|
+
details.events - Array of Trackerific::Events
|
85
81
|
|
86
|
-
|
87
|
-
supplied. This is because the events are listed in LIFO order, so the most
|
88
|
-
recent events will always be at the top of the list.
|
82
|
+
=== Tracking Events
|
89
83
|
|
90
|
-
|
84
|
+
Tracking::Details has an events Array with the following properties:
|
91
85
|
|
92
|
-
|
93
|
-
|
86
|
+
event.date - The date the package was shipped.
|
87
|
+
event.date - The date/time of the event.
|
88
|
+
event.description - Description of an event.
|
89
|
+
event.location - The location of the package during that event.
|
94
90
|
|
95
|
-
|
96
|
-
|
91
|
+
Note that events are in last-in-first-out order, so the last event will always
|
92
|
+
be the first event the carrier supplied.
|
97
93
|
|
98
94
|
=== Exception handling
|
99
95
|
|
@@ -115,13 +111,15 @@ Trackerific provides a mocked service you can use in your unit tests of your
|
|
115
111
|
application. You must require the service manually.
|
116
112
|
|
117
113
|
require 'trackerific/services/mock_service'
|
118
|
-
|
119
|
-
details =
|
114
|
+
# Get a populated Trackerific::Details
|
115
|
+
details = Trackerific.track("XXXXXXXXXX")
|
116
|
+
# Throw a Trackerific::Error exception
|
117
|
+
details = Trackerific.track("XXXxxxxxxx")
|
120
118
|
|
121
119
|
== Contributing
|
122
120
|
|
123
121
|
1. Fork it
|
124
|
-
2. Create your feature branch (
|
125
|
-
3. Commit your changes (
|
126
|
-
4. Push to the branch (
|
122
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
123
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
124
|
+
4. Push to the branch (git push origin my-new-feature)
|
127
125
|
5. Create new Pull Request
|
data/lib/trackerific.rb
CHANGED
@@ -1,8 +1,21 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/object/to_query'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'date'
|
5
|
+
require 'savon'
|
6
|
+
require 'httparty'
|
7
|
+
require 'builder'
|
1
8
|
require 'trackerific/version'
|
2
|
-
require 'trackerific/
|
9
|
+
require 'trackerific/environment'
|
3
10
|
require 'trackerific/error'
|
4
11
|
require 'trackerific/details'
|
5
12
|
require 'trackerific/event'
|
13
|
+
require 'trackerific/soap/wsdl'
|
14
|
+
require 'trackerific/builders/base/soap'
|
15
|
+
require 'trackerific/builders/base/xml'
|
16
|
+
require 'trackerific/parsers/base'
|
17
|
+
require 'trackerific/services/concerns/soap'
|
18
|
+
require 'trackerific/services/concerns/xml'
|
6
19
|
require 'trackerific/services'
|
7
20
|
require 'trackerific/services/base'
|
8
21
|
|
@@ -12,32 +25,16 @@ require 'trackerific/services/ups'
|
|
12
25
|
require 'trackerific/services/usps'
|
13
26
|
|
14
27
|
module Trackerific
|
15
|
-
|
16
|
-
# Used to access the Trackerific service credentials
|
17
|
-
# @api public
|
18
|
-
def configuration
|
19
|
-
Trackerific::Configuration.config
|
20
|
-
end
|
21
|
-
|
22
|
-
# Use to configure Trackerific service credentials
|
23
|
-
# @example Configure FedEx credentials
|
24
|
-
# Trackerific.configure do |config|
|
25
|
-
# config.fedex account: 'account', meter: '123456789'
|
26
|
-
# end
|
27
|
-
# @api public
|
28
|
-
def configure(&block)
|
29
|
-
Trackerific::Configuration.configure {|config| yield(config) }
|
30
|
-
end
|
28
|
+
include ActiveSupport::Configurable
|
31
29
|
|
30
|
+
class << self
|
32
31
|
# Looks up which service(s) can track the given ID and tracks it.
|
33
32
|
# @param [String] id The package identifier
|
34
33
|
# @return [Array, Trackerific::Details] The tracking results
|
35
34
|
# @raise [Trackerific::Error] Raised when the server returns an error
|
36
35
|
# @api public
|
37
36
|
def track(id)
|
38
|
-
Trackerific::Services.find_by_package_id(id).map
|
39
|
-
service.new.track(id)
|
40
|
-
end
|
37
|
+
Trackerific::Services.find_by_package_id(id).map {|s| s.track(id) }
|
41
38
|
end
|
42
39
|
end
|
43
40
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Trackerific
|
2
|
+
module Builders
|
3
|
+
module Base
|
4
|
+
class SOAP < Struct
|
5
|
+
attr_reader :hash
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super(*args)
|
9
|
+
@hash = build
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def build
|
15
|
+
raise NotImplementedError,
|
16
|
+
"Implement this method in your builder subclass", caller
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Trackerific
|
2
|
+
module Builders
|
3
|
+
module Base
|
4
|
+
class XML < Struct
|
5
|
+
@xml_version = "1.0"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :xml_version
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :xml
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
super(*args)
|
15
|
+
@xml = ""
|
16
|
+
build
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def build
|
22
|
+
raise NotImplementedError,
|
23
|
+
"Implement this method in your builder subclass", caller
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def builder
|
29
|
+
@builder ||= begin
|
30
|
+
builder = Builder::XmlMarkup.new(target: @xml)
|
31
|
+
builder.instruct! :xml, version: self.class.xml_version
|
32
|
+
builder
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Trackerific
|
2
|
+
module Builders
|
3
|
+
class FedEx < Base::SOAP.new(:key, :password, :account_number, :meter_number, :package_id)
|
4
|
+
protected
|
5
|
+
|
6
|
+
def build
|
7
|
+
root_elements.inject({}) {|r, k| r[k] = send(k); r }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def root_elements
|
13
|
+
[ :web_authentication_detail, :client_detail, :transaction_detail,
|
14
|
+
:version, :selection_details, :processing_options ]
|
15
|
+
end
|
16
|
+
|
17
|
+
def web_authentication_detail
|
18
|
+
{ user_credential: { key: key, password: password } }
|
19
|
+
end
|
20
|
+
|
21
|
+
def client_detail
|
22
|
+
{ account_number: account_number, meter_number: meter_number }
|
23
|
+
end
|
24
|
+
|
25
|
+
def transaction_detail
|
26
|
+
{ customer_transaction_id: "Trackerific" }
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
{ service_id: 'trck', major: '8', intermediate: '0', minor: '0' }
|
31
|
+
end
|
32
|
+
|
33
|
+
def selection_details
|
34
|
+
{ carrier_code: 'FDXE', package_identifier: package_identifier }
|
35
|
+
end
|
36
|
+
|
37
|
+
def package_identifier
|
38
|
+
{ type: 'TRACKING_NUMBER_OR_DOORTAG', value: package_id }
|
39
|
+
end
|
40
|
+
|
41
|
+
def processing_options
|
42
|
+
'INCLUDE_DETAILED_SCANS'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Trackerific
|
2
|
+
module Builders
|
3
|
+
class UPS < Base::XML.new(:key, :user_id, :password, :package_id)
|
4
|
+
protected
|
5
|
+
|
6
|
+
def build
|
7
|
+
add_access_request
|
8
|
+
add_track_request
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def add_access_request
|
14
|
+
builder.AccessRequest do |ar|
|
15
|
+
ar.AccessLicenseNumber key
|
16
|
+
ar.UserId user_id
|
17
|
+
ar.Password password
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_track_request
|
22
|
+
builder.TrackRequest do |tr|
|
23
|
+
tr.Request do |r|
|
24
|
+
r.RequestAction 'Track'
|
25
|
+
r.RequestOption 'activity'
|
26
|
+
end
|
27
|
+
tr.TrackingNumber package_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|