Module: CustomerHierarchyManagement

Extended by:
ActiveSupport::Concern
Includes:
AddressHelper
Included in:
CustomerService, QuickbooksCustomerHelper
Defined in:
app/models/concerns/customer_hierarchy_management.rb

Overview

CustomerHierarchyManagement provides methods for managing customer hierarchy relationships

This concern module is responsible for:

  • Determining if a customer should be a head office (hierarchy 1) or distribution center (hierarchy 2)

  • Creating distribution center customers for different shipping addresses

  • Finding related customers with similar addresses

Customer hierarchy in this system:

  • Hierarchy 1: Head Office - belongs_to_ho_id and belongs_to_dc_id both point to itself

  • Hierarchy 2: Distribution Center - belongs_to_ho_id points to its head office, belongs_to_dc_id points to itself

Example usage:

class CustomerImporter
  include CustomerHierarchyManagement

  def import_customer(customer_data)
    # Create the customer
    customer = Customer.create!(customer_data)

    # Set appropriate hierarchy
    assign_customer_hierarchy(customer, 'QB')

    # Create distribution center if needed
    if shipping_address_different?(customer, shipping_address)
      create_member_customer_for_shipping_address(customer, shipping_address)
    end
  end
end

Instance Method Summary collapse

Methods included from AddressHelper

#canadian_postal_code?, #clean_city, #normalize_postal_code, #parse_address_line, #parse_address_parts, #us_zip_code?, #valid_city?, #valid_state?

Instance Method Details

#assign_customer_hierarchy(customer, suffix_code) ⇒ Boolean

Checks if a customer should be a head office (hierarchy 1) or distribution center (hierarchy 2) and sets appropriate attributes

This method:

  1. Checks if the customer already has a valid hierarchy setup

  2. Looks for similar customers with the same address pattern

  3. Sets hierarchy 2 if similar customers exist (this is a branch location)

  4. Sets hierarchy 1 if no similar customers exist (this is a head office)

Parameters:

  • customer (Customer)

    The customer record to update

  • suffix_code (String)

    The prefix/suffix code used in the customer code

Returns:

  • (Boolean)

    True if hierarchy was set or already valid, false otherwise



47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
# File 'app/models/concerns/customer_hierarchy_management.rb', line 47

def assign_customer_hierarchy(customer, suffix_code)
  return false unless customer&.customer_id

  Rails.logger.info "Checking hierarchy for customer #{customer.customer_id} (#{customer.name}), current hierarchy: #{customer.hierarchy}, belongs_to_ho_id: #{customer.belongs_to_ho_id}, belongs_to_dc_id: #{customer.belongs_to_dc_id}"

  # If customer already has hierarchy 1, preserve it
  if customer.hierarchy == '1' && customer.belongs_to_ho_id.present? && customer.belongs_to_dc_id.present?
    Rails.logger.info "Customer #{customer.customer_id} already has hierarchy 1, preserving it"
    return true
  end

  # If customer already has a complete hierarchy 2 setup, preserve it
  if customer.hierarchy == '2' && customer.belongs_to_ho_id.present? &&
     customer.belongs_to_dc_id.present? && customer.belongs_to_ho_id != customer.customer_id
    Rails.logger.info "Customer #{customer.customer_id} already has hierarchy 2, preserving it"
    return true
  end

  # Check if another customer with the same suffix_code has the same address
  similar_customer = find_customer_with_same_address(customer, suffix_code)

  if similar_customer.present?
    # If a similar customer exists, this is a distribution center (hierarchy 2)
    Rails.logger.info "Found similar customer #{similar_customer.customer_id} with same address. Setting #{customer.customer_id} as hierarchy 2."
    customer.assign_attributes(
      member: true,
      hierarchy: '2',
      belongs_to_dc_id: customer.customer_id,
      belongs_to_ho_id: similar_customer.customer_id
    )
  else
    # If no similar customer exists, this is a head office (hierarchy 1)
    Rails.logger.info "No similar customer found. Setting #{customer.customer_id} as hierarchy 1."
    customer.assign_attributes(
      hierarchy: '1',
      belongs_to_dc_id: customer.customer_id,
      belongs_to_ho_id: customer.customer_id
    )
  end

  # Save updated fields with error handling
  begin
    # Extract all attributes to ensure we don't lose any data
    if respond_to?(:extract_attributes_from_customer)
      attributes = extract_attributes_from_customer(customer)
      system_user = respond_to?(:user_name) ? user_name : 'SYSTEM'
      Customer.create_or_update_procedure(**attributes, system_user: system_user)
    else
      # Fallback to save! if extract_attributes_from_customer is not available
      customer.save!
    end
    true
  rescue StandardError => e
    Rails.logger.error "Failed to save customer with hierarchy: #{e.message}"
    false
  end
