tracking_number 2.2.0 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9d573b1531316f8b0bb51ed6f3dade202df6f9ecb3df58463dbb38046afb562
4
- data.tar.gz: 45ad0d821099ea7b17349d22cfae5401104ddb28765a42ad364f46cb75b788fd
3
+ metadata.gz: 7c0ef2fa722ce7a2d9cc6cfaac9c5510ef7ac79074ba4e227f9554b8157da2ee
4
+ data.tar.gz: 3a5f46d4dc5027e8bf68c4f7cf0756851e284d29b499c38d4c264b57ebc61fc4
5
5
  SHA512:
6
- metadata.gz: 56770ee8e07aecb05699fb83486517409f2bcc9741fd8dba769da7e9c9d46b00e774960b9327f3de6f75559b1d8ac1c823ba7e6d4a80863941aa06bdb10b9991
7
- data.tar.gz: 0fbeb3042b8c61cfee4eb57179c65c9bbfe733dee83669e69a78608309a2e3364add2b0290817ecdf445ea17113dc8cd2e3fb28f6a452e2d6aaf91edb988bb92
6
+ metadata.gz: 404a19a4f9847ade5581c5ff7c0cf950fde208d773f81f6882715d44b4395f94fc505538d46b718ad0c7fbfcdf152e512619ca86372dc1021a524ab2614cd898
7
+ data.tar.gz: c4fbe1cf3637fea233106008bc31c1b555a63ea7e04588f08446d00a5d6dd3948daf744e078a74c6e96d7f257c51986fa8712db5f3b9e1e742e0ea9d61a6c63c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  tracking_number changelog
2
2
 
3
+ # [2.4.0](https://github.com/jkeen/tracking_number/compare/v2.3.0...v2.4.0) (2025-08-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * update to tracking_number_data 1.9.0 ([f885bfd](https://github.com/jkeen/tracking_number/commit/f885bfd956ffacb22f38967754ec14551bca8d50))
9
+
10
+ # [2.3.0](https://github.com/jkeen/tracking_number/compare/v2.2.0...v2.3.0) (2025-08-21)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * prevent error when lookup is balnk for an additional info group ([cce4ddd](https://github.com/jkeen/tracking_number/commit/cce4dddf1ec4b62baa684af24a8be96893fd33a6))
16
+
17
+
18
+ ### Features
19
+
20
+ * add validation option to mod10 to validate in reverse ([3736b50](https://github.com/jkeen/tracking_number/commit/3736b5003d2cc4cbaec2db50f8b64add61897731))
21
+
3
22
  # [2.2.0](https://github.com/jkeen/tracking_number/compare/v2.1.1...v2.2.0) (2025-06-24)
4
23
 
5
24
 
@@ -89,6 +89,80 @@
89
89
  "GR295117494011169045"
90
90
  ]
91
91
  }
92
+ },
93
+ {
94
+ "name": "DHL E-Commerce (30)",
95
+ "id": "dhl_ecommerce_30",
96
+ "regex": [
97
+ "\\s*(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5})",
98
+ "(?!\\s*9\\s*[1-5]\\b)",
99
+ "(?<SerialNumber>",
100
+ "(?<ServiceType>(9[2-5]\\s*))",
101
+ "(?<ShipperId>([0-9]\\s*){9})",
102
+ "(?<PackageId>([0-9]\\s*){10})",
103
+ ")",
104
+ "(?<CheckDigit>[0-9]\\s*)"
105
+ ],
106
+ "validation": {
107
+ "checksum": {
108
+ "name": "mod10",
109
+ "evens_multiplier": 3,
110
+ "odds_multiplier": 1,
111
+ "reverse": true
112
+ }
113
+ },
114
+ "tracking_url": "https://ecommerceportal.dhl.com/track?trackingnumber=%s",
115
+ "partners": [{
116
+ "description": "DHL eCommerce hands off to USPS for the last mile",
117
+ "partner_type": "carrier",
118
+ "partner_id": "usps_22",
119
+ "validation": {
120
+ "matches_all": [
121
+ { "regex_group_name": "RoutingApplicationId", "matches": "420" },
122
+ { "regex_group_name": "ServiceType", "matches_regex": "9[2345]" }
123
+ ]
124
+ }
125
+ },
126
+ {
127
+ "description": "DHL eCommerce hands off to USPS for the last mile",
128
+ "partner_type": "carrier",
129
+ "partner_id": "usps_91",
130
+ "validation": {
131
+ "matches_all": [
132
+ { "regex_group_name": "RoutingApplicationId", "matches": "420" },
133
+ { "regex_group_name": "ServiceType", "matches_regex": "9[2345]" }
134
+ ]
135
+ }
136
+ }],
137
+ "additional": [
138
+ {
139
+ "name": "Service Type",
140
+ "regex_group_name": "ServiceType",
141
+ "lookup": [
142
+ { "matches": "92", "name": "USPS Tracking (Commercial)" },
143
+ { "matches": "93", "name": "Collect on Delivery (COD)" },
144
+ { "matches": "94", "name": "USPS Tracking" },
145
+ { "matches": "95", "name": "Certified Mail" }
146
+ ]
147
+ }
148
+ ],
149
+ "test_numbers": {
150
+ "valid": [
151
+ "420902459261290336128704042634",
152
+ "420941179261290336128704062441",
153
+ "420926029261290336128704067248",
154
+ "420902729261290336128704124163",
155
+ "420950549261290336128704133837",
156
+ "420193809261290336128704280838",
157
+ "420900469261290336128704216936"
158
+ ],
159
+ "invalid": [
160
+ "420941179261290336128704062442",
161
+ "420941179261290336128704062400",
162
+ "420941179261290336128704062422"
163
+ ]
164
+ }
92
165
  }
