Merge pull request #6526 from betagouv/main

2021-10-06-01
This commit is contained in:
Kara Diaby 2021-10-06 10:36:11 +02:00 committed by GitHub
commit d97f2a79f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 155 additions and 19 deletions

View file

@ -69,3 +69,10 @@
position: absolute !important; position: absolute !important;
word-wrap: normal !important; word-wrap: normal !important;
} }
.label-bold {
font-size: 18px;
margin-bottom: 16px;
display: block;
font-weight: bold;
}

View file

@ -1,11 +1,12 @@
module Experts module Experts
class AvisController < ExpertController class AvisController < ExpertController
include CreateAvisConcern include CreateAvisConcern
include Zipline
before_action :authenticate_expert!, except: [:sign_up, :update_expert] before_action :authenticate_expert!, except: [:sign_up, :update_expert]
before_action :check_if_avis_revoked, only: [:show] before_action :check_if_avis_revoked, only: [:show]
before_action :redirect_if_no_sign_up_needed, only: [:sign_up, :update_expert] before_action :redirect_if_no_sign_up_needed, only: [:sign_up, :update_expert]
before_action :set_avis_and_dossier, only: [:show, :instruction, :messagerie, :create_commentaire, :update] before_action :set_avis_and_dossier, only: [:show, :instruction, :messagerie, :create_commentaire, :update, :telecharger_pjs]
A_DONNER_STATUS = 'a-donner' A_DONNER_STATUS = 'a-donner'
DONNES_STATUS = 'donnes' DONNES_STATUS = 'donnes'
@ -120,6 +121,14 @@ module Experts
end end
end end
def telecharger_pjs
return head(:forbidden) if !avis.dossier.export_and_attachments_downloadable?
files = ActiveStorage::DownloadableFile.create_list_from_dossier(@dossier, true)
zipline(files, "dossier-#{@dossier.id}.zip")
end
private private
def redirect_if_no_sign_up_needed def redirect_if_no_sign_up_needed

View file

@ -236,6 +236,24 @@ module NewAdministrateur
end end
end end
def export_groupe_instructeurs
groupe_instructeurs = procedure.groupe_instructeurs
data = CSV.generate(headers: true) do |csv|
column_names = ["Groupe", "Email"]
csv << column_names
groupe_instructeurs.each do |gi|
gi.instructeurs.each do |instructeur|
csv << [gi.label, instructeur.email]
end
end
end
respond_to do |format|
format.csv { send_data data, filename: "#{procedure.id}-groupe-instructeurs-#{Date.today}.csv" }
end
end
private private
def create_instructeur(email) def create_instructeur(email)

View file

@ -1,7 +1,7 @@
class ActiveStorage::DownloadableFile class ActiveStorage::DownloadableFile
def self.create_list_from_dossier(dossier) def self.create_list_from_dossier(dossier, for_expert = false)
dossier_export = PiecesJustificativesService.generate_dossier_export(dossier) dossier_export = PiecesJustificativesService.generate_dossier_export(dossier)
pjs = [dossier_export] + PiecesJustificativesService.liste_documents(dossier) pjs = [dossier_export] + PiecesJustificativesService.liste_documents(dossier, for_expert)
pjs.map do |piece_justificative| pjs.map do |piece_justificative|
[ [
piece_justificative, piece_justificative,

View file

@ -1,8 +1,8 @@
class PiecesJustificativesService class PiecesJustificativesService
def self.liste_documents(dossier) def self.liste_documents(dossier, for_expert)
pjs_champs = pjs_for_champs(dossier) pjs_champs = pjs_for_champs(dossier, for_expert)
pjs_commentaires = pjs_for_commentaires(dossier) pjs_commentaires = pjs_for_commentaires(dossier)
pjs_dossier = pjs_for_dossier(dossier) pjs_dossier = pjs_for_dossier(dossier, for_expert)
(pjs_champs + pjs_commentaires + pjs_dossier) (pjs_champs + pjs_commentaires + pjs_dossier)
.filter(&:attached?) .filter(&:attached?)
@ -120,8 +120,8 @@ class PiecesJustificativesService
private private
def self.pjs_for_champs(dossier) def self.pjs_for_champs(dossier, for_expert = false)
allowed_champs = dossier.champs + dossier.champs_private allowed_champs = for_expert ? dossier.champs : dossier.champs + dossier.champs_private
allowed_child_champs = allowed_champs allowed_child_champs = allowed_champs
.filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } .filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) }
@ -138,17 +138,22 @@ class PiecesJustificativesService
.map(&:piece_jointe) .map(&:piece_jointe)
end end
def self.pjs_for_dossier(dossier) def self.pjs_for_dossier(dossier, for_expert = false)
bill_signatures = dossier.dossier_operation_logs.filter_map(&:bill_signature).uniq pjs = [
[
dossier.justificatif_motivation, dossier.justificatif_motivation,
dossier.attestation&.pdf, dossier.attestation&.pdf,
dossier.etablissement&.entreprise_attestation_sociale, dossier.etablissement&.entreprise_attestation_sociale,
dossier.etablissement&.entreprise_attestation_fiscale, dossier.etablissement&.entreprise_attestation_fiscale
].flatten.compact
if !for_expert
bill_signatures = dossier.dossier_operation_logs.filter_map(&:bill_signature).uniq
pjs += [
dossier.dossier_operation_logs.map(&:serialized), dossier.dossier_operation_logs.map(&:serialized),
bill_signatures.map(&:serialized), bill_signatures.map(&:serialized),
bill_signatures.map(&:signature) bill_signatures.map(&:signature)
].flatten.compact ].flatten.compact
end end
pjs
end
end end

