Class: OpportunitiesController

Overview

Controller: opportunities.

Constant Summary collapse

TAB_ACTIONS =

Tab fragments never render standalone — non-frame hits redirect to show ?tab=.

%i[tab_main tab_contacts tab_rooms tab_attachments tab_communications tab_sms tab_activities
tab_digital_assets tab_outlet_purchases].freeze
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

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::RequireTurboFrame

require_in_pane_tab_frames, require_turbo_frame

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, #touch_employee_last_seen

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?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #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, #registration_lead_type, #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 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?

Instance Method Details

#assign_plansvoid

This method returns an undefined value.

Renders the form for re-assigning uploaded plan files to rooms.



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

def assign_plans
  render layout: 'crm/crm'
end

#attach_ordersvoid

This method returns an undefined value.

Attaches the selected orders to the opportunity, optionally marking it won.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'app/controllers/opportunities_controller.rb', line 236

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_mergevoid

This method returns an undefined value.

Renders the batch merge form listing the customer's mergeable opportunities.



364
365
366
367
368
# File 'app/controllers/opportunities_controller.rb', line 364

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

#cancelvoid

This method returns an undefined value.

Cancels the opportunity if it is in a cancelable state.



169
170
171
172
# File 'app/controllers/opportunities_controller.rb', line 169

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

#complete_assign_plansvoid

This method returns an undefined value.

Saves the plan-to-room re-assignments from the assign plans form.



480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'app/controllers/opportunities_controller.rb', line 480