166
+
93
167
  ]
94
168
  }
@@ -98,7 +98,7 @@
98
98
  "(?<ApplicationIdentifier>9\\s*2\\s*)",
99
99
  ")?",
100
100
  "(?<SerialNumber>",
101
- "(?<SCNC>([0-9]\\s*){2})",
101
+ "(?<SCNC>6\\s*1\\s*)",
102
102
  "(?<ServiceType>2\\s*9\\s*)",
103
103
  "(?<ShipperId>([0-9]\\s*){8})",
104
104
  "(?<PackageId>([0-9]\\s*){11}|([0-9]\\s*){7})",
@@ -138,11 +138,9 @@
138
138
  "tracking_url": "https://www.fedex.com/apps/fedextrack/?tracknumbers=%s",
139
139
  "test_numbers": {
140
140
  "valid": [
141
- "61299998820821171811",
142
141
  "9261292700768711948021",
143
142
  "420 11213 92 6129098349792366623 8",
144
- "92 6129098349792366623 8",
145
- "6129098349792366623 8"
143
+ "92 6129098349792366623 8"
146
144
  ],
147
145
  "invalid": [
148
146
  "9261292700768711948020",
@@ -3,10 +3,10 @@
3
3
  "courier_code": "usps",
4
4
  "tracking_numbers": [
5
5
  {
6
- "tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
7
6
  "name": "USPS 20",
8
7
  "id": "usps_20",
9
8
  "description": "20 digit USPS numbers",
9
+ "tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
10
10
  "regex": [
11
11
  "\\s*(?<SerialNumber>",
12
12
  "(?<ServiceType>([0-9]\\s*){2})",
@@ -57,6 +57,76 @@
57
57
  }
58
58
  ]
59
59
  },
60
+ {
61
+ "name": "USPS 22",
62
+ "id": "usps_22",
63
+ "description": "22 digit USPS numbers",
64
+ "tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
65
+ "regex": [
66
+ "\\s*((?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5}))?",
67
+ "(?<SerialNumber>",
68
+ "(?<ServiceType>(9\\s*[4-8]\\s*))",
69
+ "(?<ShipperId>([0-9]\\s*){9})",
70
+ "(?<PackageId>([0-9]\\s*){10})",
71
+ ")",
72
+ "(?<CheckDigit>[0-9]\\s*)"
73
+ ],
74
+ "validation": {
75
+ "checksum": {
76
+ "name": "mod10",
77
+ "evens_multiplier": 3,
78
+ "odds_multiplier": 1,
79
+ "reverse": true
80
+ }
81
+ },
82
+ "test_numbers": {
83
+ "valid": [
84
+ "420787459400111206206406260787",
85
+ "9400111206206406260787",
86
+ "9434611206206406227577",
87
+ "9434611206206407667136",
88
+ "9400111206206407628746",
89
+ "9400111206206407628845"
90
+ ],
91
+ "invalid": [
92
+ "9434611206206407667131",
93
+ "9434611306206407667222",
94
+ "2334611306206407667222"
95
+ ]
96
+ },
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
+ "additional": [
115
+ {
116
+ "name": "Service Type",
117
+ "regex_group_name": "ServiceType",
118
+ "lookup": [
119
+ { "matches": "92", "name": "Signature Confirmation" },
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" }
126
+ ]
127
+ }
128
+ ]
129
+ },
60
130
  {
61
131
  "name": "USPS 34v2",
62
132
  "id": "usps_32V2",
@@ -131,6 +201,31 @@
131
201
  {
132
202
  "matches": "61",
133
203
  "regex_group_name": "SCNC"
204
+ },
205
+ {
206
+ "regex_group_name": "ApplicationIdentifier",
207
+ "matches": "92"
208
+ }
209
+ ]
210
+ }
211
+ },
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"
134
229
  }
