Files
eminuxandCursor c4e5f289cf
CI / scan_ruby (push) Failing after 11m20s
CI / scan_js (push) Successful in 10m35s
CI / lint (push) Has been cancelled
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>
2026-08-17 23:01:48 +02:00

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