hide expired dossiers instead of delete them

This commit is contained in:
Lisa Durand 2024-06-04 17:52:01 +02:00
parent 3dd6a59101
commit c2abceaa72
No known key found for this signature in database
GPG key ID: 0DF91F2CA1E8B816
23 changed files with 232 additions and 133 deletions

View file

@ -57,7 +57,7 @@ module Users
@user_dossiers = current_user.dossiers.state_not_termine.merge(@dossiers_visibles)
@dossiers_traites = current_user.dossiers.state_termine.merge(@dossiers_visibles)
@dossiers_invites = current_user.dossiers_invites.merge(@dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(ordered_dossiers)
@dossiers_supprimes_recemment = (current_user.dossiers.hidden_by_user.or(current_user.dossiers.hidden_by_automatic)).merge(ordered_dossiers)
@dossier_transferes = @dossiers_visibles.where(dossier_transfer_id: DossierTransfer.for_email(current_user.email))
@dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.merge(@dossiers_visibles)
@dossiers_supprimes_definitivement = deleted_dossiers
@ -255,6 +255,11 @@ module Users
def extend_conservation
dossier.extend_conservation(dossier.procedure.duree_conservation_dossiers_dans_ds.months)
if dossier.hidden_at.present?
dossier.update!(hidden_at: nil, hidden_by_reason: nil)
end
flash[:notice] = t('views.users.dossiers.archived_dossier', duree_conservation_dossiers_dans_ds: dossier.procedure.duree_conservation_dossiers_dans_ds)
redirect_back(fallback_location: dossier_path(@dossier))
end
@ -530,6 +535,8 @@ module Users
Dossier.visible_by_user.or(Dossier.for_procedure_preview).or(Dossier.for_editing_fork)
elsif action_name == 'restore'
Dossier.hidden_by_user
elsif action_name == 'extend_conservation'
Dossier.visible_by_user.or(Dossier.hidden_by_automatic)
else
Dossier.visible_by_user
end

View file

@ -129,32 +129,32 @@ class DossierMailer < ApplicationMailer
mail(to: to_email, subject: @subject)
end
def notify_deletion_to_administration(deleted_dossier, to_email)
def notify_deletion_to_administration(hidden_dossier, to_email)
configure_defaults_for_email(to_email)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
@subject = default_i18n_subject(dossier_id: hidden_dossier.id)
@hidden_dossier = hidden_dossier
mail(to: to_email, subject: @subject)
end
def notify_automatic_deletion_to_user(deleted_dossiers, to_email)
def notify_automatic_deletion_to_user(hidden_dossiers, to_email)
configure_defaults_for_email(to_email)
I18n.with_locale(deleted_dossiers.first.user_locale) do
@state = deleted_dossiers.first.state
@subject = default_i18n_subject(count: deleted_dossiers.size)
@deleted_dossiers = deleted_dossiers
I18n.with_locale(hidden_dossiers.first.user_locale) do
@state = hidden_dossiers.first.state
@subject = default_i18n_subject(count: hidden_dossiers.size)
@hidden_dossiers = hidden_dossiers
mail(to: to_email, subject: @subject)
end
end
def notify_automatic_deletion_to_administration(deleted_dossiers, to_email)
def notify_automatic_deletion_to_administration(hidden_dossiers, to_email)
configure_defaults_for_email(to_email)
@subject = default_i18n_subject(count: deleted_dossiers.size)
@deleted_dossiers = deleted_dossiers
@subject = default_i18n_subject(count: hidden_dossiers.size)
@hidden_dossiers = hidden_dossiers
mail(to: to_email, subject: @subject)
end

View file

@ -208,6 +208,7 @@ class Dossier < ApplicationRecord
scope :state_en_construction, -> { where(state: states.fetch(:en_construction)) }
scope :state_not_en_construction, -> { where.not(state: states.fetch(:en_construction)) }
scope :state_en_instruction, -> { where(state: states.fetch(:en_instruction)) }
scope :state_not_en_instruction, -> { where.not(state: states.fetch(:en_instruction)) }
scope :state_en_construction_ou_instruction, -> { where(state: EN_CONSTRUCTION_OU_INSTRUCTION) }
scope :state_instruction_commencee, -> { where(state: INSTRUCTION_COMMENCEE) }
scope :state_termine, -> { where(state: TERMINE) }
@ -220,11 +221,13 @@ class Dossier < ApplicationRecord
scope :not_archived, -> { where(archived: false) }
scope :prefilled, -> { where(prefilled: true) }
scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) }
scope :hidden_by_automatic, -> { where.not(hidden_at: nil).where(hidden_by_reason: 'expired') }
scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) }
scope :visible_by_user, -> { where(for_procedure_preview: false).where(hidden_by_user_at: nil, editing_fork_origin_id: nil) }
scope :visible_by_user, -> { where(for_procedure_preview: false).where(hidden_by_user_at: nil, editing_fork_origin_id: nil).where(hidden_at: nil) }
scope :visible_by_administration, -> {
state_not_brouillon
.where(hidden_by_administration_at: nil)
.where(hidden_at: nil)
.merge(visible_by_user.or(state_not_en_construction))
}
scope :visible_by_user_or_administration, -> { visible_by_user.or(visible_by_administration) }
@ -365,10 +368,12 @@ class Dossier < ApplicationRecord
scope :without_termine_expiration_notice_sent, -> { where(termine_close_to_expiration_notice_sent_at: nil) }
scope :deleted_by_user_expired, -> { where('dossiers.hidden_by_user_at < ?', 1.week.ago) }
scope :deleted_by_automatic_expired, -> { where('dossiers.hidden_at < ?', 1.week.ago) }
scope :deleted_by_administration_expired, -> { where('dossiers.hidden_by_administration_at < ?', 1.week.ago) }
scope :en_brouillon_expired_to_delete, -> { state_brouillon.deleted_by_user_expired }
scope :en_construction_expired_to_delete, -> { state_en_construction.deleted_by_user_expired }
scope :termine_expired_to_delete, -> { state_termine.deleted_by_user_expired.deleted_by_administration_expired }
scope :not_en_instruction_expired_to_delete, -> { state_not_en_instruction.deleted_by_automatic_expired }
scope :brouillon_near_procedure_closing_date, -> do
# select users who have submitted dossier for the given 'procedures.id'
@ -415,7 +420,7 @@ class Dossier < ApplicationRecord
when 'tous'
visible_by_administration.all_state
when 'supprimes_recemment'
hidden_by_administration.state_termine
hidden_by_administration.state_termine.or(hidden_by_automatic)
when 'archives'
visible_by_administration.archived
when 'expirant'
@ -600,6 +605,10 @@ class Dossier < ApplicationRecord
termine? || reason == :procedure_removed
end
def can_be_deleted_by_automatic?(reason)
reason == :expired
end
def can_terminer_automatiquement_by_sva_svr?
sva_svr_decision_triggered_at.nil? && !pending_correction? && (sva_svr_decision_on.today? || sva_svr_decision_on.past?)
end
@ -645,7 +654,12 @@ class Dossier < ApplicationRecord
def close_to_expiration?
return false if en_instruction?
expiration_notification_date < Time.zone.now
expiration_notification_date < Time.zone.now && expiration_notification_date > Expired::REMAINING_WEEKS_BEFORE_EXPIRATION.weeks.ago
end
def has_expired?
return false if en_instruction?
expiration_notification_date < Time.zone.now && expiration_notification_date < Expired::REMAINING_WEEKS_BEFORE_EXPIRATION.weeks.ago
end
def after_notification_expiration_date
@ -820,18 +834,6 @@ class Dossier < ApplicationRecord
end
end
def expired_keep_track_and_destroy!
transaction do
DeletedDossier.create_from_dossier(self, :expired)
log_automatic_dossier_operation(:supprimer, self)
dossier_operation_logs.purge_discarded
destroy!
end
true
rescue
false
end
def author_is_user(author)
author.is_a?(User)
end
@ -840,12 +842,18 @@ class Dossier < ApplicationRecord
author.is_a?(Instructeur) || author.is_a?(Administrateur) || author.is_a?(SuperAdmin)
end
def author_is_automatic(author)
author == :automatic
end
def hide_and_keep_track!(author, reason)
transaction do
if author_is_administration(author) && can_be_deleted_by_administration?(reason)
update(hidden_by_administration_at: Time.zone.now, hidden_by_reason: reason)
elsif author_is_user(author) && can_be_deleted_by_user?
update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil, hidden_by_reason: reason)
elsif author_is_automatic(author) && can_be_deleted_by_automatic?(reason)
update(hidden_at: Time.zone.now, hidden_by_reason: reason)
else
raise "Unauthorized dossier hide attempt Dossier##{id} by #{author} for reason #{reason}"
end
@ -1101,6 +1109,7 @@ class Dossier < ApplicationRecord
en_brouillon_expired_to_delete.find_each(&:purge_discarded)
en_construction_expired_to_delete.find_each(&:purge_discarded)
termine_expired_to_delete.find_each(&:purge_discarded)
not_en_instruction_expired_to_delete.find_each(&:purge_discarded)
end
def skip_user_notification_email?

