Merge pull request #11067 from tchak/refactor-dossier-controller

refactor(dossier): cleanup user controller
This commit is contained in:
Paul Chavard 2024-11-26 13:38:45 +00:00 committed by GitHub
commit 755a526d12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 139 additions and 158 deletions

View file

@ -12,14 +12,13 @@ class Attachment::EditComponent < ApplicationComponent
EXTENSIONS_ORDER = ['jpeg', 'png', 'pdf', 'zip'].freeze
def initialize(champ: nil, auto_attach_url: nil, attached_file:, direct_upload: true, index: 0, as_multiple: false, view_as: :link, user_can_destroy: true, user_can_replace: false, attachments: [], max: nil, **kwargs)
def initialize(champ: nil, auto_attach_url: nil, attached_file:, direct_upload: true, index: 0, as_multiple: false, view_as: :link, user_can_destroy: true, attachments: [], max: nil, **kwargs)
@champ = champ
@attached_file = attached_file
@direct_upload = direct_upload
@index = index
@view_as = view_as
@user_can_destroy = user_can_destroy
@user_can_replace = user_can_replace
@as_multiple = as_multiple
@auto_attach_url = auto_attach_url

View file

@ -17,7 +17,7 @@ class Attachment::MultipleComponent < ApplicationComponent
delegate :count, :empty?, to: :attachments, prefix: true
def initialize(champ: nil, attached_file:, form_object_name: nil, view_as: :link, user_can_destroy: true, user_can_replace: false, max: nil)
def initialize(champ: nil, attached_file:, form_object_name: nil, view_as: :link, user_can_destroy: true, max: nil)
@champ = champ
@attached_file = attached_file
@form_object_name = form_object_name

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Dossiers::EditFooterComponent < ApplicationComponent
delegate :can_passer_en_construction?, to: :@dossier
delegate :can_passer_en_construction?, :can_transition_to_en_construction?, :forked_with_changes?, to: :@dossier
def initialize(dossier:, annotation:)
@dossier = dossier
@ -18,7 +18,43 @@ class Dossiers::EditFooterComponent < ApplicationComponent
@annotation.present?
end
def disabled_submit_buttons_options
def can_submit?
can_submit_draft? || can_submit_en_construction?
end
def can_submit_draft?
!annotation? && can_transition_to_en_construction?
end
def can_submit_en_construction?
forked_with_changes?
end
def submit_button_label
if can_submit_draft?
t('.submit')
else
t('.submit_changes')
end
end
def submit_button_path
if can_submit_draft?
brouillon_dossier_path(@dossier)
else
modifier_dossier_path(@dossier.editing_fork_origin)
end
end
def submit_button_options
if can_submit_draft?
submit_draft_button_options
else
submit_en_construction_button_options
end
end
def disabled_submit_button_options
{
class: 'fr-text--sm fr-mb-0 fr-mr-2w',
data: { 'fr-opened': "true" },

View file

@ -2,16 +2,10 @@
.send-dossier-actions-bar
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
- if !annotation? && @dossier.can_transition_to_en_construction?
- if can_submit?
- if !can_passer_en_construction?
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
- if @dossier.forked_with_changes?
- if !can_passer_en_construction?
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options
= link_to t('.submit_disabled'), "#", disabled_submit_button_options
= button_to submit_button_label, submit_button_path, submit_button_options
- if @dossier.brouillon? && !owner?
.fr-pb-2w.invite-cannot-submit
@ -19,7 +13,5 @@
- c.with_body do
%p.fr-pb-0= t('.invite_notice').html_safe
- if !annotation?
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }

View file

@ -13,10 +13,6 @@ class EditableChamp::PieceJustificativeComponent < EditableChamp::EditableChampB
end
end
def user_can_destroy?
!@champ.mandatory? || @champ.dossier.brouillon?
end
def max
[true, nil].include?(@champ.procedure&.piece_justificative_multiple?) ? Attachment::MultipleComponent::DEFAULT_MAX_ATTACHMENTS : 1
end

View file

@ -1,4 +1,4 @@
= render Attachment::MultipleComponent.new(champ: @champ, attached_file: @champ.piece_justificative_file, form_object_name: @form.object_name, view_as:, user_can_destroy: user_can_destroy?, max:) do |c|
= render Attachment::MultipleComponent.new(champ: @champ, attached_file: @champ.piece_justificative_file, form_object_name: @form.object_name, view_as:, max:) do |c|
- if @champ.type_de_champ.piece_justificative_template&.attached?
- c.with_template do
= render partial: "shared/piece_justificative_template", locals: { champ: @champ }

