From 387c89f23c33db748a6234270921af555a429b32 Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Tue, 5 Sep 2023 17:14:18 +0200 Subject: [PATCH 01/24] move personnalize button inside the table --- app/assets/stylesheets/dossiers_table.scss | 5 --- .../notified_toggle_component.html.haml | 2 +- .../instructeurs/procedures/show.html.haml | 39 ++++++++++--------- app/views/recherche/index.html.haml | 8 ++-- config/locales/en.yml | 2 +- config/locales/fr.yml | 2 +- 6 files changed, 27 insertions(+), 31 deletions(-) diff --git a/app/assets/stylesheets/dossiers_table.scss b/app/assets/stylesheets/dossiers_table.scss index 4f903da50..1ea907f69 100644 --- a/app/assets/stylesheets/dossiers_table.scss +++ b/app/assets/stylesheets/dossiers_table.scss @@ -62,11 +62,6 @@ width: 110px; } - .action-col { - text-align: right; - padding-left: $default-spacer; - padding-right: $default-spacer; - } .follow-col { width: 450px; diff --git a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml index 53da63089..c1edbf03a 100644 --- a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml +++ b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml @@ -1,5 +1,5 @@ = form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, table: 'notifications', column: 'notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do .fr-toggle = check_box_tag :order, opposite_order, active?, class: 'fr-toggle__input' - = label_tag :order, t('.show_notified_first'), class: 'fr-toggle__label fr-pl-1w' + = label_tag :order, t('.show_notified_first'), class: 'fr-toggle__label' = submit_tag t('.show_notified_first'), data: {"checkbox-target": 'submit' }, class: 'visually-hidden' diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 6c39ff86a..5c336acf7 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -65,23 +65,7 @@ = render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) .fr-ml-auto - = render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', 'fr-btn--secondary'] }, menu_options: { id: 'custom-menu' }) do |menu| - - menu.with_button_inner_html do - = t('views.instructeurs.dossiers.personalize') - - menu.with_form do - = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do - = hidden_field_tag :values, nil - = react_component("ComboMultiple", - options: @displayable_fields_for_select, - selected: @displayable_fields_selected, - disabled: [], - label: 'Colonne à afficher', - group: '.columns-form', - name: 'values') - = submit_tag t('views.instructeurs.dossiers.save'), class: 'fr-btn fr-btn--secondary' - - .fr-ml-2w - if @statut == 'archives' = link_to deleted_dossiers_instructeur_procedure_path(@procedure), class: "fr-link fr-icon-delete-line fr-link--icon-left fr-mr-2w" do = t('views.instructeurs.dossiers.show_deleted_dossiers') @@ -122,10 +106,26 @@ - @procedure_presentation.displayed_fields_for_headers.each do |field| = render partial: "header_field", locals: { field: field, classname: field['classname'] } - %th.action-col.follow-col + %th.follow-col Actions - %tr + %th.text-right + = render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', 'fr-btn--tertiary-no-outline', 'fr-btn--icon-right', 'fr-icon-settings-5-line'] }, menu_options: { id: 'custom-menu' }) do |menu| + - menu.with_button_inner_html do + = t('views.instructeurs.dossiers.personalize') + - menu.with_form do + = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do + = hidden_field_tag :values, nil + = react_component("ComboMultiple", + options: @displayable_fields_for_select, + selected: @displayable_fields_selected, + disabled: [], + label: 'Colonne à afficher', + group: '.columns-form', + name: 'values') + + = submit_tag t('views.instructeurs.dossiers.save'), class: 'fr-btn fr-btn--secondary' + %tbody = render Dossiers::BatchSelectMoreComponent.new(dossiers_count: @dossiers_count, filtered_sorted_ids: @filtered_sorted_ids) @@ -179,7 +179,7 @@ %span.cell-link = link_to_if p.hidden_by_administration_at.blank?, render(Instructeurs::SVASVRDecisionBadgeComponent.new(projection_or_dossier: p, procedure: @procedure)), path - %td.action-col.follow-col + %td.follow-col{colspan:'2'} %ul.inline.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right = render partial: 'instructeurs/procedures/dossier_actions', locals: { procedure_id: @procedure.id, dossier_id: p.dossier_id, @@ -191,6 +191,7 @@ sva_svr: @procedure.sva_svr_enabled?, turbo: false, with_menu: false } + %tfoot %tr %td.force-table-100{ colspan: @procedure_presentation.displayed_fields_for_headers.size + 2 } diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index ff17ddc37..3e652405d 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -21,7 +21,7 @@ %th Démarche %th Demandeur %th.status-col Statut - %th.action-col.follow-col + %th.follow-col %tbody - @projected_dossiers.each do |p| - procedure_libelle, user_email, procedure_id = p.columns @@ -67,7 +67,7 @@ - if instructeur_dossier && expert_dossier - %td.action-col.follow-col + %td.follow-col = render Dropdown::MenuComponent.new(wrapper: :div, button_options: {class: ['fr-btn--sm']}) do |menu| - menu.with_button_inner_html do Actions @@ -86,12 +86,12 @@ - elsif instructeur_dossier - if hidden_by_administration - %td.action-col.follow-col + %td.follow-col = link_to restore_instructeur_dossier_path(procedure_id, p.dossier_id), method: :patch, class: "button primary" do = t('views.instructeurs.dossiers.restore') - else - %td.action-col.follow-col + %td.follow-col %ul.inline.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right = render partial: "instructeurs/procedures/dossier_actions", locals: { procedure_id: procedure_id, diff --git a/config/locales/en.yml b/config/locales/en.yml index 1e2c09f69..e206efedc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -380,7 +380,7 @@ en: batch_operation: enabled: "Add file %{dossier_id} to the selection for the bulk operation" disabled: "Impossible to add file %{dossier_id} to the selection because it is already in a bulk operation" - personalize: Personalize the table + personalize: Personalize show_deleted_dossiers: Show deleted files follow_file: Follow-up the file save: Save diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 17f86bfc0..1b64c62dc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -383,7 +383,7 @@ fr: enabled: "Ajouter le dossier %{dossier_id} à la sélection pour un traitement de masse" disabled: "Impossible d'ajouter le dossier %{dossier_id} à la selection car il est déjà dans un traitement de masse" show_deleted_dossiers: Afficher les dossiers supprimés - personalize: Personnaliser le tableau + personalize: Personnaliser download: Télécharger un dossier follow_file: Suivre le dossier stop_follow: Ne plus suivre From 5fe532012753a92bf638b130ecf21762dbb573fd Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Tue, 5 Sep 2023 17:49:55 +0200 Subject: [PATCH 02/24] style checkbox for notification as regular checkbox instead of switch button --- app/components/dossiers/notified_toggle_component.rb | 4 ---- .../notified_toggle_component.html.haml | 9 +++++---- .../procedures/_dossiers_filter_tags.html.haml | 2 +- app/views/instructeurs/procedures/show.html.haml | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/components/dossiers/notified_toggle_component.rb b/app/components/dossiers/notified_toggle_component.rb index 053994e70..6b2f14473 100644 --- a/app/components/dossiers/notified_toggle_component.rb +++ b/app/components/dossiers/notified_toggle_component.rb @@ -15,10 +15,6 @@ class Dossiers::NotifiedToggleComponent < ApplicationComponent sorted_by_notifications? && order_desc? end - def icon_class_name - active? ? 'fr-fi-checkbox' : 'fr-fi-checkbox-blank' - end - def order_desc? current_order == 'desc' end diff --git a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml index c1edbf03a..4f6eca594 100644 --- a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml +++ b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml @@ -1,5 +1,6 @@ = form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, table: 'notifications', column: 'notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do - .fr-toggle - = check_box_tag :order, opposite_order, active?, class: 'fr-toggle__input' - = label_tag :order, t('.show_notified_first'), class: 'fr-toggle__label' - = submit_tag t('.show_notified_first'), data: {"checkbox-target": 'submit' }, class: 'visually-hidden' + .fr-fieldset__element.fr-m-0 + .fr-checkbox-group.fr-checkbox-group--sm + = check_box_tag :order, opposite_order, active? + = label_tag :order, t('.show_notified_first'), class: 'fr-label' + = submit_tag t('.show_notified_first'), data: {"checkbox-target": 'submit' }, class: 'visually-hidden' diff --git a/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml index 534f8a66d..f3db643a3 100644 --- a/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml +++ b/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml @@ -7,5 +7,5 @@ - if i > 0 = " ou " = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }), - class: "fr-tag fr-tag--dismiss fr-mb-1w", aria: { label: "Retirer le filtre #{filter['column']}" } do + class: "fr-tag fr-tag--dismiss fr-my-1w", aria: { label: "Retirer le filtre #{filter['column']}" } do = "#{filter['label'].truncate(50)} : #{procedure_presentation.human_value_for_filter(filter)}" diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 5c336acf7..bf73cb667 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -179,7 +179,7 @@ %span.cell-link = link_to_if p.hidden_by_administration_at.blank?, render(Instructeurs::SVASVRDecisionBadgeComponent.new(projection_or_dossier: p, procedure: @procedure)), path - %td.follow-col{colspan:'2'} + %td.follow-col{ colspan:'2' } %ul.inline.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right = render partial: 'instructeurs/procedures/dossier_actions', locals: { procedure_id: @procedure.id, dossier_id: p.dossier_id, From 9cb94f92a6e47cea8ad595bb04450d384fed0497 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 7 Sep 2023 16:53:37 +0200 Subject: [PATCH 03/24] fix(archive): do not retry 25 times if the related objected does not exist no more --- app/jobs/archive_creation_job.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/jobs/archive_creation_job.rb b/app/jobs/archive_creation_job.rb index 661996ffb..7a7abbfa2 100644 --- a/app/jobs/archive_creation_job.rb +++ b/app/jobs/archive_creation_job.rb @@ -1,4 +1,6 @@ class ArchiveCreationJob < ApplicationJob + discard_on ActiveRecord::RecordNotFound + queue_as :archives def max_run_time From 46dec40543232941f62968338e2a1899be4257c6 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Fri, 11 Aug 2023 10:49:16 +0200 Subject: [PATCH 04/24] add contact information model --- app/models/contact_information.rb | 11 ++++ app/models/groupe_instructeur.rb | 1 + ...30809151357_create_contact_informations.rb | 15 +++++ db/schema.rb | 16 ++++- spec/models/contact_information_spec.rb | 62 +++++++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 app/models/contact_information.rb create mode 100644 db/migrate/20230809151357_create_contact_informations.rb create mode 100644 spec/models/contact_information_spec.rb diff --git a/app/models/contact_information.rb b/app/models/contact_information.rb new file mode 100644 index 000000000..9bf501c77 --- /dev/null +++ b/app/models/contact_information.rb @@ -0,0 +1,11 @@ +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 +end diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index 87224cf7b..f9c349e37 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -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 } diff --git a/db/migrate/20230809151357_create_contact_informations.rb b/db/migrate/20230809151357_create_contact_informations.rb new file mode 100644 index 000000000..fcd4aa13e --- /dev/null +++ b/db/migrate/20230809151357_create_contact_informations.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 8837e43d6..0badfef05 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/spec/models/contact_information_spec.rb b/spec/models/contact_information_spec.rb new file mode 100644 index 000000000..37e2278ff --- /dev/null +++ b/spec/models/contact_information_spec.rb @@ -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 From ba0d3fa678fc02e8f45251ac8524fc466cf30ca6 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Sat, 12 Aug 2023 11:13:08 +0200 Subject: [PATCH 05/24] instructeur can create contact information for groupe instructeur --- .../contact_informations_controller.rb | 30 ++++++++++ .../contact_informations/_form.html.haml | 32 ++++++++++ .../contact_informations/new.html.haml | 10 ++++ .../groupe_instructeurs/show.html.haml | 6 ++ config/locales/models/service/fr.yml | 3 +- config/routes.rb | 1 + .../contact_informations_controller_spec.rb | 59 +++++++++++++++++++ 7 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 app/controllers/instructeurs/contact_informations_controller.rb create mode 100644 app/views/instructeurs/contact_informations/_form.html.haml create mode 100644 app/views/instructeurs/contact_informations/new.html.haml create mode 100644 spec/controllers/instructeurs/contact_informations_controller_spec.rb diff --git a/app/controllers/instructeurs/contact_informations_controller.rb b/app/controllers/instructeurs/contact_informations_controller.rb new file mode 100644 index 000000000..797627894 --- /dev/null +++ b/app/controllers/instructeurs/contact_informations_controller.rb @@ -0,0 +1,30 @@ +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 instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été ajoutées" + else + flash[:alert] = @contact_information.errors.full_messages + render :new + end + end + + private + + 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 diff --git a/app/views/instructeurs/contact_informations/_form.html.haml b/app/views/instructeurs/contact_informations/_form.html.haml new file mode 100644 index 000000000..47395ea55 --- /dev/null +++ b/app/views/instructeurs/contact_informations/_form.html.haml @@ -0,0 +1,32 @@ += form_with url: instructeur_groupe_contact_information_path, model: @contact_information, local: true do |f| + + = 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 s’ils 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 qu’en pied de page lors du dépôt d’un dossier. + %br + %br + ⚠️ En cas d’informations 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" diff --git a/app/views/instructeurs/contact_informations/new.html.haml b/app/views/instructeurs/contact_informations/new.html.haml new file mode 100644 index 000000000..18450ac3e --- /dev/null +++ b/app/views/instructeurs/contact_informations/new.html.haml @@ -0,0 +1,10 @@ += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [[@procedure.libelle.truncate_words(10), instructeur_procedure_path(@procedure)], + ['Groupes d’instructeurs', 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 } diff --git a/app/views/instructeurs/groupe_instructeurs/show.html.haml b/app/views/instructeurs/groupe_instructeurs/show.html.haml index ef16232c2..dd6ec4118 100644 --- a/app/views/instructeurs/groupe_instructeurs/show.html.haml +++ b/app/views/instructeurs/groupe_instructeurs/show.html.haml @@ -48,3 +48,9 @@ class: 'button' } = paginate @instructeurs, views_prefix: 'shared' + .card.mt-2 + .card-title Informations de contact + - if @groupe_instructeur.contact_information.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" diff --git a/config/locales/models/service/fr.yml b/config/locales/models/service/fr.yml index 40b6a2e0b..1abe6afdb 100644 --- a/config/locales/models/service/fr.yml +++ b/config/locales/models/service/fr.yml @@ -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 l’adresse à laquelle un usager peut vous contacter, par exemple s’il n’est pas en capacité de compléter son formulaire en ligne. + contact_information: *service errors: models: diff --git a/config/routes.rb b/config/routes.rb index eb74b18a6..8857a0890 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -394,6 +394,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' diff --git a/spec/controllers/instructeurs/contact_informations_controller_spec.rb b/spec/controllers/instructeurs/contact_informations_controller_spec.rb new file mode 100644 index 000000000..986bcc046 --- /dev/null +++ b/spec/controllers/instructeurs/contact_informations_controller_spec.rb @@ -0,0 +1,59 @@ +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 } + + 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 + } + 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 + 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 +end From 41b2c9355b8651073ff49d462699c222096a5c29 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 14 Aug 2023 11:59:24 +0200 Subject: [PATCH 06/24] instructeur can update contact information for groupe instructeur --- .../contact_informations_controller.rb | 16 +++++++++ .../contact_informations/edit.html.haml | 10 ++++++ .../groupe_instructeurs/show.html.haml | 13 ++++++- .../contact_informations_controller_spec.rb | 35 +++++++++++++++++++ spec/factories/contact_information.rb | 11 ++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 app/views/instructeurs/contact_informations/edit.html.haml create mode 100644 spec/factories/contact_information.rb diff --git a/app/controllers/instructeurs/contact_informations_controller.rb b/app/controllers/instructeurs/contact_informations_controller.rb index 797627894..5dbe97892 100644 --- a/app/controllers/instructeurs/contact_informations_controller.rb +++ b/app/controllers/instructeurs/contact_informations_controller.rb @@ -16,6 +16,22 @@ module Instructeurs 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 instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été modifiées" + else + flash[:alert] = @contact_information.errors.full_messages + render :edit + end + end + private def assign_procedure_and_groupe_instructeur diff --git a/app/views/instructeurs/contact_informations/edit.html.haml b/app/views/instructeurs/contact_informations/edit.html.haml new file mode 100644 index 000000000..607acfa12 --- /dev/null +++ b/app/views/instructeurs/contact_informations/edit.html.haml @@ -0,0 +1,10 @@ += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [[@procedure.libelle.truncate_words(10), instructeur_procedure_path(@procedure)], + ['Groupes d’instructeurs', 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 } diff --git a/app/views/instructeurs/groupe_instructeurs/show.html.haml b/app/views/instructeurs/groupe_instructeurs/show.html.haml index dd6ec4118..1cabdabf3 100644 --- a/app/views/instructeurs/groupe_instructeurs/show.html.haml +++ b/app/views/instructeurs/groupe_instructeurs/show.html.haml @@ -50,7 +50,18 @@ = paginate @instructeurs, views_prefix: 'shared' .card.mt-2 .card-title Informations de contact - - if @groupe_instructeur.contact_information.nil? + - 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 diff --git a/spec/controllers/instructeurs/contact_informations_controller_spec.rb b/spec/controllers/instructeurs/contact_informations_controller_spec.rb index 986bcc046..14e5ec394 100644 --- a/spec/controllers/instructeurs/contact_informations_controller_spec.rb +++ b/spec/controllers/instructeurs/contact_informations_controller_spec.rb @@ -56,4 +56,39 @@ describe Instructeurs::ContactInformationsController, type: :controller do 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 + } + } + + 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 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 end diff --git a/spec/factories/contact_information.rb b/spec/factories/contact_information.rb new file mode 100644 index 000000000..7b89daca8 --- /dev/null +++ b/spec/factories/contact_information.rb @@ -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 From e9ff4292faf42e74343ab049de0f53ec44185b68 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 14 Aug 2023 11:59:53 +0200 Subject: [PATCH 07/24] instructeur can destroy groupe instructeur service --- .../instructeurs/contact_informations_controller.rb | 6 ++++++ .../contact_informations/_form.html.haml | 6 ++++++ .../contact_informations_controller_spec.rb | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/app/controllers/instructeurs/contact_informations_controller.rb b/app/controllers/instructeurs/contact_informations_controller.rb index 5dbe97892..6db8a50b4 100644 --- a/app/controllers/instructeurs/contact_informations_controller.rb +++ b/app/controllers/instructeurs/contact_informations_controller.rb @@ -32,6 +32,12 @@ module Instructeurs end end + def destroy + assign_procedure_and_groupe_instructeur + @groupe_instructeur.contact_information.destroy + redirect_to instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été supprimées" + end + private def assign_procedure_and_groupe_instructeur diff --git a/app/views/instructeurs/contact_informations/_form.html.haml b/app/views/instructeurs/contact_informations/_form.html.haml index 47395ea55..b10e9076c 100644 --- a/app/views/instructeurs/contact_informations/_form.html.haml +++ b/app/views/instructeurs/contact_informations/_form.html.haml @@ -30,3 +30,9 @@ .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' diff --git a/spec/controllers/instructeurs/contact_informations_controller_spec.rb b/spec/controllers/instructeurs/contact_informations_controller_spec.rb index 14e5ec394..9870c4b1d 100644 --- a/spec/controllers/instructeurs/contact_informations_controller_spec.rb +++ b/spec/controllers/instructeurs/contact_informations_controller_spec.rb @@ -91,4 +91,17 @@ describe Instructeurs::ContactInformationsController, type: :controller do 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 From af195d91585e0ed44dd752c3d9e1d11d5119b437 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 14 Aug 2023 17:43:35 +0200 Subject: [PATCH 08/24] display for user groupe instructeur service when available --- app/models/contact_information.rb | 6 ++++++ app/models/dossier.rb | 4 ++++ app/views/users/_procedure_footer.html.haml | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/contact_information.rb b/app/models/contact_information.rb index 9bf501c77..c716f2d40 100644 --- a/app/models/contact_information.rb +++ b/app/models/contact_information.rb @@ -8,4 +8,10 @@ class ContactInformation < ApplicationRecord 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 diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 693e4fea9..f639e347d 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1295,6 +1295,10 @@ class Dossier < ApplicationRecord ) end + def service + groupe_instructeur&.contact_information || procedure.service + end + private def create_missing_traitemets diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 0ac5c0417..96334ed39 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -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 From fa70cffa31330abc777ca3da287a38c3a69f353c Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 28 Aug 2023 17:09:42 +0200 Subject: [PATCH 09/24] display groupe instructeur service for admin --- .../_contact_information.html.haml | 23 +++++++++++++++++++ .../groupe_instructeurs/show.html.haml | 3 +++ 2 files changed, 26 insertions(+) create mode 100644 app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml diff --git a/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml b/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml new file mode 100644 index 000000000..6d3077e78 --- /dev/null +++ b/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml @@ -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), 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), 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 diff --git a/app/views/administrateurs/groupe_instructeurs/show.html.haml b/app/views/administrateurs/groupe_instructeurs/show.html.haml index 13f5d1581..d61e17433 100644 --- a/app/views/administrateurs/groupe_instructeurs/show.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/show.html.haml @@ -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 } From 587960cfd965abad5e4eda2e097b3de2269697d6 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 29 Aug 2023 14:41:28 +0200 Subject: [PATCH 10/24] clone groupe instructeur service if admin owns original procedure --- app/models/procedure.rb | 2 +- spec/models/procedure_spec.rb | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 482a0fd30..c96b0eb3b 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -467,7 +467,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 diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 9291e15cd..8d3f9c21f 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -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 From c5d02cdd6a066b2d9408fc88bea9fd4bae9eb5b4 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Mon, 4 Sep 2023 10:20:52 +0200 Subject: [PATCH 11/24] display telephone and horaires of service even if the dossier is not in brouillon state --- app/views/users/_procedure_footer.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 96334ed39..0eb2066b1 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -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)}" From 5b9fbf40edaca7cafe9d25b6a831b876d52a1f77 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Wed, 6 Sep 2023 09:02:40 +0200 Subject: [PATCH 12/24] fix: admin is redirected to admin groupe instructeur page after creating or editing groupe instructeur service --- .../contact_informations_controller.rb | 14 ++++++++++--- .../_contact_information.html.haml | 4 ++-- .../contact_informations/_form.html.haml | 1 + .../contact_informations_controller_spec.rb | 20 +++++++++++++++++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/app/controllers/instructeurs/contact_informations_controller.rb b/app/controllers/instructeurs/contact_informations_controller.rb index 6db8a50b4..7a1b4331b 100644 --- a/app/controllers/instructeurs/contact_informations_controller.rb +++ b/app/controllers/instructeurs/contact_informations_controller.rb @@ -9,7 +9,7 @@ module Instructeurs assign_procedure_and_groupe_instructeur @contact_information = @groupe_instructeur.build_contact_information(contact_information_params) if @contact_information.save - redirect_to instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été ajoutées" + redirect_to_groupe_instructeur("Les informations de contact ont bien été ajoutées") else flash[:alert] = @contact_information.errors.full_messages render :new @@ -25,7 +25,7 @@ module Instructeurs assign_procedure_and_groupe_instructeur @contact_information = @groupe_instructeur.contact_information if @contact_information.update(contact_information_params) - redirect_to instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été modifiées" + redirect_to_groupe_instructeur("Les informations de contact ont bien été modifiées") else flash[:alert] = @contact_information.errors.full_messages render :edit @@ -35,11 +35,19 @@ module Instructeurs def destroy assign_procedure_and_groupe_instructeur @groupe_instructeur.contact_information.destroy - redirect_to instructeur_groupe_path(@groupe_instructeur, procedure_id: @procedure.id), notice: "Les informations de contact ont bien été supprimées" + 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] diff --git a/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml b/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml index 6d3077e78..5fe916c04 100644 --- a/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/_contact_information.html.haml @@ -5,13 +5,13 @@ = "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), class: "fr-btn" + = 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), class: "fr-btn" + = 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 diff --git a/app/views/instructeurs/contact_informations/_form.html.haml b/app/views/instructeurs/contact_informations/_form.html.haml index b10e9076c..c08656e14 100644 --- a/app/views/instructeurs/contact_informations/_form.html.haml +++ b/app/views/instructeurs/contact_informations/_form.html.haml @@ -1,4 +1,5 @@ = 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 diff --git a/spec/controllers/instructeurs/contact_informations_controller_spec.rb b/spec/controllers/instructeurs/contact_informations_controller_spec.rb index 9870c4b1d..70cdae50c 100644 --- a/spec/controllers/instructeurs/contact_informations_controller_spec.rb +++ b/spec/controllers/instructeurs/contact_informations_controller_spec.rb @@ -3,6 +3,7 @@ describe Instructeurs::ContactInformationsController, type: :controller do 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) @@ -20,7 +21,8 @@ describe Instructeurs::ContactInformationsController, type: :controller do adresse: 'adresse' }, procedure_id: procedure.id, - groupe_id: gi.id + groupe_id: gi.id, + from_admin: from_admin } end @@ -34,6 +36,14 @@ describe Instructeurs::ContactInformationsController, type: :controller do 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 @@ -69,7 +79,8 @@ describe Instructeurs::ContactInformationsController, type: :controller do id: contact_information.id, contact_information: contact_information_params, procedure_id: procedure.id, - groupe_id: gi.id + groupe_id: gi.id, + from_admin: from_admin } } @@ -84,6 +95,11 @@ describe Instructeurs::ContactInformationsController, type: :controller do 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: '' } } From 7472b170096d5cb87e2408fceee01527bab52b81 Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Fri, 8 Sep 2023 11:52:38 +0000 Subject: [PATCH 13/24] =?UTF-8?q?Autocomplete=20pour=20instructeurs=20qui?= =?UTF-8?q?=20veulent=20affecter=20un=20expert=20au=20dossier,=20avec=20to?= =?UTF-8?q?us=20les=20experts=20sollicit=C3=A9s=20sur=20la=20d=C3=A9marche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 6 +++++- app/controllers/instructeurs/dossiers_controller.rb | 4 ++++ app/views/shared/avis/_form.html.haml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 894af0f6f..4933ea04d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -21,7 +21,7 @@ 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 + :administrateur_signed_in?, :current_administrateur, :current_account, :localization_enabled?, :set_locale, :current_expert_not_instructeur? before_action do Current.request_id = request.uuid @@ -62,6 +62,10 @@ class ApplicationController < ActionController::Base current_user&.expert end + def current_expert_not_instructeur? + current_user&.expert? && !current_user&.instructeur? + end + def expert_signed_in? current_expert.present? end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index fe19bb1e9..339b814f2 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -73,6 +73,8 @@ module Instructeurs @avis = Avis.new if @dossier.procedure.experts_require_administrateur_invitation? @experts_emails = dossier.procedure.experts_procedures.where(revoked_at: nil).map(&:expert).map(&:email).sort + else + @experts_emails = @dossier.procedure.experts.map(&:email).sort end end @@ -81,6 +83,8 @@ module Instructeurs @avis = Avis.new if @dossier.procedure.experts_require_administrateur_invitation? @experts_emails = dossier.procedure.experts_procedures.where(revoked_at: nil).map(&:expert).map(&:email).sort + else + @experts_emails = @dossier.procedure.experts.map(&:email).sort end end diff --git a/app/views/shared/avis/_form.html.haml b/app/views/shared/avis/_form.html.haml index 78d468b8e..43f3dad28 100644 --- a/app/views/shared/avis/_form.html.haml +++ b/app/views/shared/avis/_form.html.haml @@ -13,7 +13,7 @@ = hidden_field_tag 'avis[emails]', nil .fr-input-group = react_component("ComboMultiple", - options: @dossier.procedure.experts_require_administrateur_invitation ? @experts_emails : [], + options: current_expert_not_instructeur? ? [] : @experts_emails, selected: [], disabled: [], label: 'Emails', group: '.ask-avis', From 6e5c466e576a23b4beb6580a746c47a0e5d2b51e Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Mon, 11 Sep 2023 17:41:54 +0200 Subject: [PATCH 14/24] style: update favicons with chart color, apple touch icon and shortcuts names --- app/assets/images/favicons/16x16.png | Bin 466 -> 480 bytes app/assets/images/favicons/32x32.png | Bin 889 -> 878 bytes app/assets/images/favicons/96x96.png | Bin 2557 -> 2815 bytes .../images/favicons/apple-touch-icon.png | Bin 0 -> 2837 bytes app/views/graphql/playground.html.haml | 6 +++--- app/views/layouts/_favicons.html.haml | 5 +++++ app/views/layouts/application.html.haml | 6 +++--- app/views/layouts/component_preview.html.haml | 6 +++--- app/views/layouts/print.html.haml | 6 +++--- config/env.example.optional | 3 +++ config/initializers/images.rb | 9 ++++++--- 11 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 app/assets/images/favicons/apple-touch-icon.png create mode 100644 app/views/layouts/_favicons.html.haml diff --git a/app/assets/images/favicons/16x16.png b/app/assets/images/favicons/16x16.png index c73712dba80395a62963b701b7fd25623182e474..ed096c4e0c0d22ceccb340e481deb1716604179e 100644 GIT binary patch delta 454 zcmV;%0XhEC1K11ruqyx_ zUWT`Itv#w@pDOYK?nNv6Wa~xIH%r0!)E^2jd&=W29Ro|X3Qq>j2Fa6T0I{lItSzAN z8Of)JdL7_NGk>kS%l80&)Tr>J(5R29NHzcuX}>j~kQR-yY4k{tfBiV|6&`(rTPZb3c%-OJAjX=3oun+_mm}lmc!j w@T<0UM{i@AxlX4~SwYpUPup4s=6av01QbLWJv>O(^Z)<=07*qoM6N<$f)*0r5&!@I delta 440 zcmV;p0Z0De1JVPKB!8JnL_t(|0o_sq>=t1hzHD1_8soh9-euR+j@htcIcAJ2o)|*aWI|xoOyM^ciD}Kv`55cdy0?co+KXaHO_1b z8SYOpI*tz0eSZmaa8tvNdN9E=!;c%JCU73W8q!D;{~UXTBLU9wq=*Vgb8K-;h@541oA!cXf^)?iuc%x>a4nM+|sL{Bd<3Eo>u`QN0x^( zsCS}cm#tGjrHuQrlUoGs6O+B=J=|q`Tt9JSyLUKq#4-Ds{6*M_oYA ief|%{Z=YN(x7Ht2-SKvasV@=$0000FDa|d@KTF9{y zFO&F{m6(6AU4KC02Ua9XSu)Nacvfux&UZ01{TF@G6S2zQcf~p@5~a*NRWFfvoNl4v z3jD`Rb|FeJuvis6kG0p6HK;i^ww>7{uBwT+`D*Lk$+}Bcrh@708iJ>jc!jPR#d(fm zDziDKUtDoW#7$$Z*Ks9X$l8djNjwn(FOxW#tirvS$bTHp857+)MWmdy{g^-vXHm@P zvn8`B4}qVlW7~|Pjj5!8%jrxI*7F>}^tler=(QB1s0)EVsbRB>qNAy0CVRB;09%bp z64#S2(Q8qvNFnesi32h=pc}W6_?wNI3}1qeNW9L_j9zOpg-IcB6Ny{NWfb<~aT33c zitg)0+<�wRcE-#!^H^pW{e;90J=il`@tit9S^{x)oD-^!{tRIO>~-o07G!ScxEO zCqAT+(_-$u`z(q3=|+_DLkt=ASuFfUoXr zT6_a^V-uccPQ*<$edCIIevUzxRYtGLb8pX&*qz=4Il8kR4={^4TtLT`8<1yrej_o9 zZ+MhDd6%gq$~Z3KwyypusNaR}Xk<2@a}W3PF_TG*;uN}+`XE`7%lL&kq`B%T<0e)h z*HGf(8o{~zLJeuII?A|{HOZIwB7(Wi2eSzWb2K}#D20sAu_0{5ksQu8EJE@B5pI&v h9}m0(MjbHf007+FQmo=@h{ON@002ovPDHLkV1kium|y?^ delta 866 zcmV-o1D*Wt2Kfe%B!56jL_t(|0b&>hqhJ(_0sv#x0PZ*!M1R}1ZDWn}t!=fr>9cLy zOxCvd!`ilO+q+5o{ATB-cTpsL{k|&q&YUx6r1dgXSU%t+OA03{Te%AP23Mg}bE13# z|BvCM%4UxD!EAm5Oe743kQHDgH(&@~4MzS9e$U2t9{_l~(SP8aq=X@$(zZedtv3=` zz>TQ?vi??P(oGav|GfKdoSMXsvnlq4blYJ#_wEdDld#cGf&K3exY zhRN^rF1fOBzkgqv3S4UxtW;*L`;-upSYRVTE`YD^J5HB64TaY+$G0f~N<>d3jO=Bb zq&!?Uhl8o`L<6Rb=-PODKaJpf8Hu4Tq*o$svdioA3I>9Lo<%3Cn4_cqd52P2y6QN| z+EbUr5aBPhtD^;qgkE*b{c7sSAXZGHjyr*ew6mnVQH^Aieqg?=^Qo@S&mSK3+*V;b#bej($$uR}h)Ipz_ z@Qh9&)v)MO9M5T&Ac-q19bA?PLse%x^eIMzZOCBbCfVGxxew5>XfvVKG*=0r;( z&Wx)n(ek^u&Uweod1M!?1rs6az~TMh1D^jI|EJ-Z!tPuILVj?+kVLsJf?sm84VQNq zQCsz;Z5xsSv>x1S!=>2DA2oU<_mSWwx{&Bb#E6g59>+ZI!%81TH>8UZ{R(YUd@n?> s6O<6jy$k;jr^R<~41*Pnf>AIE0EA@4=7Bk50{{R307*qoM6N<$g5VIOW&i*H diff --git a/app/assets/images/favicons/96x96.png b/app/assets/images/favicons/96x96.png index a4d73f1d7fe22f6040d9dab3fcaddae339916782..fa679a50cc9ba7fc0b635ccf859e553771cbaed3 100644 GIT binary patch literal 2815 zcmYjTc{tQ-8-9&_EE!`f3_{47$(FK=F_vU0q_TyGYzZl{WjRKlAr+FXRHLjXbdboB z{U9mjWH+dcB_hkr_nY5*r|Y|}XWn_A_qp%;xu195KPJW2+Kdmm4+#L^voOb>gudMw zA1+pC9VR401y*2dWp8R0Yz06j0O@o9Ccqv5GN3&i8Iz1O4LXMb^a;TK|5fPxGozF~ z1^C8+FuW0(WMCL{2n+?J09B#%JpkhX37t$c(z{dtgF(HZE(|178K%bo@E`ab>dugw z0s;_VoM8s?nE}iGc5)l0FxN~7342Z-1t@ryOemcIys&u291vy7!liVkH;4{XG9XL8 z_skO}i|GO0LuMct3O3KM0qOi61m-dnm@VMGO#je~Bqjwep`aBaGU*Tu4*n@12sdI} z2n>Vf<(UM0%wd1yp%Qb(F(3fpVV{iOju}uo2KbmBm^#d%0D$g?foJMKCFn9CFqHla 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 diff --git a/app/assets/images/favicons/apple-touch-icon.png b/app/assets/images/favicons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dc28ab04135b974dfa6a32ad03e6944fa5326f9f GIT binary patch literal 2837 zcmV+w3+nWVP)0{{R3FC5Sl00093P)t-s|NsB{ z`~BzV^w`+$%F5`!zvH;L;Jm!z$jImJ?)U!w{{H#-`{m~O)YkaL$o9Ft`OVM!-rxQ2 z@9^;W!ouW@jm~0XyFo#%D=VWI7@G+RlmP&d1qG897Md(9q)ADzb92M3t=s12^!@hs z`qkI-nV#%pZ0J5o<|8lT5+C6K58(n3;tw6FV^UuIp7_CngRaLbb8k|~MxBTbm@|2qBLQUcd809E5_{hum_xnpru?q{95fPaE@$vMhtnYDo z+S>1IY`-5Lo`8VJ_{+}gSz_W18|ExH`uhB`vfTm#kplygLqn~)y5ao%{qm8Q>|SRG z2$UTiojW_KV`ICCiOiv)*0i+VkdV*m>Gk6jA?Q0s^|H3_eT3#NI_Ee;_r1XW{QT_f z_IG#0S68-{meJDE>!zmIQBkze(CYN4t>q>%{O#`WfQJVMl*h;B@$vYuu-y3g`~2$b z^{%q>prv15x=Kp0@X^`qTxH<|5~!%zB_*Le4SUERJNCN1{`mO&ql+-|&Tu{OIX^e#fMw*YSvsH#e!y z>CT@A>)s-rn%}&(R|zpy@+SnVHl2+}X;*34+4t%+1uFw6L}000000*d?Z1YHNMuDNZhein)1s`S|(y`2+-oxM=DqVG&U= zaS2H&X=xc*Ie7&|B}P1|l~q*L)HO7?`55Xp7cT#VfeGL+!n-t-HIsEAH;=`)u8JGygjYm6>!V;rD5E&Uda(rnkmb%rj=D zb5)@kBR$|InOOs}8`b2~pgwC(ZXOgK`N0j7gog44k_%$t7V+NQm|fwl*>mR3Yo9xR z!5setW|uU*NYr)HkYq=u;*#h-H{Mv0ds!ZD4cj+{I9A}o*aj=mY}pYN&V`{nO>oViKwm}@cD2n!W81dw_O;jPT~=Pfg%Q!Q z5*nMluB1Zw`3|@wbFMOu{+01)PSJuYrL%wjOL^?Ds9UPh45_A8sWhbyuIS+>hH=7L zsOh+-kTF*8bL(yPx^7Q|it1El%?8~JZ% zjLwEQt>8YdQRc&hP%6l-q6-EiXZCzM=IKJ_%5 zc;Fc>d-gdPQ`CjY?py^YJklQJlUl^SSCtgga9p!cjhP*^O!K5Beb43pmxn922 zhb2`?&=&Vf7_H3fVT9+aO!mRmJKKK%T6W4~89Ekv9i#{BJ_HRrhBoMn zk|~|#9EOG?#oVSZ^xAxs*#t^ZSC=Ak4Y>{u?LH7E?200&dydZXW6�J}0fmq3-%; zIf*&}Lbje{GV7_+P&Ii7w^x*z$r(<99zz4>(cPGAqIV|Lm_DQ{2J|{k z7qPY)8<@DSM$SMZtyYR_Nw?8vs9DHmMuQ!In$mEwAYViiTUIc#|18vOyjHM@E5qO;z18IbU^GF)`vWy#vDzrEzoPLe7CUfZNV-! zymm4Oxg*Di$u1s-syN#EpMhTOX32MpJy2(UQB1SsjlJM-_*F*UTMKoTAiDS%eL-50 z2X)oAixWJ2Y&8^T$5@%{Xa@=E9*f{MGoe@ft90M*seRWMTSmxO6G-d3vc=73v;+;M z^p_t)w^MZCpARjGm+Z-^sjs_ynQY+7R?r|tQ`I}5+ulNEvpf%4uDcX^b!R>A67zo2 zf=paNsKlmIpJXcP+_&Ey+xZ>~xMzZfbzO1>~HGNh#Bb)B}C6Wsj&!&2y33KnX5u6Oay%$`w>Q^Ro z52*B%&_L^z`?HylaHR-0$>StT9`ZnSqgXe8oDs^&urv?2YVE65`?I_JcEA&gL#uw< zU&s_6%o`8Dk`}@hAwxa^-D>gBO&#Vc{&wI65)~R*B<8!L4 zOyL{Q5whx-_1rEwsCGx}1`o@T2l78GN#}OOZ^pqLiuU+12cPZ(lcr6ZHIT|8v%W8a zVE*G$GPes$9}y7|F(RD0BB2*{nBX3Vw(hqwb(;dbTESs*pOi4JF1_G-xQ%r8^pi}5 z&wIy<(7@8(uZ-X-DoRrSggfQ6J$2NVHRb!ko;GNr`lm}HLYr%AaefWltyP=3T@qMB z-GB8y*b2>wyWy240~#?ib-RRXp|#iCH6QpT*qZaPCM4|-{OGLugQ{a=%2SGyZRx^> zg$#}C8}rAXxMrk7Mg1y&+Y=bLe8K1gr}LoAlvuZO-|xTwzHj7@fBb3st35cN0RR9% n7-av{_V|DS0000006^OTR{wW{`u~r200000NkvXXu0mjf@z>Tj literal 0 HcmV?d00001 diff --git a/app/views/graphql/playground.html.haml b/app/views/graphql/playground.html.haml index 1f2a5dad6..c45cd4fb1 100644 --- a/app/views/graphql/playground.html.haml +++ b/app/views/graphql/playground.html.haml @@ -4,14 +4,14 @@ %meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" } %meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" } %meta{ name: "viewport", content: "width=device-width, initial-scale=1" } + %meta{ name: "application-name", content: APPLICATION_NAME } + %meta{ name: "apple-mobile-web-app-title", content: APPLICATION_NAME } = csrf_meta_tags %title = content_for?(:title) ? "#{yield(:title)} · #{APPLICATION_NAME}" : APPLICATION_NAME - = favicon_link_tag(image_url("#{FAVICON_16PX_SRC}"), type: "image/png", sizes: "16x16") - = favicon_link_tag(image_url("#{FAVICON_32PX_SRC}"), type: "image/png", sizes: "32x32") - = favicon_link_tag(image_url("#{FAVICON_96PX_SRC}"), type: "image/png", sizes: "96x96") + = render partial: "layouts/favicons" = vite_client_tag = vite_typescript_tag 'playground' diff --git a/app/views/layouts/_favicons.html.haml b/app/views/layouts/_favicons.html.haml new file mode 100644 index 000000000..4c2ba2744 --- /dev/null +++ b/app/views/layouts/_favicons.html.haml @@ -0,0 +1,5 @@ += favicon_link_tag(image_url(FAVICONS_SRC["16px"]), type: "image/png", sizes: "16x16") if FAVICONS_SRC.key?("16px") += favicon_link_tag(image_url(FAVICONS_SRC["32px"]), type: "image/png", sizes: "32x32") if FAVICONS_SRC.key?("32px") += favicon_link_tag(image_url(FAVICONS_SRC["96px"]), type: "image/png", sizes: "96x96") if FAVICONS_SRC.key?("96px") += favicon_link_tag(image_url(FAVICONS_SRC["apple_touch"]), type: nil, sizes: "152x152", rel: "apple-touch-icon") if FAVICONS_SRC.key?("apple_touch") +%meta{ name: "theme-color", content: "#ffffff" } diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 281018841..932431329 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -4,14 +4,14 @@ %meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" } %meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" } %meta{ name: "viewport", content: "width=device-width, initial-scale=1" } + %meta{ name: "application-name", content: APPLICATION_NAME } + %meta{ name: "apple-mobile-web-app-title", content: APPLICATION_NAME } = csrf_meta_tags %title = content_for?(:title) ? "#{sanitize(yield(:title))} · #{APPLICATION_NAME}" : APPLICATION_NAME - = favicon_link_tag(image_url("#{FAVICON_16PX_SRC}"), type: "image/png", sizes: "16x16") - = favicon_link_tag(image_url("#{FAVICON_32PX_SRC}"), type: "image/png", sizes: "32x32") - = favicon_link_tag(image_url("#{FAVICON_96PX_SRC}"), type: "image/png", sizes: "96x96") + = render partial: "layouts/favicons" = Gon::Base.render_data(camel_case: true, init: true, nonce: request.content_security_policy_nonce) diff --git a/app/views/layouts/component_preview.html.haml b/app/views/layouts/component_preview.html.haml index 82fcf217c..da52c5d1f 100644 --- a/app/views/layouts/component_preview.html.haml +++ b/app/views/layouts/component_preview.html.haml @@ -4,14 +4,14 @@ %meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" } %meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" } %meta{ name: "viewport", content: "width=device-width, initial-scale=1" } + %meta{ name: "application-name", content: APPLICATION_NAME } + %meta{ name: "apple-mobile-web-app-title", content: APPLICATION_NAME } = csrf_meta_tags %title = content_for?(:title) ? "#{yield(:title)} · #{APPLICATION_NAME}" : APPLICATION_NAME - = favicon_link_tag(image_url("#{FAVICON_16PX_SRC}"), type: "image/png", sizes: "16x16") - = favicon_link_tag(image_url("#{FAVICON_32PX_SRC}"), type: "image/png", sizes: "32x32") - = favicon_link_tag(image_url("#{FAVICON_96PX_SRC}"), type: "image/png", sizes: "96x96") + = render partial: "layouts/favicons" = vite_client_tag = vite_javascript_tag 'application' diff --git a/app/views/layouts/print.html.haml b/app/views/layouts/print.html.haml index 5b3baa7b0..2de782bd2 100644 --- a/app/views/layouts/print.html.haml +++ b/app/views/layouts/print.html.haml @@ -3,14 +3,14 @@ %meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" } %meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" } %meta{ name: "viewport", content: "width=device-width, initial-scale=1" } + %meta{ name: "application-name", content: APPLICATION_NAME } + %meta{ name: "apple-mobile-web-app-title", content: APPLICATION_NAME } = csrf_meta_tags %title = t("dynamics.page_title") - = favicon_link_tag(image_url("#{FAVICON_16PX_SRC}"), type: "image/png", sizes: "16x16") - = favicon_link_tag(image_url("#{FAVICON_32PX_SRC}"), type: "image/png", sizes: "32x32") - = favicon_link_tag(image_url("#{FAVICON_96PX_SRC}"), type: "image/png", sizes: "96x96") + = render partial: "layouts/favicons" %body = yield diff --git a/config/env.example.optional b/config/env.example.optional index 8935825e9..c9d341df6 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -46,9 +46,12 @@ DS_ENV="staging" # STATUS_PAGE_URL="" # Instance customization: Favicons ---> to be put in "app/assets/images" +# Search "real favicon generator" to find websites generating all these formats from a single image source. +# An empty string disable the icon if you don't care. # FAVICON_16PX_SRC="favicons/16x16.png" # FAVICON_32PX_SRC="favicons/32x32.png" # FAVICON_96PX_SRC="favicons/96x96.png" +# FAVICON_APPLE_TOUCH_152PX_SRC="favicons/apple-touch-icon.png" # Instance customization: Application logo ---> to be put in "app/assets/images" # HEADER_LOGO_SRC="marianne.png" diff --git a/config/initializers/images.rb b/config/initializers/images.rb index 161c9fc24..9bf0804dc 100644 --- a/config/initializers/images.rb +++ b/config/initializers/images.rb @@ -1,7 +1,10 @@ # Favicons -FAVICON_16PX_SRC = ENV.fetch("FAVICON_16PX_SRC", "favicons/16x16.png") -FAVICON_32PX_SRC = ENV.fetch("FAVICON_32PX_SRC", "favicons/32x32.png") -FAVICON_96PX_SRC = ENV.fetch("FAVICON_96PX_SRC", "favicons/96x96.png") +FAVICONS_SRC = { + "16px" => ENV.fetch("FAVICON_16PX_SRC", "favicons/16x16.png"), + "32px" => ENV.fetch("FAVICON_32PX_SRC", "favicons/32x32.png"), + "96px" => ENV.fetch("FAVICON_96PX_SRC", "favicons/96x96.png"), + "apple_touch" => ENV.fetch("FAVICON_APPLE_TOUCH_152PX_SRC", "favicons/apple-touch-icon.png") +}.compact_blank.freeze # Header logo HEADER_LOGO_SRC = ENV.fetch("HEADER_LOGO_SRC", "marianne.png") From 201b31bf36a651647e9c317512da9f32e75f20a5 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 7 Sep 2023 18:10:26 +0200 Subject: [PATCH 15/24] refactor(watermark): small text based watermarked to increase document visibility --- app/assets/images/watermark.png | Bin 9935 -> 0 bytes app/jobs/titre_identite_watermark_job.rb | 65 +------------ app/services/watermark_service.rb | 118 +++++++++++++++++++++++ config/env.example.optional | 3 - config/initializers/watermark.rb | 1 - lib/tasks/pjs.rake | 28 ++++++ spec/services/watermark_service_spec.rb | 23 +++++ 7 files changed, 172 insertions(+), 66 deletions(-) delete mode 100644 app/assets/images/watermark.png create mode 100644 app/services/watermark_service.rb delete mode 100644 config/initializers/watermark.rb create mode 100644 spec/services/watermark_service_spec.rb diff --git a/app/assets/images/watermark.png b/app/assets/images/watermark.png deleted file mode 100644 index 07114bdcfe14c6897e0d4c648906a42887528909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9935 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 diff --git a/app/jobs/titre_identite_watermark_job.rb b/app/jobs/titre_identite_watermark_job.rb index 580dc204d..491530d05 100644 --- a/app/jobs/titre_identite_watermark_job.rb +++ b/app/jobs/titre_identite_watermark_job.rb @@ -10,77 +10,18 @@ class TitreIdentiteWatermarkJob < ApplicationJob # (to avoid modifying the file while it is being scanned). retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10 - MAX_IMAGE_SIZE = 1500 - SCALE = 0.9 - WATERMARK = URI.parse(WATERMARK_FILE).is_a?(URI::HTTP) ? WATERMARK_FILE : Rails.root.join("app/assets/images/#{WATERMARK_FILE}") - def perform(blob) return if blob.watermark_done? raise FileNotScannedYetError if blob.virus_scanner.pending? blob.open do |file| - watermark = resize_watermark(file) - - if watermark.present? - processed = watermark_image(file, watermark) + Tempfile.create(["watermarked", File.extname(file)]) do |output| + processed = WatermarkService.new.process(file, output) + return if processed.blank? blob.upload(processed) blob.touch(:watermarked_at) end end end - - private - - def watermark_image(file, watermark) - ImageProcessing::MiniMagick - .source(file) - .convert("png") - .resize_to_limit(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) - .composite(watermark, mode: "over", gravity: "center") - .call - end - - def resize_watermark(file) - metadata = image_metadata(file) - - if metadata[:width].present? && metadata[:height].present? - width = [metadata[:width], MAX_IMAGE_SIZE].min * SCALE - height = [metadata[:height], MAX_IMAGE_SIZE].min * SCALE - diagonal = Math.sqrt(height**2 + width**2) - angle = Math.asin(height / diagonal) * 180 / Math::PI - - ImageProcessing::MiniMagick - .source(WATERMARK) - .resize_to_limit(diagonal, diagonal / 2) - .rotate(-angle, background: :transparent) - .call - end - end - - def image_metadata(file) - read_image(file) do |image| - if rotated_image?(image) - { width: image.height, height: image.width } - else - { width: image.width, height: image.height } - end - end - end - - def read_image(file) - require "mini_magick" - image = MiniMagick::Image.new(file.path) - - if image.valid? - yield image - else - logger.info "Skipping image analysis because ImageMagick doesn't support the file" - {} - end - end - - def rotated_image?(image) - ['RightTop', 'LeftBottom'].include?(image["%[orientation]"]) - end end diff --git a/app/services/watermark_service.rb b/app/services/watermark_service.rb new file mode 100644 index 000000000..1f192f879 --- /dev/null +++ b/app/services/watermark_service.rb @@ -0,0 +1,118 @@ +class WatermarkService + POINTSIZE = 20 + KERNING = 1.2 + ANGLE = 45 + FILL_COLOR = "rgba(0,0,0,0.4)" + + attr_reader :text + attr_reader :text_length + + def initialize(text = APPLICATION_NAME) + @text = " #{text} " # give more space around each occurence + @text_length = @text.length + end + + def process(file, output) + metadata = image_metadata(file) + + return if metadata.blank? + + watermark_image(file, output, metadata) + + output + end + + private + + def watermark_image(file, output, metadata) + MiniMagick::Tool::Convert.new do |convert| + setup_conversion_commands(convert, file) + apply_watermark(convert, metadata) + convert << output.to_path + end + end + + def setup_conversion_commands(convert, file) + convert << file.to_path + convert << "-pointsize" + convert << POINTSIZE + convert << "-kerning" + convert << KERNING + convert << "-fill" + convert << FILL_COLOR + convert << "-gravity" + convert << "northwest" + end + + # Parcourt l'image ligne par ligne et colonne par colonne en y apposant un filigrane + # en alternant un décalage horizontal sur chaque ligne + def apply_watermark(convert, metadata) + stride_x, stride_y, initial_offsets_x, initial_offset_y = calculate_watermark_params + + 0.step(by: stride_y, to: metadata[:height] + stride_y * 2).with_index do |offset_y, index| + initial_offset_x = initial_offsets_x[index % 2] + + 0.step(by: stride_x, to: metadata[:width] + stride_x * 2) do |offset_x| + x = initial_offset_x + offset_x + y = initial_offset_y + offset_y + draw_text(convert, x, y) + end + end + end + + def calculate_watermark_params + # Approximation de la longueur du texte, qui marche bien pour les constantes par défaut + char_width_approx = POINTSIZE / 2 + char_height_approx = POINTSIZE * 3 / 4 + + # Dimensions du rectangle de texte + text_width_approx = char_width_approx * text_length * Math.cos(ANGLE * (Math::PI / 180)).abs + text_height_approx = char_width_approx * text_length * Math.sin(ANGLE * (Math::PI / 180)).abs + char_height_approx + diagonal_length = Math.sqrt(text_width_approx**2 + text_height_approx**2) + + # Calcul des décalages entre chaque colonne et ligne + # afin que chaque occurence "suive" la précédente + stride_x = ((diagonal_length + char_width_approx) / Math.cos(ANGLE * (Math::PI / 180))) + stride_y = text_height_approx + + initial_offsets_x = [0, (0 - stride_x / 2).round] # Motif de damier en alternant le décalage horizontal + initial_offset_y = 0 - stride_y # Offset négatif pour mieux couvrir le nord ouest + + [stride_x.round, stride_y.round, initial_offsets_x, initial_offset_y.round] + end + + def draw_text(convert, x, y) + # A chaque insertion de texte, positionne le curseur, définit la rotation, puis réinitialise ces paramètres pour la prochaine occurence + # Note: x and y can be negative value + convert << "-draw" + convert << "translate #{x},#{y} rotate #{-ANGLE} text 0,0 '#{text}' rotate #{ANGLE} translate #{-x},#{-y}" + end + + def image_metadata(file) + read_image(file) do |image| + width = image.width + height = image.height + + if rotated_image?(image) + width, height = height, width + end + + { width: width, height: height } + end + end + + def read_image(file) + image = MiniMagick::Image.new(file.to_path) + + if image.valid? + yield image + else + Rails.logger.info "Skipping image analysis because ImageMagick doesn't support the file #{file}" + nil + end + end + + def rotated_image?(image) + ['RightTop', 'LeftBottom'].include?(image["%[orientation]"]) + end +end diff --git a/config/env.example.optional b/config/env.example.optional index 8935825e9..969c105d3 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -68,9 +68,6 @@ DS_ENV="staging" # Instance customization: PDF export logo ---> to be put in "app/assets/images" # DOSSIER_PDF_EXPORT_LOGO_SRC="app/assets/images/header/logo-ds-wide.png" -# Instance customization: watermark for identity documents -# WATERMARK_FILE="" - # Enabling maintenance mode # MAINTENANCE_MODE="true" diff --git a/config/initializers/watermark.rb b/config/initializers/watermark.rb deleted file mode 100644 index 8c87c9cda..000000000 --- a/config/initializers/watermark.rb +++ /dev/null @@ -1 +0,0 @@ -WATERMARK_FILE = ENV.fetch('WATERMARK_FILE', 'watermark.png') diff --git a/lib/tasks/pjs.rake b/lib/tasks/pjs.rake index 6b52198fc..73d952e3e 100644 --- a/lib/tasks/pjs.rake +++ b/lib/tasks/pjs.rake @@ -22,4 +22,32 @@ namespace :pjs do blobs.in_batches { |batch| batch.ids.each { |id| PjsMigrationJob.perform_later(id) } } end + + desc "Watermark demo. Usage: noglob rake pjs:watermark_demo[tmp/carte-identite-demo-1.jpg]" + task :watermark_demo, [:file_path] => :environment do |_t, args| + file = Pathname.new(args[:file_path]) + output_file = Rails.root.join('tmp', "#{file.basename(file.extname)}_watermarked#{file.extname}") + + processed = WatermarkService.new.process(file, output_file) + + if processed + rake_puts "Watermarked: #{processed}" + else + rake_puts "File #{file} not watermarked. Read application log for more information" + end + end + + desc "Watermark demo all defined demo files. Usage: noglob rake pjs:watermark_demo_all" + task :watermark_demo_all => :environment do + # You must have these filenames in tmp/ to run this demo (download ID cards specimens) + filenames = [ + "carte-identite-demo-1.jpg", "carte-identite-demo-2.jpg", "carte-identite-demo-3.png", "carte-identite-demo-4.jpg", + "carte-identite-demo-5.jpg", "passeport-1.jpg", "passeport-2.jpg" + ] + + filenames.each do |file| + Rake::Task["pjs:watermark_demo"].invoke("tmp/#{file}") + Rake::Task["pjs:watermark_demo"].reenable + end + end end diff --git a/spec/services/watermark_service_spec.rb b/spec/services/watermark_service_spec.rb new file mode 100644 index 000000000..abe0da8b4 --- /dev/null +++ b/spec/services/watermark_service_spec.rb @@ -0,0 +1,23 @@ +RSpec.describe WatermarkService do + let(:image) { file_fixture("logo_test_procedure.png") } + let(:watermark_service) { WatermarkService.new } + + describe '#process' do + it 'returns a tempfile if watermarking succeeds' do + Tempfile.create do |output| + watermark_service.process(image, output) + # output size should always be a little greater than input size + expect(output.size).to be_between(image.size, image.size * 1.5) + end + end + + it 'returns nil if metadata is blank' do + allow(watermark_service).to receive(:image_metadata).and_return(nil) + + Tempfile.create do |output| + expect(watermark_service.process(image.to_path, output)).to be_nil + expect(output.size).to eq(0) + end + end + end +end From 6520ea027c0dd1467126d4c5abeb4c944aeaf88a Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Mon, 11 Sep 2023 14:55:23 +0200 Subject: [PATCH 16/24] ci: install a font for watermarks --- .github/workflows/ci.yml | 3 +++ README.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 941671096..3bf5b4b1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,9 @@ jobs: - name: Setup the app runtime and dependencies uses: ./.github/actions/ci-setup-rails + - name: Install fonts pickable by ImageMagick + run: sudo apt-get install -y gsfonts + - name: Pre-compile assets uses: ./.github/actions/ci-setup-assets diff --git a/README.md b/README.md index a427f1fe2..add1b33f2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Vous souhaitez y apporter des changements ou des améliorations ? Lisez notre [ #### Tous environnements - postgresql +- imagemagick et gsfonts pour générer les filigranes sur les titres d'identité. #### Développement From 5afaa458e2e45e4163b06dc3d7c08af78708d85e Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:14:36 +0200 Subject: [PATCH 17/24] feat(routing): add regions to routable_type_de_champ --- app/models/procedure_revision.rb | 2 +- spec/models/procedure_revision_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index 716334e57..545754516 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -224,7 +224,7 @@ class ProcedureRevision < ApplicationRecord end def routable_types_de_champ - types_de_champ_public.filter { |tdc| [:drop_down_list, :departements].include?(tdc.type_champ.to_sym) } + types_de_champ_public.filter { |tdc| [:drop_down_list, :departements, :regions].include?(tdc.type_champ.to_sym) } end private diff --git a/spec/models/procedure_revision_spec.rb b/spec/models/procedure_revision_spec.rb index b6011ca24..6204d3d43 100644 --- a/spec/models/procedure_revision_spec.rb +++ b/spec/models/procedure_revision_spec.rb @@ -948,9 +948,10 @@ describe ProcedureRevision do p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'l1') p.draft_revision.add_type_de_champ(type_champ: :drop_down_list, libelle: 'l2') p.draft_revision.add_type_de_champ(type_champ: :departements, libelle: 'l3') + p.draft_revision.add_type_de_champ(type_champ: :regions, libelle: 'l4') end end - it { expect(draft.routable_types_de_champ.pluck(:libelle)).to eq(['l2', 'l3']) } + it { expect(draft.routable_types_de_champ.pluck(:libelle)).to eq(['l2', 'l3', 'l4']) } end end From e9760e5aa32d24b15d4963a4abdba8a45339c18e Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:21:53 +0200 Subject: [PATCH 18/24] feat(routing): add regions options to value tag --- app/components/procedure/one_groupe_management_component.rb | 6 ++++++ app/models/groupe_instructeur.rb | 2 ++ 2 files changed, 8 insertions(+) diff --git a/app/components/procedure/one_groupe_management_component.rb b/app/components/procedure/one_groupe_management_component.rb index 4fc003628..b700f1c66 100644 --- a/app/components/procedure/one_groupe_management_component.rb +++ b/app/components/procedure/one_groupe_management_component.rb @@ -81,6 +81,8 @@ class Procedure::OneGroupeManagementComponent < ApplicationComponent case @revision.types_de_champ_public.find_by(stable_id: targeted_champ.stable_id).type_champ when TypeDeChamp.type_champs.fetch(:departements) departements_for_select + when TypeDeChamp.type_champs.fetch(:regions) + regions_for_select when TypeDeChamp.type_champs.fetch(:drop_down_list) targeted_champ .options(@revision.types_de_champ_public) @@ -91,4 +93,8 @@ class Procedure::OneGroupeManagementComponent < ApplicationComponent def departements_for_select APIGeoService.departements.map { ["#{_1[:code]} – #{_1[:name]}", constant(_1[:code]).to_json] } end + + def regions_for_select + APIGeoService.regions.map { ["#{_1[:code]} – #{_1[:name]}", constant(_1[:code]).to_json] } + end end diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index bda2b18d1..43ef657ee 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -107,6 +107,8 @@ class GroupeInstructeur < ApplicationRecord options = case routing_tdc.type_champ when TypeDeChamp.type_champs.fetch(:departements) APIGeoService.departements.map { _1[:code] } + when TypeDeChamp.type_champs.fetch(:regions) + APIGeoService.regions.map { _1[:code] } when TypeDeChamp.type_champs.fetch(:drop_down_list) routing_tdc.options_with_drop_down_other end From c2beca7d6d519b4b780a57dcf9b441c753ffdffa Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:31:24 +0200 Subject: [PATCH 19/24] feat(routing): can create simple routing from regions tdc --- .../administrateurs/groupe_instructeurs_controller.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 147aaa260..5f5d79e19 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -50,7 +50,15 @@ module Administrateurs .find_or_create_by(label: code_and_name) .update(instructeurs: [current_administrateur.instructeur], routing_rule:) end - + when TypeDeChamp.type_champs.fetch(:regions) + tdc_options = APIGeoService.regions.map { ["#{_1[:code]} – #{_1[:name]}", _1[:code]] } + tdc_options.each do |code_and_name, code| + routing_rule = ds_eq(champ_value(stable_id), constant(code)) + @procedure + .groupe_instructeurs + .find_or_create_by(label: code_and_name) + .update(instructeurs: [current_administrateur.instructeur], routing_rule:) + end when TypeDeChamp.type_champs.fetch(:drop_down_list) tdc_options = tdc.drop_down_options.reject(&:empty?) tdc_options.each do |option_label| From e255bb9929f2f81892cca3c60a1538f8a19a1a43 Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:39:02 +0200 Subject: [PATCH 20/24] refactor(routing): extract methods to create groups from tdcs --- .../groupe_instructeurs_controller.rb | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index 5f5d79e19..3238cb719 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -43,31 +43,13 @@ module Administrateurs case tdc.type_champ when TypeDeChamp.type_champs.fetch(:departements) tdc_options = APIGeoService.departements.map { ["#{_1[:code]} – #{_1[:name]}", _1[:code]] } - tdc_options.each do |code_and_name, code| - routing_rule = ds_eq(champ_value(stable_id), constant(code)) - @procedure - .groupe_instructeurs - .find_or_create_by(label: code_and_name) - .update(instructeurs: [current_administrateur.instructeur], routing_rule:) - end + create_groups_from_territorial_tdc(tdc_options, stable_id) when TypeDeChamp.type_champs.fetch(:regions) tdc_options = APIGeoService.regions.map { ["#{_1[:code]} – #{_1[:name]}", _1[:code]] } - tdc_options.each do |code_and_name, code| - routing_rule = ds_eq(champ_value(stable_id), constant(code)) - @procedure - .groupe_instructeurs - .find_or_create_by(label: code_and_name) - .update(instructeurs: [current_administrateur.instructeur], routing_rule:) - end + create_groups_from_territorial_tdc(tdc_options, stable_id) when TypeDeChamp.type_champs.fetch(:drop_down_list) tdc_options = tdc.drop_down_options.reject(&:empty?) - tdc_options.each do |option_label| - routing_rule = ds_eq(champ_value(stable_id), constant(option_label)) - @procedure - .groupe_instructeurs - .find_or_create_by(label: option_label) - .update(instructeurs: [current_administrateur.instructeur], routing_rule:) - end + create_groups_from_drop_down_list_tdc(tdc_options, stable_id) end if tdc.drop_down_other? @@ -468,5 +450,25 @@ module Administrateurs def flash_message_for_invalid_csv flash[:alert] = "Importation impossible, veuillez importer un csv suivant #{view_context.link_to('ce modèle', "/csv/import-instructeurs-test.csv")} pour une procédure sans routage ou #{view_context.link_to('celui-ci', "/csv/#{I18n.locale}/import-groupe-test.csv")} pour une procédure routée" end + + def create_groups_from_territorial_tdc(tdc_options, stable_id) + tdc_options.each do |label, code| + routing_rule = ds_eq(champ_value(stable_id), constant(code)) + @procedure + .groupe_instructeurs + .find_or_create_by(label: label) + .update(instructeurs: [current_administrateur.instructeur], routing_rule:) + end + end + + def create_groups_from_drop_down_list_tdc(tdc_options, stable_id) + tdc_options.each do |label| + routing_rule = ds_eq(champ_value(stable_id), constant(label)) + @procedure + .groupe_instructeurs + .find_or_create_by(label: label) + .update(instructeurs: [current_administrateur.instructeur], routing_rule:) + end + end end end From d37fb90b2f34df5cdfeb5f94fdde1811cf4d84fb Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:49:50 +0200 Subject: [PATCH 21/24] feat(routing): make routing engine work with region champ --- app/models/logic/champ_value.rb | 2 +- spec/models/routing_engine_spec.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/models/logic/champ_value.rb b/app/models/logic/champ_value.rb index 4a1c9dcc2..e3d6985ff 100644 --- a/app/models/logic/champ_value.rb +++ b/app/models/logic/champ_value.rb @@ -44,7 +44,7 @@ class Logic::ChampValue < Logic::Term targeted_champ.selected when "Champs::MultipleDropDownListChamp" targeted_champ.selected_options - when "Champs::DepartementChamp" + when "Champs::DepartementChamp", "Champs::RegionChamp" targeted_champ.code end end diff --git a/spec/models/routing_engine_spec.rb b/spec/models/routing_engine_spec.rb index 38468ebb6..8d6574d4a 100644 --- a/spec/models/routing_engine_spec.rb +++ b/spec/models/routing_engine_spec.rb @@ -92,6 +92,25 @@ describe RoutingEngine, type: :model do end end + context 'with a regions type de champ' do + let(:procedure) do + create(:procedure, types_de_champ_public: [{ type: :regions }]).tap do |p| + p.groupe_instructeurs.create(label: 'a third group') + end + end + + let(:regions_tdc) { procedure.draft_revision.types_de_champ.first } + + context 'with a matching rule' do + before do + gi_2.update(routing_rule: ds_eq(champ_value(regions_tdc.stable_id), constant('04'))) + dossier.champs.first.update(value: 'La Réunion') + end + + it { is_expected.to eq(gi_2) } + end + end + context 'routing rules priorities' do let(:procedure) do create(:procedure, From 97bfdc774d454d66e17cc9560774cd029c17750a Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 15:51:14 +0200 Subject: [PATCH 22/24] wording(routing): update routing configuration notice --- .../instructeurs_options_component.fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/procedure/instructeurs_options_component/instructeurs_options_component.fr.yml b/app/components/procedure/instructeurs_options_component/instructeurs_options_component.fr.yml index 1d98b2d9d..9d5187625 100644 --- a/app/components/procedure/instructeurs_options_component/instructeurs_options_component.fr.yml +++ b/app/components/procedure/instructeurs_options_component/instructeurs_options_component.fr.yml @@ -4,7 +4,7 @@ fr: Le routage permet d’acheminer les dossiers vers différents groupes d’instructeurs. routing_configuration_notice_2_html: |

