Skip to content

Commit

Permalink
Add NAV17 importer for event kinds (#1348)
Browse files Browse the repository at this point in the history
  • Loading branch information
codez authored Dec 6, 2024
1 parent 64ac335 commit 39608f9
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 1 deletion.
3 changes: 2 additions & 1 deletion app/domain/sac_imports/csv_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SacImports::CsvSource
NAV2b: Nav2b,
NAV3: Nav3,
NAV6: Nav6,
NAV17: Nav17,
WSO21: Wso2
}.freeze

Expand All @@ -33,7 +34,7 @@ def rows(filter: nil)
next unless filter.blank? || filter_match?(row, filter)

if block_given?
yield rows
yield row
else
data << row
end
Expand Down
43 changes: 43 additions & 0 deletions app/domain/sac_imports/csv_source/nav17.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

# Copyright (c) 2024, Schweizer Alpen-Club. This file is part of
# hitobito_sac_cas and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_sac_cas

class SacImports::CsvSource
# Event::Kinds
# !!! DO NOT CHANGE THE ORDER OF THE KEYS !!!
# they must match the order of the columns in the CSV files
Nav17 = Data.define(
:label_de, # Bezeichnung_DE
:label_fr, # Bezeichnung_FR
:label_it, # Bezeichnung_IT
:short_name, # Kurzname
:kind_category, # Kurskategorie
:general_information_de, # Standardbeschreibung_DE
:general_information_fr, # Standardbeschreibung_FR
:general_information_it, # Standardbeschreibung_IT
:application_conditions_de, # Aufnahmebedingungen_DE
:application_conditions_fr, # Aufnahmebedingungen_FR
:application_conditions_it, # Aufnahmebedingungen_IT
:level, # Kursstufe
:cost_center, # Kostenstelle
:cost_unit, # Kostenträger
:course_compensation_categories, # Vergütungskategorien
:minimum_age, # Mindestalter
:maximum_age, # Maximalalter
:minimum_participants, # Minimale_TN_Zahl
:maximum_participants, # Maximale_TN_Zahl
:ideal_class_size, # Ideale_Klassengrösse
:maximum_class_size, # Maximale_Klassengrösse
:training_days, # Ausbildungstage
:season, # Saison
:accommodation, # Unterkunft
:reserve_accommodation, # Unterkunft_reservieren_durch_SAC
:section_may_create, # Von_Sektion_erstellbar
:precondition, # Vorbedingungen
:qualification, # Qualifiziert_für
:prolongation # Verlängert
)
end
127 changes: 127 additions & 0 deletions app/domain/sac_imports/events/kind_entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true

# Copyright (c) 2024, Schweizer Alpen-Club. This file is part of
# hitobito_sac_cas and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_sac_cas.

module SacImports
module Events
class KindEntry
LOCALES = [:de, :fr, :it].freeze
ATTRS_TRANSLATED = [:label, :general_information, :application_conditions]
ATTRS_REGULAR = [:short_name, :minimum_age, :maximum_age, :minimum_participants,
:maximum_participants, :ideal_class_size, :maximum_class_size, :training_days,
:season, :accommodation]
ATTRS_BOOLEAN = [:reserve_accommodation, :section_may_create]
ATTRS_BELONGS_TO = [:kind_category, :level, :cost_center, :cost_unit]

attr_reader :row, :associations, :warnings

delegate :valid?, :errors, to: :kind

def initialize(row, associations)
@row = row
@associations = associations
@warnings = []
build_kind
end

def import!
kind.save!
end

def error_messages
errors.full_messages.join(", ")
end

def kind
@kind ||= Event::Kind.find_or_initialize_by(short_name: row.short_name)
end

def build_kind
kind.attributes = regular_attrs
kind.attributes = boolean_attrs
kind.attributes = belongs_to_attrs
LOCALES.each do |locale|
kind.attributes = translated_attrs(locale)
end
kind.course_compensation_category_ids = select_course_compensation_category_ids
build_kind_qualification_kinds
normalize_kind
end

def regular_attrs
ATTRS_REGULAR.each_with_object({}) do |attr, hash|
hash[attr] = value(attr)
end
end

def translated_attrs(locale)
ATTRS_TRANSLATED.each_with_object({locale: locale}) do |attr, hash|
val = value(:"#{attr}_#{locale}")
hash[attr] = strip_paragraph(val) unless val.nil?
end
end

def belongs_to_attrs
ATTRS_BELONGS_TO.each_with_object({}) do |attr, hash|
hash[:"#{attr}_id"] = association_id(attr, value(attr))
end
end

def association_id(attr, value)
return nil if value.nil?

