feat(piece_justificative): allow user to replace a PJ for dossier en_construction

This commit is contained in:
Colin Darie 2022-12-13 15:22:18 +01:00
parent 93de4cf821
commit 4049240c2c
No known key found for this signature in database
GPG key ID: 4FB865FDBCA4BCC4
12 changed files with 108 additions and 14 deletions

View file

@ -4,12 +4,14 @@ class Attachment::EditComponent < ApplicationComponent
attr_reader :attachment
attr_reader :user_can_destroy
alias user_can_destroy? user_can_destroy
attr_reader :user_can_replace
alias user_can_replace? user_can_replace
attr_reader :as_multiple
alias as_multiple? as_multiple
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, **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, user_can_replace: false, **kwargs)
@as_multiple = as_multiple
@attached_file = attached_file
@auto_attach_url = auto_attach_url
@ -18,6 +20,7 @@ class Attachment::EditComponent < ApplicationComponent
@index = index
@view_as = view_as
@user_can_destroy = user_can_destroy
@user_can_replace = user_can_replace
# attachment passed by kwarg because we don't want a default (nil) value.
@attachment = if kwargs.key?(:attachment)
@ -64,13 +67,14 @@ class Attachment::EditComponent < ApplicationComponent
def file_field_options
track_issue_with_missing_validators if missing_validators?
{
class: "fr-upload attachment-input #{attachment_input_class} #{persisted? ? 'hidden' : ''}",
class: class_names("fr-upload attachment-input": true, "#{attachment_input_class}": true, "hidden": persisted?, "fr-mt-2w": user_can_replace?),
direct_upload: @direct_upload,
id: input_id,
aria: { describedby: champ&.describedby_id },
data: {
auto_attach_url:
}.merge(has_file_size_validator? ? { max_file_size: } : {})
.merge(user_can_replace? ? { replace_attachment_target: "input" } : {})
}.merge(has_content_type_validator? ? { accept: accept_content_type } : {})
end
@ -103,6 +107,23 @@ class Attachment::EditComponent < ApplicationComponent
}
end
def replace_button_options
{
type: 'button',
data: {
action: "click->replace-attachment#open"
}
}
end
def replace_controller_attributes
return {} unless user_can_replace? || as_multiple?
{
"data-controller": 'replace-attachment'
}
end
def retry_button_options
{
type: 'button',
@ -155,7 +176,9 @@ class Attachment::EditComponent < ApplicationComponent
def auto_attach_url
return @auto_attach_url if @auto_attach_url.present?
return helpers.auto_attach_url(@champ) if @champ.present?
params = { replace_attachment_id: @attachment.id } if user_can_replace? && @attachment.present?
return helpers.auto_attach_url(@champ, params) if @champ.present?
nil
end

View file

@ -3,6 +3,7 @@ en:
max_file_size: "File size limit : %{max_file_size}."
allowed_formats: "Supported formats : %{formats}"
retry: Retry
replace: Replace
delete: Delete
errors:
uploading: "An error occurred while sending the file."

View file

@ -4,6 +4,7 @@ fr:
allowed_formats: "Formats supportés : %{formats}"
retry: Réessayer
delete: Supprimer
replace: Remplacer
errors:
uploading: "Une erreur sest produite pendant lenvoi du fichier."
virus_infected: "Virus détecté, merci denvoyer un autre fichier."

View file

@ -1,9 +1,11 @@
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-2w": !(as_multiple? && downloadable?)) }.compact }
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-2w": !(as_multiple? && downloadable?)) }.compact, **replace_controller_attributes }
- if persisted?
%div{ id: dom_id(attachment, :persisted_row) }
.flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) }
- if user_can_destroy?
= link_to(t('.delete'), destroy_attachment_path, **remove_button_options, class: "fr-btn fr-btn--tertiary fr-btn--sm fr-icon-delete-line", title: "Supprimer le fichier #{attachment.filename}")
- elsif user_can_replace?
= button_tag t('.replace'), **replace_button_options, class: "fr-btn fr-btn--tertiary fr-btn--sm", title: "Remplacer le fichier #{attachment.filename}"
.fr-py-1v
- if downloadable?

View file

