From ecc3638d81f93a4b663552a0c2f972d728f670c9 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 7 Dec 2023 17:29:54 +0100 Subject: [PATCH 1/5] chore(dsfr): include tooltip component --- app/javascript/entrypoints/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/entrypoints/main.css b/app/javascript/entrypoints/main.css index b07341915..aa7dec85b 100644 --- a/app/javascript/entrypoints/main.css +++ b/app/javascript/entrypoints/main.css @@ -38,3 +38,4 @@ @import '@gouvfr/dsfr/dist/component/password/password.css'; @import '@gouvfr/dsfr/dist/component/accordion/accordion.css'; @import '@gouvfr/dsfr/dist/component/tab/tab.css'; +@import '@gouvfr/dsfr/dist/component/tooltip/tooltip.css'; From c26b59722d26f7bbb2f1e9b659a7b32237eb3b53 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 7 Dec 2023 17:32:39 +0100 Subject: [PATCH 2/5] feat(instructeur): pending correction blocks passer en instruction Closes #9731 --- app/models/dossier.rb | 13 +++++++--- .../dossiers/_header_actions.html.haml | 1 + .../procedures/_dossier_actions.html.haml | 12 ++++++--- .../instructeurs/procedures/show.html.haml | 1 + app/views/recherche/index.html.haml | 1 + config/locales/en.yml | 7 ++++- config/locales/fr.yml | 6 ++++- spec/models/dossier_spec.rb | 26 +++++++++++++------ .../dossiers/show.html.haml_spec.rb | 11 +++++++- 9 files changed, 60 insertions(+), 18 deletions(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 26bacb8c6..b903ba5e8 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -163,7 +163,7 @@ class Dossier < ApplicationRecord end event :passer_en_instruction, after: :after_passer_en_instruction do - transitions from: :en_construction, to: :en_instruction + transitions from: :en_construction, to: :en_instruction, guard: :can_passer_en_instruction? end event :passer_automatiquement_en_instruction, after: :after_passer_automatiquement_en_instruction do @@ -564,10 +564,17 @@ class Dossier < ApplicationRecord false end + def can_passer_en_instruction? + return false if pending_correction? + + true + end + def can_passer_automatiquement_en_instruction? + return false if !can_passer_en_instruction? return true if declarative_triggered_at.nil? && procedure.declarative_en_instruction? return true if procedure.auto_archive_on? && !procedure.auto_archive_on.future? - return true if procedure.sva_svr_enabled? && sva_svr_decision_triggered_at.nil? && !pending_correction? + return true if procedure.sva_svr_enabled? && sva_svr_decision_triggered_at.nil? false end @@ -919,8 +926,6 @@ class Dossier < ApplicationRecord .processed_at save! - resolve_pending_correction! - MailTemplatePresenterService.create_commentaire_for_state(self, Dossier.states.fetch(:en_instruction)) if !disable_notification NotificationMailer.send_en_instruction_notification(self).deliver_later diff --git a/app/views/instructeurs/dossiers/_header_actions.html.haml b/app/views/instructeurs/dossiers/_header_actions.html.haml index d5d28a0b1..b42fb8530 100644 --- a/app/views/instructeurs/dossiers/_header_actions.html.haml +++ b/app/views/instructeurs/dossiers/_header_actions.html.haml @@ -8,6 +8,7 @@ dossier_is_followed: current_instructeur&.follow?(dossier), close_to_expiration: dossier.close_to_expiration?, hidden_by_administration: dossier.hidden_by_administration?, + has_pending_correction: dossier.pending_correction?, turbo: true, with_menu: true } diff --git a/app/views/instructeurs/procedures/_dossier_actions.html.haml b/app/views/instructeurs/procedures/_dossier_actions.html.haml index 6395cb9c8..8171590ff 100644 --- a/app/views/instructeurs/procedures/_dossier_actions.html.haml +++ b/app/views/instructeurs/procedures/_dossier_actions.html.haml @@ -44,9 +44,15 @@ - if Dossier.states[:en_construction] == state %li{ 'data-turbo': turbo ? 'true' : 'false' } - = button_to passer_en_instruction_instructeur_dossier_path(procedure_id, dossier_id), method: :post, class: 'fr-btn fr-icon-edit-line' do - Passer en instruction + = button_to passer_en_instruction_instructeur_dossier_path(procedure_id, dossier_id), method: :post, class: 'fr-btn fr-icon-edit-line', + disabled: has_pending_correction, "aria-describedby" => has_pending_correction ? "tooltip-passer-en-instruction" : nil do + = t('views.instructeurs.dossiers.passer_en_instruction') + + - if has_pending_correction + %span#tooltip-passer-en-instruction.fr-tooltip.fr-placement{ role: :tooltip, "aria-hidden" => "true" } + = t('views.instructeurs.dossiers.passer_en_instruction_blocked_by_pending_correction') + - elsif Dossier.states[:en_instruction] == state && !with_menu && !sva_svr %li{ 'data-turbo': turbo ? 'true' : 'false' } = button_to repasser_en_construction_instructeur_dossier_path(procedure_id, dossier_id), method: :post, class: 'fr-btn fr-btn--secondary fr-icon-draft-line' do - Repasser en construction + = t('views.instructeurs.dossiers.repasser_en_construction') diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 71fdd221b..7e1067e2a 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -189,6 +189,7 @@ close_to_expiration: @statut == 'expirant', hidden_by_administration: @statut == 'supprimes_recemment', sva_svr: @procedure.sva_svr_enabled?, + has_pending_correction: p.pending_correction?, turbo: false, with_menu: false } diff --git a/app/views/recherche/index.html.haml b/app/views/recherche/index.html.haml index 70469cd5a..8c42a9a6b 100644 --- a/app/views/recherche/index.html.haml +++ b/app/views/recherche/index.html.haml @@ -105,6 +105,7 @@ close_to_expiration: nil, hidden_by_administration: nil, sva_svr: p.sva_svr_decision_on.present?, + has_pending_correction: p.pending_correction?, turbo: false, with_menu: false } diff --git a/config/locales/en.yml b/config/locales/en.yml index 65fafcd71..b6fb82508 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -377,12 +377,17 @@ en: enabled: "Add file %{dossier_id} to the selection for the bulk operation" disabled: "Impossible to add file %{dossier_id} to the selection because it is already in a bulk operation" personalize: Personalize + passer_en_instruction: Switch to instruction + repasser_en_construction: Revert to submitted show_deleted_dossiers: Show deleted files follow_file: Follow-up the file save: Save stop_follow: No longer follow no_file: No file dossier_synthesis: Summary of files + passer_en_instruction_blocked_by_pending_correction: | + It is not possible to switch to instruction while the file is awaiting correction. + If necessary, the instructor who requested the correction can delete the corresponding message. avis: introduction_file_explaination: "File attached to the request for advice" search: @@ -429,7 +434,7 @@ en: status_overview: status_draft: draft status_in_progress: in progress - en_construction_html: Your file is in progress. It means that you can still edit it. You will no longer be able to edit the file when the administration will switch it to "review". + en_construction_html: Your file is in progress. It means that you can still edit it. You will no longer be able to edit the file when the administration will switch it to "processing". status_review: undergoing review admin_review: The administration is reviewing your file. You are no longer able to edit it. delay_title: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index fd974f64e..8d6d13f87 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -382,12 +382,16 @@ fr: disabled: "Impossible d'ajouter le dossier %{dossier_id} à la selection car il est déjà dans un traitement de masse" show_deleted_dossiers: Afficher les dossiers supprimés personalize: Personnaliser - download: Télécharger un dossier + passer_en_instruction: Passer en instruction + repasser_en_construction: Repasser en construction follow_file: Suivre le dossier stop_follow: Ne plus suivre save: Enregistrer no_file: Aucun dossier dossier_synthesis: Synthèse des dossiers + passer_en_instruction_blocked_by_pending_correction: | + Le passage en instruction est impossible tant que le dossier est en attente de correction. + Si nécessaire, l’instructeur ayant demandé la correction peut supprimer le message correspondant. avis: introduction_file_explaination: "Fichier joint à la demande d’avis" search: diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index ff460503f..1e23ba6f4 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1148,7 +1148,6 @@ describe Dossier, type: :model do let(:last_operation) { dossier.dossier_operation_logs.last } let(:operation_serialized) { last_operation.data } let(:instructeur) { create(:instructeur) } - let!(:correction) { create(:dossier_correction, dossier:) } # correction has a commentaire subject(:passer_en_instruction) { dossier.passer_en_instruction!(instructeur: instructeur) } @@ -1167,13 +1166,6 @@ describe Dossier, type: :model do it { expect { passer_en_instruction }.to change { dossier.commentaires.count }.by(1) } - it "resolve pending correction" do - passer_en_instruction - - expect(dossier.pending_correction?).to be_falsey - expect(correction.reload.resolved_at).to be_present - end - it 'creates a commentaire in the messagerie with expected wording' do passer_en_instruction @@ -1253,6 +1245,24 @@ describe Dossier, type: :model do end end + describe '#can_passer_en_instruction?' do + let(:dossier) { create(:dossier, :en_construction) } + + it { expect(dossier.can_passer_en_instruction?).to be_truthy } + + context 'when there is a pending correction' do + before { create(:dossier_correction, dossier:) } + + it { expect(dossier.can_passer_en_instruction?).to be_falsey } + end + + context 'when there is a resolved correction' do + before { create(:dossier_correction, :resolved, dossier:) } + + it { expect(dossier.can_passer_en_instruction?).to be_truthy } + end + end + describe '#can_passer_automatiquement_en_instruction?' do let(:dossier) { create(:dossier, :en_construction, declarative_triggered_at: declarative_triggered_at) } let(:declarative_triggered_at) { nil } diff --git a/spec/views/instructeur/dossiers/show.html.haml_spec.rb b/spec/views/instructeur/dossiers/show.html.haml_spec.rb index 8745a8b2e..912951062 100644 --- a/spec/views/instructeur/dossiers/show.html.haml_spec.rb +++ b/spec/views/instructeur/dossiers/show.html.haml_spec.rb @@ -56,7 +56,7 @@ describe 'instructeurs/dossiers/show', type: :view do let(:dossier) { create(:dossier, :en_construction) } it 'displays the correct actions' do within("form[action=\"#{passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier)}\"]") do - expect(subject).to have_button('Passer en instruction') + expect(subject).to have_button('Passer en instruction', disabled: false) end within("form[action=\"#{follow_instructeur_dossier_path(dossier.procedure, dossier)}\"]") do expect(subject).to have_button('Suivre le dossier') @@ -64,6 +64,15 @@ describe 'instructeurs/dossiers/show', type: :view do expect(subject).to have_button('Demander une correction') expect(subject).to have_selector('.header-actions ul:first-child > li.instruction-button', count: 1) end + + context 'with pending correction' do + before { create(:dossier_correction, dossier:) } + + it 'disable the instruction button' do + expect(subject).to have_button('Passer en instruction', disabled: true) + expect(subject).to have_content('Le passage en instruction est impossible') + end + end end context 'en_instruction' do From 3d21262e896eb11d651fa9e448f57ead732dc0ff Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Mon, 11 Dec 2023 13:10:25 +0100 Subject: [PATCH 3/5] test(declarative): clarify intention with pending correction & declarative procedure --- ...process_stalled_declarative_dossier_job.rb | 2 +- ...ss_stalled_declarative_dossier_job_spec.rb | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/jobs/process_stalled_declarative_dossier_job.rb b/app/jobs/process_stalled_declarative_dossier_job.rb index cead75d26..1e2481688 100644 --- a/app/jobs/process_stalled_declarative_dossier_job.rb +++ b/app/jobs/process_stalled_declarative_dossier_job.rb @@ -4,7 +4,7 @@ class ProcessStalledDeclarativeDossierJob < ApplicationJob case dossier.procedure.declarative_with_state when Procedure.declarative_with_states.fetch(:en_instruction) - if !dossier.en_instruction? + if !dossier.en_instruction? && dossier.may_passer_automatiquement_en_instruction? dossier.passer_automatiquement_en_instruction! end when Procedure.declarative_with_states.fetch(:accepte) diff --git a/spec/jobs/process_stalled_declarative_dossier_job_spec.rb b/spec/jobs/process_stalled_declarative_dossier_job_spec.rb index 89ca96e19..b481f0989 100644 --- a/spec/jobs/process_stalled_declarative_dossier_job_spec.rb +++ b/spec/jobs/process_stalled_declarative_dossier_job_spec.rb @@ -4,13 +4,12 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:last_operation) { dossier.dossier_operation_logs.last } subject(:perform_job) do - described_class.perform_now(dossier) + described_class.perform_now(dossier.reload) + dossier.reload end before do freeze_time - perform_job - dossier.reload end context 'declarative en instruction' do @@ -20,6 +19,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :en_construction, :with_individual, :with_attestation, procedure:) } it { + perform_job expect(dossier.state).to eq('en_instruction') expect(dossier.en_instruction_at).to eq(Time.current) expect(last_operation.operation).to eq('passer_en_instruction') @@ -29,7 +29,19 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do context 'dossier repasse en_construction' do let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure:, declarative_triggered_at: 1.day.ago) } - it { expect(dossier.state).to eq('en_construction') } + it { expect(subject.state).to eq('en_construction') } + end + + context 'with pending correction' do + let!(:correction) { create(:dossier_correction, dossier:) } + + it { expect(subject.state).to eq('en_construction') } + end + + context 'with resolved correction' do + let!(:correction) { create(:dossier_correction, :resolved, dossier:) } + + it { expect(subject.state).to eq('en_instruction') } end end @@ -37,6 +49,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:, en_instruction_at: 2.days.ago) } it { + perform_job expect(dossier.state).to eq('en_instruction') expect(dossier.en_instruction_at).to eq(2.days.ago) expect(dossier.processed_at).to be_nil @@ -51,6 +64,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :en_construction, :with_individual, :with_attestation, procedure:) } it { + perform_job expect(dossier.state).to eq('accepte') expect(dossier.en_instruction_at).to eq(Time.current) expect(dossier.processed_at).to eq(Time.current) @@ -64,6 +78,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure:, en_instruction_at: 2.days.ago) } it { + perform_job expect(dossier.state).to eq('en_instruction') expect(dossier.en_instruction_at).to eq(2.days.ago) expect(dossier.processed_at).to be_nil @@ -74,6 +89,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :brouillon) } it { + perform_job expect(dossier.state).to eq('brouillon') expect(dossier.en_instruction_at).to be_nil expect(dossier.processed_at).to be_nil @@ -85,7 +101,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do let(:dossier) { create(:dossier, :en_construction, :with_entreprise, :with_attestation, procedure:, as_degraded_mode: false) } - it { expect(dossier).to be_accepte } + it { expect(subject).to be_accepte } context 'having etablissement in degraded_mode' do let(:dossier) { create(:dossier, :en_construction, :with_entreprise, :with_attestation, procedure:, as_degraded_mode: true) } @@ -95,7 +111,7 @@ RSpec.describe ProcessStalledDeclarativeDossierJob, type: :job do expect(Sentry).to_not receive(:capture_exception) end - it { expect(dossier).to be_en_construction } + it { expect(subject).to be_en_construction } end end end From c0771ad903852332ac1102bbb25d5ebff151b86f Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 12 Dec 2023 12:07:12 +0100 Subject: [PATCH 4/5] fix(dossier): passe en instruction with auto-archive even with pending correction --- app/models/dossier.rb | 4 +++- spec/models/dossier_spec.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index b903ba5e8..7d9513858 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -571,9 +571,11 @@ class Dossier < ApplicationRecord end def can_passer_automatiquement_en_instruction? + # Auto archive always passe en instruction, even if there is a pending correction + return true if procedure.auto_archive_on? && !procedure.auto_archive_on.future? + return false if !can_passer_en_instruction? return true if declarative_triggered_at.nil? && procedure.declarative_en_instruction? - return true if procedure.auto_archive_on? && !procedure.auto_archive_on.future? return true if procedure.sva_svr_enabled? && sva_svr_decision_triggered_at.nil? false diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 1e23ba6f4..26029fd00 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1299,6 +1299,15 @@ describe Dossier, type: :model do it { expect(dossier.can_passer_automatiquement_en_instruction?).to be_truthy } end + + context 'when there are pending correction' do + before { create(:dossier_correction, dossier:) } + + it "passes en instruction and keep the correction request" do + expect(dossier.can_passer_automatiquement_en_instruction?).to be_truthy + expect(dossier.pending_correction?).to be_truthy + end + end end context 'when procedure has sva or svr enabled' do From 7c38cbcab39e8208c92204bf9d68c52f4dd10334 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 12 Dec 2023 12:07:26 +0100 Subject: [PATCH 5/5] test: fix random failing test --- spec/models/dossier_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 26029fd00..b774349ba 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -2197,7 +2197,7 @@ describe Dossier, type: :model do let!(:type_de_champ_2) { create(:type_de_champ_textarea, procedure: procedure) } let(:stable_ids) { [type_de_champ_1.stable_id, type_de_champ_2.stable_id] } - it { expect(subject).to match(dossier.champs_public.joins(:type_de_champ).where(types_de_champ: { stable_id: stable_ids })) } + it { expect(subject).to match_array(dossier.champs_public.joins(:type_de_champ).where(types_de_champ: { stable_id: stable_ids })) } end end end