associations.fetch(attr.to_s.pluralize.to_sym).fetch(value) do
@warnings << "#{attr} with value #{value} couldn't be found"
nil
end
end

def boolean_attrs
ATTRS_BOOLEAN.each_with_object({}) do |attr, hash|
hash[attr] = value(attr) == "1"
end
end

def select_course_compensation_category_ids
row.course_compensation_categories.to_s.split(",").map do |category|
association_id(:course_compensation_category, category.strip)
end.compact.uniq
end

def build_kind_qualification_kinds
Event::KindQualificationKind::CATEGORIES.each do |category|
row.public_send(category).to_s.split(",").each do |quali_kind|
quali_kind_id = association_id(:qualification_kind, quali_kind.strip)
next unless quali_kind_id

kind.event_kind_qualification_kinds.find_or_initialize_by(
qualification_kind_id: quali_kind_id,
category: category,
role: "participant"
)
end
end
end

def normalize_kind
kind.maximum_age = nil if kind.maximum_age&.zero?
end

def value(attr)
row.public_send(attr)
end

def strip_paragraph(text)
match = text.match(/\A<p>(.*?)<\/p>\z/m)
if match && text.scan("<p>").count == 1
match[1]
else
text
end
end
end
end
end
96 changes: 96 additions & 0 deletions app/domain/sac_imports/nav17_event_kinds_importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

# Copyright (c) 2024, Schweizer Alpen-Club. This file is part of
# hitobito_sac_cas and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_sac_cas.

module SacImports
class Nav17EventKindsImporter
include LogCounts

REPORT_HEADERS = [
:short_name,
:label,
:status,
:errors
]

def initialize(output: $stdout, import_spec_fixture: false)
@output = output
# spec fixture includes all sections and it's public data
@import_spec_fixture = import_spec_fixture
@source_file = source_file
@csv_report = SacImports::CsvReport.new("nav17-event-kinds", REPORT_HEADERS, output:)
end

def create
@csv_report.log("The file contains #{@source_file.lines_count} rows.")
progress = Progress.new(@source_file.lines_count, title: "NAV17 Event Kinds")

log_counts_delta(@csv_report, Event::Kind.unscoped) do
@source_file.rows do |row|
progress.step
process_row(row)
end
end

@csv_report.finalize
end

private

def source_file
if @import_spec_fixture
CsvSource.new(:NAV17, source_dir: spec_fixture_dir)
else
CsvSource.new(:NAV17)
end
end

def spec_fixture_dir
Pathname.new(HitobitoSacCas::Wagon.root.join("spec", "fixtures", "files", "sac_imports_src"))
end

def process_row(row)
entry = Events::KindEntry.new(row, associations)
entry.import! if entry.valid?
report_warnings(entry)
report_errors(entry)
end

def report_errors(entry)
return if entry.errors.blank?

@output.puts("#{entry.row.label_de} (#{entry.row.short_name}): ❌ #{entry.error_messages}")
@csv_report.add_row(
short_name: entry.row.short_name,
label: entry.row.label_de,
status: "error",
errors: entry.error_messages
)
end

def report_warnings(entry)
return if entry.warnings.blank?

@csv_report.add_row(
short_name: entry.row.short_name,
label: entry.row.label_de,
status: "warning",
errors: entry.warnings.join(", ")
)
end

def associations
@associations ||= {
kind_categories: Event::KindCategory.pluck(:order, :id).to_h.transform_keys(&:to_s),
qualification_kinds: QualificationKind.pluck(:label, :id).to_h,
levels: Event::Level.pluck(:code, :id).to_h.transform_keys(&:to_s),
cost_centers: CostCenter.pluck(:code, :id).to_h,
cost_units: CostUnit.pluck(:code, :id).to_h,
course_compensation_categories: CourseCompensationCategory.pluck(:short_name, :id).to_h
}
end
end
end
7 changes: 7 additions & 0 deletions lib/tasks/sac_imports.rake
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ namespace :sac_imports do
"wso21-1_people",
"nav2b-2_non_membership_roles",
:update_sac_family_address,
"nav17-1_event_kinds",
:cleanup,
:check_data_quality
] do
Expand Down Expand Up @@ -146,6 +147,12 @@ namespace :sac_imports do
Rake::Task["sac_imports:dump_database"].execute(dump_name: "nav5-huts")
end

desc "Imports event kinds"
task "nav17-1_event_kinds": :setup do
SacImports::Nav17EventKindsImporter.new.create
Rake::Task["sac_imports:dump_database"].execute(dump_name: "nav17-event-kinds")
end

desc "Run cleanup tasks"
task cleanup: :setup do
SacImports::Cleanup.new.run
Expand Down
Loading

0 comments on commit 39608f9

Please sign in to comment.