Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add export to education view #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/controllers/group/educations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions app/domain/export/tabular/people/people_education_list.rb
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions app/domain/export/tabular/people/people_education_row.rb
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +11 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not great that this logic is duplicated in large parts from the helper to here.
Maybe you could add the common part of the logic to the Youth::PersonDecorator (e.g. as methods education_event_participations etc.), and then use that here and in the helper.
Either entry.education_event_participations.join(', ') or entry.decorate.education_event_participations.join(', ') should work.


end
end
6 changes: 6 additions & 0 deletions app/views/group/educations/_actions_index.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-# Copyright (c) 2023 , Pfadibewegung Schweiz. This file is part of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-# Copyright (c) 2023 , Pfadibewegung Schweiz. This file is part of
-# 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You added the capability for CSV and XLSX to the controller, but only expose CSV here. Wouldn't XLSX be more valuable? Maybe in the future we might want to color some of the text in the spreadsheet, depending on when some qualifications expire.

The CSV is clearly easier to test though.

1 change: 1 addition & 0 deletions app/views/group/educations/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

- content_for(:filter, FilterNavigation::People.new(self, @group, @person_filter).to_s)

- content_for(:toolbar, render('actions_index'))

#main
%p
Expand Down
2 changes: 2 additions & 0 deletions config/locales/views.youth.de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions config/locales/views.youth.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions config/locales/views.youth.it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions spec/domain/export/tabular/people/people_education_list_spec.rb
Original file line number Diff line number Diff line change
@@ -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 '[email protected]' }
its(['Geburtstag']) { should eq "02.02.2020"}
its(['Qualifikationen']) { should eq 'Super Lead' }
its(['Anmeldungen']) { should eq 'Eventus' }
end
end

end
82 changes: 82 additions & 0 deletions spec/regression/group/educations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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;;[email protected];;\"\";\"\"")
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