Class: ActivitiesController

Inherits:
CrmController show all
Includes:
Controllers::Attachable
Defined in:
app/controllers/activities_controller.rb

Constant Summary

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

#attach, #publication_modal, #remove_attachment, #search_library

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, #initialize_crm_lazy_chunks, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #append_token, #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::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, #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_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!, #authenticate_account_from_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, #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, #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

#createObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'app/controllers/activities_controller.rb', line 84

def create
  @context_object = context_object
  @activity = Activity.new(params[:activity])
  @activity.party ||= @context_object
  @activity.activity_result_type_id = nil unless @activity.activity_result_type
  @activity.closed_by_id = @context_user.id if @activity.activity_result_type
  @new_activities = []
  if @activity.save
    @new_activities << @activity
    chained_activities = [@activity.chained_activity_result].flatten.compact
    @new_activities += chained_activities if chained_activities.present?
    @new_activity = Activity.new(party: @activity.party, resource: @activity.resource)
    if @activity.party
      @open_activity_counter = if @activity.party.respond_to? :open_activities_counter
                                 @activity.party.open_activities_counter
                               else
                                 @activity.party.activities.open_activities.size
                               end
    end
    @updated_activities = @activity.updated_activities || []
    respond_to do |format|
      format.turbo_stream
      format.html { redirect_to activity_path(@activity) }
    end
  else
    # Preserve context_object when validation fails
    @context_object ||= @activity.resource || @activity.party
    flash.now[:error] = @activity.errors_to_s.presence || 'Could not create activity.'
    respond_to do |format|
      format.turbo_stream { render :create, status: :unprocessable_entity }
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

#destroyObject



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

def destroy
  @activity = Activity.find(params[:id])

  respond_to do |format|
    parent = @activity.resource || @activity.party
    if @activity.destroy
      format.turbo_stream
      format.html { redirect_to_return_path_or_default polymorphic_url(parent) }
    end
  end
end

#do_prioritizationObject



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/controllers/activities_controller.rb', line 216

def do_prioritization
  if job_id = BackgroundJobStatus.search(worker_klass: 'ActivityPrioritizationWorker', limit: 1).first&.jid
    redirect_to job_path(job_id, notice: 'Job already running')
  else
    raw_date = params.dig(:payload, :allocation_start).to_s
    dt = Time.zone.try(:parse, raw_date) || Date.current
    dt = nil if dt && (dt < Date.current)
    if dt
      job_id = ActivityPrioritizationWorker.perform_async(allocation_start: dt, redirect_to: activities_url, current_user_id: current_user.id)
      redirect_to job_path(job_id)
    else
      flash.now[:error] = 'Invalid Date Provided'
      render :prioritize, status: :unprocessable_entity
    end
  end
end

#editObject



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'app/controllers/activities_controller.rb', line 70

def edit
  @activity = Activity.find(params[:id])
  respond_to do |format|
    format.turbo_stream
    format.html { redirect_to polymorphic_path(@activity.party, selected_activity: @activity.id) }
    format.json do
      render json: {
        activityId: @activity.id,
        html: raw_activity_form(@activity)
      }
    end
  end
end

#indexObject



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/controllers/activities_controller.rb', line 8

def index
  @context_object = context_object
  if @context_object
    @activities = (@context_object.try(:all_activities) || @context_object.activities).natural_order
    @activity_types_for_filter = [['Notes', 0]] + ActivityType.all.order(:task_type).pluck(:task_type, :id)
    if @selected_activity_id = params.delete(:selected_activity).presence
      @selected_activity_id = @selected_activity_id.to_i
      params[:q] = {} # Ensures a selected activity always come up
    elsif params[:q].blank?
      # Default is to exclude drips
      params[:q] = { not_tagged_with: ['Drip'] }
    end
    @q = @activities.ransack(params[:q])
    if @selected_activity_id
      offset = Activity.position_of_activity(@selected_activity_id, @q.result)
      page = (offset.to_f / Pagy.options[:items]).ceil if offset
    end
    page ||= params[:page] || 1

    @pagy, @activities = pagy(:countish, @q.result.preloaded.with_activity_uploads_count, page: page)
  else
    @pagy, @activities = pagy(Activity.none, page: 1)
  end
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#newObject



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/controllers/activities_controller.rb', line 48

