feat(dossier): do not save dossier on submit

This commit is contained in:
Paul Chavard 2022-10-11 12:21:06 +02:00
parent 12ddb0a8e8
commit d409b6f4ef
3 changed files with 162 additions and 229 deletions

View file

@ -5,13 +5,13 @@ module Users
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret] layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all] ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire, :papertrail, :restore] ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update, :create_commentaire, :papertrail, :restore]
before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update] before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :submit_brouillon, :modifier, :update]
before_action :forbid_invite_submission!, only: [:update_brouillon] before_action :forbid_invite_submission!, only: [:submit_brouillon]
before_action :forbid_closed_submission!, only: [:update_brouillon] before_action :forbid_closed_submission!, only: [:submit_brouillon]
before_action :show_demarche_en_test_banner before_action :show_demarche_en_test_banner
before_action :store_user_location!, only: :new before_action :store_user_location!, only: :new
@ -159,35 +159,24 @@ module Users
end end
end end
# FIXME: def submit_brouillon
# - delegate draft save logic to champ ?
def update_brouillon
@dossier = dossier_with_champs @dossier = dossier_with_champs
errors = submit_dossier_and_compute_errors
errors = update_dossier_and_compute_errors if errors.blank?
if passage_en_construction? && errors.blank?
@dossier.passer_en_construction! @dossier.passer_en_construction!
NotificationMailer.send_en_construction_notification(@dossier).deliver_later NotificationMailer.send_en_construction_notification(@dossier).deliver_later
@dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later
end end
return redirect_to(merci_dossier_path(@dossier))
elsif errors.present? redirect_to merci_dossier_path(@dossier)
flash.now.alert = errors
else else
flash.now.notice = t('.draft_saved') flash.now.alert = errors
end
respond_to do |format| respond_to do |format|
format.html { render :brouillon } format.html { render :brouillon }
format.turbo_stream do format.turbo_stream
@to_shows, @to_hides = @dossier.champs
.filter(&:conditional?)
.partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) }
render(:update, layout: false)
end end
end end
end end
@ -202,6 +191,23 @@ module Users
@dossier = dossier_with_champs @dossier = dossier_with_champs
end end
def update_brouillon
@dossier = dossier_with_champs
update_dossier_and_compute_errors
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream do
@to_shows, @to_hides = @dossier.champs
.filter(&:conditional?)
.partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) }
render(:update, layout: false)
end
end
end
def update def update
@dossier = dossier_with_champs @dossier = dossier_with_champs
errors = update_dossier_and_compute_errors errors = update_dossier_and_compute_errors
@ -217,8 +223,6 @@ module Users
.filter(&:conditional?) .filter(&:conditional?)
.partition(&:visible?) .partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) } .map { |champs| champs_to_one_selector(champs) }
render layout: false
end end
end end
end end
@ -450,22 +454,34 @@ module Users
end end
if !@dossier.save(**validation_options) if !@dossier.save(**validation_options)
errors += @dossier.errors.full_messages errors += @dossier.errors.full_messages
elsif should_change_groupe_instructeur? end
if should_change_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params) @dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
end end
end end
if dossier.en_construction?
errors += @dossier.check_mandatory_champs
end
errors
end
def submit_dossier_and_compute_errors
errors = []
@dossier.valid?(**submit_validation_options)
errors += @dossier.errors.full_messages
errors += @dossier.check_mandatory_champs
if should_fill_groupe_instructeur? if should_fill_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur) @dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
end end
if !save_draft?
errors += @dossier.check_mandatory_champs
if @dossier.groupe_instructeur.nil? if @dossier.groupe_instructeur.nil?
errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli" errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
end end
end
errors errors
end end
@ -483,13 +499,13 @@ module Users
end end
def forbid_invite_submission! def forbid_invite_submission!
if passage_en_construction? && !current_user.owns?(dossier) if !current_user.owns?(dossier)
forbidden! forbidden!
end end
end end
def forbid_closed_submission! def forbid_closed_submission!
if passage_en_construction? && !dossier.can_transition_to_en_construction? if !dossier.can_transition_to_en_construction?
forbidden! forbidden!
end end
end end
@ -516,23 +532,19 @@ module Users
params.require(:commentaire).permit(:body, :piece_jointe) params.require(:commentaire).permit(:body, :piece_jointe)
end end
def passage_en_construction? def submit_validation_options
dossier.brouillon? && !save_draft?
end
def save_draft?
dossier.brouillon? && !params[:submit_draft]
end
def validation_options
if save_draft?
{ context: :brouillon }
else
# rubocop:disable Lint/BooleanSymbol # rubocop:disable Lint/BooleanSymbol
# Force ActiveRecord to re-validate associated records. # Force ActiveRecord to re-validate associated records.
{ context: :false } { context: :false }
# rubocop:enable Lint/BooleanSymbol # rubocop:enable Lint/BooleanSymbol
end end
def validation_options
if dossier.brouillon?
{ context: :brouillon }
else
submit_validation_options
end
end end
def champs_to_one_selector(champs) def champs_to_one_selector(champs)