end

#billing_shipping_addresses_match?(customer, shipping_address) ⇒ Boolean

Checks if billing and shipping addresses match

Parameters:

  • customer (Customer)

    The customer with billing address

  • shipping_address (Hash)

    The shipping address details

Returns:

  • (Boolean)

    True if addresses match, false otherwise



226
227
228
229
230
231
# File 'app/models/concerns/customer_hierarchy_management.rb', line 226

def billing_shipping_addresses_match?(customer, shipping_address)
  city_match = customer.city.to_s.strip.casecmp?(shipping_address[:city].to_s.strip)
  postal_match = normalize_postal_code(customer.postal_code) == normalize_postal_code(shipping_address[:postal_code])

  city_match && postal_match
end

#create_dc_customer_for_shipping_address(customer, shipping_address) ⇒ Customer?

Creates a distribution center customer when shipping address differs from billing

This method:

  1. Checks if billing and shipping addresses are different

  2. Validates the shipping address

  3. Creates a new customer as a distribution center linked to the original customer

Parameters:

  • customer (Customer)

    The original customer (will be the head office)

  • shipping_address (Hash)

    The shipping address details

Returns:

  • (Customer, nil)

    The created distribution center customer or nil



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'app/models/concerns/customer_hierarchy_management.rb', line 243

def create_dc_customer_for_shipping_address(customer, shipping_address)
  # Only proceed if billing and shipping addresses differ
  return if billing_shipping_addresses_match?(customer, shipping_address)

  # Validate shipping address has required fields
  return unless shipping_address_valid?(shipping_address)

  # Find the original customer
  original_customer = Customer.find_by(customer_id: customer.customer_id)
  return unless original_customer.present?

  # If we have the create_distribution_center method (from CustomerService)
  if respond_to?(:create_distribution_center)
    return create_distribution_center(original_customer, shipping_address)
  end

  # Legacy implementation for backward compatibility
  # Check if a location customer already exists with these shipping details
  existing_location = Customer.find_by(
    belongs_to_ho_id: original_customer.customer_id,
    city: shipping_address[:city],
    postal_code: shipping_address[:postal_code]
  )
  return if existing_location.present?

  # Create a new customer location
  new_customer = original_customer.dup

  # Find province_id, fallback to original customer's province if not found
  province_id = Customer.find_province_by_code(shipping_address[:province_code])
  province_id = original_customer.province_id if province_id.nil?

  new_customer.assign_attributes(
    member: true,
    hierarchy: '2',
    belongs_to_ho_id: original_customer.customer_id,
    code: "#{original_customer.code}-DC",
    name: shipping_address[:company_name] || original_customer.name,
    address_line1: shipping_address[:address_line1],
    address_line2: shipping_address[:address_line2],
    city: shipping_address[:city],
    province_id: province_id,
    postal_code: shipping_address[:postal_code],
    created_by: respond_to?(:user_name) ? user_name : 'SYSTEM',
    last_modified_by: respond_to?(:user_name) ? user_name : 'SYSTEM',
    creation_date: Time.current,
    last_modification_date: Time.current
  )

  # Save the customer
  begin
    if respond_to?(:extract_attributes_from_customer)
      # Use the procedure method to avoid nulling fields
      attributes = extract_attributes_from_customer(new_customer)
      system_user = respond_to?(:user_name) ? user_name : 'SYSTEM'

      # Create the customer with proper hierarchy attributes
      result = Customer.create_or_update_procedure(**attributes, system_user: system_user)
      new_customer = Customer.find_by(customer_id: result[:customer_id])

      # Update the belongs_to_dc_id to the new customer's ID
      if new_customer.present?
        updated_attributes = extract_attributes_from_customer(new_customer).merge(
          belongs_to_dc_id: new_customer.customer_id
        )
        Customer.create_or_update_procedure(**updated_attributes, system_user: system_user)
      end
    else
      # Fallback to save! if extract_attributes_from_customer is not available
      new_customer.save!
      new_customer.update!(belongs_to_dc_id: new_customer.customer_id)
    end
  rescue StandardError => e
    Rails.logger.error "Failed to create DC customer: #{e.message}"
    Rails.logger.error "Shipping address: #{shipping_address.inspect}"
    Rails.logger.error "Province ID: #{province_id}"
    return nil
  end

  new_customer
end

#create_member_customer_for_shipping_address(customer, shipping_address = nil) ⇒ Customer?

Checks if a distribution center customer is needed based on shipping address and creates one if necessary

Parameters:

  • customer (Customer)

    The customer record to check

  • shipping_address (Hash, nil) (defaults to: nil)

    The shipping address details

Returns:

  • (Customer, nil)

    The created member customer or nil



