Module: Heatwave::TrackingNumber::Checksums

Defined in:
app/lib/heatwave/tracking_number/checksums.rb

Overview

Check-digit algorithms used by the owned courier JSON.

Ported from tracking_number 2.4.0 so existing barcode fixtures keep
the same pass/fail behaviour. Do not "clean up" the arithmetic without
re-checking every JSON test_numbers entry.

Constant Summary collapse

MOD_37_36_LETTERS =

Letter values for ISO/IEC 7064 Mod 37,36.

{
  A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, G: 16, H: 17, I: 18, J: 19,
  K: 20, L: 21, M: 22, N: 23, O: 24, P: 25, Q: 26, R: 27, S: 28, T: 29,
  U: 30, V: 31, W: 32, X: 33, Y: 34, Z: 35
}.freeze

Class Method Summary collapse

Class Method Details

.mod10_value(char, index, extras) ⇒ Integer

Parameters:

  • char (String)
  • index (Integer)
  • extras (Hash)

Returns:

  • (Integer)


97
98
99
100
101
102
103
104
105
106
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 97

def mod10_value(char, index, extras)
  value = char.match?(/[0-9]/) ? char.to_i : ((char.ord - 3) % 10)
  if extras[:odds_multiplier] && index.odd?
    value * extras[:odds_multiplier].to_i
  elsif extras[:evens_multiplier] && index.even?
    value * extras[:evens_multiplier].to_i
  else
    value
  end
end

.mod_37_36_encode(check) ⇒ String

Parameters:

  • check (Integer)

Returns:

  • (String)


121
122
123
124
125
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 121

def mod_37_36_encode(check)
  return check.to_s if check < 10

  MOD_37_36_LETTERS.find { |_letter, value| value == check }[0].to_s
end

.mod_37_36_step(check, char) ⇒ Integer

Parameters:

  • check (Integer)
  • char (String)

Returns:

  • (Integer)


111
112
113
114
115
116
117
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 111

def mod_37_36_step(check, char)
  value = char.match?(/[A-Za-z]/) ? MOD_37_36_LETTERS[char.to_sym] : char.to_i
  check += value
  check -= 36 if check > 36
  check *= 2
  check > 36 ? check - 37 : check
end

.valid?(name, sequence, check_digit, extras = {}) ⇒ Boolean

Dispatch a JSON validation.checksum.name to the matching algorithm.

Parameters:

  • name (String, Symbol)
  • sequence (String)

    serial without the check digit

  • check_digit (String)
  • extras (Hash) (defaults to: {})

    algorithm-specific JSON (weightings, modulo, …)

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    when name is not a known algorithm



28
29
30
31
32
33
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 28

def valid?(name, sequence, check_digit, extras = {})
  method = "validates_#{name}?"
  raise ArgumentError, "unknown checksum #{name.inspect}" unless respond_to?(method, true)

  public_send(method, sequence, check_digit, extras || {})
end

.validates_luhn?(sequence, check_digit, _extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • _extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


131
132
133
134
135
136
137
138
139
140
141
142
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 131

def validates_luhn?(sequence, check_digit, _extras = {})
  total = 0
  sequence.chars.reverse.each_with_index do |char, index|
    value = char.to_i
    value *= 2 if index.even?
    value -= 9 if value > 9
    total += value
  end
  check = total % 10
  check = (10 - check) unless check.zero?
  check.to_i == check_digit.to_i
end

.validates_mod10?(sequence, check_digit, extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


65
66
67
68
69
70
71
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 65

def validates_mod10?(sequence, check_digit, extras = {})
  characters = extras[:reverse] ? sequence.chars.reverse : sequence.chars
  total = characters.each_with_index.sum { |char, index| mod10_value(char, index, extras) }
  check = total % 10
  check = (10 - check) unless check.zero?
  check.to_i == check_digit.to_i
end

.validates_mod7?(sequence, check_digit, _extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • _extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


77
78
79
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 77

def validates_mod7?(sequence, check_digit, _extras = {})
  sequence.to_i % 7 == check_digit.to_i
end

.validates_mod_37_36?(sequence, check_digit, _extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • _extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


85
86
87
88
89
90
91
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 85

def validates_mod_37_36?(sequence, check_digit, _extras = {})
  check = 36
  sequence.chars.each { |char| check = mod_37_36_step(check, char) }
  check = 37 - check
  check = 0 if check == 36
  mod_37_36_encode(check) == check_digit
end

.validates_s10?(sequence, check_digit, _extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • _extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


39
40
41
42
43
44
45
46
47
48
49
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 39

def validates_s10?(sequence, check_digit, _extras = {})
  weighting = [8, 6, 4, 2, 3, 5, 9, 7]
  total = sequence.chars.zip(weighting).sum { |char, weight| char.to_i * weight.to_i }
  remainder = total % 11
  check = case remainder
          when 1 then 0
          when 0 then 5
          else 11 - remainder
          end
  check.to_i == check_digit.to_i
end

.validates_sum_product_with_weightings_and_modulo?(sequence, check_digit, extras = {}) ⇒ Boolean

Parameters:

  • sequence (String)
  • check_digit (String)
  • extras (Hash) (defaults to: {})

Returns:

  • (Boolean)


55
56
57
58
59
# File 'app/lib/heatwave/tracking_number/checksums.rb', line 55

def validates_sum_product_with_weightings_and_modulo?(sequence, check_digit, extras = {})
  weighting = extras[:weightings] || []
  total = sequence.chars.zip(weighting).sum { |char, weight| char.to_i * weight }
  (total % extras[:modulo1] % extras[:modulo2]) == check_digit.to_i
end