From 3c8bfe053f952147af5f7c9b7625798e56c5b5e0 Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Mon, 7 Sep 2026 12:35:42 +0200 Subject: [PATCH] Permette di mettere in pausa un invio e riprenderlo senza perdere la coda. Co-authored-by: Cursor --- app/controllers/mailings_controller.rb | 39 +++++++++++++++++--- app/helpers/application_helper.rb | 1 + app/models/catalog.rb | 1 + app/models/mailing.rb | 21 ++++++++++- app/views/mailings/_progress_card.html.erb | 6 +-- app/views/mailings/_send_controls.html.erb | 17 +++++++++ app/views/mailings/dashboard.html.erb | 2 + app/views/mailings/show.html.erb | 10 ++--- config/routes.rb | 2 + test/controllers/mailings_controller_test.rb | 26 +++++++++++++ 10 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 app/views/mailings/_send_controls.html.erb diff --git a/app/controllers/mailings_controller.rb b/app/controllers/mailings_controller.rb index d057bdb..4dcbbed 100644 --- a/app/controllers/mailings_controller.rb +++ b/app/controllers/mailings_controller.rb @@ -1,6 +1,6 @@ class MailingsController < ApplicationController before_action :require_current_project! - before_action :set_mailing, only: %i[show edit update destroy queue cancel test_send test_preview refresh_recipients update_recipients preview audience update_audience] + before_action :set_mailing, only: %i[show edit update destroy queue pause resume cancel test_send test_preview refresh_recipients update_recipients preview audience update_audience] before_action :load_form_collections, only: %i[new create edit update] def index @@ -11,8 +11,9 @@ class MailingsController < ApplicationController @page_title = "Invii email" @mailings = Mailing.for_project(current_project).includes(:mail_identity).recent @mail_identities_count = MailIdentity.active.count - @active_mailings = @mailings.select(&:sending?) - @paused_mailings = @active_mailings.select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current } + @active_mailings = @mailings.select { |mailing| mailing.sending? || mailing.paused? } + @window_paused_mailings = @mailings.select(&:sending?).select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current } + @paused_mailings = @window_paused_mailings @completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today } @failed_open = @mailings.sum(&:failed_count) end @@ -180,16 +181,42 @@ class MailingsController < ApplicationController redirect_to @mailing, alert: e.message end - def cancel + def pause unless @mailing.sending? - redirect_to @mailing, alert: "Solo un invio in corso può essere fermato." + redirect_to @mailing, alert: "Solo un invio in corso può essere messo in pausa." + return + end + + remaining = @mailing.pending_count + @mailing.queued_count + @mailing.pause_send! + redirect_to dashboard_mailings_path, + notice: "Invio in pausa. #{@mailing.sent_count} già inviate, #{remaining} pronte per quando riprendi." + rescue StandardError => e + redirect_to @mailing, alert: e.message + end + + def resume + unless @mailing.paused? + redirect_to @mailing, alert: "Solo un invio in pausa può essere ripreso." + return + end + + @mailing.resume_send! + redirect_to dashboard_mailings_path, notice: "Invio ripreso da dove era rimasto." + rescue StandardError => e + redirect_to @mailing, alert: e.message + end + + def cancel + unless @mailing.sending? || @mailing.paused? + redirect_to @mailing, alert: "Solo un invio in corso o in pausa può essere annullato." return end pending = @mailing.pending_count + @mailing.queued_count @mailing.cancel_send! redirect_to dashboard_mailings_path, - notice: "Invio fermato. #{@mailing.sent_count} già inviate, #{pending} annullate in coda." + notice: "Invio annullato. #{@mailing.sent_count} già inviate, #{pending} in coda saltate." rescue StandardError => e redirect_to @mailing, alert: e.message end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5a76373..bbf9028 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -163,6 +163,7 @@ module ApplicationHelper colors = { "draft" => "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200", "sending" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200", + "paused" => "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-200", "sent" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200", "cancelled" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200" } diff --git a/app/models/catalog.rb b/app/models/catalog.rb index 60394c3..891c77f 100644 --- a/app/models/catalog.rb +++ b/app/models/catalog.rb @@ -68,6 +68,7 @@ module Catalog MAILING_STATUSES = { "draft" => "Bozza", "sending" => "Invio in corso", + "paused" => "In pausa", "sent" => "Inviata", "cancelled" => "Annullata" }.freeze diff --git a/app/models/mailing.rb b/app/models/mailing.rb index a08e0c9..5b4ae2e 100644 --- a/app/models/mailing.rb +++ b/app/models/mailing.rb @@ -77,6 +77,10 @@ class Mailing < ApplicationRecord status == "sending" end + def paused? + status == "paused" + end + def sent? status == "sent" end @@ -215,8 +219,23 @@ class Mailing < ApplicationRecord update!(status: "sent", completed_at: Time.current, next_send_at: nil) end + def pause_send! + raise "Solo un invio in corso può essere messo in pausa" unless sending? + + update!(status: "paused", next_send_at: nil) + end + + def resume_send! + raise "Solo un invio in pausa può essere ripreso" unless paused? + raise "Nessun destinatario da inviare" if pending_count.zero? && queued_count.zero? + + update!(status: "sending") + enqueue_next_recipient!(mailing_recipients.pending.order(:id).first, from: Time.current) if pending_count.positive? + DrainOutboundJob.perform_later if mailing_recipients.queued.exists? + end + def cancel_send! - raise "Solo un invio in corso può essere fermato" unless sending? + raise "Solo un invio in corso o in pausa può essere annullato" unless sending? || paused? transaction do mailing_recipients.where(status: %w[pending queued]).find_each do |recipient| diff --git a/app/views/mailings/_progress_card.html.erb b/app/views/mailings/_progress_card.html.erb index 4146a40..e5dc5fd 100644 --- a/app/views/mailings/_progress_card.html.erb +++ b/app/views/mailings/_progress_card.html.erb @@ -31,11 +31,9 @@ <% elsif mailing.pending_count.positive? %>

Prossima email in lavorazione.

<% end %> - <% if mailing.sending? %> + <% if mailing.sending? || mailing.paused? %>
- <%= button_to "Ferma invio", cancel_mailing_path(mailing), method: :post, - class: btn_danger, - form: { data: { turbo_confirm: "Fermare l'invio? Restano inviate #{mailing.sent_count} email, #{mailing.pending_count + mailing.queued_count} in coda verranno annullate." } } %> + <%= render "mailings/send_controls", mailing: mailing %>
<% end %> diff --git a/app/views/mailings/_send_controls.html.erb b/app/views/mailings/_send_controls.html.erb new file mode 100644 index 0000000..1d0fd46 --- /dev/null +++ b/app/views/mailings/_send_controls.html.erb @@ -0,0 +1,17 @@ +<% if mailing.sending? %> +
+ <%= button_to "Metti in pausa", pause_mailing_path(mailing), method: :post, + class: btn_secondary, + form: { data: { turbo_confirm: "Mettere in pausa? Le #{mailing.sent_count} già inviate restano. Le #{mailing.pending_count + mailing.queued_count} non ancora partite si riprendono da qui." } } %> + <%= button_to "Annulla definitivamente", cancel_mailing_path(mailing), method: :post, + class: btn_danger, + form: { data: { turbo_confirm: "Annullare l'invio? Le #{mailing.sent_count} già inviate restano, le #{mailing.pending_count + mailing.queued_count} in coda verranno saltate e non si potranno riprendere." } } %> +
+<% elsif mailing.paused? %> +
+ <%= button_to "Riprendi invio", resume_mailing_path(mailing), method: :post, class: btn_primary %> + <%= button_to "Annulla definitivamente", cancel_mailing_path(mailing), method: :post, + class: btn_danger, + form: { data: { turbo_confirm: "Annullare l'invio? Le #{mailing.sent_count} già inviate restano, le #{mailing.pending_count + mailing.queued_count} in coda verranno saltate e non si potranno riprendere." } } %> +
+<% end %> diff --git a/app/views/mailings/dashboard.html.erb b/app/views/mailings/dashboard.html.erb index 24cb10f..342065d 100644 --- a/app/views/mailings/dashboard.html.erb +++ b/app/views/mailings/dashboard.html.erb @@ -77,6 +77,8 @@ <%= mailing.sent_count %> inviate · <%= mailing.opened_count %> aperte · <%= mailing.pending_count %> in coda <% if mailing.sending? && mailing.next_send_at.present? && mailing.next_send_at > Time.current %> · Riparte <%= l(mailing.next_send_at, format: :short) %> + <% elsif mailing.paused? %> + · In pausa, si può riprendere <% end %> <% end %> diff --git a/app/views/mailings/show.html.erb b/app/views/mailings/show.html.erb index f8314ba..8183e18 100644 --- a/app/views/mailings/show.html.erb +++ b/app/views/mailings/show.html.erb @@ -34,15 +34,13 @@ <%= link_to "Raffina destinatari", audience_mailing_path(@mailing), class: btn_secondary %> <%= button_to "Rigenera lista", refresh_recipients_mailing_path(@mailing), method: :post, class: btn_secondary %> <%= render "mailings/delete_draft_button", mailing: @mailing, extra_class: "flex-1 sm:flex-none" %> - <% elsif @mailing.sending? %> - <%= button_to "Ferma invio", cancel_mailing_path(@mailing), method: :post, - class: btn_danger, - form: { data: { turbo_confirm: "Fermare l'invio? Le #{@mailing.sent_count} già inviate restano, #{@mailing.pending_count + @mailing.queued_count} in coda verranno annullate." } } %> + <% elsif @mailing.sending? || @mailing.paused? %> + <%= render "mailings/send_controls", mailing: @mailing %> <% end %> - <% if @mailing.sending? || @mailing.sent? %> + <% if @mailing.sending? || @mailing.paused? || @mailing.sent? %> <%= render "mailings/progress_card", mailing: @mailing %> <% end %> @@ -65,7 +63,7 @@ - <% if @mailing.sending? || @mailing.sent? || @mailing.cancelled? %> + <% if @mailing.sending? || @mailing.paused? || @mailing.sent? || @mailing.cancelled? %>
Aperte
diff --git a/config/routes.rb b/config/routes.rb index 18e455b..6026d03 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,8 @@ Rails.application.routes.draw do end member do post :queue + post :pause + post :resume post :cancel post :test_send get :test_preview diff --git a/test/controllers/mailings_controller_test.rb b/test/controllers/mailings_controller_test.rb index af08168..3f569c3 100644 --- a/test/controllers/mailings_controller_test.rb +++ b/test/controllers/mailings_controller_test.rb @@ -303,6 +303,32 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest assert mailing.reload.sending? end + test "pause keeps remaining recipients and resume continues from there" do + login_as users(:admin) + follow_redirect! if response.redirect? + create_campaign_org(name: "Seconda societa", email: "seconda@example.com") + mailing = create_mailing(identity: @identity, audience: "to_send") + mailing.rebuild_recipients! + recipients = mailing.mailing_recipients.order(:id).to_a + assert recipients.size >= 2 + recipients.first.update!(status: "sent", sent_at: Time.current) + recipients.second.update!(status: "queued") + mailing.update!(status: "sending", queued_at: Time.current, test_sent_at: Time.current, test_sent_to: "test@example.com") + + post pause_mailing_path(mailing, project_code: @project.code) + + assert_redirected_to dashboard_mailings_path(project_code: @project.code) + assert_equal "paused", mailing.reload.status + assert_equal "sent", recipients.first.reload.status + assert_equal "queued", recipients.second.reload.status + + post resume_mailing_path(mailing, project_code: @project.code) + + assert_redirected_to dashboard_mailings_path(project_code: @project.code) + assert_equal "sending", mailing.reload.status + assert_equal "queued", recipients.second.reload.status + end + test "cancel stops pending recipients and keeps sent ones" do login_as users(:admin) follow_redirect! if response.redirect?