View file

@ -4,8 +4,4 @@ class EditableChamp::TitreIdentiteComponent < EditableChamp::EditableChampBaseCo
def dsfr_input_classname
'fr-input'
end
def user_can_destroy?
!@champ.mandatory? || @champ.dossier.brouillon?
end
end

View file

@ -1,4 +1,3 @@
- if @champ.type_de_champ.piece_justificative_template&.attached?
= render partial: "shared/piece_justificative_template", locals: { champ: @champ }
= render Attachment::EditComponent.new(champ: @form.object, attached_file: @champ.piece_justificative_file, attachment: @champ.piece_justificative_file[0], form_object_name: @form.object_name,
user_can_destroy: user_can_destroy?)
= render Attachment::EditComponent.new(champ: @form.object, attached_file: @champ.piece_justificative_file, attachment: @champ.piece_justificative_file[0], form_object_name: @form.object_name)

View file

@ -30,8 +30,14 @@ class Champs::PieceJustificativeController < Champs::ChampController
save_succeed = @champ.save
end
@champ.dossier.update(last_champ_updated_at: Time.zone.now.utc) if save_succeed
if save_succeed && dossier.brouillon?
dossier.touch(:last_champ_updated_at, :last_champ_piece_jointe_updated_at)
end
save_succeed
end
def dossier
@champ.dossier
end
end

View file

@ -57,7 +57,7 @@ module CreateAvisConcern
persisted, failed = create_results.partition(&:persisted?)
if persisted.any?
dossier.update!(last_avis_updated_at: Time.zone.now)
dossier.touch(:last_avis_updated_at)
sent_emails_addresses = []
persisted.each do |avis|
avis.dossier.demander_un_avis!(avis)

View file

@ -250,7 +250,7 @@ module Instructeurs
if commentaire.valid?
dossier.flag_as_pending_correction!(commentaire, params[:reason].presence)
dossier.update!(last_commentaire_updated_at: Time.zone.now)
dossier.touch(:last_commentaire_updated_at)
current_instructeur.follow(dossier)
flash.notice = "Dossier marqué comme en attente de correction."
@ -275,7 +275,7 @@ module Instructeurs
@commentaire = CommentaireService.create(current_instructeur, dossier, commentaire_params)
if @commentaire.errors.empty?
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
@commentaire.dossier.touch(:last_commentaire_updated_at)
current_instructeur.follow(dossier)
flash.notice = "Message envoyé"
redirect_to messagerie_instructeur_dossier_path(procedure, dossier)
@ -300,7 +300,7 @@ module Instructeurs
def update_annotations
dossier_with_champs.update_champs_attributes(champs_private_attributes_params, :private, updated_by: current_user.email)
if dossier.champs.any?(&:changed_for_autosave?)
dossier.last_champ_private_updated_at = Time.zone.now
dossier.touch(:last_champ_private_updated_at)
end
dossier.save(context: :champs_private_value)

View file

@ -237,7 +237,7 @@ module Instructeurs
dossiers.each do |dossier|
commentaire = CommentaireService.create(current_instructeur, dossier, bulk_message_params.except(:targets))
if commentaire.errors.empty?
commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
commentaire.dossier.touch(:last_commentaire_updated_at)
else
errors << dossier.id
end

View file