def new
  @context_object = context_object
  unless @context_object
    flash[:error] = "The record you're trying to add an activity to no longer exists."
    respond_to do |format|
      format.json { render json: { redirect_to: crm_home_path }, status: :not_found }
      format.html { redirect_to crm_home_path }
    end
    return
  end
  @activity = @context_object.build_activity if @context_object.respond_to?(:build_activity)
  @activity ||= @context_object.activities.build
  respond_to do |format|
    format.json do
      render json: {
        activityId: nil,
        html: raw_activity_form(@activity)
      }
    end
  end
end

#prioritizeObject



210
211
212
213
214
# File 'app/controllers/activities_controller.rb', line 210

def prioritize
  return unless job_id = BackgroundJobStatus.search(worker_klass: 'ActivityPrioritizationWorker', limit: 1).first&.try(:[], :job_id)

  redirect_to job_path(job_id, notice: 'Job already running')
end

#raw_activity_form(activity = @activity) ⇒ Object



291
292
293
294
295
296
297
298
299
300
# File 'app/controllers/activities_controller.rb', line 291

def raw_activity_form(activity = @activity)
  if activity.complete? && activity.communication
    render_to_string('/activities/_communication_preview', formats: [:html], layout: false, locals: { activity: activity })
  elsif activity.communication
    render_to_string('/activities/_communication_preview', formats: [:html], layout: false, locals: { activity: activity }) +
      render_to_string('/activities/_activity_form', formats: [:html], layout: false, locals: { activity: activity })
  else
    render_to_string('/activities/_activity_form', formats: [:html], layout: false, locals: { activity: activity })
  end
end

#resourcesObject

Pulls a list of resources for activities form



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'app/controllers/activities_controller.rb', line 303

def resources
  @activity = Activity.find(params[:activity_id]) if params[:activity_id]
  @party = @activity&.party
  @context ||= params[:context_type].constantize.find(params[:context_id]) if params[:context_type] && params[:context_id]
  if @context === Party
    @party = @context
  elsif @context.respond_to?(:customer)
    @party = @context.customer
  end
  if @party.respond_to?(:resources_for_select)
    results = @party.resources_for_select(filter: @activity&.activity_type&.resource_restriction,
                                          page: params[:page],
                                          query: params[:q])
  end
  results ||= {}
  render json: results
end

#showObject



37
38
39
40
41
42
43
44
45
46
# File 'app/controllers/activities_controller.rb', line 37

def show
  @activity = Activity.find(params[:id])
  return_path = begin
    polymorphic_path(@activity.resource, tab: 'activities', selected_activity: @activity.id)
  rescue StandardError
    nil
  end
  return_path ||= polymorphic_path(@activity.party, tab: 'activities', selected_activity: @activity.id)
  redirect_to_return_path_or_default return_path
end

#updateObject



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
159
# File 'app/controllers/activities_controller.rb', line 119

def update
  @activity = Activity.find(params[:id])
  @new_activities = []
  @updated_activities = []
  @chained_activity_errors = []

  if @activity.update(params[:activity])
    # Capture chained activities BEFORE reloading (attr_accessor is lost on reload)
    chained_activities = [@activity.chained_activity_result].flatten.compact
    updated_activities_from_chain = @activity.updated_activities || []

    # Reload with associations to ensure all data is fresh, including uploads_count
    @activity = Activity.includes(:activity_result_type, :activity_type, :assigned_resource, :closed_by)
                        .with_activity_uploads_count
                        .find(@activity.id)
    @new_activities += chained_activities if chained_activities.present?
    @updated_activities = updated_activities_from_chain.uniq.compact

    # Check for validation errors in chained activities
    if chained_activities.present?
      chained_activities.each do |chained_activity|
        next unless chained_activity.errors.any?

        @chained_activity_errors << {
          activity_type: chained_activity.activity_type&.name,
          assigned_resource: chained_activity.assigned_resource&.full_name,
          errors: chained_activity.errors.full_messages
        }
      end
    end

    @open_activity_counter = @activity.party.try(:open_activities_counter) || @activity.party.activities.open_activities.size if @activity.party
  else
    # Preserve context_object when validation fails
    @context_object = @activity.resource || @activity.party
  end
  respond_to do |format|
    format.turbo_stream
    format.html { redirect_back_or_to(root_path) }
  end