View file

@ -5,6 +5,16 @@
%li= link_to(dossier.procedure.libelle, procedure_expert_avis_index_path(avis.procedure)) %li= link_to(dossier.procedure.libelle, procedure_expert_avis_index_path(avis.procedure))
%li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis)) %li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis))
%span.dropdown.print-menu-opener
%button.button.dropdown-button.icon-only{ 'aria-expanded' => 'false', 'aria-controls' => 'print-pj-menu' }
%span.icon.attached
%ul#print-pj-menu.print-menu.dropdown-content
%li
- if dossier.export_and_attachments_downloadable?
= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_expert_avis_path(avis), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- else
%p.menu-item Le téléchargement des pièces jointes est désactivé pour les dossiers de plus de #{number_to_human_size Dossier::TAILLE_MAX_ZIP}.
%ul.tabs %ul.tabs
= dynamic_tab_item('Demande', expert_avis_path(avis.procedure, avis)) = dynamic_tab_item('Demande', expert_avis_path(avis.procedure, avis))
= dynamic_tab_item('Avis', instruction_expert_avis_path(avis.procedure, avis), notification: avis.answer.blank?) = dynamic_tab_item('Avis', instruction_expert_avis_path(avis.procedure, avis), notification: avis.answer.blank?)

View file

@ -1,3 +1,4 @@
- groupe_instructeurs_count = procedure.groupe_instructeurs.count
.card .card
= form_for procedure, = form_for procedure,
url: { action: :update_routing_criteria_name }, url: { action: :update_routing_criteria_name },
@ -17,7 +18,7 @@
Ajouter un groupe Ajouter un groupe
%p.notice Ce groupe sera un choix de la liste « #{procedure.routing_criteria_name} » . %p.notice Ce groupe sera un choix de la liste « #{procedure.routing_criteria_name} » .
= f.text_field :label, placeholder: 'ex. Ville de Bordeaux', required: true = f.text_field :label, placeholder: 'ex. Ville de Bordeaux', required: true
= f.submit 'Ajouter le groupe', class: 'button primary send' = f.submit 'Ajouter le groupe', class: "button primary send"
- csv_max_size = NewAdministrateur::GroupeInstructeursController::CSV_MAX_SIZE - csv_max_size = NewAdministrateur::GroupeInstructeursController::CSV_MAX_SIZE
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do = form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
@ -48,5 +49,7 @@
= link_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(procedure, group), class: 'button', title:'Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer' do = link_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(procedure, group), class: 'button', title:'Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer' do
%span.icon.follow %span.icon.follow
déplacer les dossiers déplacer les dossiers
- if groupe_instructeurs_count > 1
= link_to "Exporter au format CSV", export_groupe_instructeurs_admin_procedure_groupe_instructeurs_path(procedure, format: :csv)
= paginate groupes_instructeurs = paginate groupes_instructeurs

View file

@ -295,6 +295,7 @@ Rails.application.routes.draw do
post 'commentaire' => 'avis#create_commentaire' post 'commentaire' => 'avis#create_commentaire'
post 'avis' => 'avis#create_avis' post 'avis' => 'avis#create_avis'
get 'bilans_bdf' get 'bilans_bdf'
get 'telecharger_pjs' => 'avis#telecharger_pjs'
get 'sign_up' => 'avis#sign_up' get 'sign_up' => 'avis#sign_up'
post 'sign_up' => 'avis#update_expert' post 'sign_up' => 'avis#update_expert'
@ -426,6 +427,7 @@ Rails.application.routes.draw do
patch 'update_routing_enabled' patch 'update_routing_enabled'
patch 'update_instructeurs_self_management_enabled' patch 'update_instructeurs_self_management_enabled'
post 'import' post 'import'
get 'export_groupe_instructeurs'
end end
end end

View file

