Class: Crm::CustomerOutletPurchasesController

Inherits:
CrmController show all
Defined in:
app/controllers/crm/customer_outlet_purchases_controller.rb

Overview

Controller: customer outlet purchases.

Reps and the nightly sweep propose links between an opportunity and the
retailer invoice a customer actually generated; the rep confirms whether the
order relates to the project. Nothing here writes to the Opportunity except
through the explicit close_opportunity action — see
doc/tasks/202608081330_OUTLET_PURCHASE_ATTRIBUTION.md § 3.

Constant Summary

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

#close_opportunityvoid

This method returns an undefined value.

Mark the opportunity won against this purchase. Deliberately explicit —
28% of matched purchases are under a quarter of the quoted value and won
cancels every follow-up, so this is never automatic.



68
69
70
71
72
73
74
75
76
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 68

def close_opportunity
  authorize!(:close_opportunity, @purchase)

  if @purchase.close_opportunity!
    redirect_back_to_context(notice: 'Opportunity marked won against this outlet purchase.')
  else
    redirect_back_to_context(alert: 'Could not mark that opportunity won.')
  end
end

#createvoid

This method returns an undefined value.



51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 51

def create
  @purchase = CustomerOutletPurchase.new(link_attributes)
  authorize!(:create, @purchase)

  return unresolved_invoice if invoice_reference.present? && @purchase.invoice_id.nil?

  if @purchase.save
    redirect_back_to_context(notice: 'Outlet purchase linked and accepted.')
  else
    redirect_back_to_context(alert: @purchase.errors.full_messages.to_sentence)
  end
end

#destroyvoid

This method returns an undefined value.



107
108
109
110
111
112
113
114
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 107

def destroy
  authorize!(:destroy, @purchase)
  # A `before_destroy` can abort — `Opportunity#collapse_colliding_outlet_purchases`
  # prepends one — and a success flash over a surviving row is a lie.
  return redirect_back_to_context(notice: 'Outlet purchase link removed.') if @purchase.destroy

  redirect_back_to_context(alert: 'Could not remove that outlet purchase link.')
end

#indexvoid

This method returns an undefined value.

The review queue: every swept match nobody has ruled on yet.

Exists because the daily digest was the only way to see these, so a row
missed on the morning it was proposed was effectively invisible — the
12-month backfill landed 86 at once and none of them appear in any one
day's email.

Ordered by confidence, but the column that decides most rulings is
engagement, and the two are near-independent (r = 0.09 across the backlog).
The disagreement is the point: a certain match nobody worked is the case
that must not be waved through.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 26

def index
  authorize!(:read, CustomerOutletPurchase)
  # ponytail: RepEngagement scores each row through six counts and a quotes
  # lookup — a measured 7 queries a row, and no `includes` can reach them
  # (`.count`/`.order` reissue, and two signals are class-level scopes on
  # Communication / CallRecord). Fine at 86 and ~90/year, and the backlog
  # shrinks as it is ruled on. Batching means reimplementing the per-row
  # credit window in Ruby, which would let the queue and the analysis page
  # disagree; if this ever runs to thousands, store the score on the row at
  # match time instead. `the review queue cost per row stays bounded` holds
  # the ceiling at 8.
  @purchases = CustomerOutletPurchaseQuery.new.awaiting_verification
                                          .for_review
  @backlog_value = CustomerOutletPurchaseQuery.new.awaiting_verification.total_revenue
end

#rejectvoid

This method returns an undefined value.

Orders team rules the order out — it is not part of that project.



95
96
97
98
99
100
101
102
103
104
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 95

def reject
  authorize!(:reject, @purchase)
  record_decision_note

  if @purchase.reject(current_user)
    redirect_back_to_context(notice: 'Outlet purchase rejected.')
  else
    redirect_back_to_context(alert: 'Could not reject that outlet purchase.')
  end
end

#showvoid

This method returns an undefined value.

The analysis behind a match: what was quoted, what was bought, which SKUs
overlap, and how the confidence score was arrived at. The table row carries
the verdict; this page carries the working.



46
47
48
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 46

def show
  authorize!(:read, @purchase)
end

#verifyvoid

This method returns an undefined value.

Orders team accepts a swept match — advances it to pending.



81
82
83
84
85
86
87
88
89
90
# File 'app/controllers/crm/customer_outlet_purchases_controller.rb', line 81

def verify
  authorize!(:verify, @purchase)
  record_decision_note

  if @purchase.verify(current_user)
    redirect_back_to_context(notice: 'Outlet purchase verified.')
  else
    redirect_back_to_context(alert: 'Could not verify that outlet purchase.')
  end
end