corretti bug sull'invio email
This commit is contained in:
@@ -306,6 +306,7 @@ class MailingsController < ApplicationController
|
|||||||
def load_audience_options
|
def load_audience_options
|
||||||
orgs = Organization.for_project(current_project)
|
orgs = Organization.for_project(current_project)
|
||||||
@sport_options = orgs.where.not(sport: [nil, ""]).distinct.order(:sport).pluck(:sport)
|
@sport_options = orgs.where.not(sport: [nil, ""]).distinct.order(:sport).pluck(:sport)
|
||||||
|
@country_options = orgs.where.not(country: [nil, ""]).distinct.order(:country).pluck(:country)
|
||||||
@region_options = orgs.where.not(region: [nil, ""]).distinct.order(:region).pluck(:region)
|
@region_options = orgs.where.not(region: [nil, ""]).distinct.order(:region).pluck(:region)
|
||||||
@province_options = orgs.where.not(province: [nil, ""]).distinct.order(:province).pluck(:province)
|
@province_options = orgs.where.not(province: [nil, ""]).distinct.order(:province).pluck(:province)
|
||||||
@history_mailings = Mailing.for_project(current_project).where.not(id: @mailing.id).recent
|
@history_mailings = Mailing.for_project(current_project).where.not(id: @mailing.id).recent
|
||||||
@@ -329,7 +330,7 @@ class MailingsController < ApplicationController
|
|||||||
ActionController::Parameters.new(copied).permit(
|
ActionController::Parameters.new(copied).permit(
|
||||||
:preset, :list_min, :list_max, :estimated_value, :history_kind, :history_days,
|
:preset, :list_min, :list_max, :estimated_value, :history_kind, :history_days,
|
||||||
:history_mailing_id, :never_contacted, :exclude_customers, :exclude_mailed_within_days,
|
:history_mailing_id, :never_contacted, :exclude_customers, :exclude_mailed_within_days,
|
||||||
sports: [], team_genders: [], regions: [], provinces: [], streaming_statuses: [], statuses: [],
|
sports: [], countries: [], team_genders: [], regions: [], provinces: [], streaming_statuses: [], statuses: [],
|
||||||
send_statuses: [], ab_variants: [], pipeline_stages: [],
|
send_statuses: [], ab_variants: [], pipeline_stages: [],
|
||||||
exclude_received_mailing_ids: [], exclude_opened_mailing_ids: []
|
exclude_received_mailing_ids: [], exclude_opened_mailing_ids: []
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -115,7 +115,12 @@ class MailingRecipient < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
MAX_SMTP_DEFERS = 8
|
MAX_SMTP_DEFERS = 8
|
||||||
DEFER_PATTERN = /tentativo (\d+)\//
|
# Deve combaciare col testo generato sotto ("Da ritentare (N/8): ..."),
|
||||||
|
# altrimenti smtp_defer_count legge sempre 0 e il retry diventa infinito
|
||||||
|
# (bug osservato in produzione: un destinatario con SMTP EOF permanente
|
||||||
|
# veniva ritentato ogni 30s all'infinito, bloccando gli altri destinatari
|
||||||
|
# in errore della stessa mailing).
|
||||||
|
DEFER_PATTERN = /Da ritentare \((\d+)\//
|
||||||
|
|
||||||
def defer_or_fail!(error)
|
def defer_or_fail!(error)
|
||||||
attempt = smtp_defer_count + 1
|
attempt = smtp_defer_count + 1
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Mailings
|
|||||||
VALUE_MODES = %w[any present blank].freeze
|
VALUE_MODES = %w[any present blank].freeze
|
||||||
|
|
||||||
ARRAY_KEYS = %w[
|
ARRAY_KEYS = %w[
|
||||||
sports team_genders regions provinces streaming_statuses statuses
|
sports countries team_genders regions provinces streaming_statuses statuses
|
||||||
send_statuses ab_variants pipeline_stages
|
send_statuses ab_variants pipeline_stages
|
||||||
exclude_received_mailing_ids exclude_opened_mailing_ids
|
exclude_received_mailing_ids exclude_opened_mailing_ids
|
||||||
].freeze
|
].freeze
|
||||||
@@ -46,6 +46,7 @@ module Mailings
|
|||||||
def summary_parts
|
def summary_parts
|
||||||
parts = [Catalog.label_for(Catalog::MAILING_AUDIENCES, preset)]
|
parts = [Catalog.label_for(Catalog::MAILING_AUDIENCES, preset)]
|
||||||
parts << "sport: #{Array(@data["sports"]).join(", ")}" if @data["sports"].present?
|
parts << "sport: #{Array(@data["sports"]).join(", ")}" if @data["sports"].present?
|
||||||
|
parts << "nazione: #{Array(@data["countries"]).join(", ")}" if @data["countries"].present?
|
||||||
parts << "M/F: #{Array(@data["team_genders"]).map { |g| Catalog.label_for(Catalog::TEAM_GENDERS, g) }.join(", ")}" if @data["team_genders"].present?
|
parts << "M/F: #{Array(@data["team_genders"]).map { |g| Catalog.label_for(Catalog::TEAM_GENDERS, g) }.join(", ")}" if @data["team_genders"].present?
|
||||||
parts << "regione: #{Array(@data["regions"]).join(", ")}" if @data["regions"].present?
|
parts << "regione: #{Array(@data["regions"]).join(", ")}" if @data["regions"].present?
|
||||||
parts << "provincia: #{Array(@data["provinces"]).join(", ")}" if @data["provinces"].present?
|
parts << "provincia: #{Array(@data["provinces"]).join(", ")}" if @data["provinces"].present?
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ module Mailings
|
|||||||
|
|
||||||
def apply_organization_filters(scope)
|
def apply_organization_filters(scope)
|
||||||
scope = scope.where(sport: @filters["sports"]) if @filters["sports"].present?
|
scope = scope.where(sport: @filters["sports"]) if @filters["sports"].present?
|
||||||
|
scope = scope.where(country: @filters["countries"]) if @filters["countries"].present?
|
||||||
scope = scope.where(team_gender: @filters["team_genders"]) if @filters["team_genders"].present?
|
scope = scope.where(team_gender: @filters["team_genders"]) if @filters["team_genders"].present?
|
||||||
scope = scope.where(region: @filters["regions"]) if @filters["regions"].present?
|
scope = scope.where(region: @filters["regions"]) if @filters["regions"].present?
|
||||||
scope = scope.where(province: @filters["provinces"]) if @filters["provinces"].present?
|
scope = scope.where(province: @filters["provinces"]) if @filters["provinces"].present?
|
||||||
|
|||||||
@@ -48,11 +48,16 @@ class Mailings::OutboundQueue
|
|||||||
end
|
end
|
||||||
|
|
||||||
def next_queued
|
def next_queued
|
||||||
|
# Sceglie il destinatario piu vecchio (per priorita/updated_at) TRA le mailing
|
||||||
|
# la cui finestra di invio e attualmente aperta. Prima si sceglieva il piu vecchio
|
||||||
|
# in assoluto: se apparteneva a una mailing con finestra chiusa, drain_one!
|
||||||
|
# restituiva :closed e si fermava, bloccando l'intera coda condivisa anche per
|
||||||
|
# le mailing senza vincoli orari (bug: coda bloccata da mailing con orario chiuso).
|
||||||
queued_scope.order(
|
queued_scope.order(
|
||||||
Arel.sql(
|
Arel.sql(
|
||||||
"CASE WHEN mailing_recipients.error_message IS NULL OR mailing_recipients.error_message = '' THEN 0 ELSE 1 END, mailing_recipients.updated_at ASC, mailing_recipients.id ASC"
|
"CASE WHEN mailing_recipients.error_message IS NULL OR mailing_recipients.error_message = '' THEN 0 ELSE 1 END, mailing_recipients.updated_at ASC, mailing_recipients.id ASC"
|
||||||
)
|
)
|
||||||
).first
|
).includes(:mailing).find { |r| Mailings::SendClock.new(r.mailing).open? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def queued_scope
|
def queued_scope
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<section class="<%= card_class %> space-y-4 p-4 md:p-6">
|
<section class="<%= card_class %> space-y-4 p-4 md:p-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base font-semibold">Chi deve ricevere questa email?</h2>
|
<h2 class="text-base font-semibold">Chi deve ricevere questa email?</h2>
|
||||||
<p class="mt-1 text-sm text-zinc-500">Lista di partenza, sport, regione e chi non ha mai ricevuto una email. Il resto è opzionale.</p>
|
<p class="mt-1 text-sm text-zinc-500">Lista di partenza, sport, nazione, regione e chi non ha mai ricevuto una email. Il resto è opzionale.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -47,12 +47,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Sport</label>
|
<label class="mb-1 block text-sm font-medium">Sport</label>
|
||||||
<%= render "mailings/audience_choices", name: "filters[sports]",
|
<%= render "mailings/audience_choices", name: "filters[sports]",
|
||||||
choices: @sport_options.map { |sport| [sport, sport] }, selected: @filters["sports"] %>
|
choices: @sport_options.map { |sport| [sport, sport] }, selected: @filters["sports"] %>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-sm font-medium">Nazione</label>
|
||||||
|
<%= render "mailings/audience_choices", name: "filters[countries]",
|
||||||
|
choices: @country_options.map { |country| [country, country] }, selected: @filters["countries"] %>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1 block text-sm font-medium">Regione</label>
|
<label class="mb-1 block text-sm font-medium">Regione</label>
|
||||||
<%= render "mailings/audience_choices", name: "filters[regions]",
|
<%= render "mailings/audience_choices", name: "filters[regions]",
|
||||||
|
|||||||
@@ -85,6 +85,33 @@ class Mailings::OutboundQueueTest < ActiveSupport::TestCase
|
|||||||
assert_equal "queued", recipient_a.status
|
assert_equal "queued", recipient_a.status
|
||||||
assert_match(/Da ritentare \(1\/8\)/, recipient_a.error_message)
|
assert_match(/Da ritentare \(1\/8\)/, recipient_a.error_message)
|
||||||
assert_equal recipient_b.id, Mailings::OutboundQueue.next_queued.id
|
assert_equal recipient_b.id, Mailings::OutboundQueue.next_queued.id
|
||||||
|
|
||||||
|
# Il contatore deve avanzare sullo stesso destinatario (prima il regex
|
||||||
|
# non matchava "Da ritentare (N/8)" e restava sempre tentativo 1).
|
||||||
|
begin
|
||||||
|
gate.define_singleton_method(:deliver) { raise EOFError, "end of file reached" }
|
||||||
|
assert_equal :deferred, recipient_a.deliver_queued!
|
||||||
|
ensure
|
||||||
|
gate.define_singleton_method(:deliver, original)
|
||||||
|
end
|
||||||
|
assert_match(/Da ritentare \(2\/8\)/, recipient_a.reload.error_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "next_queued skips recipients whose send window is closed" do
|
||||||
|
open_org = create_campaign_org(name: "Club Aperto", email: "aperto@example.com")
|
||||||
|
closed_org = create_campaign_org(name: "Club Chiuso", email: "chiuso@example.com")
|
||||||
|
open_mailing = create_sending_mailing(name: "Finestra aperta")
|
||||||
|
closed_mailing = create_sending_mailing(name: "Finestra chiusa")
|
||||||
|
closed_mailing.update!(send_window_enabled: true, send_window_start_minutes: 8 * 60, send_window_end_minutes: 9 * 60)
|
||||||
|
open_mailing.update!(send_window_enabled: false)
|
||||||
|
|
||||||
|
closed_recipient = enqueue_org(closed_mailing, closed_org)
|
||||||
|
open_recipient = enqueue_org(open_mailing, open_org)
|
||||||
|
closed_recipient.update_columns(updated_at: 1.hour.ago)
|
||||||
|
|
||||||
|
travel_to Time.zone.local(2026, 9, 10, 20, 0, 0) do
|
||||||
|
assert_equal open_recipient.id, Mailings::OutboundQueue.next_queued.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ class MailingsAudienceQueryTest < ActiveSupport::TestCase
|
|||||||
assert_not_includes ids, @volley.id
|
assert_not_includes ids, @volley.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "filters by country" do
|
||||||
|
@volley.update!(country: "Francia")
|
||||||
|
@acme.update!(country: "Italia")
|
||||||
|
ids = query({ "preset" => "all", "countries" => ["Francia"] }).relation.pluck(:id)
|
||||||
|
assert_includes ids, @volley.id
|
||||||
|
assert_not_includes ids, @acme.id
|
||||||
|
end
|
||||||
|
|
||||||
test "filters by list position range" do
|
test "filters by list position range" do
|
||||||
ids = query({ "preset" => "all", "list_min" => 1, "list_max" => 5 }).relation.pluck(:id)
|
ids = query({ "preset" => "all", "list_min" => 1, "list_max" => 5 }).relation.pluck(:id)
|
||||||
assert_includes ids, @acme.id
|
assert_includes ids, @acme.id
|
||||||
|
|||||||
Reference in New Issue
Block a user