Class: OpportunitiesController

Overview

Controller: opportunities.

Constant Summary collapse

EDIT_PARTICIPANTS_ACTION =

Edit participants action.

'Edit Participants'
EDIT_ROOMS_ACTION =

Edit rooms action.

'Edit Rooms'

Constants included from Controllers::Attachable

Controllers::Attachable::PUBLICATIONS_PER_PAGE

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods included from Controllers::DigitalAssetLinkable

#link_images, #link_videos

Methods included from Controllers::Attachable

#attach, #publication_modal, #remove_attachment, #search_library

Methods included from Controllers::Workflowable

#render_workflow_error_stream, #render_workflow_success_stream, #workflow_action, #workflow_action_complete

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #stamp_impersonation_context, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #embedded_tab_frame_id, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #add_webpage_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#assign_plansObject



426
427
428
# File 'app/controllers/opportunities_controller.rb', line 426

def assign_plans
  render layout: 'crm/crm'
end

#attach_ordersObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/controllers/opportunities_controller.rb', line 215

def attach_orders
  attach_order_ids = params[:order_ids] || []
  success = []
  @opportunity.unattached_orders.where(id: attach_order_ids).find_each do |order|
    order.update_attribute!(:opportunity_id, @opportunity.id)
    success << order.reference_number
  end
  flash[:info] = if success.present?
                   "Order(s) #{success.join(', ')} were attached to opportunity #{@opportunity.name}. "
                 else
                   +'No orders were attached. '
                 end
  if params[:mark_as_won].to_b
    if @opportunity.win
      flash[:info] = "#{flash[:info]} Opportunity was marked as won. "
    else
      flash[:error] = 'Opportunity could not be marked as won. '
    end
  end
  redirect_to opportunity_path(@opportunity)
end

#batch_mergeObject



330
331
332
333
334
# File 'app/controllers/opportunities_controller.rb', line 330

def batch_merge
  @opportunities = @customer.opportunities
  @opportunities = @opportunities.open_opportunities.not_cancelled unless params[:show_all_opportunities]
  @opportunities = @opportunities.most_recent_first
end

#cancelObject



160
161
162
163
# File 'app/controllers/opportunities_controller.rb', line 160

def cancel
  @opportunity.cancel if @opportunity.cancelable?
  redirect_to_return_path_or_default opportunity_path(@opportunity)
end

#choose_next_installationObject



467
468
469
# File 'app/controllers/opportunities_controller.rb', line 467

def choose_next_installation
  @room_configuration = RoomConfiguration.find(params[:room_id])
end

#complete_assign_plansObject



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'app/controllers/opportunities_controller.rb', line 430

def complete_assign_plans
  @opportunity = Opportunity.find(params[:id])
  counter = 0
  msg = []
  params[:uploads].each do |upload_id, upload_attrs|
    next if upload_attrs[:resource_combo].blank?

    upload = Upload.find(upload_id)
    counter += 1 if upload.update(upload_attrs)
    msg << upload.errors.full_messages if upload.errors.present?
  end
  if counter.positive?
    flash[:info] = "Re-assignment complete, #{counter} room(s) updated."
    redirect_to opportunity_path(@opportunity)
  else
    flash.now[:error] = "No files were re-assigned. #{msg.join(',')}"
    render :assign_plans, layout: 'crm/crm', status: :unprocessable_content
  end
end

#complete_new_installationObject



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/controllers/opportunities_controller.rb', line 91

def complete_new_installation
  @opportunity = Opportunity.new(params[:opportunity])
  @customer.opportunities << @opportunity

  if @opportunity.new_record?
    render :new_installation, status: :unprocessable_content
  else
    redirect_to installation_record_room_configuration_path(@opportunity.room_configurations.first)
  end
rescue ActiveRecord::RangeError
  @opportunity.errors.add(:base, "A numeric value is out of range. Please check your inputs and try again.")
  render :new_installation, status: :unprocessable_content
end

#complete_promiseObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'app/controllers/opportunities_controller.rb', line 174

def complete_promise
  if @opportunity.can_promise?
    @opportunity.attributes = params[:opportunity]
    if @opportunity.promise # save
      flash[:info] = 'Opportunity marked as promised'
      redirect_to opportunity_path(@opportunity)
    else
      flash.now[:error] = @opportunity.errors_to_s.presence || 'Could not mark opportunity as promised.'
      render :promise, status: :unprocessable_content
    end
  else
    flash[:error] = 'Opportunity is not in a state that can transition to promised'
    redirect_to opportunity_path(@opportunity)
  end