111
112
113
114
115
116
117
118
# File 'app/models/concerns/customer_hierarchy_management.rb', line 111

def create_member_customer_for_shipping_address(customer, shipping_address = nil)
  return unless customer.present?

  # If shipping address is provided, check if it differs from billing
  return unless shipping_address.present?

  create_dc_customer_for_shipping_address(customer, shipping_address)
end

#determine_hierarchy_attributes(customer_id, member_of_a_chain, hierarchy) ⇒ Hash

Determines the correct hierarchy attributes based on the customer type

Parameters:

  • customer_id (Integer)

    The ID of the customer

  • member_of_a_chain (Boolean)

    Whether the customer is part of a chain

  • hierarchy (String)

    The hierarchy code (‘1’ or ‘2’)

Returns:

  • (Hash)

    The hierarchy attributes (belongs_to_ho_id and belongs_to_dc_id)



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'app/models/concerns/customer_hierarchy_management.rb', line 161

def determine_hierarchy_attributes(customer_id, member_of_a_chain, hierarchy)
  return {} unless customer_id

  # First check if customer already exists and has a hierarchy
  customer = Customer.find_by(customer_id: customer_id)
  if customer.present?
    # If customer already has a hierarchy 1 setup, preserve it
    if customer.hierarchy == '1' && customer.belongs_to_ho_id.present? &&
       customer.belongs_to_dc_id.present? && customer.belongs_to_ho_id == customer.customer_id
      Rails.logger.info "determine_hierarchy_attributes: preserving hierarchy 1 for customer #{customer_id}"
      return {
        belongs_to_ho_id: customer.belongs_to_ho_id,
        belongs_to_dc_id: customer.belongs_to_dc_id
      }
    end

    # If customer already has a complete hierarchy 2 setup, preserve it
    if customer.hierarchy == '2' && customer.belongs_to_ho_id.present? &&
       customer.belongs_to_dc_id.present? && customer.belongs_to_ho_id != customer.customer_id
      Rails.logger.info "determine_hierarchy_attributes: preserving hierarchy 2 for customer #{customer_id}"
      return {
        belongs_to_ho_id: customer.belongs_to_ho_id,
        belongs_to_dc_id: customer.belongs_to_dc_id
      }
    end
  end

  updated_belongs_to_ho_id = nil
  updated_belongs_to_dc_id = nil

  if !member_of_a_chain
    updated_belongs_to_ho_id = customer_id
    updated_belongs_to_dc_id = customer_id
  elsif member_of_a_chain
    if hierarchy == '1'
      updated_belongs_to_ho_id = customer_id
      updated_belongs_to_dc_id = customer_id
    elsif hierarchy == '2'
      updated_belongs_to_dc_id = customer_id
    end
  end

  {
    belongs_to_ho_id: updated_belongs_to_ho_id,
    belongs_to_dc_id: updated_belongs_to_dc_id
  }
end

#find_customer_with_same_address(customer, suffix_code) ⇒ Customer?

Finds customers with the same suffix_code prefix and same address

This method helps identify if a customer is related to other customers with the same address pattern.

Parameters:

  • customer (Customer)

    The customer to compare

  • suffix_code (String)

    The prefix/suffix code used in the customer code

Returns:

  • (Customer, nil)

    A similar customer if found, nil otherwise



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/models/concerns/customer_hierarchy_management.rb', line 128

def find_customer_with_same_address(customer, suffix_code)
  # Exclude the current customer to avoid self-comparison
  # Look for customers with the same billing address pattern
  similar_customer = Customer.where(
    'code LIKE ? AND city = ? AND province_id = ? AND postal_code = ? AND customer_id != ?',
    "#{suffix_code}-%",
    customer.city,
    customer.province_id,
    customer.postal_code,
    customer.customer_id
  ).where(hierarchy: '1').first

  if similar_customer.blank?
    # If no hierarchy 1 customer found, try to find any customer with the same address
    similar_customer = Customer.where(
      'code LIKE ? AND city = ? AND province_id = ? AND postal_code = ? AND customer_id != ?',
      "#{suffix_code}-%",
      customer.city,
      customer.province_id,
      customer.postal_code,
      customer.customer_id
    ).first
  end

  similar_customer
end

#shipping_address_valid?(shipping_address) ⇒ Boolean

Validates that a shipping address has the required fields

Parameters:

  • shipping_address (Hash)

    The shipping address details

Returns:

  • (Boolean)

    True if the address is valid, false otherwise



213
214
215
216
217
218
219
# File 'app/models/concerns/customer_hierarchy_management.rb', line 213

def shipping_address_valid?(shipping_address)
  # Ensure required fields are present
  return false if shipping_address[:city].blank?
  return false if shipping_address[:postal_code].blank?

  true
end