Class: OpportunityParticipant

Inherits:
ApplicationRecord show all
Defined in:
app/models/opportunity_participant.rb

Overview

== Schema Information

Table name: opportunity_participants
Database name: primary

id :integer not null, primary key
role :string
created_at :datetime
updated_at :datetime
contact_point_id :integer
creator_id :integer
opportunity_id :integer not null
party_id :integer not null
updater_id :integer

Indexes

index_opportunity_participants_on_opportunity_id_and_party_id (opportunity_id,party_id) UNIQUE
opportunity_participants_contact_point_id_idx (contact_point_id)
opportunity_participants_party_id_idx (party_id)

Foreign Keys

fk_rails_... (contact_point_id => contact_points.id)
fk_rails_... (opportunity_id => opportunities.id)
fk_rails_... (party_id => parties.id)

Constant Summary collapse

MAX_INTEGER_ID =

Max value for 4-byte signed integer (PostgreSQL integer type)

2_147_483_647

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#party_idObject (readonly)

Matches the index_opportunity_participants_on_opportunity_id_and_party_id UNIQUE
index so duplicate participants surface as a form error instead of a 500.

Validations:

  • Uniqueness ({ scope: :opportunity_id, message: 'is already a participant on this opportunity' })
  • Allow_nil


41
# File 'app/models/opportunity_participant.rb', line 41

validates :party_id, uniqueness: { scope: :opportunity_id, message: 'is already a participant on this opportunity' }, allow_nil: true

Class Method Details

.find_participants(opportunity, params = {}) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'app/models/opportunity_participant.rb', line 103

def self.find_participants(opportunity, params = {})
  results_array = []
  if (search_term = params[:q].presence)
    in_customer_results = opportunity.customer.contacts.active.order(:full_name)
    results = []
    # Only query by ID if the search term is a valid integer within range
    search_term_as_id = search_term.to_i if search_term.to_s.match?(/\A\d+\z/) && search_term.to_i <= MAX_INTEGER_ID
    results += in_customer_results.where(id: search_term_as_id) if search_term_as_id
    # Build name match query, only include ID match if within range
    name_match = Contact[:full_name].matches("%#{search_term}%")
    name_match = name_match.or(Contact[:id].eq(search_term_as_id)) if search_term_as_id
    results += in_customer_results.where(name_match)
    if results.present?
      results_array = [{ text: "#{opportunity.customer.full_name}'s Contacts Matches:" }]
      results_array += results.uniq.map { |party| { id: party.id, text: "#{party.full_name} [#{party.id}]" } }
    end
    results_array += ["NewContactOfCustomer|#{opportunity.customer_id}|#{search_term}",
                      "NewContact|#{opportunity.id}|#{search_term}",
                      "NewCustomer|#{opportunity.id}|#{search_term}"].map { |party_combo| { text: party_combo_description(party_combo, opportunity), id: party_combo } }

    # Do wildcard lookup outside of this opportunity's customer
    # for some reason this exclusion makes the look up fail to find anyone
    # where.not('parties.customer_id = :customer_id OR parties.id = :customer_id', customer_id: opportunity.customer_id)
    per_page = (params[:per_page] || 10).to_i
    page = (params[:page] || 1).to_i
    global_results = Party.where(type: %w[Customer Contact]).lookup(search_term).limit(per_page).offset((page - 1) * per_page).to_a
    if (cn_term = search_term.scan(Customer::REFERENCE_NUMBER_PATTERN).join.upcase.presence)
      global_results += Customer.where(id: cn_term).to_a
    elsif search_term_as_id # Only query by ID if within valid integer range
      global_results += Customer.where(id: search_term_as_id).to_a
    end
    if global_results.present?
      global_results.compact.uniq.each do |party|
        results_array << format_party_for_results(party)
        next unless party.respond_to? :contacts

        if (contacts = party.contacts.active.order(:full_name)).present?
          results_array += contacts.map { |contact| format_party_for_results(contact) }
        end
      end
    end
  else
    party_ids = []
    # Input is preselected with an id, load it up
    if params[:party_id].present?
      party_ids << params[:party_id].to_i # Form loaded with a part selected
    end
    # Add the opportunity's parties
    party_ids += opportunity.customer.contacts.active.order(:full_name).ids
    results = Party.where(id: party_ids.compact.uniq)
    results_array += results.to_a.uniq.map { |party| format_party_for_results(party) }
  end

  total_entries = results.size
  { results: results_array, total: total_entries }