end

#confirm_participantsObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/controllers/opportunities_controller.rb', line 40

def confirm_participants
  if @opportunity.update(params[:opportunity])
    if @opportunity.contact_id.nil?
      first_participant = @opportunity.opportunity_participants.first
      if first_participant && first_participant.party.customer&.id == @opportunity.customer_id && first_participant.party.is_a?(Contact)
        @opportunity.contact_id = first_participant.party.id
        first_participant.party.job_title ||= first_participant.role
        first_participant.party.save
        if @opportunity.save
          first_participant.destroy
          flash[:info] = "Participant #{first_participant.party.full_name} was moved to primary contact of opportunity"
        else
          flash[:error] = "Could not automatically make #{first_participant.party.full_name} primary contact of opportunity. #{@opportunity.errors_to_s}"
        end
      end
    end
    handle_next_action
  else
    flash.now[:error] = @opportunity.errors_to_s.presence || 'Could not update participants.'
    render :participants, status: :unprocessable_content
  end
rescue ActiveRecord::InvalidForeignKey
  flash[:error] = "One of the selected participants no longer exists. Please refresh and try again."
  redirect_to participants_opportunity_path(@opportunity)
rescue ActiveRecord::RecordNotUnique
  # Concurrent submit raced past the model uniqueness validation and tripped
  # the `index_opportunity_participants_on_opportunity_id_and_party_id` UNIQUE
  # index. Surface a friendly error instead of a 500 (AppSignal #2452).
  flash[:error] = 'One of the selected participants is already on this opportunity. Please refresh and try again.'
  redirect_to participants_opportunity_path(@opportunity)
end

#copyObject



268
269
270
# File 'app/controllers/opportunities_controller.rb', line 268

def copy
  @target_opportunities = @opportunity.customer.opportunities.to_a.dup - [@opportunity]
end

#createObject

PUT /customers/1/opportunities



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/controllers/opportunities_controller.rb', line 112

def create
  @opportunity = Opportunity.new(params[:opportunity])
  authorize! :create, @opportunity
  @customer.opportunities << @opportunity
  if @opportunity.new_record?
    set_default_electricity_rate
    render :new, status: :unprocessable_content
  else
    handle_next_action
  end
rescue ActiveRecord::RangeError
  @opportunity.errors.add(:base, "A numeric value is out of range. Please check your inputs and try again.")
  set_default_electricity_rate
  render :new, status: :unprocessable_content
end

#destroyObject

DELETE /customers/1/opportunities/1



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'app/controllers/opportunities_controller.rb', line 144

def destroy
  if @opportunity.ok_to_delete?
    @opportunity.destroy
    redirect_to customer_path(@customer)
  else
    deps = @opportunity.quotes.limit(10).map do |q|
      view_context.link_to("Quote #{q.reference_number}", quote_path(q))
    end
    @opportunity.room_configurations.limit(10).each do |rc|
      deps << view_context.link_to("Room #{rc.name}", room_configuration_path(rc))
    end
    flash[:error] = "Opportunity cannot be deleted because it is referenced by: #{deps.join(', ')} (limited to first 10 of each type)"
    redirect_to opportunity_path(@opportunity)
  end
end

#do_batch_mergeObject



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'app/controllers/opportunities_controller.rb', line 336

def do_batch_merge
  @opportunity = Opportunity.find(params[:master_opportunity_id])
  authorize! :merge, @opportunity

  success_messages = []
  error_messages = []
  error_messages << 'Nothing selected to merge' if params[:opportunity_action].blank?
  (params[:opportunity_action] || []).each do |opportunity_id, merge_action|
    case merge_action
    when 'M' # Merge
      imported_opportunity = Opportunity.find(opportunity_id)
      result = Opportunity::Merger.new.process(duplicate_opp: imported_opportunity, survivor_opp: @opportunity)
      if result.merged?
        success_messages += result.messages
      else
        error_messages += result.messages
      end
    when 'D' # Delete
      deleted_opportunity = Opportunity.find(opportunity_id)
      deleted_opportunity.destroy
      success_messages << "Opportunity #{deleted_opportunity} was deleted"
    end
  end
  flash[:info] = success_messages.join('. ') if success_messages.present?
  flash[:error] = error_messages.join('. ') if error_messages.present?
  if error_messages.any?
    redirect_to batch_merge_customer_opportunities_path(@opportunity.customer)
  else
    redirect_to_return_path_or_default @opportunity
  end
