tracking_number 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +0 -26
- data/lib/data/couriers/dhl.json +1 -74
- data/lib/data/couriers/fedex.json +24 -58
- data/lib/data/couriers/s10.json +1 -0
- data/lib/data/couriers/usps.json +124 -136
- data/lib/data/couriers/yodel.json +29 -0
- data/lib/tracking_number/version.rb +1 -1
- data/lib/tracking_number.rb +25 -0
- data/test/partnership_test.rb +71 -0
- data/test/renamed_types_test.rb +44 -0
- data/test/test_helper.rb +4 -3
- data/test/tracking_number_meta_test.rb +1 -1
- data/test/tracking_number_test.rb +15 -18
- data/tracking_number.gemspec +1 -0
- metadata +19 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71f01e80c118f12c210f3c9a5195edf0c9d257c37ff4ce36529962f4131a57ed
|
|
4
|
+
data.tar.gz: 79519ddef14f7728eb6de46437360a9040368e261ce3dba4cdadf9a9b382891e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86ef4bdd51d50a76f26b0bc53f17db5518c48973ecc715b90058dc446c1a7ac2558db35a75f9a572837e4aacbf9e6fa33b2c5870dfc21076a50a8c538e156313
|
|
7
|
+
data.tar.gz: f78a9a8b106ce172f87f231c9d59aa6c52a1f76b8a6b471aaa5a17ac92ee605d71cab5d216b3032d3eba8fd0244a406f6358f6be465e1332c1edce32d07c9ad8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
tracking_number changelog
|
|
2
2
|
|
|
3
|
+
# [3.0.0](https://github.com/jkeen/tracking_number/compare/v2.5.0...v3.0.0) (2026-08-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* feat!: Update to tracking_number_data 2.0, which rebuilds the IMpb USPS definitions from Publication 199 and absorbs the DHL and Fedex definitions that described the same tracking number. ([448e953](https://github.com/jkeen/tracking_number/commit/448e9534b0be8d2f205de5712e3215281922de99))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
* `fedex_smartpost`, `dhl_ecommerce_30` and `usps_32V2` are absorbed into `usps_impb_c`, which is the definition they were always describing. `usps_91` and `usps_22` become `usps_impb_c` and `usps_impb_n`, and Application Identifier 91 splits off to `usps_legacy`. Every number these matched still decodes, now as USPS.
|
|
12
|
+
|
|
13
|
+
No active definitions declare `partners` anymore, but it remains valid in our data spec for potential future use.
|
|
14
|
+
|
|
3
15
|
# [2.5.0](https://github.com/jkeen/tracking_number/compare/v2.4.0...v2.5.0) (2026-08-13)
|
|
4
16
|
|
|
5
17
|
|
data/README.md
CHANGED
|
@@ -128,32 +128,6 @@ Most tracking numbers have a format where each part of the number has meaning. `
|
|
|
128
128
|
# }
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
#### Multiple shippers / Partnerships
|
|
132
|
-
Some tracking numbers match multiple carriers, because they belong to multiple carriers. Some shipments like Fedex Smartpost contract the "last mile" out to USPS.
|
|
133
|
-
|
|
134
|
-
```ruby
|
|
135
|
-
# Search defaults to only showing numbers that fulfill the carrier side of the relationship
|
|
136
|
-
# (if a partnership exists at all), as this is the end a consumer would most likely be interested in.
|
|
137
|
-
|
|
138
|
-
results = TrackingNumber.search('420112139261290983497923666238')
|
|
139
|
-
=> [#<TrackingNumber::USPS91:0x26ac0 420112139261290983497923666238>]
|
|
140
|
-
|
|
141
|
-
all_results = TrackingNumber.search('420112139261290983497923666238', match: :all)
|
|
142
|
-
=> [#<TrackingNumber::FedExSmartPost:0x30624 420112139261290983497923666238>, #<TrackingNumber::USPS91:0x26ac0 420112139261290983497923666238>]
|
|
143
|
-
|
|
144
|
-
tn = results.first
|
|
145
|
-
tn.shipper? #=> false
|
|
146
|
-
tn.carrier? #=> true
|
|
147
|
-
tn.partnership? #=> true
|
|
148
|
-
tn.partners
|
|
149
|
-
#=> <struct TrackingNumber::Base::PartnerStruct
|
|
150
|
-
# shipper=#<TrackingNumber::FedExSmartPost:0x30624 420112139261290983497923666238>,
|
|
151
|
-
# carrier=#<TrackingNumber::USPS91:0x2f1fc 420112139261290983497923666238>>
|
|
152
|
-
|
|
153
|
-
tn.partners.shipper #=> #<TrackingNumber::FedExSmartPost:0x30624 420112139261290983497923666238>
|
|
154
|
-
tn.partners.carrier == tn #=> true
|
|
155
|
-
```
|
|
156
|
-
|
|
157
131
|
## ActiveModel validation
|
|
158
132
|
|
|
159
133
|
For Rails 3 (or any ActiveModel client), validate your fields as a tracking number:
|
data/lib/data/couriers/dhl.json
CHANGED
|
@@ -90,81 +90,8 @@
|
|
|
90
90
|
"GR295117494011169045"
|
|
91
91
|
]
|
|
92
92
|
}
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"name": "DHL E-Commerce (30)",
|
|
96
|
-
"id": "dhl_ecommerce_30",
|
|
97
|
-
"regex": [
|
|
98
|
-
"\\s*(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})",
|
|
99
|
-
"(?!\\s*9\\s*[1-5]\\b)",
|
|
100
|
-
"(?<SerialNumber>",
|
|
101
|
-
"(?<ServiceType>(9[2-5]\\s*))",
|
|
102
|
-
"(?<ShipperId>([0-9]\\s*){9})",
|
|
103
|
-
"(?<PackageId>([0-9]\\s*){10})",
|
|
104
|
-
")",
|
|
105
|
-
"(?<CheckDigit>[0-9]\\s*)"
|
|
106
|
-
],
|
|
107
|
-
"validation": {
|
|
108
|
-
"checksum": {
|
|
109
|
-
"name": "mod10",
|
|
110
|
-
"modulo": 10,
|
|
111
|
-
"evens_multiplier": 3,
|
|
112
|
-
"odds_multiplier": 1,
|
|
113
|
-
"reverse": true
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
"tracking_url": "https://ecommerceportal.dhl.com/track?trackingnumber=%s",
|
|
117
|
-
"partners": [{
|
|
118
|
-
"description": "DHL eCommerce hands off to USPS for the last mile",
|
|
119
|
-
"partner_type": "carrier",
|
|
120
|
-
"partner_id": "usps_22",
|
|
121
|
-
"validation": {
|
|
122
|
-
"matches_all": [
|
|
123
|
-
{ "regex_group_name": "RoutingApplicationId", "matches": "420" },
|
|
124
|
-
{ "regex_group_name": "ServiceType", "matches_regex": "9[2345]" }
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
"description": "DHL eCommerce hands off to USPS for the last mile",
|
|
130
|
-
"partner_type": "carrier",
|
|
131
|
-
"partner_id": "usps_91",
|
|
132
|
-
"validation": {
|
|
133
|
-
"matches_all": [
|
|
134
|
-
{ "regex_group_name": "RoutingApplicationId", "matches": "420" },
|
|
135
|
-
{ "regex_group_name": "ServiceType", "matches_regex": "9[2345]" }
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
}],
|
|
139
|
-
"additional": [
|
|
140
|
-
{
|
|
141
|
-
"name": "Service Type",
|
|
142
|
-
"regex_group_name": "ServiceType",
|
|
143
|
-
"lookup": [
|
|
144
|
-
{ "matches": "92", "name": "USPS Tracking (Commercial)" },
|
|
145
|
-
{ "matches": "93", "name": "Collect on Delivery (COD)" },
|
|
146
|
-
{ "matches": "94", "name": "USPS Tracking" },
|
|
147
|
-
{ "matches": "95", "name": "Certified Mail" }
|
|
148
|
-
]
|
|
149
|
-
}
|
|
150
|
-
],
|
|
151
|
-
"test_numbers": {
|
|
152
|
-
"valid": [
|
|
153
|
-
"420902459261290336128704042634",
|
|
154
|
-
"420941179261290336128704062441",
|
|
155
|
-
"420926029261290336128704067248",
|
|
156
|
-
"420902729261290336128704124163",
|
|
157
|
-
"420950549261290336128704133837",
|
|
158
|
-
"420193809261290336128704280838",
|
|
159
|
-
"420900469261290336128704216936"
|
|
160
|
-
],
|
|
161
|
-
"invalid": [
|
|
162
|
-
"420941179261290336128704062442",
|
|
163
|
-
"420941179261290336128704062400",
|
|
164
|
-
"420941179261290336128704062422"
|
|
165
|
-
]
|
|
166
|
-
}
|
|
167
93
|
}
|
|
168
94
|
|
|
95
|
+
|
|
169
96
|
]
|
|
170
97
|
}
|
|
@@ -92,71 +92,37 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
|
-
"name": "FedEx
|
|
96
|
-
"id": "
|
|
97
|
-
"description": "
|
|
98
|
-
"glossary": {
|
|
99
|
-
"ApplicationIdentifier": { "description": "Always 92 here, the code for a parcel handed to USPS for delivery." }
|
|
100
|
-
},
|
|
95
|
+
"name": "FedEx ASTRA (32)",
|
|
96
|
+
"id": "fedex_astra_32",
|
|
97
|
+
"description": "The legacy ASTRA barcode. Positions 17 through 28 hold a FedEx Express tracking number, and its check digit is the only one the barcode carries. What the other fields mean is not yet known.",
|
|
101
98
|
"regex": [
|
|
102
|
-
"\\s*
|
|
103
|
-
"(?:
|
|
104
|
-
"(
|
|
105
|
-
")
|
|
106
|
-
"(?<SerialNumber>",
|
|
107
|
-
"(?<
|
|
108
|
-
"(
|
|
109
|
-
"(?<ShipperId>([0-9]\\s*){8})",
|
|
110
|
-
"(?<PackageId>([0-9]\\s*){11}|([0-9]\\s*){7})",
|
|
111
|
-
")",
|
|
112
|
-
"(?<CheckDigit>([0-9]\\s*))"
|
|
113
|
-
],
|
|
114
|
-
"additional": [
|
|
115
|
-
{
|
|
116
|
-
"name": "Service Type",
|
|
117
|
-
"regex_group_name": "ServiceType",
|
|
118
|
-
"lookup": [
|
|
119
|
-
{
|
|
120
|
-
"matches_regex": ".",
|
|
121
|
-
"name": "Delivered by USPS"
|
|
122
|
-
}
|
|
123
|
-
]
|
|
124
|
-
}
|
|
99
|
+
"\\s*3\\s*",
|
|
100
|
+
"(?:[0-9]\\s*){5}",
|
|
101
|
+
"(?:[0-9]\\s*){3}",
|
|
102
|
+
"(?:[0-9]\\s*){7}",
|
|
103
|
+
"(?<SerialNumber>(?:[0-9]\\s*){11})",
|
|
104
|
+
"(?<CheckDigit>[0-9]\\s*)",
|
|
105
|
+
"(?:[0-9]\\s*){4}"
|
|
125
106
|
],
|
|
126
|
-
"partners": [{
|
|
127
|
-
"partner_id": "usps_91",
|
|
128
|
-
"partner_type": "carrier",
|
|
129
|
-
"description": "FedEx SmartPost is a shipping service that utilizes FedEx for the initial transport and the United States Postal Service for final delivery."
|
|
130
|
-
}],
|
|
131
107
|
"validation": {
|
|
132
108
|
"checksum": {
|
|
133
|
-
"name": "
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"reverse": false
|
|
138
|
-
},
|
|
139
|
-
"serial_number_format": {
|
|
140
|
-
"prepend_if": {
|
|
141
|
-
"matches_regex": "^(?!92).+",
|
|
142
|
-
"content": "92"
|
|
143
|
-
}
|
|
109
|
+
"name": "sum_product_with_weightings_and_modulo",
|
|
110
|
+
"weightings": [3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1],
|
|
111
|
+
"modulo1": 11,
|
|
112
|
+
"modulo2": 10
|
|
144
113
|
}
|
|
145
114
|
},
|
|
146
|
-
"tracking_url": "https://www.fedex.com/apps/fedextrack/?tracknumbers=%s",
|
|
147
115
|
"test_numbers": {
|
|
148
116
|
"valid": [
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
117
|
+
"32971514560102447849175802862014",
|
|
118
|
+
"32971510360102447848540980802018",
|
|
119
|
+
"32971508360102447847941133172013",
|
|
120
|
+
"33810799560000004380417923803212",
|
|
121
|
+
"3 29715 145 6010244 784917580286 2014"
|
|
152
122
|
],
|
|
153
123
|
"invalid": [
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"420 11213 61290983497923666231",
|
|
157
|
-
"92 6129098349792366623 5",
|
|
158
|
-
"9400 1112 0108 0805 4830 16",
|
|
159
|
-
"9361 2898 7870 0317 6337 95"
|
|
124
|
+
"32971514560102447849175802852014",
|
|
125
|
+
"3297151456010244784917580286201"
|
|
160
126
|
]
|
|
161
127
|
}
|
|
162
128
|
},
|
|
@@ -238,8 +204,7 @@
|
|
|
238
204
|
"name": "FedEx Ground 96 (22)",
|
|
239
205
|
"id": "fedex_ground_96",
|
|
240
206
|
"glossary": {
|
|
241
|
-
"ApplicationIdentifier": { "description": "Always 96, marking a ground shipping label." }
|
|
242
|
-
"SCNC": { "description": "FedEx Ground uses 11, 12 and 13." }
|
|
207
|
+
"ApplicationIdentifier": { "description": "Always 96, marking a ground shipping label." }
|
|
243
208
|
},
|
|
244
209
|
"regex": [
|
|
245
210
|
"\\s*(?<ApplicationIdentifier>9\\s*6\\s*)",
|
|
@@ -271,9 +236,10 @@
|
|
|
271
236
|
{
|
|
272
237
|
"name": "FedEx Ground GSN",
|
|
273
238
|
"id": "fedex_ground_gsn",
|
|
239
|
+
"description": "Ground's 34 digit barcode, carrying the Ground shipper number and the tracking number in the last twelve digits",
|
|
274
240
|
"glossary": {
|
|
275
241
|
"ApplicationIdentifier": { "description": "Always 96, marking a ground shipping label." },
|
|
276
|
-
"SCNC": { "description": "
|
|
242
|
+
"SCNC": { "description": "22, or 32 where the shipper number is nine digits." }
|
|
277
243
|
},
|
|
278
244
|
"regex": [
|
|
279
245
|
"\\s*(?<ApplicationIdentifier>9\\s*6\\s*)",
|
data/lib/data/couriers/s10.json
CHANGED
data/lib/data/couriers/usps.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{
|
|
6
6
|
"name": "USPS 20",
|
|
7
7
|
"id": "usps_20",
|
|
8
|
-
"description": "
|
|
8
|
+
"description": "Delivery and Signature Confirmation numbers, printed without the application identifier",
|
|
9
9
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
10
10
|
"regex": [
|
|
11
11
|
"\\s*(?<SerialNumber>",
|
|
@@ -39,37 +39,47 @@
|
|
|
39
39
|
"name": "Service Type",
|
|
40
40
|
"regex_group_name": "ServiceType",
|
|
41
41
|
"lookup": [
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
42
|
+
{ "matches": "01", "name": "Delivery Confirmation", "description": "Priority Mail or First-Class Mail Parcels" },
|
|
43
|
+
{ "matches": "02", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels" },
|
|
44
|
+
{ "matches_regex": "0[34]", "name": "Delivery Confirmation", "description": "Retail" },
|
|
45
|
+
{ "matches": "05", "name": "Delivery Confirmation", "description": "Priority Mail or First-Class Mail Parcels, insured over $50" },
|
|
46
|
+
{ "matches": "06", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured over $50" },
|
|
47
|
+
{ "matches": "07", "name": "Delivery Confirmation", "description": "Priority Mail, insured $50 or under" },
|
|
48
|
+
{ "matches": "08", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured $50 or under" },
|
|
49
|
+
{ "matches": "14", "name": "Delivery Confirmation", "description": "Software vendors only, by approval" },
|
|
50
|
+
{ "matches": "21", "name": "Signature Confirmation", "description": "Priority Mail or First-Class Mail Parcels" },
|
|
51
|
+
{ "matches": "22", "name": "Signature Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels" },
|
|
52
|
+
{ "matches_regex": "2[34]", "name": "Signature Confirmation", "description": "Retail" },
|
|
53
|
+
{ "matches": "25", "name": "Signature Confirmation", "description": "Priority Mail or First-Class Mail Parcels, insured, all values" },
|
|
54
|
+
{ "matches": "26", "name": "Signature Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured, all values" },
|
|
55
|
+
{ "matches": "34", "name": "Signature Confirmation", "description": "Software vendors only, by approval" },
|
|
56
|
+
{ "matches": "50", "name": "Electronic File Header", "description": "Used in the file header record only, never on a package" },
|
|
57
|
+
{ "matches": "56", "name": "Passive Enroute Scan", "description": "A free scan as the parcel passes through a sorting plant, with no confirmation service attached" },
|
|
58
|
+
{ "matches": "73", "name": "Insured Mail", "description": "Insured only, value over $50" },
|
|
59
|
+
{ "matches": "82", "name": "Merchandise Return", "description": "With Delivery Confirmation" },
|
|
60
|
+
{ "matches": "83", "name": "Merchandise Return", "description": "With Delivery Confirmation, insured value $50 or under" },
|
|
61
|
+
{ "matches": "85", "name": "Merchandise Return", "description": "With Delivery Confirmation, insured value over $50" }
|
|
58
62
|
]
|
|
59
63
|
}
|
|
60
64
|
]
|
|
61
65
|
},
|
|
62
66
|
{
|
|
63
|
-
"name": "USPS
|
|
64
|
-
"id": "
|
|
65
|
-
"description": "
|
|
67
|
+
"name": "USPS IMpb N",
|
|
68
|
+
"id": "usps_impb_n",
|
|
69
|
+
"description": "IMpb constructs N01 through N10, issued online or by a vendor or meter",
|
|
70
|
+
"glossary": {
|
|
71
|
+
"ShipperId": { "description": "The company that holds the postal permit the package was mailed under, which Publication 199 calls the Mailer ID. Nine digits when it starts with 9, six otherwise." }
|
|
72
|
+
},
|
|
66
73
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
67
74
|
"regex": [
|
|
68
|
-
"\\s*((?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5}))?",
|
|
75
|
+
"\\s*((?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5}(?=([0-9]\\s*){22}(?![0-9])|([0-9]\\s*){26}(?![0-9])))(?<DestinationZipPlus4>([0-9]\\s*){4}(?=([0-9]\\s*){22}(?![0-9])))?)?",
|
|
69
76
|
"(?<SerialNumber>",
|
|
70
|
-
"(?<
|
|
71
|
-
"(?<
|
|
72
|
-
"(
|
|
77
|
+
"(?<ApplicationIdentifier>9\\s*4\\s*)",
|
|
78
|
+
"(?<ServiceType>([0-9]\\s*){3})",
|
|
79
|
+
"(?=9\\s*([0-9]\\s*){8}(([0-9]\\s*){15}|([0-9]\\s*){11}|([0-9]\\s*){7})[0-9]\\s*(?![0-9])",
|
|
80
|
+
"|[0-8]\\s*([0-9]\\s*){5}(([0-9]\\s*){14}|([0-9]\\s*){10})[0-9]\\s*(?![0-9]))",
|
|
81
|
+
"(?<ShipperId>9\\s*([0-9]\\s*){8}|[0-8]\\s*([0-9]\\s*){5})",
|
|
82
|
+
"(?<PackageId>([0-9]\\s*){15}|([0-9]\\s*){14}|([0-9]\\s*){11}|([0-9]\\s*){10}|([0-9]\\s*){7})",
|
|
73
83
|
")",
|
|
74
84
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
75
85
|
],
|
|
@@ -86,6 +96,8 @@
|
|
|
86
96
|
"valid": [
|
|
87
97
|
"420787459400111206206406260787",
|
|
88
98
|
"9400111206206406260787",
|
|
99
|
+
"9400 1112 0108 0805 4830 16",
|
|
100
|
+
"9405803699300124287899",
|
|
89
101
|
"9434611206206406227577",
|
|
90
102
|
"9434611206206407667136",
|
|
91
103
|
"9400111206206407628746",
|
|
@@ -97,53 +109,30 @@
|
|
|
97
109
|
"2334611306206407667222"
|
|
98
110
|
]
|
|
99
111
|
},
|
|
100
|
-
"partners": [{
|
|
101
|
-
"description": "DHL ECommerce uses USPS for last mile delivery",
|
|
102
|
-
"partner_type": "shipper",
|
|
103
|
-
"partner_id": "dhl_ecommerce_30",
|
|
104
|
-
"validation": {
|
|
105
|
-
"matches_all": [
|
|
106
|
-
{
|
|
107
|
-
"regex_group_name": "RoutingApplicationId",
|
|
108
|
-
"matches": "420"
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"matches_regex": "9[24]",
|
|
112
|
-
"regex_group_name": "ServiceType"
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
}],
|
|
117
112
|
"additional": [
|
|
118
113
|
{
|
|
119
|
-
"name": "
|
|
120
|
-
"regex_group_name": "
|
|
114
|
+
"name": "Application Identifier",
|
|
115
|
+
"regex_group_name": "ApplicationIdentifier",
|
|
121
116
|
"lookup": [
|
|
122
|
-
{ "matches": "
|
|
123
|
-
{ "matches": "93", "name": "Collect on Delivery (COD)" },
|
|
124
|
-
{ "matches": "94", "name": "USPS Tracking" },
|
|
125
|
-
{ "matches": "95", "name": "Certified Mail" },
|
|
126
|
-
{ "matches": "96", "name": "Insured Mail" },
|
|
127
|
-
{ "matches": "97", "name": "Registered Mail" },
|
|
128
|
-
{ "matches": "98", "name": "Return Receipt for Merchandise" }
|
|
117
|
+
{ "matches": "94", "name": "Online or vendor label", "description": "Issued through USPS WebTools, or an authorized PC Postage provider" }
|
|
129
118
|
]
|
|
130
119
|
}
|
|
131
120
|
]
|
|
132
121
|
},
|
|
133
122
|
{
|
|
134
|
-
"name": "USPS
|
|
135
|
-
"id": "
|
|
136
|
-
"description": "
|
|
123
|
+
"name": "USPS Legacy",
|
|
124
|
+
"id": "usps_legacy",
|
|
125
|
+
"description": "Delivery and Signature Confirmation numbers, carrying the 91 application identifier",
|
|
137
126
|
"glossary": {
|
|
138
|
-
"ApplicationIdentifier": { "description": "
|
|
127
|
+
"ApplicationIdentifier": { "description": "Present only when the barcode is UCC/EAN Code 128." }
|
|
139
128
|
},
|
|
140
129
|
"regex": [
|
|
141
|
-
"\\s*(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})",
|
|
142
|
-
"(?<RoutingNumber>([0-9]\\s*){4})",
|
|
130
|
+
"\\s*(?:(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})(?<DestinationZipPlus4>([0-9]\\s*){4})?)?",
|
|
143
131
|
"(?<SerialNumber>",
|
|
144
|
-
"(?<ApplicationIdentifier>9\\s*
|
|
145
|
-
"(?<
|
|
146
|
-
"(?<
|
|
132
|
+
"(?<ApplicationIdentifier>9\\s*1\\s*)?",
|
|
133
|
+
"(?<ServiceType>([0-9]\\s*){2})",
|
|
134
|
+
"(?<ShipperId>([0-9]\\s*){9})",
|
|
135
|
+
"(?<PackageId>([0-9]\\s*){8})",
|
|
147
136
|
")",
|
|
148
137
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
149
138
|
],
|
|
@@ -154,35 +143,71 @@
|
|
|
154
143
|
"evens_multiplier": 3,
|
|
155
144
|
"odds_multiplier": 1,
|
|
156
145
|
"reverse": false
|
|
146
|
+
},
|
|
147
|
+
"serial_number_format": {
|
|
148
|
+
"prepend_if": {
|
|
149
|
+
"matches_regex": "^(?!9\\s*1).+",
|
|
150
|
+
"content": "91"
|
|
151
|
+
}
|
|
157
152
|
}
|
|
158
153
|
},
|
|
159
154
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
160
155
|
"test_numbers": {
|
|
161
156
|
"valid": [
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
157
|
+
"420 22153 9101026837331000039521",
|
|
158
|
+
"9101 1234 5678 9000 0000 13",
|
|
159
|
+
"7196 9010 7560 0307 7385"
|
|
165
160
|
],
|
|
166
161
|
"invalid": [
|
|
167
|
-
"
|
|
162
|
+
"9101 1234 5678 9000 0000 14",
|
|
163
|
+
"7196 9010 7560 0307 7386"
|
|
168
164
|
]
|
|
169
|
-
}
|
|
165
|
+
},
|
|
166
|
+
"additional": [
|
|
167
|
+
{
|
|
168
|
+
"name": "Service Type",
|
|
169
|
+
"regex_group_name": "ServiceType",
|
|
170
|
+
"lookup": [
|
|
171
|
+
{ "matches": "01", "name": "Delivery Confirmation", "description": "Priority Mail or First-Class Mail Parcels" },
|
|
172
|
+
{ "matches": "02", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels" },
|
|
173
|
+
{ "matches_regex": "0[34]", "name": "Delivery Confirmation", "description": "Retail" },
|
|
174
|
+
{ "matches": "05", "name": "Delivery Confirmation", "description": "Priority Mail or First-Class Mail Parcels, insured over $50" },
|
|
175
|
+
{ "matches": "06", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured over $50" },
|
|
176
|
+
{ "matches": "07", "name": "Delivery Confirmation", "description": "Priority Mail, insured $50 or under" },
|
|
177
|
+
{ "matches": "08", "name": "Delivery Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured $50 or under" },
|
|
178
|
+
{ "matches": "14", "name": "Delivery Confirmation", "description": "Software vendors only, by approval" },
|
|
179
|
+
{ "matches": "21", "name": "Signature Confirmation", "description": "Priority Mail or First-Class Mail Parcels" },
|
|
180
|
+
{ "matches": "22", "name": "Signature Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels" },
|
|
181
|
+
{ "matches_regex": "2[34]", "name": "Signature Confirmation", "description": "Retail" },
|
|
182
|
+
{ "matches": "25", "name": "Signature Confirmation", "description": "Priority Mail or First-Class Mail Parcels, insured, all values" },
|
|
183
|
+
{ "matches": "26", "name": "Signature Confirmation", "description": "Package Services, Parcel Select or Standard Mail Parcels, insured, all values" },
|
|
184
|
+
{ "matches": "34", "name": "Signature Confirmation", "description": "Software vendors only, by approval" },
|
|
185
|
+
{ "matches": "50", "name": "Electronic File Header", "description": "Used in the file header record only, never on a package" },
|
|
186
|
+
{ "matches": "56", "name": "Passive Enroute Scan", "description": "A free scan as the parcel passes through a sorting plant, with no confirmation service attached" },
|
|
187
|
+
{ "matches": "73", "name": "Insured Mail", "description": "Insured only, value over $50" },
|
|
188
|
+
{ "matches": "82", "name": "Merchandise Return", "description": "With Delivery Confirmation" },
|
|
189
|
+
{ "matches": "83", "name": "Merchandise Return", "description": "With Delivery Confirmation, insured value $50 or under" },
|
|
190
|
+
{ "matches": "85", "name": "Merchandise Return", "description": "With Delivery Confirmation, insured value over $50" }
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
170
194
|
},
|
|
171
195
|
{
|
|
172
|
-
"name": "USPS
|
|
173
|
-
"id": "
|
|
174
|
-
"description": "
|
|
196
|
+
"name": "USPS IMpb C",
|
|
197
|
+
"id": "usps_impb_c",
|
|
198
|
+
"description": "IMpb constructs C01 through C10, plus USPS retail",
|
|
175
199
|
"glossary": {
|
|
176
|
-
"
|
|
200
|
+
"ShipperId": { "description": "The company that holds the postal permit the package was mailed under, which Publication 199 calls the Mailer ID. Nine digits when it starts with 9, six otherwise." }
|
|
177
201
|
},
|
|
178
202
|
"regex": [
|
|
179
|
-
"\\s*(?:(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5}))?",
|
|
203
|
+
"\\s*(?:(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})(?<DestinationZipPlus4>([0-9]\\s*){4}(?=([0-9]\\s*){22}(?![0-9])))?)?",
|
|
180
204
|
"(?<SerialNumber>",
|
|
181
|
-
"(?<ApplicationIdentifier>9\\s*[
|
|
182
|
-
"(?<
|
|
183
|
-
"(
|
|
184
|
-
"(
|
|
185
|
-
"(?<
|
|
205
|
+
"(?<ApplicationIdentifier>9\\s*2\\s*(?=([0-9]\\s*){3}9)|9\\s*3\\s*(?=([0-9]\\s*){3}[0-8])|9\\s*5\\s*)",
|
|
206
|
+
"(?<ServiceType>([0-9]\\s*){3})",
|
|
207
|
+
"(?=9\\s*([0-9]\\s*){8}(([0-9]\\s*){11}|([0-9]\\s*){7})[0-9]\\s*(?![0-9])",
|
|
208
|
+
"|[0-8]\\s*([0-9]\\s*){5}(([0-9]\\s*){14}|([0-9]\\s*){10})[0-9]\\s*(?![0-9]))",
|
|
209
|
+
"(?<ShipperId>9\\s*([0-9]\\s*){8}|[0-8]\\s*([0-9]\\s*){5})",
|
|
210
|
+
"(?<PackageId>([0-9]\\s*){14}|([0-9]\\s*){11}|([0-9]\\s*){10}|([0-9]\\s*){7})",
|
|
186
211
|
")",
|
|
187
212
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
188
213
|
],
|
|
@@ -193,74 +218,35 @@
|
|
|
193
218
|
"evens_multiplier": 3,
|
|
194
219
|
"odds_multiplier": 1,
|
|
195
220
|
"reverse": false
|
|
196
|
-
},
|
|
197
|
-
"serial_number_format": {
|
|
198
|
-
"prepend_if": {
|
|
199
|
-
"matches_regex": "^(?!9[1-5]).+",
|
|
200
|
-
"content": "91"
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
"partners": [{
|
|
205
|
-
"description": "FedEx SmartPost uses USPS for last mile delivery, but not all USPS91 numbers are SmartPosts",
|
|
206
|
-
"partner_type": "shipper",
|
|
207
|
-
"partner_id": "fedex_smartpost",
|
|
208
|
-
"validation": {
|
|
209
|
-
"matches_all": [
|
|
210
|
-
{
|
|
211
|
-
"regex_group_name": "ServiceType",
|
|
212
|
-
"matches": "29"
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
"matches": "61",
|
|
216
|
-
"regex_group_name": "SCNC"
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
"regex_group_name": "ApplicationIdentifier",
|
|
220
|
-
"matches": "92"
|
|
221
|
-
}
|
|
222
|
-
]
|
|
223
221
|
}
|
|
224
222
|
},
|
|
225
|
-
{
|
|
226
|
-
"description": "DHL uses USPS for last mile delivery, but not all USPS91 numbers are DHL Ecommerce",
|
|
227
|
-
"partner_type": "shipper",
|
|
228
|
-
"partner_id": "dhl_ecommerce_30",
|
|
229
|
-
"validation": {
|
|
230
|
-
"matches_all": [
|
|
231
|
-
{
|
|
232
|
-
"regex_group_name": "ServiceType",
|
|
233
|
-
"matches": "29"
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"matches": "61",
|
|
237
|
-
"regex_group_name": "SCNC"
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
"matches": "420",
|
|
241
|
-
"regex_group_name": "RoutingApplicationId"
|
|
242
|
-
}
|
|
243
|
-
]
|
|
244
|
-
}
|
|
245
|
-
}],
|
|
246
223
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
247
224
|
"test_numbers": {
|
|
248
225
|
"valid": [
|
|
249
|
-
"
|
|
250
|
-
"
|
|
226
|
+
"4201002334249200190132607600833457",
|
|
227
|
+
"4201028200009261290113185417468510",
|
|
228
|
+
" 4 2 0 1 0 2 8 2 0 0 0 0 9 2 6 1 2 9 0 1 1 3 1 8 5 4 1 7 4 6 8 5 1 0 ",
|
|
251
229
|
"9505 5110 6960 5048 6006 24",
|
|
252
|
-
"9101 1234 5678 9000 0000 13",
|
|
253
230
|
"92748931507708513018050063",
|
|
254
231
|
"92001903060085300042901077",
|
|
255
|
-
"9400 1112 0108 0805 4830 16",
|
|
256
232
|
"9361 2898 7870 0317 6337 95",
|
|
257
|
-
"
|
|
233
|
+
"9261292700768711948021",
|
|
234
|
+
"420 11213 92 6129098349792366623 8",
|
|
235
|
+
"92 6129098349792366623 8",
|
|
236
|
+
"420902459261290336128704042634",
|
|
237
|
+
"420941179261290336128704062441",
|
|
238
|
+
"420926029261290336128704067248",
|
|
239
|
+
"420902729261290336128704124163",
|
|
240
|
+
"420950549261290336128704133837",
|
|
241
|
+
"420193809261290336128704280838",
|
|
242
|
+
"420900469261290336128704216936"
|
|
258
243
|
],
|
|
259
244
|
"invalid": [
|
|
260
245
|
"61299998820821171811",
|
|
261
246
|
"9200000000000000000000",
|
|
262
247
|
"420000000000000000000000000000",
|
|
263
|
-
"420000009200000000000000000000"
|
|
248
|
+
"420000009200000000000000000000",
|
|
249
|
+
"4201028200009261290113185417468511"
|
|
264
250
|
]
|
|
265
251
|
},
|
|
266
252
|
"additional": [
|
|
@@ -268,14 +254,16 @@
|
|
|
268
254
|
"name": "Service Type",
|
|
269
255
|
"regex_group_name": "ServiceType",
|
|
270
256
|
"lookup": [
|
|
271
|
-
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
257
|
+
{ "matches": "612", "name": "Parcel Select" }
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"name": "Application Identifier",
|
|
262
|
+
"regex_group_name": "ApplicationIdentifier",
|
|
263
|
+
"lookup": [
|
|
264
|
+
{ "matches": "92", "name": "Commercial mailer", "description": "Nine digit mailer ID, and may have been carried by another company before USPS" },
|
|
265
|
+
{ "matches": "93", "name": "Commercial mailer", "description": "Six digit mailer ID, and may have been carried by another company before USPS" },
|
|
266
|
+
{ "matches": "95", "name": "USPS Retail", "description": "Issued at a post office counter, an automated postal center, or a self-service kiosk" }
|
|
279
267
|
]
|
|
280
268
|
}
|
|
281
269
|
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Yodel",
|
|
3
|
+
"courier_code": "yodel",
|
|
4
|
+
"tracking_numbers": [
|
|
5
|
+
{
|
|
6
|
+
"name": "Yodel",
|
|
7
|
+
"id": "yodel",
|
|
8
|
+
"description": "Yodel's UK domestic numbers, carried on a JD or JJD prefix. Numbers entering the Yodel network from other carriers can take other formats.",
|
|
9
|
+
"regex": [
|
|
10
|
+
"\\s*J\\s*(J\\s*)?D\\s*(?<SerialNumber>([0-9]\\s*){16})\\s*"
|
|
11
|
+
],
|
|
12
|
+
"validation": {},
|
|
13
|
+
"tracking_url": "https://www.yodel.co.uk/tracking/%s",
|
|
14
|
+
"test_numbers": {
|
|
15
|
+
"valid": [
|
|
16
|
+
"JJD0002257639032011",
|
|
17
|
+
"JJD0002255380054291",
|
|
18
|
+
"JD0002242617644732",
|
|
19
|
+
"JD0002254405060992",
|
|
20
|
+
"J J D 0 0 0 2 2 5 7 6 3 9 0 3 2 0 2 0"
|
|
21
|
+
],
|
|
22
|
+
"invalid": [
|
|
23
|
+
"JD000225440506099",
|
|
24
|
+
"JC0002254405060992"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
data/lib/tracking_number.rb
CHANGED
|
@@ -60,4 +60,29 @@ module TrackingNumber
|
|
|
60
60
|
def self.new(tracking_number)
|
|
61
61
|
detect(tracking_number)
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
# Renamed in tracking_number_data 2.0. The old constant still describes the same barcode.
|
|
65
|
+
RENAMED_TYPES = { USPS91: :USPSIMpbC, USPS22: :USPSIMpbN, USPS34v2: :USPSIMpbC }.freeze
|
|
66
|
+
|
|
67
|
+
# Removed in tracking_number_data 2.0. An alias would answer is_a? for every IMpb number,
|
|
68
|
+
# which is the claim the data stopped making, so these raise instead.
|
|
69
|
+
WITHDRAWN_TYPES = {
|
|
70
|
+
FedExSmartPost: 'USPSIMpbC',
|
|
71
|
+
DHLECommerce30: 'USPSIMpbC'
|
|
72
|
+
}.freeze
|
|
73
|
+
|
|
74
|
+
def self.const_missing(name)
|
|
75
|
+
if (renamed = RENAMED_TYPES[name])
|
|
76
|
+
warn "TrackingNumber::#{name} is now TrackingNumber::#{renamed}"
|
|
77
|
+
return const_get(renamed)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if (replacement = WITHDRAWN_TYPES[name])
|
|
81
|
+
raise NameError, "TrackingNumber::#{name} no longer exists. These are USPS IMpb barcodes " \
|
|
82
|
+
"carrying nothing that identifies the shipper, so they now decode as " \
|
|
83
|
+
"TrackingNumber::#{replacement}."
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
super
|
|
87
|
+
end
|
|
63
88
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
|
2
|
+
|
|
3
|
+
# No shipped definition declares partners currently at the tracking number data 2.0 release,
|
|
4
|
+
# but the concept is still supported in the data spec, and should be tested, so we're faking it here
|
|
5
|
+
|
|
6
|
+
class PartnershipTest < Minitest::Test
|
|
7
|
+
NUMBER = 'ZZ1234567890'.freeze
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@shipper = build_class('test_shipper', partner_id: 'test_carrier', partner_type: 'carrier')
|
|
11
|
+
@carrier = build_class('test_carrier', partner_id: 'test_shipper', partner_type: 'shipper')
|
|
12
|
+
|
|
13
|
+
TrackingNumber::TYPES.push(@shipper, @carrier)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def teardown
|
|
17
|
+
TrackingNumber::TYPES.delete(@shipper)
|
|
18
|
+
TrackingNumber::TYPES.delete(@carrier)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
should "report a partnership from both sides" do
|
|
22
|
+
assert @shipper.new(NUMBER).partnership?
|
|
23
|
+
assert @carrier.new(NUMBER).partnership?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
should "pair each side with the other" do
|
|
27
|
+
partners = @carrier.new(NUMBER).partners
|
|
28
|
+
|
|
29
|
+
assert_instance_of @shipper, partners.shipper
|
|
30
|
+
assert_instance_of @carrier, partners.carrier
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
should "report which side of the partnership each is" do
|
|
34
|
+
assert @shipper.new(NUMBER).shipper?
|
|
35
|
+
assert !@shipper.new(NUMBER).carrier?
|
|
36
|
+
|
|
37
|
+
assert @carrier.new(NUMBER).carrier?
|
|
38
|
+
assert !@carrier.new(NUMBER).shipper?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
should "only match the carrier side when searching by default" do
|
|
42
|
+
matches = TrackingNumber.search("shipped as #{NUMBER} today")
|
|
43
|
+
|
|
44
|
+
assert_equal [@carrier], matches.collect(&:class)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should "match both sides when searching for all" do
|
|
48
|
+
matches = TrackingNumber.search("shipped as #{NUMBER} today", match: :all)
|
|
49
|
+
|
|
50
|
+
assert_equal [@shipper, @carrier], matches.collect(&:class)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def build_class(id, partner_id:, partner_type:)
|
|
56
|
+
pattern = 'Z\s*Z\s*(?<SerialNumber>([0-9]\s*){10})'
|
|
57
|
+
|
|
58
|
+
klass = Class.new(TrackingNumber::Base)
|
|
59
|
+
klass.const_set('ID', id)
|
|
60
|
+
klass.const_set('COURIER_CODE', id.to_sym)
|
|
61
|
+
klass.const_set('COURIER_INFO', name: id, courier_code: id.to_sym)
|
|
62
|
+
klass.const_set('SEARCH_PATTERN', Regexp.new("\\b#{pattern}\\b"))
|
|
63
|
+
klass.const_set('VERIFY_PATTERN', Regexp.new("^#{pattern}$"))
|
|
64
|
+
klass.const_set('VALIDATION', {})
|
|
65
|
+
klass.const_set('ADDITIONAL', nil)
|
|
66
|
+
klass.const_set('TRACKING_URL', nil)
|
|
67
|
+
klass.const_set('PARTNERS', [{ partner_id: partner_id, partner_type: partner_type }])
|
|
68
|
+
|
|
69
|
+
klass
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
|
2
|
+
|
|
3
|
+
class RenamedTypesTest < Minitest::Test
|
|
4
|
+
NUMBER = '420112139261290983497923666238'.freeze
|
|
5
|
+
|
|
6
|
+
should "resolve a renamed constant to the definition that replaced it" do
|
|
7
|
+
assert_equal TrackingNumber::USPSIMpbC, silenced { TrackingNumber::USPS91 }
|
|
8
|
+
assert_equal TrackingNumber::USPSIMpbN, silenced { TrackingNumber::USPS22 }
|
|
9
|
+
assert_equal TrackingNumber::USPSIMpbC, silenced { TrackingNumber::USPS34v2 }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
should "keep is_a? answering for a renamed constant" do
|
|
13
|
+
assert TrackingNumber.new(NUMBER).is_a?(silenced { TrackingNumber::USPS91 })
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
should "warn once a renamed constant is reached for" do
|
|
17
|
+
_, err = capture_io { TrackingNumber::USPS91 }
|
|
18
|
+
|
|
19
|
+
assert_match(/USPS91 is now TrackingNumber::USPSIMpbC/, err)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# An alias would answer is_a? for every IMpb number, including labels these carriers never touched
|
|
23
|
+
should "refuse a withdrawn constant and name its replacement" do
|
|
24
|
+
error = assert_raises(NameError) { TrackingNumber::FedExSmartPost }
|
|
25
|
+
assert_match(/no longer exists/, error.message)
|
|
26
|
+
assert_match(/USPSIMpbC/, error.message)
|
|
27
|
+
|
|
28
|
+
assert_raises(NameError) { TrackingNumber::DHLECommerce30 }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
should "leave an unrelated missing constant alone" do
|
|
32
|
+
error = assert_raises(NameError) { TrackingNumber::NotADefinition }
|
|
33
|
+
|
|
34
|
+
assert_match(/uninitialized constant/, error.message)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def silenced
|
|
40
|
+
resolved = nil
|
|
41
|
+
capture_io { resolved = yield }
|
|
42
|
+
resolved
|
|
43
|
+
end
|
|
44
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -50,10 +50,11 @@ class Minitest::Test
|
|
|
50
50
|
assert !t.valid?
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def should_fail_on_check_digit_changes(valid_number)
|
|
53
|
+
def should_fail_on_check_digit_changes(valid_number, klass)
|
|
54
54
|
digits = valid_number.gsub(/\s/, "").chars.compact.to_a
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
position = digits.join.match(klass::VERIFY_PATTERN).begin("CheckDigit")
|
|
56
|
+
current = digits[position].to_i
|
|
57
|
+
digits[position] = (current <= 2 ? current + 3 : current - 3).to_s
|
|
57
58
|
invalid_number = digits.join
|
|
58
59
|
t = TrackingNumber.new(invalid_number)
|
|
59
60
|
assert !t.valid?, "#{invalid_number} reported as a valid #{t.class}, and it shouldn't be"
|
|
@@ -30,7 +30,7 @@ class TrackingNumberMetaTest < Minitest::Test
|
|
|
30
30
|
if tracking_info[:validation][:checksum]
|
|
31
31
|
# only run this test if number format has checksum
|
|
32
32
|
should "fail on check digit changes with #{valid_number}" do
|
|
33
|
-
should_fail_on_check_digit_changes(valid_number)
|
|
33
|
+
should_fail_on_check_digit_changes(valid_number, klass)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -109,7 +109,7 @@ class TrackingNumberTest < Minitest::Test
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
should "report correct service" do
|
|
112
|
-
|
|
112
|
+
assert_equal "Delivery Confirmation", tracking_number.service_type
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
should "report correct shipper_id" do
|
|
@@ -142,7 +142,7 @@ class TrackingNumberTest < Minitest::Test
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
should "report correct shipper_id" do
|
|
145
|
-
assert_equal "
|
|
145
|
+
assert_equal "901326076", tracking_number.shipper_id
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
should "report correct no destination" do
|
|
@@ -158,7 +158,7 @@ class TrackingNumberTest < Minitest::Test
|
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
should "report no partners" do
|
|
161
|
-
|
|
161
|
+
assert_nil tracking_number.partners
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
should "report as shipper and carrier" do
|
|
@@ -172,7 +172,7 @@ class TrackingNumberTest < Minitest::Test
|
|
|
172
172
|
end
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
context "tracking number
|
|
175
|
+
context "tracking number additional data for USPS Parcel Select" do
|
|
176
176
|
tracking_number = TrackingNumber.new("420 11213 92 6129098349792366623 8")
|
|
177
177
|
|
|
178
178
|
should "report correct courier name" do
|
|
@@ -184,31 +184,28 @@ class TrackingNumberTest < Minitest::Test
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
should "report correct service type" do
|
|
187
|
-
assert_equal "
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
should "report partnership" do
|
|
191
|
-
assert_equal true, tracking_number.partnership?
|
|
187
|
+
assert_equal "Parcel Select", tracking_number.service_type
|
|
192
188
|
end
|
|
193
189
|
|
|
194
|
-
should "report
|
|
195
|
-
assert_equal false, tracking_number.
|
|
190
|
+
should "report no partnership" do
|
|
191
|
+
assert_equal false, tracking_number.partnership?
|
|
196
192
|
end
|
|
197
193
|
|
|
198
|
-
should "report
|
|
194
|
+
should "report as shipper and carrier" do
|
|
195
|
+
assert_equal true, tracking_number.shipper?
|
|
199
196
|
assert_equal true, tracking_number.carrier?
|
|
200
197
|
end
|
|
201
198
|
|
|
202
|
-
should "report
|
|
203
|
-
|
|
199
|
+
should "report no partners" do
|
|
200
|
+
assert_nil tracking_number.partners
|
|
204
201
|
end
|
|
205
202
|
end
|
|
206
203
|
|
|
207
|
-
context "searching numbers
|
|
208
|
-
|
|
204
|
+
context "searching for numbers" do
|
|
205
|
+
parcel_select_number = "420 11213 92 6129098349792366623 8"
|
|
209
206
|
single_number = "92001903060085300042901077"
|
|
210
207
|
|
|
211
|
-
search_string = ["number that matches two services",
|
|
208
|
+
search_string = ["number that matches two services", parcel_select_number, " number that matches only one: ", single_number, "let's see if that does it"].join(' ')
|
|
212
209
|
|
|
213
210
|
should "match only carriers by default" do
|
|
214
211
|
matches = TrackingNumber.search(search_string)
|
|
@@ -218,7 +215,7 @@ class TrackingNumberTest < Minitest::Test
|
|
|
218
215
|
|
|
219
216
|
should "match all if specified" do
|
|
220
217
|
matches = TrackingNumber.search(search_string, match: :all)
|
|
221
|
-
assert_equal
|
|
218
|
+
assert_equal 2, matches.size
|
|
222
219
|
end
|
|
223
220
|
|
|
224
221
|
should "match only shippers if specified" do
|
data/tracking_number.gemspec
CHANGED
|
@@ -53,6 +53,7 @@ Gem::Specification.new do |s|
|
|
|
53
53
|
s.add_runtime_dependency('activesupport', '>= 4.2.5')
|
|
54
54
|
s.add_runtime_dependency('json', '>= 1.8.3')
|
|
55
55
|
s.add_development_dependency('activemodel', '> 4.2.5.1')
|
|
56
|
+
s.add_development_dependency('irb')
|
|
56
57
|
s.add_development_dependency('minitest', '~> 5.5')
|
|
57
58
|
s.add_development_dependency('minitest-reporters', '~> 1.1')
|
|
58
59
|
s.add_development_dependency('rake', '~> 13.0')
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tracking_number
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeff Keen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - ">"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: 4.2.5.1
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: irb
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: minitest
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -175,6 +189,7 @@ files:
|
|
|
175
189
|
- lib/data/couriers/speedee.json
|
|
176
190
|
- lib/data/couriers/ups.json
|
|
177
191
|
- lib/data/couriers/usps.json
|
|
192
|
+
- lib/data/couriers/yodel.json
|
|
178
193
|
- lib/data/couriers/yunexpress.json
|
|
179
194
|
- lib/tracking_number.rb
|
|
180
195
|
- lib/tracking_number/active_model_validator.rb
|
|
@@ -187,6 +202,8 @@ files:
|
|
|
187
202
|
- lib/tracking_number/version.rb
|
|
188
203
|
- package.json
|
|
189
204
|
- test/active_model_validator_test.rb
|
|
205
|
+
- test/partnership_test.rb
|
|
206
|
+
- test/renamed_types_test.rb
|
|
190
207
|
- test/test_helper.rb
|
|
191
208
|
- test/tracking_number_meta_test.rb
|
|
192
209
|
- test/tracking_number_test.rb
|