260 lines
7.3 KiB
Ruby
260 lines
7.3 KiB
Ruby
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",
|
||
"RILEVATO" => "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) || normalize_email(cell(row, "Email verificata")).blank?
|
||
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 = I18n.transliterate(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
|