@ -9,11 +9,11 @@ module Users
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :new, :transferer_all, :deleted_dossiers]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update, :create_commentaire, :papertrail, :restore, :champ]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :update, :create_commentaire, :papertrail, :restore, :champ]
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_siret, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update, :champ]
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_siret, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :update, :champ]
before_action :ensure_dossier_can_be_filled, only: [:brouillon, :modifier, :submit_brouillon, :submit_en_construction, :update]
before_action :ensure_dossier_can_be_viewed, only: [:show]
before_action :forbid_invite_submission!, only: [:submit_brouillon]
@ -233,17 +233,9 @@ module Users
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
@dossier.passer_en_construction!
@dossier.process_declarative!
@dossier.process_sva_svr!
@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
redirect_to merci_dossier_path(@dossier)
else
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream
end
render :brouillon
end
end
@ -261,17 +253,7 @@ module Users
def modifier
@dossier = dossier_with_champs
end
# Transition to en_construction forks,
# so users editing en_construction dossiers won't completely break their changes.
# TODO: remove me after fork en_construction feature deploy (PR #8790)
def modifier_legacy
respond_to do |format|
format.turbo_stream do
flash.alert = "Une mise à jour de cette page est nécessaire pour poursuivre, veuillez la recharger (touche F5). Attention: le dernier champ modifié na pas été sauvegardé, vous devrez le ressaisir."
end
end
@dossier_for_editing = dossier.owner_editing_fork
end
def submit_en_construction
@ -284,29 +266,21 @@ module Users
submit_dossier_and_compute_errors
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
editing_fork_origin.merge_fork(@dossier)
if dossier.errors.blank? && dossier.can_passer_en_construction?
editing_fork_origin.merge_fork(dossier)
editing_fork_origin.submit_en_construction!
redirect_to dossier_path(editing_fork_origin)
else
respond_to do |format|
format.html do
render :modifier
end
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
render :update, layout: false
end
end
@dossier_for_editing = dossier
@dossier = editing_fork_origin
render :modifier
end
end
def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false)
@can_passer_en_construction_was, @can_passer_en_construction_is = @dossier.track_can_passer_en_construction do
@can_passer_en_construction_was, @can_passer_en_construction_is = dossier.track_can_passer_en_construction do
update_dossier_and_compute_errors
end
@ -324,8 +298,8 @@ module Users
def champ
@dossier = dossier_with_champs(pj_template: false)
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:stable_id], :public)
champ = @dossier.project_champ(type_de_champ, params[:row_id])
type_de_champ = dossier.find_type_de_champ_by_stable_id(params[:stable_id], :public)
champ = dossier.project_champ(type_de_champ, params[:row_id])
respond_to do |format|
format.turbo_stream do
@ -559,36 +533,34 @@ module Users
end
def update_dossier_and_compute_errors
@dossier.update_champs_attributes(champs_public_attributes_params, :public, updated_by: current_user.email)
updated_champs = @dossier.champs.filter(&:changed_for_autosave?)
if updated_champs.present?
@dossier.last_champ_updated_at = Time.zone.now
end
dossier.update_champs_attributes(champs_public_attributes_params, :public, updated_by: current_user.email)
updated_champs = dossier.champs.filter(&:changed_for_autosave?)
# We save the dossier without validating fields, and if it is successful and the client
# requests it, we ask for field validation errors.
if @dossier.save
if updated_champs.any?(&:used_by_routing_rules?)
@update_contact_information = true
RoutingEngine.compute(@dossier)
if dossier.save
if dossier.brouillon? && updated_champs.present?
dossier.touch(:last_champ_updated_at)
if updated_champs.any?(&:used_by_routing_rules?)
@update_contact_information = true
RoutingEngine.compute(dossier)
end
end
if params[:validate].present?
@dossier.valid?(:champs_public_value)
dossier.valid?(:champs_public_value)
end
end
@dossier.errors
end
def submit_dossier_and_compute_errors
@dossier.validate(:champs_public_value)
@dossier.check_mandatory_and_visible_champs
dossier.validate(:champs_public_value)
dossier.check_mandatory_and_visible_champs
if @dossier.editing_fork_origin&.pending_correction?
@dossier.editing_fork_origin.validate(:champs_public_value)
@dossier.editing_fork_origin.errors.where(:pending_correction).each do |error|
@dossier.errors.import(error)
if dossier.editing_fork_origin&.pending_correction?
dossier.editing_fork_origin.validate(:champs_public_value)
dossier.editing_fork_origin.errors.where(:pending_correction).each do |error|
dossier.errors.import(error)
end
end
end

View file

