Include autenticazione, progetti isolati, mail marketing HTML con SMTP, test A/B e editor WYSIWYG. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
3.8 KiB
Ruby
128 lines
3.8 KiB
Ruby
require "csv"
|
|
|
|
class CsvImport::Organizations
|
|
STANDARD_HEADERS = %w[
|
|
organization_name organization_type sport country region province city website
|
|
organization_email contact_first_name contact_last_name contact_role contact_email
|
|
contact_phone lead_source notes
|
|
].freeze
|
|
|
|
Result = Struct.new(:imported, :skipped, :errors, keyword_init: true)
|
|
|
|
def initialize(file:, user:, mapping: nil, project: nil)
|
|
@file = file
|
|
@user = user
|
|
@mapping = mapping
|
|
@project = project || Current.project
|
|
end
|
|
|
|
def preview(limit: 10)
|
|
rows = []
|
|
CSV.foreach(@file.path, headers: true, encoding: "bom|utf-8") do |row|
|
|
rows << row.to_h
|
|
break if rows.size >= limit
|
|
end
|
|
{ headers: rows.first&.keys || [], rows: rows }
|
|
end
|
|
|
|
def import!
|
|
imported = 0
|
|
skipped = 0
|
|
errors = []
|
|
|
|
CSV.foreach(@file.path, headers: true, encoding: "bom|utf-8").with_index(2) do |row, line|
|
|
Current.user = @user
|
|
attrs = mapped_attrs(row)
|
|
|
|
if duplicate?(attrs)
|
|
skipped += 1
|
|
next
|
|
end
|
|
|
|
Organization.transaction do
|
|
org = Organization.new(
|
|
name: attrs[:organization_name],
|
|
organization_type: normalize_type(attrs[:organization_type]),
|
|
sport: attrs[:sport],
|
|
country: attrs[:country].presence || "Italia",
|
|
region: attrs[:region],
|
|
province: attrs[:province],
|
|
city: attrs[:city],
|
|
website: normalize_website(attrs[:website]),
|
|
email: attrs[:organization_email],
|
|
lead_source: normalize_lead_source(attrs[:lead_source]),
|
|
notes: attrs[:notes],
|
|
status: "prospect",
|
|
assigned_user: @user
|
|
)
|
|
org.projects = [@project].compact
|
|
org.save!
|
|
|
|
if attrs[:contact_first_name].present? || attrs[:contact_last_name].present?
|
|
org.contacts.create!(
|
|
first_name: attrs[:contact_first_name].presence || "N/D",
|
|
last_name: attrs[:contact_last_name].presence || "N/D",
|
|
role: attrs[:contact_role],
|
|
email: attrs[:contact_email],
|
|
phone: attrs[:contact_phone],
|
|
primary_contact: true
|
|
)
|
|
end
|
|
end
|
|
imported += 1
|
|
rescue StandardError => e
|
|
errors << { line: line, message: e.message, row: row.to_h }
|
|
end
|
|
|
|
Result.new(imported: imported, skipped: skipped, errors: errors)
|
|
ensure
|
|
Current.user = nil
|
|
end
|
|
|
|
private
|
|
|
|
def mapped_attrs(row)
|
|
source = row.to_h.transform_keys { |k| k.to_s.strip }
|
|
STANDARD_HEADERS.index_with do |header|
|
|
key = @mapping&.dig(header) || header
|
|
source[key].to_s.strip.presence
|
|
end.symbolize_keys
|
|
end
|
|
|
|
def duplicate?(attrs)
|
|
name = attrs[:organization_name].to_s
|
|
email = attrs[:organization_email].to_s.downcase
|
|
website = normalize_website(attrs[:website])
|
|
|
|
return true if name.present? && Organization.where("LOWER(name) = ?", name.downcase).exists?
|
|
return true if email.present? && Organization.where("LOWER(email) = ?", email).exists?
|
|
return true if website.present? && Organization.where("LOWER(website) = ?", website.downcase).exists?
|
|
|
|
false
|
|
end
|
|
|
|
def normalize_website(value)
|
|
return if value.blank?
|
|
|
|
value.to_s.strip.downcase.sub(%r{\Ahttps?://}, "").sub(%r{/\z}, "")
|
|
end
|
|
|
|
def normalize_type(value)
|
|
return "altro" if value.blank?
|
|
|
|
key = value.to_s.downcase.gsub(/\s+/, "_")
|
|
return key if Catalog::ORGANIZATION_TYPES.key?(key)
|
|
|
|
Catalog::ORGANIZATION_TYPES.find { |_k, label| label.downcase == value.to_s.downcase }&.first || "altro"
|
|
end
|
|
|
|
def normalize_lead_source(value)
|
|
return if value.blank?
|
|
|
|
key = value.to_s.downcase.gsub(/\s+/, "_")
|
|
return key if Catalog::LEAD_SOURCES.key?(key)
|
|
|
|
Catalog::LEAD_SOURCES.find { |_k, label| label.downcase == value.to_s.downcase }&.first || "other"
|
|
end
|
|
end
|