diff --git a/CHANGELOG.md b/CHANGELOG.md index c08696dfe..e8809ff04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Hitobito PBS Changelog +## unreleased + +* Das Feld "Geschwister in der Abteilung" wird neu von eingetragenen Geschwistern abgeleitet und pro Ebene bzw. pro Anlass berechnet und angezeigt. (hitobito#2147) + ## Version 1.30 * Das Anwesenheiten-Tab bei Kursen im Status "Qualifikationen erfasst" und "Abgeschlossen" wird neu mit einem Ausrufezeichen markiert, wenn die Anwesenheiten noch gespeichert werden müssen. Merci @ewangler! (hitobito/hitobito_pbs#262) diff --git a/app/decorators/pbs/person_decorator.rb b/app/decorators/pbs/person_decorator.rb index 9b2479dd0..de24e5f10 100644 --- a/app/decorators/pbs/person_decorator.rb +++ b/app/decorators/pbs/person_decorator.rb @@ -20,10 +20,18 @@ def roles_grouped(scope:) end end + def siblings_in_context(context) + family_member_finder.family_members_in_context(context, kind: :sibling) + end + end private + def family_member_finder + @family_member_finder ||= Person::FamilyMemberFinder.new(self) + end + def layer_group_ids @layer_group_ids ||= current_user&.layer_group_ids || [current_service_token&.layer_group_id].compact.presence || diff --git a/app/domain/pbs/export/tabular/people/participation_row.rb b/app/domain/pbs/export/tabular/people/participation_row.rb index 34152028e..653c1042e 100644 --- a/app/domain/pbs/export/tabular/people/participation_row.rb +++ b/app/domain/pbs/export/tabular/people/participation_row.rb @@ -15,6 +15,13 @@ def bsv_days participation.bsv_days || participation.event.bsv_days end + def has_siblings_in_event + event = participation.event + + ::Person::FamilyMemberFinder.new(participation.person) + .family_members_in_context(event, kind: :sibling).any? + end + end end end diff --git a/app/domain/pbs/export/tabular/people/participations_full.rb b/app/domain/pbs/export/tabular/people/participations_full.rb index d51e647f7..5eda29b9b 100644 --- a/app/domain/pbs/export/tabular/people/participations_full.rb +++ b/app/domain/pbs/export/tabular/people/participations_full.rb @@ -13,12 +13,19 @@ module ParticipationsFull extend ActiveSupport::Concern included do + alias_method_chain :person_attributes, :pbs alias_method_chain :build_attribute_labels, :pbs end + def person_attributes_with_pbs + person_attributes_without_pbs + [:has_siblings_in_event] + end + def build_attribute_labels_with_pbs build_attribute_labels_without_pbs.tap do |labels| labels[:bsv_days] = ::Event::Participation.human_attribute_name(:bsv_days) + labels[:has_siblings_in_event] = + ::Event::Participation.human_attribute_name(:has_siblings_in_event) end end end diff --git a/app/domain/pbs/export/tabular/people/people_full.rb b/app/domain/pbs/export/tabular/people/people_full.rb index 068f2d954..e9b668d31 100644 --- a/app/domain/pbs/export/tabular/people/people_full.rb +++ b/app/domain/pbs/export/tabular/people/people_full.rb @@ -17,8 +17,24 @@ module PeopleFull end def person_attributes_with_pbs - person_attributes_without_pbs + [:id, :layer_group_id, :pbs_number] + attrs = person_attributes_without_pbs + [:id, :layer_group_id, :pbs_number] + attrs += [:has_siblings_in_layer] if @group.present? + attrs end + + def initialize(list, group = nil) + super(list) + @group = group + end + + private + + def row_for(entry, format = nil) + return super unless row_class == ::Export::Tabular::People::PersonRow && + @group.present? + row_class.new(entry, format, @group) + end + end end end diff --git a/app/domain/pbs/export/tabular/people/person_row.rb b/app/domain/pbs/export/tabular/people/person_row.rb index 0c4e61d8c..0f0a05324 100644 --- a/app/domain/pbs/export/tabular/people/person_row.rb +++ b/app/domain/pbs/export/tabular/people/person_row.rb @@ -12,6 +12,11 @@ module People module PersonRow extend ActiveSupport::Concern + def initialize(entry, format = nil, group = nil) + super(entry, format) + @group = group + end + def salutation entry.salutation_value end @@ -24,6 +29,10 @@ def layer_group_id entry.try(:primary_group).try(:layer_group).try(:id) end + def has_siblings_in_layer + ::Person::FamilyMemberFinder.new(entry) + .family_members_in_context(@group, kind: :sibling)&.any? + end end end end diff --git a/app/domain/person/family_member_finder.rb b/app/domain/person/family_member_finder.rb new file mode 100644 index 000000000..84776cbdc --- /dev/null +++ b/app/domain/person/family_member_finder.rb @@ -0,0 +1,33 @@ +# Copyright (c) 2023, Pfadibewegung Schweiz. This file is part of +# hitobito_pbs 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_pbs. + +class Person::FamilyMemberFinder + attr_reader :person + + def initialize(person) + @person = person + end + + def family_members_in_layer(group, kind: :sibling) + Role.joins(person: :family_members) + .where(group: group.groups_in_same_layer, + person: { family_members: { kind: kind, other: person } }) + end + + def family_members_in_event(event, kind: :sibling) + Event::Participation.joins(person: :family_members) + .where(person: { family_members: { kind: kind, other: person } }, + event: event) + end + + def family_members_in_context(context, kind: :sibling) + case context + when Event + family_members_in_event(context, kind: kind) + when Group + family_members_in_layer(context, kind: kind) + end + end +end diff --git a/app/jobs/pbs/export/event_participations_export_job.rb b/app/jobs/pbs/export/event_participations_export_job.rb index 3ff4f3a28..b899b17f3 100644 --- a/app/jobs/pbs/export/event_participations_export_job.rb +++ b/app/jobs/pbs/export/event_participations_export_job.rb @@ -33,5 +33,10 @@ def unfiltered_participants references(:people). distinct end + + def data + return super unless exporter == ::Export::Tabular::People::ParticipationsFull + ::Export::Tabular::People::ParticipationsFull.export(@format, entries, @filter.event) + end end diff --git a/app/jobs/pbs/export/people_export_job.rb b/app/jobs/pbs/export/people_export_job.rb index be7926ac7..162d1d2aa 100644 --- a/app/jobs/pbs/export/people_export_job.rb +++ b/app/jobs/pbs/export/people_export_job.rb @@ -13,8 +13,12 @@ module Pbs::Export::PeopleExportJob end def exporter_with_detail - return Pbs::Export::Tabular::People::HouseholdsFull if @options[:household_details] + return Pbs::Export::Tabular::People::HouseholdsFull if @options[:household_details] exporter_without_detail end + def data + return super unless exporter == ::Export::Tabular::People::PeopleFull + ::Export::Tabular::People::PeopleFull.export(@format, entries, group) + end end diff --git a/app/models/pbs/person.rb b/app/models/pbs/person.rb index c3c6f71ca..b2f87d6a0 100644 --- a/app/models/pbs/person.rb +++ b/app/models/pbs/person.rb @@ -62,8 +62,7 @@ module Pbs::Person alias_method_chain :full_name, :title - i18n_boolean_setter :brother_and_sisters, :prefers_digital_correspondence - + i18n_boolean_setter :prefers_digital_correspondence belongs_to :kantonalverband, class_name: 'Group' # might also be Group::Bund has_many :crises, foreign_key: :creator_id diff --git a/app/serializers/pbs/event_participation_serializer.rb b/app/serializers/pbs/event_participation_serializer.rb index 1954b1f88..03ca236c8 100644 --- a/app/serializers/pbs/event_participation_serializer.rb +++ b/app/serializers/pbs/event_participation_serializer.rb @@ -16,6 +16,10 @@ module Pbs::EventParticipationSerializer translated_label: number.translated_label } end) + + property(:has_siblings_in_event, + ::Person::FamilyMemberFinder.new(item.person) + .family_members_in_context(item.event, kind: :sibling).any?) end end end diff --git a/app/serializers/pbs/person_serializer.rb b/app/serializers/pbs/person_serializer.rb index 693df326a..c5f043492 100644 --- a/app/serializers/pbs/person_serializer.rb +++ b/app/serializers/pbs/person_serializer.rb @@ -11,9 +11,13 @@ module Pbs::PersonSerializer included do extension(:details) do |_| map_properties :pbs_number, :salutation_value, :correspondence_language, - :prefers_digital_correspondence, :grade_of_school, :brother_and_sisters, - :entry_date, :leaving_date + :prefers_digital_correspondence, :grade_of_school, :entry_date, :leaving_date + + if context[:group].present? + property :has_siblings_in_layer, item.siblings_in_context(context[:group]).any? + end end + end end diff --git a/app/views/people/_details_pbs.html.haml b/app/views/people/_details_pbs.html.haml index 669040b9a..406750dfd 100644 --- a/app/views/people/_details_pbs.html.haml +++ b/app/views/people/_details_pbs.html.haml @@ -4,6 +4,17 @@ -# https://github.com/hitobito/hitobito_pbs. = render_attrs(entry, :pbs_number, :salutation, :correspondence_language, - :grade_of_school, :brother_and_sisters) + :grade_of_school) + +%dl.dl-horizontal + - sibling_context = parent.try(:layer_group) || parent + - siblings_in_context = entry.siblings_in_context(sibling_context) + = labeled(t('people.fields_pbs.siblings_in_context', context: sibling_context)) do + - if siblings_in_context.any? + = t('global.yes') + = simple_list(siblings_in_context, class: 'unstyled mb-0') do |sibling_role| + - assoc_link(sibling_role.person) + - else + = t('global.no') = render_attrs(entry, :entry_date, :leaving_date) diff --git a/app/views/people/_fields_pbs.html.haml b/app/views/people/_fields_pbs.html.haml index 9db0179f0..37e5ece52 100644 --- a/app/views/people/_fields_pbs.html.haml +++ b/app/views/people/_fields_pbs.html.haml @@ -17,6 +17,3 @@ = field_set_tag do = f.labeled_input_fields(:entry_date, :leaving_date) - -= field_set_tag do - = f.labeled_boolean_field(:brother_and_sisters, caption: t('.in_abteilung')) diff --git a/config/locales/models.pbs.de.yml b/config/locales/models.pbs.de.yml index c6fe931cb..cb1ca0a2e 100644 --- a/config/locales/models.pbs.de.yml +++ b/config/locales/models.pbs.de.yml @@ -1243,6 +1243,7 @@ de: additional_information: Wie möchtest du dich im Anlass ernähren? Was sind deine Essgewohnheiten? bsv_days: BSV-Tage j_s_data_sharing_accepted: J+S Datenweitergabe + has_siblings_in_event: Geschwister im Anlass event/question: pass_on_to_supercamp: An übergeordnetes Lager weitergeben @@ -1259,8 +1260,7 @@ de: leaving_date: Austrittsdatum j_s_number: J+S Personennummer correspondence_language: Korrespondenzsprache - brother_and_sisters: Geschwister - relations_to_tails: Geschwister + has_siblings_in_layer: Geschwister in der Ebene kv: Kantonalverband prefers_digital_correspondence: Digitale Korrespondenz bevorzugt diff --git a/config/locales/views.pbs.de.yml b/config/locales/views.pbs.de.yml index 5c344f9b3..15d7cfaa7 100644 --- a/config/locales/views.pbs.de.yml +++ b/config/locales/views.pbs.de.yml @@ -348,7 +348,7 @@ de: no_sensitive_information: In diesem Feld keine besonders schützenswerte Personendaten erfassen. fields_pbs: non_automatic_field: Dies ist kein automatisches Feld und muss manuell angepasst werden. - in_abteilung: (in der Abteilung) + siblings_in_context: Geschwister in %{context} qualification_buttons_pbs: print_course_confirmation: Kursbestätigung drucken household_attrs_pbs: diff --git a/db/migrate/20230912120642_remove_brother_and_sister_from_persons.rb b/db/migrate/20230912120642_remove_brother_and_sister_from_persons.rb new file mode 100644 index 000000000..c12f0cabd --- /dev/null +++ b/db/migrate/20230912120642_remove_brother_and_sister_from_persons.rb @@ -0,0 +1,5 @@ +class RemoveBrotherAndSisterFromPersons < ActiveRecord::Migration[6.1] + def change + remove_column :people, :brother_and_sisters, :boolean, default: false, null: false + end +end diff --git a/lib/hitobito_pbs/wagon.rb b/lib/hitobito_pbs/wagon.rb index b2852414c..63f66d52c 100644 --- a/lib/hitobito_pbs/wagon.rb +++ b/lib/hitobito_pbs/wagon.rb @@ -118,7 +118,7 @@ class Wagon < Rails::Engine ### controllers PeopleController.permitted_attrs += [:salutation, :title, :grade_of_school, :entry_date, :leaving_date, :j_s_number, :correspondence_language, - :prefers_digital_correspondence, :brother_and_sisters] + :prefers_digital_correspondence] GroupsController.permitted_attrs += [:hostname] Event::KindsController.permitted_attrs += [:documents_text, :campy, :can_have_confirmations, :confirmation_name] diff --git a/spec/domain/person/family_member_finder_spec.rb b/spec/domain/person/family_member_finder_spec.rb new file mode 100644 index 000000000..96b7236ca --- /dev/null +++ b/spec/domain/person/family_member_finder_spec.rb @@ -0,0 +1,93 @@ +# encoding: utf-8 + +# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of +# hitobito_pbs 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_pbs. + +require 'spec_helper' + +describe Person::FamilyMemberFinder do + + let(:service) { described_class.new(person) } + let(:person) { Fabricate(:person) } + let(:sibling) { Fabricate(:person) } + + let!(:sibling_relation) { Fabricate(:family_member, person: person, other: sibling, kind: :sibling) } + + describe '#family_members_in_event' do + + let(:person_event) { Fabricate(:event) } + let!(:person_participation) { Fabricate(:event_participation, person: person, event: person_event) } + let(:sibling_event) { Fabricate(:event) } + let!(:sibling_participation) { Fabricate(:event_participation, person: sibling, event: sibling_event) } + + subject do + service.family_members_in_event(person_event, kind: :sibling) + end + + context 'without siblings' do + let!(:sibling_relation) { nil } + it { is_expected.to be_empty } + end + + context 'with siblings in different events' do + it { is_expected.to be_empty } + end + + context 'with siblings in same event' do + let(:sibling_event) { person_event } + it { is_expected.to contain_exactly(sibling_participation) } + end + + context 'with siblings with deleted role in same group' do + let!(:sibling_participation) { Fabricate(:event_participation, person: sibling, event: sibling_event, active: false) } + it { is_expected.to be_empty } + end + + end + + describe '#family_members_in_layer' do + + let(:layer) { Fabricate(Group::Abteilung.name) } + + let(:person_group) { Fabricate(Group::Pfadi.name, parent: layer)} + let!(:person_role) { Fabricate(person_group.default_role.name, person: person, group: person_group) } + + let(:sibling_group) { Fabricate(Group::Woelfe.name, parent: layer)} + let!(:sibling_role) { Fabricate(sibling_group.default_role.name, person: sibling, group: sibling_group) } + + subject do + service.family_members_in_layer(person_group, kind: :sibling) + end + + context 'without siblings' do + let!(:sibling_relation) { nil } + it { is_expected.to be_empty } + end + + context 'with siblings in different groups' do + let(:sibling_group) { Fabricate(Group::Woelfe.name, parent: Fabricate(Group::Abteilung.name))} + it { is_expected.to be_empty } + end + + context 'with siblings in same group' do + let(:sibling_group) { person_group } + it { is_expected.to contain_exactly(sibling_role) } + end + + context 'with siblings in same layer' do + it { is_expected.to contain_exactly(sibling_role) } + end + + context 'with siblings with deleted role in same group' do + let!(:sibling_role) do + Fabricate(sibling_group.default_role.name, person: sibling, group: sibling_group, + created_at: 2.months.ago, deleted_at: 1.month.ago) + end + it { is_expected.to be_empty } + end + + end + +end