@ -11,6 +11,10 @@ class EditableChamp::PieceJustificativeComponent < EditableChamp::EditableChampB
!@champ.mandatory? || @champ.dossier.brouillon?
end
def user_can_replace?
@champ.mandatory? && @champ.dossier.en_construction?
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:, user_can_destroy: user_can_destroy?, user_can_replace: user_can_replace?, 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

@ -1,2 +1,9 @@
class EditableChamp::TitreIdentiteComponent < EditableChamp::EditableChampBaseComponent
def user_can_destroy?
!@champ.mandatory? || @champ.dossier.brouillon?
end
def user_can_replace?
@champ.mandatory? && @champ.dossier.en_construction?
end
end

View file

@ -1,5 +1,5 @@
- user_can_destroy = !@champ.mandatory? || @champ.dossier.brouillon?
- 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:)
= 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?, user_can_replace: user_can_replace?)

View file

@ -28,9 +28,22 @@ class Champs::PieceJustificativeController < ApplicationController
end
def attach_piece_justificative
@champ.piece_justificative_file.attach(params[:blob_signed_id])
save_succeed = @champ.save
save_succeed = nil
ActiveStorage::Attachment.transaction do
if params.key?(:replace_attachment_id)
@champ.piece_justificative_file.attachments.find do
_1.id == params[:replace_attachment_id].to_i
end&.destroy
end
@champ.piece_justificative_file.attach(params[:blob_signed_id])
save_succeed = @champ.save
end
@champ.dossier.update(last_champ_updated_at: Time.zone.now.utc) if save_succeed
save_succeed
end

View file

@ -7,11 +7,11 @@ module ChampHelper
simple_format(auto_linked_text, {}, sanitize: false)
end
def auto_attach_url(object)
def auto_attach_url(object, params = {})
if object.is_a?(Champ)
champs_attach_piece_justificative_url(object.id)
champs_attach_piece_justificative_url(object.id, params)
elsif object.is_a?(TypeDeChamp)
piece_justificative_template_admin_procedure_type_de_champ_url(stable_id: object.stable_id, procedure_id: object.procedure.id)
piece_justificative_template_admin_procedure_type_de_champ_url(stable_id: object.stable_id, procedure_id: object.procedure.id, **params)
end
end
end

View file

@ -0,0 +1,13 @@
import { ApplicationController } from './application_controller';
import { show } from '@utils';
export class ReplaceAttachmentController extends ApplicationController {
static targets = ['input'];
declare readonly inputTarget: HTMLInputElement;
open() {
show(this.inputTarget);
this.inputTarget.click(); // opens input prompt
}
}

View file

@ -5,6 +5,8 @@ describe Champs::PieceJustificativeController, type: :controller do
let(:champ) { dossier.champs_public.first }
describe '#update' do
let(:replace_attachment_id) { nil }
render_views
before { sign_in user }
@ -12,8 +14,9 @@ describe Champs::PieceJustificativeController, type: :controller do
put :update, params: {
position: '1',
champ_id: champ.id,
blob_signed_id: file
}, format: :turbo_stream
blob_signed_id: file,
replace_attachment_id:
}.compact, format: :turbo_stream
end
context 'when the file is valid' do
@ -65,6 +68,33 @@ describe Champs::PieceJustificativeController, type: :controller do
expect(JSON.parse(response.body)).to eq({ 'errors' => ['La pièce justificative nest pas dun type accepté'] })
end
end
context 'replace an attachment' do
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
before { subject }
context "attachment associated to dossier" do
let(:champ) { create(:champ, :with_piece_justificative_file, dossier:) }
let(:replace_attachment_id) { champ.piece_justificative_file.first.id }
it "replaces an existing attachment" do
champ.reload
expect(champ.piece_justificative_file.attachments.count).to eq(1)
expect(champ.piece_justificative_file.attachments.first.filename).to eq("piece_justificative_0.pdf")
end
end
context "attachment not associated to dossier" do
let(:other_champ) { create(:champ, :with_piece_justificative_file) }
let(:replace_attachment_id) { other_champ.piece_justificative_file.first.id }
it "add attachment, don't replace attachment" do
expect(champ.reload.piece_justificative_file.attachments.count).to eq(1)
expect(other_champ.reload.piece_justificative_file.attachments.count).to eq(1)
end
end
end
end
describe '#template' do