Class: Crm::SmsMessagesController
- Inherits:
-
CrmController
- Object
- ActionController::Base
- ApplicationController
- CrmController
- Crm::SmsMessagesController
- Includes:
- Controllers::Attachable, Controllers::SmsTab
- Defined in:
- app/controllers/crm/sms_messages_controller.rb
Overview
CRUD, threading, and moderation actions for SmsMessage records — the
standalone /sms_messages index plus the per-party SMS tab
(Controllers::SmsTab).
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
Instance Method Summary collapse
-
#attach_to_party ⇒ void
Attaches an unmatched inbound message's sender to the party chosen in
params[:attach][:party_id], and registers the number as a contact point. -
#block_sender ⇒ void
Blocks the message's sender number (inbound messages only).
-
#bulk_destroy ⇒ Object
DELETE /sms_messages/bulk_destroy.
-
#create ⇒ void
Creates and sends an outbound message.
-
#create_customer ⇒ void
Creates a new Customer from an unmatched inbound message's sender number, or attaches an existing matching party if one is found.
-
#destroy ⇒ void
Deletes a message.
-
#destroy_and_block_sender ⇒ void
Blocks the message's sender number, then deletes it via #destroy.
-
#do_forward ⇒ void
Reassigns the message to another SMS-enabled employee and marks it unread for them.
-
#edit ⇒ void
Loads a draft message for editing (only
new-state messages qualify). -
#forward ⇒ void
Renders the employee picker for forwarding the message loaded by #load_and_authorize_inbound_sms_message.
-
#index ⇒ void
Lists SMS messages (standalone) or renders the per-party SMS tab data when a context object (customer/contact) is present.
-
#match_inbound_sender ⇒ void
Re-attempts matching an unmatched inbound message's sender to an existing party.
-
#new ⇒ void
Builds a new outbound message, defaulting the sender from a preset param, the user's own SMS number, or a shared company number.
-
#resend ⇒ Object
If you resend a new message, you're just editing it If you resend any other state of messages, it's a clone and new.
-
#show ⇒ void
Shows a message and its thread, marking it read when requested or when the current user is the recipient of an unread message.
-
#thread_preview ⇒ Object
Live conversation panel for the composer — the Stimulus controller (crm-sms-conversation) points a Turbo Frame here whenever the sender or recipient number changes, so the thread appears without submitting.
-
#toggle_unread ⇒ void
Flips the message's unread flag (inbound messages only).
-
#unblock_sender ⇒ void
Unblocks the message's sender number.
-
#update ⇒ void
Updates a draft message and attempts to send it.
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, #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
Methods included from Controllers::SubdomainDetection
#is_crm_request?, #is_www_request?, #json_request?
Methods included from Controllers::TurboSafeRedirect
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
#attach_to_party ⇒ void
This method returns an undefined value.
Attaches an unmatched inbound message's sender to the party chosen in
params[:attach][:party_id], and registers the number as a contact
point.
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 281 def attach_to_party @sms_message = SmsMessage.find(params[:id]) if @sms_message.inbound? && @sms_message.sender_party.nil? && (new_party_id = params.dig(:attach, :party_id).presence) @sms_message.sender_party_id = new_party_id @sms_message.save @sms_message.reload # Add this number to the party @sms_message.sender_party.contact_points.create!(category: ContactPoint::CELL, detail: @sms_message.sender) SmsMessage.match_unknown(numbers: [@sms_message.sender], party: @sms_message.sender_party) flash[:info] = "#{@sms_message.sender_party.full_name} attached to message" else flash[:error] = 'Message is not inbound or already has a party' end redirect_to_return_path_or_default (@sms_message) end |
#block_sender ⇒ void
This method returns an undefined value.
Blocks the message's sender number (inbound messages only).
300 301 302 303 304 305 306 307 308 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 300 def block_sender @sms_message = SmsMessage.find(params[:id]) if @sms_message.inbound? @sms_message.block_sender else flash[:error] = 'Can only block inbounds' end redirect_to_return_path_or_default (@sms_message) end |
#bulk_destroy ⇒ Object
DELETE /sms_messages/bulk_destroy
Mass delete from the index checkboxes. Selection is bounded by the page
size, but cap defensively anyway; each record is authorized individually
so a crafted id list can't delete anything the per-row button couldn't.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 210 def bulk_destroy (:destroy, SmsMessage) ids = Array(params[:sms_message_ids]).map(&:to_i).uniq.first(500) destroyed = 0 failed = [] SmsMessage.where(id: ids).find_each do || if can?(:destroy, ) && .destroy destroyed += 1 else failed << .id end end flash[:info] = "#{destroyed} SMS #{'message'.pluralize(destroyed)} deleted" if destroyed.positive? flash[:error] = "Could not delete SMS #{'message'.pluralize(failed.size)}: #{failed.join(', ')}" if failed.any? flash[:warning] = 'No messages selected' if ids.empty? redirect_to_return_path_or_default end |
#create ⇒ void
This method returns an undefined value.
Creates and sends an outbound message.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 117 def create # Extract our upload ids @sms_message = SmsMessage.new(params[:sms_message]) (:create, @sms_message) @sms_message.direction = :outbound if @sms_message.valid? && @sms_message. # Sending a reply means the operator read the thread they're replying to. @sms_message.mark_conversation_read! # Inline replies (thread / tab reply box) return to where they were sent # from; the standalone composer lands on the sent message's thread. if params[:inline_reply].present? redirect_to_return_path_or_default (@sms_message) else redirect_to (@sms_message, return_path: params[:return_path]) end else flash.now[:error] = "Unable to create outbound sms message. #{@sms_message.errors_to_s}" load_destination_numbers load_thread_preview render :new, status: :unprocessable_content end end |
#create_customer ⇒ void
This method returns an undefined value.
Creates a new Customer from an unmatched inbound message's sender
number, or attaches an existing matching party if one is found.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 232 def create_customer @sms_message = SmsMessage.find(params[:id]) if @sms_message.inbound? && @sms_message.sender_party.nil? # Try first a match just in case @sms_message.match_inbound_sender if @sms_message.sender_party flash[:info] = "An existing matching party was discovered: #{@sms_message.sender_party.full_name}" @sms_message.save! redirect_to_return_path_or_default (@sms_message) else new_customer = { person_name: params.dig(:new_customer, :person_name), cell: @sms_message.sender, source_id: Source.find_by(referral_code: 'SMS'), country_iso: @sms_message.from_country_iso } redirect_to new_customer_identify_path(new_customer: new_customer) end else flash[:error] = 'Cannot create customer from message, message must be inbound and not already have a party' redirect_to_return_path_or_default (@sms_message) end end |
#destroy ⇒ void
This method returns an undefined value.
Deletes a message.
183 184 185 186 187 188 189 190 191 192 193 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 183 def destroy @sms_message = SmsMessage.find(params[:id]) (:destroy, @sms_message) if @sms_message.destroy flash[:info] = "SMS Message #{@sms_message.id} deleted" redirect_to_return_path_or_default redirect_url(@sms_message) else flash[:error] = "SMS Message could not be deleted. #{@sms_message..join(', ')}" redirect_to_return_path_or_default (@sms_message) end end |
#destroy_and_block_sender ⇒ void
This method returns an undefined value.
Blocks the message's sender number, then deletes it via #destroy.
198 199 200 201 202 203 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 198 def destroy_and_block_sender @sms_message = SmsMessage.find(params[:id]) (:destroy, @sms_message) @sms_message.block_sender destroy end |
#do_forward ⇒ void
This method returns an undefined value.
Reassigns the message to another SMS-enabled employee and marks it
unread for them.
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 361 362 363 364 365 366 367 368 369 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 335 def do_forward target_employee = Employee.active_employees.find_by(id: params[:target_employee_id]) unless target_employee flash[:error] = 'Employee not found' return redirect_to_return_path_or_default (@sms_message) end target_number = target_employee.sms_enabled_numbers.first unless target_number flash[:error] = "#{target_employee.full_name} does not have an SMS-enabled number" return redirect_to_return_path_or_default (@sms_message) end if target_employee.id == @sms_message.recipient_party_id flash[:warning] = 'Message is already assigned to this employee' return redirect_to_return_path_or_default (@sms_message) end @sms_message.raw_data ||= {} @sms_message.raw_data['forwarded_from'] = { 'original_recipient' => @sms_message.recipient, 'original_recipient_party_id' => @sms_message.recipient_party_id, 'forwarded_by' => current_user.id, 'forwarded_at' => Time.current.iso8601 } @sms_message.recipient = target_number @sms_message.recipient_party = target_employee @sms_message.unread = true # Skip validations: state-machine callbacks (e.g. trigger_send) must not fire # on a received message; only recipient, recipient_party_id, unread, and raw_data change. @sms_message.save!(validate: false) flash[:info] = "Message forwarded to #{target_employee.full_name}" redirect_to_return_path_or_default (@sms_message) end |
#edit ⇒ void
This method returns an undefined value.
Loads a draft message for editing (only new-state messages qualify).
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 101 def edit @sms_message = SmsMessage.find(params[:id]) (:update, @sms_message) if @sms_message.new? load_destination_numbers load_thread_preview render :edit else flash[:error] = 'Only messages in new state can be edited' redirect_to_return_path_or_default redirect_url(@sms_message) end end |
#forward ⇒ void
This method returns an undefined value.
Renders the employee picker for forwarding the message loaded by
#load_and_authorize_inbound_sms_message.
327 328 329 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 327 def forward @sms_enabled_employee_options = Employee.sms_enabled_for_select end |
#index ⇒ void
This method returns an undefined value.
Lists SMS messages (standalone) or renders the per-party SMS tab data
when a context object (customer/contact) is present.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 16 def index (:show, SmsMessage) if context_object build_sms_tab_data(context_object) else @context_object = nil @q = SmsMessage.ransack(params[:q]) @q.sorts = 'created_at desc' if @q.sorts.blank? @return_path = (q: params[:q]) @context_sms_contact_points = ContactPoint.none @pagy, @sms_messages = pagy(@q.result.includes(:recipient_party, :sender_party), limit: params[:per]) # Gates the bulk-delete checkbox column (standalone /sms_messages page # only — the per-parent SMS tabs don't get it). @sms_bulk_select = can?(:destroy, SmsMessage) end respond_to do |format| format.html { render layout: should_render_layout? } end end |
#match_inbound_sender ⇒ void
This method returns an undefined value.
Re-attempts matching an unmatched inbound message's sender to an
existing party.
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 260 def match_inbound_sender @sms_message = SmsMessage.find(params[:id]) if @sms_message.inbound? && @sms_message.sender_party.nil? @sms_message.match_inbound_sender if @sms_message.sender_party flash[:info] = "Found matching party: #{@sms_message.sender_party.full_name}" @sms_message.save! else flash[:warning] = 'No matching party found' end else flash[:error] = 'Cannot create customer from message, message must be inbound and not already have a party' end redirect_to_return_path_or_default (@sms_message) end |
#new ⇒ void
This method returns an undefined value.
Builds a new outbound message, defaulting the sender from a preset
param, the user's own SMS number, or a shared company number.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 77 def new @sms_message = SmsMessage.new(params[:sms_message] || {}) (:create, SmsMessage) @original_sms_message = nil if params[:mark_as_read_sms_message_id].present? @original_sms_message = SmsMessage.find_by(id: params[:mark_as_read_sms_message_id]) @original_sms_message&.update_attribute!(:unread, false) end # @sms_message.sender_party = current_user @sms_message.direction = :outbound # Sender precedence: a preset from params (the Reply link passes the number # the customer actually texted), then the user's own work SMS number, then # the shared company numbers. The old hard error for users without a # personal number was needless — load_destination_numbers already offers # SMS_GLOBAL_NUMBERS to everyone in the sender dropdown, and it blocked # replying even when the reply had preset a perfectly valid sender. @sms_message.sender ||= current_user.sms_enabled_numbers.first || SmsMessage::SMS_GLOBAL_NUMBERS.first load_destination_numbers load_thread_preview end |
#resend ⇒ Object
If you resend a new message, you're just editing it
If you resend any other state of messages, it's a clone and new
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 142 def resend @sms_message = SmsMessage.find(params[:id]) (:create, @sms_message) if @sms_message.outbound? load_destination_numbers if @sms_message.draft? # only new messages can be edited as is render :edit else # Clone and send to new @sms_message = @sms_message.deep_dup load_thread_preview render :new end else flash[:error] = 'Can only resend outbound messages' redirect_to_return_path_or_default redirect_url(@sms_message) end end |
#show ⇒ void
This method returns an undefined value.
Shows a message and its thread, marking it read when requested or when
the current user is the recipient of an unread message.
59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 59 def show @sms_message = SmsMessage.find(params[:id]) (:show, @sms_message) load_conversation_thread return unless params[:mark_as_read].to_b || (@sms_message.unread && @sms_message.recipient_party_id == current_user.id) @sms_message.update_attribute!(:unread, false) notice = <<-EOS Message was marked as read, #{view_context.link_to('undo and mark unread', (@sms_message, return_path: @return_path), data: { turbo_method: :post })} EOS flash.now[:notice] = notice end |
#thread_preview ⇒ Object
Live conversation panel for the composer — the Stimulus controller
(crm-sms-conversation) points a Turbo Frame here whenever the sender or
recipient number changes, so the thread appears without submitting.
39 40 41 42 43 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 39 def thread_preview (:show, SmsMessage) @conversation_messages = conversation_between(params[:sender], params[:recipient]) render partial: 'conversation_preview', layout: false end |
#toggle_unread ⇒ void
This method returns an undefined value.
Flips the message's unread flag (inbound messages only).
48 49 50 51 52 53 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 48 def toggle_unread @sms_message = SmsMessage.find(params[:id]) (:update, SmsMessage) @sms_message.toggle!(:unread) if @sms_message.inbound? redirect_to_return_path_or_default redirect_url(@sms_message) end |
#unblock_sender ⇒ void
This method returns an undefined value.
Unblocks the message's sender number.
313 314 315 316 317 318 319 320 321 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 313 def unblock_sender @sms_message = SmsMessage.find(params[:id]) if @sms_message.unblock_sender flash[:info] = "Sender #{@sms_message.sender} was unblocked" else flash[:error] = "Could not unblock #{@sms_message.sender}. Could be already unblocked" end redirect_to_return_path_or_default (@sms_message) end |
#update ⇒ void
This method returns an undefined value.
Updates a draft message and attempts to send it.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'app/controllers/crm/sms_messages_controller.rb', line 163 def update @sms_message = SmsMessage.find(params[:id]) (:update, @sms_message) if @sms_message.update(params[:sms_message]) if @sms_message. @sms_message.mark_conversation_read! flash[:info] = 'SMS message sent' redirect_to (@sms_message, return_path: params[:return_path]) else flash[:error] = @sms_message.errors_to_s.presence || 'Problem sending message' redirect_to_return_path_or_default redirect_url(@sms_message) end else render :edit, status: :unprocessable_content end end |