end

#do_copyObject



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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'app/controllers/opportunities_controller.rb', line 272

def do_copy
  if (customer_id = params.dig(:copy, :customer_id)).present?
    customer_copying_to = Customer.find(customer_id)
  end
  quotes_copying_to = (params[:quote_ids].presence || []).map(&:to_i)

  if customer_copying_to.present? && quotes_copying_to.present?
    Customer.find(@opportunity.customer_id).catalog_id
    customer_copying_to.catalog_id
    target_customer = params.dig(:copy, :customer_id).to_i
    current_customer = @opportunity.customer_id
    has_same_opportunity = Opportunity.where(customer_id: customer_copying_to.id).where(name: @opportunity.name)

    if target_customer == current_customer
      flash[:error] = 'Cannot copy an opportunity to the same customer.'
      redirect_back_or_to root_path && return
    end

    # if target_customer_catalog_id != current_customer_catalog_id
    #   flash[:error] = 'Cannot copy an opportunity between different customer catalog.'
    #   redirect_back(fallback_location: root_path) && return
    # end

    if has_same_opportunity.present?
      flash[:error] = 'Current opportunity cannot be copied. Target customer already has it.'
      redirect_back_or_to root_path && return
    end

    # if @opportunity.orders.active.non_carts.present?
    #   flash[:error] = 'Cannot copy an opportunity with active orders, cancel them before.'
    #   redirect_back(fallback_location: root_path) && return
    # end

    job_options = {
        customer_id: customer_copying_to.id,
        opportunity_id: @opportunity.id,
        quote_ids: quotes_copying_to,
        notify_email: current_user.email
    }.stringify_keys
    job_id = OpportunityCopyWorker.perform_async(job_options)
    if job_id.blank?
      if (job_id = BackgroundJobStatus.search(worker_klass: 'OpportunityCopyWorker', args: job_options).first&.dig(:job_id))
        flash[:warning] = 'A similar job was already enqueued, watching status of that job now'
      end
    end
    if job_id.present?
      redirect_to job_path(job_id)
    else
      flash[:warning] = 'There was a problem queuing the job'
      redirect_to_return_path_or_default opportunity_path(@opportunity)
    end

  else
    flash[:error] = 'Customer or quotes selected are not valid. Remember that you must select the target customer and at least 1 quote.'
    redirect_back_or_to root_path
  end
end

#do_mark_as_duplicateObject



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'app/controllers/opportunities_controller.rb', line 384

def do_mark_as_duplicate
  authorize! :update, @opportunity
  target = Opportunity.find_by(id: params[:merged_into_id], customer_id: @opportunity.customer_id)
  if target.nil? || target.id == @opportunity.id
    flash[:error] = 'Pick a different opportunity on this customer to mark as the canonical one.'
    redirect_to mark_as_duplicate_opportunity_path(@opportunity)
    return
  end

  @opportunity.merge_into!(target)
  flash[:info] = "Marked as duplicate of #{target.reference_number}. The two are now linked for reporting."
  redirect_to opportunity_path(target)
rescue ActiveRecord::RecordInvalid, ArgumentError => e
  flash[:error] = "Could not mark as duplicate: #{e.message}"
  redirect_to opportunity_path(@opportunity)
end

#do_moveObject



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/controllers/opportunities_controller.rb', line 246

def do_move
  if (customer_id = params.dig(:move, :customer_id)).present?
    customer_moving_to = Customer.find(customer_id)
  end

  if customer_moving_to
    om = Opportunity::Mover.new(@opportunity)
    if om.move_to(customer_moving_to)
      flash[:info] = 'Move successful'
      redirect_to opportunity_path(@opportunity)
    else
      flash.now[:error] = om.results.join(',')
      @target_opportunities = @opportunity.customer.opportunities.to_a.dup - [@opportunity]
      render :move, status: :unprocessable_content
    end
  else
    flash.now[:error] = 'Invalid customer selected'
    @target_opportunities = @opportunity.customer.opportunities.to_a.dup - [@opportunity]
    render :move, status: :unprocessable_content
  end
end

#editObject



105
106
107
108
109
# File 'app/controllers/opportunities_controller.rb', line 105

def edit
  @opportunity = Opportunity.find(params[:id])
  set_default_electricity_rate
  authorize! :update, @opportunity
end

