Permette di mettere in pausa un invio e riprenderlo senza perdere la coda.
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / scan_ruby (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-07 12:35:42 +02:00
co-authored by Cursor
parent 4cf34b2033
commit 3c8bfe053f
10 changed files with 108 additions and 17 deletions
+33 -6
View File
@@ -1,6 +1,6 @@
class MailingsController < ApplicationController class MailingsController < ApplicationController
before_action :require_current_project! 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] before_action :load_form_collections, only: %i[new create edit update]
def index def index
@@ -11,8 +11,9 @@ class MailingsController < ApplicationController
@page_title = "Invii email" @page_title = "Invii email"
@mailings = Mailing.for_project(current_project).includes(:mail_identity).recent @mailings = Mailing.for_project(current_project).includes(:mail_identity).recent
@mail_identities_count = MailIdentity.active.count @mail_identities_count = MailIdentity.active.count
@active_mailings = @mailings.select(&:sending?) @active_mailings = @mailings.select { |mailing| mailing.sending? || mailing.paused? }
@paused_mailings = @active_mailings.select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current } @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 } @completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today }
@failed_open = @mailings.sum(&:failed_count) @failed_open = @mailings.sum(&:failed_count)
end end
@@ -180,16 +181,42 @@ class MailingsController < ApplicationController
redirect_to @mailing, alert: e.message redirect_to @mailing, alert: e.message
end end
def cancel def pause
unless @mailing.sending? 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 return
end end
pending = @mailing.pending_count + @mailing.queued_count pending = @mailing.pending_count + @mailing.queued_count
@mailing.cancel_send! @mailing.cancel_send!
redirect_to dashboard_mailings_path, 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 rescue StandardError => e
redirect_to @mailing, alert: e.message redirect_to @mailing, alert: e.message
end end
+1
View File
@@ -163,6 +163,7 @@ module ApplicationHelper
colors = { colors = {
"draft" => "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200", "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", "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", "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" "cancelled" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
} }
+1
View File
@@ -68,6 +68,7 @@ module Catalog
MAILING_STATUSES = { MAILING_STATUSES = {
"draft" => "Bozza", "draft" => "Bozza",
"sending" => "Invio in corso", "sending" => "Invio in corso",
"paused" => "In pausa",
"sent" => "Inviata", "sent" => "Inviata",
"cancelled" => "Annullata" "cancelled" => "Annullata"
}.freeze }.freeze
+20 -1
View File
@@ -77,6 +77,10 @@ class Mailing < ApplicationRecord
status == "sending" status == "sending"
end end
def paused?
status == "paused"
end
def sent? def sent?
status == "sent" status == "sent"
end end
@@ -215,8 +219,23 @@ class Mailing < ApplicationRecord
update!(status: "sent", completed_at: Time.current, next_send_at: nil) update!(status: "sent", completed_at: Time.current, next_send_at: nil)
end 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! 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 transaction do
mailing_recipients.where(status: %w[pending queued]).find_each do |recipient| mailing_recipients.where(status: %w[pending queued]).find_each do |recipient|
+2 -4
View File
@@ -31,11 +31,9 @@
<% elsif mailing.pending_count.positive? %> <% elsif mailing.pending_count.positive? %>
<p class="mt-3 text-xs text-zinc-500">Prossima email in lavorazione.</p> <p class="mt-3 text-xs text-zinc-500">Prossima email in lavorazione.</p>
<% end %> <% end %>
<% if mailing.sending? %> <% if mailing.sending? || mailing.paused? %>
<div class="mt-4 border-t border-zinc-100 pt-4 dark:border-zinc-800"> <div class="mt-4 border-t border-zinc-100 pt-4 dark:border-zinc-800">
<%= button_to "Ferma invio", cancel_mailing_path(mailing), method: :post, <%= render "mailings/send_controls", mailing: mailing %>
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." } } %>
</div> </div>
<% end %> <% end %>
</div> </div>
@@ -0,0 +1,17 @@
<% if mailing.sending? %>
<div class="flex flex-wrap gap-2">
<%= 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." } } %>
</div>
<% elsif mailing.paused? %>
<div class="flex flex-wrap gap-2">
<%= 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." } } %>
</div>
<% end %>
+2
View File
@@ -77,6 +77,8 @@
<%= mailing.sent_count %> inviate · <%= mailing.opened_count %> aperte · <%= mailing.pending_count %> in coda <%= 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 %> <% if mailing.sending? && mailing.next_send_at.present? && mailing.next_send_at > Time.current %>
· Riparte <%= l(mailing.next_send_at, format: :short) %> · Riparte <%= l(mailing.next_send_at, format: :short) %>
<% elsif mailing.paused? %>
· In pausa, si può riprendere
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
+4 -6
View File
@@ -34,15 +34,13 @@
<%= link_to "Raffina destinatari", audience_mailing_path(@mailing), class: btn_secondary %> <%= 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 %> <%= 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" %> <%= render "mailings/delete_draft_button", mailing: @mailing, extra_class: "flex-1 sm:flex-none" %>
<% elsif @mailing.sending? %> <% elsif @mailing.sending? || @mailing.paused? %>
<%= button_to "Ferma invio", cancel_mailing_path(@mailing), method: :post, <%= render "mailings/send_controls", mailing: @mailing %>
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." } } %>
<% end %> <% end %>
</div> </div>
</div> </div>
<% if @mailing.sending? || @mailing.sent? %> <% if @mailing.sending? || @mailing.paused? || @mailing.sent? %>
<%= render "mailings/progress_card", mailing: @mailing %> <%= render "mailings/progress_card", mailing: @mailing %>
<% end %> <% end %>
@@ -65,7 +63,7 @@
</div> </div>
</div> </div>
<% if @mailing.sending? || @mailing.sent? || @mailing.cancelled? %> <% if @mailing.sending? || @mailing.paused? || @mailing.sent? || @mailing.cancelled? %>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div class="<%= card_class %> p-4"> <div class="<%= card_class %> p-4">
<div class="text-xs uppercase tracking-wide text-zinc-500">Aperte</div> <div class="text-xs uppercase tracking-wide text-zinc-500">Aperte</div>
+2
View File
@@ -74,6 +74,8 @@ Rails.application.routes.draw do
end end
member do member do
post :queue post :queue
post :pause
post :resume
post :cancel post :cancel
post :test_send post :test_send
get :test_preview get :test_preview
@@ -303,6 +303,32 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest
assert mailing.reload.sending? assert mailing.reload.sending?
end 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 test "cancel stops pending recipients and keeps sent ones" do
login_as users(:admin) login_as users(:admin)
follow_redirect! if response.redirect? follow_redirect! if response.redirect?