tracking_number 2.4.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/.github/FUNDING.yml +2 -0
- data/CHANGELOG.md +19 -0
- data/README.md +3 -35
- data/lib/data/couriers/canadapost.json +3 -1
- data/lib/data/couriers/canpar.json +29 -0
- data/lib/data/couriers/dhl.json +3 -74
- data/lib/data/couriers/dpd.json +6 -2
- data/lib/data/couriers/fedex.json +44 -56
- data/lib/data/couriers/gofo.json +24 -0
- data/lib/data/couriers/lasership.json +21 -0
- data/lib/data/couriers/old_dominion.json +4 -2
- data/lib/data/couriers/ontrac.json +6 -2
- data/lib/data/couriers/purolator.json +50 -0
- data/lib/data/couriers/s10.json +9 -1
- data/lib/data/couriers/speedee.json +26 -0
- data/lib/data/couriers/ups.json +6 -2
- data/lib/data/couriers/usps.json +138 -137
- data/lib/data/couriers/yodel.json +29 -0
- data/lib/data/couriers/yunexpress.json +25 -0
- data/lib/tracking_number/version.rb +1 -1
- data/lib/tracking_number.rb +25 -0
- data/package.json +1 -25
- 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 +25 -2
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>",
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
"validation": {
|
|
19
19
|
"checksum": {
|
|
20
20
|
"name": "mod10",
|
|
21
|
+
"modulo": 10,
|
|
21
22
|
"evens_multiplier": 3,
|
|
22
|
-
"odds_multiplier": 1
|
|
23
|
+
"odds_multiplier": 1,
|
|
24
|
+
"reverse": false
|
|
23
25
|
}
|
|
24
26
|
},
|
|
25
27
|
"test_numbers": {
|
|
@@ -37,43 +39,54 @@
|
|
|
37
39
|
"name": "Service Type",
|
|
38
40
|
"regex_group_name": "ServiceType",
|
|
39
41
|
"lookup": [
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
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" }
|
|
56
62
|
]
|
|
57
63
|
}
|
|
58
64
|
]
|
|
59
65
|
},
|
|
60
66
|
{
|
|
61
|
-
"name": "USPS
|
|
62
|
-
"id": "
|
|
63
|
-
"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
|
+
},
|
|
64
73
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
65
74
|
"regex": [
|
|
66
|
-
"\\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])))?)?",
|
|
67
76
|
"(?<SerialNumber>",
|
|
68
|
-
"(?<
|
|
69
|
-
"(?<
|
|
70
|
-
"(
|
|
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})",
|
|
71
83
|
")",
|
|
72
84
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
73
85
|
],
|
|
74
86
|
"validation": {
|
|
75
87
|
"checksum": {
|
|
76
88
|
"name": "mod10",
|
|
89
|
+
"modulo": 10,
|
|
77
90
|
"evens_multiplier": 3,
|
|
78
91
|
"odds_multiplier": 1,
|
|
79
92
|
"reverse": true
|
|
@@ -83,6 +96,8 @@
|
|
|
83
96
|
"valid": [
|
|
84
97
|
"420787459400111206206406260787",
|
|
85
98
|
"9400111206206406260787",
|
|
99
|
+
"9400 1112 0108 0805 4830 16",
|
|
100
|
+
"9405803699300124287899",
|
|
86
101
|
"9434611206206406227577",
|
|
87
102
|
"9434611206206407667136",
|
|
88
103
|
"9400111206206407628746",
|
|
@@ -94,160 +109,144 @@
|
|
|
94
109
|
"2334611306206407667222"
|
|
95
110
|
]
|
|
96
111
|
},
|
|
97
|
-
"partners": [{
|
|
98
|
-
"description": "DHL ECommerce uses USPS for last mile delivery",
|
|
99
|
-
"partner_type": "shipper",
|
|
100
|
-
"partner_id": "dhl_ecommerce_30",
|
|
101
|
-
"validation": {
|
|
102
|
-
"matches_all": [
|
|
103
|
-
{
|
|
104
|
-
"regex_group_name": "RoutingApplicationId",
|
|
105
|
-
"matches": "420"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"matches_regex": "9[24]",
|
|
109
|
-
"regex_group_name": "ServiceType"
|
|
110
|
-
}
|
|
111
|
-
]
|
|
112
|
-
}
|
|
113
|
-
}],
|
|
114
112
|
"additional": [
|
|
115
113
|
{
|
|
116
|
-
"name": "
|
|
117
|
-
"regex_group_name": "
|
|
114
|
+
"name": "Application Identifier",
|
|
115
|
+
"regex_group_name": "ApplicationIdentifier",
|
|
118
116
|
"lookup": [
|
|
119
|
-
{ "matches": "
|
|
120
|
-
{ "matches": "93", "name": "Collect on Delivery (COD)" },
|
|
121
|
-
{ "matches": "94", "name": "USPS Tracking" },
|
|
122
|
-
{ "matches": "95", "name": "Certified Mail" },
|
|
123
|
-
{ "matches": "96", "name": "Insured Mail" },
|
|
124
|
-
{ "matches": "97", "name": "Registered Mail" },
|
|
125
|
-
{ "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" }
|
|
126
118
|
]
|
|
127
119
|
}
|
|
128
120
|
]
|
|
129
121
|
},
|
|
130
122
|
{
|
|
131
|
-
"name": "USPS
|
|
132
|
-
"id": "
|
|
133
|
-
"description": "
|
|
123
|
+
"name": "USPS Legacy",
|
|
124
|
+
"id": "usps_legacy",
|
|
125
|
+
"description": "Delivery and Signature Confirmation numbers, carrying the 91 application identifier",
|
|
126
|
+
"glossary": {
|
|
127
|
+
"ApplicationIdentifier": { "description": "Present only when the barcode is UCC/EAN Code 128." }
|
|
128
|
+
},
|
|
134
129
|
"regex": [
|
|
135
|
-
"\\s*(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})",
|
|
136
|
-
"(?<RoutingNumber>([0-9]\\s*){4})",
|
|
130
|
+
"\\s*(?:(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})(?<DestinationZipPlus4>([0-9]\\s*){4})?)?",
|
|
137
131
|
"(?<SerialNumber>",
|
|
138
|
-
"(?<ApplicationIdentifier>9\\s*
|
|
139
|
-
"(?<
|
|
140
|
-
"(?<
|
|
132
|
+
"(?<ApplicationIdentifier>9\\s*1\\s*)?",
|
|
133
|
+
"(?<ServiceType>([0-9]\\s*){2})",
|
|
134
|
+
"(?<ShipperId>([0-9]\\s*){9})",
|
|
135
|
+
"(?<PackageId>([0-9]\\s*){8})",
|
|
141
136
|
")",
|
|
142
137
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
143
138
|
],
|
|
144
139
|
"validation": {
|
|
145
140
|
"checksum": {
|
|
146
141
|
"name": "mod10",
|
|
142
|
+
"modulo": 10,
|
|
147
143
|
"evens_multiplier": 3,
|
|
148
|
-
"odds_multiplier": 1
|
|
144
|
+
"odds_multiplier": 1,
|
|
145
|
+
"reverse": false
|
|
146
|
+
},
|
|
147
|
+
"serial_number_format": {
|
|
148
|
+
"prepend_if": {
|
|
149
|
+
"matches_regex": "^(?!9\\s*1).+",
|
|
150
|
+
"content": "91"
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
153
|
},
|
|
151
154
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
152
155
|
"test_numbers": {
|
|
153
156
|
"valid": [
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
+
"420 22153 9101026837331000039521",
|
|
158
|
+
"9101 1234 5678 9000 0000 13",
|
|
159
|
+
"7196 9010 7560 0307 7385"
|
|
157
160
|
],
|
|
158
161
|
"invalid": [
|
|
159
|
-
"
|
|
162
|
+
"9101 1234 5678 9000 0000 14",
|
|
163
|
+
"7196 9010 7560 0307 7386"
|
|
160
164
|
]
|
|
161
|
-
}
|
|
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
|
+
]
|
|
162
194
|
},
|
|
163
195
|
{
|
|
164
|
-
"name": "USPS
|
|
165
|
-
"id": "
|
|
166
|
-
"description": "
|
|
196
|
+
"name": "USPS IMpb C",
|
|
197
|
+
"id": "usps_impb_c",
|
|
198
|
+
"description": "IMpb constructs C01 through C10, plus USPS retail",
|
|
199
|
+
"glossary": {
|
|
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." }
|
|
201
|
+
},
|
|
167
202
|
"regex": [
|
|
168
|
-
"\\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])))?)?",
|
|
169
204
|
"(?<SerialNumber>",
|
|
170
|
-
"(?<ApplicationIdentifier>9\\s*[
|
|
171
|
-
"(?<
|
|
172
|
-
"(
|
|
173
|
-
"(
|
|
174
|
-
"(?<
|
|
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})",
|
|
175
211
|
")",
|
|
176
212
|
"(?<CheckDigit>[0-9]\\s*)"
|
|
177
213
|
],
|
|
178
214
|
"validation": {
|
|
179
215
|
"checksum": {
|
|
180
216
|
"name": "mod10",
|
|
217
|
+
"modulo": 10,
|
|
181
218
|
"evens_multiplier": 3,
|
|
182
|
-
"odds_multiplier": 1
|
|
183
|
-
|
|
184
|
-
"serial_number_format": {
|
|
185
|
-
"prepend_if": {
|
|
186
|
-
"matches_regex": "^(?!9[1-5]).+",
|
|
187
|
-
"content": "91"
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
"partners": [{
|
|
192
|
-
"description": "FedEx SmartPost uses USPS for last mile delivery, but not all USPS91 numbers are SmartPosts",
|
|
193
|
-
"partner_type": "shipper",
|
|
194
|
-
"partner_id": "fedex_smartpost",
|
|
195
|
-
"validation": {
|
|
196
|
-
"matches_all": [
|
|
197
|
-
{
|
|
198
|
-
"regex_group_name": "ServiceType",
|
|
199
|
-
"matches": "29"
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"matches": "61",
|
|
203
|
-
"regex_group_name": "SCNC"
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"regex_group_name": "ApplicationIdentifier",
|
|
207
|
-
"matches": "92"
|
|
208
|
-
}
|
|
209
|
-
]
|
|
219
|
+
"odds_multiplier": 1,
|
|
220
|
+
"reverse": false
|
|
210
221
|
}
|
|
211
222
|
},
|
|
212
|
-
{
|
|
213
|
-
"description": "DHL uses USPS for last mile delivery, but not all USPS91 numbers are DHL Ecommerce",
|
|
214
|
-
"partner_type": "shipper",
|
|
215
|
-
"partner_id": "dhl_ecommerce_30",
|
|
216
|
-
"validation": {
|
|
217
|
-
"matches_all": [
|
|
218
|
-
{
|
|
219
|
-
"regex_group_name": "ServiceType",
|
|
220
|
-
"matches": "29"
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
"matches": "61",
|
|
224
|
-
"regex_group_name": "SCNC"
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
"matches": "420",
|
|
228
|
-
"regex_group_name": "RoutingApplicationId"
|
|
229
|
-
}
|
|
230
|
-
]
|
|
231
|
-
}
|
|
232
|
-
}],
|
|
233
223
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
|
|
234
224
|
"test_numbers": {
|
|
235
225
|
"valid": [
|
|
236
|
-
"
|
|
237
|
-
"
|
|
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 ",
|
|
238
229
|
"9505 5110 6960 5048 6006 24",
|
|
239
|
-
"9101 1234 5678 9000 0000 13",
|
|
240
230
|
"92748931507708513018050063",
|
|
241
231
|
"92001903060085300042901077",
|
|
242
|
-
"9400 1112 0108 0805 4830 16",
|
|
243
232
|
"9361 2898 7870 0317 6337 95",
|
|
244
|
-
"
|
|
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"
|
|
245
243
|
],
|
|
246
244
|
"invalid": [
|
|
247
245
|
"61299998820821171811",
|
|
248
246
|
"9200000000000000000000",
|
|
249
247
|
"420000000000000000000000000000",
|
|
250
|
-
"420000009200000000000000000000"
|
|
248
|
+
"420000009200000000000000000000",
|
|
249
|
+
"4201028200009261290113185417468511"
|
|
251
250
|
]
|
|
252
251
|
},
|
|
253
252
|
"additional": [
|
|
@@ -255,14 +254,16 @@
|
|
|
255
254
|
"name": "Service Type",
|
|
256
255
|
"regex_group_name": "ServiceType",
|
|
257
256
|
"lookup": [
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
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" }
|
|
266
267
|
]
|
|
267
268
|
}
|
|
268
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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "YunExpress",
|
|
3
|
+
"courier_code": "yunexpress",
|
|
4
|
+
"tracking_numbers": [
|
|
5
|
+
{
|
|
6
|
+
"name": "YunExpress",
|
|
7
|
+
"id": "yunexpress",
|
|
8
|
+
"tracking_url": "https://www.yuntrack.com/parcelTracking?id=%s",
|
|
9
|
+
"regex": [
|
|
10
|
+
"\\s*Y\\s*T\\s*(?<SerialNumber>([0-9]\\s*){16})\\s*"
|
|
11
|
+
],
|
|
12
|
+
"validation": {},
|
|
13
|
+
"test_numbers": {
|
|
14
|
+
"valid": [
|
|
15
|
+
"YT2229521272164446",
|
|
16
|
+
"YT2229521272164447",
|
|
17
|
+
" Y T 2 2 2 9 5 2 1 2 7 2 1 6 4 4 4 7 "
|
|
18
|
+
],
|
|
19
|
+
"invalid": [
|
|
20
|
+
"YT123456789012345"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
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
|
data/package.json
CHANGED
|
@@ -38,31 +38,7 @@
|
|
|
38
38
|
"release": "minor"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
"type": "
|
|
42
|
-
"release": "patch"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"type": "ci",
|
|
46
|
-
"release": "patch"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"type": "chore",
|
|
50
|
-
"release": "patch"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"type": "docs",
|
|
54
|
-
"release": "patch"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"type": "refactor",
|
|
58
|
-
"release": "patch"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"type": "style",
|
|
62
|
-
"release": "patch"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"type": "test",
|
|
41
|
+
"type": "fix",
|
|
66
42
|
"release": "patch"
|
|
67
43
|
}
|
|
68
44
|
],
|
|
@@ -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
|
|