#enter_roomsObject



475
476
477
# File 'app/controllers/opportunities_controller.rb', line 475

def enter_rooms
  handle_next_action
end

#indexObject



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'app/controllers/opportunities_controller.rb', line 22

def index
  if context_object
    build_opportunities_tab_data(context_object)
  else
    @context_object = nil
    @q = Opportunity.all.ransack(params[:q])
    @pagy, @opportunities = pagy(:countish, @q.result.order('opportunities.created_at desc'))
  end
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#loseObject



190
191
192
# File 'app/controllers/opportunities_controller.rb', line 190

def lose
  redirect_to lost_reason_opportunity_path(@opportunity)
end

#lost_reasonObject



401
# File 'app/controllers/opportunities_controller.rb', line 401

def lost_reason; end

#lost_reason_updateObject



403
404
405
406
407
408
409
410
411
412
413
# File 'app/controllers/opportunities_controller.rb', line 403

def lost_reason_update
  @opportunity.attributes = opportunity_lost_params

  if transition_to_lost_state
    flash[:info] = "Opportunity marked as #{@opportunity.state.humanize.downcase}."
    redirect_to opportunity_path(@opportunity)
  else
    flash.now[:error] = @opportunity.errors_to_s.presence || 'Could not mark opportunity as lost.'
    render :lost_reason, status: :unprocessable_content
  end
end

#mark_as_duplicateObject

Show the "mark this opp as a duplicate of another" form. Lists the
other open opps on the same customer as candidates.



375
376
377
378
379
380
381
382
# File 'app/controllers/opportunities_controller.rb', line 375

def mark_as_duplicate
  authorize! :update, @opportunity
  @candidates = @opportunity.customer.opportunities
                            .open_opportunities
                            .where.not(id: @opportunity.id)
                            .order(created_at: :desc)
                            .limit(50)
end

#mergeObject



368
369
370
371
# File 'app/controllers/opportunities_controller.rb', line 368

def merge
  # sending to new page
  redirect_to batch_merge_customer_opportunities_path(@opportunity.customer, master_opportunity_id: @opportunity.id)
end

#moveObject



242
243
244
# File 'app/controllers/opportunities_controller.rb', line 242

def move
  @target_opportunities = @opportunity.customer.opportunities.to_a.dup - [@opportunity]
end

#newObject



80
81
82
83
84
# File 'app/controllers/opportunities_controller.rb', line 80

def new
  @opportunity = @customer.opportunities.build(contact_id: params[:contact_id], source: @customer.source_for_opps_and_orders)
  set_default_electricity_rate
  authorize! :create, @opportunity
end

#new_installationObject



86
87
88
89
# File 'app/controllers/opportunities_controller.rb', line 86

def new_installation
  @opportunity = @customer.opportunities.build
  @opportunity.room_configurations.build(room_type_id: 18) # start with 'Other' here to avoid nil room_type
end

#participant_lookupObject

Used by tom-select field on opportunity/:id/participants
if q is specified a global lookup is run
if party_id is specified, then a single party is looked up (used to initialize form on load typically)



75
76
77
78
# File 'app/controllers/opportunities_controller.rb', line 75

def participant_lookup
  results = OpportunityParticipant.find_participants(@opportunity, params)
  render json: results
end

#participantsObject



38
# File 'app/controllers/opportunities_controller.rb', line 38

def participants; end

#promiseObject



165
166
167
168
169
170
171
172
# File 'app/controllers/opportunities_controller.rb', line 165

def promise
  authorize!(:update, @opportunity)
  if @opportunity.can_promise?
    render :promise
  else
    redirect_to(opportunity_path(@opportunity), flash: { error: 'Opportunity cannot promise from current state' })
  end
end

#refresh_all_room_plansObject



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'app/controllers/opportunities_controller.rb', line 450

def refresh_all_room_plans
  @opportunity = Opportunity.find(params[:id])
  room_configuration_ids = @opportunity.room_configurations.select(&:has_plans?).map(&:id)
  if room_configuration_ids.any?
    job_id = GenerateMultipleRoomPlansWorker.perform_async({ 'on_finish_redirect_to' => "/opportunities/#{@opportunity.id}", 'room_configuration_ids' => room_configuration_ids })
    if job_id
      redirect_to job_path(job_id)
    else
      flash[:error] = 'Job could not be queued or did not provide status, perhaps a duplicate'
      redirect_to_return_path_or_default opportunity_path(@opportunity)
    end
  else
    flash[:warning] = 'No rooms have plans to refresh'
    redirect_to_return_path_or_default opportunity_path(@opportunity)
  end
