Merge pull request #2498 from betagouv/frederic/fix_1946-warn_expiring_dossiers
Avertir l’équipe des dossiers expirés ou sur le point d’expirer
This commit is contained in:
commit
dd76c068fc
11 changed files with 203 additions and 41 deletions
|
@ -53,6 +53,7 @@ L'application tourne à l'adresse `http://localhost:3000`. Un utilisateur de tes
|
||||||
AutoReceiveDossiersForProcedureJob.set(cron: "* * * * *").perform_later(procedure_declaratoire_id, Dossier.states.fetch(:en_instruction))
|
AutoReceiveDossiersForProcedureJob.set(cron: "* * * * *").perform_later(procedure_declaratoire_id, Dossier.states.fetch(:en_instruction))
|
||||||
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
||||||
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
||||||
|
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
||||||
|
|
||||||
## Mise à jour de l'application
|
## Mise à jour de l'application
|
||||||
|
|
||||||
|
|
12
app/jobs/warn_expiring_dossiers_job.rb
Normal file
12
app/jobs/warn_expiring_dossiers_job.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class WarnExpiringDossiersJob < ApplicationJob
|
||||||
|
queue_as :cron
|
||||||
|
|
||||||
|
def perform(*args)
|
||||||
|
expiring, expired = Dossier
|
||||||
|
.includes(:procedure)
|
||||||
|
.nearing_end_of_retention
|
||||||
|
.partition(&:retention_expired?)
|
||||||
|
|
||||||
|
AdministrationMailer.dossier_expiration_summary(expiring, expired).deliver_later
|
||||||
|
end
|
||||||
|
end
|
|
@ -36,4 +36,22 @@ class AdministrationMailer < ApplicationMailer
|
||||||
mail(to: EQUIPE_EMAIL,
|
mail(to: EQUIPE_EMAIL,
|
||||||
subject: subject)
|
subject: subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dossier_expiration_summary(expiring_dossiers, expired_dossiers)
|
||||||
|
subject =
|
||||||
|
if expired_dossiers.present? && expiring_dossiers.present?
|
||||||
|
"Des dossiers ont dépassé leur délai de conservation, et d’autres en approchent"
|
||||||
|
elsif expired_dossiers.present?
|
||||||
|
"Des dossiers ont dépassé leur délai de conservation"
|
||||||
|
elsif expiring_dossiers.present?
|
||||||
|
"Des dossiers approchent de la fin de leur délai de conservation"
|
||||||
|
else
|
||||||
|
"Aucun dossier en fin de délai de conservation"
|
||||||
|
end
|
||||||
|
|
||||||
|
@expiring_dossiers = expiring_dossiers
|
||||||
|
@expired_dossiers = expired_dossiers
|
||||||
|
|
||||||
|
mail(to: TECH_EMAIL, subject: subject)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,6 +61,7 @@ class Dossier < ApplicationRecord
|
||||||
scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) }
|
scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) }
|
||||||
scope :followed_by, -> (gestionnaire) { joins(:follows).where(follows: { gestionnaire: gestionnaire }) }
|
scope :followed_by, -> (gestionnaire) { joins(:follows).where(follows: { gestionnaire: gestionnaire }) }
|
||||||
scope :with_champs, -> { includes(champs: :type_de_champ) }
|
scope :with_champs, -> { includes(champs: :type_de_champ) }
|
||||||
|
scope :nearing_end_of_retention, -> (duration = '1 month') { joins(:procedure).where("en_instruction_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - now() < interval ?", duration) }
|
||||||
|
|
||||||
accepts_nested_attributes_for :individual
|
accepts_nested_attributes_for :individual
|
||||||
|
|
||||||
|
@ -182,6 +183,16 @@ class Dossier < ApplicationRecord
|
||||||
brouillon? || en_construction?
|
brouillon? || en_construction?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retention_end_date
|
||||||
|
if instruction_commencee?
|
||||||
|
en_instruction_at + procedure.duree_conservation_dossiers_dans_ds.months
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def retention_expired?
|
||||||
|
instruction_commencee? && retention_end_date <= DateTime.now
|
||||||
|
end
|
||||||
|
|
||||||
def text_summary
|
def text_summary
|
||||||
if brouillon?
|
if brouillon?
|
||||||
parts = [
|
parts = [
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
- content_for(:title, 'Expiration du délai de conservation des dossiers')
|
||||||
|
|
||||||
|
- if @expired_dossiers.present?
|
||||||
|
%h1= t('mail.administration.dossier_expiration_summary.expired_dossiers', count: @expired_dossiers.count)
|
||||||
|
- @expired_dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
||||||
|
%dl
|
||||||
|
%dt
|
||||||
|
#{procedure.libelle} (#{link_to(procedure.id, manager_procedure_url(procedure))}) :
|
||||||
|
%dd
|
||||||
|
= dossiers.map { |d| link_to(d.id, manager_dossier_url(d)) }.join(', ').html_safe
|
||||||
|
|
||||||
|
- if @expiring_dossiers.present?
|
||||||
|
%h1= t('mail.administration.dossier_expiration_summary.expiring_dossiers', count: @expiring_dossiers.count)
|
||||||
|
- @expiring_dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
||||||
|
%dl
|
||||||
|
%dt
|
||||||
|
#{procedure.libelle} (#{link_to(procedure.id, manager_procedure_url(procedure))}) :
|
||||||
|
%dd
|
||||||
|
= dossiers.map { |d| link_to(d.id, manager_dossier_url(d)) }.join(', ').html_safe
|
||||||
|
|
||||||
|
- if @expired_dossiers.empty? && @expiring_dossiers.empty?
|
||||||
|
Il n’y a pas de dossier expiré ou sur le point d’expirer.
|
|
@ -36,6 +36,16 @@ fr:
|
||||||
apipie:
|
apipie:
|
||||||
api_documentation: "Documentation de l'API demarches-simplifiees.fr"
|
api_documentation: "Documentation de l'API demarches-simplifiees.fr"
|
||||||
|
|
||||||
|
mail:
|
||||||
|
administration:
|
||||||
|
dossier_expiration_summary:
|
||||||
|
expired_dossiers:
|
||||||
|
one: "Un dossier a passé sa date limite de conservation"
|
||||||
|
other: "%{count} dossiers ont passé leur date limite de conservation"
|
||||||
|
expiring_dossiers:
|
||||||
|
one: "Un dossier est sur le point de passer sa date limite de conservation"
|
||||||
|
other: "%{count} dossiers sont sur le point de passer leur date limite de conservation"
|
||||||
|
|
||||||
modal:
|
modal:
|
||||||
publish:
|
publish:
|
||||||
title:
|
title:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
require Rails.root.join("lib", "tasks", "task_helper")
|
||||||
|
|
||||||
|
namespace :'2018_08_31_monthly_dossier_expiration_summary' do
|
||||||
|
task enable: :environment do
|
||||||
|
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
||||||
|
end
|
||||||
|
end
|
|
@ -80,7 +80,7 @@ FactoryBot.define do
|
||||||
trait :en_construction do
|
trait :en_construction do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
dossier.state = Dossier.states.fetch(:en_construction)
|
dossier.state = Dossier.states.fetch(:en_construction)
|
||||||
dossier.en_construction_at = dossier.created_at + 1.minute
|
dossier.en_construction_at ||= dossier.created_at + 1.minute
|
||||||
dossier.save!
|
dossier.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -88,8 +88,8 @@ FactoryBot.define do
|
||||||
trait :en_instruction do
|
trait :en_instruction do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
dossier.state = Dossier.states.fetch(:en_instruction)
|
dossier.state = Dossier.states.fetch(:en_instruction)
|
||||||
dossier.en_construction_at = dossier.created_at + 1.minute
|
dossier.en_construction_at ||= dossier.created_at + 1.minute
|
||||||
dossier.created_at = dossier.created_at + 2.minutes
|
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
|
||||||
dossier.save!
|
dossier.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -97,9 +97,9 @@ FactoryBot.define do
|
||||||
trait :accepte do
|
trait :accepte do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
dossier.state = Dossier.states.fetch(:accepte)
|
dossier.state = Dossier.states.fetch(:accepte)
|
||||||
dossier.processed_at ||= dossier.created_at + 1.minute
|
dossier.en_construction_at ||= dossier.created_at + 1.minute
|
||||||
dossier.en_construction_at ||= dossier.created_at + 2.minutes
|
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
|
||||||
dossier.created_at ||= dossier.created_at + 3.minutes
|
dossier.processed_at ||= dossier.en_instruction_at + 1.minute
|
||||||
dossier.save!
|
dossier.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -107,9 +107,9 @@ FactoryBot.define do
|
||||||
trait :refuse do
|
trait :refuse do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
dossier.state = Dossier.states.fetch(:refuse)
|
dossier.state = Dossier.states.fetch(:refuse)
|
||||||
dossier.processed_at = dossier.created_at + 1.minute
|
dossier.en_construction_at ||= dossier.created_at + 1.minute
|
||||||
dossier.en_construction_at = dossier.created_at + 2.minutes
|
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
|
||||||
dossier.created_at = dossier.created_at + 3.minutes
|
dossier.processed_at ||= dossier.en_instruction_at + 1.minute
|
||||||
dossier.save!
|
dossier.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -117,9 +117,9 @@ FactoryBot.define do
|
||||||
trait :sans_suite do
|
trait :sans_suite do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
dossier.state = Dossier.states.fetch(:sans_suite)
|
dossier.state = Dossier.states.fetch(:sans_suite)
|
||||||
dossier.processed_at = dossier.created_at + 1.minute
|
dossier.en_construction_at ||= dossier.created_at + 1.minute
|
||||||
dossier.en_construction_at = dossier.created_at + 2.minutes
|
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
|
||||||
dossier.created_at = dossier.created_at + 3.minutes
|
dossier.processed_at ||= dossier.en_instruction_at + 1.minute
|
||||||
dossier.save!
|
dossier.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,12 +3,15 @@ require 'rails_helper'
|
||||||
RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
|
RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
|
||||||
describe "perform" do
|
describe "perform" do
|
||||||
let(:date) { Time.utc(2017, 9, 1, 10, 5, 0) }
|
let(:date) { Time.utc(2017, 9, 1, 10, 5, 0) }
|
||||||
|
let(:instruction_date) { date + 120 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Timecop.freeze(date)
|
||||||
|
AutoReceiveDossiersForProcedureJob.new.perform(procedure_id, state)
|
||||||
|
end
|
||||||
|
|
||||||
before { Timecop.freeze(date) }
|
|
||||||
after { Timecop.return }
|
after { Timecop.return }
|
||||||
|
|
||||||
subject { AutoReceiveDossiersForProcedureJob.new.perform(procedure_id, state) }
|
|
||||||
|
|
||||||
context "with some dossiers" do
|
context "with some dossiers" do
|
||||||
let(:nouveau_dossier1) { create(:dossier, :en_construction) }
|
let(:nouveau_dossier1) { create(:dossier, :en_construction) }
|
||||||
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: nouveau_dossier1.procedure) }
|
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: nouveau_dossier1.procedure) }
|
||||||
|
@ -19,43 +22,37 @@ RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
|
||||||
context "en_construction" do
|
context "en_construction" do
|
||||||
let(:state) { Dossier.states.fetch(:en_instruction) }
|
let(:state) { Dossier.states.fetch(:en_instruction) }
|
||||||
|
|
||||||
it do
|
it { expect(nouveau_dossier1.reload.en_instruction?).to be true }
|
||||||
subject
|
it { expect(nouveau_dossier1.reload.en_instruction_at).to eq(date) }
|
||||||
expect(nouveau_dossier1.reload.en_instruction?).to be true
|
|
||||||
expect(nouveau_dossier1.reload.en_instruction_at).to eq(date)
|
|
||||||
|
|
||||||
expect(nouveau_dossier2.reload.en_instruction?).to be true
|
it { expect(nouveau_dossier2.reload.en_instruction?).to be true }
|
||||||
expect(nouveau_dossier2.reload.en_instruction_at).to eq(date)
|
it { expect(nouveau_dossier2.reload.en_instruction_at).to eq(date) }
|
||||||
|
|
||||||
expect(dossier_recu.reload.en_instruction?).to be true
|
it { expect(dossier_recu.reload.en_instruction?).to be true }
|
||||||
expect(dossier_recu.reload.en_instruction_at).to eq(date)
|
it { expect(dossier_recu.reload.en_instruction_at).to eq(instruction_date) }
|
||||||
|
|
||||||
expect(dossier_brouillon.reload.brouillon?).to be true
|
it { expect(dossier_brouillon.reload.brouillon?).to be true }
|
||||||
expect(dossier_brouillon.reload.en_instruction_at).to eq(nil)
|
it { expect(dossier_brouillon.reload.en_instruction_at).to eq(nil) }
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "accepte" do
|
context "accepte" do
|
||||||
let(:state) { Dossier.states.fetch(:accepte) }
|
let(:state) { Dossier.states.fetch(:accepte) }
|
||||||
|
|
||||||
it do
|
it { expect(nouveau_dossier1.reload.accepte?).to be true }
|
||||||
subject
|
it { expect(nouveau_dossier1.reload.en_instruction_at).to eq(date) }
|
||||||
expect(nouveau_dossier1.reload.accepte?).to be true
|
it { expect(nouveau_dossier1.reload.processed_at).to eq(date) }
|
||||||
expect(nouveau_dossier1.reload.en_instruction_at).to eq(date)
|
|
||||||
expect(nouveau_dossier1.reload.processed_at).to eq(date)
|
|
||||||
|
|
||||||
expect(nouveau_dossier2.reload.accepte?).to be true
|
it { expect(nouveau_dossier2.reload.accepte?).to be true }
|
||||||
expect(nouveau_dossier2.reload.en_instruction_at).to eq(date)
|
it { expect(nouveau_dossier2.reload.en_instruction_at).to eq(date) }
|
||||||
expect(nouveau_dossier2.reload.processed_at).to eq(date)
|
it { expect(nouveau_dossier2.reload.processed_at).to eq(date) }
|
||||||
|
|
||||||
expect(dossier_recu.reload.en_instruction?).to be true
|
it { expect(dossier_recu.reload.en_instruction?).to be true }
|
||||||
expect(dossier_recu.reload.en_instruction_at).to eq(date)
|
it { expect(dossier_recu.reload.en_instruction_at).to eq(instruction_date) }
|
||||||
expect(dossier_recu.reload.processed_at).to eq(nil)
|
it { expect(dossier_recu.reload.processed_at).to eq(nil) }
|
||||||
|
|
||||||
expect(dossier_brouillon.reload.brouillon?).to be true
|
it { expect(dossier_brouillon.reload.brouillon?).to be true }
|
||||||
expect(dossier_brouillon.reload.en_instruction_at).to eq(nil)
|
it { expect(dossier_brouillon.reload.en_instruction_at).to eq(nil) }
|
||||||
expect(dossier_brouillon.reload.processed_at).to eq(nil)
|
it { expect(dossier_brouillon.reload.processed_at).to eq(nil) }
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,4 +33,36 @@ RSpec.describe AdministrationMailer, type: :mailer do
|
||||||
|
|
||||||
it { expect(subject.subject).not_to be_empty }
|
it { expect(subject.subject).not_to be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#dossier_expiration_summary' do
|
||||||
|
subject { described_class.dossier_expiration_summary(expiring, expired) }
|
||||||
|
|
||||||
|
context 'with expiring dossiers only' do
|
||||||
|
let(:expiring) { [create(:dossier)] }
|
||||||
|
let(:expired) { [] }
|
||||||
|
|
||||||
|
it { expect(subject.subject).to eq("Des dossiers approchent de la fin de leur délai de conservation") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with expired dossiers only' do
|
||||||
|
let(:expiring) { [] }
|
||||||
|
let(:expired) { [create(:dossier)] }
|
||||||
|
|
||||||
|
it { expect(subject.subject).to eq("Des dossiers ont dépassé leur délai de conservation") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with both expiring and expired dossiers' do
|
||||||
|
let(:expiring) { [create(:dossier)] }
|
||||||
|
let(:expired) { [create(:dossier)] }
|
||||||
|
|
||||||
|
it { expect(subject.subject).to eq("Des dossiers ont dépassé leur délai de conservation, et d’autres en approchent") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with neither expiring nor expired dossiers' do
|
||||||
|
let(:expiring) { [] }
|
||||||
|
let(:expired) { [] }
|
||||||
|
|
||||||
|
it { expect(subject.subject).to eq("Aucun dossier en fin de délai de conservation") }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,32 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'nearing_end_of_retention' do
|
||||||
|
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
|
let!(:young_dossier) { create(:dossier, procedure: procedure) }
|
||||||
|
let!(:expiring_dossier) { create(:dossier, :en_instruction, en_instruction_at: 170.days.ago, procedure: procedure) }
|
||||||
|
let!(:just_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: (6.months + 1.second).ago, procedure: procedure) }
|
||||||
|
let!(:long_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: 1.year.ago, procedure: procedure) }
|
||||||
|
|
||||||
|
context 'with default delay to end of retention' do
|
||||||
|
subject { Dossier.nearing_end_of_retention }
|
||||||
|
|
||||||
|
it { is_expected.not_to include(young_dossier) }
|
||||||
|
it { is_expected.to include(expiring_dossier) }
|
||||||
|
it { is_expected.to include(just_expired_dossier) }
|
||||||
|
it { is_expected.to include(long_expired_dossier) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with custom delay to end of retention' do
|
||||||
|
subject { Dossier.nearing_end_of_retention('0') }
|
||||||
|
|
||||||
|
it { is_expected.not_to include(young_dossier) }
|
||||||
|
it { is_expected.not_to include(expiring_dossier) }
|
||||||
|
it { is_expected.to include(just_expired_dossier) }
|
||||||
|
it { is_expected.to include(long_expired_dossier) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'methods' do
|
describe 'methods' do
|
||||||
let(:dossier) { create(:dossier, :with_entreprise, user: user) }
|
let(:dossier) { create(:dossier, :with_entreprise, user: user) }
|
||||||
let(:etablissement) { dossier.etablissement }
|
let(:etablissement) { dossier.etablissement }
|
||||||
|
@ -976,4 +1002,30 @@ describe Dossier do
|
||||||
it { is_expected.to be false }
|
it { is_expected.to be false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "retention date" do
|
||||||
|
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
|
let(:uninstructed_dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
|
let(:young_dossier) { create(:dossier, :en_instruction, en_instruction_at: DateTime.now, procedure: procedure) }
|
||||||
|
let(:just_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: 6.months.ago, procedure: procedure) }
|
||||||
|
let(:long_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: 1.year.ago, procedure: procedure) }
|
||||||
|
let(:modif_date) { DateTime.parse('01/01/2100') }
|
||||||
|
|
||||||
|
before { Timecop.freeze(modif_date) }
|
||||||
|
after { Timecop.return }
|
||||||
|
|
||||||
|
describe "#retention_end_date" do
|
||||||
|
it { expect(uninstructed_dossier.retention_end_date).to be_nil }
|
||||||
|
it { expect(young_dossier.retention_end_date).to eq(6.months.from_now) }
|
||||||
|
it { expect(just_expired_dossier.retention_end_date).to eq(DateTime.now) }
|
||||||
|
it { expect(long_expired_dossier.retention_end_date).to eq(6.months.ago) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#retention_expired?" do
|
||||||
|
it { expect(uninstructed_dossier).not_to be_retention_expired }
|
||||||
|
it { expect(young_dossier).not_to be_retention_expired }
|
||||||
|
it { expect(just_expired_dossier).to be_retention_expired }
|
||||||
|
it { expect(long_expired_dossier).to be_retention_expired }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue