diff --git a/app/controllers/admins_group_managers/admins_group_manager_controller.rb b/app/controllers/admins_group_managers/admins_group_manager_controller.rb new file mode 100644 index 000000000..fad6629e3 --- /dev/null +++ b/app/controllers/admins_group_managers/admins_group_manager_controller.rb @@ -0,0 +1,9 @@ +module AdminsGroupManagers + class AdminsGroupManagerController < ApplicationController + before_action :authenticate_admins_group_manager! + + def nav_bar_profile + :admins_group_manager + end + end +end diff --git a/app/controllers/admins_group_managers/admins_groups_controller.rb b/app/controllers/admins_group_managers/admins_groups_controller.rb new file mode 100644 index 000000000..3e599883a --- /dev/null +++ b/app/controllers/admins_group_managers/admins_groups_controller.rb @@ -0,0 +1,14 @@ +module AdminsGroupManagers + class AdminsGroupsController < AdminsGroupManagerController + def index + @admins_groups = admins_groups + end + + private + + def admins_groups + admins_group_ids = current_admins_group_manager.admins_group_ids + AdminsGroup.where(id: admins_group_ids.compact.uniq) + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4933ea04d..b95db7663 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -21,7 +21,8 @@ class ApplicationController < ActionController::Base around_action :switch_locale helper_method :multiple_devise_profile_connect?, :instructeur_signed_in?, :current_instructeur, :current_expert, :expert_signed_in?, - :administrateur_signed_in?, :current_administrateur, :current_account, :localization_enabled?, :set_locale, :current_expert_not_instructeur? + :administrateur_signed_in?, :current_administrateur, :current_account, :localization_enabled?, :set_locale, :current_expert_not_instructeur?, + :admins_group_manager_signed_in?, :current_admins_group_manager before_action do Current.request_id = request.uuid @@ -37,9 +38,12 @@ class ApplicationController < ActionController::Base def multiple_devise_profile_connect? user_signed_in? && instructeur_signed_in? || instructeur_signed_in? && administrateur_signed_in? || + instructeur_signed_in? && admins_group_manager_signed_in? || instructeur_signed_in? && expert_signed_in? || user_signed_in? && administrateur_signed_in? || - user_signed_in? && expert_signed_in? + user_signed_in? && admins_group_manager_signed_in? || + user_signed_in? && expert_signed_in? || + administrateur_signed_in? && admins_group_manager_signed_in? end def current_instructeur @@ -58,6 +62,14 @@ class ApplicationController < ActionController::Base current_administrateur.present? end + def current_admins_group_manager + current_user&.admins_group_manager + end + + def admins_group_manager_signed_in? + current_admins_group_manager.present? + end + def current_expert current_user&.expert end @@ -72,6 +84,7 @@ class ApplicationController < ActionController::Base def current_account { + admins_group_manager: current_admins_group_manager, administrateur: current_administrateur, instructeur: current_instructeur, user: current_user @@ -115,6 +128,8 @@ class ApplicationController < ActionController::Base authenticate_expert! elsif administrateur_signed_in? authenticate_administrateur! + elsif admins_group_manager_signed_in? + authenticate_admins_group_manager! else authenticate_user! end @@ -144,6 +159,12 @@ class ApplicationController < ActionController::Base end end + def authenticate_admins_group_manager! + if !admins_group_manager_signed_in? + redirect_to new_user_session_path + end + end + def after_sign_out_path_for(_resource_or_scope) stored_location_for(:user) || super end @@ -178,6 +199,7 @@ class ApplicationController < ActionController::Base current_user, current_instructeur, current_administrateur, + current_admins_group_manager, current_super_admin ].compact.map { |role| role.class.name } diff --git a/app/controllers/manager/admins_group_managers_controller.rb b/app/controllers/manager/admins_group_managers_controller.rb new file mode 100644 index 000000000..f580748e1 --- /dev/null +++ b/app/controllers/manager/admins_group_managers_controller.rb @@ -0,0 +1,4 @@ +module Manager + class AdminsGroupManagersController < Manager::ApplicationController + end +end diff --git a/app/controllers/manager/admins_groups_controller.rb b/app/controllers/manager/admins_groups_controller.rb new file mode 100644 index 000000000..ebb92784c --- /dev/null +++ b/app/controllers/manager/admins_groups_controller.rb @@ -0,0 +1,32 @@ +module Manager + class AdminsGroupsController < Manager::ApplicationController + def add_admins_group_manager + emails = (params['emails'].presence || '').split(',').to_json + emails = JSON.parse(emails).map { EmailSanitizableConcern::EmailSanitizer.sanitize(_1) } + + admins_group_managers, invalid_emails = admins_group.add_admins_group_managers(emails:) + + if invalid_emails.present? + flash[:alert] = t('.wrong_address', + count: invalid_emails.size, + emails: invalid_emails) + end + + if admins_group_managers.present? + flash[:notice] = "Les gestionnaires ont bien été affectés au groupe d'administrateurs" + + AdminsGroupMailer + .notify_added_admins_group_managers(admins_group, admins_group_managers, current_super_admin.email) + .deliver_later + end + + redirect_to manager_admins_groups_path(admins_group) + end + + private + + def admins_group + @admins_group ||= AdminsGroup.find(params[:id]) + end + end +end diff --git a/app/dashboards/admins_group_dashboard.rb b/app/dashboards/admins_group_dashboard.rb new file mode 100644 index 000000000..223d15a43 --- /dev/null +++ b/app/dashboards/admins_group_dashboard.rb @@ -0,0 +1,54 @@ +require "administrate/base_dashboard" + +class AdminsGroupDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + name: Field::String, + created_at: Field::DateTime, + updated_at: Field::DateTime, + admins_group_managers: Field::HasMany, + administrateurs: Field::HasMany + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [ + :id, + :created_at, + :name, + :admins_group_managers, + :administrateurs + ].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [ + :admins_group_managers, + :administrateurs, + :id, + :created_at + ].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [ + :name + ].freeze + + # Overwrite this method to customize how users are displayed + # across all pages of the admin dashboard. + # + def display_resource(admins_group) + admins_group.name + end +end diff --git a/app/dashboards/admins_group_manager_dashboard.rb b/app/dashboards/admins_group_manager_dashboard.rb new file mode 100644 index 000000000..09a8c8c20 --- /dev/null +++ b/app/dashboards/admins_group_manager_dashboard.rb @@ -0,0 +1,57 @@ +require "administrate/base_dashboard" + +class AdminsGroupManagerDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + user: Field::HasOne.with_options(searchable: true, searchable_field: 'email'), + created_at: Field::DateTime, + updated_at: Field::DateTime, + admins_groups: Field::HasMany.with_options(limit: 20), + registration_state: Field::String.with_options(searchable: false), + email: Field::Email.with_options(searchable: false) + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [ + :id, + :user, + :created_at, + :admins_groups, + :registration_state + ].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [ + :id, + :user, + :created_at, + :updated_at, + :registration_state, + :admins_groups + ].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [ + :email + ].freeze + + # Overwrite this method to customize how users are displayed + # across all pages of the admin dashboard. + # + def display_resource(admins_group_manager) + admins_group_manager.email + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 061a6c957..974a2aac1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -47,7 +47,8 @@ module ApplicationHelper def current_email current_user&.email || current_instructeur&.email || - current_administrateur&.email + current_administrateur&.email || + current_admins_group_manager&.email end def staging? @@ -77,6 +78,8 @@ module ApplicationHelper case nav_bar_profile when :administrateur [admin_procedures_path, t("admin", scope: "layouts.root_path_link_title")] + when :admins_group_manager + [admins_group_manager_admins_groups_path, t("admins_group_manager", scope: "layouts.root_path_link_title")] when :instructeur [instructeur_procedures_path, t("instructeur", scope: "layouts.root_path_link_title")] when :user diff --git a/app/mailers/admins_group_mailer.rb b/app/mailers/admins_group_mailer.rb new file mode 100644 index 000000000..03e552a1b --- /dev/null +++ b/app/mailers/admins_group_mailer.rb @@ -0,0 +1,13 @@ +class AdminsGroupMailer < ApplicationMailer + layout 'mailers/layout' + + def notify_added_admins_group_managers(admins_group, added_admins_group_managers, current_super_admin_email) + added_admins_group_manager_emails = added_admins_group_managers.map(&:email) + @admins_group = admins_group + @current_super_admin_email = current_super_admin_email + + subject = "Vous avez été ajouté(e) en tant que gestionnaire du groupe d'administrateur \"#{admins_group.name}\"" + + mail(bcc: added_admins_group_manager_emails, subject: subject) + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index b7e293aed..4304dfce5 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -38,6 +38,17 @@ class UserMailer < ApplicationMailer reply_to: CONTACT_EMAIL) end + def invite_admins_group_manager(user, reset_password_token, admins_group) + @reset_password_token = reset_password_token + @user = user + @admins_group = admins_group + subject = "Activez votre compte gestionnaire" + + mail(to: user.email, + subject: subject, + reply_to: CONTACT_EMAIL) + end + def send_archive(administrateur_or_instructeur, procedure, archive) @archive = archive @procedure = procedure diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index 1c68711c0..73904ae39 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -9,6 +9,7 @@ class Administrateur < ApplicationRecord has_and_belongs_to_many :default_zones, class_name: 'Zone', join_table: 'default_zones_administrateurs' belongs_to :user + belongs_to :admins_group, optional: true default_scope { eager_load(:user) } diff --git a/app/models/admins_group.rb b/app/models/admins_group.rb new file mode 100644 index 000000000..e828e419b --- /dev/null +++ b/app/models/admins_group.rb @@ -0,0 +1,30 @@ +class AdminsGroup < ApplicationRecord + belongs_to :admins_group, optional: true # parent + has_many :children, class_name: "AdminsGroup", inverse_of: :admins_group + has_many :administrateurs + has_and_belongs_to_many :admins_group_managers + + def add(admins_group_manager) + admins_group_managers << admins_group_manager + end + + def add_admins_group_managers(ids: [], emails: []) + admins_group_managers_to_add, valid_emails, invalid_emails = AdminsGroupManager.find_all_by_identifier_with_emails(ids:, emails:) + not_found_emails = valid_emails - admins_group_managers_to_add.map(&:email) + + # Send invitations to users without account + if not_found_emails.present? + admins_group_managers_to_add += not_found_emails.map do |email| + user = User.create_or_promote_to_admins_group_manager(email, SecureRandom.hex) + user.invite_admins_group_manager!(self) + user.admins_group_manager + end + end + + # We dont't want to assign a user to an admins_group if they are already assigned to it + admins_group_managers_to_add -= admins_group_managers + admins_group_managers_to_add.each { add(_1) } + + [admins_group_managers_to_add, invalid_emails] + end +end diff --git a/app/models/admins_group_manager.rb b/app/models/admins_group_manager.rb new file mode 100644 index 000000000..cb11b1bd3 --- /dev/null +++ b/app/models/admins_group_manager.rb @@ -0,0 +1,41 @@ +class AdminsGroupManager < ApplicationRecord + has_and_belongs_to_many :admins_groups + + belongs_to :user + + delegate :email, to: :user + + default_scope { eager_load(:user) } + + def self.by_email(email) + find_by(users: { email: email }) + end + + def self.find_all_by_identifier(ids: [], emails: []) + find_all_by_identifier_with_emails(ids:, emails:).first + end + + def self.find_all_by_identifier_with_emails(ids: [], emails: []) + valid_emails, invalid_emails = emails.partition { URI::MailTo::EMAIL_REGEXP.match?(_1) } + + [ + where(id: ids).or(where(users: { email: valid_emails })).distinct(:id), + valid_emails, + invalid_emails + ] + end + + def can_be_deleted? + !(root_admins_group = admins_groups.where(admins_group: nil).first) || root_admins_group.admins_group_managers.size > 1 + end + + def registration_state + if user.active? + 'Actif' + elsif user.reset_password_period_valid? + 'En attente' + else + 'Expiré' + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 9aabfe35f..25c08fb4e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,6 +26,7 @@ class User < ApplicationRecord has_one :france_connect_information, dependent: :destroy has_one :instructeur, dependent: :destroy has_one :administrateur, dependent: :destroy + has_one :admins_group_manager, dependent: :destroy has_one :expert, dependent: :destroy belongs_to :requested_merge_into, class_name: 'User', optional: true @@ -76,6 +77,10 @@ class User < ApplicationRecord UserMailer.invite_instructeur(self, set_reset_password_token).deliver_later end + def invite_admins_group_manager!(admins_group) + UserMailer.invite_admins_group_manager(self, set_reset_password_token, admins_group).deliver_later + end + def invite_administrateur!(administration_id) AdministrationMailer.invite_admin(self, set_reset_password_token, administration_id).deliver_later end @@ -103,6 +108,16 @@ class User < ApplicationRecord user end + def self.create_or_promote_to_admins_group_manager(email, password) + user = User.create_or_promote_to_administrateur(email, password) + + if user.valid? && user.admins_group_manager.nil? + user.create_admins_group_manager! + end + + user + end + def self.create_or_promote_to_administrateur(email, password) user = User.create_or_promote_to_instructeur(email, password) @@ -145,6 +160,10 @@ class User < ApplicationRecord instructeur.present? end + def admins_group_manager? + admins_group_manager.present? + end + def expert? expert.present? end diff --git a/app/views/admins_group_mailer/notify_added_admins_group_managers.html.haml b/app/views/admins_group_mailer/notify_added_admins_group_managers.html.haml new file mode 100644 index 000000000..40c1190a3 --- /dev/null +++ b/app/views/admins_group_mailer/notify_added_admins_group_managers.html.haml @@ -0,0 +1,6 @@ +%p= t(:hello, scope: [:views, :shared, :greetings]) + +%p + = t(".email_body", admins_group_name: @admins_group.name, email: @current_super_admin_email, application_name: APPLICATION_NAME) + += render partial: "layouts/mailers/signature" diff --git a/app/views/admins_group_managers/_breadcrumbs.html.haml b/app/views/admins_group_managers/_breadcrumbs.html.haml new file mode 100644 index 000000000..2aee56b39 --- /dev/null +++ b/app/views/admins_group_managers/_breadcrumbs.html.haml @@ -0,0 +1,16 @@ +#breadcrumbs.sub-header + .fr-container.flex.justify-between.align-baseline.column + %nav.fr-breadcrumb.mt-0{ role: "navigation", aria: { label: t('you_are_here', scope: [:layouts, :breadcrumb]) } } + %button.fr-breadcrumb__button{ aria: { expanded: "false", controls: "breadcrumb-1" } } + = t('show', scope: [:layouts, :breadcrumb]) + + .fr-collapse#breadcrumb-1 + %ol.fr-breadcrumb__list + %li= link_to t('root', scope: [:layouts, :breadcrumb]), root_path, class: 'fr-breadcrumb__link' + + - steps.each.with_index do |step, i| + - if i == steps.size - 1 + %li{ aria: { current: "page" } } + %span.fr-breadcrumb__link= step[0] + - else + %li= link_to step[0], step[1], class: 'fr-breadcrumb__link' diff --git a/app/views/admins_group_managers/admins_groups/index.html.haml b/app/views/admins_group_managers/admins_groups/index.html.haml new file mode 100644 index 000000000..fc9fb0924 --- /dev/null +++ b/app/views/admins_group_managers/admins_groups/index.html.haml @@ -0,0 +1,17 @@ += render partial: 'admins_group_managers/breadcrumbs', + locals: { steps: [['Groupes d\'administrateurs', admins_group_manager_admins_groups_path]] } + +#admins_groups-index.container + %h1.fr-h1 Liste des groupes d'administrateurs + + %table.fr-table.width-100.mt-3 + %thead + %tr + %th{ scope: "col" } + Nom + + %tbody + - @admins_groups.each do |admins_group| + %tr + %td + = admins_group.name diff --git a/app/views/layouts/_account_dropdown.haml b/app/views/layouts/_account_dropdown.haml index cd76068ff..bfcd975d6 100644 --- a/app/views/layouts/_account_dropdown.haml +++ b/app/views/layouts/_account_dropdown.haml @@ -30,6 +30,11 @@ = link_to admin_procedures_path, class: "fr-nav__link" do %span.fr-icon-refresh-line.fr-icon--sm = t('go_admin', scope: [:layouts]) + - if admins_group_manager_signed_in? && nav_bar_profile != :admins_group_manager + %li + = link_to admins_group_manager_admins_groups_path, class: "fr-nav__link" do + %span.fr-icon-refresh-line.fr-icon--sm + = t('go_admins_group_manager', scope: [:layouts]) - if super_admin_signed_in? %li diff --git a/app/views/manager/admins_groups/show.html.erb b/app/views/manager/admins_groups/show.html.erb new file mode 100644 index 000000000..e7e777c31 --- /dev/null +++ b/app/views/manager/admins_groups/show.html.erb @@ -0,0 +1,57 @@ +<%# +# Show + +This view is the template for the show page. +It renders the attributes of a resource, +as well as a link to its edit page. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Show][1]. + Contains methods for accessing the resource to be displayed on the page, + as well as helpers for describing how each attribute of the resource + should be displayed. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show +%> +<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %> +<% admins_group = page.resource %> + + + +
+
+ <% page.attributes.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: attribute.name.titleize, + ) %> +
+ +
+ <%= render_field attribute, page: page %> + <% if attribute.name == 'admins_group_managers' %> + <%= form_tag(add_admins_group_manager_manager_admins_group_path(admins_group), style: 'margin-top: 1rem;') do %> + <%= email_field_tag(:emails, '', placeholder: 'Emails', autocapitalize: 'off', autocorrect: 'off', spellcheck: 'false', style: 'margin-bottom: 1rem;width:24rem;') %> + + <% end %> + <% end %> +
+ <% end %> +
+
diff --git a/app/views/user_mailer/invite_admins_group_manager.html.haml b/app/views/user_mailer/invite_admins_group_manager.html.haml new file mode 100644 index 000000000..879f9898f --- /dev/null +++ b/app/views/user_mailer/invite_admins_group_manager.html.haml @@ -0,0 +1,13 @@ +- content_for(:title, 'Activation de votre compte gestionnaire') + +%p + Bonjour, + +%p + Vous venez d’être nommé gestionnaire du groupe #{@admins_group.name} sur #{APPLICATION_NAME}. + +%p + Votre compte a été créé pour l'adresse email #{@user.email}. Pour l’activer, nous vous invitons à cliquer sur le lien suivant :  + = link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token)) + += render partial: "layouts/mailers/signature" diff --git a/config/env.example b/config/env.example index a3624b572..05277a328 100644 --- a/config/env.example +++ b/config/env.example @@ -155,3 +155,6 @@ CLAMAV_ENABLED="disabled" # Siret number used for API Entreprise, by default we use SIRET from dinum API_ENTREPRISE_DEFAULT_SIRET="put_your_own_siret" + +# Admins group usage (gestionnaire de groupes d'administrateurs) +ADMINS_GROUP_ENABLED="disabled" \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 50e4b11f9..15da4b231 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -101,6 +101,7 @@ en: user: 'Go to files list' instructeur: 'Go to procedures list' admin: 'Go to administration panel' + admins_group_manager: "Aller admins group panel" views: legal_notice: title: "Legal Notices" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 26fe87ff1..aa6d6fb5f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -92,6 +92,7 @@ fr: user: 'Aller à la liste des dossiers' instructeur: 'Aller à la liste des démarches' admin: "Aller au panneau d’administration" + admins_group_manager: "Aller au panneau de gestionnaire" views: legal_notice: title: "Mentions légales" diff --git a/config/locales/models/admins_group/fr.yml b/config/locales/models/admins_group/fr.yml new file mode 100644 index 000000000..4aeb8b4ba --- /dev/null +++ b/config/locales/models/admins_group/fr.yml @@ -0,0 +1,9 @@ +fr: + activerecord: + attributes: + admins_group: + admins_group_managers: Gestionnaires + models: + admins_group: + one: Groupe d'administrateurs + other: Groupes d'administrateurs diff --git a/config/locales/models/admins_group_manager/fr.yml b/config/locales/models/admins_group_manager/fr.yml new file mode 100644 index 000000000..c6d9b2293 --- /dev/null +++ b/config/locales/models/admins_group_manager/fr.yml @@ -0,0 +1,9 @@ +fr: + activerecord: + attributes: + admins_group_manager: + admins_groups: Groupes + models: + admins_group_manager: + one: Gestionnaire + other: Gestionnaires diff --git a/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/en.yml b/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/en.yml new file mode 100644 index 000000000..95528051d --- /dev/null +++ b/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/en.yml @@ -0,0 +1,4 @@ +en: + admins_group_mailer: + notify_added_admins_group_managers: + email_body: "You were assigned as manager on the admins group %{admins_group_name} on %{application_name} by « %{email} »" diff --git a/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/fr.yml b/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/fr.yml new file mode 100644 index 000000000..f63b8007c --- /dev/null +++ b/config/locales/views/admins_group_mailer/notify_added_admins_group_managers/fr.yml @@ -0,0 +1,4 @@ +fr: + admins_group_mailer: + notify_added_admins_group_managers: + email_body: "Vous venez d’être nommé gestionnaire du groupe %{admins_group_name} sur %{application_name} par « %{email} »." diff --git a/config/locales/views/layouts/_account_dropdown.en.yml b/config/locales/views/layouts/_account_dropdown.en.yml index b9925c6f7..b4ec8ff11 100644 --- a/config/locales/views/layouts/_account_dropdown.en.yml +++ b/config/locales/views/layouts/_account_dropdown.en.yml @@ -8,12 +8,14 @@ en: go_instructor: "Switch to instructor" go_expert: "Switch to expert" go_admin: "Switch to administrator" + go_admins_group_manager: "Switch to admins group manager" profile: "See my profile" logout: "Log out" my_account: "My account" connected_as: "connected as %{profile}" instructeur: instructor administrateur: admin + admins_group_manager: admins group manager expert: expert user: user guest: guest diff --git a/config/locales/views/layouts/_account_dropdown.fr.yml b/config/locales/views/layouts/_account_dropdown.fr.yml index bbdba1e9e..4a83e1dc2 100644 --- a/config/locales/views/layouts/_account_dropdown.fr.yml +++ b/config/locales/views/layouts/_account_dropdown.fr.yml @@ -8,12 +8,14 @@ fr: go_instructor: "Passer en instructeur" go_expert: "Passer en expert" go_admin: "Passer en administrateur" + go_admins_group_manager: "Passer en gestionnaire" profile: "Voir mon profil" logout: "Se déconnecter" my_account: "Mon compte" connected_as: "connecté en tant qu’%{profile}" instructeur: instructeur administrateur: administrateur + admins_group_manager: gestionnaire expert: expert user: usager guest: invité diff --git a/config/locales/views/manager/fr.yml b/config/locales/views/manager/fr.yml new file mode 100644 index 000000000..2da3bf467 --- /dev/null +++ b/config/locales/views/manager/fr.yml @@ -0,0 +1,7 @@ +fr: + manager: + admins_groups: + add_admins_group_manager: + wrong_address: + one: "%{emails} n’est pas une adresse email valide" + other: "%{emails} ne sont pas des adresses emails valides" diff --git a/config/routes.rb b/config/routes.rb index c8c10cae4..9d7dc0dc9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,6 +56,14 @@ Rails.application.routes.draw do delete 'delete', on: :member end + if ENV.fetch('ADMINS_GROUP_ENABLED') == 'enabled' + resources :admins_group_managers, path: 'gestionnaires', only: [:index, :show, :edit, :update] + + resources :admins_groups, path: 'groupe_administrateurs', only: [:index, :show, :new, :create, :edit, :update] do + post 'add_admins_group_manager', on: :member + end + end + resources :dossiers, only: [:show] resources :bill_signatures, only: [:index] @@ -465,6 +473,17 @@ Rails.application.routes.draw do end end + if ENV.fetch('ADMINS_GROUP_ENABLED') == 'enabled' + + # + # Admins Group Manager (gestionnaire) + # + + scope module: 'admins_group_managers', path: 'gestionnaire', as: 'admins_group_manager' do + resources :admins_groups, path: 'groupe_administrateurs', only: [:index, :create] + end + end + # # Administrateur # diff --git a/db/migrate/20230813091837_create_admins_group_managers.rb b/db/migrate/20230813091837_create_admins_group_managers.rb new file mode 100644 index 000000000..b8cb3780c --- /dev/null +++ b/db/migrate/20230813091837_create_admins_group_managers.rb @@ -0,0 +1,9 @@ +class CreateAdminsGroupManagers < ActiveRecord::Migration[7.0] + def change + create_table "admins_group_managers" do |t| + t.bigint :user_id, null: false + t.index [:user_id], name: :index_admins_group_managers_on_user_id + t.timestamps + end + end +end diff --git a/db/migrate/20230813091838_create_admins_groups.rb b/db/migrate/20230813091838_create_admins_groups.rb new file mode 100644 index 000000000..75a38013e --- /dev/null +++ b/db/migrate/20230813091838_create_admins_groups.rb @@ -0,0 +1,15 @@ +class CreateAdminsGroups < ActiveRecord::Migration[7.0] + def change + create_table "admins_groups" do |t| + t.string :name, null: false + t.references :admins_group + t.index [:name], name: :index_admins_groups_on_name + t.timestamps + end + + create_join_table :admins_groups, :admins_group_managers do |t| + t.index [:admins_group_id, :admins_group_manager_id], name: :index_on_admins_group_and_admins_group_manager + t.index [:admins_group_manager_id, :admins_group_id], name: :index_on_admins_group_manager_and_admins_group + end + end +end diff --git a/db/migrate/20230813091846_add_admins_group_to_administrateurs.rb b/db/migrate/20230813091846_add_admins_group_to_administrateurs.rb new file mode 100644 index 000000000..52c07d340 --- /dev/null +++ b/db/migrate/20230813091846_add_admins_group_to_administrateurs.rb @@ -0,0 +1,7 @@ +class AddAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + add_reference :administrateurs, :admins_group, index: { algorithm: :concurrently }, null: true + end +end diff --git a/db/migrate/20230813091847_add_foreign_key_admins_group_to_administrateurs.rb b/db/migrate/20230813091847_add_foreign_key_admins_group_to_administrateurs.rb new file mode 100644 index 000000000..e155c3a13 --- /dev/null +++ b/db/migrate/20230813091847_add_foreign_key_admins_group_to_administrateurs.rb @@ -0,0 +1,5 @@ +class AddForeignKeyAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0] + def change + add_foreign_key :administrateurs, :admins_groups, validate: false + end +end diff --git a/db/migrate/20230813091848_validate_foreign_key_admins_group_to_administrateurs.rb b/db/migrate/20230813091848_validate_foreign_key_admins_group_to_administrateurs.rb new file mode 100644 index 000000000..6af848b01 --- /dev/null +++ b/db/migrate/20230813091848_validate_foreign_key_admins_group_to_administrateurs.rb @@ -0,0 +1,5 @@ +class ValidateForeignKeyAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0] + def change + validate_foreign_key :administrateurs, :admins_groups + end +end diff --git a/db/schema.rb b/db/schema.rb index 18ae407b2..d06624b51 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,9 +61,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_28_083809) do end create_table "administrateurs", id: :serial, force: :cascade do |t| + t.bigint "admins_group_id" t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 t.bigint "user_id", null: false + t.index ["admins_group_id"], name: "index_administrateurs_on_admins_group_id" t.index ["user_id"], name: "index_administrateurs_on_user_id" end @@ -88,6 +90,29 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_28_083809) do t.index ["procedure_id"], name: "index_administrateurs_procedures_on_procedure_id" end + create_table "admins_group_managers", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "user_id", null: false + t.index ["user_id"], name: "index_admins_group_managers_on_user_id" + end + + create_table "admins_group_managers_groups", id: false, force: :cascade do |t| + t.bigint "admins_group_id", null: false + t.bigint "admins_group_manager_id", null: false + t.index ["admins_group_id", "admins_group_manager_id"], name: "index_on_admins_group_and_admins_group_manager" + t.index ["admins_group_manager_id", "admins_group_id"], name: "index_on_admins_group_manager_and_admins_group" + end + + create_table "admins_groups", force: :cascade do |t| + t.bigint "admins_group_id" + t.datetime "created_at", null: false + t.string "name", null: false + t.datetime "updated_at", null: false + t.index ["admins_group_id"], name: "index_admins_groups_on_admins_group_id" + t.index ["name"], name: "index_admins_groups_on_name" + end + create_table "api_tokens", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.bigint "administrateur_id", null: false t.bigint "allowed_procedure_ids", array: true @@ -1034,6 +1059,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_28_083809) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "administrateurs", "admins_groups" add_foreign_key "administrateurs", "users" add_foreign_key "administrateurs_instructeurs", "administrateurs" add_foreign_key "administrateurs_instructeurs", "instructeurs" diff --git a/spec/controllers/admins_group_managers/admins_group_manager_controller_spec.rb b/spec/controllers/admins_group_managers/admins_group_manager_controller_spec.rb new file mode 100644 index 000000000..721188fc1 --- /dev/null +++ b/spec/controllers/admins_group_managers/admins_group_manager_controller_spec.rb @@ -0,0 +1,12 @@ +describe AdminsGroupManagers::AdminsGroupManagerController, type: :controller do + describe 'before actions: authenticate_admins_group_manager!' do + it 'is present' do + before_actions = AdminsGroupManagers::AdminsGroupManagerController + ._process_action_callbacks + .filter { |process_action_callbacks| process_action_callbacks.kind == :before } + .map(&:filter) + + expect(before_actions).to include(:authenticate_admins_group_manager!) + end + end +end diff --git a/spec/controllers/admins_group_managers/admins_groups_controller_spec.rb b/spec/controllers/admins_group_managers/admins_groups_controller_spec.rb new file mode 100644 index 000000000..0feaecf17 --- /dev/null +++ b/spec/controllers/admins_group_managers/admins_groups_controller_spec.rb @@ -0,0 +1,23 @@ +describe AdminsGroupManagers::AdminsGroupsController, type: :controller do + let(:admins_group_manager) { create(:admins_group_manager) } + + describe "#index" do + subject { get :index } + + context "when not logged" do + before { subject } + it { expect(response).to redirect_to(new_user_session_path) } + end + + context "when logged in" do + let!(:admins_group) { create(:admins_group, admins_group_managers: [admins_group_manager]) } + before do + sign_in(admins_group_manager.user) + subject + end + + it { expect(response).to have_http_status(:ok) } + it { expect(assigns(:admins_groups)).to include(admins_group) } + end + end +end diff --git a/spec/controllers/manager/admins_group_managers_controller_spec.rb b/spec/controllers/manager/admins_group_managers_controller_spec.rb new file mode 100644 index 000000000..a732bdee7 --- /dev/null +++ b/spec/controllers/manager/admins_group_managers_controller_spec.rb @@ -0,0 +1,25 @@ +describe Manager::AdminsGroupManagersController, type: :controller do + let(:super_admin) { create(:super_admin) } + let(:admins_group_manager) { create(:admins_group_manager) } + + before { sign_in super_admin } + + describe '#index' do + render_views + + it 'searches admin by email' do + get :index, params: { search: admins_group_manager.email } + expect(response).to have_http_status(:success) + end + end + + describe '#show' do + render_views + + before do + get :show, params: { id: admins_group_manager.id } + end + + it { expect(response.body).to include(admins_group_manager.email) } + end +end diff --git a/spec/controllers/manager/admins_groups_controller_spec.rb b/spec/controllers/manager/admins_groups_controller_spec.rb new file mode 100644 index 000000000..d556ec317 --- /dev/null +++ b/spec/controllers/manager/admins_groups_controller_spec.rb @@ -0,0 +1,27 @@ +describe Manager::AdminsGroupsController, type: :controller do + let(:super_admin) { create(:super_admin) } + let(:admins_group) { create(:admins_group) } + + before { sign_in super_admin } + + describe '#index' do + render_views + + before do + admins_group + get :index + end + + it { expect(response.body).to include(admins_group.name) } + end + + describe '#show' do + render_views + + before do + get :show, params: { id: admins_group.id } + end + + it { expect(response.body).to include(admins_group.name) } + end +end diff --git a/spec/factories/admins_group.rb b/spec/factories/admins_group.rb new file mode 100644 index 000000000..b85b14cd4 --- /dev/null +++ b/spec/factories/admins_group.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :admins_group do + sequence(:name) { |n| "Group #{n}" } + end +end diff --git a/spec/factories/admins_group_manager.rb b/spec/factories/admins_group_manager.rb new file mode 100644 index 000000000..b8bf34a83 --- /dev/null +++ b/spec/factories/admins_group_manager.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + sequence(:admins_group_manager_email) { |n| "admins_group_manager#{n}@demarches-simplifiees.fr" } + + factory :admins_group_manager do + user { association :user, email: email, password: password } + + transient do + email { generate(:admins_group_manager_email) } + password { 'somethingverycomplated!' } + end + end +end diff --git a/spec/mailers/admins_group_mailer_spec.rb b/spec/mailers/admins_group_mailer_spec.rb new file mode 100644 index 000000000..dfb507417 --- /dev/null +++ b/spec/mailers/admins_group_mailer_spec.rb @@ -0,0 +1,16 @@ +RSpec.describe AdminsGroupMailer, type: :mailer do + describe '#notify_added_admins_group_managers' do + let(:admins_group) { create(:admins_group) } + + let(:admins_group_managers_to_add) { [create(:admins_group_manager, email: 'int3@g'), create(:admins_group_manager, email: 'int4@g')] } + + let(:current_super_admin_email) { 'toto@email.com' } + + subject { described_class.notify_added_admins_group_managers(admins_group, admins_group_managers_to_add, current_super_admin_email) } + + before { admins_group_managers_to_add.each { admins_group.add(_1) } } + + it { expect(subject.body).to include('Vous venez d’être nommé gestionnaire du groupe') } + it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) } + end +end diff --git a/spec/mailers/previews/admins_group_mailer_preview.rb b/spec/mailers/previews/admins_group_mailer_preview.rb new file mode 100644 index 000000000..0c92760ef --- /dev/null +++ b/spec/mailers/previews/admins_group_mailer_preview.rb @@ -0,0 +1,14 @@ +class AdminsGroupMailerPreview < ActionMailer::Preview + def notify_added_admins_group_managers + admins_group = AdminsGroup.new(name: 'un groupe d\'admin') + current_super_admin_email = 'admin@dgfip.com' + admins_group_managers = [AdminsGroupManager.new(user: user)] + AdminsGroupMailer.notify_added_admins_group_managers(admins_group, admins_group_managers, current_super_admin_email) + end + + private + + def user + User.new(id: 10, email: 'test@exemple.fr') + end +end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 2a02dad4f..2601027d8 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -24,6 +24,11 @@ class UserMailerPreview < ActionMailer::Preview UserMailer.invite_instructeur(user, 'aedfa0d0') end + def invite_admins_group_manager + admins_group = AdminsGroup.new(name: 'Root admins group') + UserMailer.invite_admins_group_manager(user, 'aedfa0d0', admins_group) + end + private def user diff --git a/spec/models/administrateur_spec.rb b/spec/models/administrateur_spec.rb index 065fda8ca..e658359e7 100644 --- a/spec/models/administrateur_spec.rb +++ b/spec/models/administrateur_spec.rb @@ -3,6 +3,7 @@ describe Administrateur, type: :model do describe 'associations' do it { is_expected.to have_and_belong_to_many(:instructeurs) } + it { is_expected.to belong_to(:admins_group).optional } end describe "#can_be_deleted?" do diff --git a/spec/models/admins_group_manager_spec.rb b/spec/models/admins_group_manager_spec.rb new file mode 100644 index 000000000..61ca156d0 --- /dev/null +++ b/spec/models/admins_group_manager_spec.rb @@ -0,0 +1,5 @@ +describe AdminsGroupManager, type: :model do + describe 'associations' do + it { is_expected.to have_and_belong_to_many(:admins_groups) } + end +end diff --git a/spec/models/admins_group_spec.rb b/spec/models/admins_group_spec.rb new file mode 100644 index 000000000..30d227a4a --- /dev/null +++ b/spec/models/admins_group_spec.rb @@ -0,0 +1,8 @@ +describe AdminsGroup, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:admins_group).optional } + it { is_expected.to have_many(:children) } + it { is_expected.to have_many(:administrateurs) } + it { is_expected.to have_and_belong_to_many(:admins_group_managers) } + end +end