@ -26,12 +26,6 @@ module DossierCloneConcern
find_or_create_editing_fork(user).tap { DossierPreloader.load_one(_1) }
end
def reset_editing_fork!
if editing_fork? && forked_with_changes?
destroy_editing_fork!
end
end
def destroy_editing_fork!
if editing_fork?
update!(hidden_by_administration_at: Time.current, hidden_by_reason: :stale_fork)
@ -43,6 +37,18 @@ module DossierCloneConcern
editing_fork_origin_id.present?
end
def forked_with_changes?
if forked_diff.present?
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
end
end
def champ_forked_with_changes?(champ)
if forked_diff.present?
forked_diff.values.any? { |champs| champs.any? { _1.public_id == champ.public_id } }
end
end
def make_diff(editing_fork)
origin_champs_index = project_champs_public_all.index_by(&:public_id)
forked_champs_index = editing_fork.project_champs_public_all.index_by(&:public_id)
@ -126,18 +132,6 @@ module DossierCloneConcern
cloned_dossier.reload
end
def forked_with_changes?
if forked_diff.present?
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
end
end
def champ_forked_with_changes?(champ)
if forked_diff.present?
forked_diff.values.any? { _1.include?(champ) }
end
end
private
def forked_diff

View file

@ -3,6 +3,19 @@
module DossierStateConcern
extend ActiveSupport::Concern
def submit_en_construction!
self.traitements.submit_en_construction
save!
RoutingEngine.compute(self)
resolve_pending_correction!
process_sva_svr!
remove_piece_justificative_file_not_visible!
editing_forks.each(&:destroy_editing_fork!)
end
def after_passer_en_construction
self.conservation_extension = 0.days
self.depose_at = self.en_construction_at = self.traitements
@ -16,12 +29,18 @@ module DossierStateConcern
MailTemplatePresenterService.create_commentaire_for_state(self, Dossier.states.fetch(:en_construction))
procedure.compute_dossiers_count
process_declarative!
process_sva_svr!
index_search_terms_later
end
def after_commit_passer_en_construction
NotificationMailer.send_en_construction_notification(self).deliver_later
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(self, instructeur.email).deliver_later
end
remove_piece_justificative_file_not_visible!
end
@ -49,6 +68,8 @@ module DossierStateConcern
NotificationMailer.send_en_instruction_notification(self).deliver_later
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
end
editing_forks.each(&:destroy_editing_fork!)
end
def after_passer_automatiquement_en_instruction

View file

@ -865,17 +865,6 @@ class Dossier < ApplicationRecord
procedure.email_template_for(state)
end
def submit_en_construction!
self.traitements.submit_en_construction
save!
RoutingEngine.compute(self)
resolve_pending_correction!
process_sva_svr!
remove_piece_justificative_file_not_visible!
end
def process_declarative!
if procedure.declarative_accepte? && may_accepter_automatiquement?
accepter_automatiquement!

View file

@ -13,6 +13,6 @@
active: @tab == 'annotations-privees')
- if @tab == 'dossier'
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier, dossier_for_editing: @dossier }
- else
= render partial: "shared/dossiers/edit_annotations", locals: { dossier: @dossier, seen_at: nil }

View file

@ -12,4 +12,4 @@
.fr-container
%h2.fr-h4= t('.title')
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier, dossier_for_editing: @dossier }

View file

@ -1,5 +1,3 @@
- dossier_for_editing = dossier.en_construction? ? dossier.owner_editing_fork : dossier
- if dossier.france_connected_with_one_identity? && current_user.instructeur? && !current_user.owns_or_invite?(dossier)
- content_for(:notice_info) do
= render partial: "shared/dossiers/france_connect_informations_notice", locals: { user_information: dossier.user.france_connect_informations.first }
@ -10,7 +8,7 @@
= render NestedForms::FormOwnerComponent.new
= form_for dossier_for_editing, url: brouillon_dossier_url(dossier), method: :patch, html: { id: 'dossier-edit-form', class: 'form', multipart: true, novalidate: 'novalidate' } do |f|
= render Dossiers::ErrorsFullMessagesComponent.new(dossier: dossier)
= render Dossiers::ErrorsFullMessagesComponent.new(dossier: dossier_for_editing)
%header.mb-6
.fr-highlight
%p.fr-text--sm

View file

@ -9,4 +9,4 @@
.fr-container
= render partial: "shared/dossiers/header", locals: { dossier: @dossier }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier, dossier_for_editing: @dossier }

View file

@ -7,4 +7,4 @@
= render partial: 'users/dossiers/show/header', locals: { dossier: @dossier }
.container
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier }
= render partial: "shared/dossiers/edit", locals: { dossier: @dossier, dossier_for_editing: @dossier_for_editing }

View file

