diff --git a/README.md b/README.md
index 94a643ecf..d64b44dbd 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
InstructeurEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
PurgeUnattachedBlobsJob.set(cron: "0 0 * * *").perform_later
OperationsSignatureJob.set(cron: "0 6 * * *").perform_later
+ SeekAndDestroyExpiredDossiersJob.set(cron: "0 7 * * *").perform_later
### Voir les emails envoyés en local
diff --git a/app/jobs/seek_and_destroy_expired_dossiers_job.rb b/app/jobs/seek_and_destroy_expired_dossiers_job.rb
new file mode 100644
index 000000000..d720873bf
--- /dev/null
+++ b/app/jobs/seek_and_destroy_expired_dossiers_job.rb
@@ -0,0 +1,8 @@
+class SeekAndDestroyExpiredDossiersJob < ApplicationJob
+ queue_as :cron
+
+ def perform(*args)
+ Dossier.send_brouillon_expiration_notices
+ Dossier.destroy_brouillons_and_notify
+ end
+end
diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb
index 4754e526a..8813730fe 100644
--- a/app/mailers/dossier_mailer.rb
+++ b/app/mailers/dossier_mailer.rb
@@ -69,4 +69,18 @@ class DossierMailer < ApplicationMailer
format.html { render layout: 'mailers/notifications_layout' }
end
end
+
+ def notify_brouillon_near_deletion(user, dossiers)
+ @subject = default_i18n_subject(count: dossiers.count)
+ @dossiers = dossiers
+
+ mail(to: user.email, subject: @subject)
+ end
+
+ def notify_brouillon_deletion(user, dossier_hashes)
+ @subject = default_i18n_subject(count: dossier_hashes.count)
+ @dossier_hashes = dossier_hashes
+
+ mail(to: user.email, subject: @subject)
+ end
end
diff --git a/app/models/dossier.rb b/app/models/dossier.rb
index ab339ebd3..3075a1f32 100644
--- a/app/models/dossier.rb
+++ b/app/models/dossier.rb
@@ -18,6 +18,8 @@ class Dossier < ApplicationRecord
TAILLE_MAX_ZIP = 50.megabytes
+ DRAFT_EXPIRATION = 1.month + 5.days
+
has_one :etablissement, dependent: :destroy
has_one :individual, dependent: :destroy
has_one :attestation, dependent: :destroy
@@ -162,6 +164,14 @@ class Dossier < ApplicationRecord
user: [])
}
+ scope :brouillon_close_to_expiration, -> do
+ brouillon
+ .joins(:procedure)
+ .where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - (1 * interval '1 month') <= now()")
+ end
+ scope :expired_brouillon, -> { brouillon.where("brouillon_close_to_expiration_notice_sent_at < ?", (Time.zone.now - (DRAFT_EXPIRATION))) }
+ scope :without_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) }
+
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) }
@@ -577,6 +587,10 @@ class Dossier < ApplicationRecord
Dossier.where(id: champs.filter(&:dossier_link?).map(&:value).compact)
end
+ def hash_for_deletion_mail
+ { id: self.id, procedure_libelle: self.procedure.libelle }
+ end
+
private
def log_dossier_operation(author, operation, subject = nil)
@@ -627,4 +641,37 @@ class Dossier < ApplicationRecord
)
end
end
+
+ def self.send_brouillon_expiration_notices
+ brouillons = Dossier
+ .brouillon_close_to_expiration
+ .without_notice_sent
+
+ brouillons
+ .includes(:user)
+ .group_by(&:user)
+ .each do |(user, dossiers)|
+ DossierMailer.notify_brouillon_near_deletion(user, dossiers).deliver_later
+ end
+
+ brouillons.update_all(brouillon_close_to_expiration_notice_sent_at: Time.zone.now)
+ end
+
+ def self.destroy_brouillons_and_notify
+ expired_brouillons = Dossier.expired_brouillon
+
+ expired_brouillons
+ .includes(:procedure, :user)
+ .group_by(&:user)
+ .each do |(user, dossiers)|
+
+ dossier_hashes = dossiers.map(&:hash_for_deletion_mail)
+ DossierMailer.notify_brouillon_deletion(user, dossier_hashes).deliver_later
+
+ dossiers.each do |dossier|
+ DeletedDossier.create_from_dossier(dossier)
+ dossier.destroy
+ end
+ end
+ end
end
diff --git a/app/views/dossier_mailer/notify_brouillon_deletion.html.haml b/app/views/dossier_mailer/notify_brouillon_deletion.html.haml
new file mode 100644
index 000000000..7a28ec3f7
--- /dev/null
+++ b/app/views/dossier_mailer/notify_brouillon_deletion.html.haml
@@ -0,0 +1,12 @@
+- content_for(:title, "#{@subject}")
+
+%p
+ Bonjour,
+
+%p= t('.automatic_dossier_deletion', count: @dossier_hashes.count)
+
+%ul
+ - @dossier_hashes.each do |d|
+ %li n° #{d[:id]} (#{d[:procedure_libelle]})
+
+= render partial: "layouts/mailers/signature"
diff --git a/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml b/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml
new file mode 100644
index 000000000..fc972ddd0
--- /dev/null
+++ b/app/views/dossier_mailer/notify_brouillon_near_deletion.html.haml
@@ -0,0 +1,16 @@
+- content_for(:title, "#{@subject}")
+
+%p
+ Bonjour,
+
+%p
+ Afin de limiter la conservation de vos données personnelles,
+ = t('.automatic_dossier_deletion', count: @dossiers.count)
+ %ul
+ - @dossiers.each do |d|
+ %li= link_to("n° #{d.id} (#{d.procedure.libelle})", dossier_url(d))
+
+%p
+ #{sanitize(t('.send_your_draft', count: @dossiers.count))}. Et sinon, vous n'avez rien à faire.
+
+= render partial: "layouts/mailers/signature"
diff --git a/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml b/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml
new file mode 100644
index 000000000..291dffc23
--- /dev/null
+++ b/config/locales/views/dossier_mailer/notify_brouillon_deletion/fr.yml
@@ -0,0 +1,9 @@
+fr:
+ dossier_mailer:
+ notify_brouillon_deletion:
+ subject:
+ one: "Un dossier en brouillon a été supprimé automatiquement"
+ other: "Des dossiers en brouillon ont été supprimés automatiquement"
+ automatic_dossier_deletion:
+ one: "Le délai maximum de conservation du dossier en brouillon suivant a été atteint, il a donc été supprimé :"
+ other: "Le délai maximum de conservation des dossiers en brouillon suivants a été atteint, ils ont donc été supprimés :"
diff --git a/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml b/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml
new file mode 100644
index 000000000..a3cbf01dc
--- /dev/null
+++ b/config/locales/views/dossier_mailer/notify_brouillon_near_deletion/fr.yml
@@ -0,0 +1,12 @@
+fr:
+ dossier_mailer:
+ notify_brouillon_near_deletion:
+ subject:
+ one: Un dossier en brouillon va bientôt être supprimé
+ other: Des dossiers en brouillon vont bientôt être supprimés
+ automatic_dossier_deletion:
+ one: "le dossier en brouillon suivant sera bientôt automatiquement supprimé :"
+ other: "les dossiers en brouillon suivant seront bientôt automatiquement supprimés :"
+ send_your_draft:
+ one: "Si vous souhaitez toujours déposer ce dossier, vous pouvez retrouver votre brouillon pendant encore un mois"
+ other: "Si vous souhaitez toujours déposer ces dossiers, vous pouvez retrouver vos brouillons pendant encore un mois"
diff --git a/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb b/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb
new file mode 100644
index 000000000..45c159631
--- /dev/null
+++ b/db/migrate/20191128081324_add_near_deletion_notice_send_to_dossier.rb
@@ -0,0 +1,5 @@
+class AddNearDeletionNoticeSendToDossier < ActiveRecord::Migration[5.2]
+ def change
+ add_column :dossiers, :brouillon_close_to_expiration_notice_sent_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 009fa088b..cb42fecf4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_11_14_113700) do
+ActiveRecord::Schema.define(version: 2019_11_28_081324) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -252,6 +252,7 @@ ActiveRecord::Schema.define(version: 2019_11_14_113700) do
t.text "search_terms"
t.text "private_search_terms"
t.bigint "groupe_instructeur_id"
+ t.datetime "brouillon_close_to_expiration_notice_sent_at"
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
t.index ["archived"], name: "index_dossiers_on_archived"
diff --git a/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake b/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake
new file mode 100644
index 000000000..5cb1f9c65
--- /dev/null
+++ b/lib/tasks/deployment/20191203142402_enable_seek_and_destroy_job.rake
@@ -0,0 +1,7 @@
+namespace :after_party do
+ desc 'Deployment task: enable_seek_and_destroy_job'
+ task enable_seek_and_destroy_job: :environment do
+ SeekAndDestroyExpiredDossiersJob.set(cron: "0 7 * * *").perform_later
+ AfterParty::TaskRecord.create version: '20191203142402'
+ end
+end
diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb
index d8c1b07f4..38c121a51 100644
--- a/spec/mailers/dossier_mailer_spec.rb
+++ b/spec/mailers/dossier_mailer_spec.rb
@@ -83,4 +83,27 @@ RSpec.describe DossierMailer, type: :mailer do
it_behaves_like 'a dossier notification'
end
+
+ describe '.notify_brouillon_near_deletion' do
+ let(:dossier) { create(:dossier) }
+
+ before do
+ duree = dossier.procedure.duree_conservation_dossiers_dans_ds
+ @date_suppression = dossier.created_at + duree.months
+ end
+
+ subject { described_class.notify_brouillon_near_deletion(dossier.user, [dossier]) }
+
+ it { expect(subject.body).to include("n° #{dossier.id} ") }
+ it { expect(subject.body).to include(dossier.procedure.libelle) }
+ end
+
+ describe '.notify_brouillon_deletion' do
+ let(:dossier) { create(:dossier) }
+
+ subject { described_class.notify_brouillon_deletion(dossier.user, [dossier.hash_for_deletion_mail]) }
+
+ it { expect(subject.subject).to eq("Un dossier en brouillon a été supprimé automatiquement") }
+ it { expect(subject.body).to include("n° #{dossier.id} (#{dossier.procedure.libelle})") }
+ end
end
diff --git a/spec/mailers/previews/dossier_mailer_preview.rb b/spec/mailers/previews/dossier_mailer_preview.rb
index 8ac4eab87..f1fc2dab7 100644
--- a/spec/mailers/previews/dossier_mailer_preview.rb
+++ b/spec/mailers/previews/dossier_mailer_preview.rb
@@ -20,6 +20,23 @@ class DossierMailerPreview < ActionMailer::Preview
DossierMailer.notify_revert_to_instruction(dossier)
end
+ def notify_brouillon_near_deletion
+ DossierMailer.notify_brouillon_near_deletion(User.new(email: "usager@example.com"), [dossier])
+ end
+
+ def notify_brouillons_near_deletion
+ DossierMailer.notify_brouillon_near_deletion(User.new(email: "usager@example.com"), [dossier, dossier])
+ end
+
+ def notify_brouillon_deletion
+ DossierMailer.notify_brouillon_deletion(User.new(email: "usager@example.com"), [dossier.hash_for_deletion_mail])
+ end
+
+ def notify_brouillons_deletion
+ dossier_hashes = [dossier, dossier].map(&:hash_for_deletion_mail)
+ DossierMailer.notify_brouillon_deletion(User.new(email: "usager@example.com"), dossier_hashes)
+ end
+
private
def deleted_dossier
diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb
index 70e141121..e0e31b9b4 100644
--- a/spec/models/dossier_spec.rb
+++ b/spec/models/dossier_spec.rb
@@ -1047,4 +1047,57 @@ describe Dossier do
it { expect(Dossier.for_procedure(procedure_1)).to contain_exactly(dossier_1_1, dossier_1_2) }
it { expect(Dossier.for_procedure(procedure_2)).to contain_exactly(dossier_2_1) }
end
+
+ describe '#send_brouillon_expiration_notices' do
+ let!(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
+ let!(:date_close_to_expiration) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 1.month }
+ let!(:date_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months - 6.days }
+ let!(:date_not_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months }
+
+ before { Timecop.freeze(Time.zone.parse('12/12/2012').beginning_of_day) }
+ after { Timecop.return }
+
+ context "Envoi de message pour les dossiers expirant dans - d'un mois" do
+ let!(:expired_brouillon) { create(:dossier, procedure: procedure, created_at: date_expired) }
+ let!(:brouillon_close_to_expiration) { create(:dossier, procedure: procedure, created_at: date_close_to_expiration) }
+ let!(:brouillon_close_but_with_notice_sent) { create(:dossier, procedure: procedure, created_at: date_close_to_expiration, brouillon_close_to_expiration_notice_sent_at: Time.zone.now) }
+ let!(:valid_brouillon) { create(:dossier, procedure: procedure, created_at: date_not_expired) }
+
+ before do
+ allow(DossierMailer).to receive(:notify_brouillon_near_deletion).and_return(double(deliver_later: nil))
+ Dossier.send_brouillon_expiration_notices
+ end
+
+ it 'verification de la creation de mail' do
+ expect(DossierMailer).to have_received(:notify_brouillon_near_deletion).with(brouillon_close_to_expiration.user, [brouillon_close_to_expiration])
+ expect(DossierMailer).to have_received(:notify_brouillon_near_deletion).with(expired_brouillon.user, [expired_brouillon])
+ end
+
+ it 'Verification du changement d etat du champ' do
+ expect(brouillon_close_to_expiration.reload.brouillon_close_to_expiration_notice_sent_at).not_to be_nil
+ end
+ end
+ end
+
+ describe '#destroy_brouillons_and_notify' do
+ let!(:today) { Time.zone.now.at_midnight }
+
+ let!(:expired_brouillon) { create(:dossier, brouillon_close_to_expiration_notice_sent_at: today - (Dossier::DRAFT_EXPIRATION + 1.day)) }
+ let!(:other_brouillon) { create(:dossier, brouillon_close_to_expiration_notice_sent_at: today - (Dossier::DRAFT_EXPIRATION - 1.day)) }
+
+ before do
+ allow(DossierMailer).to receive(:notify_brouillon_deletion).and_return(double(deliver_later: nil))
+ Dossier.destroy_brouillons_and_notify
+ end
+
+ it 'notifies deletion' do
+ expect(DossierMailer).to have_received(:notify_brouillon_deletion).once
+ expect(DossierMailer).to have_received(:notify_brouillon_deletion).with(expired_brouillon.user, [expired_brouillon.hash_for_deletion_mail])
+ end
+
+ it 'deletes the expired brouillon' do
+ expect(DeletedDossier.find_by(dossier_id: expired_brouillon.id)).to be_present
+ expect { expired_brouillon.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
end