end

.format_party_for_results(party) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/models/opportunity_participant.rb', line 160

def self.format_party_for_results(party)
  display_text = if party.is_a?(Contact)
                   if party.customer
                     "#{party.customer.try(:full_name)} (#{party.customer.reference_number}) • #{party.full_name}"
                   else
                     party.full_name.to_s
                   end
                 else
                   "#{party.full_name} (#{party.reference_number})"
                 end
  { id: party.id, text: display_text }
end

.party_combo_description(party_combo, opportunity = nil) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/models/opportunity_participant.rb', line 77

def self.party_combo_description(party_combo, opportunity = nil)
  parts = party_combo.to_s.split('|')
  action, record_id, name = *parts
  opportunity ||= Opportunity.find(record_id)
  case action
  when 'NewContactOfCustomer'
    "Create #{name} as contact of #{opportunity.customer.full_name}"
  when 'NewContact'
    "Create #{name} as contact only for this opportunity"
  when 'NewCustomer'
    "Create #{name} as new customer"
  end
end

Instance Method Details

#contactObject



50
51
52
# File 'app/models/opportunity_participant.rb', line 50

def contact
  party.is_a?(Contact) && party
end

#contact_pointContactPoint



37
# File 'app/models/opportunity_participant.rb', line 37

belongs_to :contact_point, inverse_of: :opportunity_participants, optional: true

#contact_point_detailObject



58
59
60
# File 'app/models/opportunity_participant.rb', line 58

def contact_point_detail
  contact_point.try(:detail) || party_primary_contact_point_detail || @contact_point_detail
end

#contact_point_detail=(val) ⇒ Object



62
63
64
65
# File 'app/models/opportunity_participant.rb', line 62

def contact_point_detail=(val)
  contact_point_id_will_change!
  @contact_point_detail = val
end

#customerObject



54
55
56
# File 'app/models/opportunity_participant.rb', line 54

def customer
  contact&.customer || (party.is_a?(Customer) && party)
end

#opportunityOpportunity



35
# File 'app/models/opportunity_participant.rb', line 35

belongs_to :opportunity, inverse_of: :opportunity_participants, optional: true

#partyParty

Returns:

See Also:



36
# File 'app/models/opportunity_participant.rb', line 36

belongs_to :party, inverse_of: :opportunity_participants, optional: true

#party_comboObject



96
97
98
# File 'app/models/opportunity_participant.rb', line 96

def party_combo
  party_id || @party_combo
end

#party_combo=(val) ⇒ Object



91
92
93
94
# File 'app/models/opportunity_participant.rb', line 91

def party_combo=(val)
  party_id_will_change!
  @party_combo = val
end

#party_combo_for_selectObject

Gets called to populate our initial select options



68
69
70
71
72
73
74
75
# File 'app/models/opportunity_participant.rb', line 68

def party_combo_for_select
  if party_id
    output = self.class.format_party_for_results(party)
    [[output[:text], output[:id]]]
  elsif @party_combo
    [[self.class.party_combo_description(@party_combo, opportunity), @party_combo]]
  end
end

#party_primary_contact_point_detailObject



173
174
175
# File 'app/models/opportunity_participant.rb', line 173

def party_primary_contact_point_detail
  party && party.contact_points.sort_by(&:position).find(&:contactable?).try(:detail)
end

#roles_for_selectObject



45
46
47
48
# File 'app/models/opportunity_participant.rb', line 45

def roles_for_select
  options = [role] + Profile.where.not(name: 'Buying Group').map(&:friendly_name).sort
  options.compact.uniq.sort
end