From f41b8a8ee85dc015c564d5eef5e711216f30861f Mon Sep 17 00:00:00 2001 From: Christoph Marzell Date: Fri, 7 Aug 2026 05:44:51 +0200 Subject: [PATCH] add mediation values --- app/models/entry.rb | 420 +++++++++++++++++++++++++++++++++++--------- app/models/user.rb | 410 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 703 insertions(+), 127 deletions(-) diff --git a/app/models/entry.rb b/app/models/entry.rb index 5fc8dcd..15d6275 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -1,104 +1,281 @@ +require "csv" + class Entry < ApplicationRecord + belongs_to :user - require 'csv' + # ------------------------------------------------------------ + # Konstanten + # ------------------------------------------------------------ - belongs_to :user + PRAKTIKUMSTYPEN = %w[ + propädeutikum + fachspezifikum + mediation + ].freeze + + ENTRY_ARTEN = [ + # Allgemein + "Praktikum", + "Selbsterfahrung", + "Supervision", + "Fortbildung", + "Semesterkosten", + + # Mediation + "Präsenzmodul", + "Peergruppenarbeit", + "Fallarbeit", + "Praxisseminare", + "Literatur- und Selbststudium", + + # Fachspezifikum + "Gruppenselbsterfahrung", + "Theorie/Methodikseminare", + "Peergruppensupervision", + "Einzel-/Kleingruppensupervision", + "Eigenständige Tätigkeit" + ].freeze + + LEGACY_ENTRY_ART_ALIASES = { + "Peergroup" => "Peergruppenarbeit", + "Peer-Gruppe" => "Peergruppenarbeit" + }.freeze + + # ------------------------------------------------------------ + # Validierungen + # ------------------------------------------------------------ + + validates :date, + presence: true + + validates :hours, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + }, + unless: -> { start_time.present? && end_time.nil? } + + validates :minutes, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0, + less_than: 60 + }, + unless: -> { start_time.present? && end_time.nil? } + + validates :praktikums_typ, + inclusion: { in: PRAKTIKUMSTYPEN } + + validates :entry_art, + inclusion: { in: ENTRY_ARTEN } + + validates :distance_km, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + }, + allow_nil: true - validates :date, presence: true - validates :hours, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, unless: -> { start_time.present? && end_time.nil? } - validates :minutes, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 60 }, unless: -> { start_time.present? && end_time.nil? } + # ------------------------------------------------------------ + # Callbacks + # ------------------------------------------------------------ + before_validation :normalize_legacy_entry_art before_save :normalize_time - PRAKTIKUMSTYPEN = %w[propädeutikum fachspezifikum mediation] - ENTRY_ARTEN = [ - "Praktikum", - "Selbsterfahrung", - "Supervision", - "Fortbildung", - "Semesterkosten", - # Neue differenzierte Einträge (nur sichtbar im fachspezifikum-Kontext) - "Gruppenselbsterfahrung", # 64h - "Theorie/Methodikseminare", # 216h - "Peergruppensupervision", # 168h - "Einzel-/Kleingruppensupervision", # 40h - "Eigenständige Tätigkeit" - ] - - validates :praktikums_typ, inclusion: { in: PRAKTIKUMSTYPEN } - validates :entry_art, inclusion: { in: ENTRY_ARTEN } - validates :distance_km, numericality: { only_integer: true, greater_than_or_equal_to: 0 } - - after_initialize :set_default_date, if: :new_record? + after_initialize :set_default_date, + if: :new_record? + + # ------------------------------------------------------------ + # Scopes + # ------------------------------------------------------------ + + scope :propädeutikum, -> { + where(praktikums_typ: "propädeutikum") + } + + scope :fachspezifikum, -> { + where(praktikums_typ: "fachspezifikum") + } + + scope :mediation, -> { + where(praktikums_typ: "mediation") + } + + scope :peergruppenarbeit, -> { + where(entry_art: "Peergruppenarbeit") + } + + scope :fallarbeit, -> { + where(entry_art: "Fallarbeit") + } + + scope :praxisseminare, -> { + where(entry_art: "Praxisseminare") + } + + scope :praesenzmodule, -> { + where(entry_art: "Präsenzmodul") + } + + scope :literatur_und_selbststudium, -> { + where(entry_art: "Literatur- und Selbststudium") + } + + # ------------------------------------------------------------ + # Defaults + # ------------------------------------------------------------ def set_default_date - self.date ||= Date.today + self.date ||= Date.current + end + + # ------------------------------------------------------------ + # Zeit + # ------------------------------------------------------------ + + def total_minutes + hours.to_i * 60 + minutes.to_i + end + + def total_minutes_including_break + return nil unless start_time.present? && end_time.present? + + worked_minutes = + ((end_time - start_time) / 60).to_i + + worked_minutes -= lunch_break_minutes.to_i + + [worked_minutes, 0].max + end + + def hours_and_minutes + mins = total_minutes_including_break + + return [hours.to_i, minutes.to_i] if mins.nil? + + [ + mins / 60, + mins % 60 + ] end + # ------------------------------------------------------------ + # Datum + # ------------------------------------------------------------ + + def jahr + date&.year + end + + # ------------------------------------------------------------ + # Kilometer + # ------------------------------------------------------------ + def kilometer_pauschale return 0 unless distance_km.present? - distance_km * 0.42 + + year = date&.year + + rate = + MileageRate.find_by(year: year)&.rate_per_km || + 0.42 + + (distance_km.to_d * rate.to_d).round(2) end def self.total_kilometer_cost_by_year(user) - yearly_distances = where(user: user) - .where.not(distance_km: nil) - .group(Arel.sql("EXTRACT(YEAR FROM date)")) - .order(Arel.sql("EXTRACT(YEAR FROM date) DESC")) - .sum(:distance_km) + yearly_distances = + where(user: user) + .where.not(distance_km: nil) + .group(Arel.sql("EXTRACT(YEAR FROM date)")) + .order(Arel.sql("EXTRACT(YEAR FROM date) DESC")) + .sum(:distance_km) yearly_distances.each_with_object({}) do |(year, km_sum), result| - rate = MileageRate.find_by(year: year.to_i)&.rate_per_km || 0.42 - result[year.to_i] = (km_sum * rate).round(2) + rate = + MileageRate.find_by(year: year.to_i)&.rate_per_km || + 0.42 + + result[year.to_i] = + (km_sum.to_d * rate.to_d).round(2) end end + # ------------------------------------------------------------ + # Kosten + # ------------------------------------------------------------ + def self.total_fortbildungskosten_by_year(user) where(user: user) .where.not(kosten: nil) .where( - Entry.arel_table[:entry_art].eq('Fortbildung') - .or(Entry.arel_table[:zaehlt_als_fortbildung].eq(true)) + Entry.arel_table[:entry_art] + .eq("Fortbildung") + .or( + Entry.arel_table[:zaehlt_als_fortbildung] + .eq(true) + ) ) .group(Arel.sql("DATE_PART('year', date)")) .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck(Arel.sql("DATE_PART('year', date)::int"), Arel.sql("SUM(kosten)")) - .to_h - end - - def self.total_selbsterfahrungskosten_by_year(user) - where(user: user, entry_art: 'Selbsterfahrung') - .where.not(kosten: nil) - .group(Arel.sql("DATE_PART('year', date)")) - .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck(Arel.sql("DATE_PART('year', date)::int"), Arel.sql("SUM(kosten)")) + .pluck( + Arel.sql("DATE_PART('year', date)::int"), + Arel.sql("SUM(kosten)") + ) .to_h end def self.total_selbsterfahrungskosten_by_year(user) - where(user: user, entry_art: 'Selbsterfahrung') + where( + user: user, + entry_art: [ + "Selbsterfahrung", + "Gruppenselbsterfahrung" + ] + ) .where.not(kosten: nil) .group(Arel.sql("DATE_PART('year', date)")) .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck(Arel.sql("DATE_PART('year', date)::int"), Arel.sql("SUM(kosten)")) + .pluck( + Arel.sql("DATE_PART('year', date)::int"), + Arel.sql("SUM(kosten)") + ) .to_h end def self.total_supervision_by_year(user) - where(user: user, entry_art: 'Supervision') + where( + user: user, + entry_art: [ + "Supervision", + "Peergruppensupervision", + "Einzel-/Kleingruppensupervision" + ] + ) .where.not(kosten: nil) .group(Arel.sql("DATE_PART('year', date)")) .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck(Arel.sql("DATE_PART('year', date)::int"), Arel.sql("SUM(kosten)")) + .pluck( + Arel.sql("DATE_PART('year', date)::int"), + Arel.sql("SUM(kosten)") + ) .to_h end + # Der Name bleibt aus Kompatibilitätsgründen erhalten. + # + # Die Methode summiert weiterhin alle eingetragenen Kosten + # eines Jahres, so wie dein bisheriger Code. def self.total_semesterkosten_by_year(user) where(user: user) .where.not(kosten: nil) .group(Arel.sql("DATE_PART('year', date)")) .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck(Arel.sql("DATE_PART('year', date)::int"), Arel.sql("SUM(kosten)")) + .pluck( + Arel.sql("DATE_PART('year', date)::int"), + Arel.sql("SUM(kosten)") + ) .to_h end @@ -106,27 +283,86 @@ class Entry < ApplicationRecord semester_by_year = total_semesterkosten_by_year(user) km_by_year = total_kilometer_cost_by_year(user) - years = (semester_by_year.keys + km_by_year.keys).uniq.sort.reverse + years = + ( + semester_by_year.keys + + km_by_year.keys + ) + .uniq + .sort + .reverse years.each_with_object({}) do |year, out| - semester = (semester_by_year[year] || 0).to_d - km = (km_by_year[year] || 0).to_d - out[year] = (semester + km).round(2) + semester = + (semester_by_year[year] || 0).to_d + + km = + (km_by_year[year] || 0).to_d + + out[year] = + (semester + km).round(2) end end + # ------------------------------------------------------------ + # Mediation + # ------------------------------------------------------------ - def jahr - date.year + def mediation? + praktikums_typ == "mediation" end - def total_minutes - hours * 60 + minutes + def praesenzmodul? + mediation? && + entry_art == "Präsenzmodul" + end + + def mediation_supervision? + mediation? && + entry_art == "Supervision" + end + + def peergruppenarbeit? + mediation? && + entry_art == "Peergruppenarbeit" + end + + def fallarbeit? + mediation? && + entry_art == "Fallarbeit" end + def praxisseminar? + mediation? && + entry_art == "Praxisseminare" + end + + def literatur_und_selbststudium? + mediation? && + entry_art == "Literatur- und Selbststudium" + end + + # ------------------------------------------------------------ + # CSV + # ------------------------------------------------------------ + def self.to_csv - CSV.generate(headers: true, col_sep: ";") do |csv| - csv << %w[Datum Stunden Minuten Typ Art Kilometer User] + CSV.generate( + headers: true, + col_sep: ";" + ) do |csv| + + csv << [ + "Datum", + "Stunden", + "Minuten", + "Typ", + "Art", + "Kilometer", + "Beschreibung", + "Kosten", + "User" + ] all.each do |entry| csv << [ @@ -144,34 +380,52 @@ class Entry < ApplicationRecord end end - def total_minutes_including_break - return nil unless start_time && end_time - minutes = ((end_time - start_time) / 60).to_i - minutes -= lunch_break_minutes.to_i - minutes = 0 if minutes < 0 - minutes - end + private - def hours_and_minutes - mins = total_minutes_including_break - return [0, 0] unless mins - [mins / 60, mins % 60] + # ------------------------------------------------------------ + # Alte Bezeichnungen automatisch migrieren + # ------------------------------------------------------------ + + def normalize_legacy_entry_art + return if entry_art.blank? + + self.entry_art = + LEGACY_ENTRY_ART_ALIASES.fetch( + entry_art, + entry_art + ) end - private + # ------------------------------------------------------------ + # Zeit normalisieren + # ------------------------------------------------------------ def normalize_time - if (hours.to_i + minutes.to_i) == 0 && start_time.present? && end_time.present? - total_minutes_worked = total_minutes_including_break - if total_minutes_worked - self.hours = total_minutes_worked / 60 - self.minutes = total_minutes_worked % 60 + if hours.to_i.zero? && + minutes.to_i.zero? && + start_time.present? && + end_time.present? + + total_worked_minutes = + total_minutes_including_break + + if total_worked_minutes + self.hours = + total_worked_minutes / 60 + + self.minutes = + total_worked_minutes % 60 end else - self.minutes ||= 0 - self.hours ||= 0 - self.hours += minutes / 60 - self.minutes = minutes % 60 + total = + hours.to_i * 60 + + minutes.to_i + + self.hours = + total / 60 + + self.minutes = + total % 60 end end -end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 0973680..6eee679 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,91 +1,413 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable, :confirmable + devise :database_authenticatable, + :registerable, + :recoverable, + :rememberable, + :validatable, + :confirmable has_many :entries, dependent: :destroy - PRAKTIKUMSTYPEN = %w[propädeutikum fachspezifikum mediation] + PRAKTIKUMSTYPEN = %w[ + propädeutikum + fachspezifikum + mediation + ].freeze + + # ------------------------------------------------------------ + # Alle möglichen Entry-Arten + # ------------------------------------------------------------ ENTRY_ARTEN = [ - "Praktikum", - "Selbsterfahrung", - "Supervision", - # Neue differenzierte Einträge (nur sichtbar im fachspezifikum-Kontext) - "Gruppenselbsterfahrung", # 64h - "Theorie/Methodikseminare", # 216h - "Peergruppensupervision", # 168h - "Einzel-/Kleingruppensupervision", # 40h - "Eigenständige Tätigkeit" - ] + # Allgemein + "Praktikum", + "Selbsterfahrung", + "Supervision", + "Fortbildung", + "Semesterkosten", + + # Mediation + "Präsenzmodul", + "Peergruppenarbeit", + "Fallarbeit", + "Praxisseminare", + "Literatur- und Selbststudium", + + # Fachspezifikum + "Gruppenselbsterfahrung", + "Theorie/Methodikseminare", + "Peergruppensupervision", + "Einzel-/Kleingruppensupervision", + "Eigenständige Tätigkeit" + ].freeze + + # ------------------------------------------------------------ + # Arten pro Ausbildung + # + # Kann im Frontend / Controller verwendet werden, damit nicht + # jede Entry-Art bei jedem Ausbildungstyp angezeigt wird. + # ------------------------------------------------------------ + ENTRY_ARTEN_BY_TYP = { + "propädeutikum" => [ + "Praktikum", + "Selbsterfahrung", + "Supervision", + "Fortbildung", + "Semesterkosten" + ], + + "fachspezifikum" => [ + "Praktikum", + "Selbsterfahrung", + "Supervision", + "Fortbildung", + "Semesterkosten", + "Gruppenselbsterfahrung", + "Theorie/Methodikseminare", + "Peergruppensupervision", + "Einzel-/Kleingruppensupervision", + "Eigenständige Tätigkeit" + ], + + "mediation" => [ + "Präsenzmodul", + "Selbsterfahrung", + "Supervision", + "Peergruppenarbeit", + "Fallarbeit", + "Praxisseminare", + "Literatur- und Selbststudium", + "Fortbildung", + "Semesterkosten" + ] + }.freeze + + # ------------------------------------------------------------ + # Mediation – zusätzliche Anforderungen + # ------------------------------------------------------------ + + # Vorgabe des konkreten Lehrgangs + MEDIATION_PRAESENZMODULE_TAGE = 19 + + # ZivMediat-AV: + # Von den insgesamt 26 Supervisionseinheiten müssen mindestens + # 3 Einheiten Einzelsupervision sein. + MEDIATION_EINZELSUPERVISION_MINDESTENS = 3 after_initialize :set_default, if: :new_record? + # ------------------------------------------------------------ + # Allgemein + # ------------------------------------------------------------ + def praepedeutikum_abgeschlossen? praepedeutikum_done end + def is_admin? - self.email =="christoph@marzell.net" + email == "christoph@marzell.net" end + def self.entry_arten_for(typ) + ENTRY_ARTEN_BY_TYP[typ.to_s] || ENTRY_ARTEN + end + + # ------------------------------------------------------------ + # Matrizen aktualisieren + # ------------------------------------------------------------ + def update_required_matrices! + required = (required_hours_matrix || {}).deep_stringify_keys + weekly = (weekly_target_matrix || {}).deep_stringify_keys + + migrate_legacy_peer_group_key!( + required, + use_default_when_zero: true + ) + + migrate_legacy_peer_group_key!( + weekly, + use_default_when_zero: false + ) + PRAKTIKUMSTYPEN.each do |typ| - required_hours_matrix[typ] ||= {} - weekly_target_matrix[typ] ||= {} + required[typ] ||= {} + weekly[typ] ||= {} ENTRY_ARTEN.each do |art| - required_hours_matrix[typ][art] ||= default_hours_for(typ, art) - weekly_target_matrix[typ][art] ||= default_weekly_target_for(typ, art) + unless required[typ].key?(art) + required[typ][art] = default_hours_for(typ, art) + end + + unless weekly[typ].key?(art) + weekly[typ][art] = default_weekly_target_for(typ, art) + end end end + self.required_hours_matrix = required + self.weekly_target_matrix = weekly + save! end + # ------------------------------------------------------------ + # Default-Wochenziele + # ------------------------------------------------------------ def default_weekly_target_for(typ, art) + typ = typ.to_s + art = canonical_entry_art(art) + case [typ, art] - when ["propädeutikum", "Praktikum"] then 12 - when ["fachspezifikum", "Praktikum"] then 10 + when ["propädeutikum", "Praktikum"] + 12 + + when ["fachspezifikum", "Praktikum"] + 10 + when ["propädeutikum", "Selbsterfahrung"], ["fachspezifikum", "Selbsterfahrung"], - ["fachspezifikum", "Gruppenselbsterfahrung"] then 0.25 + ["fachspezifikum", "Gruppenselbsterfahrung"] + 0.25 + when ["propädeutikum", "Supervision"], ["fachspezifikum", "Supervision"], ["fachspezifikum", "Peergruppensupervision"], - ["fachspezifikum", "Einzel-/Kleingruppensupervision"] then 0.25 - when ["fachspezifikum", "Eigenständige Tätigkeit"] then 10 - else 0 + ["fachspezifikum", "Einzel-/Kleingruppensupervision"] + 0.25 + + when ["fachspezifikum", "Eigenständige Tätigkeit"] + 10 + + # Mediation findet typischerweise blockweise statt. + # Deshalb kein künstliches Wochenziel. + when ["mediation", "Präsenzmodul"], + ["mediation", "Selbsterfahrung"], + ["mediation", "Supervision"], + ["mediation", "Peergruppenarbeit"], + ["mediation", "Fallarbeit"], + ["mediation", "Praxisseminare"], + ["mediation", "Literatur- und Selbststudium"] + 0 + + else + 0 end end + # ------------------------------------------------------------ + # Default-Sollstunden / Einheiten + # ------------------------------------------------------------ - def set_default - self.required_hours_matrix = PRAKTIKUMSTYPEN.to_h do |typ| - [typ, ENTRY_ARTEN.to_h { |art| [art, default_hours_for(typ, art)] }] + def default_hours_for(typ, art) + typ = typ.to_s + art = canonical_entry_art(art) + + case [typ, art] + # ---------------------------------------------------------- + # Propädeutikum + # ---------------------------------------------------------- + when ["propädeutikum", "Praktikum"] + 480 + + when ["propädeutikum", "Selbsterfahrung"] + 50 + + when ["propädeutikum", "Supervision"] + 20 + + # ---------------------------------------------------------- + # Fachspezifikum + # ---------------------------------------------------------- + when ["fachspezifikum", "Praktikum"] + 600 + + when ["fachspezifikum", "Selbsterfahrung"] + 80 + + when ["fachspezifikum", "Supervision"] + 40 + + when ["fachspezifikum", "Gruppenselbsterfahrung"] + 64 + + when ["fachspezifikum", "Theorie/Methodikseminare"] + 216 + + when ["fachspezifikum", "Peergruppensupervision"] + 168 + + when ["fachspezifikum", "Einzel-/Kleingruppensupervision"] + 40 + + when ["fachspezifikum", "Eigenständige Tätigkeit"] + 600 + + # ---------------------------------------------------------- + # Mediation + # ---------------------------------------------------------- + + # Wird in Tagen und nicht in Stunden bewertet. + when ["mediation", "Präsenzmodul"] + 0 + + # ZivMediat-AV Anlage 1 + when ["mediation", "Selbsterfahrung"] + 40 + + when ["mediation", "Supervision"] + 26 + + when ["mediation", "Peergruppenarbeit"] + 24 + + when ["mediation", "Fallarbeit"] + 17 + + when ["mediation", "Praxisseminare"] + 58 + + # Zusätzliche Lehrgangsvorgabe + when ["mediation", "Literatur- und Selbststudium"] + 150 + + else + 0 end - self.weekly_target_matrix = { - "propädeutikum" => { "Praktikum" => 12, "Selbsterfahrung" => 0.25, "Supervision" => 0.25 }, - "fachspezifikum" => { "Praktikum" => 10, "Selbsterfahrung" => 0.25, "Supervision" => 0.25 } - } end + # ------------------------------------------------------------ + # Sollwert aus persönlicher Matrix + # ------------------------------------------------------------ - def default_hours_for(typ, art) - case [typ, art] - when ["propädeutikum", "Praktikum"] then 480 - when ["propädeutikum", "Selbsterfahrung"] then 50 - when ["propädeutikum", "Supervision"] then 20 - when ["fachspezifikum", "Praktikum"] then 600 - when ["fachspezifikum", "Selbsterfahrung"] then 80 - when ["fachspezifikum", "Supervision"] then 40 - when ["fachspezifikum", "Eigenständige Tätigkeit"] then 600 - else 0 + def required_hours_for(typ, art) + typ = typ.to_s + art = canonical_entry_art(art) + + value = required_hours_matrix&.dig(typ, art) + + value.nil? ? default_hours_for(typ, art) : value + end + + def weekly_target_for(typ, art) + typ = typ.to_s + art = canonical_entry_art(art) + + value = weekly_target_matrix&.dig(typ, art) + + value.nil? ? default_weekly_target_for(typ, art) : value + end + + # ------------------------------------------------------------ + # Mediation – Präsenztage + # ------------------------------------------------------------ + + def mediation_praesenzmodule_required + MEDIATION_PRAESENZMODULE_TAGE + end + + def mediation_praesenzmodule_completed + entries + .where( + praktikums_typ: "mediation", + entry_art: "Präsenzmodul" + ) + .distinct + .count(:date) + end + + def mediation_praesenzmodule_remaining + [ + mediation_praesenzmodule_required - + mediation_praesenzmodule_completed, + 0 + ].max + end + + def mediation_praesenzmodule_completed? + mediation_praesenzmodule_completed >= + mediation_praesenzmodule_required + end + + # ------------------------------------------------------------ + # Mediation – besondere Vorgaben + # ------------------------------------------------------------ + + def mediation_einzelsupervision_required + MEDIATION_EINZELSUPERVISION_MINDESTENS + end + + private + + # ------------------------------------------------------------ + # Defaults für neuen User + # ------------------------------------------------------------ + + def set_default + self.required_hours_matrix ||= PRAKTIKUMSTYPEN.to_h do |typ| + [ + typ, + ENTRY_ARTEN.to_h do |art| + [art, default_hours_for(typ, art)] + end + ] + end + + self.weekly_target_matrix ||= PRAKTIKUMSTYPEN.to_h do |typ| + [ + typ, + ENTRY_ARTEN.to_h do |art| + [art, default_weekly_target_for(typ, art)] + end + ] end end - def required_hours_for(typ, art) - required_hours_matrix.dig(typ, art) || 0 + # ------------------------------------------------------------ + # Alte Bezeichnungen normalisieren + # ------------------------------------------------------------ + + def canonical_entry_art(art) + case art.to_s + when "Peergroup", "Peer-Gruppe" + "Peergruppenarbeit" + else + art.to_s + end end -end + def migrate_legacy_peer_group_key!(matrix, use_default_when_zero:) + PRAKTIKUMSTYPEN.each do |typ| + values = matrix[typ] + next unless values.is_a?(Hash) + + legacy_value = nil + + if values.key?("Peergroup") + legacy_value = values.delete("Peergroup") + end + + if values.key?("Peer-Gruppe") + legacy_value ||= values.delete("Peer-Gruppe") + end + + next if legacy_value.nil? + next if values.key?("Peergruppenarbeit") + + if use_default_when_zero && + typ == "mediation" && + legacy_value.to_f.zero? + + values["Peergruppenarbeit"] = + default_hours_for( + "mediation", + "Peergruppenarbeit" + ) + else + values["Peergruppenarbeit"] = legacy_value + end + end + end +end \ No newline at end of file