Class: Customer::NewCustomer
- Inherits:
-
Object
- Object
- Customer::NewCustomer
- Includes:
- ActiveModel::API, ActiveModel::Attributes, ActiveModel::Validations, ActiveModel::Validations::Callbacks, Heatwave::AttributeNormalizing, StripAttributes
- Defined in:
- app/services/customer/new_customer.rb
Overview
Service object: new customer.
rubocop:disable Metrics/ClassLength -- pre-existing god-class (330+ lines on master); decompose separately
Constant Summary collapse
- REP_ASSIGN_METHODS =
Rep assign methods.
{ aa: 'Auto Assign', sa: 'Self Assign', ma: 'Manually Assign', dna: 'Do Not Assign' }.with_indifferent_access
Instance Attribute Summary collapse
- #cell ⇒ Object readonly
-
#email ⇒ Object
readonly
We always validate these fields no matter what step we're in.
-
#email2 ⇒ Object
readonly
We always validate these fields no matter what step we're in.
- #fax ⇒ Object readonly
- #phone ⇒ Object readonly
- #phone2 ⇒ Object readonly
- #website ⇒ Object readonly
Delegated Instance Attributes collapse
-
#company_id ⇒ Object
Alias for Catalog#company_id.
Instance Method Summary collapse
-
#address_complete? ⇒ Boolean
Bare mininum information we need for a valid address.
-
#allow_disablement_of_address_validation? ⇒ Boolean
Accessor method to determine if we can allow disabling the address validation.
- #assignment_step? ⇒ Boolean
-
#build_address(address) ⇒ Object
Copy over address attributes.
-
#build_and_save_customer(check_for_spam: false) ⇒ Object
Builds, qualify, create lead and save customer.
-
#build_contact_points(party) ⇒ Object
Build the contact points into the party.
-
#build_customer ⇒ Object
Instantiate a new customer translating all attributes from newcustomer.
-
#buying_group ⇒ Object
Accessor method to retrieve buying group.
-
#buying_group_options_for_select ⇒ Object
Retrieves a list of buying available for the customer company.
-
#catalog ⇒ Object
Catalog is based on country in this model.
- #catalog_id ⇒ Object
- #catalog_name ⇒ Object
-
#company_name_parts ⇒ Object
Split company name in parts for highlighting on screen.
- #contact_point_details ⇒ Object
-
#country_must_exist ⇒ Object
build_address looks the Country row up by iso — an unknown value (tampered or legacy form posts) must fail validation here instead of crashing on nil.iso3 later (AppSignal #6410).
- #default_pricing_program ⇒ Object
- #default_profile ⇒ Object
- #find_duplicates ⇒ Object
- #full_address ⇒ Object
- #has_address? ⇒ Boolean
- #identify_step? ⇒ Boolean
- #is_individual? ⇒ Boolean
- #is_organization? ⇒ Boolean
- #is_spam? ⇒ Boolean
-
#name_presence ⇒ Object
Check for name presence and normalize the person name using our name parser e.g Smith, John III will be John Smith III.
- #person_first_name ⇒ Object
- #person_last_name ⇒ Object
-
#person_name_parts ⇒ Object
Split person name in parts for highlighting on screen.
-
#pricing_program_options_for_select ⇒ Object
Used in the final step to determine the pricing offered.
-
#profile ⇒ Object
Accessor method to retrieve profile.
-
#profile_collection_for_select ⇒ Object
List of profiles for selection, we assume an individual is always a homeowner only, otherwise we pull up every trade.
- #rep_assignment_method_name ⇒ Object
- #set_default_email_preferences ⇒ Object
-
#set_email_preferences ⇒ Object
Creates or updates email preferences if an email address was provided.
- #setup_or_assignment_step? ⇒ Boolean
- #setup_step? ⇒ Boolean
-
#split_person_name ⇒ Object
Calls the person name parser to split a name, returns a hash with :first_name, :last_name, :middle_name, :suffix, :prefix.
- #start_in_lead_qualify? ⇒ Boolean
- #website_url_to_base_url ⇒ Object
Methods included from Heatwave::AttributeNormalizing
Instance Attribute Details
#cell ⇒ Object (readonly)
72 |
# File 'app/services/customer/new_customer.rb', line 72 validates :phone, :phone2, :cell, :fax, phone_format: true |
#email ⇒ Object (readonly)
We always validate these fields no matter what step we're in
Validations:
- Email_format
71 |
# File 'app/services/customer/new_customer.rb', line 71 validates :email, :email2, email_format: true |
#email2 ⇒ Object (readonly)
We always validate these fields no matter what step we're in
Validations:
- Email_format
71 |
# File 'app/services/customer/new_customer.rb', line 71 validates :email, :email2, email_format: true |
#fax ⇒ Object (readonly)
72 |
# File 'app/services/customer/new_customer.rb', line 72 validates :phone, :phone2, :cell, :fax, phone_format: true |
#phone ⇒ Object (readonly)
72 |
# File 'app/services/customer/new_customer.rb', line 72 validates :phone, :phone2, :cell, :fax, phone_format: true |
#phone2 ⇒ Object (readonly)
72 |
# File 'app/services/customer/new_customer.rb', line 72 validates :phone, :phone2, :cell, :fax, phone_format: true |
#website ⇒ Object (readonly)
73 |
# File 'app/services/customer/new_customer.rb', line 73 validates :website, url: { allow_nil: true, allow_blank: true } |
Instance Method Details
#address_complete? ⇒ Boolean
Bare mininum information we need for a valid address
339 340 341 |
# File 'app/services/customer/new_customer.rb', line 339 def address_complete? %i[street1 city postal_code country_iso].all? { |f| send(f).presence } end |
#allow_disablement_of_address_validation? ⇒ Boolean
Accessor method to determine if we can allow disabling the address validation
407 408 409 |
# File 'app/services/customer/new_customer.rb', line 407 def allow_disablement_of_address_validation? @address_carrier_error end |
#assignment_step? ⇒ Boolean
108 109 110 |
# File 'app/services/customer/new_customer.rb', line 108 def assignment_step? step == :assignment end |
#build_address(address) ⇒ Object
Copy over address attributes
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'app/services/customer/new_customer.rb', line 307 def build_address(address) address.street1 = street1 address.street2 = street2 address.city = city address.state_code = state_code address.zip = postal_code address.country_iso3 = Country.find_by(iso: country_iso)&.iso3 address.is_residential = residential_address.nil? ? false : residential_address address.lat = lat address.lng = lng address.has_loading_dock = has_loading_dock address.is_construction_site = is_construction_site address.requires_inside_delivery = requires_inside_delivery address.requires_liftgate = requires_liftgate address.limited_access = limited_access address.requires_appointment = requires_appointment address.disable_address_correction = disable_address_correction.nil? ? false : disable_address_correction address end |
#build_and_save_customer(check_for_spam: false) ⇒ Object
Builds, qualify, create lead and save customer
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'app/services/customer/new_customer.rb', line 348 def build_and_save_customer(check_for_spam: false) customer = build_customer if check_for_spam && is_spam? customer.errors.add(:base, 'Spam customer detected') else customer.state = 'lead_qualify' customer.state_event = :complete_qualification unless start_in_lead_qualify? if customer.save # Update the return path self.return_path = return_path && customer.id && return_path.gsub('new_party_id', customer.id.to_s) NewLead::LeadActivityAssigner.new.process(customer, notes: notes) # unless customer.primary_sales_rep_id.nil? # record email preferences set_email_preferences # Broadcast product interest if any customer.broadcast_product_interest_changed(product_line_ids_added: customer.product_line_ids) # Broadcast buying groups if any customer.(buying_group_id_added: customer.) # Queue profile image lookup (logo from Logo.dev's image CDN) PartyProfileImageWorker.perform_async(customer.id) # Cheap domain-keyed brand pull (social profiles + description). # Complements — doesn't replace — the full Apollo/PDL/Gemini run # that fires later when the lead reaches `lead_qualify`: this one # needs nothing but a company domain, so a business lead lands # with its socials already proposed for review. No-ops for # homeowners and webmail-only leads. CurrentScope.without_job_tracking do PartyResearch::AutoEnricher.enqueue(customer, only: %w[logo_dev]) end end end customer end |
#build_contact_points(party) ⇒ Object
Build the contact points into the party
328 329 330 331 332 333 334 335 336 |
# File 'app/services/customer/new_customer.rb', line 328 def build_contact_points(party) party.contact_points.build(detail: phone, category: ContactPoint::PHONE) if phone.present? party.contact_points.build(detail: phone2, category: ContactPoint::PHONE) if phone2.present? party.contact_points.build(detail: cell, category: ContactPoint::CELL) if cell.present? party.contact_points.build(detail: fax, category: ContactPoint::FAX) if fax.present? party.contact_points.build(detail: email, category: ContactPoint::EMAIL) if email.present? party.contact_points.build(detail: email2, category: ContactPoint::EMAIL) if email2.present? party.contact_points.build(detail: website, category: ContactPoint::WEBSITE) if website.present? end |
#build_customer ⇒ Object
Instantiate a new customer translating all attributes from newcustomer
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 |
# File 'app/services/customer/new_customer.rb', line 266 def build_customer cu = Customer.new cu.profile_id = profile_id cu. = cu.source_id = source_id cu.source_info = source_info.presence cu.preferred_language = preferred_language cu.catalog_id = catalog_id cu.tier2_program_pricing_id = tier2_program_pricing_id cu.gclid = gclid.presence # Split the person name, we'll reuse that later pnp = split_person_name if company_name.blank? pnp.to_party(cu) else # If not company, build a person cu.full_name = company_name if pnp.valid? # A contact is present cnt = cu.contacts.build pnp.to_party(cnt) cnt.job_title = job_title end end # Build the contact points into the contact if present otherwise customer build_contact_points(cnt || cu) cu.primary_sales_rep_id = primary_sales_rep_id cu.secondary_sales_rep_id = secondary_sales_rep_id cu.local_sales_rep_id = local_sales_rep_id cu.service_rep_id = service_rep_id cu.do_not_auto_assign = (rep_assignment_method.to_s.to_sym == :dna) cu.creation_method = creation_method # Build the address if we have all the elements if address_complete? a = build_address(cu.addresses.new) cu.shipping_address = cu.billing_address = cu.mailing_address = a end cu.product_line_ids = (product_line_ids || []).compact.uniq cu end |
#buying_group ⇒ Object
Accessor method to retrieve buying group
230 231 232 |
# File 'app/services/customer/new_customer.rb', line 230 def BuyingGroup.find(.presence) if .presence end |
#buying_group_options_for_select ⇒ Object
Retrieves a list of buying available for the customer company
235 236 237 |
# File 'app/services/customer/new_customer.rb', line 235 def BuyingGroup.(company_id: company_id) end |
#catalog ⇒ Object
Catalog is based on country in this model
240 241 242 243 |
# File 'app/services/customer/new_customer.rb', line 240 def catalog # Always return a catalog; default to US when country is not provided Catalog.default_for_country(country_iso) end |
#catalog_id ⇒ Object
245 246 247 |
# File 'app/services/customer/new_customer.rb', line 245 def catalog_id catalog.try(:id) end |
#catalog_name ⇒ Object
249 250 251 |
# File 'app/services/customer/new_customer.rb', line 249 def catalog_name catalog.try(:name) end |
#company_id ⇒ Object
Alias for Catalog#company_id
253 |
# File 'app/services/customer/new_customer.rb', line 253 delegate :company_id, to: :catalog |
#company_name_parts ⇒ Object
Split company name in parts for highlighting on screen
180 181 182 |
# File 'app/services/customer/new_customer.rb', line 180 def company_name_parts company_name.to_s.split(/[ ,.]/).filter_map(&:presence) end |
#contact_point_details ⇒ Object
170 171 172 |
# File 'app/services/customer/new_customer.rb', line 170 def contact_point_details [email, phone, fax].compact.uniq end |
#country_must_exist ⇒ Object
build_address looks the Country row up by iso — an unknown value
(tampered or legacy form posts) must fail validation here instead of
crashing on nil.iso3 later (AppSignal #6410).
138 139 140 141 142 |
# File 'app/services/customer/new_customer.rb', line 138 def country_must_exist return if country_iso.blank? || Country.exists?(iso: country_iso) errors.add(:country_iso, 'is not a supported country') end |
#default_pricing_program ⇒ Object
192 193 194 195 196 197 198 |
# File 'app/services/customer/new_customer.rb', line 192 def default_pricing_program if is_individual? Coupon::Tier2ProgramPricing.msrp_tier2_pricing_coupon else Coupon::Tier2ProgramPricing.customer_default_trade end end |
#default_profile ⇒ Object
200 201 202 203 204 205 206 |
# File 'app/services/customer/new_customer.rb', line 200 def default_profile if is_individual? Profile.homeowner_profile else Profile.unknown_trade end end |
#find_duplicates ⇒ Object
157 158 159 160 |
# File 'app/services/customer/new_customer.rb', line 157 def find_duplicates cf = Query::CustomerFinder.new(attributes) cf.find_duplicates end |
#full_address ⇒ Object
343 344 345 |
# File 'app/services/customer/new_customer.rb', line 343 def full_address %i[street1 city postal_code country_iso].map { |f| send(f).presence }.join(' ') end |
#has_address? ⇒ Boolean
118 119 120 |
# File 'app/services/customer/new_customer.rb', line 118 def has_address? street1.present? end |
#identify_step? ⇒ Boolean
100 101 102 |
# File 'app/services/customer/new_customer.rb', line 100 def identify_step? step == :identify end |
#is_individual? ⇒ Boolean
184 185 186 |
# File 'app/services/customer/new_customer.rb', line 184 def is_individual? company_name.blank? end |
#is_organization? ⇒ Boolean
188 189 190 |
# File 'app/services/customer/new_customer.rb', line 188 def is_organization? company_name.present? end |
#is_spam? ⇒ Boolean
411 412 413 414 415 416 |
# File 'app/services/customer/new_customer.rb', line 411 def is_spam? SpamCheck.new.process( author_email: email.presence, phone_number: phone.presence || phone2.presence || cell.presence || fax.presence ) end |
#name_presence ⇒ Object
Check for name presence and normalize the person name using our name parser
e.g Smith, John III will be John Smith III
124 125 126 127 128 129 130 131 132 133 |
# File 'app/services/customer/new_customer.rb', line 124 def name_presence if person_name.present? pnp = split_person_name self.person_name = pnp.full_name end return if company_name.present? || person_name.present? errors.add :company_name, 'must be specified if no person name present' errors.add :person_name, 'must be specified if no company name present' end |
#person_first_name ⇒ Object
162 163 164 |
# File 'app/services/customer/new_customer.rb', line 162 def person_first_name pnp.first end |
#person_last_name ⇒ Object
166 167 168 |
# File 'app/services/customer/new_customer.rb', line 166 def person_last_name pnp.last end |
#person_name_parts ⇒ Object
Split person name in parts for highlighting on screen
175 176 177 |
# File 'app/services/customer/new_customer.rb', line 175 def person_name_parts person_name.to_s.scan(/(\w+)/).flatten end |
#pricing_program_options_for_select ⇒ Object
Used in the final step to determine the pricing offered
209 210 211 |
# File 'app/services/customer/new_customer.rb', line 209 def Coupon::Tier2ProgramPricing.(build_customer) end |
#profile ⇒ Object
Accessor method to retrieve profile
214 215 216 |
# File 'app/services/customer/new_customer.rb', line 214 def profile Profile.find(profile_id) if profile_id.present? end |
#profile_collection_for_select ⇒ Object
List of profiles for selection, we assume an individual is always a homeowner
only, otherwise we pull up every trade
220 221 222 223 224 225 226 227 |
# File 'app/services/customer/new_customer.rb', line 220 def profile_collection_for_select profiles = if is_individual? Profile.homeowner else Profile.trades end profiles.order(:name).pluck(:name, :id) end |
#rep_assignment_method_name ⇒ Object
96 97 98 |
# File 'app/services/customer/new_customer.rb', line 96 def rep_assignment_method_name REP_ASSIGN_METHODS[rep_assignment_method] end |
#set_default_email_preferences ⇒ Object
255 256 257 258 259 260 261 262 263 |
# File 'app/services/customer/new_customer.rb', line 255 def set_default_email_preferences # If it's for US, subscribe to everything by default # Canada will therefore be unsubscribed by default default_value = company_id == 1 %i[subscribe_to_announcements subscribe_to_events subscribe_to_newsletters subscribe_to_product_review_requests subscribe_to_promotions subscribe_to_webinars].each do |field| public_send(:"#{field}=", default_value) if public_send(field).nil? end end |
#set_email_preferences ⇒ Object
Creates or updates email preferences if an email address was provided
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'app/services/customer/new_customer.rb', line 383 def set_email_preferences return true if email.blank? ep = EmailPreference.find_or_initialize_by(email: email&.downcase) ep.disable_announcements = subscribe_to_announcements != true ep.disable_events = subscribe_to_events != true ep. = != true ep.disable_promotions = subscribe_to_promotions != true ep.disable_webinars = subscribe_to_webinars != true # Default new FR/IT signups to tracking-disabled (CNIL/Garante 2026 rules on # open/click pixels for marketing). country_iso is required, so no fallback # needed. Only set on new records to preserve existing opt-in/opt-out choices. ep.disable_email_tracking = %w[FR IT FRA ITA].include?(country_iso.to_s.upcase) if ep.new_record? ep.save end |
#setup_or_assignment_step? ⇒ Boolean
112 113 114 |
# File 'app/services/customer/new_customer.rb', line 112 def setup_or_assignment_step? (setup_step? or assignment_step?) end |
#setup_step? ⇒ Boolean
104 105 106 |
# File 'app/services/customer/new_customer.rb', line 104 def setup_step? step == :setup end |
#split_person_name ⇒ Object
Calls the person name parser to split a name, returns a hash with :first_name, :last_name, :middle_name, :suffix, :prefix
402 403 404 |
# File 'app/services/customer/new_customer.rb', line 402 def split_person_name PersonNameParser.new(person_name) end |
#start_in_lead_qualify? ⇒ Boolean
116 |
# File 'app/services/customer/new_customer.rb', line 116 def start_in_lead_qualify? = start_in_lead_qualify |
#website_url_to_base_url ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'app/services/customer/new_customer.rb', line 144 def website_url_to_base_url return if website.blank? pv = begin Addressable::URI.parse(website) rescue StandardError nil end return unless pv self.website = "#{pv.scheme || 'https'}://#{pv.host}" end |