View file

@ -80,6 +80,11 @@ class DossierOperationLog < ApplicationRecord
def self.serialize_author(author)
if author.nil?
nil
elsif author == :automatic
{
id: "Automatic",
email: CONTACT_EMAIL
}.as_json
else
{
id: serialize_author_id(author),

View file

@ -227,18 +227,18 @@ class Instructeur < ApplicationRecord
def dossiers_count_summary(groupe_instructeur_ids)
query = <<~EOF
SELECT
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND not archived) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NOT NULL AND not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS supprimes_recemment,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND procedures.procedure_expires_when_termine_enabled
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND not archived) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NOT NULL AND not archived OR dossiers.hidden_at IS NOT NULL) AS supprimes_recemment,
COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND dossiers.hidden_at IS NULL AND procedures.procedure_expires_when_termine_enabled
AND (
dossiers.state in ('accepte', 'refuse', 'sans_suite')
AND dossiers.processed_at + dossiers.conservation_extension + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now
) OR (
dossiers.state in ('en_construction')
dossiers.state in ('en_construction') AND dossiers.hidden_at IS NULL
AND dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now
)
) AS expirant

View file

@ -93,27 +93,26 @@ class Expired::DossiersDeletionService < Expired::MailRateLimiter
administration_notifications = group_by_fonctionnaire_email(dossiers_to_remove)
.map { |(email, dossiers)| [email, dossiers.map(&:id)] }
deleted_dossier_ids = []
hidden_dossier_ids = []
dossiers_to_remove.find_each do |dossier|
if dossier.expired_keep_track_and_destroy!
deleted_dossier_ids << dossier.id
end
dossier.hide_and_keep_track!(:automatic, :expired)
hidden_dossier_ids << dossier.id
end
user_notifications.each do |(email, dossier_ids)|
dossier_ids = dossier_ids.intersection(deleted_dossier_ids)
dossier_ids = dossier_ids.intersection(hidden_dossier_ids)
if dossier_ids.present?
mail = DossierMailer.notify_automatic_deletion_to_user(
DeletedDossier.where(dossier_id: dossier_ids).to_a,
Dossier.where(id: dossier_ids).to_a,
email
)
send_with_delay(mail)
end
end
administration_notifications.each do |(email, dossier_ids)|
dossier_ids = dossier_ids.intersection(deleted_dossier_ids)
dossier_ids = dossier_ids.intersection(hidden_dossier_ids)
if dossier_ids.present?
mail = DossierMailer.notify_automatic_deletion_to_administration(
DeletedDossier.where(dossier_id: dossier_ids).to_a,
Dossier.where(id: dossier_ids).to_a,
email
)
send_with_delay(mail)