def complete_assign_plans
  @opportunity = Opportunity.find(params.expect(: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_promisevoid

This method returns an undefined value.

Marks the opportunity as promised from the submitted form.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/controllers/opportunities_controller.rb', line 187

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_participantsvoid

This method returns an undefined value.

Saves participant selections, promoting the first participant to primary
contact when none is set.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/controllers/opportunities_controller.rb', line 59

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

#copyvoid

This method returns an undefined value.

Renders the form for copying the opportunity (and quotes) to another customer.



297
298
299
# File 'app/controllers/opportunities_controller.rb', line 297

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

#createObject

PUT /customers/1/opportunities



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/controllers/opportunities_controller.rb', line 119

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



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/controllers/opportunities_controller.rb', line 151

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_mergevoid

This method returns an undefined value.

Merges or deletes the selected opportunities into the master opportunity.



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'app/controllers/opportunities_controller.rb', line 372

def do_batch_merge
  @opportunity = Opportunity.find(params.expect(: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_copyvoid

This method returns an undefined value.

Queues an OpportunityCopyWorker to copy the opportunity and selected quotes
to another customer.



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
329
330
331
332
333
334
335
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
# File 'app/controllers/opportunities_controller.rb', line 304

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_duplicatevoid

This method returns an undefined value.

Links this opportunity to another as a duplicate for reporting.



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'app/controllers/opportunities_controller.rb', line 424

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_movevoid

This method returns an undefined value.

Moves the opportunity to the selected customer via Opportunity::Mover.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'app/controllers/opportunities_controller.rb', line 273

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

#editvoid

This method returns an undefined value.

Renders the opportunity edit form.



112
113
114
115
116
# File 'app/controllers/opportunities_controller.rb', line 112

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

#enter_roomsvoid

This method returns an undefined value.

Continues the workflow after room entry, honoring the submit button's
next action.



529
530
531
# File 'app/controllers/opportunities_controller.rb', line 529

def enter_rooms
  handle_next_action
end

#indexvoid

This method returns an undefined value.

Lists opportunities, either for the context object or globally via Ransack.



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'app/controllers/opportunities_controller.rb', line 31

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

#losevoid

This method returns an undefined value.

Redirects to the lost-reason form before losing the opportunity.



205
206
207
# File 'app/controllers/opportunities_controller.rb', line 205

def lose
  redirect_to lost_reason_opportunity_path(@opportunity)
end

#lost_reasonvoid

This method returns an undefined value.

Renders the lost-reason form.



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

def lost_reason; end

#lost_reason_updatevoid

This method returns an undefined value.

Saves the lost reason and transitions the opportunity to a lost state.



447
448
449
450
451
452
453
454
455
456
457
# File 'app/controllers/opportunities_controller.rb', line 447

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.



413
414
415
416
417
418
419
420
# File 'app/controllers/opportunities_controller.rb', line 413

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

#mergevoid

This method returns an undefined value.

Redirects to the batch merge page with this opportunity as the master.



406
407
408
409
# File 'app/controllers/opportunities_controller.rb', line 406

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

#movevoid

This method returns an undefined value.

Renders the form for moving the opportunity to another customer.



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

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

#newvoid

This method returns an undefined value.

Renders the new opportunity form for the customer.



101
102
103
104
105
106
107
108
# File 'app/controllers/opportunities_controller.rb', line 101

def new
  @opportunity = @customer.opportunities.build(
    contact_id: params[:contact_id],
    source: @customer.most_recent_attributable_visit_source || @customer.source_for_opps_and_orders
  )
  set_default_electricity_rate
  authorize! :create, @opportunity
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)



94
95
96
97
# File 'app/controllers/opportunities_controller.rb', line 94

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

#participantsvoid

This method returns an undefined value.

Renders the participant editing form.



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

def participants; end

#promisevoid

This method returns an undefined value.

Renders the form for marking the opportunity as promised.



176
177
178
179
180
181
182
183
# File 'app/controllers/opportunities_controller.rb', line 176

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_plansvoid

This method returns an undefined value.

Queues a GenerateMultipleRoomPlansWorker to regenerate every room plan on
the opportunity.



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'app/controllers/opportunities_controller.rb', line 503

def refresh_all_room_plans
  @opportunity = Opportunity.find(params.expect(: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

#reopenvoid

This method returns an undefined value.

Reopens a lost opportunity, clearing its lost-reason fields.



461
462
463
464
465
466
467
468
469
470
# File 'app/controllers/opportunities_controller.rb', line 461

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

#restorevoid

This method returns an undefined value.

Reopens the opportunity.



260
261
262
263
# File 'app/controllers/opportunities_controller.rb', line 260

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

#roomsvoid

This method returns an undefined value.

Redirects to the opportunity's rooms tab.



522
523
524
# File 'app/controllers/opportunities_controller.rb', line 522

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

#showvoid

This method returns an undefined value.

Shows an opportunity with its briefing variables.



47
48
49
50
# File 'app/controllers/opportunities_controller.rb', line 47

def show
  authorize!(:read, @opportunity)
  set_opportunity_briefing_variables
end

#tab_activitiesvoid

This method returns an undefined value.

Renders the activities tab fragment.



601
602
603
604
605
606
607
608
# File 'app/controllers/opportunities_controller.rb', line 601

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_attachmentsvoid

This method returns an undefined value.

Renders the attachments tab fragment.



568
569
570
571
572
573
574
575
# File 'app/controllers/opportunities_controller.rb', line 568

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_communicationsvoid

This method returns an undefined value.

Renders the communications tab fragment.



579
580
581
582
583
584
585
586
# File 'app/controllers/opportunities_controller.rb', line 579

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_contactsvoid

This method returns an undefined value.

Renders the contacts tab fragment.



545
546
547
548
549
550
551
# File 'app/controllers/opportunities_controller.rb', line 545

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

#tab_digital_assetsvoid

This method returns an undefined value.

Renders the digital assets tab fragment.



612
613
614
615
616
617
618
# File 'app/controllers/opportunities_controller.rb', line 612

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

#tab_mainvoid

This method returns an undefined value.

Renders the main tab fragment.



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

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

#tab_outlet_purchasesObject

Retailer invoices attributed to this opportunity — Amazon, Home Depot, a
distributor. Record-keeping only; nothing here changes the opportunity's
own state. See doc/tasks/202608081330_OUTLET_PURCHASE_ATTRIBUTION.md.



623
624
625
626
627
628
629
630
631
# File 'app/controllers/opportunities_controller.rb', line 623

def tab_outlet_purchases
  authorize!(:read, @opportunity)
  @outlet_purchases = @opportunity.outlet_purchases.includes(invoice: { customer: :catalog }, verified_by: {})
                                  .order(created_at: :desc)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_roomsvoid

This method returns an undefined value.

Renders the rooms tab fragment.



555
556
557
558
559
560
561
562
563
564
# File 'app/controllers/opportunities_controller.rb', line 555

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_smsvoid

This method returns an undefined value.

Renders the SMS tab fragment.



590
591
592
593
594
595
596
597
# File 'app/controllers/opportunities_controller.rb', line 590

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_ordersvoid

This method returns an undefined value.

Lists the customer's orders not yet attached to an opportunity.



230
231
232
# File 'app/controllers/opportunities_controller.rb', line 230

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

#updateObject

PUT /customers/1/opportunities/1



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'app/controllers/opportunities_controller.rb', line 136

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

#winvoid

This method returns an undefined value.

Marks the opportunity as won, requiring at least one linked order.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'app/controllers/opportunities_controller.rb', line 211

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