feat(instructeur): can flag a dossier as "pending corrections"
This commit is contained in:
parent
5d61c6fa35
commit
ca3b127942
13 changed files with 254 additions and 32 deletions
|
@ -7,3 +7,4 @@ en:
|
|||
automatic_email: Automatic email
|
||||
you: You
|
||||
deleted_body: Message deleted
|
||||
flagged_pending_corrections: Modification requested
|
||||
|
|
|
@ -7,3 +7,4 @@ fr:
|
|||
automatic_email: Email automatique
|
||||
you: Vous
|
||||
deleted_body: Message supprimé
|
||||
flagged_pending_corrections: Modification demandée
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
= commentaire_issuer
|
||||
- if commentaire_from_guest?
|
||||
%span.fr-text--xs.fr-text-mention--grey.font-weight-normal= t('.guest')
|
||||
- if commentaire.flagged_pending_corrections?
|
||||
%span.fr-badge.fr-badge--sm.fr-badge--info
|
||||
= t('.flagged_pending_corrections')
|
||||
%span.date{ class: ["fr-text--xs", "fr-text-mention--grey", "font-weight-normal", highlight_if_unseen_class], data: scroll_to_target }
|
||||
= commentaire_date
|
||||
.rich-text
|
||||
|
|
|
@ -223,6 +223,34 @@ module Instructeurs
|
|||
render :change_state
|
||||
end
|
||||
|
||||
def pending_corrections
|
||||
message, piece_jointe = params.require(:dossier).permit(:motivation, :justificatif_motivation).values
|
||||
|
||||
if message.empty?
|
||||
flash.alert = "Vous devez préciser quelle modification est attendue."
|
||||
elsif !dossier.may_flag_as_pending_correction?
|
||||
flash.alert = dossier.termine? ? "Impossible de demander de corriger un dossier terminé." : "Le dossier est déjà en attente de correction."
|
||||
else
|
||||
commentaire = CommentaireService.create(current_instructeur, dossier, { body: message, piece_jointe: })
|
||||
dossier.flag_as_pending_correction!(commentaire)
|
||||
dossier.update!(last_commentaire_updated_at: Time.zone.now)
|
||||
current_instructeur.follow(dossier)
|
||||
|
||||
flash.notice = "Dossier marqué comme en attente de correction."
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
@dossier = dossier
|
||||
render :change_state
|
||||
end
|
||||
|
||||
format.html do
|
||||
redirect_back(fallback_location: instructeur_procedure_path(procedure))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_commentaire
|
||||
@commentaire = CommentaireService.create(current_instructeur, dossier, commentaire_params)
|
||||
|
||||
|
|
|
@ -95,6 +95,10 @@ class Commentaire < ApplicationRecord
|
|||
update! body: ''
|
||||
end
|
||||
|
||||
def flagged_pending_corrections?
|
||||
DossierResolution.exists?(commentaire: self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify
|
||||
|
|
|
@ -4,6 +4,22 @@ module DossierResolvableConcern
|
|||
included do
|
||||
has_many :resolutions, class_name: 'DossierResolution', dependent: :destroy
|
||||
|
||||
def flag_as_pending_correction!(commentaire)
|
||||
return unless may_flag_as_pending_correction?
|
||||
|
||||
resolutions.create(commentaire:)
|
||||
|
||||
return if en_construction?
|
||||
|
||||
repasser_en_construction!(instructeur: commentaire.instructeur)
|
||||
end
|
||||
|
||||
def may_flag_as_pending_correction?
|
||||
return false if resolutions.pending.exists?
|
||||
|
||||
en_construction? || may_repasser_en_construction?
|
||||
end
|
||||
|
||||
def pending_resolution?
|
||||
# We don't want to show any alert if user is not allowed to modify the dossier
|
||||
return false unless en_construction?
|
||||
|
|
|
@ -1,35 +1,56 @@
|
|||
- if dossier.en_instruction?
|
||||
- if dossier.en_instruction? || (dossier.en_construction? && dossier.may_flag_as_pending_correction?)
|
||||
= render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: { data: { turbo_force: :server } }, button_options: { class: [button_or_label_class(dossier)]}, role: dossier.en_instruction? ? :region : :menu) do |menu|
|
||||
- menu.with_button_inner_html do
|
||||
Instruire le dossier
|
||||
= dossier.en_instruction? ? "Instruire le dossier" : "Demander une modification"
|
||||
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'accept');", role: 'menuitem') do
|
||||
%span.icon.accept
|
||||
.dropdown-description
|
||||
%h4 Accepter
|
||||
L’usager sera informé que son dossier a été accepté
|
||||
- if dossier.en_instruction?
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'accept');", role: 'menuitem') do
|
||||
%span.icon.accept
|
||||
.dropdown-description
|
||||
%h4 Accepter
|
||||
L’usager sera informé que son dossier a été accepté
|
||||
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
|
||||
|
||||
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'without-continuation');", role: 'menuitem') do
|
||||
%span.icon.without-continuation
|
||||
.dropdown-description
|
||||
%h4 Classer sans suite
|
||||
L’usager sera informé que son dossier a été classé sans suite
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'without-continuation');", role: 'menuitem') do
|
||||
%span.icon.without-continuation
|
||||
.dropdown-description
|
||||
%h4 Classer sans suite
|
||||
L’usager sera informé que son dossier a été classé sans suite
|
||||
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
|
||||
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'refuse');", role: 'menuitem') do
|
||||
%span.icon.refuse
|
||||
.dropdown-description
|
||||
%h4 Refuser
|
||||
L’usager sera informé que son dossier a été refusé
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'refuse');", role: 'menuitem') do
|
||||
%span.icon.refuse
|
||||
.dropdown-description
|
||||
%h4 Refuser
|
||||
L’usager sera informé que son dossier a été refusé
|
||||
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
|
||||
- menu.with_item(class: "hidden inactive form-inside") do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier, placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
|
||||
|
||||
- if dossier.may_flag_as_pending_correction?
|
||||
- menu.with_item do
|
||||
= link_to('#', onclick: "DS.showMotivation(event, 'pending_corrections');", role: 'menuitem') do
|
||||
%span.fr-icon.fr-icon-error-warning-line.fr-text-default--info{ "aria-hidden": "true" }
|
||||
.dropdown-description
|
||||
%h4 Demander une modification
|
||||
L’usager sera informé que des modifications sont attendues
|
||||
|
||||
- menu.with_item(class: class_names("inactive form-inside": true, hidden: dossier.en_instruction?)) do
|
||||
= render partial: 'instructeurs/dossiers/instruction_button_motivation', locals: { dossier: dossier,
|
||||
visible: !dossier.en_instruction?,
|
||||
form_path: pending_corrections_instructeur_dossier_path(dossier.procedure, dossier),
|
||||
placeholder: 'Expliquez au demandeur quelles modifications sont attendues',
|
||||
popup_class: 'pending_corrections',
|
||||
button_justificatif_label: "Ajouter une pièce jointe (facultatif)",
|
||||
process_button: dossier.en_construction? ? 'Valider' : 'Valider et repasser en construction',
|
||||
process_action: nil,
|
||||
title: 'Marquer en attente de corrections',
|
||||
confirm: 'Envoyer la demande de corrections ?'}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.motivation.hidden{ class: popup_class }
|
||||
= form_tag(terminer_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo: true, turbo_confirm: confirm }, method: :post, multipart: true) do
|
||||
.motivation{ class: class_names(popup_class => true, hidden: !defined?(visible) || !visible) }
|
||||
= form_tag(defined?(form_path) ? form_path : terminer_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo: true, turbo_confirm: confirm }, method: :post, multipart: true) do
|
||||
- if title == 'Accepter'
|
||||
= text_area :dossier, :motivation, class: 'fr-input', placeholder: placeholder, required: false
|
||||
- if dossier.attestation_template&.activated?
|
||||
|
@ -28,11 +28,11 @@
|
|||
- else
|
||||
= text_area :dossier, :motivation, class: 'fr-input', placeholder: placeholder, required: true
|
||||
.optional-justificatif{ id: "justificatif_motivation_suggest_#{popup_class}", onclick: "DS.showImportJustificatif('#{popup_class}');" }
|
||||
%button.fr-btn.fr-btn--tertiary-no-outline.fr-btn--icon-left.fr-icon-attachment-line.fr-ml-0{ type: 'button', onclick: "DS.showImportJustificatif('accept');" } Ajouter un justificatif (optionnel)
|
||||
%button.fr-btn.fr-btn--tertiary-no-outline.fr-btn--icon-left.fr-icon-attachment-line.fr-ml-0{ type: 'button', onclick: "DS.showImportJustificatif('accept');" }= defined?(button_justificatif_label) ? button_justificatif_label : "Ajouter un justificatif (optionnel)"
|
||||
.hidden{ id: "justificatif_motivation_import_#{popup_class}" }
|
||||
= file_field :dossier, :justificatif_motivation, direct_upload: true, id: "dossier_justificatif_motivation_#{popup_class}",onchange: "DS.showDeleteJustificatif('#{popup_class}');"
|
||||
.hidden.js_delete_motivation{ id: "delete_motivation_import_#{popup_class}" }
|
||||
%button.fr-btn.fr-btn--tertiary-no-outline.fr-btn--icon-left.fr-icon-delete-line.fr-ml-0.fr-mt-1w{ type: 'button', onclick: "DS.deleteJustificatif('#{popup_class}');" } Supprimer le justificatif
|
||||
.fr-mt-2w
|
||||
= button_tag "Annuler", type: :reset, class: 'fr-btn fr-btn--secondary', onclick: 'DS.motivationCancel();'
|
||||
= button_tag 'Valider la décision', name: :process_action, value: process_action, class: 'fr-btn fr-mr-0', title: title
|
||||
= button_tag defined?(process_button) ? process_button : 'Valider la décision', name: :process_action, value: process_action, class: 'fr-btn fr-mr-0', title: title
|
||||
|
|
|
@ -445,6 +445,7 @@ Rails.application.routes.draw do
|
|||
post 'repasser-en-construction' => 'dossiers#repasser_en_construction'
|
||||
post 'repasser-en-instruction' => 'dossiers#repasser_en_instruction'
|
||||
post 'terminer'
|
||||
post 'pending_corrections'
|
||||
post 'send-to-instructeurs' => 'dossiers#send_to_instructeurs'
|
||||
post 'avis' => 'dossiers#create_avis'
|
||||
get 'print' => 'dossiers#print'
|
||||
|
|
|
@ -496,6 +496,90 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#pending_corrections' do
|
||||
let(:message) { 'do that' }
|
||||
let(:justificatif) { nil }
|
||||
|
||||
subject do
|
||||
post :pending_corrections, params: {
|
||||
procedure_id: procedure.id, dossier_id: dossier.id,
|
||||
dossier: { motivation: message, justificatif_motivation: justificatif }
|
||||
}, format: :turbo_stream
|
||||
end
|
||||
|
||||
before { sign_in(instructeur.user) }
|
||||
|
||||
context "dossier en instruction" do
|
||||
let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) }
|
||||
|
||||
before { subject }
|
||||
|
||||
it 'pass en_construction and create a pending resolution' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include('en attente de modifications')
|
||||
|
||||
expect(dossier.reload).to be_en_construction
|
||||
expect(dossier).to be_pending_resolution
|
||||
end
|
||||
|
||||
it 'create a comment with text body' do
|
||||
expect(dossier.commentaires.last.body).to eq("do that")
|
||||
expect(dossier.commentaires.last).to be_flagged_pending_corrections
|
||||
end
|
||||
|
||||
context 'with an attachment' do
|
||||
let(:justificatif) { fake_justificatif }
|
||||
|
||||
it 'attach file to comment' do
|
||||
expect(dossier.commentaires.last.piece_jointe).to be_attached
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an empty message' do
|
||||
let(:message) { '' }
|
||||
|
||||
it 'requires a message' do
|
||||
expect(dossier.reload).not_to be_pending_resolution
|
||||
expect(dossier.commentaires.count).to eq(0)
|
||||
expect(response.body).to include('Vous devez préciser')
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossier already having pending corrections' do
|
||||
before do
|
||||
create(:dossier_resolution, dossier:)
|
||||
end
|
||||
|
||||
it 'does not create an new pending resolution' do
|
||||
expect { subject }.not_to change { DossierResolution.count }
|
||||
end
|
||||
|
||||
it 'shows a flash alert' do
|
||||
subject
|
||||
|
||||
expect(response.body).to include('')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossier en_construction' do
|
||||
it 'can create a pending resolution' do
|
||||
subject
|
||||
expect(dossier.reload).to be_pending_resolution
|
||||
expect(dossier.commentaires.last).to be_flagged_pending_corrections
|
||||
end
|
||||
end
|
||||
|
||||
context 'dossier is termine' do
|
||||
let(:dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure) }
|
||||
|
||||
it 'does not create a pending resolution' do
|
||||
expect { subject }.not_to change { DossierResolution.count }
|
||||
expect(response.body).to include('Impossible')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#messagerie' do
|
||||
before { expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :messagerie) }
|
||||
subject { get :messagerie, params: { procedure_id: procedure.id, dossier_id: dossier.id } }
|
||||
|
|
|
@ -25,4 +25,56 @@ describe DossierResolvableConcern do
|
|||
it { expect(dossier.pending_resolution?).to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#flag_as_pending_correction!' do
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:commentaire) { create(:commentaire, dossier:, instructeur:) }
|
||||
|
||||
context 'when dossier is en_construction' do
|
||||
it 'creates a resolution' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.to change { dossier.resolutions.pending.count }.by(1)
|
||||
end
|
||||
|
||||
it 'does not change dossier state' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.not_to change { dossier.state }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier is not en_instruction' do
|
||||
let(:dossier) { create(:dossier, :en_instruction) }
|
||||
|
||||
it 'creates a resolution' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.to change { dossier.resolutions.pending.count }.by(1)
|
||||
end
|
||||
|
||||
it 'repasse dossier en_construction' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.to change { dossier.state }.to('en_construction')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier has already a pending resolution' do
|
||||
before { create(:dossier_resolution, dossier:) }
|
||||
|
||||
it 'does not create a resolution' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.not_to change { dossier.resolutions.pending.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier has already a resolved resolution' do
|
||||
before { create(:dossier_resolution, :resolved, dossier:) }
|
||||
|
||||
it 'creates a resolution' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.to change { dossier.resolutions.pending.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier is not en_construction and may not be repassed en_construction' do
|
||||
let(:dossier) { create(:dossier, :accepte) }
|
||||
|
||||
it 'does not create a resolution' do
|
||||
expect { dossier.flag_as_pending_correction!(commentaire) }.not_to change { dossier.resolutions.pending.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,15 +29,25 @@ describe 'instructeurs/dossiers/instruction_button', type: :view do
|
|||
end
|
||||
end
|
||||
|
||||
context 'en_construction' do
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
|
||||
it 'renders a dropdown' do
|
||||
expect(rendered).to have_dropdown_title('Demander une modification')
|
||||
expect(rendered).to have_dropdown_items(count: 2) # form is already expanded so we have 2 visible items
|
||||
end
|
||||
end
|
||||
|
||||
context 'en_instruction' do
|
||||
let(:dossier) { create(:dossier, :en_instruction) }
|
||||
|
||||
it 'renders a dropdown' do
|
||||
expect(rendered).to have_dropdown_title('Instruire le dossier')
|
||||
expect(rendered).to have_dropdown_items(count: 3)
|
||||
expect(rendered).to have_dropdown_items(count: 4)
|
||||
expect(rendered).to have_dropdown_item('Accepter')
|
||||
expect(rendered).to have_dropdown_item('Classer sans suite')
|
||||
expect(rendered).to have_dropdown_item('Refuser')
|
||||
expect(rendered).to have_dropdown_item('Demander une modification')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,7 +61,8 @@ describe 'instructeurs/dossiers/show', type: :view do
|
|||
within("form[action=\"#{follow_instructeur_dossier_path(dossier.procedure, dossier)}\"]") do
|
||||
expect(subject).to have_button('Suivre le dossier')
|
||||
end
|
||||
expect(subject).to have_selector('.header-actions ul:first-child .fr-btn', count: 2)
|
||||
expect(subject).to have_button('Demander une modification')
|
||||
expect(subject).to have_selector('.header-actions ul:first-child > li.instruction-button', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue