From 54bbfcaa1f4ba559081c0e86d40fcab420cc41a6 Mon Sep 17 00:00:00 2001 From: Davud Evren Date: Mon, 27 Mar 2023 09:16:26 +0200 Subject: [PATCH] add export to education view --- .../group/educations_controller.rb | 12 ++- .../tabular/people/people_education_list.rb | 25 ++++++ .../tabular/people/people_education_row.rb | 37 +++++++++ .../group/educations/_actions_index.html.haml | 6 ++ app/views/group/educations/index.html.haml | 1 + config/locales/views.youth.de.yml | 2 + config/locales/views.youth.fr.yml | 2 + config/locales/views.youth.it.yml | 2 + .../people/people_education_list_spec.rb | 50 +++++++++++ .../group/educations_controller_spec.rb | 82 +++++++++++++++++++ 10 files changed, 218 insertions(+), 1 deletion(-) create mode 100755 app/domain/export/tabular/people/people_education_list.rb create mode 100755 app/domain/export/tabular/people/people_education_row.rb create mode 100644 app/views/group/educations/_actions_index.html.haml create mode 100755 spec/domain/export/tabular/people/people_education_list_spec.rb diff --git a/app/controllers/group/educations_controller.rb b/app/controllers/group/educations_controller.rb index 8407199b..93007dde 100644 --- a/app/controllers/group/educations_controller.rb +++ b/app/controllers/group/educations_controller.rb @@ -12,7 +12,17 @@ class Group::EducationsController < ApplicationController def index authorize!(:education, group) - @people = education_entries.page(params[:page]) + + respond_to do |format| + format.html { @people = education_entries.page(params[:page]) } + format.csv { export_people(:csv) } + format.xlsx { export_people(:xlsx) } + end + end + + def export_people(format) + exporter = Export::Tabular::People::PeopleEducationList + send_data exporter.export(format, education_entries), type: format end private diff --git a/app/domain/export/tabular/people/people_education_list.rb b/app/domain/export/tabular/people/people_education_list.rb new file mode 100755 index 00000000..b3acc20f --- /dev/null +++ b/app/domain/export/tabular/people/people_education_list.rb @@ -0,0 +1,25 @@ +# encoding: utf-8 + +# Copyright (c) 2012-2023, Pfadibewegung Schweiz. This file is part of +# hitobito_youth 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_youth. + +module Export::Tabular::People + class PeopleEducationList < Export::Tabular::Base + + self.model_class = ::Person + self.row_class = PeopleEducationRow + + def attribute_labels + { first_name: human_attribute(:first_name), + last_name: human_attribute(:last_name), + nickname: human_attribute(:nickname), + email: human_attribute(:email), + birthday: human_attribute(:birthday), + qualifications: Qualification.model_name.human(count: 2), + event_participations: Event::Application.model_name.human(count: 2) } + end + + end +end diff --git a/app/domain/export/tabular/people/people_education_row.rb b/app/domain/export/tabular/people/people_education_row.rb new file mode 100755 index 00000000..e254523c --- /dev/null +++ b/app/domain/export/tabular/people/people_education_row.rb @@ -0,0 +1,37 @@ +# encoding: utf-8 + +# Copyright (c) 2012-2023, Pfadibewegung Schweiz. This file is part of +# hitobito_youth 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_youth. + +module Export::Tabular::People + class PeopleEducationRow < Export::Tabular::Row + + def event_participations + today = Time.zone.today + entry.event_participations. + select do |p| + p.event.supports_applications? && + p.event.dates.sort_by(&:start_at).last.start_at >= today + end. + collect do |p| + p.event.name + end. + join(', ') + end + + def qualifications + entry.qualifications. + select(&:reactivateable?). + sort_by(&:start_at). + reverse. + uniq(&:qualification_kind). + collect do |q| + "#{q.qualification_kind.label}".strip + end. + join(', ') + end + + end +end diff --git a/app/views/group/educations/_actions_index.html.haml b/app/views/group/educations/_actions_index.html.haml new file mode 100644 index 00000000..0db8092a --- /dev/null +++ b/app/views/group/educations/_actions_index.html.haml @@ -0,0 +1,6 @@ +-# Copyright (c) 2023 , Pfadibewegung Schweiz. This file is part of +-# hitobito_youth 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_youth. + += action_button(t('.export_people'), educations_path({format: :csv}.merge(params.to_unsafe_h)), :download) \ No newline at end of file diff --git a/app/views/group/educations/index.html.haml b/app/views/group/educations/index.html.haml index 85f0520e..94af90f8 100644 --- a/app/views/group/educations/index.html.haml +++ b/app/views/group/educations/index.html.haml @@ -7,6 +7,7 @@ - content_for(:filter, FilterNavigation::People.new(self, @group, @person_filter).to_s) +- content_for(:toolbar, render('actions_index')) #main %p diff --git a/config/locales/views.youth.de.yml b/config/locales/views.youth.de.yml index ec6bd2e9..e9f49135 100644 --- a/config/locales/views.youth.de.yml +++ b/config/locales/views.youth.de.yml @@ -94,6 +94,8 @@ de: valid: Gültig valid_until_end_of_year: Gültig bis Ende Jahr can_be_reactivated: Weggefallen, kann reaktiviert werden + actions_index: + export_people: Export dropdown/people_export: nds_course: NDS-Kurs nds_camp: NDS-Lager diff --git a/config/locales/views.youth.fr.yml b/config/locales/views.youth.fr.yml index c14407e0..04851c0d 100644 --- a/config/locales/views.youth.fr.yml +++ b/config/locales/views.youth.fr.yml @@ -63,6 +63,8 @@ fr: valid: Valable valid_until_end_of_year: Valable jusqu'à la fin de l'année can_be_reactivated: Omis, peut être réactivé + actions_index: + export_people: Export event/lists: courses: bsv_export_params_missing: Au minimum, une date de finalisation (au / du) est nécesaire pour l'export OFAS. diff --git a/config/locales/views.youth.it.yml b/config/locales/views.youth.it.yml index 85b776d0..d4be1ad7 100644 --- a/config/locales/views.youth.it.yml +++ b/config/locales/views.youth.it.yml @@ -62,6 +62,8 @@ it: valid: Valido valid_until_end_of_year: Valido fino alla fine dell'anno can_be_reactivated: Omesso, può essere riattivato + actions_index: + export_people: Esportazione event/lists: courses: bsv_export_params_missing: Per l'esportazione UFAS bisogna indicare almeno una data di scadenza (da/a) diff --git a/spec/domain/export/tabular/people/people_education_list_spec.rb b/spec/domain/export/tabular/people/people_education_list_spec.rb new file mode 100755 index 00000000..6f6d0665 --- /dev/null +++ b/spec/domain/export/tabular/people/people_education_list_spec.rb @@ -0,0 +1,50 @@ +# encoding: utf-8 + +# Copyright (c) 2012-2023, Pfadibewegung Schweiz. This file is part of +# hitobito_youth 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_youth. + + +require 'spec_helper' +require 'csv' + +describe Export::Tabular::People::PeopleEducationList do + + let(:top_leader) { people(:top_leader) } + let(:sl) { qualification_kinds(:sl) } + Event::Course + let!(:course) { Fabricate(:course, groups: [groups(:top_group)]) } + let!(:dummy) {course.dates.create!(start_at: 3.month.from_now, finish_at: 4.month.from_now)} + let!(:participation) { Fabricate(:event_participation, person: top_leader, event: course) } + let!(:qualification) { Fabricate(:qualification, person: top_leader, qualification_kind: sl, finish_at: 1.year.from_now) } + + let(:data) { Export::Tabular::People::PeopleEducationList.export(:csv, [top_leader]) } + let(:csv) { CSV.parse(data, headers: true, col_sep: Settings.csv.separator) } + + before do + top_leader.update(birthday: Date.new(2020, 02, 02), nickname: "Tonka") + + end + + context 'german' do + let(:lang) { :de } + + it 'has correct headers' do + expect(csv.headers).to eq([ 'Vorname', 'Nachname', 'Übername', 'Haupt-E-Mail', 'Geburtstag', 'Qualifikationen', 'Anmeldungen' ]) + end + + context 'first row' do + subject { csv[0] } + + its(['Vorname']) { should eq 'Top' } + its(['Nachname']) { should eq 'Leader' } + its(['Übername']) { should eq "Tonka" } + its(['Haupt-E-Mail']) { should eq 'top_leader@example.com' } + its(['Geburtstag']) { should eq "02.02.2020"} + its(['Qualifikationen']) { should eq 'Super Lead' } + its(['Anmeldungen']) { should eq 'Eventus' } + end + end + +end diff --git a/spec/regression/group/educations_controller_spec.rb b/spec/regression/group/educations_controller_spec.rb index ca89454a..25f55ba1 100644 --- a/spec/regression/group/educations_controller_spec.rb +++ b/spec/regression/group/educations_controller_spec.rb @@ -81,4 +81,86 @@ def create_qualification(attrs) end.to raise_error CanCan::AccessDenied end + context 'exports to csv' do + let(:rows) { response.body.split("\n") } + it 'CSV does list leader participations' do + get :index, params: { locale: :de, id: groups(:top_layer).id, range: :layer, filters: { role: { role_type_ids: Group::TopGroup::Leader.id } }, format: :csv } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.size).to eq(2) + expect(rows.second).to have_content 'Top;Leader' + expect(rows.second).to have_content 'Top Course' + end + + it 'CSV does list participant participations' do + get :index, params: { locale: :de, format: :csv, id: groups(:bottom_layer_one).id } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.size).to eq(3) + expect(rows.third).to have_content 'Bottom;Member;' + expect(rows.third).to have_content 'Top Course' + end + + it 'CSV does not list completed events' do + events(:top_course).dates.last.update!(start_at: Date.today - 1.month) + get :index, params: { locale: :de, format: :csv, id: groups(:top_layer).id, range: :layer, filters: { role: { role_type_ids: Group::TopGroup::Leader.id } } } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.second).to eq("Top;Leader;;top_leader@example.com;;\"\";\"\"") + end + + it 'CSV lists qualifications' do + create_qualification(start_at: Date.yesterday) + get :index, params: { locale: :de, format: :csv, id: groups(:top_layer).id, range: :layer, filters: { role: { role_type_ids: Group::TopGroup::Leader.id } } } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.second).to have_content 'Super Lead' + end + + it 'CSV lists qualifications event when expired' do + create_qualification(start_at: Date.today - 3.days, finish_at: Date.yesterday) + get :index, params: { locale: :de, format: :csv, id: groups(:top_layer).id, range: :layer, filters: { role: { role_type_ids: Group::TopGroup::Leader.id } } } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.second).to have_content 'Super Lead' + end + + it 'CSV filters qualifications positive' do + create_qualification(start_at: Date.yesterday) + get :index, + params: { + locale: :de, + format: :csv, + id: groups(:top_layer).id, + range: :layer, + filters: { qualification: { qualification_kind_ids: qualification_kinds(:sl).id } } + } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.second).to have_content 'Super Lead' + end + + it 'CSV filters qualifications negative' do + create_qualification(start_at: Date.yesterday) + get :index, + params: { + locale: :de, + format: :csv, + id: groups(:top_layer).id, + range: :layer, + filters: { qualification: { qualification_kind_ids: qualification_kinds(:gl).id } } + } + expect(response).to be_successful + expect(rows.first).to match("Vorname;Nachname;Übername;Haupt-E-Mail;Geburtstag;Qualifikationen;Anmeldungen") + expect(rows.second).not_to have_content 'Super Lead' + end + + it 'CSV raises AccessDenied if not permitted' do + sign_in(people(:bottom_leader)) + expect do + get :index, params: { locale: :de, format: :csv, id: groups(:top_layer).id, range: :layer, filters: { role: { role_type_ids: Group::TopGroup::Leader.id } } } + end.to raise_error CanCan::AccessDenied + end + end + end