@ -422,6 +422,31 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end end
end end
describe '#export_groupe_instructeurs' do
let(:procedure) { create(:procedure, :published) }
let(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 1 2') }
let(:instructeur_assigned_1) { create :instructeur, email: 'instructeur_1@ministere_a.gouv.fr', administrateurs: [admin] }
let(:instructeur_assigned_2) { create :instructeur, email: 'instructeur_2@ministere_b.gouv.fr', administrateurs: [admin] }
subject do
get :export_groupe_instructeurs, params: { procedure_id: procedure.id, format: :csv }
end
before do
procedure.administrateurs << admin
gi_1_2.instructeurs << [instructeur_assigned_1, instructeur_assigned_2]
end
it 'generates a CSV file containing the instructeurs and groups' do
expect(subject.status).to eq(200)
expect(subject.stream.body.split("\n").size).to eq(3)
expect(subject.stream.body).to include("groupe instructeur 1 2")
expect(subject.stream.body).to include(instructeur_assigned_1.email)
expect(subject.stream.body).to include(instructeur_assigned_2.email)
expect(subject.header["Content-Disposition"]).to include("#{procedure.id}-groupe-instructeurs-#{Date.today}.csv")
end
end
describe '#update_routing_criteria_name' do describe '#update_routing_criteria_name' do
before do before do
patch :update_routing_criteria_name, patch :update_routing_criteria_name,

View file

@ -5,9 +5,10 @@ feature 'Inviting an expert:' do
context 'as an invited Expert' do context 'as an invited Expert' do
let(:expert) { create(:expert) } let(:expert) { create(:expert) }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let(:procedure) { create(:procedure, :published, :with_piece_justificative, instructeurs: [instructeur]) }
let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) } let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) }
let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) }
let(:champ) { dossier.champs.first }
let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: true) } let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: true) }
context 'when I dont already have an account' do context 'when I dont already have an account' do
@ -83,6 +84,42 @@ feature 'Inviting an expert:' do
# scenario 'I can invite other experts' do # scenario 'I can invite other experts' do
# end # end
context 'with dossiers having attached files', js: true do
let(:path) { 'spec/fixtures/files/piece_justificative_0.pdf' }
let(:commentaire) { create(:commentaire, instructeur: instructeur, dossier: dossier) }
before do
champ.piece_justificative_file.attach(io: File.open(path), filename: "piece_justificative_0.pdf", content_type: "application/pdf")
dossier.champs_private << create(:champ_piece_justificative, :with_piece_justificative_file, private: true, dossier: dossier)
end
scenario 'An Expert can download an archive containing attachments without any private champ, bill signature and operations logs' do
avis # create avis
login_as expert.user, scope: :user
visit expert_all_avis_path
click_on '1 avis à donner'
click_on avis.dossier.user.email
find(:css, '.attached').click
click_on 'Télécharger le dossier et toutes ses pièces jointes'
# For some reason, clicking the download link does not trigger the download in the headless browser ;
# So we need to go to the download link directly
visit telecharger_pjs_expert_avis_path(avis.dossier.procedure, avis)
DownloadHelpers.wait_for_download
files = ZipTricks::FileReader.read_zip_structure(io: File.open(DownloadHelpers.download))
expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip"
expect(files.size).to be 2
expect(files[0].filename.include?('export')).to be_truthy
expect(files[1].filename.include?('piece_justificative_0')).to be_truthy
expect(files[1].uncompressed_size).to be File.size(path)
end
before { DownloadHelpers.clear_downloads }
after { DownloadHelpers.clear_downloads }
end
end end
context 'when there are two experts' do context 'when there are two experts' do

View file

@ -50,5 +50,25 @@ describe ActiveStorage::DownloadableFile do
it { expect(list.length).to eq 2 } it { expect(list.length).to eq 2 }
end end
context 'when the files are asked by an expert with piece justificative and private piece justificative' do
let(:expert) { create(:expert) }
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published, :with_piece_justificative, instructeurs: [instructeur]) }
let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) }
let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) }
let(:champ) { dossier.champs.first }
let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: true) }
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossier(dossier, true) }
before do
dossier.champs_private << create(:champ_piece_justificative, :with_piece_justificative_file, private: true, dossier: dossier)
dossier.champs << create(:champ_piece_justificative, :with_piece_justificative_file, dossier: dossier)
end
it { expect(list.length).to eq 2 }
end
end end
end end

View file

@ -33,7 +33,7 @@ describe PiecesJustificativesService do
end end
describe '.liste_documents' do describe '.liste_documents' do
subject { PiecesJustificativesService.liste_documents(dossier) } subject { PiecesJustificativesService.liste_documents(dossier, false) }
it "doesn't return sensitive documents like titre_identite" do it "doesn't return sensitive documents like titre_identite" do
expect(champ_identite.piece_justificative_file).to be_attached expect(champ_identite.piece_justificative_file).to be_attached