Merge pull request #9425 from demarches-simplifiees/9356-service-gi

ETQ Usager, je veux voir dans mon dossier les informations de contact de mon groupe instructeur
This commit is contained in:
krichtof 2023-09-11 07:38:34 +00:00 committed by GitHub
commit 5aabce488b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 429 additions and 5 deletions

View file

@ -0,0 +1,60 @@
module Instructeurs
class ContactInformationsController < InstructeurController
def new
assign_procedure_and_groupe_instructeur
@contact_information = @groupe_instructeur.build_contact_information
end
def create
assign_procedure_and_groupe_instructeur
@contact_information = @groupe_instructeur.build_contact_information(contact_information_params)
if @contact_information.save
redirect_to_groupe_instructeur("Les informations de contact ont bien été ajoutées")
else
flash[:alert] = @contact_information.errors.full_messages
render :new
end
end
def edit
assign_procedure_and_groupe_instructeur
@contact_information = @groupe_instructeur.contact_information
end
def update
assign_procedure_and_groupe_instructeur
@contact_information = @groupe_instructeur.contact_information
if @contact_information.update(contact_information_params)
redirect_to_groupe_instructeur("Les informations de contact ont bien été modifiées")
else
flash[:alert] = @contact_information.errors.full_messages
render :edit
end
end
def destroy
assign_procedure_and_groupe_instructeur
@groupe_instructeur.contact_information.destroy
redirect_to_groupe_instructeur("Les informations de contact ont bien été supprimées")
end
private
def redirect_to_groupe_instructeur(notice)
if params[:from_admin] == "true"
redirect_to admin_procedure_groupe_instructeur_path(@procedure, @groupe_instructeur), notice: notice
else
redirect_to instructeur_groupe_path(@procedure, @groupe_instructeur), notice: notice
end
end
def assign_procedure_and_groupe_instructeur
@procedure = current_instructeur.procedures.find params[:procedure_id]
@groupe_instructeur = current_instructeur.groupe_instructeurs.find params[:groupe_id]
end
def contact_information_params
params.require(:contact_information).permit(:nom, :email, :telephone, :horaires, :adresse)
end
end
end

View file

@ -0,0 +1,17 @@
class ContactInformation < ApplicationRecord
belongs_to :groupe_instructeur
validates :nom, presence: { message: 'doit être renseigné' }, allow_nil: false
validates :nom, uniqueness: { scope: :groupe_instructeur, message: 'existe déjà' }
validates :email, format: { with: Devise.email_regexp, message: "n'est pas valide" }, presence: { message: 'doit être renseigné' }, allow_nil: false
validates :telephone, phone: { possible: true, allow_blank: false }
validates :horaires, presence: { message: 'doivent être renseignés' }, allow_nil: false
validates :adresse, presence: { message: 'doit être renseignée' }, allow_nil: false
validates :groupe_instructeur, presence: { message: 'doit être renseigné' }, allow_nil: false
def telephone_url
if telephone.present?
"tel:#{telephone.gsub(/[[:blank:]]/, '')}"
end
end
end

View file

@ -1304,6 +1304,10 @@ class Dossier < ApplicationRecord
)
end
def service
groupe_instructeur&.contact_information || procedure.service
end
private
def create_missing_traitemets

View file

@ -13,6 +13,7 @@ class GroupeInstructeur < ApplicationRecord
has_and_belongs_to_many :bulk_messages, dependent: :destroy
has_one :defaut_procedure, -> { with_discarded }, class_name: 'Procedure', foreign_key: :defaut_groupe_instructeur_id, dependent: :nullify, inverse_of: :defaut_groupe_instructeur
has_one :contact_information
validates :label, presence: true, allow_nil: false
validates :label, uniqueness: { scope: :procedure }

View file

@ -469,7 +469,7 @@ class Procedure < ApplicationRecord
dossier_submitted_message: []
}
}
include_list[:groupe_instructeurs] = :instructeurs if !is_different_admin
include_list[:groupe_instructeurs] = [:instructeurs, :contact_information] if !is_different_admin
procedure = self.deep_clone(include: include_list) do |original, kopy|
PiecesJustificativesService.clone_attachments(original, kopy)
end

