trackerific 0.7.1 → 0.7.2
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
- 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
|