diff --git a/app/models/dossier.rb b/app/models/dossier.rb index d13310604..4adf8bda6 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -96,6 +96,12 @@ class Dossier < ApplicationRecord processed_at: processed_at) end + def refuser_automatiquement(processed_at: Time.zone.now, motivation:) + build(state: Dossier.states.fetch(:refuse), + motivation: motivation, + processed_at: processed_at) + end + def classer_sans_suite(motivation: nil, instructeur: nil, processed_at: Time.zone.now) build(state: Dossier.states.fetch(:sans_suite), instructeur_email: instructeur&.email, @@ -177,6 +183,10 @@ class Dossier < ApplicationRecord transitions from: :en_instruction, to: :refuse, guard: :can_terminer? end + event :refuser_automatiquement, after: :after_refuser_automatiquement do + transitions from: :en_instruction, to: :refuse, guard: :can_refuser_automatiquement? + end + event :classer_sans_suite, after: :after_classer_sans_suite do transitions from: :en_instruction, to: :sans_suite, guard: :can_terminer? end @@ -526,7 +536,14 @@ class Dossier < ApplicationRecord def can_accepter_automatiquement? return false unless can_terminer? return true if declarative_triggered_at.nil? && procedure.declarative_accepte? && en_construction? - return true if procedure.sva? && sva_svr_decision_triggered_at.nil? && !pending_correction? && (sva_svr_decision_on.today? || sva_svr_decision_on.past?) + return true if procedure.sva? && can_terminer_automatiquement_by_sva_svr? + + false + end + + def can_refuser_automatiquement? + return false unless can_terminer? + return true if procedure.svr? && can_terminer_automatiquement_by_sva_svr? false end @@ -559,6 +576,10 @@ class Dossier < ApplicationRecord termine? || reason == :procedure_removed 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 + def any_etablissement_as_degraded_mode? return true if etablissement&.as_degraded_mode? return true if champs_public_all.any? { _1.etablissement&.as_degraded_mode? } @@ -996,7 +1017,7 @@ class Dossier < ApplicationRecord if procedure.declarative_accepte? self.en_instruction_at = self.processed_at self.declarative_triggered_at = self.processed_at - elsif procedure.sva_svr_enabled? + elsif procedure.sva? self.sva_svr_decision_triggered_at = self.processed_at end @@ -1040,6 +1061,23 @@ class Dossier < ApplicationRecord log_dossier_operation(instructeur, :refuser, self) end + def after_refuser_automatiquement + # Only SVR can refuse automatically + I18n.with_locale(user.locale || I18n.default_locale) do + self.motivation = I18n.t("shared.dossiers.motivation.refused_by_svr") + end + + self.processed_at = traitements.refuser_automatiquement(motivation:).processed_at + self.sva_svr_decision_triggered_at = self.processed_at + + save! + + remove_titres_identite! + MailTemplatePresenterService.create_commentaire_for_state(self) + NotificationMailer.send_refuse_notification(self).deliver_later + log_automatic_dossier_operation(:refuser, self) + end + def after_classer_sans_suite(h) instructeur = h[:instructeur] motivation = h[:motivation] @@ -1090,6 +1128,8 @@ class Dossier < ApplicationRecord passer_automatiquement_en_instruction! elsif en_instruction? && procedure.sva? && may_accepter_automatiquement? accepter_automatiquement! + elsif en_instruction? && procedure.svr? && may_refuser_automatiquement? + refuser_automatiquement! elsif will_save_change_to_sva_svr_decision_on? save! # we always want the most up to date decision when there is a pending correction end diff --git a/config/locales/shared.en.yml b/config/locales/shared.en.yml index 4c2b8c06e..f9e8ec865 100644 --- a/config/locales/shared.en.yml +++ b/config/locales/shared.en.yml @@ -5,6 +5,8 @@ en: details_no_name: "The file was submitted by a FranceConnect account." details: "The file was submitted by the account of %{name}." details_updated: "The file was submitted by the account of %{name}, authenticated by FranceConnect on %{date}." + motivation: + refused_by_svr: "The service handling your file was unable to process it within the time limit set by the Silence Vaut Rejet law." header: expires_at: brouillon: "Expires on %{date} (%{duree_conservation_totale} months after this file was created)" diff --git a/config/locales/shared.fr.yml b/config/locales/shared.fr.yml index 45ed5c04e..0295744f0 100644 --- a/config/locales/shared.fr.yml +++ b/config/locales/shared.fr.yml @@ -5,6 +5,8 @@ fr: details_no_name: "Le dossier a été déposé par un compte FranceConnect." details: "Le dossier a été déposé par le compte de %{name}." details_updated: "Le dossier a été déposé par le compte de %{name}, authentifié par FranceConnect le %{date}." + motivation: + refused_by_svr: "Le service traitant n’a pas été en mesure de traiter votre demande dans le délai imparti par la règle du Silence Vaut Rejet." header: expires_at: brouillon: "Expirera le %{date} (%{duree_conservation_totale} mois après la création du dossier)" diff --git a/spec/jobs/procedure_sva_svr_process_dossier_job_spec.rb b/spec/jobs/procedure_sva_svr_process_dossier_job_spec.rb index b916c4310..f486b0108 100644 --- a/spec/jobs/procedure_sva_svr_process_dossier_job_spec.rb +++ b/spec/jobs/procedure_sva_svr_process_dossier_job_spec.rb @@ -48,6 +48,42 @@ RSpec.describe ProcedureSVASVRProcessDossierJob, type: :job do end end + context 'when procedure is SVR' do + let(:procedure) { create(:procedure, :published, :svr, :for_individual) } + + it 'should refuse dossier' do + expect(subject.sva_svr_decision_on).to eq(Date.current) + expect(subject).to be_refuse + expect(subject.processed_at).to within(1.second).of(Time.current) + end + + context 'when decision is scheduled in the future' do + let!(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:, depose_at: 1.day.ago, sva_svr_decision_on: 2.months.from_now.to_date) } + + it 'should not refuses dossier' do + expect { subject }.not_to change { dossier.reload.updated_at } + expect(subject).to be_en_instruction + end + end + + context 'when dossier has pending correction / is en_construction' do + before do + travel_to 2.days.ago do # create correction in past so it will be 3 days of delay + dossier.flag_as_pending_correction!(build(:commentaire, dossier: dossier)) + end + end + + it 'should not refuses dossier' do + subject + expect(dossier).to be_en_construction + end + + it 'should update sva_svr_decision_on with corrections delay' do + expect { subject }.to change { dossier.reload.sva_svr_decision_on }.from(Date.current).to(Date.current + 3.days) + end + end + end + context 'when dossier was submitted before sva was enabled' do let!(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:, depose_at: 2.months.ago) } diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index b16097168..3f8083873 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1103,6 +1103,46 @@ describe Dossier, type: :model do end end + describe '#refuser_automatiquement' do + context 'as svr procedure' do + let(:last_operation) { dossier.dossier_operation_logs.last } + let(:procedure) { create(:procedure, :for_individual, :published, :svr) } + let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:, sva_svr_decision_on: Date.current, en_instruction_at: DateTime.new(2021, 5, 1, 12)) } + + before { + freeze_time + allow(NotificationMailer).to receive(:send_refuse_notification).and_return(double(deliver_later: true)) + } + + subject { + dossier.refuser_automatiquement! + dossier.reload + } + + it 'refuses dossier automatiquement' do + expect(subject.en_instruction_at).to eq(DateTime.new(2021, 5, 1, 12)) + expect(subject.processed_at).to eq(Time.current) + expect(subject.declarative_triggered_at).to be_nil + expect(subject.sva_svr_decision_triggered_at).to eq(Time.current) + expect(subject.motivation).to include("dans le délai imparti") + expect(subject).to be_refuse + expect(last_operation.operation).to eq('refuser') + expect(last_operation.automatic_operation?).to be_truthy + expect(NotificationMailer).to have_received(:send_refuse_notification).with(dossier) + expect(subject.attestation).to be_nil + expect(dossier.commentaires.count).to eq(1) + end + + context 'for an user having english locale' do + before { dossier.user.update!(locale: 'en') } + + it 'translates the motivation' do + expect(subject.motivation).to include('within the time limit') + end + end + end + end + describe '#passer_en_instruction!' do let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) } let(:last_operation) { dossier.dossier_operation_logs.last } @@ -1324,6 +1364,60 @@ describe Dossier, type: :model do end end + describe '#can_refuser_automatiquement?' do + let(:dossier) { create(:dossier, state: initial_state) } + let(:initial_state) { :en_instruction } + + it { expect(dossier.can_refuser_automatiquement?).to be_falsey } + + context 'when procedure is sva/svr' do + let(:decision) { :svr } + + before do + dossier.procedure.update!(sva_svr: SVASVRConfiguration.new(decision:).attributes) + dossier.update!(sva_svr_decision_on: Date.current) + end + + it { expect(dossier.can_refuser_automatiquement?).to be_truthy } + + context 'when procedure is svr' do + let(:decision) { :svr } + + before do + dossier.procedure.update!(sva_svr: SVASVRConfiguration.new(decision:).attributes) + dossier.update!(sva_svr_decision_on: Date.current) + end + + it { expect(dossier.can_refuser_automatiquement?).to be_truthy } + + context 'when sva_svr_decision_on is in the future' do + before { dossier.update!(sva_svr_decision_on: 1.day.from_now) } + + it { expect(dossier.can_refuser_automatiquement?).to be_falsey } + end + + context 'when dossier has pending correction' do + let(:dossier) { create(:dossier, :en_construction) } + let!(:dossier_correction) { create(:dossier_correction, dossier:) } + + it { expect(dossier.can_refuser_automatiquement?).to be_falsey } + end + + context 'when decision is sva' do + let(:decision) { :sva } + + it { expect(dossier.can_refuser_automatiquement?).to be_falsey } + end + + context 'when dossier was already processed by svr' do + before { dossier.update!(sva_svr_decision_triggered_at: 1.hour.ago) } + + it { expect(dossier.can_refuser_automatiquement?).to be_falsey } + end + end + end + end + describe "can't transition to terminer when etablissement is in degraded mode" do let(:instructeur) { create(:instructeur) } let(:motivation) { 'motivation' } @@ -1957,6 +2051,12 @@ describe Dossier, type: :model do it { expect(dossier.spreadsheet_columns(types_de_champ: [])).to include(["Date SVA", :sva_svr_decision_on]) } end + + context 'procedure svr' do + let(:dossier) { create(:dossier, :en_instruction, procedure: create(:procedure, :svr)) } + + it { expect(dossier.spreadsheet_columns(types_de_champ: [])).to include(["Date SVR", :sva_svr_decision_on]) } + end end describe '#processed_in_month' do