View file

@ -0,0 +1,23 @@
.card.mt-2
.card-title Informations de contact
- service = groupe_instructeur.contact_information
- if service.nil?
= "Le groupe #{groupe_instructeur.label} n'a pas d'informations de contact. Les informations de contact affichées à l'usager seront celles du service de la procédure"
%p.mt-3
- if groupe_instructeur.instructeurs.include?(current_administrateur.user.instructeur)
= link_to "+ Ajouter des informations de contact", new_instructeur_groupe_contact_information_path(procedure_id: procedure.id, groupe_id: groupe_instructeur.id, from_admin: true), class: "fr-btn"
- else
Si vous souhaitez créer un service pour ce groupe, vous devez faire partie du groupe instructeur
- else
%p.mt-3
- if groupe_instructeur.instructeurs.include?(current_administrateur.user.instructeur)
= link_to "Modifier les informations de contact", edit_instructeur_groupe_contact_information_path(procedure_id: procedure.id, groupe_id: groupe_instructeur.id, from_admin: true), class: "fr-btn"
- else
Si vous souhaitez modifier ce service, vous devez faire partie du groupe instructeur
%p.mt-3= service.nom
= render SimpleFormatComponent.new(service.adresse, class_names_map: {paragraph: 'fr-footer__content-desc'})
= service.email
- if service.telephone.present?
%p= service.telephone
- if service.horaires.present?
%p= service.horaires

View file

@ -13,3 +13,6 @@
instructeurs: @instructeurs,
available_instructeur_emails: @available_instructeur_emails,
disabled_as_super_admin: administrateur_as_manager? }
= render partial: 'administrateurs/groupe_instructeurs/contact_information',
locals: { procedure: @procedure,
groupe_instructeur: @groupe_instructeur }

View file

@ -0,0 +1,39 @@
= form_with url: instructeur_groupe_contact_information_path, model: @contact_information, local: true do |f|
= hidden_field_tag :from_admin, params[:from_admin]
= render Dsfr::CalloutComponent.new(title: "Informations de contact") do |c|
- c.body do
Votre démarche est hébergée par #{APPLICATION_NAME} mais nous ne pouvons pas assurer le support des démarches. Et malgré la dématérialisation, les usagers se posent parfois des questions légitimes sur le processus administratif.
%br
%br
%strong Il est donc indispensable que les usagers puissent vous contacter
par le moyen de leur choix sils ont des questions sur votre démarche.
%br
%br
Ces informations de contact seront visibles par les utilisateurs de la démarche, affichées dans le menu « Aide », ainsi quen pied de page lors du dépôt dun dossier.
%br
%br
⚠️ En cas dinformations invalides, #{APPLICATION_NAME} se réserve le droit de suspendre la publication de la démarche.
= render Dsfr::InputComponent.new(form: f, attribute: :nom, input_type: :text_field) do |c|
- c.with_hint do
Indiquez le nom à utiliser pour contacter le groupe instructeur
(Exemple: Secrétariat de la Mairie)
= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field)
= render Dsfr::InputComponent.new(form: f, attribute: :telephone, input_type: :telephone_field)
= render Dsfr::InputComponent.new(form: f, attribute: :horaires, input_type: :text_area)
= render Dsfr::InputComponent.new(form: f, attribute: :adresse, input_type: :text_area)
- if procedure_id.present?
= hidden_field_tag :procedure_id, procedure_id
.sticky-action-footer
= f.submit "Enregistrer", class: "fr-btn fr-mr-2w"
= link_to "Annuler", instructeur_groupe_path(@groupe_instructeur, procedure_id: procedure_id), class: "fr-btn fr-btn--secondary"
- if [ "edit", "update"].include? params[:action]
= link_to 'Supprimer',
instructeur_groupe_contact_information_path(procedure_id: @procedure.id, groupe_id: @groupe_instructeur.id),
method: :delete,
data: { confirm: "Confirmez vous la suppression de ces informations de contact ?" },
class: 'fr-btn fr-btn--secondary'

View file

