Skip to content

Commit

Permalink
Merge pull request #291 from hitobito/feature/pbs_273-silverscouts-optin
Browse files Browse the repository at this point in the history
Implement alumni invitation/reminder emails
  • Loading branch information
TheWalkingLeek authored Oct 24, 2023
2 parents 77114cf + 1ad4017 commit 296b70c
Show file tree
Hide file tree
Showing 22 changed files with 1,086 additions and 10 deletions.
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
2 changes: 1 addition & 1 deletion app/models/group/silverscouts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ class PowerUser < ::Role

roles Verantwortung, Lesezugriff, PowerUser

children Silverscouts::Region
children SilverscoutsRegion
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_pbs.

class Group::Silverscouts::Region < ::Group
class Group::SilverscoutsRegion < ::Group
self.layer = true

class Leitung < ::Role
Expand Down
6 changes: 3 additions & 3 deletions config/locales/models.pbs.de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ de:
group/silverscouts:
one: Silverscouts
other: Silverscouts
group/silverscouts/region:
group/silverscouts_region:
one: Region
other: Regionen
group/woelfe:
Expand Down Expand Up @@ -688,11 +688,11 @@ de:
other: PowerUser
description:

group/silverscouts/region/leitung:
group/silverscouts_region/leitung:
one: Leitung
other: Leitung
description:
group/silverscouts/region/mitglied:
group/silverscouts_region/mitglied:
one: Mitglied
other: Mitglieder
description:
Expand Down
9 changes: 9 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,12 @@ groups:
enabled: true
statistics:
enabled: false

alumni:
invitation:
enabled: true
role_deleted_before_ago: P3M # must be a ISO 8601 duration string
role_deleted_after_ago: P6M # must be a ISO 8601 duration string
reminder:
role_deleted_before_ago: P6M # must be a ISO 8601 duration string

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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 AddAlumniProcessingTimestampsToRole < ActiveRecord::Migration[6.1]
def up
add_column :roles, :alumni_invitation_processed_at, :datetime, null: true
add_column :roles, :alumni_reminder_processed_at, :datetime, null: true

connection.execute <<~SQL
UPDATE roles
SET alumni_invitation_processed_at = '1970-01-01', alumni_reminder_processed_at = '1970-01-01'
WHERE deleted_at < NOW()
SQL
end

def down
remove_column :roles, :alumni_reminder_processed_at
remove_column :roles, :alumni_invitation_processed_at
end
end
Loading

0 comments on commit 296b70c

Please sign in to comment.