Merge pull request #7460 from tchak/fix-dossier-submit
feat(dossier): do not update dossier champs on submit
This commit is contained in:
commit
2354d21cdd
18 changed files with 340 additions and 300 deletions
24
app/components/dossiers/edit_footer_component.rb
Normal file
24
app/components/dossiers/edit_footer_component.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class Dossiers::EditFooterComponent < ApplicationComponent
|
||||
def initialize(dossier:)
|
||||
@dossier = dossier
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def owner?
|
||||
controller.current_user.owns?(@dossier)
|
||||
end
|
||||
|
||||
def button_options
|
||||
{
|
||||
class: 'fr-btn fr-btn--sm',
|
||||
disabled: !owner?,
|
||||
method: :post,
|
||||
data: { 'disable-with': t('.sbumitting'), controller: 'autosave-submit' }
|
||||
}
|
||||
end
|
||||
|
||||
def render?
|
||||
!@dossier.for_procedure_preview?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
en:
|
||||
submit: Submit the file
|
||||
submitting: Submitting…
|
||||
invite_notice: You are invited to make amendments to this file but only the owner themselves can submit it.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
fr:
|
||||
submit: Déposer le dossier
|
||||
submitting: Envoi en cours…
|
||||
invite_notice: En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le déposer lui-même.
|
|
@ -0,0 +1,12 @@
|
|||
.dossier-edit-sticky-footer
|
||||
.send-dossier-actions-bar
|
||||
= render partial: 'shared/dossiers/autosave', locals: { dossier: @dossier }
|
||||
|
||||
- if @dossier.can_transition_to_en_construction?
|
||||
= button_to t('.submit'), brouillon_dossier_url(@dossier), button_options
|
||||
|
||||
- if @dossier.brouillon? && !owner?
|
||||
.send-notice.invite-cannot-submit
|
||||
= t('.invite_notice')
|
||||
|
||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }
|
|
@ -5,13 +5,13 @@ module Users
|
|||
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
|
||||
|
||||
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_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 :forbid_invite_submission!, only: [:update_brouillon]
|
||||
before_action :forbid_closed_submission!, only: [:update_brouillon]
|
||||
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :submit_brouillon, :modifier, :update]
|
||||
before_action :forbid_invite_submission!, only: [:submit_brouillon]
|
||||
before_action :forbid_closed_submission!, only: [:submit_brouillon]
|
||||
before_action :show_demarche_en_test_banner
|
||||
before_action :store_user_location!, only: :new
|
||||
|
||||
|
@ -159,35 +159,24 @@ module Users
|
|||
end
|
||||
end
|
||||
|
||||
# FIXME:
|
||||
# - delegate draft save logic to champ ?
|
||||
def update_brouillon
|
||||
def submit_brouillon
|
||||
@dossier = dossier_with_champs
|
||||
errors = submit_dossier_and_compute_errors
|
||||
|
||||
errors = update_dossier_and_compute_errors
|
||||
|
||||
if passage_en_construction? && errors.blank?
|
||||
if errors.blank?
|
||||
@dossier.passer_en_construction!
|
||||
NotificationMailer.send_en_construction_notification(@dossier).deliver_later
|
||||
@dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
|
||||
DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later
|
||||
end
|
||||
return redirect_to(merci_dossier_path(@dossier))
|
||||
elsif errors.present?
|
||||
flash.now.alert = errors
|
||||
|
||||
redirect_to merci_dossier_path(@dossier)
|
||||
else
|
||||
flash.now.notice = t('.draft_saved')
|
||||
end
|
||||
flash.now.alert = 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)
|
||||
respond_to do |format|
|
||||
format.html { render :brouillon }
|
||||
format.turbo_stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -202,6 +191,23 @@ module Users
|
|||
@dossier = dossier_with_champs
|
||||
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
|
||||
@dossier = dossier_with_champs
|
||||
errors = update_dossier_and_compute_errors
|
||||
|
@ -217,8 +223,6 @@ module Users
|
|||
.filter(&:conditional?)
|
||||
.partition(&:visible?)
|
||||
.map { |champs| champs_to_one_selector(champs) }
|
||||
|
||||
render layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -450,21 +454,33 @@ module Users
|
|||
end
|
||||
if !@dossier.save(**validation_options)
|
||||
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)
|
||||
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?
|
||||
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
|
||||
end
|
||||
|
||||
if !save_draft?
|
||||
errors += @dossier.check_mandatory_champs
|
||||
|
||||
if @dossier.groupe_instructeur.nil?
|
||||
errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
|
||||
end
|
||||
if @dossier.groupe_instructeur.nil?
|
||||
errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
|
||||
end
|
||||
|
||||
errors
|
||||
|
@ -483,13 +499,13 @@ module Users
|
|||
end
|
||||
|
||||
def forbid_invite_submission!
|
||||
if passage_en_construction? && !current_user.owns?(dossier)
|
||||
if !current_user.owns?(dossier)
|
||||
forbidden!
|
||||
end
|
||||
end
|
||||
|
||||
def forbid_closed_submission!
|
||||
if passage_en_construction? && !dossier.can_transition_to_en_construction?
|
||||
if !dossier.can_transition_to_en_construction?
|
||||
forbidden!
|
||||
end
|
||||
end
|
||||
|
@ -516,22 +532,18 @@ module Users
|
|||
params.require(:commentaire).permit(:body, :piece_jointe)
|
||||
end
|
||||
|
||||
def passage_en_construction?
|
||||
dossier.brouillon? && !save_draft?
|
||||
end
|
||||
|
||||
def save_draft?
|
||||
dossier.brouillon? && !params[:submit_draft]
|
||||
def submit_validation_options
|
||||
# rubocop:disable Lint/BooleanSymbol
|
||||
# Force ActiveRecord to re-validate associated records.
|
||||
{ context: :false }
|
||||
# rubocop:enable Lint/BooleanSymbol
|
||||
end
|
||||
|
||||
def validation_options
|
||||
if save_draft?
|
||||
if dossier.brouillon?
|
||||
{ context: :brouillon }
|
||||
else
|
||||
# rubocop:disable Lint/BooleanSymbol
|
||||
# Force ActiveRecord to re-validate associated records.
|
||||
{ context: :false }
|
||||
# rubocop:enable Lint/BooleanSymbol
|
||||
submit_validation_options
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ export class AutosaveStatusController extends ApplicationController {
|
|||
connect(): void {
|
||||
this.onGlobal('autosave:enqueue', () => this.didEnqueue());
|
||||
this.onGlobal('autosave:end', () => this.didSucceed());
|
||||
// This event is used in tests to reset the state of the controller
|
||||
this.onGlobal('autosave:reset', () => this.didReset());
|
||||
this.onGlobal<CustomEvent>('autosave:error', (event) =>
|
||||
this.didFail(event)
|
||||
);
|
||||
|
@ -46,6 +48,10 @@ export class AutosaveStatusController extends ApplicationController {
|
|||
this.debounce(this.hideSucceededStatus, AUTOSAVE_STATUS_VISIBLE_DURATION);
|
||||
}
|
||||
|
||||
private didReset() {
|
||||
this.setState('idle');
|
||||
}
|
||||
|
||||
private didFail(event: CustomEvent<{ error: ResponseError }>) {
|
||||
const error = event.detail.error;
|
||||
|
||||
|
|
61
app/javascript/controllers/autosave_submit_controller.ts
Normal file
61
app/javascript/controllers/autosave_submit_controller.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { isButtonElement } from '@utils';
|
||||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class AutosaveSubmitController extends ApplicationController {
|
||||
#isSaving = false;
|
||||
#shouldSubmit = true;
|
||||
#buttonText?: string;
|
||||
|
||||
connect(): void {
|
||||
this.onGlobal('autosave:enqueue', () => this.didEnqueue());
|
||||
this.onGlobal('autosave:end', () => this.didSucceed());
|
||||
this.onGlobal('autosave:error', () => this.didFail());
|
||||
this.on('click', (event) => this.onClick(event));
|
||||
}
|
||||
|
||||
// Intercept form submit if autosave is still in progress
|
||||
private onClick(event: Event) {
|
||||
if (this.#isSaving) {
|
||||
this.#shouldSubmit = true;
|
||||
this.disableButton();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private didEnqueue() {
|
||||
this.#isSaving = true;
|
||||
this.#shouldSubmit = false;
|
||||
}
|
||||
|
||||
// If submit was previously requested, send it, now that autosave have finished
|
||||
private didSucceed() {
|
||||
if (this.#shouldSubmit && isButtonElement(this.element)) {
|
||||
this.element.form?.requestSubmit(this.element);
|
||||
}
|
||||
this.#isSaving = false;
|
||||
this.#shouldSubmit = false;
|
||||
this.enableButton();
|
||||
}
|
||||
|
||||
private didFail() {
|
||||
this.#isSaving = false;
|
||||
this.#shouldSubmit = false;
|
||||
this.enableButton();
|
||||
}
|
||||
|
||||
private disableButton() {
|
||||
if (isButtonElement(this.element)) {
|
||||
this.#buttonText = this.element.value;
|
||||
this.element.value = this.element.dataset.disableWith ?? '';
|
||||
this.element.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private enableButton() {
|
||||
if (isButtonElement(this.element) && this.#buttonText) {
|
||||
this.element.value = this.#buttonText;
|
||||
this.element.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -277,6 +277,16 @@ export function isNumeric(s: string) {
|
|||
return !isNaN(n) && isFinite(n);
|
||||
}
|
||||
|
||||
export function isButtonElement(
|
||||
element: Element
|
||||
): element is HTMLButtonElement {
|
||||
return (
|
||||
element.tagName == 'BUTTON' ||
|
||||
(element.tagName == 'INPUT' &&
|
||||
(element as HTMLInputElement).type == 'submit')
|
||||
);
|
||||
}
|
||||
|
||||
export function isSelectElement(
|
||||
element: HTMLElement
|
||||
): element is HTMLSelectElement {
|
||||
|
|
|
@ -47,21 +47,4 @@
|
|||
= fields_for champ.input_name, champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: form, champ: champ
|
||||
|
||||
- if !dossier.for_procedure_preview?
|
||||
.dossier-edit-sticky-footer
|
||||
.send-dossier-actions-bar
|
||||
= render partial: 'shared/dossiers/autosave', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.can_transition_to_en_construction?
|
||||
= f.button t('views.shared.dossiers.edit.submit_dossier'),
|
||||
name: :submit_draft,
|
||||
value: true,
|
||||
class: 'fr-btn fr-btn--sm',
|
||||
disabled: !current_user.owns?(dossier),
|
||||
data: { 'disable-with': "Envoi en cours…" }
|
||||
|
||||
- if dossier.brouillon? && !current_user.owns?(dossier)
|
||||
.send-notice.invite-cannot-submit
|
||||
En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le déposer lui-même.
|
||||
|
||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier)
|
||||
|
|
|
@ -138,7 +138,6 @@ en:
|
|||
autosave: Your file is automatically saved after each modification. You can close the window at any time and pick up where you left off later.
|
||||
notice: "Download the notice of the procedure"
|
||||
notice_title: "To help you complete your file, you can consult the notice to this procedure."
|
||||
submit_dossier: Submit the file
|
||||
messages:
|
||||
form:
|
||||
send_message: "Send message"
|
||||
|
@ -447,8 +446,6 @@ en:
|
|||
undergoingreview: "Your file is undergoing review. It is no longer possible to delete your file. To cancel the undergoingreview contact the adminitration via the mailbox."
|
||||
soft_deleted_dossier: "Your file has been successfully deleted from your interface"
|
||||
restore: "Your file has been successfully restored"
|
||||
update_brouillon:
|
||||
draft_saved: "Your draft has been saved."
|
||||
etablissement:
|
||||
no_establishment: "There is no establishment tied to this file"
|
||||
update_identite:
|
||||
|
|
|
@ -133,7 +133,6 @@ fr:
|
|||
autosave: Votre dossier est enregistré automatiquement après chaque modification. Vous pouvez à tout moment fermer la fenêtre et reprendre plus tard là où vous en étiez.
|
||||
notice: Télécharger le guide de la démarche
|
||||
notice_title: "Pour vous aider à remplir votre dossier, vous pouvez consulter le guide de cette démarche."
|
||||
submit_dossier: Déposer le dossier
|
||||
messages:
|
||||
form:
|
||||
send_message: "Envoyer le message"
|
||||
|
@ -458,8 +457,6 @@ fr:
|
|||
undergoingreview: "L’instruction de votre dossier a commencé, il n’est plus possible de supprimer votre dossier. Si vous souhaitez annuler l’instruction contactez votre administration par la messagerie de votre dossier."
|
||||
soft_deleted_dossier: "Votre dossier a bien été supprimé de votre interface"
|
||||
restore: "Votre dossier a bien été restauré"
|
||||
update_brouillon:
|
||||
draft_saved: "Votre brouillon a bien été sauvegardé."
|
||||
etablissement:
|
||||
no_establishment: "Aucun établissement n’est associé à ce dossier"
|
||||
update_identite:
|
||||
|
|
|
@ -276,6 +276,7 @@ Rails.application.routes.draw do
|
|||
get 'etablissement'
|
||||
get 'brouillon'
|
||||
patch 'brouillon', to: 'dossiers#update_brouillon'
|
||||
post 'brouillon', to: 'dossiers#submit_brouillon'
|
||||
get 'modifier', to: 'dossiers#modifier'
|
||||
patch 'modifier', to: 'dossiers#update'
|
||||
get 'merci'
|
||||
|
|
|
@ -98,38 +98,21 @@ describe Users::DossiersController, type: :controller do
|
|||
let(:user) { create(:user) }
|
||||
let(:asked_dossier) { create(:dossier) }
|
||||
let(:ensure_authorized) { :forbid_invite_submission! }
|
||||
let(:submit) { true }
|
||||
|
||||
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(:redirect_to)
|
||||
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
|
||||
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'
|
||||
end
|
||||
|
||||
context 'when an invite submit a dossier where they where invited' do
|
||||
before { create(:invite, dossier: asked_dossier, user: user) }
|
||||
let(:submit) { true }
|
||||
|
||||
it_behaves_like 'redirects and flashes'
|
||||
end
|
||||
|
@ -355,30 +338,18 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#update_brouillon' do
|
||||
describe '#submit_brouillon' do
|
||||
before { sign_in(user) }
|
||||
|
||||
let!(:dossier) { create(:dossier, user: user) }
|
||||
let(:first_champ) { dossier.champs.first }
|
||||
let(:value) { 'beautiful value' }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:payload) { submit_payload.merge(submit_draft: true) }
|
||||
let(:payload) { { id: dossier.id } }
|
||||
|
||||
subject do
|
||||
Timecop.freeze(now) do
|
||||
patch :update_brouillon, params: payload
|
||||
post :submit_brouillon, params: payload
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -393,120 +364,6 @@ describe Users::DossiersController, type: :controller do
|
|||
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
|
||||
delivery = double
|
||||
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
|
||||
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)
|
||||
.and_return(double(full_messages: ['nop']))
|
||||
.and_return(double('errors', full_messages: ['nop']))
|
||||
|
||||
subject
|
||||
end
|
||||
|
@ -550,21 +407,6 @@ describe Users::DossiersController, type: :controller do
|
|||
|
||||
it { expect(response).to render_template(:brouillon) }
|
||||
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
|
||||
|
||||
context 'when dossier has no champ' do
|
||||
|
@ -581,19 +423,6 @@ describe Users::DossiersController, type: :controller do
|
|||
let(:dossier) { create(:dossier) }
|
||||
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
|
||||
before { subject }
|
||||
|
||||
|
@ -603,6 +432,101 @@ describe Users::DossiersController, type: :controller do
|
|||
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
|
||||
before { sign_in(user) }
|
||||
|
||||
|
@ -751,16 +675,12 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
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) }
|
||||
|
||||
before do
|
||||
dossier.passer_en_construction!
|
||||
subject
|
||||
end
|
||||
before { subject }
|
||||
|
||||
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) }
|
||||
end
|
||||
|
||||
|
|
|
@ -171,6 +171,12 @@ module SystemHelpers
|
|||
def form_id_for(libelle)
|
||||
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
|
||||
end
|
||||
|
||||
def wait_for_autosave(brouillon = true)
|
||||
blur
|
||||
expect(page).to have_css('span', text: "#{brouillon ? 'Brouillon' : 'Dossier'} enregistré", visible: true)
|
||||
page.execute_script("document.documentElement.dispatchEvent(new CustomEvent('autosave:reset'));")
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
|
|
|
@ -274,9 +274,7 @@ describe 'fetch API Particulier Data', js: true do
|
|||
|
||||
fill_in 'Le numéro d’allocataire CAF', with: numero_allocataire
|
||||
fill_in 'Le code postal', with: 'wrong_code'
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
dossier = Dossier.last
|
||||
cnaf_champ = dossier.champs.find(&:cnaf?)
|
||||
|
@ -286,12 +284,15 @@ describe 'fetch API Particulier Data', js: true do
|
|||
click_on 'Déposer le dossier'
|
||||
expect(page).to have_content(/code postal doit posséder 5 caractères/)
|
||||
|
||||
fill_in 'Le code postal', with: code_postal
|
||||
|
||||
VCR.use_cassette('api_particulier/success/composition_familiale') do
|
||||
perform_enqueued_jobs { click_on 'Déposer le dossier' }
|
||||
perform_enqueued_jobs do
|
||||
fill_in 'Le code postal', with: code_postal
|
||||
wait_for_autosave
|
||||
end
|
||||
end
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
|
||||
visit demande_dossier_path(dossier)
|
||||
expect(page).to have_content(/Des données.*ont été reçues depuis la CAF/)
|
||||
|
||||
|
@ -329,21 +330,24 @@ describe 'fetch API Particulier Data', js: true do
|
|||
click_button('Continuer')
|
||||
|
||||
fill_in "Identifiant", with: 'wrong code'
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
dossier = Dossier.last
|
||||
pole_emploi_champ = dossier.champs.find(&:pole_emploi?)
|
||||
|
||||
expect(pole_emploi_champ.identifiant).to eq('wrong code')
|
||||
|
||||
fill_in "Identifiant", with: identifiant
|
||||
clear_enqueued_jobs
|
||||
pole_emploi_champ.update(external_id: nil, identifiant: nil)
|
||||
|
||||
VCR.use_cassette('api_particulier/success/situation_pole_emploi') do
|
||||
perform_enqueued_jobs { click_on 'Déposer le dossier' }
|
||||
perform_enqueued_jobs do
|
||||
fill_in "Identifiant", with: identifiant
|
||||
wait_until { pole_emploi_champ.reload.external_id.present? }
|
||||
end
|
||||
end
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
|
||||
visit demande_dossier_path(dossier)
|
||||
expect(page).to have_content(/Des données.*ont été reçues depuis Pôle emploi/)
|
||||
|
||||
|
@ -397,21 +401,24 @@ describe 'fetch API Particulier Data', js: true do
|
|||
click_button('Continuer')
|
||||
|
||||
fill_in "INE", with: 'wrong code'
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
dossier = Dossier.last
|
||||
mesri_champ = dossier.champs.find(&:mesri?)
|
||||
|
||||
expect(mesri_champ.ine).to eq('wrong code')
|
||||
|
||||
fill_in "INE", with: ine
|
||||
clear_enqueued_jobs
|
||||
mesri_champ.update(external_id: nil, ine: nil)
|
||||
|
||||
VCR.use_cassette('api_particulier/success/etudiants') do
|
||||
perform_enqueued_jobs { click_on 'Déposer le dossier' }
|
||||
perform_enqueued_jobs do
|
||||
fill_in "INE", with: ine
|
||||
wait_until { mesri_champ.reload.external_id.present? }
|
||||
end
|
||||
end
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
|
||||
visit demande_dossier_path(dossier)
|
||||
expect(page).to have_content(/Des données.*ont été reçues depuis le MESRI/)
|
||||
|
||||
|
@ -457,9 +464,7 @@ describe 'fetch API Particulier Data', js: true do
|
|||
|
||||
fill_in 'Le numéro fiscal', with: numero_fiscal
|
||||
fill_in "La référence d'avis d'imposition", with: 'wrong_code'
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
dossier = Dossier.last
|
||||
dgfip_champ = dossier.champs.find(&:dgfip?)
|
||||
|
@ -469,12 +474,15 @@ describe 'fetch API Particulier Data', js: true do
|
|||
click_on 'Déposer le dossier'
|
||||
expect(page).to have_content(/reference avis doit posséder 13 ou 14 caractères/)
|
||||
|
||||
fill_in "La référence d'avis d'imposition", with: reference_avis
|
||||
|
||||
VCR.use_cassette('api_particulier/success/avis_imposition') do
|
||||
perform_enqueued_jobs { click_on 'Déposer le dossier' }
|
||||
perform_enqueued_jobs do
|
||||
fill_in "La référence d'avis d'imposition", with: reference_avis
|
||||
wait_for_autosave
|
||||
end
|
||||
end
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
|
||||
visit demande_dossier_path(dossier)
|
||||
expect(page).to have_content(/Des données.*ont été reçues depuis la DGFiP/)
|
||||
|
||||
|
|
|
@ -120,8 +120,8 @@ describe 'The routing', js: true do
|
|||
click_on 'Modifier mon dossier'
|
||||
|
||||
fill_in litteraire_user.dossiers.first.champs.first.libelle, with: 'some value'
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Dossier enregistré', visible: true)
|
||||
wait_for_autosave(false)
|
||||
|
||||
log_out
|
||||
|
||||
# the litteraires instructeurs should have a notification
|
||||
|
@ -199,6 +199,7 @@ describe 'The routing', js: true do
|
|||
click_button('Continuer')
|
||||
|
||||
select(groupe, from: 'dossier_groupe_instructeur_id')
|
||||
wait_for_autosave
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
expect(page).to have_text('Merci')
|
||||
|
@ -215,7 +216,8 @@ describe 'The routing', js: true do
|
|||
expect(page).not_to have_selector("option", text: "Groupe inactif")
|
||||
|
||||
select(new_group, from: 'dossier_groupe_instructeur_id')
|
||||
expect(page).to have_css('span', text: 'Dossier enregistré', visible: true)
|
||||
wait_for_autosave(false)
|
||||
|
||||
expect(page).to have_text(new_group)
|
||||
|
||||
log_out
|
||||
|
|
|
@ -43,8 +43,7 @@ describe 'The user' do
|
|||
find('.editable-champ-piece_justificative input[type=file]').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
|
||||
|
||||
expect(page).to have_css('span', text: 'Votre brouillon est automatiquement enregistré', visible: true)
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
# check data on the dossier
|
||||
expect(user_dossier.brouillon?).to be true
|
||||
|
@ -116,18 +115,14 @@ describe 'The user' do
|
|||
end
|
||||
|
||||
expect(page).to have_content('Supprimer', count: 2)
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
expect(page).to have_content('Supprimer', count: 2)
|
||||
|
||||
within '.repetition .row:first-child' do
|
||||
click_on 'Supprimer l’élément'
|
||||
end
|
||||
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
expect(page).to have_content('Supprimer', count: 1)
|
||||
end
|
||||
|
@ -140,8 +135,8 @@ describe 'The user' do
|
|||
|
||||
# Check an incomplete dossier can be saved as a draft, even when mandatory fields are missing
|
||||
fill_in('texte optionnel', with: 'ça ne suffira pas')
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
||||
|
||||
# Check an incomplete dossier cannot be submitted when mandatory fields are missing
|
||||
|
@ -151,6 +146,7 @@ describe 'The user' do
|
|||
|
||||
# Check a dossier can be submitted when all mandatory fields are filled
|
||||
fill_in('texte obligatoire', with: 'super texte')
|
||||
wait_for_autosave
|
||||
|
||||
click_on 'Déposer le dossier'
|
||||
expect(user_dossier.reload.en_construction?).to be(true)
|
||||
|
@ -370,8 +366,7 @@ describe 'The user' do
|
|||
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
||||
|
||||
fill_in('age', with: '18')
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Dossier enregistré', visible: true)
|
||||
wait_for_autosave(false)
|
||||
|
||||
# the champ keeps their previous value so they are all displayed
|
||||
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
|
||||
|
@ -389,9 +384,7 @@ describe 'The user' do
|
|||
expect(page).to have_content('Votre brouillon est automatiquement enregistré')
|
||||
|
||||
fill_in('texte obligatoire', with: 'a valid user input')
|
||||
blur
|
||||
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
visit current_path
|
||||
expect(page).to have_field('texte obligatoire', with: 'a valid user input')
|
||||
|
@ -410,7 +403,7 @@ describe 'The user' do
|
|||
# Test that retrying after a failure works
|
||||
allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_call_original
|
||||
click_on 'réessayer'
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
|
||||
visit current_path
|
||||
expect(page).to have_field('texte obligatoire', with: 'a valid user input')
|
||||
|
@ -434,8 +427,7 @@ describe 'The user' do
|
|||
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
||||
|
||||
fill_in('texte obligatoire', with: 'a valid user input')
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true)
|
||||
wait_for_autosave
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@ RSpec.shared_examples 'the user can edit the submitted demande' do
|
|||
|
||||
expect(page).to have_current_path(modifier_dossier_path(dossier))
|
||||
fill_in('Texte obligatoire', with: 'Nouveau texte')
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Dossier enregistré', visible: true)
|
||||
wait_for_autosave(false)
|
||||
|
||||
click_on 'Demande'
|
||||
expect(page).to have_current_path(demande_dossier_path(dossier))
|
||||
|
|
Loading…
Reference in a new issue