From 387701e7cb77de83274e6f85cf19e4d0170d08e4 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 26 Jan 2022 11:59:59 +0100 Subject: [PATCH 01/11] config: add a new SAML_IDP_ENABLED env var This fixes the app crashing on launch when using the production profile, because the certificates are not present. --- app/views/manager/application/_navigation.html.erb | 2 +- config/env.example | 10 +++++----- config/initializers/saml_idp.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/manager/application/_navigation.html.erb b/app/views/manager/application/_navigation.html.erb index 98665a79d..b7879e718 100644 --- a/app/views/manager/application/_navigation.html.erb +++ b/app/views/manager/application/_navigation.html.erb @@ -25,7 +25,7 @@ as defined by the routes in the `admin/` namespace <%= link_to "Delayed Jobs", manager_delayed_job_path, class: "navigation__link" %> <%= link_to "Features", manager_flipper_path, class: "navigation__link" %> - <% if Rails.env.production? && ENV['SENDINBLUE_ENABLED'] == 'enabled'%> + <% if ENV["SENDINBLUE_ENABLED"] == "enabled" && ENV["SAML_IDP_ENABLED"] == "enabled" %> <%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %> <% end %> diff --git a/config/env.example b/config/env.example index 303b225f9..d2f9de017 100644 --- a/config/env.example +++ b/config/env.example @@ -16,11 +16,6 @@ SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93 # Secret key for One-Time-Password codes, used for 2-factors authentication OTP_SECRET_KEY="" -# SAML IdP - -# SAML_IDP_CERTIFICATE="billybop" -# SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n" - # Database credentials DB_DATABASE="tps_development" DB_HOST="localhost" @@ -41,6 +36,11 @@ FOG_OPENSTACK_URL="" FOG_OPENSTACK_REGION="" DS_PROXY_URL="" +# SAML Identity provider +SAML_IDP_ENABLED="disabled" +SAML_IDP_CERTIFICATE="" +SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n" + # External service: authentication through France Connect FC_PARTICULIER_ID="" FC_PARTICULIER_SECRET="" diff --git a/config/initializers/saml_idp.rb b/config/initializers/saml_idp.rb index d37e49d9b..946567e10 100644 --- a/config/initializers/saml_idp.rb +++ b/config/initializers/saml_idp.rb @@ -1,7 +1,7 @@ # The certificate and secret key are not fetched from secrets.yml because there is a problem to set a secret key from a multiline env var" # So we fetch env var directly here -if Rails.env.production? +if ENV['SAML_IDP_ENABLED'] == 'enabled' SamlIdp.config.x509_certificate = ENV.fetch("SAML_IDP_CERTIFICATE") SamlIdp.config.secret_key = ENV.fetch("SAML_IDP_SECRET_KEY") end From 8b76a8c2ae55937dabef13a041bdff2be59f006e Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:00:41 +0100 Subject: [PATCH 02/11] add supprimes_recemment to procedure presentation --- ...d_supprimes_recemment_to_procedure_presentations.rb | 10 ++++++++++ db/schema.rb | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20220127135056_add_supprimes_recemment_to_procedure_presentations.rb diff --git a/db/migrate/20220127135056_add_supprimes_recemment_to_procedure_presentations.rb b/db/migrate/20220127135056_add_supprimes_recemment_to_procedure_presentations.rb new file mode 100644 index 000000000..4aa655ffe --- /dev/null +++ b/db/migrate/20220127135056_add_supprimes_recemment_to_procedure_presentations.rb @@ -0,0 +1,10 @@ +class AddSupprimesRecemmentToProcedurePresentations < ActiveRecord::Migration[6.1] + def up + ProcedurePresentation.update_all(%Q(filters = filters || '{"supprimes_recemment": []}')) + change_column_default :procedure_presentations, :filters, { "tous" => [], "suivis" => [], "traites" => [], "a-suivre" => [], "archives" => [], "supprimes_recemment" => [], "expirant": [] } + end + + def down + change_column_default :procedure_presentations, :filters, { "tous" => [], "suivis" => [], "traites" => [], "a-suivre" => [], "archives" => [], "expirant": [] } + end +end diff --git a/db/schema.rb b/db/schema.rb index aef752fc6..973f86d48 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.define(version: 2021_12_02_133139) do +ActiveRecord::Schema.define(version: 2022_01_27_135056) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -569,7 +569,7 @@ ActiveRecord::Schema.define(version: 2021_12_02_133139) do create_table "procedure_presentations", id: :serial, force: :cascade do |t| t.integer "assign_to_id" t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false - t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[]}, null: false + t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[], "supprimes_recemment"=>[]}, null: false t.datetime "created_at" t.datetime "updated_at" t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false From a94568e2efb7ad5476a14cc22164958a87c43be6 Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:01:09 +0100 Subject: [PATCH 03/11] modify procedures controller --- .../instructeurs/procedures_controller.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 2bede5eed..921699168 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -18,6 +18,7 @@ module Instructeurs @dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count @dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_expirant_count_per_procedure = dossiers.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count + @dossiers_supprimes_recemment_count_per_procedure = dossiers.hidden_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id) @followed_dossiers_count_per_procedure = current_instructeur @@ -35,7 +36,8 @@ module Instructeurs 'traités' => @dossiers_termines_count_per_procedure.sum { |_, v| v }, 'dossiers' => @dossiers_count_per_procedure.sum { |_, v| v }, 'expirant' => @dossiers_expirant_count_per_procedure.sum { |_, v| v }, - 'archivés' => @dossiers_archived_count_per_procedure.sum { |_, v| v } + 'archivés' => @dossiers_archived_count_per_procedure.sum { |_, v| v }, + 'supprimes_recemment' => @dossiers_supprimes_recemment_count_per_procedure.sum { |_, v| v } } @procedure_ids_en_cours_with_notifications = current_instructeur.procedure_ids_with_notifications(:en_cours) @@ -51,9 +53,9 @@ module Instructeurs @current_filters = current_filters @displayed_fields_options, @displayed_fields_selected = procedure_presentation.displayed_fields_for_select - @a_suivre_count, @suivis_count, @traites_count, @tous_count, @archives_count, @expirant_count = current_instructeur + @a_suivre_count, @suivis_count, @traites_count, @tous_count, @supprimes_recemment_count, @archives_count, @expirant_count = current_instructeur .dossiers_count_summary(groupe_instructeur_ids) - .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'archives', 'expirant') + .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'supprimes_recemment', 'archives', 'expirant') dossiers_visibles = Dossier .where(groupe_instructeur_id: groupe_instructeur_ids) @@ -72,6 +74,7 @@ module Instructeurs @termines_dossiers = dossiers_visibles.termine.visible_by_administration @all_state_dossiers = dossiers_visibles.all_state.visible_by_administration + @supprimes_recemment_dossiers = dossiers_visibles.termine.hidden_by_administration @archived_dossiers = dossiers_visibles.archived @expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration @@ -88,6 +91,9 @@ module Instructeurs when 'tous' dossiers_count = @tous_count @all_state_dossiers + when 'supprimes_recemment' + dossiers_count = @supprimes_recemment_count + @supprimes_recemment_dossiers when 'archives' dossiers_count = @archives_count @archived_dossiers @@ -282,6 +288,13 @@ module Instructeurs ) end + def restore + dossier = current_instructeur.dossiers.find(params[:dossier_id]) + dossier.restore(current_instructeur) + flash.notice = t('instructeurs.dossiers.restore') + redirect_to instructeur_procedure_path(procedure) + end + private def assign_to_params From 0f968f4c4e4fb9aa971f2c03e80e8f5378e8512f Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:01:27 +0100 Subject: [PATCH 04/11] modify models --- app/models/dossier.rb | 4 ++++ app/models/instructeur.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index b1dcc6b0a..c0f11c577 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -815,6 +815,10 @@ class Dossier < ApplicationRecord restore_dossier_and_destroy_deleted_dossier(author) end end + elsif author_is_administration(author) && hidden_by_administration? + transaction do + update(hidden_by_administration_at: nil) + end end end diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index 1e29a5b6e..b569ee895 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -236,7 +236,8 @@ class Instructeur < ApplicationRecord COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction')) AS tous, - COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives, + COUNT(DISTINCT dossiers.id) FILTER (where not archived AND (dossiers.hidden_by_administration_at IS NOT NULL AND dossiers.state in ('accepte', 'refuse', 'sans_suite') )) AS supprimes_recemment, + COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives, COUNT(DISTINCT dossiers.id) FILTER (where procedures.procedure_expires_when_termine_enabled AND ( @@ -256,7 +257,6 @@ class Instructeur < ApplicationRecord ON follows.dossier_id = dossiers.id AND follows.unfollowed_at IS NULL WHERE "dossiers"."hidden_at" IS NULL - AND "dossiers"."hidden_by_administration_at" IS NULL AND "dossiers"."state" != 'brouillon' AND "dossiers"."groupe_instructeur_id" in (:groupe_instructeur_ids) EOF From 82ddad125f7a5ef829d0b1b49ea40e64c74bc3e8 Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:02:02 +0100 Subject: [PATCH 05/11] layout --- .../dossiers/_header_actions.html.haml | 3 ++- .../procedures/_dossier_actions.html.haml | 18 ++++++++++++------ .../instructeurs/procedures/_list.html.haml | 9 +++++++++ .../instructeurs/procedures/_tabs.html.haml | 5 +++++ .../instructeurs/procedures/index.html.haml | 1 + .../instructeurs/procedures/show.html.haml | 6 +++++- 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/app/views/instructeurs/dossiers/_header_actions.html.haml b/app/views/instructeurs/dossiers/_header_actions.html.haml index 47f7b4893..a680b9f8d 100644 --- a/app/views/instructeurs/dossiers/_header_actions.html.haml +++ b/app/views/instructeurs/dossiers/_header_actions.html.haml @@ -28,7 +28,8 @@ state: dossier.state, archived: dossier.archived, dossier_is_followed: current_instructeur&.follow?(dossier), - close_to_expiration: dossier.close_to_expiration? } + close_to_expiration: dossier.close_to_expiration?, + supprimes_recemment: dossier.hidden_by_administration? } .state-button diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 87add5c29..ad122588b 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -21,12 +21,18 @@ %span.icon.archive .dropdown-description Archiver le dossier - - %li.danger - = link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do - %span.icon.delete - .dropdown-description - = t('views.instructeurs.dossiers.delete_dossier') + - if supprimes_recemment + %li.danger + = link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment restaurer le dossier #{dossier_id}" } do + %span.icon.reply + .dropdown-description + = t('views.instructeurs.dossiers.restore') + - else + %li.danger + = link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do + %span.icon.delete + .dropdown-description + = t('views.instructeurs.dossiers.delete_dossier') - elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state) - if dossier_is_followed diff --git a/app/views/instructeurs/procedures/_list.html.haml b/app/views/instructeurs/procedures/_list.html.haml index 6f60a13af..073a1bea9 100644 --- a/app/views/instructeurs/procedures/_list.html.haml +++ b/app/views/instructeurs/procedures/_list.html.haml @@ -45,6 +45,15 @@ .stats-legend = t('pluralize.case', count: dossier_count) + %li + %object + = link_to(instructeur_procedure_path(p, statut: 'supprimes_recemment')) do + - dossier_count = dossiers_supprimes_recemment_count_per_procedure[p.id] || 0 + .stats-number + = number_with_html_delimiter(dossier_count) + .stats-legend + = t('pluralize.dossiers_supprimes_recemment', count: dossier_count) + - if p.procedure_expires_when_termine_enabled %li %object diff --git a/app/views/instructeurs/procedures/_tabs.html.haml b/app/views/instructeurs/procedures/_tabs.html.haml index e1337fdaa..9b35e6d70 100644 --- a/app/views/instructeurs/procedures/_tabs.html.haml +++ b/app/views/instructeurs/procedures/_tabs.html.haml @@ -22,6 +22,11 @@ active: statut == 'tous', badge: number_with_html_delimiter(tous_count)) + = tab_item(t('pluralize.dossiers_supprimes_recemment', count: supprimes_recemment_count), + instructeur_procedure_path(procedure, statut: 'supprimes_recemment'), + active: statut == 'supprimes_recemment', + badge: number_with_html_delimiter(supprimes_recemment_count)) + - if procedure.procedure_expires_when_termine_enabled = tab_item(t('pluralize.dossiers_close_to_expiration', count: expirant_count), instructeur_procedure_path(procedure, statut: 'expirant'), diff --git a/app/views/instructeurs/procedures/index.html.haml b/app/views/instructeurs/procedures/index.html.haml index 4356c463e..cfafa34b1 100644 --- a/app/views/instructeurs/procedures/index.html.haml +++ b/app/views/instructeurs/procedures/index.html.haml @@ -13,6 +13,7 @@ dossiers_archived_count_per_procedure: @dossiers_archived_count_per_procedure, dossiers_termines_count_per_procedure: @dossiers_termines_count_per_procedure, dossiers_expirant_count_per_procedure: @dossiers_expirant_count_per_procedure, + dossiers_supprimes_recemment_count_per_procedure: @dossiers_supprimes_recemment_count_per_procedure, followed_dossiers_count_per_procedure: @followed_dossiers_count_per_procedure, procedure_ids_en_cours_with_notifications: @procedure_ids_en_cours_with_notifications, procedure_ids_termines_with_notifications: @procedure_ids_termines_with_notifications } diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 4738e7f6e..dfeb65b78 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -17,6 +17,7 @@ suivis_count: @suivis_count, traites_count: @traites_count, tous_count: @tous_count, + supprimes_recemment_count: @supprimes_recemment_count, archives_count: @archives_count, expirant_count: @expirant_count, has_en_cours_notifications: @has_en_cours_notifications, @@ -31,6 +32,8 @@ %p.explication-onglet Les dossiers dans cet onglet sont terminés : ils ont été acceptés, refusés ou classés sans suite. - if @statut == 'tous' %p.explication-onglet Tous les dossiers qui ont été déposés sur cette démarche, quel que soit le statut. + - if @statut == 'supprimes_recemment' + %p.explication-onglet Tous les dossiers terminés et supprimés par les instructeurs sur cette démarche - if @statut == 'archives' %p.explication-onglet Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier. @@ -135,7 +138,8 @@ state: p.state, archived: p.archived, dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id), - close_to_expiration: @statut == 'expirant' } + close_to_expiration: @statut == 'expirant', + supprimes_recemment: @statut == 'supprimes_recemment' } = pagination - else From 64f2dfdea165b4c9bc0fddacacb344eb8fad6112 Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:02:18 +0100 Subject: [PATCH 06/11] routes --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index 9cd48ead5..cc303bc9e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -386,6 +386,7 @@ Rails.application.routes.draw do post 'avis' => 'dossiers#create_avis' get 'print' => 'dossiers#print' get 'telecharger_pjs' => 'dossiers#telecharger_pjs' + patch 'restore' end end From 96556ca4b33ab8d66f717c5bd80781cfe589435e Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Thu, 27 Jan 2022 17:02:27 +0100 Subject: [PATCH 07/11] locales --- config/locales/en.yml | 2 ++ config/locales/fr.yml | 14 ++++++++------ .../instructeur/procedures/_tabs.html.haml_spec.rb | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index ec96dd382..e6668fac8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -140,6 +140,7 @@ en: archived_dossier: "This file will be kept for an additional month" delete_dossier: "Delete file" deleted_by_user: "File deleted by user" + restore: "Restore the file" avis: introduction_file_explaination: "File attached to the request for advice" users: @@ -419,6 +420,7 @@ en: dossiers: deleted_by_instructeur: "The folder has been deleted" impossible_deletion: "Unable to delete : the folder is not processed" + restore: "The folder has been restored" france_connect: particulier: password_confirmation: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 49bed38a3..51c6641fd 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -137,6 +137,7 @@ fr: archived_dossier: "Le dossier sera conservé 1 mois supplémentaire" delete_dossier: "Supprimer le dossier" deleted_by_user: "Dossier supprimé par l'usager" + restore: "Restaurer le dossier" avis: introduction_file_explaination: "Fichier joint à la demande d’avis" users: @@ -372,13 +373,13 @@ fr: one: dossier invité other: dossiers invités dossiers_supprimes_recemment: - zero: dossier supprimé recemment - one: dossier supprimé recemment - other: dossiers supprimés recemment + zero: supprimé recemment + one: supprimé recemment + other: supprimés recemment dossiers_supprimes_definitivement: - zero: dossier supprimé définitivement - one: dossier supprimé définitivement - other: dossiers supprimés définitivement + zero: supprimé définitivement + one: supprimé définitivement + other: supprimés définitivement dossiers_transferes: zero: demande de transfert one: demande de transfert @@ -427,6 +428,7 @@ fr: dossiers: deleted_by_instructeur: "Le dossier a bien été supprimé de votre interface" impossible_deletion: "Supression impossible : le dossier n'est pas traité" + restore: "Le dossier a bien été restauré" administrateurs: procedures: show: diff --git a/spec/views/instructeur/procedures/_tabs.html.haml_spec.rb b/spec/views/instructeur/procedures/_tabs.html.haml_spec.rb index e69a66a37..7fdb7bf54 100644 --- a/spec/views/instructeur/procedures/_tabs.html.haml_spec.rb +++ b/spec/views/instructeur/procedures/_tabs.html.haml_spec.rb @@ -11,6 +11,7 @@ describe 'instructeurs/procedures/_tabs.html.haml', type: :view do suivis_count: 0, traites_count: 0, tous_count: 0, + supprimes_recemment_count: 0, archives_count: 0, expirant_count: 0, has_en_cours_notifications: false, From 842cc11689959253cadca6e426b0004f7a1f3032 Mon Sep 17 00:00:00 2001 From: Kara Diaby Date: Fri, 28 Jan 2022 15:08:08 +0100 Subject: [PATCH 08/11] tests --- .../instructeurs/dossiers_controller_spec.rb | 21 +++++++++++++++++++ .../procedures/_list.html.haml_spec.rb | 1 + 2 files changed, 22 insertions(+) diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 336afd076..8cde2714c 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -839,4 +839,25 @@ describe Instructeurs::DossiersController, type: :controller do end end end + + describe '#restore' do + let(:instructeur) { create(:instructeur) } + let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) } + let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } + let!(:dossier) { create(:dossier, state: 'accepte', procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first, hidden_by_administration_at: 1.hour.ago) } + + before do + sign_in(instructeur.user) + instructeur.groupe_instructeurs << gi_p1_1 + patch :restore, + params: { + procedure_id: procedure.id, + dossier_id: dossier.id + } + end + + it "puts hidden_by_administration_at to nil" do + expect(dossier.reload.hidden_by_administration_at).to eq(nil) + end + end end diff --git a/spec/views/instructeur/procedures/_list.html.haml_spec.rb b/spec/views/instructeur/procedures/_list.html.haml_spec.rb index 7de854f9a..9c9de8f25 100644 --- a/spec/views/instructeur/procedures/_list.html.haml_spec.rb +++ b/spec/views/instructeur/procedures/_list.html.haml_spec.rb @@ -8,6 +8,7 @@ describe 'instructeurs/procedures/_list.html.haml', type: :view do dossiers_a_suivre_count_per_procedure: 2, dossiers_archived_count_per_procedure: 1, dossiers_termines_count_per_procedure: 1, + dossiers_supprimes_recemment_count_per_procedure: 0, dossiers_expirant_count_per_procedure: 0, followed_dossiers_count_per_procedure: 0, procedure_ids_en_cours_with_notifications: [], From e8e37cce15b305e432b2fd54b78f8999bf6aa3ac Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 26 Jan 2022 16:52:32 +0000 Subject: [PATCH 09/11] models: refactor the types_de_champ validators Context: we want to validate public and private types_de_champ separately. Before we validated the whole revision (and then validators themselves enumerated all champs, public and private). Now we validate the actual public types_de_champ, which will let us validate separately the private types_de_champ. --- app/models/procedure.rb | 2 +- .../revisions/no_empty_drop_down_validator.rb | 8 ++------ .../no_empty_repetition_validator.rb | 8 ++------ .../administrateurs/procedures/show.html.haml | 2 +- config/locales/fr.yml | 4 ++-- config/locales/models/procedure/fr.yml | 4 ++-- spec/models/procedure_spec.rb | 20 +++++++++---------- .../administrateurs/procedure_publish_spec.rb | 4 ++-- 8 files changed, 22 insertions(+), 30 deletions(-) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 99482064b..9e7121655 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -245,7 +245,7 @@ class Procedure < ApplicationRecord validates :description, presence: true, allow_blank: false, allow_nil: false validates :administrateurs, presence: true validates :lien_site_web, presence: true, if: :publiee? - validates :draft_revision, + validates :draft_types_de_champ, 'revisions/no_empty_repetition': true, 'revisions/no_empty_drop_down': true, if: :validate_for_publication? diff --git a/app/validators/revisions/no_empty_drop_down_validator.rb b/app/validators/revisions/no_empty_drop_down_validator.rb index 86e257e34..4917a2006 100644 --- a/app/validators/revisions/no_empty_drop_down_validator.rb +++ b/app/validators/revisions/no_empty_drop_down_validator.rb @@ -1,10 +1,6 @@ class Revisions::NoEmptyDropDownValidator < ActiveModel::EachValidator - def validate_each(procedure, attribute, revision) - return if revision.nil? - - tdcs = revision.types_de_champ + revision.types_de_champ_private - drop_downs = tdcs.filter(&:drop_down_list?) - drop_downs.each do |drop_down| + def validate_each(procedure, attribute, types_de_champ) + types_de_champ.filter(&:drop_down_list?).each do |drop_down| validate_drop_down_not_empty(procedure, attribute, drop_down) end end diff --git a/app/validators/revisions/no_empty_repetition_validator.rb b/app/validators/revisions/no_empty_repetition_validator.rb index ba8bf85c7..5bc3de49a 100644 --- a/app/validators/revisions/no_empty_repetition_validator.rb +++ b/app/validators/revisions/no_empty_repetition_validator.rb @@ -1,10 +1,6 @@ class Revisions::NoEmptyRepetitionValidator < ActiveModel::EachValidator - def validate_each(procedure, attribute, revision) - return if revision.nil? - - revision_tdcs = revision.types_de_champ + revision.types_de_champ_private - repetitions = revision_tdcs.filter(&:repetition?) - repetitions.each do |repetition| + def validate_each(procedure, attribute, types_de_champ) + types_de_champ.filter(&:repetition?).each do |repetition| validate_repetition_not_empty(procedure, attribute, repetition) end end diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml index 3c5059cfb..dae702a7e 100644 --- a/app/views/administrateurs/procedures/show.html.haml +++ b/app/views/administrateurs/procedures/show.html.haml @@ -53,7 +53,7 @@ - if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions) - @procedure.validate(:publication) - - error_messages = @procedure.errors.messages_for(:draft_revision).to_sentence + - error_messages = @procedure.errors.messages_for(:draft_types_de_champ).to_sentence = link_to champs_admin_procedure_path(@procedure), class: 'card-admin', title: error_messages do - if @procedure.draft_types_de_champ.count == 0 diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 51c6641fd..4c230ae37 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -328,8 +328,8 @@ fr: connexion: "Erreur lors de la connexion à France Connect." forbidden_html: "Seul-e-s les usagers peuvent se connecter via France Connect. En tant qu’instructeur ou administrateur, nous vous invitons à réininitialiser votre mot de passe." procedure_archived: "Cette démarche en ligne a été close, il n’est plus possible de déposer de dossier." - empty_repetition: 'Le bloc répétable « %{value} » doit comporter au moins un champ' - empty_drop_down: 'La liste de choix « %{value} » doit comporter au moins un choix sélectionnable' + empty_repetition: '« %{value} » doit comporter au moins un champ répétable' + empty_drop_down: '« %{value} » doit comporter au moins un choix sélectionnable' # procedure_not_draft: "Cette démarche n’est maintenant plus en brouillon." cadastres_empty: one: "Aucune parcelle cadastrale sur la zone sélectionnée" diff --git a/config/locales/models/procedure/fr.yml b/config/locales/models/procedure/fr.yml index 332e3114b..054155a50 100644 --- a/config/locales/models/procedure/fr.yml +++ b/config/locales/models/procedure/fr.yml @@ -23,5 +23,5 @@ fr: attributes: api_particulier_token: invalid: 'n’a pas le bon format' - draft_revision: - format: '%{message}' + draft_types_de_champ: + format: 'Le champ %{message}' diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index d940f07a3..7c36a8d40 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -302,17 +302,17 @@ describe Procedure do describe 'draft_revision' do let(:repetition) { build(:type_de_champ_repetition, libelle: 'Enfants') } let(:text_field) { build(:type_de_champ_text) } - let(:invalid_repetition_error_message) { 'Le bloc répétable « Enfants » doit comporter au moins un champ' } + let(:invalid_repetition_error_message) { 'Le champ « Enfants » doit comporter au moins un champ répétable' } let(:drop_down) { build(:type_de_champ_drop_down_list, :without_selectable_values, libelle: 'Civilité') } - let(:invalid_drop_down_error_message) { 'La liste de choix « Civilité » doit comporter au moins un choix sélectionnable' } + let(:invalid_drop_down_error_message) { 'Le champ « Civilité » doit comporter au moins un choix sélectionnable' } let(:procedure) { create(:procedure, types_de_champ: [repetition, drop_down]) } context 'on a draft procedure' do it 'doesn’t validate the draft revision' do procedure.validate - expect(procedure.errors[:draft_revision]).not_to be_present + expect(procedure.errors[:draft_types_de_champ]).not_to be_present end end @@ -321,35 +321,35 @@ describe Procedure do it 'validates that no repetition type de champ is empty' do procedure.validate - expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_repetition_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message) text_field.revision = repetition.revision text_field.order_place = repetition.types_de_champ.size - procedure.draft_revision.types_de_champ.find(&:repetition?).types_de_champ << text_field + procedure.draft_types_de_champ.find(&:repetition?).types_de_champ << text_field procedure.validate - expect(procedure.errors.full_messages_for(:draft_revision)).not_to include(invalid_repetition_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_repetition_error_message) end it 'validates that no drop-down type de champ is empty' do procedure.validate - expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_drop_down_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message) drop_down.update!(drop_down_list_value: "--title--\r\nsome value") procedure.reload.validate - expect(procedure.errors.full_messages_for(:draft_revision)).not_to include(invalid_drop_down_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_drop_down_error_message) end end context 'when validating for publication' do it 'validates that no repetition type de champ is empty' do procedure.validate(:publication) - expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_repetition_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message) end it 'validates that no drop-down type de champ is empty' do procedure.validate(:publication) - expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_drop_down_error_message) + expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message) end end end diff --git a/spec/system/administrateurs/procedure_publish_spec.rb b/spec/system/administrateurs/procedure_publish_spec.rb index dc8f6c14a..e27c4cf78 100644 --- a/spec/system/administrateurs/procedure_publish_spec.rb +++ b/spec/system/administrateurs/procedure_publish_spec.rb @@ -47,7 +47,7 @@ describe 'Publishing a procedure', js: true do end context 'when the procedure has invalid champs' do - let(:empty_repetition) { build(:type_de_champ_repetition, types_de_champ: []) } + let(:empty_repetition) { build(:type_de_champ_repetition, types_de_champ: [], libelle: 'Enfants') } let!(:procedure) do create(:procedure, :with_path, @@ -59,7 +59,7 @@ describe 'Publishing a procedure', js: true do scenario 'an error message prevents the publication' do expect(page).to have_content('Des problèmes empêchent la publication de la démarche') - expect(page).to have_content("Le bloc répétable « #{empty_repetition.libelle} » doit comporter au moins un champ") + expect(page).to have_content("Le champ « Enfants » doit comporter au moins un champ répétable") expect(find_field('procedure_path').value).to eq procedure.path fill_in 'lien_site_web', with: 'http://some.website' From d680602c846484e0cbb105854d249e98f6eea3e7 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 26 Jan 2022 17:52:54 +0100 Subject: [PATCH 10/11] models: validate private types_de_champ --- app/models/procedure.rb | 4 ++++ .../administrateurs/procedures/show.html.haml | 9 ++++++++- config/locales/models/procedure/fr.yml | 2 ++ spec/models/procedure_spec.rb | 17 +++++++++++++++++ .../administrateurs/procedure_publish_spec.rb | 6 +++++- 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 9e7121655..e23adeb0c 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -249,6 +249,10 @@ class Procedure < ApplicationRecord 'revisions/no_empty_repetition': true, 'revisions/no_empty_drop_down': true, if: :validate_for_publication? + validates :draft_types_de_champ_private, + 'revisions/no_empty_repetition': true, + 'revisions/no_empty_drop_down': true, + if: :validate_for_publication? validate :check_juridique validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION } diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml index dae702a7e..c0985cf4e 100644 --- a/app/views/administrateurs/procedures/show.html.haml +++ b/app/views/administrateurs/procedures/show.html.haml @@ -171,11 +171,18 @@ %p.button Modifier - if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions) - = link_to annotations_admin_procedure_path(@procedure), class: 'card-admin' do + - @procedure.validate(:publication) + - error_messages = @procedure.errors.messages_for(:draft_types_de_champ_private).to_sentence + + = link_to annotations_admin_procedure_path(@procedure), class: 'card-admin', title: error_messages do - if @procedure.draft_types_de_champ_private.present? %div %span.icon.accept %p.card-admin-status-accept Validé + - elsif error_messages.present? + %div + %span.icon.refuse + %p.card-admin-status-error À modifier - else %div %span.icon.clock diff --git a/config/locales/models/procedure/fr.yml b/config/locales/models/procedure/fr.yml index 054155a50..752936742 100644 --- a/config/locales/models/procedure/fr.yml +++ b/config/locales/models/procedure/fr.yml @@ -25,3 +25,5 @@ fr: invalid: 'n’a pas le bon format' draft_types_de_champ: format: 'Le champ %{message}' + draft_types_de_champ_private: + format: 'L’annotation privée %{message}' diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 7c36a8d40..7e208bb62 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -352,6 +352,23 @@ describe Procedure do expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message) end end + + context 'when the champ is private' do + let(:procedure) { create(:procedure, types_de_champ_private: [repetition, drop_down]) } + + let(:invalid_repetition_error_message) { 'L’annotation privée « Enfants » doit comporter au moins un champ répétable' } + let(:invalid_drop_down_error_message) { 'L’annotation privée « Civilité » doit comporter au moins un choix sélectionnable' } + + it 'validates that no repetition type de champ is empty' do + procedure.validate(:publication) + expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to include(invalid_repetition_error_message) + end + + it 'validates that no drop-down type de champ is empty' do + procedure.validate(:publication) + expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to include(invalid_drop_down_error_message) + end + end end end diff --git a/spec/system/administrateurs/procedure_publish_spec.rb b/spec/system/administrateurs/procedure_publish_spec.rb index e27c4cf78..21e6192c2 100644 --- a/spec/system/administrateurs/procedure_publish_spec.rb +++ b/spec/system/administrateurs/procedure_publish_spec.rb @@ -48,18 +48,22 @@ describe 'Publishing a procedure', js: true do context 'when the procedure has invalid champs' do let(:empty_repetition) { build(:type_de_champ_repetition, types_de_champ: [], libelle: 'Enfants') } + let(:empty_drop_down) { build(:type_de_champ_drop_down_list, :without_selectable_values, libelle: 'Civilité') } + let!(:procedure) do create(:procedure, :with_path, :with_service, instructeurs: instructeurs, administrateur: administrateur, - types_de_champ: [empty_repetition]) + types_de_champ: [empty_repetition], + types_de_champ_private: [empty_drop_down]) end scenario 'an error message prevents the publication' do expect(page).to have_content('Des problèmes empêchent la publication de la démarche') expect(page).to have_content("Le champ « Enfants » doit comporter au moins un champ répétable") + expect(page).to have_content("L’annotation privée « Civilité » doit comporter au moins un choix sélectionnable") expect(find_field('procedure_path').value).to eq procedure.path fill_in 'lien_site_web', with: 'http://some.website' From ca58e60dc0f48988b1bb116876de9361dc923ddf Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 26 Jan 2022 18:06:39 +0100 Subject: [PATCH 11/11] validators: rename `revisions` namespace to `types_de_champ` --- app/models/procedure.rb | 8 ++++---- .../no_empty_drop_down_validator.rb | 2 +- .../no_empty_repetition_validator.rb | 2 +- spec/models/procedure_spec.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename app/validators/{revisions => types_de_champ}/no_empty_drop_down_validator.rb (87%) rename app/validators/{revisions => types_de_champ}/no_empty_repetition_validator.rb (86%) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index e23adeb0c..dec7d8a27 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -246,12 +246,12 @@ class Procedure < ApplicationRecord validates :administrateurs, presence: true validates :lien_site_web, presence: true, if: :publiee? validates :draft_types_de_champ, - 'revisions/no_empty_repetition': true, - 'revisions/no_empty_drop_down': true, + 'types_de_champ/no_empty_repetition': true, + 'types_de_champ/no_empty_drop_down': true, if: :validate_for_publication? validates :draft_types_de_champ_private, - 'revisions/no_empty_repetition': true, - 'revisions/no_empty_drop_down': true, + 'types_de_champ/no_empty_repetition': true, + 'types_de_champ/no_empty_drop_down': true, if: :validate_for_publication? validate :check_juridique validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } diff --git a/app/validators/revisions/no_empty_drop_down_validator.rb b/app/validators/types_de_champ/no_empty_drop_down_validator.rb similarity index 87% rename from app/validators/revisions/no_empty_drop_down_validator.rb rename to app/validators/types_de_champ/no_empty_drop_down_validator.rb index 4917a2006..bd8fa21dd 100644 --- a/app/validators/revisions/no_empty_drop_down_validator.rb +++ b/app/validators/types_de_champ/no_empty_drop_down_validator.rb @@ -1,4 +1,4 @@ -class Revisions::NoEmptyDropDownValidator < ActiveModel::EachValidator +class TypesDeChamp::NoEmptyDropDownValidator < ActiveModel::EachValidator def validate_each(procedure, attribute, types_de_champ) types_de_champ.filter(&:drop_down_list?).each do |drop_down| validate_drop_down_not_empty(procedure, attribute, drop_down) diff --git a/app/validators/revisions/no_empty_repetition_validator.rb b/app/validators/types_de_champ/no_empty_repetition_validator.rb similarity index 86% rename from app/validators/revisions/no_empty_repetition_validator.rb rename to app/validators/types_de_champ/no_empty_repetition_validator.rb index 5bc3de49a..450d4c661 100644 --- a/app/validators/revisions/no_empty_repetition_validator.rb +++ b/app/validators/types_de_champ/no_empty_repetition_validator.rb @@ -1,4 +1,4 @@ -class Revisions::NoEmptyRepetitionValidator < ActiveModel::EachValidator +class TypesDeChamp::NoEmptyRepetitionValidator < ActiveModel::EachValidator def validate_each(procedure, attribute, types_de_champ) types_de_champ.filter(&:repetition?).each do |repetition| validate_repetition_not_empty(procedure, attribute, repetition) diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 7e208bb62..82ff19c73 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -299,7 +299,7 @@ describe Procedure do it_behaves_like 'duree de conservation' end - describe 'draft_revision' do + describe 'draft_types_de_champ validations' do let(:repetition) { build(:type_de_champ_repetition, libelle: 'Enfants') } let(:text_field) { build(:type_de_champ_text) } let(:invalid_repetition_error_message) { 'Le champ « Enfants » doit comporter au moins un champ répétable' } @@ -310,7 +310,7 @@ describe Procedure do let(:procedure) { create(:procedure, types_de_champ: [repetition, drop_down]) } context 'on a draft procedure' do - it 'doesn’t validate the draft revision' do + it 'doesn’t validate the types de champs' do procedure.validate expect(procedure.errors[:draft_types_de_champ]).not_to be_present end