Class: Party

Inherits:
ApplicationRecord show all
Includes:
ActionView::Helpers::NumberHelper, CrmLinkable, Models::Auditable, Models::LiquidMethods, Models::Notable, PartyAccount, PartyAddresses, PartyCart, PartyContactInfo, PartyIdentity, PartySearch, PgSearch::Model
Defined in:
app/models/party.rb

Overview

Single-table inheritance (STI) base for every CRM party row in parties
(customers, contacts, employees, suppliers, buying-group rows, and similar roles).

Shared persistence (addresses, reps, consent JSON, catalog links) lives on this model;
subtype behaviour is composed through subclasses and Party*, Customer*, and Contact*
concerns.

Direct Known Subclasses

Contact, Customer, Employee, GroupAssociation, Supplier

Defined Under Namespace

Classes: ExistingAccountError

Constant Summary collapse

PREFERRED_LANGUAGES =

Preferred languages.

%w[French Spanish].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Has and belongs to many collapse

Delegated Instance Attributes collapse

Instance Method Summary collapse

Methods included from CrmLinkable

#crm_link, #crm_link_with_host

Methods included from PartyAccount

#ability, #build_account, #create_account, #online_account_invite, #refuse_silent_account_replacement!, search_for_authenticable_linked_party_by_email

Methods included from PartyCart

#all_uploads, #assign_chained_activities?, #cart, #cart=, #cart?, #cart_id, #cart_info, #create_activity, #create_quote_builder_project, #load_cart, #store_original_source

Methods included from PartyIdentity

#active?, #admin?, #aka, #aka=, #all_activities, #blog_admin?, #broadcast_product_interest_changed, #can_be_certified?, #can_list_all_contact_resources?, #clear_individual_names, #clear_names, #contact?, #customer?, #determine_company_id, #direct_commercial?, #direct_pro?, #employee?, #first_name, #first_name=, #guest_individual_name?, #guest_name?, #homeowner?, #id_and_name, #last_name, #last_name=, #manager?, #middle_name, #middle_name=, #name, #name=, #organization?, #person?, #primary_party, #public_facing_full_name, #public_facing_full_name=, #show_name_four, #to_s, #update_product_interests

Methods included from PartyAddresses

#addresses_options_for_select, #can_change_country?, #catalog_country, #catalog_country_iso, #catalog_country_iso3, #city, #country, #country_iso, #country_iso3, #first_address, #flag, #location_name, #most_recent_visit, #new_address, #street1, #street2, #street3, #timezone, #unique_addresses, #visit_data, #visit_location, #zip

Methods included from PartyContactInfo

#all_support_cases, #call_records?, #cell_phone, #cell_phone=, #cell_phone_formatted, #contact_point_options_for_select, #contactable?, #email, #email=, #email_options_for_select, #email_with_name, #emails, #enrichable_via_research?, #fax, #fax=, #fax_formatted, #fax_options_for_select, #first_callable_contact_point, #first_contact_point_by_category, #phone, #phone=, #phone_formatted, #phone_options_for_select, #research_location, #set_email_from_account, #set_primary_contact_point, #sms_enabled_numbers, #sms_messages, #tracking_email_address, #tracking_email_prefix, #website, #website=, #website_options_for_select

Methods included from PartySearch

active, address_search, customer_or_contact, email_search, inactive, lookup, phone_search, ransackable_scopes, searchable, with_main_address

Methods included from Models::Notable

#quick_note

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#creation_methodObject (readonly)

The pre-Rails-7 normalizr config had normalize :profile, ... — a typo
that targeted no real column (Party has profile_id and profile_info,
not profile) and was a silent no-op. Under Rails native normalizes
it became harmful: registering on :profile creates a real virtual
attribute that collides with the belongs_to :profile setter and gets
nullified during attributes= mass-assignment, wiping profile_id.
profile_info is a free-text Bio field, not a hash, so the original
[strip, blank, hash_compactor] chain would also corrupt its value.
Drop the normalize entirely.

Validations:



315
# File 'app/models/party.rb', line 315

validates :creation_method, inclusion: { in: creation_methods.keys }

Instance Method Details

#accountAccount

NOTE: this is a documentation-only association used as a convenience
accessor. Use accounts (has_many below) for collection access. We keep
dependent: :destroy so that destroying a party still cleans up its sole
account row; the truly dangerous primitive (auto-destroying an existing
account when assigning a new one) is intercepted by the build_account
override below.

Returns:

See Also:



242
# File 'app/models/party.rb', line 242

has_one :account, dependent: :destroy

#accountsActiveRecord::Relation<Account>

Returns:

  • (ActiveRecord::Relation<Account>)

See Also:



250
# File 'app/models/party.rb', line 250

has_many :accounts, dependent: :destroy