@ -380,7 +380,6 @@ Rails.application.routes.draw do
post 'brouillon', to: 'dossiers#submit_brouillon'
get 'modifier', to: 'dossiers#modifier'
post 'modifier', to: 'dossiers#submit_en_construction'
patch 'modifier', to: 'dossiers#modifier_legacy'
get 'champs/:stable_id', to: 'dossiers#champ', as: :champ
get 'merci'
get 'demande'

View file

@ -96,14 +96,6 @@ RSpec.describe Attachment::MultipleComponent, type: :component do
end
end
context 'when user can replace' do
let(:kwargs) { { user_can_replace: true } }
before do
attach_to_champ(attached_file, champ)
end
end
def attach_to_champ(attached_file, champ)
attached_file.attach(
io: StringIO.new("x" * 2),

View file

@ -836,10 +836,11 @@ describe Users::DossiersController, type: :controller do
before { sign_in(user) }
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{}, { type: :piece_justificative }]) }
let!(:dossier) { create(:dossier, :en_construction, user:, procedure:) }
let(:first_champ) { dossier.project_champs_public.first }
let(:dossier) { create(:dossier, :en_construction, user:, procedure:) }
let!(:editing_fork) { dossier.owner_editing_fork }
let(:first_champ) { editing_fork.project_champs_public.first }
let(:piece_justificative_champ) { editing_fork.project_champs_public.last }
let(:anchor_to_first_champ) { controller.helpers.link_to I18n.t('views.users.dossiers.fix_champ'), brouillon_dossier_path(anchor: first_champ.labelledby_id), class: 'error-anchor' }
let(:piece_justificative_champ) { dossier.project_champs_public.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') }
@ -887,14 +888,9 @@ describe Users::DossiersController, type: :controller do
it 'updates the dossier timestamps' do
subject
dossier.reload
expect(dossier.updated_at).to eq(now)
expect(dossier.last_champ_updated_at).to eq(now)
end
it 'updates the dossier state' do
subject
expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction))
editing_fork.reload
expect(editing_fork.updated_at).to eq(now)
expect(editing_fork.last_champ_updated_at).to eq(now)
end
it { is_expected.to have_http_status(:ok) }
@ -923,9 +919,9 @@ describe Users::DossiersController, type: :controller do
it 'updates the dossier timestamps' do
subject
dossier.reload
expect(dossier.updated_at).to eq(now)
expect(dossier.last_champ_updated_at).to eq(now)
editing_fork.reload
expect(editing_fork.updated_at).to eq(now)
expect(editing_fork.last_champ_updated_at).to eq(now)
end
end
end
@ -958,14 +954,10 @@ describe Users::DossiersController, type: :controller do
end
context 'iban error' do
let(:types_de_champ_public) { [{ type: :iban }] }
let(:value) { 'abc' }
before do
first_champ.type_de_champ.update!(type_champ: :iban, mandatory: true, libelle: 'l')
dossier.project_champs_public.first.becomes!(Champs::IbanChamp).save!
subject
end
before { subject }
it { expect(response).to have_http_status(:success) }
end
@ -998,9 +990,7 @@ describe Users::DossiersController, type: :controller do
end
context 'when the champ is a phone number' do
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :phone }]) }
let!(:dossier) { create(:dossier, :en_construction, user:, procedure:) }
let(:first_champ) { dossier.project_champs_public.first }
let(:types_de_champ_public) { [{ type: :phone }] }
let(:now) { Time.zone.parse('01/01/2100') }
let(:submit_payload) do

View file

@ -6,10 +6,11 @@ describe 'shared/dossiers/edit', type: :view do
allow(view).to receive(:administrateur_signed_in?).and_return(false)
end
subject { render 'shared/dossiers/edit', dossier: dossier, apercu: false }
subject { render 'shared/dossiers/edit', dossier:, dossier_for_editing:, apercu: false }
let(:procedure) { create(:procedure, types_de_champ_public:) }
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:dossier_for_editing) { dossier }
context 'when there are some champs' do
let(:type_de_champ_header_section) { procedure.draft_types_de_champ_public.find(&:header_section?) }
@ -116,6 +117,7 @@ describe 'shared/dossiers/edit', type: :view do
context 'when dossier is en construction' do
let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:) }
let(:dossier_for_editing) { dossier.owner_editing_fork }
it 'can delete a piece justificative' do
expect(subject).to have_selector("[title='Supprimer le fichier #{champ.piece_justificative_file.attachments[0].filename}']")