Files
eminuxCRM/app/services/campaign_import/matchlivetv_launch.rb
T
eminuxandCursor e492c43bf8
CI / scan_ruby (push) Failing after 15m50s
CI / scan_js (push) Successful in 18m7s
CI / lint (push) Failing after 12m1s
Importa i prospect Calcio a 5 senza toccare la Pallavolo e filtra le organizzazioni per sport.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 11:07:43 +02:00

259 lines
7.2 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
require "csv"
class CampaignImport::MatchlivetvLaunch
CAMPAIGN_NAME = "Campagna lancio 01".freeze
SPORT = "Pallavolo".freeze
LEGAL_PREFIXES = /\b(ssdarl|ssd arl|a\.?s\.?d\.?|asd|s\.?s\.?d\.?|ssd|s\.?s\.?|a\.?f\.?|a\.?p\.?|c\.?s\.?|polisportiva)\b/i
Result = Struct.new(:wiped_organizations, :imported, :skipped, :errors, keyword_init: true)
STREAMING_MAP = {
"NON RILEVATO" => "not_detected",
"LIMITATO" => "limited",
"SI / PARZIALE" => "yes_partial",
"SI" => "yes",
"SI - SPORTCAM" => "yes_sportcam",
"SI SPORTCAM" => "yes_sportcam"
}.freeze
GENDER_MAP = {
"F" => "female",
"M" => "male",
"M/F" => "mixed"
}.freeze
SEND_MAP = {
"DA INVIARE" => "to_send",
"INVIATO" => "sent",
"NON INVIARE" => "no_send"
}.freeze
def initialize(path:, user: nil, project: nil, wipe: true, sport: SPORT, campaign_name: CAMPAIGN_NAME)
@path = Pathname.new(path)
@user = user
@project = project
@wipe = wipe
@sport = sport
@campaign_name = campaign_name
end
def call
raise ArgumentError, "File non trovato: #{@path}" unless @path.exist?
@project ||= Project.find_by!(code: "matchlivetv")
@user ||= User.find_by!(role: "admin")
Current.user = @user
Current.project = @project
wiped = @wipe ? wipe_project! : 0
index_existing_names!
imported = 0
skipped = 0
errors = []
rows.each_with_index do |row, idx|
name = cell(row, "Società")
if duplicate_name?(name)
skipped += 1
next
end
import_row!(row)
remember_name!(name)
imported += 1
rescue StandardError => e
message = e.is_a?(ActiveRecord::RecordInvalid) ? e.record.errors.full_messages.to_sentence : e.message
errors << { line: idx + 2, name: row["Società"], message: message }
end
Result.new(wiped_organizations: wiped, imported: imported, skipped: skipped, errors: errors)
ensure
Current.user = nil
Current.project = nil
end
private
def rows
CSV.read(@path, headers: true, encoding: "bom|utf-8")
end
def wipe_project!
destroyed = 0
Organization.transaction do
Opportunity.where(project_id: @project.id).find_each(&:destroy!)
org_ids = OrganizationProject.where(project_id: @project.id).pluck(:organization_id)
OrganizationProject.where(project_id: @project.id).delete_all
Organization.where(id: org_ids).find_each do |org|
next if org.organization_projects.exists?
org.destroy!
destroyed += 1
end
end
destroyed
end
def import_row!(row)
name = cell(row, "Società")
raise "Società vuota" if name.blank?
org = Organization.new(
name: name,
organization_type: "societa_sportiva",
sport: @sport,
country: "Italia",
region: cell(row, "Regione"),
province: cell(row, "Prov."),
email: normalize_email(cell(row, "Email verificata")),
website: cell(row, "Sito / profilo"),
source_url: cell(row, "Fonte contatto / ricerca"),
commercial_fit: cell(row, "Evidenza / fit commerciale"),
team_gender: GENDER_MAP.fetch(normalize_key(cell(row, "M/F"))) { raise "M/F sconosciuto: #{row['M/F']}" },
streaming_status: streaming_status_for(cell(row, "Streaming rilevato")),
list_position: integer_cell(row, "N."),
verified_at: parse_date(cell(row, "Data verifica")),
status: yes?(cell(row, "Conversione")) ? "active_customer" : "prospect",
lead_source: "campaign",
assigned_user: @user,
notes: cell(row, "Note follow-up")
)
org.projects = [@project]
org.save!
first_name, last_name, role = contact_from(org.email, org.name)
org.contacts.create!(
first_name: first_name,
last_name: last_name,
role: role,
email: org.email,
preferred_contact_method: "email",
primary_contact: true
)
converted = yes?(cell(row, "Conversione"))
demo = yes?(cell(row, "Demo / Trial"))
stage = if converted
"won"
elsif demo
"demo_trial"
else
"to_contact"
end
org.opportunities.create!(
name: @campaign_name,
project: @project,
pipeline_stage: stage,
product: blank_if_no(cell(row, "Piano")),
assigned_user: @user,
ab_variant: cell(row, "Test A/B").to_s.upcase.presence,
send_status: SEND_MAP.fetch(normalize_key(cell(row, "Stato invio")), "to_send"),
sent_on: parse_date(cell(row, "Data invio")),
outcome: cell(row, "Esito"),
demo_trial: demo,
converted: converted,
notes: [cell(row, "Evidenza / fit commerciale"), cell(row, "Note follow-up")].compact_blank.join("\n\n")
)
end
def index_existing_names!
@existing_names = {}
Organization.find_each { |org| remember_name!(org.name) }
end
def remember_name!(name)
key = normalize_org_name(name)
@existing_names[key] = true if key.present?
end
def duplicate_name?(name)
key = normalize_org_name(name)
key.blank? || @existing_names[key]
end
def normalize_org_name(name)
I18n.transliterate(name.to_s)
.downcase
.gsub("'", " ")
.gsub(LEGAL_PREFIXES, " ")
.gsub(/[^a-z0-9]+/, " ")
.squeeze(" ")
.strip
end
def blank_if_no(value)
return if value.blank?
return if %w[NO N/A].include?(normalize_key(value))
value
end
def normalize_email(value)
email = value.to_s.strip.downcase.gsub(/\s+/, "")
email.presence
end
def cell(row, header)
value = row[header].to_s.strip
value.presence
end
def integer_cell(row, header)
raw = cell(row, header)
return if raw.blank?
raw.to_i
end
def normalize_key(value)
I18n.transliterate(value.to_s)
.gsub(/[\u2013\u2014\u2212]/, "-")
.encode("ASCII", invalid: :replace, undef: :replace, replace: "")
.strip
.upcase
.gsub(/\s+/, " ")
end
def streaming_status_for(value)
key = normalize_key(value)
return STREAMING_MAP[key] if STREAMING_MAP.key?(key)
return "yes_sportcam" if key.include?("SPORTCAM")
return "yes_partial" if key.include?("PARZIALE")
return "not_detected" if key.include?("NON RILEVATO")
return "limited" if key.include?("LIMITATO")
return "yes" if key == "SI" || key.start_with?("SI ")
raise "Streaming sconosciuto: #{value}"
end
def yes?(value)
%w[SI YES TRUE 1].include?(normalize_key(value))
end
def parse_date(value)
return if value.blank?
return Date.iso8601(value) if value.match?(/\A\d{4}-\d{2}-\d{2}\z/)
return Date.strptime(value, "%d/%m/%Y") if value.match?(/\A\d{1,2}\/\d{1,2}\/\d{4}\z/)
serial = Float(value)
Date.new(1899, 12, 30) + serial.to_i
rescue ArgumentError, TypeError
nil
end
def contact_from(email, org_name)
local = email.to_s.split("@").first.to_s
if local.match?(/\A[a-z]+[._][a-z]+\z/i)
first, last = local.split(/[._]/)
[first.capitalize, last.capitalize, "Contatto"]
elsif local.match?(/\A[a-z]\.[a-z]+\z/i)
initial, last = local.split(".")
["#{initial.upcase}.", last.capitalize, "Contatto"]
else
token = org_name.to_s.split(/[\s\/-]+/).last.presence || "Società"
["Segreteria", token, "Segreteria"]
end
end
end