end

#update_defaultsObject



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'app/controllers/activities_controller.rb', line 257

def update_defaults
  @activity = Activity.new(params[:activity])
  assigned_resource_id = begin
    @activity.activity_type.determine_assigned_resource_id(@activity.party, @context_user.try(:id))
  rescue StandardError
    nil
  end
  @activity.assigned_resource_id = assigned_resource_id

  activity_result_types = []
  if @activity.activity_type
    activity_result_types = @activity.activity_type.activity_result_types.map { |r| ["#{r.result_code} - #{r.description}", r.id] }
  elsif params[:activity] && params[:activity][:activity_type_id] && (params[:activity][:activity_type_id].class == Array)
    # these come from the activity search, so add in the additional 'Any' and 'Open' options
    activity_result_types = [%w[Open Open], %w[Any Any]] + ActivityResultType.joins(:activity_chain_types).where(activity_chain_types: { activity_type_id: params[:activity][:activity_type_id] }).map do |r|
      ["#{r.result_code} - #{r.description}", r.id]
    end
  end

  respond_to do |format|
    format.json do
      render json: {
        assigned_resource_id: assigned_resource_id,
        activity_result_types: activity_result_types,
        combo_resources_collection: @activity.default_resource_for_select,
        resource_requirement: @activity.activity_type.try(:resource_requirement),
        activity_resource_gid: @activity.resource_gid,
        party_collection: @activity.contact_options_for_select,
        party_id: @activity.party_id
      }
    end
  end
end


243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'app/controllers/activities_controller.rb', line 243

def update_fast_contact_links
  party_id = params.dig(:activity, :party_id).presence
  @party = Party.find(party_id) if party_id
  @customer = Customer.find(params[:customer_id]) if params[:customer_id].present?
  @customer ||= @party.try(:customer)
  @party ||= @customer
  @activity = params[:activity_id].nil? ? Activity.new : Activity.find(params[:activity_id])
  @activity.party = @party
  @activity.parties = @parties = [@party, @customer].compact.uniq
  respond_to do |format|
    format.turbo_stream
  end
end

#update_rep_calendarObject



233
234
235
236
237
238
239
240
241
# File 'app/controllers/activities_controller.rb', line 233

def update_rep_calendar
  if rep_id = params.dig(:activity, :assigned_resource_id).presence
    @rep = Employee.find(rep_id)
  end
  @activity = params[:activity_id].nil? ? Activity.new : Activity.find(params[:activity_id])
  respond_to do |format|
    format.turbo_stream
  end
end

#workloadObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/controllers/activities_controller.rb', line 173

def workload
  @date = Date.strptime(params[:date], '%Y-%m-%d') if params[:date].present?
  @date ||= Date.current
  @start_date = @date.beginning_of_month
  @end_date = @date.end_of_month
  activities = Activity.open_activities
                       .where(Activity[:target_datetime].lteq(@end_date.end_of_day))
                       .where(Activity[:target_datetime].gteq(@start_date.beginning_of_day))
                       .joins(:activity_type)

  if params[:employee_id].present? && @context_user.selected_watch_list_employee_ids && !@context_user.selected_watch_list_employee_ids.include?(params[:employee_id].to_i)
    new_eid = params[:employee_id].to_i
    employee_ids = @context_user.selected_watch_list_employee_ids | [new_eid]
    flash[:info] = "Adding employee id #{new_eid} to your watch list"
    @context_user.update(selected_watch_list_employee_ids: employee_ids)
  end

  activities = activities.where(assigned_resource_id: @context_user.effective_watch_list_ids)

  # Adjust our range to the first day with activities
  @start_date = [activities.minimum(:target_datetime).try(:to_date), @start_date].compact.max
  @date_range = (@start_date..@end_date).to_a

  # activities = activities.group(['assigned_resource_id',"(target_datetime - interval '#{Time.zone.utc_offset.abs} seconds')::Date"]).count(:id)
  activities = activities.group(['assigned_resource_id', 'target_datetime::date']).count(:id)
  @activities = {}
  activities.each do |k, v|
    rep_id = k[0]
    date = k[1]
    @activities[rep_id] ||= {}
    @activities[rep_id][date] ||= 0
    @activities[rep_id][date] += v
  end

  @employees = Employee.where(id: @activities.keys).order(:full_name)
end