Module: AddressHelper

Included in:
CustomerHierarchyManagement, CustomerService, QuickbooksInvoiceHeaderResolver
Defined in:
app/helpers/address_helper.rb

Instance Method Summary collapse

Instance Method Details

#canadian_postal_code?(code) ⇒ Boolean

Detects Canadian postal code format

Parameters:

  • code (String)

Returns:

  • (Boolean)


153
154
155
# File 'app/helpers/address_helper.rb', line 153

def canadian_postal_code?(code)
  !!(code =~ /^[A-Z]\d[A-Z][ ]?\d[A-Z]\d$/)
end

#clean_city(raw) ⇒ Object

Helper to clean up city



115
116
117
118
119
120
121
122
# File 'app/helpers/address_helper.rb', line 115

def clean_city(raw)
  return '' if raw.blank?

  cleaned = raw.strip

  # Remove trailing commas
  cleaned.gsub(/,+$/, '').strip
end

#normalize_postal_code(postal_code) ⇒ String?

Normalizes postal code for comparison

For Canadian postal codes:

- Removes spaces
- Uppercases

For US ZIP codes:

- Removes hyphens

Parameters:

  • postal_code (String, nil)

Returns:

  • (String, nil)


135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'app/helpers/address_helper.rb', line 135

def normalize_postal_code(postal_code)
  return nil if postal_code.blank?

  code = postal_code.strip.upcase

  if canadian_postal_code?(code)
    code.gsub(/\s+/, '')
  elsif us_zip_code?(code)
    code.gsub('-', '')
  else
    code
  end
end

#parse_address_line(address_line) ⇒ Hash

Parses an address line into city, state, and normalized postal_code

Parameters:

  • address_line (String)

Returns:

  • (Hash)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/helpers/address_helper.rb', line 61

def parse_address_line(address_line)
  return { city: '', state: '', postal_code: '' } if address_line.blank?

  # Remove trailing country name if present
  address_line = address_line.gsub(/\s+(Canada|USA|US)$/i, '').strip

  result = {}
  raw_postal_code = nil

  # Unified regex to match Canadian or US postal codes
  regex = /
    ^\s*
    (?<city>.*?)
    (?:,\s*)?
    (?<state>[A-Z]{2})
    \s+
    (?<postal>
      [A-Z]\d[A-Z]\s*\d[A-Z]\d |
      \d{5}(?:-\d{4})?
    )
    $
  /ix

  if (match = address_line.match(regex))
    raw_city = match[:city]
    result[:city] = clean_city(raw_city)
    result[:state] = match[:state].strip.upcase
    raw_postal_code = match[:postal]
  else
    # Fallback generic splitting
    parts = address_line.split(/\s+/)
    return { city: '', state: '', postal_code: '' } unless parts.size >= 3

    result[:city] = parts[0..-3].join(' ').strip
    result[:state] = parts[-2].strip.upcase
    raw_postal_code = parts[-1]
  end

  # Validate city
  result[:city] = '' if result[:city].blank? || result[:city].match?(/^\d+$/)
  # Validate state
  result[:state] = '' if result[:state].blank? || result[:state].length != 2
  # Validate postal code
  result[:postal_code] = if raw_postal_code.blank? ||
                            !(canadian_postal_code?(raw_postal_code) || us_zip_code?(raw_postal_code))
                           ''
                         else
                           normalize_postal_code(raw_postal_code)
                         end

  result
end

#parse_address_parts(address_hash, type:) ⇒ Hash

Determines the city, state, and postal_code fields with fallback parsing logic. For ShipAddr and BillAddr with different priorities.

Parameters:

  • address_hash (Hash)

    The address data (e.g., invoice or invoice)

  • type (Symbol)

    :ship_to or :bill_to

Returns:

  • (Hash)

    { city:, state:, postal_code: }



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/helpers/address_helper.rb', line 8

def parse_address_parts(address_hash, type:)
  if address_hash.present? &&
     address_hash['City'].present? &&
     address_hash['CountrySubDivisionCode'].present? &&
     address_hash['PostalCode'].present?

    {
      city: address_hash['City'],
      state: address_hash['CountrySubDivisionCode'],
      postal_code: address_hash['PostalCode']
    }
  else
    line1 = address_hash&.dig('Line1')
    line2 = address_hash&.dig('Line2')
    line3 = address_hash&.dig('Line3')
    line4 = address_hash&.dig('Line4')
    line5 = address_hash&.dig('Line5')

    line_to_parse =
      if type == :ship_to
        if line5.present? && line5.match?(/\d/)
          line5
        elsif line4.present? && line4.match?(/\d/)
          line4
        else
          line3
        end
      elsif line4.present? && line4.match?(/\d/) # :bill_to
        line4
      elsif line3.present? && line3.match?(/\d/)
        line3
      elsif line2.blank? && line3.blank? && line4.blank?
        if line1.present? && line1.match?(/\d/)
          line1
        else
          nil
        end
      else
        nil
      end

    if line_to_parse.present?
      parse_address_line(line_to_parse)
    else
      { city: '', state: '', postal_code: '' }
    end
  end
end

#us_zip_code?(code) ⇒ Boolean

Detects US ZIP code format

Parameters:

  • code (String)

Returns:

  • (Boolean)


161
162
163
# File 'app/helpers/address_helper.rb', line 161

def us_zip_code?(code)
  !!(code =~ /^\d{5}(-\d{4})?$/)
end

#valid_city?(city) ⇒ Boolean

Checks if the city value is valid (not blank and not numeric-only)

Parameters:

  • city (String, nil)

Returns:

  • (Boolean)

    true if valid, false otherwise



169
170
171
172
173
# File 'app/helpers/address_helper.rb', line 169

def valid_city?(city)
  return false if city.blank?

  !city.strip.match?(/^\d+$/)
end

#valid_state?(state) ⇒ Boolean

Checks if the state value is valid (not blank and exactly 2 characters)

Parameters:

  • state (String, nil)

Returns:

  • (Boolean)

    true if valid, false otherwise



179
180
181
182
183
# File 'app/helpers/address_helper.rb', line 179

def valid_state?(state)
  return false if state.blank?

  state.strip.length == 2
end