View file

@ -3,9 +3,9 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= t('.header', count: @deleted_dossiers.size)
= t('.header', count: @hidden_dossiers.size)
%ul
- @deleted_dossiers.each do |d|
%li n° #{d.dossier_id} (#{d.procedure.libelle})
- @hidden_dossiers.each do |d|
%li n° #{d.id} (#{d.procedure.libelle})
= render partial: "layouts/mailers/signature"

View file

@ -3,15 +3,15 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= t('.header', count: @deleted_dossiers.size)
= t('.header', count: @hidden_dossiers.size)
%ul
- @deleted_dossiers.each do |d|
%li N° #{d.dossier_id} (#{d.procedure.libelle})
- @hidden_dossiers.each do |d|
%li N° #{d.id} (#{d.procedure.libelle})
%p
%strong= t('.account_active', count: @deleted_dossiers.size)
%strong= t('.account_active', count: @hidden_dossiers.size)
- if @state == Dossier.states.fetch(:en_construction)
%p= t('.footer_en_construction', count: @deleted_dossiers.size, remaining_weeks_before_expiration: distance_of_time_in_words(Expired::REMAINING_WEEKS_BEFORE_EXPIRATION.weeks))
%p= t('.footer_en_construction', count: @hidden_dossiers.size, remaining_weeks_before_expiration: distance_of_time_in_words(Expired::REMAINING_WEEKS_BEFORE_EXPIRATION.weeks))
= render partial: "layouts/mailers/signature"

View file

@ -3,6 +3,6 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= t('.body', dossier_id: @deleted_dossier.dossier_id, procedure: @deleted_dossier.procedure.libelle)
= t('.body', dossier_id: @hidden_dossier.id, procedure: @hidden_dossier.procedure.libelle)
= render partial: "layouts/mailers/signature"