#activitiesActiveRecord::Relation<Activity>

rubocop:enable Rails/InverseOf

Returns:

See Also:



254
# File 'app/models/party.rb', line 254

has_many :activities, dependent: :destroy

#addressesActiveRecord::Relation<Address>

Returns:

  • (ActiveRecord::Relation<Address>)

See Also:



243
244
245
246
247
248
249
# File 'app/models/party.rb', line 243

has_many :addresses, dependent: :nullify, inverse_of: :party do
  def main_address
    find_by(id: [proxy_association.owner.mailing_address_id,
                 proxy_association.owner.billing_address_id,
                 proxy_association.owner.shipping_address_id]) || first
  end
end

#agreement_participantsActiveRecord::Relation<AgreementParticipant>

Returns:

See Also:



285
# File 'app/models/party.rb', line 285

has_many :agreement_participants, dependent: :destroy

#agreementsActiveRecord::Relation<Agreement>

Returns:

See Also:



286
# File 'app/models/party.rb', line 286

has_many :agreements, through: :agreement_participants

#assistant_conversationsActiveRecord::Relation<AssistantConversation>

Returns:

See Also:



291
# File 'app/models/party.rb', line 291

has_many :assistant_conversations, foreign_key: :user_id, dependent: :destroy, inverse_of: :user

#billing_credit_memosActiveRecord::Relation<CreditMemo>

Returns:

See Also:



274
275
# File 'app/models/party.rb', line 274

has_many :billing_credit_memos, class_name: 'CreditMemo', foreign_key: 'billing_customer_id', dependent: :nullify,
inverse_of: :billing_customer

#can?Object

Alias for Ability#can?

Returns:

  • (Object)

    Ability#can?

See Also:



300
# File 'app/models/party.rb', line 300

delegate :can?, :cannot?, to: :ability

#cannot?Object

Alias for Ability#cannot?

Returns:

  • (Object)

    Ability#cannot?

See Also:



300
# File 'app/models/party.rb', line 300

delegate :can?, :cannot?, to: :ability

#catalogCatalog

Returns:

See Also:



229
# File 'app/models/party.rb', line 229

belongs_to :catalog, optional: true

#companyCompany

Returns:

See Also:

Validations:



230
# File 'app/models/party.rb', line 230

belongs_to :company, optional: true

#consignment_storesActiveRecord::Relation<Store>

Returns:

  • (ActiveRecord::Relation<Store>)

See Also:



261
# File 'app/models/party.rb', line 261

has_many :consignment_stores, class_name: 'Store', foreign_key: :consignee_party_id, dependent: :nullify, inverse_of: :consignee_party

#contact_pointsActiveRecord::Relation<ContactPoint>

-- Contact shares customer_id FK with supplier; see Contact model

Returns:

See Also:



251
# File 'app/models/party.rb', line 251

has_many :contact_points, -> { order(:position) }, dependent: :nullify, inverse_of: :party

#contactsActiveRecord::Relation<Contact>

Returns:

  • (ActiveRecord::Relation<Contact>)

See Also:

Validations (if => #must_have_one_contact ):



252
# File 'app/models/party.rb', line 252

has_many :contacts, foreign_key: 'customer_id', dependent: :destroy

#course_enrollmentsActiveRecord::Relation<CourseEnrollment>

Returns:

See Also:



282
# File 'app/models/party.rb', line 282

has_many :course_enrollments, dependent: :destroy, inverse_of: :party

#credit_memosActiveRecord::Relation<CreditMemo>

owner of the credit memo

Returns:

See Also:



273
# File 'app/models/party.rb', line 273

has_many :credit_memos, foreign_key: 'customer_id', dependent: :nullify, inverse_of: :customer

#customerCustomer

WHEN CREATING A NEW ASSOCIATION FOR YOUR PARTY, CUSTOMER OR CONTACT
DO NOT FORGET TO HANDLE THEM IN MERGE OPERATIONS
IMPLEMENT THIS IN customer_merger.rb or contact_merger.rb

Returns:

See Also:



228
# File 'app/models/party.rb', line 228

belongs_to :customer, optional: true

#destination_call_logsActiveRecord::Relation<CallLog>

Returns:

  • (ActiveRecord::Relation<CallLog>)

See Also:



259
# File 'app/models/party.rb', line 259

has_many :destination_call_logs, class_name: 'CallLog', foreign_key: 'to_party_id', dependent: :nullify, inverse_of: :to_party

#destination_call_recordsActiveRecord::Relation<CallRecord>

Returns:

See Also:



256
257
# File 'app/models/party.rb', line 256

has_many :destination_call_records, class_name: 'CallRecord', foreign_key: 'destination_party_id', dependent: :nullify,
inverse_of: :destination_party

#digital_assetsActiveRecord::Relation<DigitalAsset>

Returns:

See Also:



297
# File 'app/models/party.rb', line 297

has_and_belongs_to_many :digital_assets

#gl_offset_accountLedgerAccount



232
# File 'app/models/party.rb', line 232

belongs_to :gl_offset_account, class_name: 'LedgerAccount', optional: true

#has_role?Object

Alias for Account#has_role?

Returns:

  • (Object)

    Account#has_role?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#inbound_communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



265
266
# File 'app/models/party.rb', line 265

has_many :inbound_communications, class_name: 'Communication', foreign_key: 'recipient_party_id', dependent: :nullify,
inverse_of: :recipient_party

#is_admin?Object

Alias for Account#is_admin?

Returns:

  • (Object)

    Account#is_admin?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_document_maintainer?Object

Alias for Account#is_document_maintainer?

Returns:

  • (Object)

    Account#is_document_maintainer?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_local_sales_rep?Object

Alias for Account#is_local_sales_rep?

Returns:

  • (Object)

    Account#is_local_sales_rep?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_manager?Object

Alias for Account#is_manager?

Returns:

  • (Object)

    Account#is_manager?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_marketing_manager?Object

Alias for Account#is_marketing_manager?

Returns:

  • (Object)

    Account#is_marketing_manager?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_sales_manager?Object

Alias for Account#is_sales_manager?

Returns:

  • (Object)

    Account#is_sales_manager?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#is_sales_rep?Object

Alias for Account#is_sales_rep?

Returns:

  • (Object)

    Account#is_sales_rep?

See Also:



301
# File 'app/models/party.rb', line 301

delegate :has_role?, :is_admin?, :is_manager?, :is_sales_manager?, :is_sales_rep?, :is_local_sales_rep?, :is_marketing_manager?, :is_document_maintainer?, to: :account

#linked_activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



296
# File 'app/models/party.rb', line 296

has_and_belongs_to_many :linked_activities, class_name: 'Activity'

#localeObject

Alias for I18n#locale

Returns:

  • (Object)

    I18n#locale

See Also:



330
# File 'app/models/party.rb', line 330

delegate :locale, to: :I18n

#main_addressObject

Alias for Addresses#main_address

Returns:

  • (Object)

    Addresses#main_address

See Also:



302
# File 'app/models/party.rb', line 302

delegate :main_address, to: :addresses

#notificationsActiveRecord::Relation<Noticed::Notification>

Returns:

  • (ActiveRecord::Relation<Noticed::Notification>)

See Also:



292
# File 'app/models/party.rb', line 292

has_many :notifications, as: :recipient, dependent: :destroy, class_name: 'Noticed::Notification'

#opportunity_participantsActiveRecord::Relation<OpportunityParticipant>

Returns:

See Also:



280
# File 'app/models/party.rb', line 280

has_many :opportunity_participants, inverse_of: :party, dependent: :destroy

#origin_call_logsActiveRecord::Relation<CallLog>

Returns:

  • (ActiveRecord::Relation<CallLog>)

See Also:



258
# File 'app/models/party.rb', line 258

has_many :origin_call_logs, class_name: 'CallLog', foreign_key: 'from_party_id', dependent: :nullify, inverse_of: :from_party

#origin_call_recordsActiveRecord::Relation<CallRecord>

Returns:

See Also:



255
# File 'app/models/party.rb', line 255

has_many :origin_call_records, class_name: 'CallRecord', foreign_key: 'origin_party_id', dependent: :nullify, inverse_of: :origin_party

#outbound_communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



263
264
# File 'app/models/party.rb', line 263

has_many :outbound_communications, class_name: 'Communication', foreign_key: 'sender_party_id', dependent: :nullify,
inverse_of: :sender_party

#outgoing_paymentsActiveRecord::Relation<OutgoingPayment>

Returns:

See Also:



269
# File 'app/models/party.rb', line 269

has_many :outgoing_payments, foreign_key: 'supplier_id', dependent: :nullify, inverse_of: :supplier

#party_topicsActiveRecord::Relation<PartyTopic>

Returns:

See Also:



277
# File 'app/models/party.rb', line 277

has_many :party_topics, inverse_of: :party, dependent: :destroy

#product_linesActiveRecord::Relation<ProductLine>

-- legacy join tables; migrating to has_many :through is a separate effort

Returns:

See Also:



295
# File 'app/models/party.rb', line 295

has_and_belongs_to_many :product_lines

#profile_imageImage

Returns:

See Also:



233
# File 'app/models/party.rb', line 233

belongs_to :profile_image, class_name: 'Image', dependent: :destroy, optional: true

#purchase_ordersActiveRecord::Relation<PurchaseOrder>

Returns:

See Also:



276
# File 'app/models/party.rb', line 276

has_many :purchase_orders, foreign_key: :supplier_id, dependent: :nullify, inverse_of: :supplier

#queue_call_logsActiveRecord::Relation<QueueCallLog>

Returns:

See Also:



260
# File 'app/models/party.rb', line 260

has_many :queue_call_logs, class_name: 'QueueCallLog', foreign_key: 'caller_party_id', dependent: :nullify, inverse_of: :caller_party

#receiptsActiveRecord::Relation<Receipt>

Returns:

  • (ActiveRecord::Relation<Receipt>)

See Also:



271
# File 'app/models/party.rb', line 271

has_many :receipts, foreign_key: 'customer_id', dependent: :nullify, inverse_of: :customer

#recipient_sms_messagesActiveRecord::Relation<SmsMessage>

Returns:

See Also:



289
290
# File 'app/models/party.rb', line 289

has_many :recipient_sms_messages, class_name: 'SmsMessage', foreign_key: :recipient_party_id, dependent: :nullify,
inverse_of: :recipient_party

#room_plansActiveRecord::Relation<RoomPlan>

Returns:

See Also:



281
# File 'app/models/party.rb', line 281

has_many :room_plans, inverse_of: :party, dependent: :destroy

#sender_sms_messagesActiveRecord::Relation<SmsMessage>

Returns:

See Also:



287
288
# File 'app/models/party.rb', line 287

has_many :sender_sms_messages, class_name: 'SmsMessage', foreign_key: :sender_party_id, dependent: :nullify,
inverse_of: :sender_party

#set_full_nameObject (protected)

Before-save callback: rebuild the denormalised full_name column
from the party-type-specific name fields (first/last for people,
company name for organizations). Preserves any externally-supplied
value if PartyIdentity#apply_full_name_for_party_type! couldn't compose one.



357
358
359
360
361
# File 'app/models/party.rb', line 357

protected def set_full_name
  existing_full_name = full_name
  apply_full_name_for_party_type!(existing_full_name)
  self.full_name ||= existing_full_name
end

#set_state_codeObject (protected)

Validation/before-save callback that pins state_code to the main
address's state_code, falling back to whatever Cloudflare visit
data was captured at landing time. Letting the column denormalise
state means CRM searches and reports can filter by state without
joining addresses.



337
338
339
340
# File 'app/models/party.rb', line 337

protected def set_state_code
  self.state_code = main_address&.state_code
  self.state_code ||= visit_data(:state_code)
end

#set_timezoneObject (protected)

Before-save callback: lazily resolve and persist the party's IANA
timezone once we have enough info to do so. Skipped for guests
(cart-only sessions) and any party that already has a timezone
set, so we don't re-run the geocode lookup on every save.



346
347
348
349
350
351
# File 'app/models/party.rb', line 346

protected def set_timezone
  return if timezone_name.present?
  return if state == 'guest' # Ignore guests

  PartyRecordTimezone.new.process(self, save_directly: true)
end

#storeStore

Returns:

See Also:



235
# File 'app/models/party.rb', line 235

has_one :store, through: :catalog

#support_case_participantsActiveRecord::Relation<SupportCaseParticipant>

Returns:

See Also:



267
# File 'app/models/party.rb', line 267

has_many :support_case_participants, dependent: :destroy

#support_casesActiveRecord::Relation<SupportCase>

Returns:

See Also:



268
# File 'app/models/party.rb', line 268

has_many :support_cases, through: :support_case_participants

#survey_enrollmentsActiveRecord::Relation<SurveyEnrollment>

Returns:

See Also:



283
# File 'app/models/party.rb', line 283

has_many :survey_enrollments, dependent: :destroy, inverse_of: :party

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



284
# File 'app/models/party.rb', line 284

has_many :uploads, as: :resource, dependent: :destroy

#visitVisit

Returns:

See Also:



231
# File 'app/models/party.rb', line 231

belongs_to :visit, inverse_of: :party, optional: true

#visit_eventsActiveRecord::Relation<VisitEvent>

Returns:

See Also:



279
# File 'app/models/party.rb', line 279

has_many :visit_events, through: :visits

#visitsActiveRecord::Relation<Visit>

Returns:

  • (ActiveRecord::Relation<Visit>)

See Also:



278
# File 'app/models/party.rb', line 278

has_many :visits, foreign_key: :user_id, dependent: :nullify, inverse_of: :user

#voucher_itemsActiveRecord::Relation<VoucherItem>

Returns:

See Also:



272
# File 'app/models/party.rb', line 272

has_many :voucher_items, through: :vouchers

#vouchersActiveRecord::Relation<Voucher>

Returns:

  • (ActiveRecord::Relation<Voucher>)

See Also:



270
# File 'app/models/party.rb', line 270

has_many :vouchers, foreign_key: 'supplier_id', dependent: :nullify, inverse_of: :supplier