Pour le configurer, votre formulaire doit comporter - au moins un champ de type « choix simple » ou « départements ».

+ au moins un champ de type « choix simple », « départements » ou « régions ».

Ajoutez ce champ dans la page « Configuration des champs ».

delete_title: Aucun dossier ne sera supprimé. Les groupes d'instructeurs vont être supprimés. Seuls les instructeurs du groupe « %{defaut_label} » resteront affectés à la démarche. delete_confirmation: | From cebeffb195b5349283042367aa5ba47b3d333452 Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Fri, 8 Sep 2023 16:01:05 +0200 Subject: [PATCH 23/24] test(routing): test simple routing with regions type de champ --- .../groupe_instructeurs_controller_spec.rb | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index 40c731ae4..5ea80ee2f 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -761,6 +761,26 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do expect(procedure3.routing_enabled).to be_truthy end end + + context 'with a regions type de champ' do + let!(:procedure3) do + create(:procedure, + types_de_champ_public: [{ type: :regions }], + administrateurs: [admin]) + end + + let!(:regions_tdc) { procedure3.draft_revision.types_de_champ.first } + + before { post :create_simple_routing, params: { procedure_id: procedure3.id, create_simple_routing: { stable_id: regions_tdc.stable_id } } } + + it do + expect(response).to redirect_to(admin_procedure_groupe_instructeurs_path(procedure3)) + expect(flash.notice).to eq 'Les groupes instructeurs ont été ajoutés' + expect(procedure3.groupe_instructeurs.pluck(:label)).to include("01 – Guadeloupe") + expect(procedure3.reload.defaut_groupe_instructeur.routing_rule).to eq(ds_eq(champ_value(regions_tdc.stable_id), constant('01'))) + expect(procedure3.routing_enabled).to be_truthy + end + end end describe '#wizard' do From 345729a159c926d21b6602267a219a56aa54f33a Mon Sep 17 00:00:00 2001 From: Eric Leroy-Terquem Date: Wed, 13 Sep 2023 10:20:34 +0200 Subject: [PATCH 24/24] test(champ value): test compute of champ value for regions tdc --- spec/models/logic/champ_value_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/models/logic/champ_value_spec.rb b/spec/models/logic/champ_value_spec.rb index 4e359032a..a482935d3 100644 --- a/spec/models/logic/champ_value_spec.rb +++ b/spec/models/logic/champ_value_spec.rb @@ -76,6 +76,12 @@ describe Logic::ChampValue do it { is_expected.to eq(true) } end + context 'region tdc' do + let(:champ) { create(:champ_regions, value: 'La Réunion') } + + it { is_expected.to eq('04') } + end + describe 'errors' do let(:champ) { create(:champ) }