Class: Party

Inherits:
ApplicationRecord show all
Includes:
ActionView::Helpers::NumberHelper, Models::Auditable, Models::CrmLinkable, Models::LiquidMethods, Models::Notable, Models::PartyAccount, Models::PartyAddresses, Models::PartyCart, Models::PartyContactInfo, Models::PartyIdentity, Models::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 Models::Schedulable

Models::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 Models::CrmLinkable

#crm_link, #crm_link_with_host

Methods included from Models::PartyAccount

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

Methods included from Models::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 Models::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 Models::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 Models::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 Models::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 Models::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:



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

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

Instance Method Details

#accountAccount

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:



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

has_one :account, dependent: :destroy

#accountsActiveRecord::Relation<Account>

Accounts owned by this party.

Returns:

  • (ActiveRecord::Relation<Account>)

See Also:



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

has_many :accounts, dependent: :destroy

#activitiesActiveRecord::Relation<Activity>

rubocop:enable Rails/InverseOf

Returns:

See Also:



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

has_many :activities, dependent: :destroy

#addressesActiveRecord::Relation<Address>

Postal addresses on file; the extension resolves the preferred main address.

Returns:

  • (ActiveRecord::Relation<Address>)

See Also:



249
250
251
252
253
254
255
256
# File 'app/models/party.rb', line 249

has_many :addresses, dependent: :nullify, inverse_of: :party do
  def main_address
    owner = proxy_association.owner
    preferred_ids = [owner.mailing_address_id, owner.billing_address_id, owner.shipping_address_id].compact

    where(id: preferred_ids).in_order_of(:id, preferred_ids).first || first
  end
end

#agreement_participantsActiveRecord::Relation<AgreementParticipant>

Join records linking this party to agreements.

Returns:

See Also:



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

has_many :agreement_participants, dependent: :destroy

#agreementsActiveRecord::Relation<Agreement>

Agreements this party participates in.

Returns:

See Also:



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

has_many :agreements, through: :agreement_participants

#assistant_conversationsActiveRecord::Relation<AssistantConversation>

Assistant (AI) conversations held by this party as user.

Returns:

See Also:



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

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

#billing_credit_memosActiveRecord::Relation<CreditMemo>

Credit memos billed to this party as the billing customer.

Returns:

See Also:



298
299
# File 'app/models/party.rb', line 298

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:



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

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

#cannot?Object

Alias for Ability#cannot?

Returns:

  • (Object)

    Ability#cannot?

See Also:



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

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

#catalogCatalog

Catalog this party shops from.

Returns:

See Also:



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

belongs_to :catalog, optional: true

#companyCompany

Company this party belongs to.

Returns:

See Also:

Validations:



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

belongs_to :company, optional: true

#consignment_storesActiveRecord::Relation<Store>

Stores for which this party is the consignee.

Returns:

  • (ActiveRecord::Relation<Store>)

See Also:



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

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:



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

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

#contactsActiveRecord::Relation<Contact>

Contacts belonging to this customer party.

Returns:

  • (ActiveRecord::Relation<Contact>)

See Also:

Validations (if => #must_have_one_contact ):



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

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

#course_enrollmentsActiveRecord::Relation<CourseEnrollment>

Course enrollments for this party.

Returns:

See Also:



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

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

#credit_memosActiveRecord::Relation<CreditMemo>

owner of the credit memo

Returns:

See Also:



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

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:



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

belongs_to :customer, optional: true

#destination_call_logsActiveRecord::Relation<CallLog>

Call logs received by this party.

Returns:

  • (ActiveRecord::Relation<CallLog>)

See Also:



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

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

#destination_call_recordsActiveRecord::Relation<CallRecord>

Call records where this party was the destination.

Returns:

See Also:



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

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

#digital_assetsActiveRecord::Relation<DigitalAsset>

Digital assets attached to this party.

Returns:

See Also:



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

has_and_belongs_to_many :digital_assets

#gl_offset_accountLedgerAccount

Ledger account used to offset this party's GL entries.



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

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:



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

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>

Communications received by this party.

Returns:

See Also:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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>

Activities linked to this party via the legacy join table.

Returns:

See Also:



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

has_and_belongs_to_many :linked_activities, class_name: 'Activity'

#localeObject

Alias for I18n#locale

Returns:

  • (Object)

    I18n#locale

See Also:



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

delegate :locale, to: :I18n

#main_addressObject

Alias for Addresses#main_address

Returns:

  • (Object)

    Addresses#main_address

See Also:



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

delegate :main_address, to: :addresses

#notificationsActiveRecord::Relation<Noticed::Notification>

Notifications addressed to this party.

Returns:

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

See Also:



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

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

#opportunity_participantsActiveRecord::Relation<OpportunityParticipant>

Join records linking this party to opportunities.

Returns:

See Also:



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

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

#origin_call_logsActiveRecord::Relation<CallLog>

Call logs placed by this party.

Returns:

  • (ActiveRecord::Relation<CallLog>)

See Also:



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

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

#origin_call_recordsActiveRecord::Relation<CallRecord>

Call records where this party originated the call.

Returns:

See Also:



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

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

#outbound_communicationsActiveRecord::Relation<Communication>

Communications sent by this party.

Returns:

See Also:



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

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

#outgoing_paymentsActiveRecord::Relation<OutgoingPayment>

Payments made to this party as supplier.

Returns:

See Also:



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

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

#party_topicsActiveRecord::Relation<PartyTopic>

Topic subscriptions for this party.

Returns:

See Also:



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

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:



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

has_and_belongs_to_many :product_lines

#profile_imageImage

Profile image for this party.

Returns:

See Also:



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

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

#purchase_ordersActiveRecord::Relation<PurchaseOrder>

Purchase orders placed with this party as supplier.

Returns:

See Also:



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

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

#queue_call_logsActiveRecord::Relation<QueueCallLog>

Queue call logs where this party was the caller.

Returns:

See Also:



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

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

#receiptsActiveRecord::Relation<Receipt>

Receipts issued to this party as customer.

Returns:

  • (ActiveRecord::Relation<Receipt>)

See Also:



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

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

#recipient_sms_messagesActiveRecord::Relation<SmsMessage>

SMS messages received by this party.

Returns:

See Also:



326
327
# File 'app/models/party.rb', line 326

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

#room_plansActiveRecord::Relation<RoomPlan>

Room plans owned by this party.

Returns:

See Also:



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

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

#sender_sms_messagesActiveRecord::Relation<SmsMessage>

SMS messages sent by this party.

Returns:

See Also:



323
324
# File 'app/models/party.rb', line 323

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 Models::PartyIdentity#apply_full_name_for_party_type! couldn't compose one.



397
398
399
400
401
# File 'app/models/party.rb', line 397

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.



377
378
379
380
# File 'app/models/party.rb', line 377

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.



386
387
388
389
390
391
# File 'app/models/party.rb', line 386

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

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

#storeStore

Store this party's catalog belongs to.

Returns:

See Also:



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

has_one :store, through: :catalog

#support_case_participantsActiveRecord::Relation<SupportCaseParticipant>

Join records linking this party to support cases.

Returns:

See Also:



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

has_many :support_case_participants, dependent: :destroy

#support_casesActiveRecord::Relation<SupportCase>

Support cases this party participates in.

Returns:

See Also:



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

has_many :support_cases, through: :support_case_participants

#survey_enrollmentsActiveRecord::Relation<SurveyEnrollment>

Survey enrollments for this party.

Returns:

See Also:



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

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

#uploadsActiveRecord::Relation<Upload>

File uploads attached to this party.

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



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

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

#visitVisit

Visit that created this party (for guest/anonymous parties).

Returns:

See Also:



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

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

#visit_eventsActiveRecord::Relation<VisitEvent>

Events across this party's visits.

Returns:

See Also:



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

has_many :visit_events, through: :visits

#visitsActiveRecord::Relation<Visit>

Site visits recorded for this party as user.

Returns:

  • (ActiveRecord::Relation<Visit>)

See Also:



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

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

#voucher_itemsActiveRecord::Relation<VoucherItem>

Line items across this party's vouchers.

Returns:

See Also:



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

has_many :voucher_items, through: :vouchers

#vouchersActiveRecord::Relation<Voucher>

Vouchers issued to this party as supplier.

Returns:

  • (ActiveRecord::Relation<Voucher>)

See Also:



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

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