@ -0,0 +1,10 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[@procedure.libelle.truncate_words(10), instructeur_procedure_path(@procedure)],
['Groupes dinstructeurs', instructeur_groupes_path(@procedure)],
[@groupe_instructeur.label, instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id) ],
['Service']]}
.container
%h1 Modifier les informations de contact
= render partial: 'form',
locals: { contact_information: @contact_information, procedure_id: @procedure.id }

View file

@ -0,0 +1,10 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[@procedure.libelle.truncate_words(10), instructeur_procedure_path(@procedure)],
['Groupes dinstructeurs', instructeur_groupes_path(@procedure)],
[@groupe_instructeur.label, instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id) ],
['Service']]}
.container
%h1 Informations de contact
= render partial: 'form',
locals: { contact_information: @contact_information, procedure_id: @procedure.id }

View file

@ -48,3 +48,20 @@
class: 'button' }
= paginate @instructeurs, views_prefix: 'shared'
.card.mt-2
.card-title Informations de contact
- service = @groupe_instructeur.contact_information
- if service.nil?
= "Le groupe #{@groupe_instructeur.label} n'a pas d'informations de contact. Les informations de contact affichées à l'usager seront celles du service de la procédure"
%p.mt-3
= link_to "+ Ajouter des informations de contact", new_instructeur_groupe_contact_information_path(procedure_id: @procedure.id, groupe_id: @groupe_instructeur.id), class: "fr-btn"
- else
%p.mt-3
= link_to "Modifier les informations de contact", edit_instructeur_groupe_contact_information_path(procedure_id: @procedure.id, groupe_id: @groupe_instructeur.id), class: "fr-btn"
%p.mt-3= service.nom
= render SimpleFormatComponent.new(service.adresse, class_names_map: {paragraph: 'fr-footer__content-desc'})
= service.email
- if service.telephone.present?
%p= service.telephone
- if service.horaires.present?
%p= service.horaires

View file

@ -1,5 +1,5 @@
%footer.fr-footer.footer-procedure#footer{ role: "contentinfo" }
- service = procedure.service
- service = dossier&.service || procedure.service
.fr-footer__top.fr-mb-0
.fr-container
.fr-grid-row.fr-grid-row--start.fr-grid-row--gutters
@ -21,6 +21,7 @@
= I18n.t('users.procedure_footer.contact.email.link')
= link_to service.email, "mailto:#{service.email}", class: "fr-footer__top-link"
- if service.present?
- if service.telephone.present? || service.horaires.present?
%li
- horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}"

View file

@ -5,7 +5,7 @@ fr:
one: 'Service'
other: 'Services'
attributes:
service:
service: &service
adresse: 'Adresse postale'
email: 'Email de contact'
telephone: 'Téléphone'
@ -27,6 +27,7 @@ fr:
Exemple : Du lundi au vendredi de 9h30 à 17h30, le samedi de 9h30 à 12h.
adresse: |
Indiquez ladresse à laquelle un usager peut vous contacter, par exemple sil nest pas en capacité de compléter son formulaire en ligne.
contact_information: *service
errors:
models:

View file

@ -392,6 +392,7 @@ Rails.application.routes.draw do
resources :archives, only: [:index, :create]
resources :groupes, only: [:index, :show], controller: 'groupe_instructeurs' do
resource :contact_information
member do
post 'add_instructeur'
delete 'remove_instructeur'

View file