View file

@ -6,8 +6,9 @@
- if dossier.conservation_extension.positive?
= t('instructeurs.dossiers.header.banner.expiration_date_extended')
- if dossier.close_to_expiration?
= render Dsfr::CalloutComponent.new(title: t('instructeurs.dossiers.header.banner.title'), theme: :warning) do |c|
- if dossier.close_to_expiration? || dossier.has_expired?
- title = dossier.has_expired? ? 'title_expired' : 'title'
= render Dsfr::CalloutComponent.new(title: t("instructeurs.dossiers.header.banner.#{title}"), theme: :warning) do |c|
- c.with_body do
- if dossier.brouillon?
= t('instructeurs.dossiers.header.banner.states.brouillon')

View file

@ -19,6 +19,8 @@
= t('views.users.dossiers.dossiers_list.n_dossier')
= dossier.dossier_id
= status_badge(dossier.state, 'fr-mb-1w')
%br
%span.fr-badge.fr-badge--sm.fr-badge--warning
= t('views.users.dossiers.dossiers_list.deleted_badge')

View file

@ -18,7 +18,10 @@
- if dossier.hidden_by_user?
%p.fr-icon--sm.fr-icon-delete-line
= t('views.users.dossiers.dossiers_list.deleted', date: l(dossier.hidden_by_user_at.to_date))
= t('views.users.dossiers.dossiers_list.deleted_by_user', date: l(dossier.hidden_by_user_at.to_date))
- elsif dossier.hidden_at?
%p.fr-icon--sm.fr-icon-delete-line
= t('views.users.dossiers.dossiers_list.deleted_by_automatic', date: l(dossier.hidden_at.to_date))
- else
%p.fr-icon--sm.fr-icon-edit-box-line
- if dossier.depose_at.present?
@ -40,11 +43,13 @@
= t('views.users.dossiers.dossiers_list.n_dossier')
= number_with_html_delimiter(dossier.id)
= status_badge_user(dossier, 'fr-mb-1w')
- if @statut == "dossiers-supprimes-recemment"
%br
%span.fr-badge.fr-badge--sm.fr-badge--warning
= t('views.users.dossiers.dossiers_list.deleted_badge')
- else
= status_badge_user(dossier, 'fr-mb-1w')
- if dossier.pending_correction?
%br
@ -103,8 +108,18 @@
- if @statut == "dossiers-supprimes-recemment"
.flex.justify-end
= link_to restore_dossier_path(dossier.id), method: :patch, class: "fr-btn fr-btn--sm" do
Restaurer
- if dossier.hidden_at.blank?
= link_to restore_dossier_path(dossier.id), method: :patch, class: "fr-btn fr-btn--sm" do
Restaurer
- else
- if dossier.expiration_can_be_extended?
= button_to users_dossier_repousser_expiration_path(dossier), class: 'fr-btn fr-btn--sm' do
Restaurer et étendre la conservation
- else
= render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier })
= paginate dossiers, views_prefix: 'shared'

View file

@ -3,8 +3,9 @@
%p.expires_at
%small= t("shared.dossiers.header.expires_at.#{dossier.state}", date: safe_expiration_date(dossier), duree_conservation_totale: dossier.duree_totale_conservation_in_months)
- if dossier.close_to_expiration?
= render Dsfr::CalloutComponent.new(title: t('users.dossiers.header.banner.title'), theme: :warning) do |c|
- if dossier.close_to_expiration? || dossier.has_expired?
- title = dossier.has_expired? ? 'title_expired' : 'title'
= render Dsfr::CalloutComponent.new(title: t("users.dossiers.header.banner.#{title}"), theme: :warning) do |c|
- c.with_body do
- if dossier.brouillon?
= t('users.dossiers.header.banner.states.brouillon')

View file

@ -536,6 +536,8 @@ fr:
updated_at: modifié le %{date}
shared_with: Dossier partagé par %{owner} avec
deleted: Supprimé le %{date}
deleted_by_user: Supprimé le %{date} par l'usager
deleted_by_automatic: Supprimé le %{date} automatiquement du à la date d'expiration
deleted_badge: Supprimé
dossier_action:
edit_dossier: "Modifier le dossier"

View file

