From 3a9f82bd6fc33e23061ed50b626fd69bcc7c5b22 Mon Sep 17 00:00:00 2001 From: Fabrice Gangler Date: Fri, 11 Dec 2020 01:32:23 +0100 Subject: [PATCH 01/17] Fix(form /contact-admin): use APPLICATION_NAME Refs: #5799 --- app/lib/helpscout/form_adapter.rb | 6 +++--- config/locales/views/support/index.en.yml | 6 +++--- config/locales/views/support/index.fr.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/lib/helpscout/form_adapter.rb b/app/lib/helpscout/form_adapter.rb index 6e4dcdaac..4b073953c 100644 --- a/app/lib/helpscout/form_adapter.rb +++ b/app/lib/helpscout/form_adapter.rb @@ -13,9 +13,9 @@ class Helpscout::FormAdapter def self.admin_options [ - [I18n.t(ADMIN_TYPE_QUESTION, scope: [:supportadmin]), ADMIN_TYPE_QUESTION], - [I18n.t(ADMIN_TYPE_RDV, scope: [:supportadmin]), ADMIN_TYPE_RDV], - [I18n.t(ADMIN_TYPE_SOUCIS, scope: [:supportadmin]), ADMIN_TYPE_SOUCIS], + [I18n.t(ADMIN_TYPE_QUESTION, scope: [:supportadmin], app_name: APPLICATION_NAME), ADMIN_TYPE_QUESTION], + [I18n.t(ADMIN_TYPE_RDV, scope: [:supportadmin], app_name: APPLICATION_NAME), ADMIN_TYPE_RDV], + [I18n.t(ADMIN_TYPE_SOUCIS, scope: [:supportadmin], app_name: APPLICATION_NAME), ADMIN_TYPE_SOUCIS], [I18n.t(ADMIN_TYPE_PRODUIT, scope: [:supportadmin]), ADMIN_TYPE_PRODUIT], [I18n.t(ADMIN_TYPE_DEMANDE_COMPTE, scope: [:supportadmin]), ADMIN_TYPE_DEMANDE_COMPTE], [I18n.t(ADMIN_TYPE_AUTRE, scope: [:supportadmin]), ADMIN_TYPE_AUTRE] diff --git a/config/locales/views/support/index.en.yml b/config/locales/views/support/index.en.yml index f804c581c..405ea15ce 100644 --- a/config/locales/views/support/index.en.yml +++ b/config/locales/views/support/index.en.yml @@ -38,9 +38,9 @@ en: contact_team: Contact our team pro_phone_number: Professional phone number (direct line) pro_mail: Professional email address - admin demande rdv: I request an appointment for an online presentation of demarches-simplifiees.fr - admin question: I have a question about demarches-simplifiees.fr - admin soucis: I am facing a technical issue on demarches-simplifiees.fr + admin demande rdv: I request an appointment for an online presentation of %{app_name} + admin question: I have a question about %{app_name} + admin soucis: I am facing a technical issue on %{app_name} admin suggestion produit: I have a suggestion for an evolution admin demande compte: I want to open an admin account with an Orange, Wanadoo, etc. email admin autre: Other topic diff --git a/config/locales/views/support/index.fr.yml b/config/locales/views/support/index.fr.yml index 0043277ca..e1dfd67ff 100644 --- a/config/locales/views/support/index.fr.yml +++ b/config/locales/views/support/index.fr.yml @@ -38,9 +38,9 @@ fr: contact_team: Contactez notre équipe pro_phone_number: Numéro de téléphone professionnel (ligne directe) pro_mail: Adresse e-mail professionnelle - admin demande rdv: Demande de RDV pour une présentation à distance de demarches-simplifiees.fr - admin question: J’ai une question sur demarches-simplifiees.fr - admin soucis: J’ai un problème technique avec demarches-simplifiees.fr + admin demande rdv: Demande de RDV pour une présentation à distance de %{app_name} + admin question: J’ai une question sur %{app_name} + admin soucis: J’ai un problème technique avec %{app_name} admin suggestion produit: J’ai une proposition d’évolution admin demande compte: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc. admin autre: Autre sujet From 94b3ec942be0b883de995312e9a85d0efd3356ed Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 17 Nov 2020 16:20:27 +0100 Subject: [PATCH 02/17] super_admin: check password complexity client side --- .../super_admins/passwords_controller.rb | 13 +++++++++++++ app/views/super_admins/passwords/edit.html.haml | 7 +++---- config/routes.rb | 4 ++++ .../super_admins/passwords_controller_spec.rb | 12 ++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 spec/controllers/super_admins/passwords_controller_spec.rb diff --git a/app/controllers/super_admins/passwords_controller.rb b/app/controllers/super_admins/passwords_controller.rb index acfcf6157..69c8de2d3 100644 --- a/app/controllers/super_admins/passwords_controller.rb +++ b/app/controllers/super_admins/passwords_controller.rb @@ -3,4 +3,17 @@ class SuperAdmins::PasswordsController < Devise::PasswordsController super self.resource.disable_otp! end + + def test_strength + @score, @words, @length = ZxcvbnService.new(password_params[:password]).complexity + @min_length = PASSWORD_MIN_LENGTH + @min_complexity = PASSWORD_COMPLEXITY_FOR_ADMIN + render 'shared/password/test_strength' + end + + private + + def password_params + params.require(:super_admin).permit(:password) + end end diff --git a/app/views/super_admins/passwords/edit.html.haml b/app/views/super_admins/passwords/edit.html.haml index 33b8d466b..d14e4ca7b 100644 --- a/app/views/super_admins/passwords/edit.html.haml +++ b/app/views/super_admins/passwords/edit.html.haml @@ -14,9 +14,8 @@ = f.hidden_field :reset_password_token = f.label 'Nouveau mot de passe' - = f.password_field :password, autofocus: true, autocomplete: 'off' - = f.label 'Confirmez le nouveau mot de passe' - = f.password_field :password_confirmation, autocomplete: 'off' + = render partial: 'shared/password/edit_password', locals: { form: f, controller: 'super_admins/passwords' } - = f.submit 'Changer le mot de passe', class: 'button primary' + + = f.submit 'Changer le mot de passe', class: 'button large primary expand', id: "submit-password", data: { disable_with: "Envoi..." } diff --git a/config/routes.rb b/config/routes.rb index bd385df4f..54b5f82fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -84,6 +84,10 @@ Rails.application.routes.draw do passwords: 'super_admins/passwords' } + devise_scope :super_admin do + get '/super_admins/password/test_strength' => 'super_admins/passwords#test_strength' + end + get 'super_admins/edit_otp', to: 'super_admins#edit_otp', as: 'edit_super_admin_otp' put 'super_admins/enable_otp', to: 'super_admins#enable_otp', as: 'enable_super_admin_otp' diff --git a/spec/controllers/super_admins/passwords_controller_spec.rb b/spec/controllers/super_admins/passwords_controller_spec.rb new file mode 100644 index 000000000..d2e7c2b08 --- /dev/null +++ b/spec/controllers/super_admins/passwords_controller_spec.rb @@ -0,0 +1,12 @@ +describe SuperAdmins::PasswordsController, type: :controller do + describe '#test_strength' do + it 'calculate score' do + password = "bonjour" + @request.env["devise.mapping"] = Devise.mappings[:super_admin] + + get 'test_strength', xhr: true, params: { super_admin: { password: password } } + + expect(assigns(:score)).to be_present + end + end +end From 15a395fbc7406891cb8c8712381f06d99544eb6a Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 17 Nov 2020 17:11:04 +0100 Subject: [PATCH 03/17] superadmin: check complexity password server side --- app/models/super_admin.rb | 8 +++++++ config/locales/fr.yml | 14 +++++++++--- spec/factories/super_admin.rb | 2 +- spec/models/super_admin_spec.rb | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app/models/super_admin.rb b/app/models/super_admin.rb index a10c45052..d9029cbc4 100644 --- a/app/models/super_admin.rb +++ b/app/models/super_admin.rb @@ -28,6 +28,14 @@ class SuperAdmin < ApplicationRecord devise :rememberable, :trackable, :validatable, :lockable, :async, :recoverable, :two_factor_authenticatable, :otp_secret_encryption_key => Rails.application.secrets.otp_secret_key + validate :password_complexity, if: -> (u) { Devise.password_length.include?(u.password.try(:size)) } + + def password_complexity + if password.present? && ZxcvbnService.new(password).score < PASSWORD_COMPLEXITY_FOR_ADMIN + errors.add(:password, :not_strong) + end + end + def enable_otp! self.otp_secret = SuperAdmin.generate_otp_secret self.otp_required_for_login = true diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2b2a198f2..551f43688 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -60,11 +60,15 @@ fr: activerecord: attributes: + default_attributes: &default_attributes + password: 'Le mot de passe' user: siret: 'Numéro SIRET' - password: 'Le mot de passe' + << : *default_attributes instructeur: - password: 'Le mot de passe' + << : *default_attributes + super_admin: + << : *default_attributes errors: messages: not_a_phone: 'Numéro de téléphone invalide' @@ -80,7 +84,7 @@ fr: email: invalid: invalide taken: déjà utilisé - password: + password: &password too_short: 'est trop court' not_strong: 'n’est pas assez complexe' password_confirmation: @@ -96,6 +100,10 @@ fr: taken: déjà utilisé password: too_short: 'est trop court' + super_admin: + attributes: + password: + << : *password procedure: attributes: path: diff --git a/spec/factories/super_admin.rb b/spec/factories/super_admin.rb index 0a4f5472e..65c0d265b 100644 --- a/spec/factories/super_admin.rb +++ b/spec/factories/super_admin.rb @@ -2,7 +2,7 @@ FactoryBot.define do sequence(:super_admin_email) { |n| "plop#{n}@plop.com" } factory :super_admin do email { generate(:super_admin_email) } - password { 'my-s3cure-p4ssword' } + password { '{My-$3cure-p4ssWord}' } otp_required_for_login { true } end end diff --git a/spec/models/super_admin_spec.rb b/spec/models/super_admin_spec.rb index 606b6078a..19cf1f8d3 100644 --- a/spec/models/super_admin_spec.rb +++ b/spec/models/super_admin_spec.rb @@ -61,4 +61,43 @@ describe SuperAdmin, type: :model do expect { subject }.to change { super_admin.reload.otp_secret }.to(nil) end end + + describe '#password_complexity' do + # This password list is sorted by password complexity, according to zxcvbn (used for complexity evaluation) + # 0 - too guessable: risky password. (guesses < 10^3) + # 1 - very guessable: protection from throttled online attacks. (guesses < 10^6) + # 2 - somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8) + # 3 - safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10) + # 4 - very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10) + passwords = ['pass', '12pass23', 'démarches ', 'démarches-simple', '{My-$3cure-p4ssWord}'] + min_complexity = PASSWORD_COMPLEXITY_FOR_ADMIN + + let(:email) { 'mail@beta.gouv.fr' } + let(:super_admin) { build(:super_admin, email: email, password: password) } + + subject do + super_admin.save + super_admin.errors.full_messages + end + + context 'when password is too short' do + let(:password) { 's' * (PASSWORD_MIN_LENGTH - 1) } + + it { expect(subject).to eq(["Le mot de passe est trop court"]) } + end + + context 'when password is too simple' do + passwords[0..(min_complexity - 1)].each do |password| + let(:password) { password } + + it { expect(subject).to eq(["Le mot de passe n’est pas assez complexe"]) } + end + end + + context 'when password is acceptable' do + let(:password) { passwords[min_complexity] } + + it { expect(subject).to eq([]) } + end + end end From 3428c58b9e2fb2a58366785ad2dc273f8b50192e Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 17 Nov 2020 17:13:31 +0100 Subject: [PATCH 04/17] extract password complexity validator for user and superadmin --- app/models/super_admin.rb | 8 +------- app/models/user.rb | 8 +------- app/validators/password_complexity_validator.rb | 7 +++++++ 3 files changed, 9 insertions(+), 14 deletions(-) create mode 100644 app/validators/password_complexity_validator.rb diff --git a/app/models/super_admin.rb b/app/models/super_admin.rb index d9029cbc4..eeb69a255 100644 --- a/app/models/super_admin.rb +++ b/app/models/super_admin.rb @@ -28,13 +28,7 @@ class SuperAdmin < ApplicationRecord devise :rememberable, :trackable, :validatable, :lockable, :async, :recoverable, :two_factor_authenticatable, :otp_secret_encryption_key => Rails.application.secrets.otp_secret_key - validate :password_complexity, if: -> (u) { Devise.password_length.include?(u.password.try(:size)) } - - def password_complexity - if password.present? && ZxcvbnService.new(password).score < PASSWORD_COMPLEXITY_FOR_ADMIN - errors.add(:password, :not_strong) - end - end + validates :password, password_complexity: true, if: -> (u) { Devise.password_length.include?(u.password.try(:size)) } def enable_otp! self.otp_secret = SuperAdmin.generate_otp_secret diff --git a/app/models/user.rb b/app/models/user.rb index 1582b6a6c..9ed96c681 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -54,13 +54,7 @@ class User < ApplicationRecord before_validation -> { sanitize_email(:email) } - validate :password_complexity, if: -> (u) { u.administrateur.present? && Devise.password_length.include?(u.password.try(:size)) } - - def password_complexity - if password.present? && ZxcvbnService.new(password).score < PASSWORD_COMPLEXITY_FOR_ADMIN - errors.add(:password, :not_strong) - end - end + validates :password, password_complexity: true, if: -> (u) { u.administrateur.present? && Devise.password_length.include?(u.password.try(:size)) } # Override of Devise::Models::Confirmable#send_confirmation_instructions def send_confirmation_instructions diff --git a/app/validators/password_complexity_validator.rb b/app/validators/password_complexity_validator.rb new file mode 100644 index 000000000..a915a8575 --- /dev/null +++ b/app/validators/password_complexity_validator.rb @@ -0,0 +1,7 @@ +class PasswordComplexityValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if value.present? && ZxcvbnService.new(value).score < PASSWORD_COMPLEXITY_FOR_ADMIN + record.errors.add(attribute, :not_strong) + end + end +end From a8081d4b83f673d90c0ee6ab17aa5906e93f6525 Mon Sep 17 00:00:00 2001 From: Fabrice Gangler Date: Fri, 11 Dec 2020 03:38:50 +0100 Subject: [PATCH 05/17] allow configuration of the URLs of FAQ and documentation websites in an .env file Refs: #5801 --- config/env.example.optional | 6 ++++++ config/initializers/urls.rb | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config/env.example.optional b/config/env.example.optional index 7587634fb..7707b8501 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -24,6 +24,12 @@ APPLICATION_BASE_URL="https://www.demarches-simplifiees.fr" # Personnalisation d'instance - URL pour la création de compte administrateur sur l'instance # DEMANDE_INSCRIPTION_ADMIN_PAGE_URL="" +# Personnalisation d'instance - URL du site web de documentation +# DOC_URL="https://doc.demarches-simplifiees.fr" + +# Personnalisation d'instance - URL du site web FAQ +# FAQ_URL="https://faq.demarches-simplifiees.fr" + # Personnalisation d'instance - Page externe "Disponibilité" (status page) # STATUS_PAGE_URL="" diff --git a/config/initializers/urls.rb b/config/initializers/urls.rb index 82ff12802..84053627e 100644 --- a/config/initializers/urls.rb +++ b/config/initializers/urls.rb @@ -16,7 +16,7 @@ FOG_BASE_URL = "https://static.demarches-simplifiees.fr" WEBINAIRE_URL = "https://app.livestorm.co/demarches-simplifiees" CALENDLY_URL = "https://calendly.com/demarches-simplifiees/accompagnement-administrateur-demarches-simplifiees-fr" -DOC_URL = "https://doc.demarches-simplifiees.fr" +DOC_URL = ENV.fetch("DOC_URL", "https://doc.demarches-simplifiees.fr") DOC_NOUVEAUTES_URL = [DOC_URL, "nouveautes"].join("/") ADMINISTRATEUR_TUTORIAL_URL = [DOC_URL, "tutoriels", "tutoriel-administrateur"].join("/") INSTRUCTEUR_TUTORIAL_URL = [DOC_URL, "tutoriels", "tutoriel-accompagnateur"].join("/") @@ -29,7 +29,7 @@ WEBHOOK_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "webhook"].join("/") ARCHIVAGE_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "archivage-longue-duree-des-demarches"].join("/") DOC_INTEGRATION_MONAVIS_URL = [DOC_URL, "tutoriels", "integration-du-bouton-mon-avis"].join("/") -FAQ_URL = "https://faq.demarches-simplifiees.fr" +FAQ_URL = ENV.fetch("FAQ_URL", "https://faq.demarches-simplifiees.fr") FAQ_ADMIN_URL = [FAQ_URL, "collection", "1-administrateur-creation-dun-formulaire"].join("/") FAQ_AUTOSAVE_URL = [FAQ_URL, "article", "77-enregistrer-mon-formulaire-pour-le-reprendre-plus-tard?preview=5ec28ca1042863474d1aee00"].join("/") COMMENT_TROUVER_MA_DEMARCHE_URL = [FAQ_URL, "article", "59-comment-trouver-ma-demarche"].join("/") From 43976550cf4191b0afa98d22e09ce4d036228123 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 15 Dec 2020 17:57:37 +0100 Subject: [PATCH 06/17] GraphQL: make parts of address nullable --- app/graphql/schema.graphql | 4 ++-- app/graphql/types/personne_morale_type.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 0fe8b1878..81b5acf78 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -1055,13 +1055,13 @@ type PersonneMorale implements Demandeur { association: Association codeInseeLocalite: String! codePostal: String! - complementAdresse: String! + complementAdresse: String entreprise: Entreprise id: ID! libelleNaf: String! localite: String! naf: String! - nomVoie: String! + nomVoie: String numeroVoie: String siegeSocial: Boolean! siret: String! diff --git a/app/graphql/types/personne_morale_type.rb b/app/graphql/types/personne_morale_type.rb index a0216046b..2a74a0cea 100644 --- a/app/graphql/types/personne_morale_type.rb +++ b/app/graphql/types/personne_morale_type.rb @@ -89,8 +89,8 @@ module Types field :adresse, String, null: false field :numero_voie, String, null: true field :type_voie, String, null: true - field :nom_voie, String, null: false - field :complement_adresse, String, null: false + field :nom_voie, String, null: true + field :complement_adresse, String, null: true field :code_postal, String, null: false field :localite, String, null: false field :code_insee_localite, String, null: false From 00b5ad7a103f6de1cf3fa927b339ef7b7296c020 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 17 Nov 2020 13:25:35 +0100 Subject: [PATCH 07/17] =?UTF-8?q?EQT=20instructeur,=20je=20peux=20supprime?= =?UTF-8?q?r=20un=20dossier=20termin=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instructeurs/dossiers_controller.rb | 11 +++ app/controllers/users/dossiers_controller.rb | 25 ++----- app/mailers/dossier_mailer.rb | 14 ++++ app/models/deleted_dossier.rb | 5 +- app/models/dossier.rb | 14 ++++ app/models/dossier_operation_log.rb | 13 +++- ...ify_instructeur_deletion_to_user.html.haml | 9 +++ .../dossiers/_state_button.html.haml | 8 ++ .../users/dossiers/_dossiers_list.html.haml | 67 +++++++++++++++++ app/views/users/dossiers/index.html.haml | 73 +++++-------------- config/locales/models/deleted_dossier/fr.yml | 1 + .../notify_deletion_to_user/fr.yml | 3 + config/routes.rb | 1 + .../instructeurs/dossiers_controller_spec.rb | 65 +++++++++++++++++ .../users/dossiers_controller_spec.rb | 23 +++--- .../previews/dossier_mailer_preview.rb | 4 + .../dossiers/_state_button.html.haml_spec.rb | 2 +- .../users/dossiers/index.html.haml_spec.rb | 14 ++-- 18 files changed, 255 insertions(+), 97 deletions(-) create mode 100644 app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml create mode 100644 app/views/users/dossiers/_dossiers_list.html.haml diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 85bb998bf..c8d60dcbb 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -214,6 +214,17 @@ module Instructeurs zipline(files, "dossier-#{dossier.id}.zip") end + def delete_dossier + if dossier.termine? + dossier.deleted_by_instructeur_and_keep_track!(current_instructeur) + flash.notice = 'Le dossier a bien été supprimé' + redirect_to instructeur_procedure_path(procedure) + else + flash.alert = "Suppression impossible : le dossier n'est pas terminé" + redirect_back(fallback_location: instructeur_procedures_url) + end + end + private def dossier diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 362657b35..5cd6732b1 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -14,19 +14,12 @@ module Users before_action :forbid_closed_submission!, only: [:update_brouillon] before_action :show_demarche_en_test_banner before_action :store_user_location!, only: :new + before_action :statut, only: :index def index @user_dossiers = current_user.dossiers.includes(:procedure).order_by_updated_at.page(page) @dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page) - - @current_tab = current_tab(@user_dossiers.count, @dossiers_invites.count) - - @dossiers = case @current_tab - when 'mes-dossiers' - @user_dossiers - when 'dossiers-invites' - @dossiers_invites - end + @dossiers_supprimes = DeletedDossier.where(user_id: current_user.id).order_by_updated_at.page(page) end def show @@ -282,6 +275,10 @@ module Users private + def statut + @statut = params[:statut].blank? ? 'mes-dossiers' : params[:statut] + end + def store_user_location! store_location_for(:user, request.fullpath) end @@ -307,16 +304,6 @@ module Users [params[:page].to_i, 1].max end - def current_tab(mes_dossiers_count, dossiers_invites_count) - if dossiers_invites_count == 0 - 'mes-dossiers' - elsif mes_dossiers_count == 0 - 'dossiers-invites' - else - params[:current_tab].presence || 'mes-dossiers' - end - end - # FIXME: require(:dossier) when all the champs are united def champs_params params.permit(dossier: { diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index c650c0a8f..51e80f443 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -75,6 +75,20 @@ class DossierMailer < ApplicationMailer mail(to: to_email, subject: @subject) end + def notify_instructeur_deletion_to_user(deleted_dossier, to_email) + @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) + @deleted_dossier = deleted_dossier + + mail(to: to_email, subject: @subject) + end + + def notify_instructeur(deleted_dossier, to_email) + @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) + @deleted_dossier = deleted_dossier + + mail(to: to_email, subject: @subject) + end + def notify_deletion_to_administration(deleted_dossier, to_email) @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) @deleted_dossier = deleted_dossier diff --git a/app/models/deleted_dossier.rb b/app/models/deleted_dossier.rb index 98f69d8a2..1d465fef9 100644 --- a/app/models/deleted_dossier.rb +++ b/app/models/deleted_dossier.rb @@ -19,12 +19,15 @@ class DeletedDossier < ApplicationRecord validates :dossier_id, uniqueness: true + scope :order_by_updated_at, -> (order = :desc) { order(created_at: order) } + enum reason: { user_request: 'user_request', manager_request: 'manager_request', user_removed: 'user_removed', procedure_removed: 'procedure_removed', - expired: 'expired' + expired: 'expired', + instructeur_request: 'instructeur_request' } def self.create_from_dossier(dossier, reason) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 3766f0cf1..d6fc4ee70 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -520,6 +520,16 @@ class Dossier < ApplicationRecord end end + def deleted_by_instructeur_and_keep_track!(author) + if keep_track_on_deletion? + deleted_dossier = DeletedDossier.create_from_dossier(self, :instructeur_request) + self.delete_operations_logs + log_dossier_operation(author, :supprime_par_instructeur, self) + DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, user.email).deliver_later + self.destroy + end + end + def discard_and_keep_track!(author, reason) if keep_track_on_deletion? && en_construction? deleted_dossier = DeletedDossier.create_from_dossier(self, reason) @@ -797,6 +807,10 @@ class Dossier < ApplicationRecord private + def delete_operations_logs + DossierOperationLog.where(dossier: self).destroy_all + end + def geo_areas champs.includes(:geo_areas).flat_map(&:geo_areas) + champs_private.includes(:geo_areas).flat_map(&:geo_areas) end diff --git a/app/models/dossier_operation_log.rb b/app/models/dossier_operation_log.rb index f52254f31..21697a945 100644 --- a/app/models/dossier_operation_log.rb +++ b/app/models/dossier_operation_log.rb @@ -28,7 +28,8 @@ class DossierOperationLog < ApplicationRecord modifier_annotation: 'modifier_annotation', demander_un_avis: 'demander_un_avis', archiver: 'archiver', - desarchiver: 'desarchiver' + desarchiver: 'desarchiver', + supprime_par_instructeur: 'supprime_par_instructeur' } has_one_attached :serialized @@ -58,7 +59,7 @@ class DossierOperationLog < ApplicationRecord operation: operation_log.operation, dossier_id: operation_log.dossier_id, author: self.serialize_author(params[:author]), - subject: self.serialize_subject(params[:subject]), + subject: self.serialize_subject(params[:subject], operation_log.operation), automatic_operation: operation_log.automatic_operation?, executed_at: operation_log.executed_at.iso8601 }.compact.to_json @@ -84,9 +85,15 @@ class DossierOperationLog < ApplicationRecord end end - def self.serialize_subject(subject) + def self.serialize_subject(subject, operation = nil) if subject.nil? nil + elsif operation == "supprime_par_instructeur" + { + date_de_depot: subject.en_construction_at, + date_de_mise_en_instruction: subject.en_instruction_at, + date_de_decision: subject.traitements.last.processed_at + }.as_json else case subject when Dossier diff --git a/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml new file mode 100644 index 000000000..e64cab4b6 --- /dev/null +++ b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml @@ -0,0 +1,9 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p + = t('.body', dossier_id: @deleted_dossier.dossier_id, procedure: @deleted_dossier.procedure.libelle) + += render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/dossiers/_state_button.html.haml b/app/views/instructeurs/dossiers/_state_button.html.haml index c6b58fcae..61dd4efd7 100644 --- a/app/views/instructeurs/dossiers/_state_button.html.haml +++ b/app/views/instructeurs/dossiers/_state_button.html.haml @@ -105,3 +105,11 @@ .dropdown-description %h4 Repasser en instruction L’usager sera notifié que son dossier est réexaminé. + - if dossier.termine? + %li + = link_to supprimer_dossier_instructeur_dossier_path(dossier.procedure, dossier), 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.refuse + .dropdown-description + %h4 Supprimer le dossier + L’usager sera notifié que son dossier sera supprimé. + diff --git a/app/views/users/dossiers/_dossiers_list.html.haml b/app/views/users/dossiers/_dossiers_list.html.haml new file mode 100644 index 000000000..b5d75dffe --- /dev/null +++ b/app/views/users/dossiers/_dossiers_list.html.haml @@ -0,0 +1,67 @@ +- if dossiers.present? + %table.table.dossiers-table.hoverable + %thead + %tr + %th.number-col Nº dossier + %th Démarche + - if dossiers.present? + %th Demandeur + %th.status-col Statut + %th.updated-at-col Mis à jour + %th.sr-only Actions + %tbody + - dossiers.each do |dossier| + -# check if the dossier is deleted or not + - if dossier.try(:deleted_at).nil? + %tr{ data: { 'dossier-id': dossier.id } } + %td.number-col + = link_to(url_for_dossier(dossier), class: 'cell-link', tabindex: -1) do + %span.icon.folder + = dossier.id + %td + = link_to(url_for_dossier(dossier), class: 'cell-link') do + = procedure_libelle(dossier.procedure) + - if dossiers.present? + %td.cell-link + = demandeur_dossier(dossier) + %td.status-col + = status_badge(dossier.state) + %td.updated-at-col.cell-link + = try_format_date(dossier.updated_at) + %td.action-col + = render partial: 'dossier_actions', locals: { dossier: dossier } + - else + %tr{ data: { 'dossier-id': dossier.dossier_id } } + %td.number-col + = dossier.dossier_id + %td + = dossier.procedure_id + + - if dossiers.present? + %td.cell-link + = dossier.reason + %td + = status_badge(dossier.state) + %td + = dossier.updated_at.strftime('%d/%m/%Y') + + = paginate(dossiers) + + - if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago + #user-satisfaction + %h3 Que pensez-vous de la facilité d'utilisation de ce service ? + .icons + = link_to feedback_path(rating: Feedback.ratings.fetch(:unhappy)), data: { remote: true, method: :post } do + %span.icon.frown + = link_to feedback_path(rating: Feedback.ratings.fetch(:neutral)), data: { remote: true, method: :post } do + %span.icon.meh + = link_to feedback_path(rating: Feedback.ratings.fetch(:happy)), data: { remote: true, method: :post } do + %span.icon.smile + +- else + .blank-tab + %h2.empty-text Aucun dossier. + %p.empty-text-details + Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche. + %br + Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx. diff --git a/app/views/users/dossiers/index.html.haml b/app/views/users/dossiers/index.html.haml index a38137a03..9ac567718 100644 --- a/app/views/users/dossiers/index.html.haml +++ b/app/views/users/dossiers/index.html.haml @@ -10,68 +10,33 @@ .container - if @search_terms.present? %h1.page-title Résultat de la recherche pour « #{@search_terms} » - - elsif @dossiers_invites.count == 0 - %h1.page-title Mes dossiers + = render partial: "dossiers_list", locals: { dossiers: @dossiers } - else %h1.page-title Dossiers %ul.tabs = tab_item('mes dossiers', - dossiers_path(current_tab: 'mes-dossiers'), - active: @current_tab == 'mes-dossiers') + dossiers_path(statut: 'mes-dossiers'), + active: @statut == 'mes-dossiers', + badge: number_with_html_delimiter(@user_dossiers.count)) = tab_item('dossiers invités', - dossiers_path(current_tab: 'dossiers-invites'), - active: @current_tab == 'dossiers-invites') + dossiers_path(statut: 'dossiers-invites'), + active: @statut == 'dossiers-invites', + badge: number_with_html_delimiter(@dossiers_invites.count)) + + = tab_item('dossiers supprimés', + dossiers_path(statut: 'dossiers-supprimes'), + active: @statut == 'dossiers-supprimes', + badge: number_with_html_delimiter(@dossiers_supprimes.count)) + .container - - if @dossiers.present? - %table.table.dossiers-table.hoverable - %thead - %tr - %th.number-col Nº dossier - %th Démarche - - if @dossiers.count > 1 - %th Demandeur - %th.status-col Statut - %th.updated-at-col Mis à jour - %th.sr-only Actions - %tbody - - @dossiers.each do |dossier| - %tr{ data: { 'dossier-id': dossier.id } } - %td.number-col - = link_to(url_for_dossier(dossier), class: 'cell-link', tabindex: -1) do - %span.icon.folder - = dossier.id - %td - = link_to(url_for_dossier(dossier), class: 'cell-link') do - = procedure_libelle(dossier.procedure) - - if @dossiers.count > 1 - %td.cell-link - = demandeur_dossier(dossier) - %td.status-col - = status_badge(dossier.state) - %td.updated-at-col.cell-link - = try_format_date(dossier.updated_at) - %td.action-col - = render partial: 'dossier_actions', locals: { dossier: dossier } - = paginate(@dossiers) + - if @statut === "mes-dossiers" + = render partial: "dossiers_list", locals: { dossiers: @user_dossiers } - - if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago - #user-satisfaction - %h3 Que pensez-vous de la facilité d'utilisation de ce service ? - .icons - = link_to feedback_path(rating: Feedback.ratings.fetch(:unhappy)), data: { remote: true, method: :post } do - %span.icon.frown - = link_to feedback_path(rating: Feedback.ratings.fetch(:neutral)), data: { remote: true, method: :post } do - %span.icon.meh - = link_to feedback_path(rating: Feedback.ratings.fetch(:happy)), data: { remote: true, method: :post } do - %span.icon.smile + - if @statut === "dossiers-invites" + = render partial: "dossiers_list", locals: { dossiers: @dossiers_invites } - - else - .blank-tab - %h2.empty-text Aucun dossier. - %p.empty-text-details - Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche. - %br - Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx. + - if @statut === "dossiers-supprimes" + = render partial: "dossiers_list", locals: { dossiers: @dossiers_supprimes } diff --git a/config/locales/models/deleted_dossier/fr.yml b/config/locales/models/deleted_dossier/fr.yml index b4232fba2..87b6836fa 100644 --- a/config/locales/models/deleted_dossier/fr.yml +++ b/config/locales/models/deleted_dossier/fr.yml @@ -9,3 +9,4 @@ fr: procedure_removed: Suppression d’une démarche expired: Expiration unknown: Inconnue + instructeur_request: Suppression par l’instructeur diff --git a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml index 5d8caecf7..6d649575f 100644 --- a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml +++ b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml @@ -3,3 +3,6 @@ fr: notify_deletion_to_user: subject: Votre dossier nº %{dossier_id} a bien été supprimé body: Votre dossier n° %{dossier_id} (%{procedure}) a bien été supprimé. Une trace de ce traitement sera conservée pour l’administration. + notify_instructeur_deletion_to_user: + subject: Votre dossier nº %{dossier_id} a été supprimé par l'administration + body: Nous vous informons que votre dossier n° %{dossier_id} (%{procedure}) a été supprimé par l'administration en charge de la démarche. Une trace de ce traitement sera conservée pour l’administration. diff --git a/config/routes.rb b/config/routes.rb index 54b5f82fe..cfa066dc7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -346,6 +346,7 @@ Rails.application.routes.draw do patch 'unfollow' patch 'archive' patch 'unarchive' + patch 'supprimer-dossier' => 'dossiers#delete_dossier' patch 'annotations' => 'dossiers#update_annotations' post 'commentaire' => 'dossiers#create_commentaire' post 'passer-en-instruction' => 'dossiers#passer_en_instruction' diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 379bb84d9..844a8d40e 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -715,4 +715,69 @@ describe Instructeurs::DossiersController, type: :controller do end end end + + describe "#delete_dossier" do + subject do + patch :delete_dossier, params: { + procedure_id: procedure.id, + dossier_id: dossier.id + } + end + + before do + dossier.passer_en_instruction(instructeur) + end + + context 'just before delete the dossier, the operation must be equal to 2' do + before do + dossier.accepter!(instructeur, 'le dossier est correct') + end + + it 'has 2 operations logs before deletion' do + expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(2) + end + end + + context 'when the instructeur want to delete a dossier with a decision' do + before do + dossier.accepter!(instructeur, "le dossier est correct") + allow(DossierMailer).to receive(:notify_instructeur_deletion_to_user).and_return(double(deliver_later: nil)) + subject + end + + it 'deletes previous logs and add a suppression log' do + expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(1) + expect(DossierOperationLog.where(dossier_id: dossier.id).first.operation).to eq('supprime_par_instructeur') + end + + it 'send an email to the user' do + expect(DossierMailer).to have_received(:notify_instructeur_deletion_to_user).with(DeletedDossier.find(dossier.id), dossier.user.email) + end + + it 'add a record into deleted_dossiers table' do + expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(1) + expect(DeletedDossier.where(dossier_id: dossier.id).first.revision_id).to eq(dossier.revision_id) + expect(DeletedDossier.where(dossier_id: dossier.id).first.user_id).to eq(dossier.user_id) + expect(DeletedDossier.where(dossier_id: dossier.id).first.groupe_instructeur_id).to eq(dossier.groupe_instructeur_id) + end + + it 'delete the dossier' do + expect { dossier.reload }.to raise_error ActiveRecord::RecordNotFound + end + end + + context 'when the instructeur want to delete a dossier without a decision' do + before do + subject + end + + it 'does not delete the dossier' do + expect { dossier.reload }.not_to raise_error ActiveRecord::RecordNotFound + end + + it 'does not add a record into deleted_dossiers table' do + expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) + end + end + end end diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 052b9c96e..58fdeadb5 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -743,16 +743,15 @@ describe Users::DossiersController, type: :controller do context 'when the user does not have any dossiers' do before { get(:index) } - it { expect(assigns(:current_tab)).to eq('mes-dossiers') } + it { expect(assigns(:statut)).to eq('mes-dossiers') } end context 'when the user only have its own dossiers' do let!(:own_dossier) { create(:dossier, user: user) } before { get(:index) } - - it { expect(assigns(:current_tab)).to eq('mes-dossiers') } - it { expect(assigns(:dossiers)).to match([own_dossier]) } + it { expect(assigns(:statut)).to eq('mes-dossiers') } + it { expect(assigns(:user_dossiers)).to match([own_dossier]) } end context 'when the user only have some dossiers invites' do @@ -760,30 +759,30 @@ describe Users::DossiersController, type: :controller do before { get(:index) } - it { expect(assigns(:current_tab)).to eq('dossiers-invites') } - it { expect(assigns(:dossiers)).to match([invite.dossier]) } + it { expect(assigns(:statut)).to eq('mes-dossiers') } + it { expect(assigns(:dossiers_invites)).to match([invite.dossier]) } end context 'when the user has both' do let!(:own_dossier) { create(:dossier, user: user) } let!(:invite) { create(:invite, dossier: create(:dossier), user: user) } - context 'and there is no current_tab param' do + context 'and there is no statut param' do before { get(:index) } - it { expect(assigns(:current_tab)).to eq('mes-dossiers') } + it { expect(assigns(:statut)).to eq('mes-dossiers') } end context 'and there is "dossiers-invites" param' do - before { get(:index, params: { current_tab: 'dossiers-invites' }) } + before { get(:index, params: { statut: 'dossiers-invites' }) } - it { expect(assigns(:current_tab)).to eq('dossiers-invites') } + it { expect(assigns(:statut)).to eq('dossiers-invites') } end context 'and there is "mes-dossiers" param' do - before { get(:index, params: { current_tab: 'mes-dossiers' }) } + before { get(:index, params: { statut: 'mes-dossiers' }) } - it { expect(assigns(:current_tab)).to eq('mes-dossiers') } + it { expect(assigns(:statut)).to eq('mes-dossiers') } end end diff --git a/spec/mailers/previews/dossier_mailer_preview.rb b/spec/mailers/previews/dossier_mailer_preview.rb index d55c5b4e2..85a7d6252 100644 --- a/spec/mailers/previews/dossier_mailer_preview.rb +++ b/spec/mailers/previews/dossier_mailer_preview.rb @@ -49,6 +49,10 @@ class DossierMailerPreview < ActionMailer::Preview DossierMailer.notify_deletion_to_user(deleted_dossier, usager_email) end + def notify_instructeur_deletion_to_user + DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, usager_email) + end + def notify_deletion_to_administration DossierMailer.notify_deletion_to_administration(deleted_dossier, administration_email) end diff --git a/spec/views/instructeur/dossiers/_state_button.html.haml_spec.rb b/spec/views/instructeur/dossiers/_state_button.html.haml_spec.rb index e320d2464..d44baf449 100644 --- a/spec/views/instructeur/dossiers/_state_button.html.haml_spec.rb +++ b/spec/views/instructeur/dossiers/_state_button.html.haml_spec.rb @@ -70,7 +70,7 @@ describe 'instructeurs/dossiers/state_button.html.haml', type: :view do it 'renders a dropdown' do expect(rendered).to have_dropdown_title(dossier_display_state(dossier)) - expect(rendered).to have_dropdown_items(count: 1) + expect(rendered).to have_dropdown_items(count: 2) expect(rendered).to have_dropdown_item('Repasser en instruction', href: repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier)) end diff --git a/spec/views/users/dossiers/index.html.haml_spec.rb b/spec/views/users/dossiers/index.html.haml_spec.rb index ff91ccce5..e3d2514e5 100644 --- a/spec/views/users/dossiers/index.html.haml_spec.rb +++ b/spec/views/users/dossiers/index.html.haml_spec.rb @@ -4,15 +4,15 @@ describe 'users/dossiers/index.html.haml', type: :view do let(:dossier_en_construction) { create(:dossier, state: Dossier.states.fetch(:en_construction), user: user) } let(:user_dossiers) { [dossier_brouillon, dossier_en_construction] } let(:dossiers_invites) { [] } - let(:current_tab) { 'mes-dossiers' } + let(:statut) { 'mes-dossiers' } before do allow(view).to receive(:new_demarche_url).and_return('#') allow(controller).to receive(:current_user) { user } assign(:user_dossiers, Kaminari.paginate_array(user_dossiers).page(1)) assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1)) - assign(:dossiers, Kaminari.paginate_array(user_dossiers).page(1)) - assign(:current_tab, current_tab) + assign(:dossiers_supprimes, Kaminari.paginate_array(user_dossiers).page(1)) + assign(:statut, statut) render end @@ -48,11 +48,11 @@ describe 'users/dossiers/index.html.haml', type: :view do let(:dossiers_invites) { [] } it 'affiche un titre adapté' do - expect(rendered).to have_selector('h1', text: 'Mes dossiers') + expect(rendered).to have_selector('h1', text: 'Dossiers') end - it 'n’affiche pas la barre d’onglets' do - expect(rendered).not_to have_selector('ul.tabs') + it 'n’affiche la barre d’onglets' do + expect(rendered).to have_selector('ul.tabs') end end @@ -65,7 +65,7 @@ describe 'users/dossiers/index.html.haml', type: :view do it 'affiche la barre d’onglets' do expect(rendered).to have_selector('ul.tabs') - expect(rendered).to have_selector('ul.tabs li', count: 2) + expect(rendered).to have_selector('ul.tabs li', count: 3) expect(rendered).to have_selector('ul.tabs li.active', count: 1) end end From d2f3bb18f940ee5d12d471eb146e5ed41b8186a4 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 23 Nov 2020 17:29:41 +0100 Subject: [PATCH 08/17] instructeur can delete a dossier UI --- app/controllers/users/dossiers_controller.rb | 2 +- spec/controllers/instructeurs/dossiers_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 5cd6732b1..4186cca5e 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -276,7 +276,7 @@ module Users private def statut - @statut = params[:statut].blank? ? 'mes-dossiers' : params[:statut] + @statut = params[:statut].presence || 'mes-dossiers' end def store_user_location! diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 844a8d40e..c396f0347 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -751,7 +751,7 @@ describe Instructeurs::DossiersController, type: :controller do end it 'send an email to the user' do - expect(DossierMailer).to have_received(:notify_instructeur_deletion_to_user).with(DeletedDossier.find(dossier.id), dossier.user.email) + expect(DossierMailer).to have_received(:notify_instructeur_deletion_to_user).with(DeletedDossier.where(dossier_id: dossier.id).first, dossier.user.email) end it 'add a record into deleted_dossiers table' do From f3c4040d2df161b48da6da11cf0cc761804d0472 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 26 Nov 2020 15:13:32 +0100 Subject: [PATCH 09/17] add grace period to deleted dossiers --- .../instructeurs/dossiers_controller.rb | 2 +- app/controllers/users/dossiers_controller.rb | 14 ++++-- .../cron/discarded_dossiers_deletion_job.rb | 8 ++++ app/models/dossier.rb | 48 +++++++++---------- app/models/dossier_operation_log.rb | 7 ++- app/views/users/dossiers/index.html.haml | 27 ++++++----- .../instructeurs/dossiers_controller_spec.rb | 8 ++-- .../users/dossiers_controller_spec.rb | 2 +- .../discarded_dossiers_deletion_job_spec.rb | 34 +++++++++++++ spec/models/dossier_spec.rb | 4 +- 10 files changed, 103 insertions(+), 51 deletions(-) create mode 100644 spec/jobs/discarded_dossiers_deletion_job_spec.rb diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index c8d60dcbb..00ccfefdc 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -216,7 +216,7 @@ module Instructeurs def delete_dossier if dossier.termine? - dossier.deleted_by_instructeur_and_keep_track!(current_instructeur) + dossier.discard_and_keep_track!(current_instructeur, :instructeur_request) flash.notice = 'Le dossier a bien été supprimé' redirect_to instructeur_procedure_path(procedure) else diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 4186cca5e..95bbee4ad 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -14,12 +14,12 @@ module Users before_action :forbid_closed_submission!, only: [:update_brouillon] before_action :show_demarche_en_test_banner before_action :store_user_location!, only: :new - before_action :statut, only: :index def index @user_dossiers = current_user.dossiers.includes(:procedure).order_by_updated_at.page(page) @dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page) @dossiers_supprimes = DeletedDossier.where(user_id: current_user.id).order_by_updated_at.page(page) + @statut = statut(@user_dossiers.count, @dossiers_invites.count, @dossiers_supprimes.count) end def show @@ -275,8 +275,16 @@ module Users private - def statut - @statut = params[:statut].presence || 'mes-dossiers' + def statut(mes_dossiers_count, dossiers_invites_count, dossiers_supprimes_count) + if dossiers_invites_count == 0 && dossiers_supprimes_count == 0 + 'mes-dossiers' + elsif mes_dossiers_count == 0 && dossiers_invites_count > 0 + 'dossiers-invites' + elsif dossiers_invites_count == 0 && mes_dossiers_count == 0 && dossiers_supprimes_count > 0 + 'dossiers-supprimes' + else + @statut = params[:statut].presence || 'mes-dossiers' + end end def store_user_location! diff --git a/app/jobs/cron/discarded_dossiers_deletion_job.rb b/app/jobs/cron/discarded_dossiers_deletion_job.rb index 2a8d4b748..45dd82fe6 100644 --- a/app/jobs/cron/discarded_dossiers_deletion_job.rb +++ b/app/jobs/cron/discarded_dossiers_deletion_job.rb @@ -2,7 +2,15 @@ class Cron::DiscardedDossiersDeletionJob < Cron::CronJob self.schedule_expression = "every day at 2 am" def perform(*args) + DossierOperationLog.where(dossier: Dossier.discarded_en_construction_expired) + .where.not(operation: DossierOperationLog.operations.fetch(:supprimer)) + .destroy_all + DossierOperationLog.where(dossier: Dossier.discarded_termine_expired) + .where.not(operation: DossierOperationLog.operations.fetch(:supprimer)) + .destroy_all + Dossier.discarded_brouillon_expired.destroy_all Dossier.discarded_en_construction_expired.destroy_all + Dossier.discarded_termine_expired.destroy_all end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index d6fc4ee70..e9bc7da9b 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -263,13 +263,19 @@ class Dossier < ApplicationRecord with_discarded .discarded .state_brouillon - .where('hidden_at < ?', 1.month.ago) + .where('hidden_at < ?', 1.week.ago) end scope :discarded_en_construction_expired, -> do with_discarded .discarded .state_en_construction - .where('dossiers.hidden_at < ?', 1.month.ago) + .where('dossiers.hidden_at < ?', 1.week.ago) + end + scope :discarded_termine_expired, -> do + with_discarded + .discarded + .state_termine + .where('dossiers.hidden_at < ?', 1.week.ago) end scope :brouillon_near_procedure_closing_date, -> do @@ -520,27 +526,25 @@ class Dossier < ApplicationRecord end end - def deleted_by_instructeur_and_keep_track!(author) - if keep_track_on_deletion? - deleted_dossier = DeletedDossier.create_from_dossier(self, :instructeur_request) - self.delete_operations_logs - log_dossier_operation(author, :supprime_par_instructeur, self) - DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, user.email).deliver_later - self.destroy - end - end - def discard_and_keep_track!(author, reason) - if keep_track_on_deletion? && en_construction? - deleted_dossier = DeletedDossier.create_from_dossier(self, reason) + if keep_track_on_deletion? + if en_construction? + deleted_dossier = DeletedDossier.create_from_dossier(self, reason) - administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email) - administration_emails.each do |email| - DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later + administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email) + administration_emails.each do |email| + DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later + end + DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later + + log_dossier_operation(author, :supprimer, self) + elsif termine? + deleted_dossier = DeletedDossier.create_from_dossier(self, reason) + + DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, user.email).deliver_later + + log_dossier_operation(author, :supprimer, self) end - DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later - - log_dossier_operation(author, :supprimer, self) end discard! @@ -807,10 +811,6 @@ class Dossier < ApplicationRecord private - def delete_operations_logs - DossierOperationLog.where(dossier: self).destroy_all - end - def geo_areas champs.includes(:geo_areas).flat_map(&:geo_areas) + champs_private.includes(:geo_areas).flat_map(&:geo_areas) end diff --git a/app/models/dossier_operation_log.rb b/app/models/dossier_operation_log.rb index 21697a945..846dbadda 100644 --- a/app/models/dossier_operation_log.rb +++ b/app/models/dossier_operation_log.rb @@ -28,8 +28,7 @@ class DossierOperationLog < ApplicationRecord modifier_annotation: 'modifier_annotation', demander_un_avis: 'demander_un_avis', archiver: 'archiver', - desarchiver: 'desarchiver', - supprime_par_instructeur: 'supprime_par_instructeur' + desarchiver: 'desarchiver' } has_one_attached :serialized @@ -88,11 +87,11 @@ class DossierOperationLog < ApplicationRecord def self.serialize_subject(subject, operation = nil) if subject.nil? nil - elsif operation == "supprime_par_instructeur" + elsif operation == operations.fetch(:supprimer) { date_de_depot: subject.en_construction_at, date_de_mise_en_instruction: subject.en_instruction_at, - date_de_decision: subject.traitements.last.processed_at + date_de_decision: subject.termine? ? subject.traitements.last.processed_at : nil }.as_json else case subject diff --git a/app/views/users/dossiers/index.html.haml b/app/views/users/dossiers/index.html.haml index 9ac567718..644c0e1ef 100644 --- a/app/views/users/dossiers/index.html.haml +++ b/app/views/users/dossiers/index.html.haml @@ -15,20 +15,23 @@ - else %h1.page-title Dossiers %ul.tabs - = tab_item('mes dossiers', - dossiers_path(statut: 'mes-dossiers'), - active: @statut == 'mes-dossiers', - badge: number_with_html_delimiter(@user_dossiers.count)) + - if @user_dossiers.count > 0 + = tab_item('mes dossiers', + dossiers_path(statut: 'mes-dossiers'), + active: @statut == 'mes-dossiers', + badge: number_with_html_delimiter(@user_dossiers.count)) - = tab_item('dossiers invités', - dossiers_path(statut: 'dossiers-invites'), - active: @statut == 'dossiers-invites', - badge: number_with_html_delimiter(@dossiers_invites.count)) + - if @dossiers_invites.count > 0 + = tab_item('dossiers invités', + dossiers_path(statut: 'dossiers-invites'), + active: @statut == 'dossiers-invites', + badge: number_with_html_delimiter(@dossiers_invites.count)) - = tab_item('dossiers supprimés', - dossiers_path(statut: 'dossiers-supprimes'), - active: @statut == 'dossiers-supprimes', - badge: number_with_html_delimiter(@dossiers_supprimes.count)) + - if @dossiers_supprimes.count > 0 + = tab_item('dossiers supprimés', + dossiers_path(statut: 'dossiers-supprimes'), + active: @statut == 'dossiers-supprimes', + badge: number_with_html_delimiter(@dossiers_supprimes.count)) .container diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index c396f0347..032057a30 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -746,8 +746,8 @@ describe Instructeurs::DossiersController, type: :controller do end it 'deletes previous logs and add a suppression log' do - expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(1) - expect(DossierOperationLog.where(dossier_id: dossier.id).first.operation).to eq('supprime_par_instructeur') + expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(3) + expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer') end it 'send an email to the user' do @@ -761,8 +761,8 @@ describe Instructeurs::DossiersController, type: :controller do expect(DeletedDossier.where(dossier_id: dossier.id).first.groupe_instructeur_id).to eq(dossier.groupe_instructeur_id) end - it 'delete the dossier' do - expect { dossier.reload }.to raise_error ActiveRecord::RecordNotFound + it 'discard the dossier' do + expect(dossier.reload.hidden_at).not_to eq(nil) end end diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 58fdeadb5..b7208be2d 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -759,7 +759,7 @@ describe Users::DossiersController, type: :controller do before { get(:index) } - it { expect(assigns(:statut)).to eq('mes-dossiers') } + it { expect(assigns(:statut)).to eq('dossiers-invites') } it { expect(assigns(:dossiers_invites)).to match([invite.dossier]) } end diff --git a/spec/jobs/discarded_dossiers_deletion_job_spec.rb b/spec/jobs/discarded_dossiers_deletion_job_spec.rb new file mode 100644 index 000000000..c8b97dd83 --- /dev/null +++ b/spec/jobs/discarded_dossiers_deletion_job_spec.rb @@ -0,0 +1,34 @@ +RSpec.describe DiscardedDossiersDeletionJob, type: :job do + include ActiveJob::TestHelper + + let(:instructeur) { create(:instructeur) } + let!(:dossier_brouillon) { create(:dossier) } + let!(:dossier) { create(:dossier, :en_construction) } + + let!(:discarded_dossier_brouillon) { create(:dossier, hidden_at: 2.weeks.ago) } + let!(:discarded_dossier_en_construction) { create(:dossier, :en_construction, hidden_at: 2.weeks.ago) } + let!(:discarded_dossier_termine) { create(:dossier, :accepte, hidden_at: 2.weeks.ago) } + let!(:discarded_dossier_termine_today) { create(:dossier, :accepte, hidden_at: 1.hour.ago) } + + before do + discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_en_construction) + discarded_dossier_termine.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine) + discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine_today) + + discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_en_construction) + discarded_dossier_termine.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine) + discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine_today) + end + + context 'cleanup discared dossiers' do + it 'delete dossiers and operation logs' do + expect(Dossier.with_discarded.count).to eq(6) + expect(DossierOperationLog.count).to eq(6) + + DiscardedDossiersDeletionJob.perform_now + + expect(Dossier.with_discarded.count).to eq(3) + expect(DossierOperationLog.count).to eq(4) + end + end +end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 0e27d568a..43d171d5e 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1268,8 +1268,8 @@ describe Dossier do end end - it { expect(Dossier.discarded_brouillon_expired.count).to eq(2) } - it { expect(Dossier.discarded_en_construction_expired.count).to eq(2) } + it { expect(Dossier.discarded_brouillon_expired.count).to eq(3) } + it { expect(Dossier.discarded_en_construction_expired.count).to eq(3) } end describe "discarded procedure dossier should be able to access it's procedure" do From c7643154d1a1a27e7c4a17a3894dd9cc67d20779 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Mon, 7 Dec 2020 15:10:26 +0100 Subject: [PATCH 10/17] final optims after sim review --- app/controllers/users/dossiers_controller.rb | 27 ++++++---- app/mailers/dossier_mailer.rb | 2 +- app/models/user.rb | 1 + ...ify_instructeur_deletion_to_user.html.haml | 2 +- .../dossiers/_state_button.html.haml | 2 +- .../procedures/deleted_dossiers.html.haml | 3 -- .../dossiers/_deleted_dossiers_list.html.haml | 42 ++++++++++++++++ .../users/dossiers/_dossiers_list.html.haml | 50 +++++++------------ app/views/users/dossiers/index.html.haml | 14 +++--- config/locales/fr.yml | 12 +++++ .../notify_deletion_to_user/fr.yml | 7 ++- .../discarded_dossiers_deletion_job_spec.rb | 5 +- 12 files changed, 106 insertions(+), 61 deletions(-) create mode 100644 app/views/users/dossiers/_deleted_dossiers_list.html.haml rename spec/jobs/{ => cron}/discarded_dossiers_deletion_job_spec.rb (93%) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 95bbee4ad..257277b03 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -18,8 +18,8 @@ module Users def index @user_dossiers = current_user.dossiers.includes(:procedure).order_by_updated_at.page(page) @dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page) - @dossiers_supprimes = DeletedDossier.where(user_id: current_user.id).order_by_updated_at.page(page) - @statut = statut(@user_dossiers.count, @dossiers_invites.count, @dossiers_supprimes.count) + @dossiers_supprimes = current_user.deleted_dossiers.order_by_updated_at.page(page) + @statut = statut(@user_dossiers, @dossiers_invites, @dossiers_supprimes, params[:statut]) end def show @@ -275,15 +275,22 @@ module Users private - def statut(mes_dossiers_count, dossiers_invites_count, dossiers_supprimes_count) - if dossiers_invites_count == 0 && dossiers_supprimes_count == 0 - 'mes-dossiers' - elsif mes_dossiers_count == 0 && dossiers_invites_count > 0 - 'dossiers-invites' - elsif dossiers_invites_count == 0 && mes_dossiers_count == 0 && dossiers_supprimes_count > 0 - 'dossiers-supprimes' + # if the status tab is filled, then this tab + # else first filled tab + # else mes-dossiers + def statut(mes_dossiers, dossiers_invites, dossiers_supprimes, params_statut) + tabs = { + 'mes-dossiers' => mes_dossiers.present?, + 'dossiers-invites' => dossiers_invites.present?, + 'dossiers-supprimes' => dossiers_supprimes.present? + } + if tabs[params_statut] + params_statut else - @statut = params[:statut].presence || 'mes-dossiers' + tabs + .filter { |_tab, filled| filled } + .map { |tab, _| tab } + .first || 'mes-dossiers' end end diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 51e80f443..1d340c32b 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -76,7 +76,7 @@ class DossierMailer < ApplicationMailer end def notify_instructeur_deletion_to_user(deleted_dossier, to_email) - @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) + @subject = default_i18n_subject(libelle_demarche: deleted_dossier.procedure.libelle) @deleted_dossier = deleted_dossier mail(to: to_email, subject: @subject) diff --git a/app/models/user.rb b/app/models/user.rb index 9ed96c681..d1356face 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,6 +44,7 @@ class User < ApplicationRecord has_many :invites, dependent: :destroy has_many :dossiers_invites, through: :invites, source: :dossier has_many :feedbacks, dependent: :destroy + has_many :deleted_dossiers has_one :france_connect_information, dependent: :destroy belongs_to :instructeur, optional: true belongs_to :administrateur, optional: true diff --git a/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml index e64cab4b6..d2f7eb23c 100644 --- a/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml +++ b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml @@ -4,6 +4,6 @@ Bonjour, %p - = t('.body', dossier_id: @deleted_dossier.dossier_id, procedure: @deleted_dossier.procedure.libelle) + = t('.body', dossier_id: @deleted_dossier.dossier_id, libelle_demarche: @deleted_dossier.procedure.libelle) = render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/dossiers/_state_button.html.haml b/app/views/instructeurs/dossiers/_state_button.html.haml index 61dd4efd7..b97987ca5 100644 --- a/app/views/instructeurs/dossiers/_state_button.html.haml +++ b/app/views/instructeurs/dossiers/_state_button.html.haml @@ -108,7 +108,7 @@ - if dossier.termine? %li = link_to supprimer_dossier_instructeur_dossier_path(dossier.procedure, dossier), 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.refuse + %span.icon.delete .dropdown-description %h4 Supprimer le dossier L’usager sera notifié que son dossier sera supprimé. diff --git a/app/views/instructeurs/procedures/deleted_dossiers.html.haml b/app/views/instructeurs/procedures/deleted_dossiers.html.haml index 65a2413a6..c9f0944dd 100644 --- a/app/views/instructeurs/procedures/deleted_dossiers.html.haml +++ b/app/views/instructeurs/procedures/deleted_dossiers.html.haml @@ -52,7 +52,6 @@ %tr %th.notification-col %th.number-col N° dossier - %th.status-col Etat %th.status-col Raison de suppression %th.status-col Date de suppression %tbody @@ -62,8 +61,6 @@ %span.icon.folder %td.number-col = deleted_dossier.dossier_id - %td.status-col - = status_badge(deleted_dossier.state) %td.reason-col = deletion_reason_badge(deleted_dossier.reason) %td.date-col.deleted-cell diff --git a/app/views/users/dossiers/_deleted_dossiers_list.html.haml b/app/views/users/dossiers/_deleted_dossiers_list.html.haml new file mode 100644 index 000000000..aa9b9e2f2 --- /dev/null +++ b/app/views/users/dossiers/_deleted_dossiers_list.html.haml @@ -0,0 +1,42 @@ +- if deleted_dossiers.present? + %table.table.dossiers-table.hoverable + %thead + %tr + %th.number-col Nº dossier + %th Démarche + %th Raison de suppression + %th Date de suppression + %tbody + - deleted_dossiers.each do |dossier| + - libelle_demarche = Procedure.find(dossier.procedure_id).libelle + %tr{ data: { 'dossier-id': dossier.dossier_id } } + %td.number-col + = dossier.dossier_id + %td + = libelle_demarche + + %td.cell-link + = deletion_reason_badge(dossier.reason) + %td + = dossier.updated_at.strftime('%d/%m/%Y') + + = paginate(deleted_dossiers) + + - if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago + #user-satisfaction + %h3 Que pensez-vous de la facilité d'utilisation de ce service ? + .icons + = link_to feedback_path(rating: Feedback.ratings.fetch(:unhappy)), data: { remote: true, method: :post } do + %span.icon.frown + = link_to feedback_path(rating: Feedback.ratings.fetch(:neutral)), data: { remote: true, method: :post } do + %span.icon.meh + = link_to feedback_path(rating: Feedback.ratings.fetch(:happy)), data: { remote: true, method: :post } do + %span.icon.smile + +- else + .blank-tab + %h2.empty-text Aucun dossier. + %p.empty-text-details + Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche. + %br + Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx. diff --git a/app/views/users/dossiers/_dossiers_list.html.haml b/app/views/users/dossiers/_dossiers_list.html.haml index b5d75dffe..35a90aae3 100644 --- a/app/views/users/dossiers/_dossiers_list.html.haml +++ b/app/views/users/dossiers/_dossiers_list.html.haml @@ -11,39 +11,23 @@ %th.sr-only Actions %tbody - dossiers.each do |dossier| - -# check if the dossier is deleted or not - - if dossier.try(:deleted_at).nil? - %tr{ data: { 'dossier-id': dossier.id } } - %td.number-col - = link_to(url_for_dossier(dossier), class: 'cell-link', tabindex: -1) do - %span.icon.folder - = dossier.id - %td - = link_to(url_for_dossier(dossier), class: 'cell-link') do - = procedure_libelle(dossier.procedure) - - if dossiers.present? - %td.cell-link - = demandeur_dossier(dossier) - %td.status-col - = status_badge(dossier.state) - %td.updated-at-col.cell-link - = try_format_date(dossier.updated_at) - %td.action-col - = render partial: 'dossier_actions', locals: { dossier: dossier } - - else - %tr{ data: { 'dossier-id': dossier.dossier_id } } - %td.number-col - = dossier.dossier_id - %td - = dossier.procedure_id - - - if dossiers.present? - %td.cell-link - = dossier.reason - %td - = status_badge(dossier.state) - %td - = dossier.updated_at.strftime('%d/%m/%Y') + %tr{ data: { 'dossier-id': dossier.id } } + %td.number-col + = link_to(url_for_dossier(dossier), class: 'cell-link', tabindex: -1) do + %span.icon.folder + = dossier.id + %td + = link_to(url_for_dossier(dossier), class: 'cell-link') do + = procedure_libelle(dossier.procedure) + - if dossiers.present? + %td.cell-link + = demandeur_dossier(dossier) + %td.status-col + = status_badge(dossier.state) + %td.updated-at-col.cell-link + = try_format_date(dossier.updated_at) + %td.action-col + = render partial: 'dossier_actions', locals: { dossier: dossier } = paginate(dossiers) diff --git a/app/views/users/dossiers/index.html.haml b/app/views/users/dossiers/index.html.haml index 644c0e1ef..28a9ae6f3 100644 --- a/app/views/users/dossiers/index.html.haml +++ b/app/views/users/dossiers/index.html.haml @@ -16,30 +16,30 @@ %h1.page-title Dossiers %ul.tabs - if @user_dossiers.count > 0 - = tab_item('mes dossiers', + = tab_item(t('pluralize.mes_dossiers', count: @user_dossiers.count), dossiers_path(statut: 'mes-dossiers'), active: @statut == 'mes-dossiers', badge: number_with_html_delimiter(@user_dossiers.count)) - if @dossiers_invites.count > 0 - = tab_item('dossiers invités', + = tab_item(t('pluralize.dossiers_invites', count: @dossiers_invites.count), dossiers_path(statut: 'dossiers-invites'), active: @statut == 'dossiers-invites', badge: number_with_html_delimiter(@dossiers_invites.count)) - if @dossiers_supprimes.count > 0 - = tab_item('dossiers supprimés', + = tab_item(t('pluralize.dossiers_supprimes', count: @dossiers_supprimes.count), dossiers_path(statut: 'dossiers-supprimes'), active: @statut == 'dossiers-supprimes', badge: number_with_html_delimiter(@dossiers_supprimes.count)) .container - - if @statut === "mes-dossiers" + - if @statut == "mes-dossiers" = render partial: "dossiers_list", locals: { dossiers: @user_dossiers } - - if @statut === "dossiers-invites" + - if @statut == "dossiers-invites" = render partial: "dossiers_list", locals: { dossiers: @dossiers_invites } - - if @statut === "dossiers-supprimes" - = render partial: "dossiers_list", locals: { dossiers: @dossiers_supprimes } + - if @statut == "dossiers-supprimes" + = render partial: "deleted_dossiers_list", locals: { deleted_dossiers: @dossiers_supprimes } diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 551f43688..b3de0f42d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -169,6 +169,18 @@ fr: zero: archivé one: archivé other: archivés + mes_dossiers: + zero: mon dossier + one: mon dossier + other: mes dossiers + dossiers_invites: + zero: dossier invité + one: dossier invité + other: dossiers invités + dossiers_supprimes: + zero: dossier supprimé + one: dossier supprimé + other: dossiers supprimés dossier_trouve: zero: 0 dossier trouvé one: 1 dossier trouvé diff --git a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml index 6d649575f..4c8e0a525 100644 --- a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml +++ b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml @@ -4,5 +4,8 @@ fr: subject: Votre dossier nº %{dossier_id} a bien été supprimé body: Votre dossier n° %{dossier_id} (%{procedure}) a bien été supprimé. Une trace de ce traitement sera conservée pour l’administration. notify_instructeur_deletion_to_user: - subject: Votre dossier nº %{dossier_id} a été supprimé par l'administration - body: Nous vous informons que votre dossier n° %{dossier_id} (%{procedure}) a été supprimé par l'administration en charge de la démarche. Une trace de ce traitement sera conservée pour l’administration. + subject: Votre dossier concernant %{libelle_demarche} est supprimé + body: | + Afin de limiter la conservation de vos données personnelles, votre dossier n° %{dossier_id} concernant %{libelle_demarche} a été supprimé. + Cette suppression ne modifie pas le statut final (accepté, refusé ou sans suite) de votre dossier. + Une trace de ce dossier restera cependant visible dans votre interface demarches-simplifiees.fr. diff --git a/spec/jobs/discarded_dossiers_deletion_job_spec.rb b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb similarity index 93% rename from spec/jobs/discarded_dossiers_deletion_job_spec.rb rename to spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb index c8b97dd83..95e358427 100644 --- a/spec/jobs/discarded_dossiers_deletion_job_spec.rb +++ b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe DiscardedDossiersDeletionJob, type: :job do +RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do include ActiveJob::TestHelper let(:instructeur) { create(:instructeur) } @@ -14,7 +14,6 @@ RSpec.describe DiscardedDossiersDeletionJob, type: :job do discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_en_construction) discarded_dossier_termine.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine) discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine_today) - discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_en_construction) discarded_dossier_termine.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine) discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine_today) @@ -25,7 +24,7 @@ RSpec.describe DiscardedDossiersDeletionJob, type: :job do expect(Dossier.with_discarded.count).to eq(6) expect(DossierOperationLog.count).to eq(6) - DiscardedDossiersDeletionJob.perform_now + Cron::DiscardedDossiersDeletionJob.perform_now expect(Dossier.with_discarded.count).to eq(3) expect(DossierOperationLog.count).to eq(4) From 57bff8ca70ebb3cc329e256867a0bd881158848b Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 10:15:50 +0100 Subject: [PATCH 11/17] improve deletion mail --- .../notify_instructeur_deletion_to_user.html.haml | 2 +- .../dossier_mailer/notify_deletion_to_user/fr.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml index d2f7eb23c..0dde82206 100644 --- a/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml +++ b/app/views/dossier_mailer/notify_instructeur_deletion_to_user.html.haml @@ -4,6 +4,6 @@ Bonjour, %p - = t('.body', dossier_id: @deleted_dossier.dossier_id, libelle_demarche: @deleted_dossier.procedure.libelle) + = t('.body_html', dossier_id: @deleted_dossier.dossier_id, libelle_demarche: @deleted_dossier.procedure.libelle, deleted_dossiers_link: dossiers_url(statut: 'dossiers-supprimes')) = render partial: "layouts/mailers/signature" diff --git a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml index 4c8e0a525..ea104b332 100644 --- a/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml +++ b/config/locales/views/dossier_mailer/notify_deletion_to_user/fr.yml @@ -4,8 +4,8 @@ fr: subject: Votre dossier nº %{dossier_id} a bien été supprimé body: Votre dossier n° %{dossier_id} (%{procedure}) a bien été supprimé. Une trace de ce traitement sera conservée pour l’administration. notify_instructeur_deletion_to_user: - subject: Votre dossier concernant %{libelle_demarche} est supprimé - body: | - Afin de limiter la conservation de vos données personnelles, votre dossier n° %{dossier_id} concernant %{libelle_demarche} a été supprimé. - Cette suppression ne modifie pas le statut final (accepté, refusé ou sans suite) de votre dossier. - Une trace de ce dossier restera cependant visible dans votre interface demarches-simplifiees.fr. + subject: Votre dossier sur la démarche « %{libelle_demarche} » est supprimé + body_html: | + Afin de limiter la conservation de vos données personnelles, votre dossier n° %{dossier_id} concernant la démarche « %{libelle_demarche} » est supprimé.

+ Cette suppression ne modifie pas le statut final (accepté, refusé ou sans suite) de votre dossier.

+ Une trace de ce dossier est visible dans votre interface : %{deleted_dossiers_link}. From 996180bf24d5d2cd41c3267b691571954ecf6838 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 10:22:31 +0100 Subject: [PATCH 12/17] add folder icon to dossier --- app/views/users/dossiers/_deleted_dossiers_list.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/users/dossiers/_deleted_dossiers_list.html.haml b/app/views/users/dossiers/_deleted_dossiers_list.html.haml index aa9b9e2f2..2b4efe2b3 100644 --- a/app/views/users/dossiers/_deleted_dossiers_list.html.haml +++ b/app/views/users/dossiers/_deleted_dossiers_list.html.haml @@ -11,6 +11,7 @@ - libelle_demarche = Procedure.find(dossier.procedure_id).libelle %tr{ data: { 'dossier-id': dossier.dossier_id } } %td.number-col + %span.icon.folder = dossier.dossier_id %td = libelle_demarche From d7604023933aa86909cf167e32ce0e21e81999a1 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 10:27:46 +0100 Subject: [PATCH 13/17] fix typo --- app/controllers/users/dossiers_controller.rb | 2 +- app/views/users/dossiers/_dossier_actions.html.haml | 2 +- config/locales/models/deleted_dossier/fr.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 257277b03..86da6303f 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -304,7 +304,7 @@ module Users def show_demarche_en_test_banner if @dossier.present? && @dossier.procedure.brouillon? - flash.now.alert = "Ce dossier est déposé sur une démarche en test. Toute modification de la démarche par l'administrateur (ajout d'un champ, publication de la démarche...) entrainera sa suppression." + flash.now.alert = "Ce dossier est déposé sur une démarche en test. Toute modification de la démarche par l'administrateur (ajout d'un champ, publication de la démarche...) entraînera sa suppression." end end diff --git a/app/views/users/dossiers/_dossier_actions.html.haml b/app/views/users/dossiers/_dossier_actions.html.haml index 1ae1a87f6..fd48bb53d 100644 --- a/app/views/users/dossiers/_dossier_actions.html.haml +++ b/app/views/users/dossiers/_dossier_actions.html.haml @@ -32,7 +32,7 @@ - if has_delete_action %li.danger - = link_to ask_deletion_dossier_path(dossier), method: :post, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" } do + = link_to ask_deletion_dossier_path(dossier), method: :post, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraîne l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" } do %span.icon.delete .dropdown-description Supprimer le dossier diff --git a/config/locales/models/deleted_dossier/fr.yml b/config/locales/models/deleted_dossier/fr.yml index 87b6836fa..87dd5a670 100644 --- a/config/locales/models/deleted_dossier/fr.yml +++ b/config/locales/models/deleted_dossier/fr.yml @@ -3,7 +3,7 @@ fr: attributes: deleted_dossier: reason: - user_request: Demande d’usager + user_request: Demande de l’usager manager_request: Demande d’administration user_removed: Suppression d’un compte usager procedure_removed: Suppression d’une démarche From ef11190cdaf3c4caff64776997edec8f1decd77e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 10:55:57 +0100 Subject: [PATCH 14/17] wording to be consistent with the other entries --- app/views/instructeurs/dossiers/_state_button.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/instructeurs/dossiers/_state_button.html.haml b/app/views/instructeurs/dossiers/_state_button.html.haml index b97987ca5..2b4d5851c 100644 --- a/app/views/instructeurs/dossiers/_state_button.html.haml +++ b/app/views/instructeurs/dossiers/_state_button.html.haml @@ -111,5 +111,5 @@ %span.icon.delete .dropdown-description %h4 Supprimer le dossier - L’usager sera notifié que son dossier sera supprimé. + L’usager sera notifié que son dossier est supprimé. From 5bc4fab0c4d47fff23dea445dbb8b8c04ff4b4d2 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 14:35:55 +0100 Subject: [PATCH 15/17] exhaustive test of discarded_dossier_deletion_job --- spec/factories/dossier.rb | 3 + .../discarded_dossiers_deletion_job_spec.rb | 85 +++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index cb10c08b7..10c590a14 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -111,6 +111,9 @@ FactoryBot.define do end end + trait :brouillon do + end + trait :en_construction do after(:create) do |dossier, _evaluator| dossier.state = Dossier.states.fetch(:en_construction) diff --git a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb index 95e358427..61078f168 100644 --- a/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb +++ b/spec/jobs/cron/discarded_dossiers_deletion_job_spec.rb @@ -1,33 +1,68 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do - include ActiveJob::TestHelper + describe '#perform' do + let(:instructeur) { create(:instructeur) } + let(:dossier) { create(:dossier, state, hidden_at: hidden_at) } - let(:instructeur) { create(:instructeur) } - let!(:dossier_brouillon) { create(:dossier) } - let!(:dossier) { create(:dossier, :en_construction) } - - let!(:discarded_dossier_brouillon) { create(:dossier, hidden_at: 2.weeks.ago) } - let!(:discarded_dossier_en_construction) { create(:dossier, :en_construction, hidden_at: 2.weeks.ago) } - let!(:discarded_dossier_termine) { create(:dossier, :accepte, hidden_at: 2.weeks.ago) } - let!(:discarded_dossier_termine_today) { create(:dossier, :accepte, hidden_at: 1.hour.ago) } - - before do - discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_en_construction) - discarded_dossier_termine.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine) - discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :passer_en_instruction, discarded_dossier_termine_today) - discarded_dossier_en_construction.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_en_construction) - discarded_dossier_termine.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine) - discarded_dossier_termine_today.send(:log_dossier_operation, instructeur, :supprimer, discarded_dossier_termine_today) - end - - context 'cleanup discared dossiers' do - it 'delete dossiers and operation logs' do - expect(Dossier.with_discarded.count).to eq(6) - expect(DossierOperationLog.count).to eq(6) + before do + # hack to add passer_en_instruction and supprimer to dossier.dossier_operation_logs + dossier.send(:log_dossier_operation, instructeur, :passer_en_instruction, dossier) + dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier) Cron::DiscardedDossiersDeletionJob.perform_now + end - expect(Dossier.with_discarded.count).to eq(3) - expect(DossierOperationLog.count).to eq(4) + def operations_left + DossierOperationLog.where(dossier_id: dossier.id).pluck(:operation) + end + + RSpec.shared_examples "does not delete" do + it 'does not delete it' do + expect { dossier.reload }.not_to raise_error + end + + it 'does not delete its operations logs' do + expect(operations_left).to match_array(["passer_en_instruction", "supprimer"]) + end + end + + RSpec.shared_examples "does delete" do + it 'does delete it' do + expect { dossier.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes its operations logs except supprimer' do + expect(operations_left).to eq(["supprimer"]) + end + end + + [:brouillon, :en_construction, :en_instruction, :accepte, :refuse, :sans_suite].each do |state| + context "with a dossier #{state}" do + let(:state) { state } + + context 'not hidden' do + let(:hidden_at) { nil } + + include_examples "does not delete" + end + + context 'hidden not so long ago' do + let(:hidden_at) { 1.week.ago + 1.hour } + + include_examples "does not delete" + end + end + end + + [:en_construction, :accepte, :refuse, :sans_suite].each do |state| + context "with a dossier #{state}" do + let(:state) { state } + + context 'hidden long ago' do + let(:hidden_at) { 1.week.ago - 1.hour } + + include_examples "does delete" + end + end end end end From a2e87cbb56e7b8d7b078f5f5b99f419d5c58dff7 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 8 Dec 2020 16:34:38 +0100 Subject: [PATCH 16/17] Add attestation, justificatifs, operation_logs and bill_signatures to dossier export --- app/lib/active_storage/downloadable_file.rb | 26 ++++++++++++++----- app/services/pieces_justificatives_service.rb | 17 +++++++++++- .../instructeurs/dossiers_controller_spec.rb | 2 +- .../features/instructeurs/instruction_spec.rb | 7 +++-- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/lib/active_storage/downloadable_file.rb b/app/lib/active_storage/downloadable_file.rb index 914ca478d..92b86784f 100644 --- a/app/lib/active_storage/downloadable_file.rb +++ b/app/lib/active_storage/downloadable_file.rb @@ -23,15 +23,29 @@ class ActiveStorage::DownloadableFile private - def self.timestamped_filename(piece_justificative) + def self.timestamped_filename(attachment) # we pad the original file name with a timestamp # and a short id in order to help identify multiple versions and avoid name collisions - extension = File.extname(piece_justificative.filename.to_s) - basename = File.basename(piece_justificative.filename.to_s, extension) - timestamp = piece_justificative.created_at.strftime("%d-%m-%Y-%H-%M") - id = piece_justificative.id % 10000 + folder = self.folder(attachment) + extension = File.extname(attachment.filename.to_s) + basename = File.basename(attachment.filename.to_s, extension) + timestamp = attachment.created_at.strftime("%d-%m-%Y-%H-%M") + id = attachment.id % 10000 - "#{basename}-#{timestamp}-#{id}#{extension}" + "#{folder}/#{basename}-#{timestamp}-#{id}#{extension}" + end + + def self.folder(attachment) + case attachment.record_type + when 'Dossier' + 'dossier' + when 'DossierOperationLog', 'BillSignature' + 'horodatage' + when 'Commentaire' + 'messagerie' + else + 'pieces_justificatives' + end end def using_local_backend? diff --git a/app/services/pieces_justificatives_service.rb b/app/services/pieces_justificatives_service.rb index 2c4e6fe80..a5903531b 100644 --- a/app/services/pieces_justificatives_service.rb +++ b/app/services/pieces_justificatives_service.rb @@ -2,8 +2,9 @@ class PiecesJustificativesService def self.liste_pieces_justificatives(dossier) pjs_champs = pjs_for_champs(dossier) pjs_commentaires = pjs_for_commentaires(dossier) + pjs_dossier = pjs_for_dossier(dossier) - (pjs_champs + pjs_commentaires) + (pjs_champs + pjs_commentaires + pjs_dossier) .filter(&:attached?) end @@ -59,4 +60,18 @@ class PiecesJustificativesService .commentaires .map(&:piece_jointe) end + + def self.pjs_for_dossier(dossier) + bill_signatures = dossier.dossier_operation_logs.map(&:bill_signature).compact.uniq + + [ + dossier.justificatif_motivation, + dossier.attestation&.pdf, + dossier.etablissement&.entreprise_attestation_sociale, + dossier.etablissement&.entreprise_attestation_fiscale, + dossier.dossier_operation_logs.map(&:serialized), + bill_signatures.map(&:serialized), + bill_signatures.map(&:signature) + ].flatten.compact + end end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 032057a30..f95471b26 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -306,7 +306,7 @@ describe Instructeurs::DossiersController, type: :controller do context 'when the dossier has an attestation' do before do attestation = Attestation.new - allow(attestation).to receive(:pdf).and_return(double(read: 'pdf', size: 2.megabytes)) + allow(attestation).to receive(:pdf).and_return(double(read: 'pdf', size: 2.megabytes, attached?: false)) allow(attestation).to receive(:pdf_url).and_return('http://some_document_url') allow_any_instance_of(Dossier).to receive(:build_attestation).and_return(attestation) diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index 22e083ad4..573a562b8 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -146,6 +146,7 @@ feature 'Instructing a dossier:' do let(:commentaire) { create(:commentaire, instructeur: instructeur, dossier: dossier) } before do + dossier.passer_en_instruction!(instructeur) champ.piece_justificative_file.attach(io: File.open(path), filename: "piece_justificative_0.pdf", content_type: "application/pdf") log_in(instructeur.email, password) @@ -163,9 +164,10 @@ feature 'Instructing a dossier:' do files = ZipTricks::FileReader.read_zip_structure(io: File.open(DownloadHelpers.download)) expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip" - expect(files.size).to be 1 + expect(files.size).to be 2 expect(files[0].filename.include?('piece_justificative_0')).to be_truthy expect(files[0].uncompressed_size).to be File.size(path) + expect(files[1].filename.include?('horodatage/operation')).to be_truthy end scenario 'A instructeur can download an archive containing several identical attachments' do @@ -176,12 +178,13 @@ feature 'Instructing a dossier:' do files = ZipTricks::FileReader.read_zip_structure(io: File.open(DownloadHelpers.download)) expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip" - expect(files.size).to be 2 + expect(files.size).to be 3 expect(files[0].filename.include?('piece_justificative_0')).to be_truthy expect(files[1].filename.include?('piece_justificative_0')).to be_truthy expect(files[0].filename).not_to eq files[1].filename expect(files[0].uncompressed_size).to be File.size(path) expect(files[1].uncompressed_size).to be File.size(path) + expect(files[2].filename.include?('horodatage/operation')).to be_truthy end after { DownloadHelpers.clear_downloads } From 1159494d375281bb1e075881c0554eebe38a0b4a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 16 Dec 2020 17:27:57 +0100 Subject: [PATCH 17/17] Add test to ensure no titre identite is exported --- spec/factories/procedure.rb | 6 +++++ .../pieces_justificatives_service_spec.rb | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 spec/services/pieces_justificatives_service_spec.rb diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 3e05e2b55..f74e2da10 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -170,6 +170,12 @@ FactoryBot.define do end end + trait :with_titre_identite do + after(:build) do |procedure, _evaluator| + build(:type_de_champ_titre_identite, procedure: procedure) + end + end + trait :with_repetition do after(:build) do |procedure, _evaluator| build(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure) diff --git a/spec/services/pieces_justificatives_service_spec.rb b/spec/services/pieces_justificatives_service_spec.rb new file mode 100644 index 000000000..d0ad55499 --- /dev/null +++ b/spec/services/pieces_justificatives_service_spec.rb @@ -0,0 +1,22 @@ +describe PiecesJustificativesService do + describe '.liste_pieces_justificatives' do + let(:procedure) { create(:procedure, :with_titre_identite) } + let(:dossier) { create(:dossier, procedure: procedure) } + let(:champ_identite) { dossier.champs.find { |c| c.type == 'Champs::TitreIdentiteChamp' } } + + before do + champ_identite + .piece_justificative_file + .attach(io: StringIO.new("toto"), filename: "toto.png", content_type: "image/png") + end + + subject { PiecesJustificativesService.liste_pieces_justificatives(dossier) } + + # titre identite is too sensitive + # to be exported + it 'ensures no titre identite is given' do + expect(champ_identite.piece_justificative_file).to be_attached + expect(subject).to eq([]) + end + end +end