Merge pull request #9507 from demarches-simplifiees/9449-signature-groupe-instructeur
9449 ETQ instructeur ou admin, je peux apposer sur une attestation un tampon dédié à un groupe instructeur
This commit is contained in:
commit
428ae4a45a
21 changed files with 258 additions and 76 deletions
|
@ -83,7 +83,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
if champ.present?
|
||||
auto_attach_url
|
||||
else
|
||||
attachment_path(user_can_edit: true, view_as: @view_as, auto_attach_url: @auto_attach_url)
|
||||
attachment_path(user_can_edit: true, view_as: @view_as, auto_attach_url: @auto_attach_url, direct_upload: @direct_upload)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -204,12 +204,14 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def allowed_formats
|
||||
return nil unless champ&.titre_identite?
|
||||
|
||||
@allowed_formats ||= begin
|
||||
content_type_validator.options[:in].filter_map do |content_type|
|
||||
formats = content_type_validator.options[:in].filter_map do |content_type|
|
||||
MiniMime.lookup_by_content_type(content_type)&.extension
|
||||
end.uniq.sort_by { EXTENSIONS_ORDER.index(_1) || 999 }
|
||||
|
||||
# When too many formats are allowed, consider instead manually indicating
|
||||
# above the input a more comprehensive of formats allowed, like "any image", or a simplified list.
|
||||
formats.size > 5 ? [] : formats
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
%p.fr-hint-text.fr-mb-1w
|
||||
- if max_file_size.present?
|
||||
= t('.max_file_size', max_file_size: number_to_human_size(max_file_size))
|
||||
- if allowed_formats
|
||||
- if allowed_formats.present?
|
||||
= t('.allowed_formats', formats: allowed_formats.join(', '))
|
||||
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def alert_for_missing_siret_service
|
||||
return if flash[:alert].present?
|
||||
|
||||
procedures = missing_siret_services
|
||||
if procedures.any?
|
||||
errors = []
|
||||
|
@ -61,6 +63,8 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def alert_for_missing_service
|
||||
return if flash[:alert].present?
|
||||
|
||||
procedures = missing_service
|
||||
if procedures.any?
|
||||
errors = []
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module Administrateurs
|
||||
class AttestationTemplatesController < AdministrateurController
|
||||
include UninterlacePngConcern
|
||||
|
||||
before_action :retrieve_procedure
|
||||
|
||||
def show
|
||||
|
@ -63,23 +65,14 @@ module Administrateurs
|
|||
signature_file = params['attestation_template'].delete('signature')
|
||||
|
||||
if logo_file.present?
|
||||
@activated_attestation_params[:logo] = uninterlaced_png(logo_file)
|
||||
@activated_attestation_params[:logo] = uninterlace_png(logo_file)
|
||||
end
|
||||
if signature_file.present?
|
||||
@activated_attestation_params[:signature] = uninterlaced_png(signature_file)
|
||||
@activated_attestation_params[:signature] = uninterlace_png(signature_file)
|
||||
end
|
||||
end
|
||||
|
||||
@activated_attestation_params
|
||||
end
|
||||
|
||||
def uninterlaced_png(uploaded_file)
|
||||
if uploaded_file&.content_type == 'image/png'
|
||||
chunky_img = ChunkyPNG::Image.from_io(uploaded_file.to_io)
|
||||
chunky_img.save(uploaded_file.tempfile.to_path, interlace: false)
|
||||
uploaded_file.tempfile.reopen(uploaded_file.tempfile.to_path, 'rb')
|
||||
end
|
||||
uploaded_file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ module Administrateurs
|
|||
class GroupeInstructeursController < AdministrateurController
|
||||
include ActiveSupport::NumberHelper
|
||||
include Logic
|
||||
include UninterlacePngConcern
|
||||
include GroupeInstructeursSignatureConcern
|
||||
|
||||
before_action :ensure_not_super_admin!, only: [:add_instructeur]
|
||||
|
||||
|
@ -389,6 +391,10 @@ module Administrateurs
|
|||
params.require(:groupe_instructeur).permit(:label)
|
||||
end
|
||||
|
||||
def signature_params
|
||||
params.require(:groupe_instructeur).permit(:signature)
|
||||
end
|
||||
|
||||
def paginated_groupe_instructeurs
|
||||
groupes = if params[:q].present?
|
||||
query = ActiveRecord::Base.sanitize_sql_like(params[:q])
|
||||
|
|
|
@ -6,6 +6,7 @@ class AttachmentsController < ApplicationController
|
|||
@attachment = @blob.attachments.find(params[:id])
|
||||
|
||||
@user_can_edit = cast_bool(params[:user_can_edit])
|
||||
@direct_upload = cast_bool(params[:direct_upload])
|
||||
@view_as = params[:view_as]&.to_sym
|
||||
@auto_attach_url = params[:auto_attach_url]
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
module GroupeInstructeursSignatureConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def add_signature
|
||||
@procedure = procedure
|
||||
@groupe_instructeur = groupe_instructeur
|
||||
@instructeurs = paginated_instructeurs
|
||||
|
||||
signature_file = params[:groupe_instructeur][:signature]
|
||||
|
||||
if params[:groupe_instructeur].nil? || signature_file.blank?
|
||||
if respond_to?(:available_instructeur_emails)
|
||||
@available_instructeur_emails = available_instructeur_emails
|
||||
end
|
||||
|
||||
flash[:alert] = "Aucun fichier joint pour le tampon de l'attestation"
|
||||
render :show
|
||||
else
|
||||
signature = uninterlace_png(signature_file)
|
||||
|
||||
if @groupe_instructeur.signature.attach(signature)
|
||||
handle_redirect :success
|
||||
else
|
||||
handle_redirect :alert
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def preview_attestation
|
||||
attestation_template = procedure.attestation_template || procedure.build_attestation_template
|
||||
@attestation = attestation_template.render_attributes_for({ groupe_instructeur: groupe_instructeur })
|
||||
|
||||
render 'administrateurs/attestation_templates/show', formats: [:pdf]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_redirect(status)
|
||||
redirect, preview = if self.class.module_parent_name == "Administrateurs"
|
||||
[
|
||||
:admin_procedure_groupe_instructeur_path,
|
||||
:preview_attestation_admin_procedure_groupe_instructeur_path
|
||||
]
|
||||
else
|
||||
[
|
||||
:instructeur_groupe_path,
|
||||
:preview_attestation_instructeur_groupe_path
|
||||
]
|
||||
end
|
||||
|
||||
redirect_path = method(redirect).call(@procedure, @groupe_instructeur)
|
||||
preview_path = method(preview).call(@procedure, @groupe_instructeur)
|
||||
|
||||
case status
|
||||
when :success
|
||||
redirect_to redirect_path, notice: "Le tampon de l’attestation a bien été ajouté. #{helpers.link_to("Prévisualiser l’attestation", preview_path)}"
|
||||
when :alert
|
||||
redirect_to redirect_path, alert: "Une erreur a empêché l’ajout du tampon. Réessayez dans quelques instants."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
app/controllers/concerns/uninterlace_png_concern.rb
Normal file
19
app/controllers/concerns/uninterlace_png_concern.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module UninterlacePngConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def uninterlace_png(uploaded_file)
|
||||
if uploaded_file&.content_type == 'image/png' && interlaced?(uploaded_file.tempfile.to_path)
|
||||
chunky_img = ChunkyPNG::Image.from_io(uploaded_file.to_io)
|
||||
chunky_img.save(uploaded_file.tempfile.to_path, interlace: false)
|
||||
uploaded_file.tempfile.reopen(uploaded_file.tempfile.to_path, 'rb')
|
||||
end
|
||||
uploaded_file
|
||||
end
|
||||
|
||||
def interlaced?(png_path)
|
||||
png = MiniMagick::Image.open(png_path)
|
||||
png.data["interlace"] != "None"
|
||||
end
|
||||
end
|
|
@ -1,5 +1,8 @@
|
|||
module Instructeurs
|
||||
class GroupeInstructeursController < InstructeurController
|
||||
include UninterlacePngConcern
|
||||
include GroupeInstructeursSignatureConcern
|
||||
|
||||
ITEMS_PER_PAGE = 25
|
||||
|
||||
def index
|
||||
|
|
|
@ -60,16 +60,27 @@ class AttestationTemplate < ApplicationRecord
|
|||
end
|
||||
|
||||
def render_attributes_for(params = {})
|
||||
dossier = params.fetch(:dossier, false)
|
||||
|
||||
{
|
||||
attributes = {
|
||||
created_at: Time.zone.now,
|
||||
title: dossier ? replace_tags(title, dossier) : params.fetch(:title, title),
|
||||
body: dossier ? replace_tags(body, dossier) : params.fetch(:body, body),
|
||||
footer: params.fetch(:footer, footer),
|
||||
logo: params.fetch(:logo, logo.attached? ? logo : nil),
|
||||
signature: params.fetch(:signature, signature.attached? ? signature : nil)
|
||||
logo: params.fetch(:logo, logo.attached? ? logo : nil)
|
||||
}
|
||||
|
||||
dossier = params[:dossier]
|
||||
|
||||
if dossier.present?
|
||||
attributes.merge({
|
||||
title: replace_tags(title, dossier),
|
||||
body: replace_tags(body, dossier),
|
||||
signature: signature_to_render(dossier.groupe_instructeur)
|
||||
})
|
||||
else
|
||||
attributes.merge({
|
||||
title: params.fetch(:title, title),
|
||||
body: params.fetch(:body, body),
|
||||
signature: signature_to_render(params[:groupe_instructeur])
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def logo_checksum
|
||||
|
@ -90,6 +101,14 @@ class AttestationTemplate < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def signature_to_render(groupe_instructeur)
|
||||
if groupe_instructeur&.signature&.attached?
|
||||
groupe_instructeur.signature
|
||||
else
|
||||
signature
|
||||
end
|
||||
end
|
||||
|
||||
def used_tags
|
||||
used_tags_for(title) + used_tags_for(body)
|
||||
end
|
||||
|
|
|
@ -15,6 +15,11 @@ class GroupeInstructeur < ApplicationRecord
|
|||
has_one :defaut_procedure, -> { with_discarded }, class_name: 'Procedure', foreign_key: :defaut_groupe_instructeur_id, dependent: :nullify, inverse_of: :defaut_groupe_instructeur
|
||||
has_one :contact_information
|
||||
|
||||
has_one_attached :signature
|
||||
|
||||
SIGNATURE_MAX_SIZE = 1.megabytes
|
||||
validates :signature, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: SIGNATURE_MAX_SIZE }
|
||||
|
||||
validates :label, presence: true, allow_nil: false
|
||||
validates :label, uniqueness: { scope: :procedure }
|
||||
validates :closed, acceptance: { accept: [false] }, if: -> { (self == procedure.defaut_groupe_instructeur) }
|
||||
|
|
|
@ -24,19 +24,22 @@
|
|||
= tag[:description]
|
||||
|
||||
%h3.header-subsection Logo de l'attestation
|
||||
%p.fr-text--sm.fr-text-mention--grey.fr-mb-0
|
||||
Dimensions conseillées : au minimum 500px de largeur ou de hauteur.
|
||||
= render Attachment::EditComponent.new(attached_file: @attestation_template.logo, direct_upload: false)
|
||||
|
||||
%p.notice
|
||||
Formats acceptés : JPG / JPEG / PNG.
|
||||
%br
|
||||
Dimensions conseillées : au minimum 500 px de largeur ou de hauteur, poids maximum : 0,5 Mo.
|
||||
|
||||
%h3.header-subsection Tampon de l'attestation
|
||||
%h3.header-subsection.fr-mt-5w Tampon de l'attestation
|
||||
%p.fr-text--sm.fr-text-mention--grey.fr-mb-0
|
||||
Dimensions conseillées : au minimum 500px de largeur ou de hauteur.
|
||||
= render Attachment::EditComponent.new(attached_file: @attestation_template.signature, direct_upload: false)
|
||||
|
||||
%p.notice
|
||||
Formats acceptés : JPG / JPEG / PNG.
|
||||
%br
|
||||
Dimensions conseillées : au minimum 500 px de largeur ou de hauteur, poids maximum : 0,5 Mo.
|
||||
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :footer, input_type: :text_field, opts: { maxlength: 190, size: nil }, required: false)
|
||||
- if @attestation_template.procedure.routing_enabled?
|
||||
%p.fr-text--sm.fr-text-mention--grey
|
||||
À noter : chaque groupe instructeur peut apposer son propre tampon à la place de celui-ci.
|
||||
|
||||
|
||||
.fr-mt-4w
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :footer, input_type: :text_field, opts: { maxlength: 190, size: nil }, required: false)
|
||||
|
||||
|
|
|
@ -16,3 +16,6 @@
|
|||
= render partial: 'administrateurs/groupe_instructeurs/contact_information',
|
||||
locals: { procedure: @procedure,
|
||||
groupe_instructeur: @groupe_instructeur }
|
||||
|
||||
= render partial: "shared/groupe_instructeurs/signature_form", locals: { groupe_instructeur: @groupe_instructeur,
|
||||
preview_path: preview_attestation_admin_procedure_groupe_instructeur_path(@groupe_instructeur.procedure, @groupe_instructeur) }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= turbo_stream.replace dom_id(@attachment, :edit) do
|
||||
- if @user_can_edit
|
||||
= render Attachment::EditComponent.new(attachment: @attachment, attached_file: @attachment.record.public_send(@attachment.name), auto_attach_url: @auto_attach_url, view_as: @view_as)
|
||||
= render Attachment::EditComponent.new(attachment: @attachment, attached_file: @attachment.record.public_send(@attachment.name), auto_attach_url: @auto_attach_url, view_as: @view_as, direct_upload: @direct_upload)
|
||||
- else
|
||||
= render Attachment::ShowComponent.new(attachment: @attachment)
|
||||
|
|
|
@ -65,3 +65,6 @@
|
|||
%p= service.telephone
|
||||
- if service.horaires.present?
|
||||
%p= service.horaires
|
||||
|
||||
= render partial: "shared/groupe_instructeurs/signature_form", locals: { groupe_instructeur: @groupe_instructeur,
|
||||
preview_path: preview_attestation_instructeur_groupe_path(@groupe_instructeur.procedure, @groupe_instructeur) }
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
.card.mt-2
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_with url: { action: :add_signature }, method: :post, html: { multipart: true } do |f|
|
||||
.card-title Tampon de l'attestation
|
||||
|
||||
%p.fr-text--sm.fr-text-mention--grey
|
||||
Vous pouvez apposer sur l’attestation un tampon (ou signature) dédié à ce groupe d’instructeurs.
|
||||
Si vous n’en fournissez pas, celui de la démarche sera utilisé, le cas échéant.
|
||||
|
||||
.fr-upload-group.fr-mb-4w
|
||||
%p.fr-text--sm.fr-text-mention--grey.fr-mb-1w
|
||||
Dimensions conseillées : au minimum 500px de largeur ou de hauteur.
|
||||
= render Attachment::EditComponent.new(attached_file: groupe_instructeur.signature, direct_upload: false)
|
||||
|
||||
.fr-btns-group.fr-btns-group--inline
|
||||
= f.submit 'Ajouter le tampon', class: 'fr-btn'
|
||||
|
||||
- if @groupe_instructeur.signature.persisted?
|
||||
= link_to("Prévisualiser", preview_path, class: "fr-btn fr-btn--secondary", **external_link_attributes)
|
|
@ -397,6 +397,8 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
post 'add_instructeur'
|
||||
delete 'remove_instructeur'
|
||||
post 'add_signature'
|
||||
get 'preview_attestation'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -535,6 +537,8 @@ Rails.application.routes.draw do
|
|||
delete 'remove_instructeur'
|
||||
get 'reaffecter_dossiers'
|
||||
post 'reaffecter'
|
||||
post 'add_signature'
|
||||
get 'preview_attestation'
|
||||
end
|
||||
|
||||
collection do
|
||||
|
|
|
@ -58,7 +58,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
|
|||
expect(assigns(:attestation)).to include(attestation_params)
|
||||
expect(assigns(:attestation)[:created_at]).to eq(Time.zone.now)
|
||||
expect(assigns(:attestation)[:logo]).to eq(nil)
|
||||
expect(assigns(:attestation)[:signature]).to eq(nil)
|
||||
expect(assigns(:attestation)[:signature]).not_to be_attached
|
||||
end
|
||||
it_behaves_like 'rendering a PDF successfully'
|
||||
end
|
||||
|
|
|
@ -843,4 +843,22 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
|
|||
expect(procedure4.reload.routing_enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_signature' do
|
||||
let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') }
|
||||
|
||||
before {
|
||||
post :add_signature,
|
||||
params: {
|
||||
procedure_id: procedure.id,
|
||||
id: gi_1_1.id,
|
||||
groupe_instructeur: {
|
||||
signature: signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||
it { expect(gi_1_1.signature).to be_attached }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -103,4 +103,22 @@ describe Instructeurs::GroupeInstructeursController, type: :controller do
|
|||
it { expect(response).to redirect_to(instructeur_groupe_path(procedure, gi_1_1)) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_signature' do
|
||||
let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') }
|
||||
|
||||
before do
|
||||
post :add_signature,
|
||||
params: {
|
||||
procedure_id: procedure.id,
|
||||
id: gi_1_2.id,
|
||||
groupe_instructeur: {
|
||||
signature: signature
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it { expect(response).to redirect_to(instructeur_groupe_path(procedure, gi_1_2)) }
|
||||
it { expect(gi_1_2.reload.signature).to be_attached }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,45 +1,4 @@
|
|||
describe AttestationTemplate, type: :model do
|
||||
# describe 'validate' do
|
||||
# let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
|
||||
# let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
|
||||
# let(:fake_logo) { double(AttestationTemplateLogoUploader, file: double(size: logo_size)) }
|
||||
# let(:fake_signature) { double(AttestationTemplateSignatureUploader, file: double(size: signature_size)) }
|
||||
# let(:attestation_template) { AttestationTemplate.new }
|
||||
|
||||
# before do
|
||||
# allow(attestation_template).to receive(:logo).and_return(fake_logo)
|
||||
# allow(attestation_template).to receive(:signature).and_return(fake_signature)
|
||||
# attestation_template.validate
|
||||
# end
|
||||
|
||||
# subject { attestation_template.errors.details }
|
||||
|
||||
# context 'when no files are present' do
|
||||
# let(:fake_logo) { nil }
|
||||
# let(:fake_signature) { nil }
|
||||
|
||||
# it { is_expected.to match({}) }
|
||||
# end
|
||||
|
||||
# context 'when the logo and the signature have the right size' do
|
||||
# it { is_expected.to match({}) }
|
||||
# end
|
||||
|
||||
# context 'when the logo and the signature are too heavy' do
|
||||
# let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
|
||||
# let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
|
||||
|
||||
# it do
|
||||
# expected = {
|
||||
# signature: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }],
|
||||
# logo: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }]
|
||||
# }
|
||||
|
||||
# is_expected.to match(expected)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
describe 'validates footer length' do
|
||||
let(:attestation_template) { build(:attestation_template, footer: footer) }
|
||||
|
||||
|
@ -175,4 +134,44 @@ describe AttestationTemplate, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render_attributes_for' do
|
||||
context 'signature' do
|
||||
let(:dossier) { create(:dossier, procedure: attestation.procedure, groupe_instructeur: groupe_instructeur) }
|
||||
|
||||
subject { attestation.render_attributes_for(dossier: dossier)[:signature] }
|
||||
|
||||
context 'procedure with signature' do
|
||||
let(:attestation) { create(:attestation_template, signature: Rack::Test::UploadedFile.new('spec/fixtures/files/logo_test_procedure.png', 'image/png')) }
|
||||
|
||||
context "groupe instructeur without signature" do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, signature: nil) }
|
||||
|
||||
it { expect(subject.blob.filename).to eq("logo_test_procedure.png") }
|
||||
end
|
||||
|
||||
context 'groupe instructeur with signature' do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, signature: Rack::Test::UploadedFile.new('spec/fixtures/files/black.png', 'image/png')) }
|
||||
|
||||
it { expect(subject.blob.filename).to eq("black.png") }
|
||||
end
|
||||
end
|
||||
|
||||
context 'procedure without signature' do
|
||||
let(:attestation) { create(:attestation_template, signature: nil) }
|
||||
|
||||
context "groupe instructeur without signature" do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, signature: nil) }
|
||||
|
||||
it { expect(subject.attached?).to be_falsey }
|
||||
end
|
||||
|
||||
context 'groupe instructeur with signature' do
|
||||
let(:groupe_instructeur) { create(:groupe_instructeur, signature: Rack::Test::UploadedFile.new('spec/fixtures/files/black.png', 'image/png')) }
|
||||
|
||||
it { expect(subject.blob.filename).to eq("black.png") }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue