Skip to content

Commit

Permalink
Merge pull request #295 from hitobito/feature/silverscouts
Browse files Browse the repository at this point in the history
Feature/silverscouts
  • Loading branch information
TheWalkingLeek committed Nov 21, 2023
2 parents 1be1532 + 83a84a4 commit 38910f8
Show file tree
Hide file tree
Showing 46 changed files with 1,478 additions and 75 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* 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)
* Adressverwalter\*innen auf der Abteilungsebene können neu auch für Zugriffsanfragen ausgewählt werden. Merci @philobr! (hitobito/hitobito_pbs#261)
* Die Gruppenstruktur wurde so angepasst, dass neu Silverscouts und Silverscouts Regionen unter der "Root" Ebene erstellt werden können (hitobito_pbs#271)
* Neu kann der Hostname auf PBS und SiSc Ebene gesetzt werden (hitobito_pbs#272)

## Version 1.28

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ This hitobito wagon defines the organization hierarchy with groups and roles of
# Pfadi Organization Hierarchy

<!-- roles:start -->
* Root
* Root
* Admin: 2FA [:layer_and_below_full]
* Bund
* Bund
* Mitarbeiter*in GS: 2FA [:layer_and_below_full, :contact_data, :admin]
Expand Down Expand Up @@ -256,6 +259,14 @@ This hitobito wagon defines the organization hierarchy with groups and roles of
* Internes Gremium
* Leitung: [:group_and_below_full]
* Mitglied: [:group_read]
* Silverscouts
* Silverscouts
* Leitung: [:group_read, :contact_data]
* Mitglied: []
* Global
* Ehemalige
* Mitglied: []
* Leitung: [:group_full]

(Output of rake app:hitobito:roles)
<!-- roles:end -->
2 changes: 1 addition & 1 deletion app/abilities/pbs/event/constraints.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def in_same_layer_or_below_with_excluded_abteilungen_for_courses
def in_same_layer_or_course_in_below_abteilung
in_same_layer ||
(course_in_abteilung? &&
permission_in_layers?(course_group_ids_above_abteilung - [Group.root.id]))
permission_in_layers?(course_group_ids_above_abteilung - [Group.bund.id]))
end

private
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/censuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ class CensusesController < CrudController
decorates :group

def create
super(location: census_bund_group_path(Group.root))
super(location: census_bund_group_path(group))
end

private

def group
@group ||= Group.root
@group ||= Group.bund
end

end
20 changes: 20 additions & 0 deletions app/controllers/concerns/hostnamed_groups.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# 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.

module HostnamedGroups
extend ActiveSupport::Concern

included do
prepend_before_action :determine_group_by_hostname
end

# Initialize @group by matching the current request hostname.
# This is used in LayoutHelper#header_logo to show a specific group logo depending on the hostname
def determine_group_by_hostname
@group ||= Group.where(hostname: request.host).first
end
end
32 changes: 32 additions & 0 deletions app/domain/alumni/applicable_groups.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

# 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.

module Alumni
class ApplicableGroups
attr_reader :role

def initialize(role)
@role = role
end

def silverscout_group_ids
Group::SilverscoutsRegion.
without_deleted.
where.not(self_registration_role_type: nil).
pluck(:id)
end

def ex_members_group_ids
ancestor_layers = role.group.layer_group.self_and_ancestors
Group::Ehemalige.
without_deleted.
where(layer_group_id: ancestor_layers).
where.not(self_registration_role_type: nil).
pluck(:id)
end
end
end
89 changes: 89 additions & 0 deletions app/domain/alumni/invitation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

# 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.

module Alumni
class Invitation
AGE_GROUPS = [
Group::Biber, Group::Woelfe, Group::Pfadi, Group::Pio, Group::Pta
].freeze

ALUMNI_ROLES = [Group::Ehemalige::Mitglied, Group::Ehemalige::Leitung].freeze

SILVERSCOUT_GROUPS = [Group::Silverscouts, Group::SilverscoutsRegion].freeze

attr_reader :role, :type, :alumni_groups

def initialize(role, type, alumni_groups = ApplicableGroups.new(role))
@role = role
@type = type
@alumni_groups = alumni_groups
raise "Unknown type: #{type}" unless AlumniMailer.respond_to?(type)
end

def process
set_timestamp
send_invitation if conditions_met?
end

def conditions_met?
feature_enabled? &&
no_active_role_in_layer? &&
old_enough_if_in_age_group? &&
applicable_role? &&
person_has_main_email? &&
person_has_no_alumni_role?
end

def feature_enabled?
FeatureGate.enabled?('alumni.invitation')
end

def no_active_role_in_layer?
!Role.
joins(:group).
where(person_id: role.person_id, group: { layer_group_id: role.group.layer_group_id }).
exists?
end

def old_enough_if_in_age_group?
return true unless AGE_GROUPS.include?(role.group.class)

role.person.birthday.present? && role.person.birthday <= 16.years.ago.to_date
end

def applicable_role?
ALUMNI_ROLES.exclude?(role.class) &&
SILVERSCOUT_GROUPS.exclude?(role.group.class) &&
!role.group.is_a?(Group::Root)
end

def person_has_main_email?
role.person.email.present?
end

def person_has_no_alumni_role?
role.person.roles.none? do |role|
ALUMNI_ROLES.include?(role.class) ||
SILVERSCOUT_GROUPS.include?(role.group.class) ||
role.group.is_a?(Group::Root)
end
end

private

def set_timestamp
timestamp_attr = "alumni_#{type}_processed_at"
role.update!(timestamp_attr => Time.zone.now)
end

def send_invitation
AlumniMailer.send(type, role.person, alumni_groups.ex_members_group_ids.presence,
alumni_groups.silverscout_group_ids.presence).
deliver_later
end
end
end
39 changes: 39 additions & 0 deletions app/domain/alumni/invitations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

# 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.

module Alumni
class Invitations
class_attribute :type, default: :invitation

def process
relevant_roles.each { |role| Alumni::Invitation.new(role, type).process }
end

def relevant_roles
Role.
with_deleted.
where(deleted_at: time_range, alumni_invitation_processed_at: nil).
includes(:person, :group)
end

def time_range
from = parse_duration(:alumni, :invitation, :role_deleted_after_ago)
to = parse_duration(:alumni, :invitation, :role_deleted_before_ago)
from.ago..to.ago
end

private

def parse_duration(*settings_path)
iso8601duration = Settings.dig(*settings_path)
ActiveSupport::Duration.parse(iso8601duration)
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError, ArgumentError
raise "Value #{iso8601duration.inspect} at Settings.#{settings_path.join('')} " +
'is not a valid ISO8601 duration'
end
end
end
16 changes: 16 additions & 0 deletions app/domain/alumni/reminders.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# 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.

module Alumni
class Reminders < Invitations
self.type = :reminder

def time_range
..parse_duration(:alumni, :reminder, :role_deleted_before_ago).ago
end
end
end
15 changes: 15 additions & 0 deletions app/jobs/alumni_invitations_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

# 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 AlumniInvitationsJob < RecurringJob
run_every 1.day

def perform
Alumni::Invitations.new.process
Alumni::Reminders.new.process
end
end
70 changes: 70 additions & 0 deletions app/mailers/alumni_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

# 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 AlumniMailer < ApplicationMailer

include ActionView::Helpers::TagHelper
include ActionView::Context
include Rails.application.routes.url_helpers

CONTENT_INVITATION_WITH_REGIONAL_GROUPS = 'alumni_invitation_with_regional_alumni_groups'
CONTENT_INVITATION_WITHOUT_REGIONAL_GROUPS = 'alumni_invitation_without_regional_alumni_groups'

CONTENT_REMINDER_WITH_REGIONAL_GROUPS = 'alumni_reminder_with_regional_alumni_groups'
CONTENT_REMINDER_WITHOUT_REGIONAL_GROUPS = 'alumni_reminder_without_regional_alumni_groups'

def invitation(person, ex_members_group_ids, silverscout_group_ids)
@person = person
@ex_members_groups = Group.where(id: ex_members_group_ids)
@silverscout_groups = Group.where(id: silverscout_group_ids)

key = if @ex_members_groups.present?
CONTENT_INVITATION_WITH_REGIONAL_GROUPS
else
CONTENT_INVITATION_WITHOUT_REGIONAL_GROUPS
end

custom_content_mail(@person.email, key, values_for_placeholders(key))
end

def reminder(person, ex_members_group_ids, silverscout_group_ids)
@person = person
@ex_members_groups = Group.where(id: ex_members_group_ids)
@silverscout_groups = Group.where(id: silverscout_group_ids)

key = if @ex_members_groups.present?
CONTENT_REMINDER_WITH_REGIONAL_GROUPS
else
CONTENT_REMINDER_WITHOUT_REGIONAL_GROUPS
end

custom_content_mail(@person.email, key, values_for_placeholders(key))
end

private

# Placeholder {person-name}
def placeholder_person_name
@person.full_name
end

# Placeholder {SiScRegion-Links}
def placeholder_si_sc_region_links
format_helper.group_selfinscription_links(@silverscout_groups, &:name)
end

# Placeholder {AlumniGroup-Links}
def placeholder_alumni_group_links
format_helper.group_selfinscription_links(@ex_members_groups) do |group|
"#{group.parent.name}: #{group.name}"
end
end

def format_helper
@format_helper ||= AlumniMailer::FormatHelper.new(default_url_options)
end
end
37 changes: 37 additions & 0 deletions app/mailers/alumni_mailer/format_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

# 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 AlumniMailer::FormatHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include ActionView::Context
include Rails.application.routes.url_helpers

def initialize(default_url_options)
@default_url_options = default_url_options # used for path helpers
end

def group_selfinscription_links(groups, &block)
content_tag(:ul) do
groups.each_with_object([]) do |group, links|
links << content_tag(:li) { group_selfinscription_link(group, &block) }
end.join.html_safe
end
end

def group_selfinscription_link(group, &block)
url = group_self_registration_url(group_id: group, target: '_blank')
label = block.call(group)
link_to(label, url).html_safe
end

private

# used for path helpers
def controller; end

end
3 changes: 2 additions & 1 deletion app/models/group/abteilung.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class Group::Abteilung < Group
Group::Elternrat,
Group::AbteilungsGremium,
Group::InternesAbteilungsGremium,
Group::ErziehungsberechtigtenGremium
Group::ErziehungsberechtigtenGremium,
Group::Ehemalige

has_many :member_counts # rubocop:disable Rails/HasManyOrHasOneDependent since groups are only soft-deleted
has_many :geolocations, as: :geolocatable, dependent: :destroy
Expand Down

0 comments on commit 38910f8

Please sign in to comment.