135
230
  ]
136
231
  }
@@ -126,16 +126,16 @@ module TrackingNumber
126
126
 
127
127
  def info
128
128
  Info.new({
129
- courier: courier_info,
130
- service_type: service_type,
131
- service_description: service_description,
132
- destination_zip: destination_zip,
133
- shipper_id: shipper_id,
134
- package_type: package_type,
135
- tracking_url: tracking_url,
136
- partners: partners,
137
- decode: decode
138
- })
129
+ courier: courier_info,
130
+ service_type: service_type,
131
+ service_description: service_description,
132
+ destination_zip: destination_zip,
133
+ shipper_id: shipper_id,
134
+ package_type: package_type,
135
+ tracking_url: tracking_url,
136
+ partners: partners,
137
+ decode: decode
138
+ })
139
139
  end
140
140
 
141
141
  def courier_code
@@ -186,8 +186,7 @@ module TrackingNumber
186
186
  partner_hash = {}
187
187
 
188
188
  return unless (partner_tn = find_matching_partner)
189
-
190
- possible_twin = partner_tn.send(:find_matching_partner)
189
+ possible_twin = partner_tn.send(:find_matching_partner, self.class.const_get("ID"))
191
190
 
192
191
  if possible_twin.instance_of?(self.class) && possible_twin.tracking_number == tracking_number
193
192
  partner_hash[partner_data[:partner_type].to_sym] = partner_tn
@@ -235,8 +234,10 @@ module TrackingNumber
235
234
 
236
235
  additional.each do |additional_info|
237
236
  next unless matches && matches.length > 0 # skip if no match groups
237
+
238
238
  value = matches[additional_info[:regex_group_name]].gsub(/\s/, '') # match is empty
239
239
  next unless value
240
+ next unless additional_info[:lookup]
240
241
 
241
242
  matches = additional_info[:lookup].find do |i|
242
243
  if i[:matches]
@@ -293,8 +294,12 @@ module TrackingNumber
293
294
  nil
294
295
  end
295
296
 
296
- def find_matching_partner
297
- partner_info = self.class.const_get(:PARTNERS) || []
297
+ def find_matching_partner(partner_id = nil)
298
+ partner_info = if partner_id
299
+ (self.class.const_get(:PARTNERS) || []).select { |info| info[:partner_id]&.to_sym == partner_id.to_sym }
300
+ else
301
+ self.class.const_get(:PARTNERS) || []
302
+ end
298
303
 
299
304
  partner_info.each do |partner_data|
300
305
  klass = find_tracking_class_by_id(partner_data[:partner_id])
@@ -302,7 +307,6 @@ module TrackingNumber
302
307
  return false unless klass
303
308
 
304
309
  tn = klass.new(tracking_number)
305
-
306
310
  valid = if partner_data.dig(:validation, :matches_all)
307
311
  tn.valid? && match_all(partner_data.dig(:validation, :matches_all))
308
312
  elsif partner_data.dig(:validation, :matches_any)
@@ -34,7 +34,13 @@ module TrackingNumber
34
34
 
35
35
  def validates_mod10?(sequence, check_digit, extras = {})
36
36
  total = 0
37
- sequence.chars.each_with_index do |c, i|
37
+ characters = if extras[:reverse]
38
+ sequence.chars.reverse
39
+ else
40
+ sequence.chars
41
+ end
42
+
43
+ characters.each_with_index do |c, i|
38
44
  x = if c[/[0-9]/] # numeric
39
45
  c.to_i
40
46
  else
@@ -1,3 +1,3 @@
1
1
  module TrackingNumber
2
- VERSION = "2.2.0"
2
+ VERSION = "2.4.0"
3
3
  end
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: 2.2.0
4
+ version: 2.4.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: 2025-06-24 00:00:00.000000000 Z
11
+ date: 2025-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport