Class: Lead

Inherits:
BaseFormObject show all
Defined in:
app/forms/lead.rb

Defined Under Namespace

Classes: MidAtlanticScrapper, NuheatDealerScrapper

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseFormObject

#attributes, #initialize, #persisted?

Constructor Details

This class inherits a constructor from BaseFormObject

Instance Attribute Details

#profile_idObject (readonly)



90
# File 'app/forms/lead.rb', line 90

validates :profile_id, inclusion: { in: Profile.all.map(&:id), message: 'is not a valid profile', allow_nil: true }

Class Method Details

.application_typeObject



92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/forms/lead.rb', line 92

def self.application_type
  [
    'Floor Heating',
    'Snow Melting',
    'Roof & Gutter Deicing',
    'Pipe Freeze Protection',
    'Towel Warmers',
    'Radiant Panels',
    'Mirrors & Defoggers',
    'Countertop Heating'
  ]
end

.floor_type_indoorObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/forms/lead.rb', line 134

def self.floor_type_indoor
  [
    'Tile, Marble or Stone',
    'Bamboo (Floating)',
    'Bamboo (Glued)',
    'Carpet (Glued)',
    'Carpet (Stretch In)',
    'Cork',
    'Engineered Wood (Floating)',
    'Engineered Wood (Glued)',
    'Engineered Wood (Nailed)',
    'Hard Wood (Glued)',
    'Hard Wood (Nailed)',
    'Laminate Wood (Click Together Floating)',
    'Laminate Wood (Glued Together Floating)',
    'Laminate Wood (Nailed)',
    'Resilients, Vinyl and Luxury Vinyl Tile (LVT)',
    'Other'
  ]
end

.floor_type_outdoorObject



163
164
165
166
167
168
169
# File 'app/forms/lead.rb', line 163

def self.floor_type_outdoor
  %w[
    Asphalt
    Concrete
    Pavers
  ]
end

.project_statusObject



105
106
107
108
109
110
111
# File 'app/forms/lead.rb', line 105

def self.project_status
  [
    'Looking for inspiration',
    'I am planning and budgeting',
    'Ready to buy - I need some support'
  ]
end

.room_type_indoorObject



113
114
115
116
117
118
119
120
121
122
# File 'app/forms/lead.rb', line 113

def self.room_type_indoor
  [
    'Bathroom',
    'Kitchen',
    'Bedroom',
    'Living Room',
    'Basement',
    'Other'
  ]
end

.room_type_outdoorObject



124
125
126
127
128
129
130
131
132
# File 'app/forms/lead.rb', line 124

def self.room_type_outdoor
  %w[
    Driveway
    Walkway
    Stairs
    Patio
    Other
  ]
end

.subfloor_type_indoorObject



155
156
157
158
159
160
161
# File 'app/forms/lead.rb', line 155

def self.subfloor_type_indoor
  [
    'Existing Concrete Slab',
    'Newly Poured Indoor Concrete Slab',
    'Wood'
  ]
end

Instance Method Details

#create_lead_activity(party) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/forms/lead.rb', line 196

def create_lead_activity(party)
  # Going to do in two steps, first create teh uploads, this will allow us
  # to recover them in case of failure
  upload_ids = []
  %i[sketch_drawing_1 sketch_drawing_2 sketch_drawing_3].each do |dp|
    next unless attributes[dp].present? && attributes[dp].respond_to?(:path)

    upload_ids << Upload.uploadify(attributes[dp].path, 'room_layout').id
  end

  # Include any pre-uploaded files provided via hidden field (Uppy)
  if attributes[:sketch_drawings].present?
    begin
      ids = JSON.parse(attributes[:sketch_drawings].to_s)
      upload_ids.concat(Array(ids).compact.map(&:to_i)) if ids.present?
    rescue StandardError
      # ignore parse errors
    end
  end

  self.activity_type_id ||= ActivityTypeConstants::LEAD_FORM
  activity = party.activities.create(
    party:,
    activity_type_id:,
    target_datetime: 1.working.hour.from_now,
    lock_target_datetime: true,
    new_note: to_note
  )

  # Associate uploads after activity creation to avoid triggering Dragonfly file access
  if activity.persisted? && upload_ids.present?
    Upload.where(id: upload_ids).update_all(
      resource_type: 'Activity',
      resource_id: activity.id,
      updated_at: Time.current
    )
  end

  activity
end

#create_lead_support_case(party) ⇒ Object



237
238
239
240
241
242
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
# File 'app/forms/lead.rb', line 237

def create_lead_support_case(party)
  # Going to do in two steps, first create teh uploads, this will allow us
  # to recover them in case of failure
  upload_ids = []
  %i[sketch_drawing_1 sketch_drawing_2 sketch_drawing_3].each do |dp|
    next unless attributes[dp].present?

    upload_ids << Upload.uploadify(attributes[dp].path, 'room_layout').id
  end

  # Include any pre-uploaded files provided via hidden field (Uppy)
  if attributes[:sketch_drawings].present?
    begin
      ids = JSON.parse(attributes[:sketch_drawings].to_s)
      upload_ids.concat(Array(ids).compact.map(&:to_i)) if ids.present?
    rescue StandardError
      # ignore parse errors
    end
  end

  a = SupportCase.new(
    description: to_note,
    priority: 'Low',
    case_type: 'Tech'
  )
  a.build_participants_and_rooms(participant_id: party.id)
  
  if a.save && upload_ids.present?
    # Associate uploads after support case creation to avoid triggering Dragonfly file access
    Upload.where(id: upload_ids).update_all(
      resource_type: 'SupportCase',
      resource_id: a.id,
      updated_at: Time.current
    )
  end

  a