@ -5,10 +5,11 @@ fr:
banner:
expiration_date_extended: " la date de conservation a déjà été étendue"
title: Ce dossier va expirer
title_expired: Ce dossier a expiré
states:
brouillon: "" # not applicable, instructeur does not see brouillons
en_construction: Ce dossier est en attente de prise en charge. Vous pouvez toutefois étendre cette durée dun mois en cliquant sur le bouton suivant.
termine: Le traitement de ce dossier est terminé, mais il va bientôt expirer. Cela signifie quil va bientôt être supprimé. Si vous souhaitez en conserver une trace, vous pouvez le télécharger au format PDF.
termine: Le traitement de ce dossier est terminé, mais il va bientôt être supprimé. Si vous souhaitez en conserver une trace, vous pouvez le télécharger au format PDF.
button_delay_expiration: "Conserver un mois de plus"
notification_management: gestion des notifications
administrators_list: voir les administrateurs

View file

@ -12,10 +12,11 @@ fr:
new_procedure_content: "Une nouvelle démarche est disponible, consultez-la ici :"
contact_service_html: Pour plus dinformations, veuillez vous rapprocher du service %{service_name}, disponible au %{service_phone_number} ou par email à <a href="mailto:%{service_email}">%{service_email}</a>
title: Votre dossier va expirer
title_expired: Votre dossier a expiré
states:
brouillon: Votre dossier est en brouillon, mais va bientôt expirer. Cela signifie quil va bientôt être supprimé sans avoir été déposé. Si vous souhaitez le conserver afin de poursuivre la démarche, vous pouvez étendre la durée de conversation en cliquant sur le bouton ci-dessous.
brouillon: Votre dossier est en brouillon, mais va bientôt être supprimé sans avoir été déposé. Si vous souhaitez le conserver afin de poursuivre la démarche, vous pouvez étendre la durée de conversation en cliquant sur le bouton ci-dessous.
en_construction: Votre dossier est en attente de prise en charge par ladministration. Le délai de prise en charge maximal est de %{nominal_duration_months} mois. Vous pouvez toutefois étendre cette durée en cliquant sur le bouton ci-dessous.
termine: Le traitement de votre dossier est terminé, mais il va bientôt expirer. Cela signifie quil va bientôt être supprimé. Si vous souhaitez en conserver une trace, vous pouvez le télécharger au format PDF.
termine: Le traitement de votre dossier est terminé, mais il va bientôt être supprimé. Si vous souhaitez en conserver une trace, vous pouvez le télécharger au format PDF.
button_delay_expiration:
one: "Conserver %{count} mois supplémentaire"
other: "Conserver %{count} mois supplémentaires"

View file

@ -114,6 +114,16 @@ FactoryBot.define do
hidden_at { Time.zone.now }
end
trait :hidden_by_automatic do
hidden_at { Time.zone.now }
hidden_by_reason { DeletedDossier.reasons.fetch(:expired) }
end
trait :hidden_by_user do
hidden_by_user_at { 1.day.ago }
hidden_by_reason { DeletedDossier.reasons.fetch(:user_request) }
end
trait :hidden_by_administration do
hidden_by_administration_at { 1.day.ago }
hidden_by_reason { DeletedDossier.reasons.fetch(:instructeur_request) }

View file

