Module: Models::PartyContactInfo
- Extended by:
- ActiveSupport::Concern
- Included in:
- Party
- Defined in:
- app/concerns/models/party_contact_info.rb
Overview
Email, phone, fax, and website accessors backed by ContactPoint records.
Instance Method Summary collapse
-
#all_support_cases ⇒ ActiveRecord::Relation<SupportCase>
Returns all support cases this party (or its customer) participates in.
-
#call_records? ⇒ Boolean
(also: #has_call_records?)
Whether the party has any call records.
-
#cell_phone ⇒ String?
The primary cell phone number (raw dial string).
-
#cell_phone=(value) ⇒ ContactPoint?
Sets the primary cell phone contact point.
-
#cell_phone_formatted ⇒ String?
The primary cell phone number formatted for display.
-
#contact_point_options_for_select(category) ⇒ Array<Array(String, Integer)>
Options for a select dropdown of contact points in the given category.
-
#contactable? ⇒ Boolean
Whether the party has any reachable contact point or address.
-
#email ⇒ String?
The primary email address for this party.
-
#email=(value) ⇒ ContactPoint?
Sets the primary email contact point.
-
#email_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of email addresses.
-
#email_with_name ⇒ String
The primary email address formatted with the party's display name.
-
#emails ⇒ Array<String>
All email addresses associated with this party.
-
#enrichable_via_research? ⇒ Boolean
Whether the party has enough starting signal (phone, email, address, or geo-IP location from a tracked visit) for the Lead Enrichment feature to do anything useful.
-
#fax ⇒ String?
The primary fax number (raw dial string).
-
#fax=(value) ⇒ ContactPoint?
Sets the primary fax contact point.
-
#fax_formatted ⇒ String?
The primary fax number formatted for display.
-
#fax_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of fax numbers.
-
#first_callable_contact_point ⇒ ContactPoint?
The first contact point that can receive a voice call.
-
#first_contact_point_by_category(category) ⇒ ContactPoint?
The first contact point matching the given category.
-
#phone ⇒ String?
The primary phone number (raw dial string).
-
#phone=(value) ⇒ ContactPoint?
Sets the primary phone contact point.
-
#phone_formatted ⇒ String?
The primary phone number formatted for display.
-
#phone_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of voice-callable phone numbers.
-
#research_location ⇒ Hash{String => String}?
Best-effort location for enrichment purposes, in priority order: 1.
-
#set_email_from_account ⇒ ContactPoint?
Builds an email contact point from the associated account if one does not already exist.
-
#set_primary_contact_point(category, value) ⇒ ContactPoint?
Builds or updates the primary contact point for the given category.
-
#sms_enabled_numbers ⇒ Array<String>
SMS-enabled numbers formatted for sending.
-
#sms_messages ⇒ ActiveRecord::Relation<SmsMessage>
Messages directly attributed to this party via the denormalized FKs that match_inbound_sender / match_outbound_recipient (and the manual attach-to-party flow) populate at SMS save time.
-
#tracking_email_address ⇒ String
A unique tracking email address that routes back to this party.
-
#tracking_email_prefix ⇒ String
Three-letter prefix used when constructing a tracking email address.
-
#website ⇒ String?
The primary website URL.
-
#website=(value) ⇒ ContactPoint?
Sets the primary website contact point.
-
#website_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of websites.
Instance Method Details
#all_support_cases ⇒ ActiveRecord::Relation<SupportCase>
Returns all support cases this party (or its customer) participates in.
13 14 15 |
# File 'app/concerns/models/party_contact_info.rb', line 13 def all_support_cases SupportCase.joins(support_case_participants: :party).where('parties.id = ? or parties.customer_id = ?', id, id).distinct end |
#call_records? ⇒ Boolean Also known as: has_call_records?
Whether the party has any call records.
245 246 247 |
# File 'app/concerns/models/party_contact_info.rb', line 245 def call_records? CallRecord.party_records(id).present? end |
#cell_phone ⇒ String?
The primary cell phone number (raw dial string).
106 107 108 |
# File 'app/concerns/models/party_contact_info.rb', line 106 def cell_phone first_contact_point_by_category(ContactPoint::CELL)&.dial_string end |
#cell_phone=(value) ⇒ ContactPoint?
Sets the primary cell phone contact point.
121 122 123 |
# File 'app/concerns/models/party_contact_info.rb', line 121 def cell_phone=(value) set_primary_contact_point(ContactPoint::CELL, value) end |
#cell_phone_formatted ⇒ String?
The primary cell phone number formatted for display.
113 114 115 |
# File 'app/concerns/models/party_contact_info.rb', line 113 def cell_phone_formatted first_contact_point_by_category(ContactPoint::CELL)&.formatted_dial_string end |
#contact_point_options_for_select(category) ⇒ Array<Array(String, Integer)>
Options for a select dropdown of contact points in the given category.
279 280 281 282 |
# File 'app/concerns/models/party_contact_info.rb', line 279 def (category) scope = category == :voice_callable ? contact_points.voice_callable : contact_points.by_category(category) scope.map { |cp| [cp.detail.to_s, cp.id] } end |
#contactable? ⇒ Boolean
Whether the party has any reachable contact point or address.
161 162 163 |
# File 'app/concerns/models/party_contact_info.rb', line 161 def contactable? contact_points.contactable.present? || addresses.present? end |
#email ⇒ String?
The primary email address for this party.
Prefers an email attribute that may have been selected onto the record
(e.g. for guest authentication), falling back to the first email contact point.
30 31 32 33 34 |
# File 'app/concerns/models/party_contact_info.rb', line 30 def email # We use attributes email here in case the email was retrieved using a select custom append, # such as when loading guest in authenticable @email ||= attributes[:email] || first_contact_point_by_category(ContactPoint::EMAIL)&.detail end |
#email=(value) ⇒ ContactPoint?
Sets the primary email contact point.
49 50 51 |
# File 'app/concerns/models/party_contact_info.rb', line 49 def email=(value) set_primary_contact_point(ContactPoint::EMAIL, value) end |
#email_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of email addresses.
301 302 303 |
# File 'app/concerns/models/party_contact_info.rb', line 301 def (ContactPoint::EMAIL) end |
#email_with_name ⇒ String
The primary email address formatted with the party's display name.
39 40 41 42 43 |
# File 'app/concerns/models/party_contact_info.rb', line 39 def email_with_name address = Mail::Address.new email address.display_name = name.dup address.format end |
#emails ⇒ Array<String>
All email addresses associated with this party.
20 21 22 |
# File 'app/concerns/models/party_contact_info.rb', line 20 def emails contact_points.emails.reorder(:detail).distinct.pluck(:detail) end |
#enrichable_via_research? ⇒ Boolean
Whether the party has enough starting signal (phone, email, address,
or geo-IP location from a tracked visit) for the Lead Enrichment
feature to do anything useful. Name-only parties can't be enriched
confidently — Apollo would fall back to a fuzzy name search, PDL
has nothing to anchor on, etc.
For a Customer, signal on any of its Contacts also counts (a
Contact's phone is the customer's reach-out number).
175 176 177 178 179 180 181 182 |
# File 'app/concerns/models/party_contact_info.rb', line 175 def enrichable_via_research? return false if respond_to?(:guest?) && guest? return true if contactable? return true if research_location.present? return contacts.any? { |c| c.contactable? || c.research_location.present? } if is_a?(Customer) false end |
#fax ⇒ String?
The primary fax number (raw dial string).
Prefers a primary_fax attribute when one has been selected onto the record.
58 59 60 61 |
# File 'app/concerns/models/party_contact_info.rb', line 58 def fax # primary_fax is sometime selected directly in advanced searches respond_to?(:primary_fax) ? primary_fax : first_contact_point_by_category(ContactPoint::FAX)&.dial_string end |
#fax=(value) ⇒ ContactPoint?
Sets the primary fax contact point.
74 75 76 |
# File 'app/concerns/models/party_contact_info.rb', line 74 def fax=(value) set_primary_contact_point(ContactPoint::FAX, value) end |
#fax_formatted ⇒ String?
The primary fax number formatted for display.
66 67 68 |
# File 'app/concerns/models/party_contact_info.rb', line 66 def fax_formatted first_contact_point_by_category(ContactPoint::FAX)&.formatted_dial_string end |
#fax_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of fax numbers.
294 295 296 |
# File 'app/concerns/models/party_contact_info.rb', line 294 def (ContactPoint::FAX) end |
#first_callable_contact_point ⇒ ContactPoint?
The first contact point that can receive a voice call.
253 254 255 |
# File 'app/concerns/models/party_contact_info.rb', line 253 def first_callable_contact_point contact_points.voice_callable.sorted.first end |
#first_contact_point_by_category(category) ⇒ ContactPoint?
The first contact point matching the given category.
261 262 263 |
# File 'app/concerns/models/party_contact_info.rb', line 261 def first_contact_point_by_category(category) contact_points.sorted.by_category(category).first end |
#phone ⇒ String?
The primary phone number (raw dial string).
Prefers a primary_phone attribute when one has been selected onto the record.
83 84 85 86 |
# File 'app/concerns/models/party_contact_info.rb', line 83 def phone # primary_phone is sometime selected directly in advanced searches first_contact_point_by_category(ContactPoint::PHONE)&.dial_string end |
#phone=(value) ⇒ ContactPoint?
Sets the primary phone contact point.
99 100 101 |
# File 'app/concerns/models/party_contact_info.rb', line 99 def phone=(value) set_primary_contact_point(ContactPoint::PHONE, value) end |
#phone_formatted ⇒ String?
The primary phone number formatted for display.
91 92 93 |
# File 'app/concerns/models/party_contact_info.rb', line 91 def phone_formatted first_contact_point_by_category(ContactPoint::PHONE)&.formatted_dial_string end |
#phone_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of voice-callable phone numbers.
287 288 289 |
# File 'app/concerns/models/party_contact_info.rb', line 287 def (:voice_callable) end |
#research_location ⇒ Hash{String => String}?
Best-effort location for enrichment purposes, in priority order:
- Main billing/shipping/mailing address (street + city + state)
- Most recent tracked visit's geo-IP (city + region + postal_code
- country) — useful even when the party hasn't entered any
address yet
- country) — useful even when the party hasn't entered any
Returns a Hash with string keys (matches the shape adapters consume),
or nil when neither source has anything.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'app/concerns/models/party_contact_info.rb', line 194 def research_location if (addr = main_address) return { 'street_line_1' => addr.street1, 'city' => addr.city, 'state_code' => addr.state_code, 'postal_code' => addr.zip, 'country_code' => addr.country_iso3, 'source' => 'address' }.compact end visit = visits.order(started_at: :desc).limit(1).first return nil unless visit && (visit.city.present? || visit.region.present? || visit.postal_code.present?) # Shape MUST match the address branch (state_code + country_code) # so adapters can consume `loc['state_code']` and # `loc['country_code']` uniformly regardless of source. { 'city' => visit.city, 'state_code' => visit.state_code, 'postal_code' => visit.postal_code, 'country_code' => visit.country_iso3, 'source' => 'visit' }.compact end |
#set_email_from_account ⇒ ContactPoint?
Builds an email contact point from the associated account if one does not already exist.
268 269 270 271 272 273 |
# File 'app/concerns/models/party_contact_info.rb', line 268 def set_email_from_account return unless account && account.email.present? return if contact_points.any? { |cp| cp.email? && cp.detail == account.email } contact_points.build(category: ContactPoint::EMAIL, detail: account.email) end |
#set_primary_contact_point(category, value) ⇒ ContactPoint?
Builds or updates the primary contact point for the given category.
317 318 319 320 321 |
# File 'app/concerns/models/party_contact_info.rb', line 317 def set_primary_contact_point(category, value) cp = ContactPoint.build_from_string(value, contact_points, category) cp.move_to_top if cp&.persisted? cp end |
#sms_enabled_numbers ⇒ Array<String>
SMS-enabled numbers formatted for sending.
224 225 226 |
# File 'app/concerns/models/party_contact_info.rb', line 224 def sms_enabled_numbers contact_points.sms_numbers.order(:detail).map(&:formatted_for_sms).uniq end |
#sms_messages ⇒ ActiveRecord::Relation<SmsMessage>
Messages directly attributed to this party via the denormalized FKs that
match_inbound_sender / match_outbound_recipient (and the manual attach-to-party
flow) populate at SMS save time. Decoupled from contact_points.sms_status:
a number's sms_status is a sender-routing concern, not a visibility filter on
conversations that already happened (AppSignal trace re David Grégoire,
2026-05-06: contact_point was sms_none for several days while real messages
piled up under correct sender_party_id/recipient_party_id, leaving the SMS
tab empty until an outbound attempt flipped the flag).
238 239 240 |
# File 'app/concerns/models/party_contact_info.rb', line 238 def SmsMessage.where('sender_party_id = :id OR recipient_party_id = :id', id: id) end |
#tracking_email_address ⇒ String
A unique tracking email address that routes back to this party.
145 146 147 148 149 |
# File 'app/concerns/models/party_contact_info.rb', line 145 def tracking_email_address domain = Rails.application.config.x.email_domain encrypted_id = Encryption.encrypt_string(id.to_s) "#{tracking_email_prefix}+id#{encrypted_id}@#{domain}" end |
#tracking_email_prefix ⇒ String
Three-letter prefix used when constructing a tracking email address.
154 155 156 |
# File 'app/concerns/models/party_contact_info.rb', line 154 def tracking_email_prefix type.to_s.downcase[0, 3] end |
#website ⇒ String?
The primary website URL.
128 129 130 131 132 |
# File 'app/concerns/models/party_contact_info.rb', line 128 def website first_contact_point_by_category(ContactPoint::WEBSITE).detail rescue StandardError nil end |
#website=(value) ⇒ ContactPoint?
Sets the primary website contact point.
138 139 140 |
# File 'app/concerns/models/party_contact_info.rb', line 138 def website=(value) set_primary_contact_point(ContactPoint::WEBSITE, value) end |
#website_options_for_select ⇒ Array<Array(String, Integer)>
Options for a select dropdown of websites.
308 309 310 |
# File 'app/concerns/models/party_contact_info.rb', line 308 def (ContactPoint::WEBSITE) end |