From eca5238abd2658c0122699b066c82cb431692442 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 10 Oct 2025 17:16:25 +0200 Subject: [PATCH] Adding bulk email functionality --- app/controllers/admin/emails_controller.rb | 54 +++++++++++++++++++ app/mailers/mailbot.rb | 7 +++ app/views/admin/emails/bulk.html.haml | 61 ++++++++++++++++++++++ app/views/admin/emails/index.html.haml | 13 +++++ config/routes.rb | 7 ++- 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 app/views/admin/emails/bulk.html.haml diff --git a/app/controllers/admin/emails_controller.rb b/app/controllers/admin/emails_controller.rb index ba456efe..f9aa4200 100644 --- a/app/controllers/admin/emails_controller.rb +++ b/app/controllers/admin/emails_controller.rb @@ -22,8 +22,62 @@ module Admin @settings = @conference.email_settings end + def bulk + authorize! :index, @conference.email_settings + @registrations = @conference.registrations.includes(:user, :qanswers) + @questions = @conference.questions.includes(:qanswers) + end + + def send_bulk + authorize! :index, @conference.email_settings + + subject = params[:subject] + body = params[:body] + filter_type = params[:filter_type] + + if subject.blank? || body.blank? + redirect_to bulk_admin_conference_emails_path(@conference.short_title), alert: 'Subject and body are required.' + return + end + + recipients = get_filtered_recipients(filter_type) + + if recipients.empty? + redirect_to bulk_admin_conference_emails_path(@conference.short_title), alert: 'No recipients found with the selected filter.' + return + end + + recipients.each do |user| + Mailbot.bulk_mail(@conference, user, subject, body).deliver_later + end + + redirect_to bulk_admin_conference_emails_path(@conference.short_title), + notice: "Bulk email sent to #{recipients.count} recipients." + end + private + def get_filtered_recipients(filter_type) + registrations = @conference.registrations.includes(:user, :qanswers) + + case filter_type + when 'no_questions_answered' + registrations.select { |r| r.qanswers.empty? }.map(&:user) + when 'some_questions_answered' + registrations.select { |r| r.qanswers.any? && r.qanswers.count < @conference.questions.count }.map(&:user) + when 'all_questions_answered' + registrations.select { |r| r.qanswers.count >= @conference.questions.count }.map(&:user) + when 'all_registered' + registrations.map(&:user) + else + [] + end + end + + def bulk_email_params + params.permit(:subject, :body, :filter_type) + end + def email_params params.require(:email_settings).permit(:send_on_registration, :send_on_accepted, :send_on_rejected, :send_on_confirmed_without_registration, diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 52e15b80..7561a912 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -137,4 +137,11 @@ class Mailbot < ActionMailer::Base template_name: 'comment_template', subject: "New comment has been posted for #{@event.title}") end + + def bulk_mail(conference, user, subject, body) + mail(to: user.email, + from: conference.contact.email, + subject: subject, + body: EmailSettings.new.generate_email_on_conf_updates(conference, user, body)) + end end diff --git a/app/views/admin/emails/bulk.html.haml b/app/views/admin/emails/bulk.html.haml new file mode 100644 index 00000000..a3b3b245 --- /dev/null +++ b/app/views/admin/emails/bulk.html.haml @@ -0,0 +1,61 @@ +.row + .col-md-8 + %h2 Bulk Email + %p Send customized emails to registered users based on their registration status. + + = form_tag send_bulk_admin_conference_emails_path(@conference.short_title), method: :post, class: 'form-horizontal' do + .form-group + = label_tag :subject, 'Subject', class: 'col-sm-2 control-label' + .col-sm-10 + = text_field_tag :subject, params[:subject], class: 'form-control', required: true + + .form-group + = label_tag :body, 'Body', class: 'col-sm-2 control-label' + .col-sm-10 + = text_area_tag :body, params[:body], rows: 10, class: 'form-control', required: true + %p.help-block You can use the following variables in your email: {name}, {conference}, {registrationlink} + + .form-group + = label_tag :filter_type, 'Send to', class: 'col-sm-2 control-label' + .col-sm-10 + = select_tag :filter_type, options_for_select([ + ['All registered users', 'all_registered'], + ['Users who haven\'t answered any questions', 'no_questions_answered'], + ['Users who answered some but not all questions', 'some_questions_answered'], + ['Users who answered all questions', 'all_questions_answered'] + ], params[:filter_type]), { class: 'form-control', required: true } + + - if @questions.any? + .form-group + .col-sm-offset-2.col-sm-10 + %h4 Registration Questions + %ul + - @questions.each do |question| + %li= question.title + + .form-group + .col-sm-offset-2.col-sm-10 + = submit_tag 'Send Bulk Email', class: 'btn btn-primary', data: { confirm: 'Are you sure you want to send this email to all selected recipients?' } + = link_to 'Cancel', admin_conference_emails_path(@conference.short_title), class: 'btn btn-default' + + .col-md-4 + .panel.panel-default + .panel-heading + %h4 Recipient Preview + .panel-body + %p + %strong All registered users: + = @conference.registrations.count + - if @questions.any? + %p + %strong No questions answered: + = @registrations.select { |r| r.qanswers.empty? }.count + %p + %strong Some questions answered: + = @registrations.select { |r| r.qanswers.any? && r.qanswers.count < @questions.count }.count + %p + %strong All questions answered: + = @registrations.select { |r| r.qanswers.count >= @questions.count }.count + - else + %p + %em No registration questions configured for this conference. diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml index e296d824..dc4cd328 100644 --- a/app/views/admin/emails/index.html.haml +++ b/app/views/admin/emails/index.html.haml @@ -16,6 +16,8 @@ %a{ 'aria-controls' => 'cfp', 'data-toggle' => 'tab', href: '#cfp', role: 'tab' } Call for Papers %li{ role: 'presentation' } %a{ 'aria-controls' => 'booths', 'data-toggle' => 'tab', href: '#booth', role: 'tab' } Booth + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'bulk', 'data-toggle' => 'tab', href: '#bulk', role: 'tab' } Bulk Email / Tab panes .tab-content #onboarding.tab-pane.active{ role: 'tabpanel' } @@ -211,6 +213,17 @@ 'data-body-text' => "Dear {name},\n\nThank you for your #{t'booth'} request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} + #bulk.tab-pane{ role: 'tabpanel' } + %p Send customized emails to registered users based on their registration status. + .form-group + = link_to 'Go to Bulk Email', bulk_admin_conference_emails_path(@conference.short_title), class: 'btn btn-success' + %p + %strong Available filters: + %ul + %li All registered users + %li Users who haven't answered any registration questions + %li Users who answered some but not all questions + %li Users who answered all questions .row .col-md-12 diff --git a/config/routes.rb b/config/routes.rb index 730d43cf..04dba8cd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -115,7 +115,12 @@ Osem::Application.routes.draw do resources :tickets resources :sponsors, except: [:show] resources :lodgings, except: [:show] - resources :emails, only: [:show, :update, :index] + resources :emails, only: [:show, :update, :index] do + collection do + get :bulk + post :send_bulk + end + end resources :physical_tickets, only: [:index] resources :roles, except: [:new, :create] do member do