@ -2,6 +2,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
describe '#perform' do
let(:instructeur) { create(:instructeur) }
let(:dossier) { create(:dossier, :with_individual, state) }
let(:dossier_2) { create(:dossier, :with_individual, state) }
before do
# hack to add passer_en_instruction and supprimer to dossier.dossier_operation_logs
@ -9,6 +10,8 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier)
dossier.update_columns(hidden_by_user_at: hidden_at, hidden_by_administration_at: hidden_at)
dossier.update_column(:hidden_by_reason, "user_request")
dossier_2.update_columns(hidden_at: hidden_at)
dossier_2.update_column(:hidden_by_reason, "expired")
end
subject do
@ -24,6 +27,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
it 'does not delete it' do
expect { dossier.reload }.not_to raise_error
expect { dossier_2.reload }.not_to raise_error
end
it 'does not delete its operations logs' do
@ -36,6 +40,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
it 'does delete it' do
expect { dossier.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { dossier_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'deletes its operations logs except supprimer' do

View file

@ -84,23 +84,23 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.perform_deliveries).to be_falsy }
end
def notify_deletion_to_administration(deleted_dossier, to_email)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
def notify_deletion_to_administration(hidden_dossier, to_email)
@subject = default_i18n_subject(dossier_id: hidden_dossier.id)
@hidden_dossier = hidden_dossier
mail(to: to_email, subject: @subject)
end
describe '.notify_deletion_to_administration' do
let(:deleted_dossier) { build(:deleted_dossier) }
let(:hidden_dossier) { build(:dossier) }
subject { described_class.notify_deletion_to_administration(deleted_dossier, to_email) }
subject { described_class.notify_deletion_to_administration(hidden_dossier, to_email) }
it 'verifies subject and body content for deletion notification' do
expect(subject.subject).to eq("Le dossier nº #{deleted_dossier.dossier_id} a été supprimé à la demande de lusager")
expect(subject.subject).to eq("Le dossier nº #{hidden_dossier.id} a été supprimé à la demande de lusager")
expect(subject.body).to include("À la demande de lusager")
expect(subject.body).to include(deleted_dossier.dossier_id)
expect(subject.body).to include(deleted_dossier.procedure.libelle)
expect(subject.body).to include(hidden_dossier.id)
expect(subject.body).to include(hidden_dossier.procedure.libelle)
end
end
@ -127,46 +127,49 @@ RSpec.describe DossierMailer, type: :mailer do
end
describe '.notify_automatic_deletion_to_user' do
let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
# let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
# let(:hidden_dossier) { build(:dossier, :en_construction, hidden_at: Time.zone.now, hidden_by_reason: 'expired') }
describe 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) }
# let(:dossier) { create(:dossier, :en_construction) }
let(:hidden_dossier) { create(:dossier, :en_construction, hidden_at: Time.zone.now, hidden_by_reason: 'expired') }
subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) }
subject { described_class.notify_automatic_deletion_to_user([hidden_dossier], hidden_dossier.user.email) }
it 'checks email subject, to, and body for correct inclusions and exclusions for en_construction status' do
expect(subject.to).to eq([dossier.user.email])
expect(subject.to).to eq([hidden_dossier.user.email])
expect(subject.subject).to eq("Un dossier a été supprimé automatiquement de votre compte")
expect(subject.body).to include("#{dossier.id} ")
expect(subject.body).to include(dossier.procedure.libelle)
expect(subject.body).to include("#{hidden_dossier.id} ")
expect(subject.body).to include(hidden_dossier.procedure.libelle)
expect(subject.body).to include("nous nous excusons de la gêne occasionnée")
end
end
describe 'termine' do
let(:dossier) { create(:dossier, :accepte) }
let(:hidden_dossier) { create(:dossier, :accepte, hidden_at: Time.zone.now, hidden_by_reason: 'expired') }
subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) }
subject { described_class.notify_automatic_deletion_to_user([hidden_dossier], hidden_dossier.user.email) }
it 'checks email subject, to, and body for correct inclusions and exclusions for termine status' do
expect(subject.to).to eq([dossier.user.email])
expect(subject.to).to eq([hidden_dossier.user.email])
expect(subject.subject).to eq("Un dossier a été supprimé automatiquement de votre compte")
expect(subject.body).to include("#{dossier.id} ")
expect(subject.body).to include(dossier.procedure.libelle)
expect(subject.body).to include("#{hidden_dossier.id} ")
expect(subject.body).to include(hidden_dossier.procedure.libelle)
expect(subject.body).not_to include("nous nous excusons de la gêne occasionnée")
end
end
end
describe '.notify_automatic_deletion_to_administration' do
let(:dossier) { create(:dossier, :en_construction) }
let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
# let(:dossier) { create(:dossier, :en_construction) }
let(:hidden_dossier) { create(:dossier, :accepte, hidden_at: Time.zone.now, hidden_by_reason: 'expired') }
# let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
subject { described_class.notify_automatic_deletion_to_administration([deleted_dossier], dossier.user.email) }
subject { described_class.notify_automatic_deletion_to_administration([hidden_dossier], hidden_dossier.user.email) }
it 'verifies subject and body content for automatic deletion notification' do
expect(subject.subject).to eq("Un dossier a été supprimé automatiquement")
expect(subject.body).to include("#{dossier.id} (#{dossier.procedure.libelle})")
expect(subject.body).to include("#{hidden_dossier.id} (#{hidden_dossier.procedure.libelle})")
end
end

View file