View file

@ -276,6 +276,7 @@ Rails.application.routes.draw do
get 'etablissement' get 'etablissement'
get 'brouillon' get 'brouillon'
patch 'brouillon', to: 'dossiers#update_brouillon' patch 'brouillon', to: 'dossiers#update_brouillon'
post 'brouillon', to: 'dossiers#submit_brouillon'
get 'modifier', to: 'dossiers#modifier' get 'modifier', to: 'dossiers#modifier'
patch 'modifier', to: 'dossiers#update' patch 'modifier', to: 'dossiers#update'
get 'merci' get 'merci'

View file

@ -98,38 +98,21 @@ describe Users::DossiersController, type: :controller do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:asked_dossier) { create(:dossier) } let(:asked_dossier) { create(:dossier) }
let(:ensure_authorized) { :forbid_invite_submission! } let(:ensure_authorized) { :forbid_invite_submission! }
let(:submit) { true }
before do before do
@controller.params = @controller.params.merge(dossier_id: asked_dossier.id, submit_draft: submit) @controller.params = @controller.params.merge(dossier_id: asked_dossier.id)
allow(@controller).to receive(:current_user).and_return(user) allow(@controller).to receive(:current_user).and_return(user)
allow(@controller).to receive(:redirect_to) allow(@controller).to receive(:redirect_to)
end end
context 'when a user save their own draft' do
let(:asked_dossier) { create(:dossier, user: user) }
let(:submit) { false }
it_behaves_like 'does not redirect nor flash'
end
context 'when a user submit their own dossier' do context 'when a user submit their own dossier' do
let(:asked_dossier) { create(:dossier, user: user) } let(:asked_dossier) { create(:dossier, user: user) }
let(:submit) { true }
it_behaves_like 'does not redirect nor flash'
end
context 'when an invite save the draft for a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user) }
let(:submit) { false }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when an invite submit a dossier where they where invited' do context 'when an invite submit a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user) } before { create(:invite, dossier: asked_dossier, user: user) }
let(:submit) { true }
it_behaves_like 'redirects and flashes' it_behaves_like 'redirects and flashes'
end end
@ -355,30 +338,18 @@ describe Users::DossiersController, type: :controller do
end end
end end
describe '#update_brouillon' do describe '#submit_brouillon' do
before { sign_in(user) } before { sign_in(user) }
let!(:dossier) { create(:dossier, user: user) } let!(:dossier) { create(:dossier, user: user) }
let(:first_champ) { dossier.champs.first } let(:first_champ) { dossier.champs.first }
let(:value) { 'beautiful value' } let(:value) { 'beautiful value' }
let(:now) { Time.zone.parse('01/01/2100') } let(:now) { Time.zone.parse('01/01/2100') }
let(:submit_payload) do let(:payload) { { id: dossier.id } }
{
id: dossier.id,
dossier: {
groupe_instructeur_id: dossier.groupe_instructeur_id,
champs_attributes: {
id: first_champ.id,
value: value
}
}
}
end
let(:payload) { submit_payload.merge(submit_draft: true) }
subject do subject do
Timecop.freeze(now) do Timecop.freeze(now) do
patch :update_brouillon, params: payload post :submit_brouillon, params: payload
end end
end end
@ -393,120 +364,6 @@ describe Users::DossiersController, type: :controller do
end end
end end
context 'when dossier can be updated by the owner' do
it 'updates the champs' do
subject
expect(response).to redirect_to(merci_dossier_path(dossier))
expect(first_champ.reload.value).to eq('beautiful value')
expect(dossier.reload.updated_at.year).to eq(2100)
expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction))
end
context 'without new values for champs' do
let(:submit_payload) do
{
id: dossier.id,
dossier: {
champs_attributes: {}
}
}
end
it "doesn't set last_champ_updated_at" do
subject
expect(dossier.reload.last_champ_updated_at).to eq(nil)
end
end
context 'with instructeurs ok to be notified instantly' do
let!(:instructeur_with_instant_email_dossier) { create(:instructeur) }
let!(:instructeur_without_instant_email_dossier) { create(:instructeur) }
before do
allow(DossierMailer).to receive(:notify_new_dossier_depose_to_instructeur).and_return(double(deliver_later: nil))
create(:assign_to, instructeur: instructeur_with_instant_email_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true)
create(:assign_to, instructeur: instructeur_without_instant_email_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: false)
end
it "sends notification mail to instructeurs" do
subject
expect(DossierMailer).to have_received(:notify_new_dossier_depose_to_instructeur).once.with(dossier, instructeur_with_instant_email_dossier.email)
expect(DossierMailer).not_to have_received(:notify_new_dossier_depose_to_instructeur).with(dossier, instructeur_without_instant_email_dossier.email)
end
end
context 'with procedure routee' do
let(:procedure) { create(:procedure, :routee, :with_type_de_champ) }
let(:dossier_group) { create(:groupe_instructeur, procedure: procedure) }
let(:another_group) { create(:groupe_instructeur, procedure: procedure) }
let(:instructeur_of_dossier) { create(:instructeur) }
let(:instructeur_in_another_group) { create(:instructeur) }
context "and grope instructeur is set" do
let!(:dossier) { create(:dossier, groupe_instructeur: dossier_group, user: user) }
before do
allow(DossierMailer).to receive(:notify_new_dossier_depose_to_instructeur).and_return(double(deliver_later: nil))
create(:assign_to, instructeur: instructeur_of_dossier, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true, groupe_instructeur: dossier_group)
create(:assign_to, instructeur: instructeur_in_another_group, procedure: dossier.procedure, instant_email_dossier_notifications_enabled: true, groupe_instructeur: another_group)
end
it "sends notification mail to instructeurs in the correct group instructeur" do
subject
expect(DossierMailer).to have_received(:notify_new_dossier_depose_to_instructeur).once.with(dossier, instructeur_of_dossier.email)
expect(DossierMailer).not_to have_received(:notify_new_dossier_depose_to_instructeur).with(dossier, instructeur_in_another_group.email)
end
end
context "and groupe instructeur is not set" do
let(:dossier) { create(:dossier, procedure: procedure, user: user) }
let(:submit_payload) do
{
id: dossier.id,
dossier: {
champs_attributes: {
id: first_champ.id,
value: value
}
},
submit_draft: false
}
end
it "can not submit" do
subject
expect(flash.alert).to eq(['Le champ « Votre ville » doit être rempli'])
end
end
end
context "when the dossier was created on a routee procedure, but routage was later disabled" do
let(:dossier) { create(:dossier, groupe_instructeur: nil, user: user) }
it "sets a default groupe_instructeur" do
subject
expect(response).to redirect_to(merci_dossier_path(dossier))
expect(dossier.reload.groupe_instructeur).to eq(dossier.procedure.defaut_groupe_instructeur)
end
end
context "on an closed procedure" do
before { dossier.procedure.close! }
it "it does not change state" do
subject
expect(response).not_to redirect_to(merci_dossier_path(dossier))
expect(dossier.reload.state).to eq(Dossier.states.fetch(:brouillon))
end
end
end
it 'sends an email only on the first #update_brouillon' do it 'sends an email only on the first #update_brouillon' do
delivery = double delivery = double
expect(delivery).to receive(:deliver_later).with(no_args) expect(delivery).to receive(:deliver_later).with(no_args)
@ -523,9 +380,9 @@ describe Users::DossiersController, type: :controller do
context 'when the update fails' do context 'when the update fails' do
before do before do
expect_any_instance_of(Dossier).to receive(:save).and_return(false) expect_any_instance_of(Dossier).to receive(:valid?).and_return(false)
expect_any_instance_of(Dossier).to receive(:errors) expect_any_instance_of(Dossier).to receive(:errors)
.and_return(double(full_messages: ['nop'])) .and_return(double('errors', full_messages: ['nop']))
subject subject
end end
@ -550,21 +407,6 @@ describe Users::DossiersController, type: :controller do
it { expect(response).to render_template(:brouillon) } it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) } it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
context 'and the user saves a draft' do
let(:payload) { submit_payload.except(:submit_draft) }
it { expect(response).to render_template(:brouillon) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') }
it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:brouillon)) }
context 'and the dossier is in construction' do
let!(:dossier) { create(:dossier, :en_construction, user: user) }
it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(['Le champ l doit être rempli.']) }
end
end
end end
context 'when dossier has no champ' do context 'when dossier has no champ' do
@ -581,19 +423,6 @@ describe Users::DossiersController, type: :controller do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
let!(:invite) { create(:invite, dossier: dossier, user: user) } let!(:invite) { create(:invite, dossier: dossier, user: user) }
context 'and the invite saves a draft' do
let(:payload) { submit_payload.except(:submit_draft) }
before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
subject
end
it { expect(response).to render_template(:brouillon) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') }
it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:brouillon)) }
end
context 'and the invite tries to submit the dossier' do context 'and the invite tries to submit the dossier' do
before { subject } before { subject }
@ -603,6 +432,101 @@ describe Users::DossiersController, type: :controller do
end end
end end
describe '#update_brouillon' do
before { sign_in(user) }
let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_piece_justificative) }
let!(:dossier) { create(:dossier, user: user, procedure: procedure) }
let(:first_champ) { dossier.champs.first }
let(:piece_justificative_champ) { dossier.champs.last }
let(:value) { 'beautiful value' }
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
let(:now) { Time.zone.parse('01/01/2100') }
let(:submit_payload) do
{
id: dossier.id,
dossier: {
groupe_instructeur_id: dossier.groupe_instructeur_id,
champs_attributes: [
{
id: first_champ.id,
value: value
},
{
id: piece_justificative_champ.id,
piece_justificative_file: file
}
]
}
}
end
let(:payload) { submit_payload }
subject do
Timecop.freeze(now) do
patch :update_brouillon, params: payload
end
end
context 'when the dossier cannot be updated by the user' do
let!(:dossier) { create(:dossier, :en_instruction, user: user) }
it 'redirects to the dossiers list' do
subject
expect(response).to redirect_to(dossiers_path)
expect(flash.alert).to eq('Votre dossier ne peut plus être modifié')
end
end
context 'when dossier can be updated by the owner' do
it 'updates the champs' do
subject
expect(response).to have_http_status(:ok)
expect(dossier.reload.updated_at.year).to eq(2100)
expect(dossier.reload.state).to eq(Dossier.states.fetch(:brouillon))
end
context 'without new values for champs' do
let(:submit_payload) do
{
id: dossier.id,
dossier: {
champs_attributes: {}
}
}
end
it "doesn't set last_champ_updated_at" do
subject
expect(dossier.reload.last_champ_updated_at).to eq(nil)
end
end
end
context 'when dossier has no champ' do
let(:submit_payload) { { id: dossier.id } }
it 'does not raise any errors' do
subject
expect(response).to have_http_status(:ok)
end
end
context 'when the user has an invitation but is not the owner' do
let(:dossier) { create(:dossier) }
let!(:invite) { create(:invite, dossier: dossier, user: user) }
before { subject }
it { expect(first_champ.reload.value).to eq('beautiful value') }
it { expect(response).to have_http_status(:ok) }
end
end
describe '#update' do describe '#update' do
before { sign_in(user) } before { sign_in(user) }
@ -751,16 +675,12 @@ describe Users::DossiersController, type: :controller do
end end
context 'when the user has an invitation but is not the owner' do context 'when the user has an invitation but is not the owner' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, :en_construction) }
let!(:invite) { create(:invite, dossier: dossier, user: user) } let!(:invite) { create(:invite, dossier: dossier, user: user) }
before do before { subject }
dossier.passer_en_construction!
subject
end
it { expect(first_champ.reload.value).to eq('beautiful value') } it { expect(first_champ.reload.value).to eq('beautiful value') }
it { expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction)) }
it { expect(response).to have_http_status(:ok) } it { expect(response).to have_http_status(:ok) }
end end