@ -0,0 +1,15 @@
class CreateContactInformations < ActiveRecord::Migration[7.0]
def change
create_table :contact_informations do |t|
t.belongs_to :groupe_instructeur, null: false, foreign_key: true
t.text :adresse, null: false
t.string :email, null: false
t.text :horaires, null: false
t.string :nom, null: false
t.string :telephone, null: false
t.timestamps
end
add_index :contact_informations, [:groupe_instructeur_id, :nom], unique: true, name: 'index_contact_informations_on_gi_and_nom'
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_08_02_161011) do
ActiveRecord::Schema[7.0].define(version: 2023_08_09_151357) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@ -612,6 +612,19 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_02_161011) do
t.index ["source"], name: "index_geo_areas_on_source"
end
create_table "contact_informations", force: :cascade do |t|
t.text "adresse", null: false
t.datetime "created_at", null: false
t.string "email", null: false
t.bigint "groupe_instructeur_id", null: false
t.text "horaires", null: false
t.string "nom", null: false
t.string "telephone", null: false
t.datetime "updated_at", null: false
t.index ["groupe_instructeur_id", "nom"], name: "index_contact_informations_on_gi_and_nom", unique: true
t.index ["groupe_instructeur_id"], name: "index_contact_informations_on_groupe_instructeur_id"
end
create_table "groupe_instructeurs", force: :cascade do |t|
t.boolean "closed", default: false
t.datetime "created_at", precision: 6, null: false
@ -1044,6 +1057,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_02_161011) do
add_foreign_key "commentaires", "dossiers"
add_foreign_key "commentaires", "experts"
add_foreign_key "commentaires", "instructeurs"
add_foreign_key "contact_informations", "groupe_instructeurs"
add_foreign_key "dossier_assignments", "dossiers"
add_foreign_key "dossier_batch_operations", "batch_operations"
add_foreign_key "dossier_batch_operations", "dossiers"

View file

@ -0,0 +1,123 @@
describe Instructeurs::ContactInformationsController, type: :controller do
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure) }
let(:assign_to) { create(:assign_to, instructeur: instructeur, groupe_instructeur: build(:groupe_instructeur, procedure: procedure)) }
let(:gi) { assign_to.groupe_instructeur }
let(:from_admin) { nil }
before do
sign_in(instructeur.user)
end
describe '#create' do
context 'when submitting a new contact_information' do
let(:params) do
{
contact_information: {
nom: 'super service',
email: 'email@toto.com',
telephone: '1234',
horaires: 'horaires',
adresse: 'adresse'
},
procedure_id: procedure.id,
groupe_id: gi.id,
from_admin: from_admin
}
end
it do
post :create, params: params
expect(flash.alert).to be_nil
expect(flash.notice).to eq('Les informations de contact ont bien été ajoutées')
expect(ContactInformation.last.nom).to eq('super service')
expect(ContactInformation.last.email).to eq('email@toto.com')
expect(ContactInformation.last.telephone).to eq('1234')
expect(ContactInformation.last.horaires).to eq('horaires')
expect(ContactInformation.last.adresse).to eq('adresse')
end
context 'from admin' do
let(:from_admin) { true }
it do
post :create, params: params
expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(gi, procedure_id: procedure.id))
end
end
end
context 'when submitting an invalid contact_information' do
before do
post :create, params: params
end
let(:params) {
{
contact_information: {
nom: 'super service'
},
procedure_id: procedure.id,
groupe_id: gi.id
}
}
it { expect(flash.alert).not_to be_nil }
it { expect(response).to render_template(:new) }
it { expect(assigns(:contact_information).nom).to eq('super service') }
end
end
describe '#update' do
let(:contact_information) { create(:contact_information, groupe_instructeur: gi) }
let(:contact_information_params) {
{
nom: 'nom'
}
}
let(:params) {
{
id: contact_information.id,
contact_information: contact_information_params,
procedure_id: procedure.id,
groupe_id: gi.id,
from_admin: from_admin
}
}
before do
patch :update, params: params
end
context 'when updating a contact_information' do
it { expect(flash.alert).to be_nil }
it { expect(flash.notice).to eq('Les informations de contact ont bien été modifiées') }
it { expect(ContactInformation.last.nom).to eq('nom') }
it { expect(response).to redirect_to(instructeur_groupe_path(gi, procedure_id: procedure.id)) }
end
context 'when updating a contact_information as an admin' do
let(:from_admin) { true }
it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(gi, procedure_id: procedure.id)) }
end
context 'when updating a contact_information with invalid data' do
let(:contact_information_params) { { nom: '' } }
it { expect(flash.alert).not_to be_nil }
it { expect(response).to render_template(:edit) }
end
end
describe '#destroy' do
let(:contact_information) { create(:contact_information, groupe_instructeur: gi) }
before do
delete :destroy, params: { id: contact_information.id, procedure_id: procedure.id, groupe_id: gi.id }
end
it { expect { contact_information.reload }.to raise_error(ActiveRecord::RecordNotFound) }
it { expect(flash.alert).to be_nil }
it { expect(flash.notice).to eq("Les informations de contact ont bien été supprimées") }
it { expect(response).to redirect_to(instructeur_groupe_path(gi, procedure_id: procedure.id)) }
end
end

View file

@ -0,0 +1,11 @@
FactoryBot.define do
factory :contact_information do
sequence(:nom) { |n| "Service #{n}" }
email { 'email@toto.com' }
telephone { '1234' }
horaires { 'de 9 h à 18 h' }
adresse { 'adresse' }
association :groupe_instructeur
end
end

View file

@ -0,0 +1,62 @@
describe ContactInformation, type: :model do
describe 'validation' do
let(:gi) { create(:groupe_instructeur) }
let(:params) do
{
nom: 'service des jardins',
email: 'super@email.com',
telephone: '012345678',
horaires: 'du lundi au vendredi',
adresse: '12 rue des schtroumpfs',
groupe_instructeur_id: gi.id
}
end
subject { ContactInformation.new(params) }
it { expect(subject).to be_valid }
it 'should forbid invalid phone numbers' do
invalid_phone_numbers = ["1", "Néant", "01 60 50 40 30 20"]
invalid_phone_numbers.each do |tel|
subject.telephone = tel
expect(subject).not_to be_valid
end
end
it 'should not accept no phone numbers' do
subject.telephone = nil
expect(subject).not_to be_valid
end
it 'should accept valid phone numbers' do
valid_phone_numbers = ["3646", "273115", "0160376983", "01 60 50 40 30 ", "+33160504030"]
valid_phone_numbers.each do |tel|
subject.telephone = tel
expect(subject).to be_valid
end
end
context 'when a contact information already exists' do
before { ContactInformation.create(params) }
context 'checks uniqueness of administrateur, name couple' do
it { expect(ContactInformation.create(params)).not_to be_valid }
end
end
context 'of nom' do
it 'should be set' do
expect(ContactInformation.new(params.except(:nom))).not_to be_valid
end
end
context 'of groupe instructeur' do
it 'should be set' do
expect(ContactInformation.new(params.except(:groupe_instructeur_id))).not_to be_valid
end
end
end
end

View file

@ -563,7 +563,7 @@ describe Procedure do
let(:logo) { Rack::Test::UploadedFile.new('spec/fixtures/files/white.png', 'image/png') }
let(:signature) { Rack::Test::UploadedFile.new('spec/fixtures/files/black.png', 'image/png') }
let(:groupe_instructeur_1) { create(:groupe_instructeur, procedure: procedure, label: "groupe_1") }
let(:groupe_instructeur_1) { create(:groupe_instructeur, procedure: procedure, label: "groupe_1", contact_information: create(:contact_information)) }
let(:instructeur_1) { create(:instructeur) }
let(:instructeur_2) { create(:instructeur) }
let!(:assign_to_1) { create(:assign_to, procedure: procedure, groupe_instructeur: groupe_instructeur_1, instructeur: instructeur_1) }
@ -594,6 +594,11 @@ describe Procedure do
expect { subject }.not_to raise_error
end
it 'should clone groupe instructeur services' do
expect(procedure.groupe_instructeurs.last.contact_information).not_to eq nil
expect(subject.groupe_instructeurs.last.contact_information).not_to eq nil
end
end
it 'should reset duree_conservation_etendue_par_ds' do
@ -707,6 +712,13 @@ describe Procedure do
expect(subject.service).to eq(nil)
end
context 'with groupe instructeur services' do
it 'should not clone groupe instructeur services' do
expect(procedure.groupe_instructeurs.last.contact_information).not_to eq nil
expect(subject.groupe_instructeurs.last.contact_information).to eq nil
end
end
it 'should discard old pj information' do
subject.draft_revision.types_de_champ_public.each do |stc|
expect(stc.old_pj).to be_nil