@ -217,7 +217,6 @@ describe Expired::DossiersDeletionService do
context 'with a single dossier' do
let!(:dossier) { create(:dossier, :en_construction, :followed, procedure: procedure, en_construction_close_to_expiration_notice_sent_at: notice_sent_at) }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
before { service.delete_expired_en_construction_and_notify }
@ -240,22 +239,21 @@ describe Expired::DossiersDeletionService do
context 'when a notice has been sent a long time ago' do
let(:notice_sent_at) { (warning_period + 4.days).ago }
it { expect { dossier.reload }.to raise_error(ActiveRecord::RecordNotFound) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with([deleted_dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.followers_instructeurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier], dossier.followers_instructeurs.first.email) }
it { expect(dossier.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier.reload.hidden_by_reason).to eq('expired') }
end
end
context 'with 2 dossiers to delete' do
let!(:dossier_1) { create(:dossier, :en_construction, procedure: procedure, user: user, en_construction_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let!(:dossier_2) { create(:dossier, :en_construction, procedure: procedure_2, user: user, en_construction_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let(:deleted_dossier_1) { DeletedDossier.find_by(dossier_id: dossier_1.id) }
let(:deleted_dossier_2) { DeletedDossier.find_by(dossier_id: dossier_2.id) }
let!(:instructeur) { create(:instructeur) }
@ -265,12 +263,17 @@ describe Expired::DossiersDeletionService do
end
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([deleted_dossier_1, deleted_dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([dossier_1, dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).thrice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([deleted_dossier_1, deleted_dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([dossier_1, dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(dossier_1.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier_1.reload.hidden_by_reason).to eq('expired') }
it { expect(dossier_2.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier_2.reload.hidden_by_reason).to eq('expired') }
end
end
@ -364,7 +367,6 @@ describe Expired::DossiersDeletionService do
context 'with a single dossier' do
let!(:dossier) { create(:dossier, :followed, :accepte, procedure: procedure, termine_close_to_expiration_notice_sent_at: notice_sent_at) }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
before { service.delete_expired_termine_and_notify }
@ -387,22 +389,21 @@ describe Expired::DossiersDeletionService do
context 'when a notice has been sent a long time ago' do
let(:notice_sent_at) { (warning_period + 4.days).ago }
it { expect { dossier.reload }.to raise_error(ActiveRecord::RecordNotFound) }
it { expect(dossier.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier.reload.hidden_by_reason).to eq('expired') }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with([deleted_dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier], dossier.followers_instructeurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier], dossier.followers_instructeurs.first.email) }
end
end
context 'with 2 dossiers to delete' do
let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let!(:dossier_2) { create(:dossier, :refuse, procedure: procedure_2, user: user, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let(:deleted_dossier_1) { DeletedDossier.find_by(dossier_id: dossier_1.id) }
let(:deleted_dossier_2) { DeletedDossier.find_by(dossier_id: dossier_2.id) }
let!(:instructeur) { create(:instructeur) }
@ -412,19 +413,22 @@ describe Expired::DossiersDeletionService do
end
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([deleted_dossier_1, deleted_dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([dossier_1, dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).thrice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([deleted_dossier_1, deleted_dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([dossier_1, dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(dossier_1.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier_1.reload.hidden_by_reason).to eq('expired') }
it { expect(dossier_2.reload.hidden_at).to be_an_instance_of(ActiveSupport::TimeWithZone) }
it { expect(dossier_2.reload.hidden_by_reason).to eq('expired') }
end
context 'with 1 dossier deleted by user and 1 dossier deleted by administration' do
let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, hidden_by_administration_at: 1.hour.ago, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let!(:dossier_2) { create(:dossier, :refuse, procedure: procedure_2, user: user, hidden_by_user_at: 1.hour.ago, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let(:deleted_dossier_1) { DeletedDossier.find_by(dossier_id: dossier_1.id) }
let(:deleted_dossier_2) { DeletedDossier.find_by(dossier_id: dossier_2.id) }
let!(:instructeur) { create(:instructeur) }
@ -434,11 +438,11 @@ describe Expired::DossiersDeletionService do
end
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([deleted_dossier_1]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([dossier_1]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([deleted_dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) }
end
end
end

View file

@ -294,13 +294,13 @@ describe 'The user' do
login_as(user, scope: :user)
visit brouillon_dossier_path(user_old_dossier)
expect(page).to have_css('.fr-callout__title', text: 'Votre dossier va expirer', visible: true)
expect(page).to have_css('.fr-callout__title', text: 'Votre dossier a expiré', visible: true)
find('#test-user-repousser-expiration').click
expect(page).to have_no_selector('#test-user-repousser-expiration')
Timecop.freeze(simple_procedure.duree_conservation_dossiers_dans_ds.month.from_now) do
visit brouillon_dossier_path(user_old_dossier)
expect(page).to have_css('.fr-callout__title', text: 'Votre dossier va expirer', visible: true)
expect(page).to have_css('.fr-callout__title', text: 'Votre dossier a expiré', visible: true)
find('#test-user-repousser-expiration').click
expect(page).to have_no_selector('#test-user-repousser-expiration')
end

View file

@ -12,6 +12,9 @@ describe 'user access to the list of their dossiers', js: true do
let!(:dossier_for_tiers) { create(:dossier, :en_instruction, :for_tiers_with_notification, user: user) }
let!(:dossier_en_construction_with_accuse_lecture) { create(:dossier, :en_construction, user: user, procedure: procedure_accuse_lecture) }
let!(:dossier_accepte_with_accuse_lecture) { create(:dossier, :accepte, user: user, procedure: procedure_accuse_lecture) }
let!(:dossier_en_construction_expire) { create(:dossier, :en_construction, :hidden_by_automatic, user: user) }
let!(:dossier_traite_expire) { create(:dossier, :accepte, :hidden_by_automatic, user: user) }
let!(:dossier_en_construction_supprime) { create(:dossier, :with_individual, :with_populated_champs, :en_construction, :hidden_by_user, user: user) }
let(:dossiers_per_page) { 25 }
let(:last_updated_dossier) { dossier_en_construction }
@ -193,23 +196,54 @@ describe 'user access to the list of their dossiers', js: true do
expect(page).not_to have_content(dossier_en_construction.procedure.libelle)
end
end
end
describe 'clone' do
it 'should have links to clone dossiers' do
expect(page).to have_link(nil, href: clone_dossier_path(dossier_brouillon))
expect(page).to have_link(nil, href: clone_dossier_path(dossier_en_construction))
expect(page).to have_link(nil, href: clone_dossier_path(dossier_en_instruction))
end
describe 'clone' do
it 'should have links to clone dossiers' do
expect(page).to have_link(nil, href: clone_dossier_path(dossier_brouillon))
expect(page).to have_link(nil, href: clone_dossier_path(dossier_en_construction))
expect(page).to have_link(nil, href: clone_dossier_path(dossier_en_instruction))
end
context 'when user clicks on clone button', js: true do
scenario 'the dossier is cloned' do
within(:css, ".card", match: :first) do
click_on 'Autres actions'
expect { click_on 'Dupliquer ce dossier' }.to change { dossier_brouillon.user.dossiers.count }.by(1)
end
expect(page).to have_content("Votre dossier a bien été dupliqué. Vous pouvez maintenant le vérifier, ladapter puis le déposer.")
context 'when user clicks on clone button', js: true do
scenario 'the dossier is cloned' do
within(:css, ".card", match: :first) do
click_on 'Autres actions'
expect { click_on 'Dupliquer ce dossier' }.to change { dossier_brouillon.user.dossiers.count }.by(1)
end
expect(page).to have_content("Votre dossier a bien été dupliqué. Vous pouvez maintenant le vérifier, ladapter puis le déposer.")
end
end
end
describe 'restore' do
it 'should have links to restore dossiers' do
click_on "3 supprimés récemment"
expect(page).to have_link('Restaurer', href: restore_dossier_path(dossier_en_construction_supprime))
expect(page).to have_button('Restaurer et étendre la conservation')
expect(page).to have_link('imprimer', href: dossier_path("#{dossier_traite_expire.id}.pdf"))
end
context 'when user clicks on restore button', js: true do
scenario 'the dossier is restored' do
click_on "3 supprimés récemment"
expect(page).to have_content(dossier_en_construction_supprime.procedure.libelle)
click_on 'Restaurer'
expect(page).to have_content('Votre dossier a bien été restauré')
expect(page).to have_content('2 supprimés récemment')
end
end
context 'when user clicks on restore and extend button', js: true do
scenario 'the dossier is restored and extended' do
click_on "3 supprimés récemment"
expect(page).to have_content(dossier_en_construction_expire.procedure.libelle)
click_on 'Restaurer et étendre la conservation'
expect(page).to have_content('Votre dossier sera conservé 3 mois supplémentaire')
expect(page).to have_content('2 supprimés récemment')
end
end
end

View file

@ -147,7 +147,7 @@ describe 'users/dossiers/index', type: :view do
expect(rendered).to have_text(/6\s+en cours/)
end
it "cache key dpeends on dossier invites" do
it "cache key depends on dossier invites" do
expect(user_dossiers).to receive(:present?).twice
render
@ -161,7 +161,7 @@ describe 'users/dossiers/index', type: :view do
expect(user_dossiers).to receive(:present?).twice
render
dossier_termine.expired_keep_track_and_destroy!
dossier_termine.hide_and_keep_track!(:automatic, :expired)
user.reload
render