Commit iniziale di eminuxCRM: CRM Rails con pipeline, campagne email e Docker.
Include autenticazione, progetti isolati, mail marketing HTML con SMTP, test A/B e editor WYSIWYG. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
class MailingRecipient < ApplicationRecord
|
||||
belongs_to :mailing
|
||||
belongs_to :organization
|
||||
belongs_to :contact, optional: true
|
||||
|
||||
validates :status, inclusion: { in: Catalog::MAILING_RECIPIENT_STATUSES.keys }
|
||||
validates :ab_variant, inclusion: { in: Catalog::AB_VARIANTS.keys }, allow_blank: true
|
||||
|
||||
scope :pending, -> { where(status: "pending") }
|
||||
scope :skipped, -> { where(status: "skipped") }
|
||||
scope :sent, -> { where(status: "sent") }
|
||||
scope :failed, -> { where(status: "failed") }
|
||||
scope :queued, -> { where(status: "queued") }
|
||||
scope :ordered, -> { joins(:organization).order("organizations.list_position ASC NULLS LAST", "organizations.name ASC") }
|
||||
|
||||
def status_label
|
||||
Catalog.label_for(Catalog::MAILING_RECIPIENT_STATUSES, status)
|
||||
end
|
||||
|
||||
def pending?
|
||||
status == "pending"
|
||||
end
|
||||
|
||||
def email_ok?
|
||||
email.present? && email.match?(URI::MailTo::EMAIL_REGEXP)
|
||||
end
|
||||
|
||||
def merge_context
|
||||
opportunity = organization.campaign_opportunity(mailing.project)
|
||||
{
|
||||
organization: organization,
|
||||
contact: contact,
|
||||
project: mailing.project,
|
||||
opportunity: opportunity,
|
||||
ab_variant: ab_variant.presence || opportunity&.ab_variant
|
||||
}
|
||||
end
|
||||
|
||||
def rendered_html
|
||||
MailMerge.render(mailing.body_for(ab_variant), **merge_context)
|
||||
end
|
||||
|
||||
def rendered_subject_line
|
||||
MailMerge.render(mailing.subject_for(ab_variant), **merge_context)
|
||||
end
|
||||
|
||||
def deliver!
|
||||
with_lock do
|
||||
return if status.in?(%w[sent skipped])
|
||||
|
||||
update!(status: "queued")
|
||||
end
|
||||
|
||||
html = rendered_html
|
||||
subject_line = rendered_subject_line
|
||||
CampaignMailer.outreach(self, html: html, subject: subject_line).deliver_now
|
||||
record_success!(subject_line)
|
||||
rescue StandardError => e
|
||||
update!(status: "failed", error_message: e.message.to_s.truncate(500))
|
||||
ensure
|
||||
mailing.mark_finished_if_done!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def record_success!(subject_line)
|
||||
transaction do
|
||||
update!(status: "sent", sent_at: Time.current, rendered_subject: subject_line, error_message: nil)
|
||||
sync_opportunity!
|
||||
log_activity!(subject_line)
|
||||
end
|
||||
end
|
||||
|
||||
def sync_opportunity!
|
||||
opportunity = organization.campaign_opportunity(mailing.project)
|
||||
return unless opportunity
|
||||
|
||||
attrs = {}
|
||||
attrs[:send_status] = "sent" if opportunity.send_status == "to_send"
|
||||
attrs[:sent_on] ||= Date.current if opportunity.sent_on.blank?
|
||||
attrs[:pipeline_stage] = "contacted" if opportunity.pipeline_stage == "to_contact"
|
||||
attrs[:ab_variant] = ab_variant if mailing.ab_test? && ab_variant.present? && opportunity.ab_variant.blank?
|
||||
opportunity.update!(attrs) if attrs.any?
|
||||
end
|
||||
|
||||
def log_activity!(subject_line)
|
||||
variant_note = "variante #{ab_variant}" if mailing.ab_test? && ab_variant.present?
|
||||
organization.activities.create!(
|
||||
activity_type: "email_sent",
|
||||
subject: subject_line.presence || mailing.name,
|
||||
description: ["Campagna email: #{mailing.name}", variant_note].compact.join(" · "),
|
||||
happened_at: Time.current,
|
||||
user: mailing.created_by,
|
||||
contact: contact,
|
||||
opportunity: organization.campaign_opportunity(mailing.project)
|
||||
)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user