end

#from_party(party) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/forms/lead.rb', line 177

def from_party(party)
  if party
    if (org = party.customer)&.is_organization?
      self.company_name = org.full_name unless org.has_guest_name?
      self.profile_id = org.profile_id
      if party.is_person? # Means they're logged in as a contact of that customer
        self.name = party.full_name unless party.has_guest_name?
      elsif (first_contact = org.contacts.active.first) # Use the first contact's full name
        self.name = first_contact.full_name unless first_contact.has_guest_name?
      end
    elsif !party.has_guest_name?
      self.name = party.full_name
    end
    self.email = party.email
    self.phone = party.phone
  end
  self
end

#save_to_user(customer = nil, send_invite = false) ⇒ Object

:reek:ControlParameter

Raises:

  • (StandardError)


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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'app/forms/lead.rb', line 276

def save_to_user(customer = nil, send_invite = false) # :reek:ControlParameter
  if customer.nil?
    country_iso = PhoneNumber.new(phone)&.country_iso if phone.present?
    catalog = Catalog.default_for_country(country_iso) if country_iso
    catalog ||= Catalog.locale_to_catalog(preferred_locale) if preferred_locale.present?
    catalog ||= Catalog.us_catalog
    customer = Customer.new(state: 'lead_qualify', catalog:)
  end

  raise StandardError.new('Party is not a customer.') unless customer.is_a?(Customer)

  result = OpenStruct.new(success: false, message: nil, support_case_id: nil)
  # If our form does not pass basic validation then return immediately
  unless valid?
    msg = errors_to_s
    Rails.logger.error "Lead form failed validation: #{msg}.  Lead data: #{inspect}"
    result.success = false
    result.message = msg
    return result
  end
  # When customer is a guest or still in lead qualify, then we take the form data and store
  if customer.guest? || customer.customer.lead_qualify?
    cu = customer
    cu.state = 'lead_qualify'
    if company_name.present?
      cu.profile_id = profile_id.presence || ProfileConstants::UNKNOWN_TRADE
      cu.clear_names # Reset, we're rebuilding as a company
      cu.full_name = company_name
      unless cu.save # Save before building the contact, if this fails return right away
        msg = cu.errors_to_s
        Rails.logger.error "Lead form error saving customer: #{msg}.  Lead data: #{inspect}"
        result.message = msg
        errors.add(:base, result.message)
        return result
      end
      contact = cu.contacts.active.where(Party[:full_name].matches(name)).first
      contact ||= cu.contacts.new(full_name: 'Unknown')
    else # This is a homeowner, or we presume it is, therefore the contact is the customer itself
      cu.profile_id = profile_id.presence || ProfileConstants::HOMEOWNER
      cu.full_name = name
      contact = cu # Contact is same as homeowner which will be saved later on
    end
  # When not a guest, and a company, we create a contact under that customer.  But we don't modify name on the customer
  elsif customer.is_organization?
    # Try to find an existing contact of the same name
    contact = customer.contacts.active.where(Party[:full_name].matches(name)).first
    # Just initialize a new one
    contact ||= customer.contacts.new(full_name: 'Unknown')
  else # homeowner
    contact = customer
  end
  customer.gclid = gclid
  customer.source_id = source_id if source_id.present? && source_id != Source::UNKNOWN_ID && (customer.source_id.nil? || customer.source_id == Source::UNKNOWN_ID)

  # Contacts being people, we try to parse the name of the person and store it in the party model
  if name.present?
    pnp = PersonNameParser.new(name)
    pnp.to_party(contact)
  end
  # Store contact info but only if its valid, otherwise it will just be stored in the form data in the activity
  # We do this to still capture the lead even if we have bad data
  contact.phone = phone if phone.present? && PhoneNumber.valid?(phone)
  contact.email = email if email.present? && (email =~ RFC822::EMAIL)
  contact.preferred_language = 'Spanish' if preferred_locale == 'es'

  if contact.save
    # Now that the contact info is saved, create the lead activity
    if skip_activity && create_support_case && (a = create_lead_support_case(contact)).persisted?
      result.success = true
      result.support_case_reference = a.case_number
      # invite customer to create an account
      contact.customer.(email, ignore_if_exist: true) if email.present? && send_invite
    elsif (a = create_lead_activity(contact)).persisted?
      result.success = true
      # invite customer to create an account
      contact.customer.(email, ignore_if_exist: true) if email.present? && send_invite
    else
      result.success = false
      msg = a.errors_to_s
      result.message = msg
      Rails.logger.error "Lead form error saving lead activity: #{msg}.  Lead data: #{inspect}"
      errors.add(:base, result.message)
    end
  else
    msg = contact.errors_to_s
    result.message = msg
    Rails.logger.error "Lead form error saving contact: #{msg}.  Lead data: #{inspect}"
    errors.add(:base, result.message)
  end
  result
end

#to_noteObject



171
172
173
174
175
# File 'app/forms/lead.rb', line 171

def to_note
  note_attributes = attributes.reject { |k, _v| k.in?(%i[gclid source_id sketch_drawing_1 sketch_drawing_2 sketch_drawing_3 activity_type_id profile_id opportunity_id]) }
  note_attributes = note_attributes.select { |_k, v| v.present? }
  note_attributes.map { |k, v| "#{k.to_s.titleize}: #{v}" }.join("\n\r")
end

#validate_phone_or_emailObject



368
369
370
371
372
# File 'app/forms/lead.rb', line 368

def validate_phone_or_email
  return if phone.present? || (email.present? && Truemail.valid?(email))

  errors.add(:base, 'Please provide either a phone or email so we can answer your question')
end