end

#reopenObject



415
416
417
418
419
420
421
422
423
424
# File 'app/controllers/opportunities_controller.rb', line 415

def reopen
  @opportunity.update(lost_reason_code: nil, purchase_outlet: nil, purchased_competitor: nil, purchased_system: nil)
  @opportunity.reopen!
  if @opportunity.open_opportunity?
    flash[:info] = 'Opportunity was reopen'
  else
    flash[:error] = 'Opportunity could not be re-opened, contact your system admin'
  end
  redirect_to opportunity_path(@opportunity)
end

#restoreObject



237
238
239
240
# File 'app/controllers/opportunities_controller.rb', line 237

def restore
  @opportunity.reopen
  redirect_to_return_path_or_default opportunity_path(@opportunity)
end

#roomsObject



471
472
473
# File 'app/controllers/opportunities_controller.rb', line 471

def rooms
  redirect_to opportunity_path(@opportunity, tab: "rooms")
end

#showObject



36
# File 'app/controllers/opportunities_controller.rb', line 36

def show; end

#tab_activitiesObject



533
534
535
536
537
538
539
540
# File 'app/controllers/opportunities_controller.rb', line 533

def tab_activities
  authorize!(:read, @opportunity)
  build_activities_tab_data(@opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_attachmentsObject



506
507
508
509
510
511
512
513
# File 'app/controllers/opportunities_controller.rb', line 506

def tab_attachments
  authorize!(:read, @opportunity)
  build_uploads_tab_data(@opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_communicationsObject



515
516
517
518
519
520
521
522
# File 'app/controllers/opportunities_controller.rb', line 515

def tab_communications
  authorize!(:read, @opportunity)
  build_communications_tab_data(@opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_contactsObject



487
488
489
490
491
492
493
# File 'app/controllers/opportunities_controller.rb', line 487

def tab_contacts
  authorize!(:read, @opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_digital_assetsObject



542
543
544
545
546
547
548
# File 'app/controllers/opportunities_controller.rb', line 542

def tab_digital_assets
  authorize!(:read, @opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_mainObject



479
480
481
482
483
484
485
# File 'app/controllers/opportunities_controller.rb', line 479

def tab_main
  authorize!(:read, @opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_roomsObject



495
496
497
498
499
500
501
502
503
504
# File 'app/controllers/opportunities_controller.rb', line 495

def tab_rooms
  authorize!(:read, @opportunity)
  @editing_locked = false
  @return_path = opportunity_path(@opportunity, tab: 'rooms')
  build_rooms_tab_data(@opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_smsObject



524
525
526
527
528
529
530
531
# File 'app/controllers/opportunities_controller.rb', line 524

def tab_sms
  authorize!(:read, @opportunity)
  build_sms_tab_data(@opportunity)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#unattached_ordersObject



211
212
213
# File 'app/controllers/opportunities_controller.rb', line 211

def unattached_orders
  @unattached_orders = @opportunity.unattached_orders(page: params[:page].presence, per: params[:per].presence)
end

#updateObject

PUT /customers/1/opportunities/1



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'app/controllers/opportunities_controller.rb', line 129

def update
  authorize! :update, @opportunity
  if @opportunity.update(params[:opportunity])
    handle_next_action
  else
    set_default_electricity_rate
    render :edit, status: :unprocessable_content
  end
rescue ActiveRecord::RangeError
  @opportunity.errors.add(:base, "A numeric value is out of range. Please check your inputs and try again.")
  set_default_electricity_rate
  render :edit, status: :unprocessable_content
end

#winObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'app/controllers/opportunities_controller.rb', line 194

def win
  # If there are no orders linked to the opportunity this is a big no no
  if @opportunity.orders.active.empty?
    if @opportunity.unattached_orders.present?
      # maybe they need to be linked
      flash[:warning] = 'Before you can mark this opportunity won you need to have one order linked.'
      redirect_to unattached_orders_opportunity_path(@opportunity)
    elsif (flash[:error] = 'No orders are linked to this opportunity and none are found in the customer, this opportunity cannot be marked as won')
      redirect_to opportunity_path(@opportunity)
    end
  else
    @opportunity.win
    flash[:info] = 'Opportunity marked as won'
    redirect_to opportunity_path(@opportunity)
  end
end