diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 747ec6e65..707f77cc7 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -32,7 +32,7 @@ module Administrateurs .new({ instructeurs: [current_administrateur.instructeur] }.merge(groupe_instructeur_params)) if @groupe_instructeur.save - routing_notice = " et le routage a été activé" if procedure.groupe_instructeurs.actif.size == 2 + routing_notice = " et le routage a été activé" if procedure.groupe_instructeurs.active.size == 2 redirect_to admin_procedure_groupe_instructeur_path(procedure, @groupe_instructeur), notice: "Le groupe d’instructeurs « #{@groupe_instructeur.label} » a été créé#{routing_notice}." else @@ -56,7 +56,7 @@ module Administrateurs @instructeurs = paginated_instructeurs @available_instructeur_emails = available_instructeur_emails - flash[:alert] = @groupe_instructeur.errors.values.join('
') + flash[:alert] = @groupe_instructeur.errors.full_messages.to_sentence render :show end end @@ -70,7 +70,7 @@ module Administrateurs flash[:alert] = "Suppression impossible : il doit y avoir au moins un groupe instructeur sur chaque procédure" else @groupe_instructeur.destroy! - if procedure.groupe_instructeurs.actif.one? + if procedure.groupe_instructeurs.active.one? procedure.update!(routing_enabled: false) routing_notice = " et le routage a été désactivé" end diff --git a/app/graphql/api/v2/stored_query.rb b/app/graphql/api/v2/stored_query.rb index 1a37a5537..08d334547 100644 --- a/app/graphql/api/v2/stored_query.rb +++ b/app/graphql/api/v2/stored_query.rb @@ -711,5 +711,16 @@ class API::V2::StoredQuery } } } + + mutation groupeInstructeurModifier($input: GroupeInstructeurModifierInput!) { + groupeInstructeurModifier(input: $input) { + groupeInstructeur { + id + } + errors { + message + } + } + } GRAPHQL end diff --git a/app/graphql/mutations/groupe_instructeur_modifier.rb b/app/graphql/mutations/groupe_instructeur_modifier.rb new file mode 100644 index 000000000..cd89174aa --- /dev/null +++ b/app/graphql/mutations/groupe_instructeur_modifier.rb @@ -0,0 +1,20 @@ +module Mutations + class GroupeInstructeurModifier < Mutations::BaseMutation + description "Modifier un groupe instructeur." + + argument :groupe_instructeur_id, ID, "Groupe instructeur ID", required: true, loads: Types::GroupeInstructeurType + argument :label, String, "Libellé du groupe instructeur.", required: false + argument :closed, Boolean, "L’état du groupe instructeur.", required: false + + field :groupe_instructeur, Types::GroupeInstructeurType, null: true + field :errors, [Types::ValidationErrorType], null: true + + def resolve(groupe_instructeur:, label: nil, closed: nil) + if groupe_instructeur.update({ label: label, closed: closed }.compact) + { groupe_instructeur: groupe_instructeur } + else + { errors: groupe_instructeur.errors.full_messages } + end + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 92c715abe..faff5fca6 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -572,7 +572,7 @@ type Demarche { updatedSince: ISO8601DateTime ): DossierConnection! draftRevision: Revision! - groupeInstructeurs: [GroupeInstructeur!]! + groupeInstructeurs(closed: Boolean): [GroupeInstructeur!]! id: ID! """ @@ -1523,8 +1523,16 @@ type GeoJSON { Un groupe instructeur """ type GroupeInstructeur { + """ + L’état du groupe instructeur. + """ + closed: Boolean! id: ID! instructeurs: [Profile!]! + + """ + Libellé du groupe instructeur. + """ label: String! """ @@ -1533,10 +1541,52 @@ type GroupeInstructeur { number: Int! } +""" +Autogenerated input type of GroupeInstructeurModifier +""" +input GroupeInstructeurModifierInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + L’état du groupe instructeur. + """ + closed: Boolean + + """ + Groupe instructeur ID + """ + groupeInstructeurId: ID! + + """ + Libellé du groupe instructeur. + """ + label: String +} + +""" +Autogenerated return type of GroupeInstructeurModifier. +""" +type GroupeInstructeurModifierPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + errors: [ValidationError!] + groupeInstructeur: GroupeInstructeur +} + """ Un groupe instructeur avec ses dossiers """ type GroupeInstructeurWithDossiers { + """ + L’état du groupe instructeur. + """ + closed: Boolean! + """ Liste de tous les dossiers supprimés d’un groupe instructeur. """ @@ -1638,6 +1688,10 @@ type GroupeInstructeurWithDossiers { ): DossierConnection! id: ID! instructeurs: [Profile!]! + + """ + Libellé du groupe instructeur. + """ label: String! """ @@ -1866,6 +1920,16 @@ type Mutation { """ input: DossierRepasserEnInstructionInput! ): DossierRepasserEnInstructionPayload + + """ + Modifier un groupe instructeur. + """ + groupeInstructeurModifier( + """ + Parameters for GroupeInstructeurModifier + """ + input: GroupeInstructeurModifierInput! + ): GroupeInstructeurModifierPayload } enum Order { diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 2b001ab98..63289cd84 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -29,7 +29,9 @@ module Types field :date_depublication, GraphQL::Types::ISO8601DateTime, "Date de la dépublication.", null: true, method: :unpublished_at field :date_fermeture, GraphQL::Types::ISO8601DateTime, "Date de la fermeture.", null: true, method: :closed_at - field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false + field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false do + argument :closed, Boolean, required: false + end field :service, Types::ServiceType, null: true field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d’une démarche.", null: false, extras: [:lookahead] do @@ -60,8 +62,14 @@ module Types object.aasm.current_state end - def groupe_instructeurs - Loaders::Association.for(object.class, groupe_instructeurs: { procedure: [:administrateurs] }).load(object) + def groupe_instructeurs(closed: nil) + if closed.nil? + Loaders::Association.for(object.class, groupe_instructeurs: { procedure: [:administrateurs] }).load(object) + elsif closed.true? + Loaders::Association.for(object.class, active_groupe_instructeurs: { procedure: [:administrateurs] }).load(object) + else + Loaders::Association.for(object.class, closed_groupe_instructeurs: { procedure: [:administrateurs] }).load(object) + end end def service diff --git a/app/graphql/types/groupe_instructeur_type.rb b/app/graphql/types/groupe_instructeur_type.rb index b89119f3c..4bdbe4401 100644 --- a/app/graphql/types/groupe_instructeur_type.rb +++ b/app/graphql/types/groupe_instructeur_type.rb @@ -4,8 +4,9 @@ module Types global_id_field :id field :number, Int, "Le numero du groupe instructeur.", null: false, method: :id - field :label, String, null: false + field :label, String, "Libellé du groupe instructeur.", null: false field :instructeurs, [Types::ProfileType], null: false + field :closed, Boolean, "L’état du groupe instructeur.", null: false def instructeurs Loaders::Association.for(object.class, :instructeurs).load(object) diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 91dd0c49b..f3784046f 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -18,5 +18,7 @@ module Types field :dossier_modifier_annotation_datetime, mutation: Mutations::DossierModifierAnnotationDatetime field :dossier_modifier_annotation_integer_number, mutation: Mutations::DossierModifierAnnotationIntegerNumber field :dossier_modifier_annotation_ajouter_ligne, mutation: Mutations::DossierModifierAnnotationAjouterLigne + + field :groupe_instructeur_modifier, mutation: Mutations::GroupeInstructeurModifier end end diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index abd161d75..4e0bab9db 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -19,16 +19,17 @@ class GroupeInstructeur < ApplicationRecord has_and_belongs_to_many :exports, dependent: :destroy has_and_belongs_to_many :bulk_messages, dependent: :destroy - validates :label, presence: { message: 'doit être renseigné' }, allow_nil: false - validates :label, uniqueness: { scope: :procedure, message: 'existe déjà' } - validates :closed, acceptance: { accept: [false], message: "Modification impossible : il doit y avoir au moins un groupe instructeur actif sur chaque procédure" }, if: -> { self.procedure.groupe_instructeurs.actif.one? } + validates :label, presence: true, allow_nil: false + validates :label, uniqueness: { scope: :procedure } + validates :closed, acceptance: { accept: [false] }, if: -> { self.procedure.groupe_instructeurs.active.one? } before_validation -> { label&.strip! } after_save :toggle_routing scope :without_group, -> (group) { where.not(id: group) } scope :for_api_v2, -> { includes(procedure: [:administrateurs]) } - scope :actif, -> { where(closed: false) } + scope :active, -> { where(closed: false) } + scope :closed, -> { where(closed: true) } def add(instructeur) return if in?(instructeur.groupe_instructeurs) @@ -48,12 +49,12 @@ class GroupeInstructeur < ApplicationRecord end def can_delete? - dossiers.empty? && (procedure.groupe_instructeurs.actif.many? || (procedure.groupe_instructeurs.actif.one? && closed)) + dossiers.empty? && (procedure.groupe_instructeurs.active.many? || (procedure.groupe_instructeurs.active.one? && closed)) end private def toggle_routing - procedure.update!(routing_enabled: procedure.groupe_instructeurs.actif.many?) + procedure.update!(routing_enabled: procedure.groupe_instructeurs.active.many?) end end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index b82ff7341..cde2c03b6 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -185,6 +185,9 @@ class Procedure < ApplicationRecord has_many :groupe_instructeurs, -> { order(:label) }, inverse_of: :procedure, dependent: :destroy has_many :instructeurs, through: :groupe_instructeurs + has_many :active_groupe_instructeurs, -> { active }, class_name: 'GroupeInstructeur', inverse_of: false + has_many :closed_groupe_instructeurs, -> { closed }, class_name: 'GroupeInstructeur', inverse_of: false + # This relationship is used in following dossiers through. We can not use revisions relationship # as order scope introduces invalid sql in some combinations. has_many :unordered_revisions, class_name: 'ProcedureRevision', inverse_of: :procedure, dependent: :destroy @@ -196,7 +199,7 @@ class Procedure < ApplicationRecord has_one :refused_mail, class_name: "Mails::RefusedMail", dependent: :destroy has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy - has_one :defaut_groupe_instructeur, -> { actif.order(:label) }, class_name: 'GroupeInstructeur', inverse_of: false + has_one :defaut_groupe_instructeur, -> { active.order(:label) }, class_name: 'GroupeInstructeur', inverse_of: false has_one_attached :logo has_one_attached :notice diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index 6f1b39b86..ed1ba11c7 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -40,7 +40,7 @@ = dossier.procedure.routing_criteria_name %span.mandatory * = f.select :groupe_instructeur_id, - dossier.procedure.groupe_instructeurs.actif.map { |gi| [gi.label, gi.id] }, + dossier.procedure.groupe_instructeurs.active.map { |gi| [gi.label, gi.id] }, { include_blank: dossier.brouillon? } - dossier.champs_public.each do |champ| diff --git a/config/locales/models/groupe_instructeur/fr.yml b/config/locales/models/groupe_instructeur/fr.yml new file mode 100644 index 000000000..b48e4b4a7 --- /dev/null +++ b/config/locales/models/groupe_instructeur/fr.yml @@ -0,0 +1,18 @@ +fr: + activerecord: + models: + procedure: + one: Groupe instructeur + other: Groupes instructeur + attributes: + groupe_instructeur: + label: Libellé + errors: + models: + groupe_instructeur: + attributes: + label: + format: "Le libellé %{message}" + closed: + format: "%{message}" + accepted: Il doit y avoir au moins un groupe instructeur actif sur chaque démarche diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 3b3c36c6c..13905cbe6 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -206,7 +206,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do it { expect(response).to render_template(:show) } it { expect(gi_1_1.label).not_to eq(new_name) } it { expect(gi_1_1.closed).to eq(false) } - it { expect(flash.alert).to be_present } + it { expect(flash.alert).to eq('Il doit y avoir au moins un groupe instructeur actif sur chaque démarche') } end context 'when the name is already taken' do @@ -214,7 +214,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do let(:new_name) { gi_1_2.label } it { expect(gi_1_1.label).not_to eq(new_name) } - it { expect(flash.alert).to be_present } + it { expect(flash.alert).to eq('Le libellé est déjà utilisé(e)') } end end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 5631679f5..38c85b902 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -1003,6 +1003,43 @@ describe API::V2::GraphqlController do expect(gql_data[:dossierClasserSansSuite][:dossier][:state]).to eq('sans_suite') } end + + context 'groupeInstructeurModifier' do + let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } + let(:variables) { { input: { groupeInstructeurId: dossier.groupe_instructeur.to_typed_id, label: 'nouveau groupe instructeur' } } } + let(:operation_name) { 'groupeInstructeurModifier' } + + it { + expect(gql_errors).to be_nil + expect(gql_data[:groupeInstructeurModifier][:errors]).to be_nil + expect(gql_data[:groupeInstructeurModifier][:groupeInstructeur][:id]).to eq(dossier.groupe_instructeur.to_typed_id) + expect(dossier.groupe_instructeur.reload.label).to eq('nouveau groupe instructeur') + } + + context 'close groupe instructeur' do + let(:variables) { { input: { groupeInstructeurId: dossier.groupe_instructeur.to_typed_id, closed: true } } } + + context 'with multiple groupes' do + before do + create(:groupe_instructeur, procedure: procedure) + end + + it { + expect(gql_errors).to be_nil + expect(gql_data[:groupeInstructeurModifier][:errors]).to be_nil + expect(gql_data[:groupeInstructeurModifier][:groupeInstructeur][:id]).to eq(dossier.groupe_instructeur.to_typed_id) + expect(dossier.groupe_instructeur.reload.closed).to be_truthy + } + end + + context 'validation error' do + it { + expect(gql_errors).to be_nil + expect(gql_data[:groupeInstructeurModifier][:errors].first[:message]).to eq('Il doit y avoir au moins un groupe instructeur actif sur chaque démarche') + } + end + end + end end end