From 6d89d914e2c2615b6c8dfee002245013d6f869bb Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 27 Sep 2021 15:07:32 +0200
Subject: [PATCH 01/11] modify expert avis controller

---
 app/controllers/experts/avis_controller.rb | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/app/controllers/experts/avis_controller.rb b/app/controllers/experts/avis_controller.rb
index 1bc066d91..3848397e4 100644
--- a/app/controllers/experts/avis_controller.rb
+++ b/app/controllers/experts/avis_controller.rb
@@ -1,11 +1,12 @@
 module Experts
   class AvisController < ExpertController
     include CreateAvisConcern
+    include Zipline
 
     before_action :authenticate_expert!, except: [:sign_up, :update_expert]
     before_action :check_if_avis_revoked, only: [:show]
     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'
     DONNES_STATUS   = 'donnes'
@@ -120,6 +121,14 @@ module Experts
       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
 
     def redirect_if_no_sign_up_needed

From bfee9c275c0addb46f35812569a263dabe4b4895 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 27 Sep 2021 15:07:42 +0200
Subject: [PATCH 02/11] layout

---
 app/views/experts/avis/_header.html.haml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/app/views/experts/avis/_header.html.haml b/app/views/experts/avis/_header.html.haml
index 803e317f4..ce3fa5f8c 100644
--- a/app/views/experts/avis/_header.html.haml
+++ b/app/views/experts/avis/_header.html.haml
@@ -5,6 +5,16 @@
       %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))
 
+      %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
       = 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?)

From 6c82e40ddb2955418b1183cbcb6a648478c89fd8 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 27 Sep 2021 15:07:50 +0200
Subject: [PATCH 03/11] routes

---
 config/routes.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/config/routes.rb b/config/routes.rb
index dcb922acb..2c8bb1c31 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -295,6 +295,7 @@ Rails.application.routes.draw do
             post 'commentaire' => 'avis#create_commentaire'
             post 'avis' => 'avis#create_avis'
             get 'bilans_bdf'
+            get 'telecharger_pjs' => 'avis#telecharger_pjs'
 
             get 'sign_up' => 'avis#sign_up'
             post 'sign_up' => 'avis#update_expert'

From cbedef996be174295e30fb1df4a539e989db5eea Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 27 Sep 2021 15:07:56 +0200
Subject: [PATCH 04/11] tests

---
 spec/features/experts/expert_spec.rb          | 39 ++++++++++++++++++-
 .../active_storage/downloadable_file_spec.rb  | 20 ++++++++++
 .../pieces_justificatives_service_spec.rb     |  2 +-
 3 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/spec/features/experts/expert_spec.rb b/spec/features/experts/expert_spec.rb
index 5a1398da7..06eaa6126 100644
--- a/spec/features/experts/expert_spec.rb
+++ b/spec/features/experts/expert_spec.rb
@@ -5,9 +5,10 @@ feature 'Inviting an expert:' do
   context 'as an invited Expert' do
     let(:expert) { create(:expert) }
     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(: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) }
 
     context 'when I don’t already have an account' do
@@ -83,6 +84,42 @@ feature 'Inviting an expert:' do
 
     # scenario 'I can invite other experts' do
     # 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
 
   context 'when there are two experts' do
diff --git a/spec/lib/active_storage/downloadable_file_spec.rb b/spec/lib/active_storage/downloadable_file_spec.rb
index 835e8b731..755a0643c 100644
--- a/spec/lib/active_storage/downloadable_file_spec.rb
+++ b/spec/lib/active_storage/downloadable_file_spec.rb
@@ -50,5 +50,25 @@ describe ActiveStorage::DownloadableFile do
 
       it { expect(list.length).to eq 2 }
     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
diff --git a/spec/services/pieces_justificatives_service_spec.rb b/spec/services/pieces_justificatives_service_spec.rb
index a9a78742c..32e46303d 100644
--- a/spec/services/pieces_justificatives_service_spec.rb
+++ b/spec/services/pieces_justificatives_service_spec.rb
@@ -33,7 +33,7 @@ describe PiecesJustificativesService do
   end
 
   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
       expect(champ_identite.piece_justificative_file).to be_attached

From 6ef5b5d47486eaf66faf7f0fb4e9c9fc5088d2f1 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Tue, 28 Sep 2021 11:23:54 +0200
Subject: [PATCH 05/11] modify downloable file

---
 app/lib/active_storage/downloadable_file.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/lib/active_storage/downloadable_file.rb b/app/lib/active_storage/downloadable_file.rb
index bdfe5760b..49bd45f32 100644
--- a/app/lib/active_storage/downloadable_file.rb
+++ b/app/lib/active_storage/downloadable_file.rb
@@ -1,7 +1,7 @@
 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)
-    pjs = [dossier_export] + PiecesJustificativesService.liste_documents(dossier)
+    pjs = [dossier_export] + PiecesJustificativesService.liste_documents(dossier, for_expert)
     pjs.map do |piece_justificative|
       [
         piece_justificative,

From 0bd879ec9baa222f0cce8878cdcb1968c5d07168 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Tue, 28 Sep 2021 11:24:06 +0200
Subject: [PATCH 06/11] modify pj service

---
 app/services/pieces_justificatives_service.rb | 31 +++++++++++--------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/app/services/pieces_justificatives_service.rb b/app/services/pieces_justificatives_service.rb
index 8b1d59a7c..da59962b4 100644
--- a/app/services/pieces_justificatives_service.rb
+++ b/app/services/pieces_justificatives_service.rb
@@ -1,8 +1,8 @@
 class PiecesJustificativesService
-  def self.liste_documents(dossier)
-    pjs_champs = pjs_for_champs(dossier)
+  def self.liste_documents(dossier, for_expert)
+    pjs_champs = pjs_for_champs(dossier, for_expert)
     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)
       .filter(&:attached?)
@@ -120,8 +120,8 @@ class PiecesJustificativesService
 
   private
 
-  def self.pjs_for_champs(dossier)
-    allowed_champs = dossier.champs + dossier.champs_private
+  def self.pjs_for_champs(dossier, for_expert = false)
+    allowed_champs = for_expert ? dossier.champs : dossier.champs + dossier.champs_private
 
     allowed_child_champs = allowed_champs
       .filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) }
@@ -138,17 +138,22 @@ class PiecesJustificativesService
       .map(&:piece_jointe)
   end
 
-  def self.pjs_for_dossier(dossier)
-    bill_signatures = dossier.dossier_operation_logs.filter_map(&:bill_signature).uniq
-
-    [
+  def self.pjs_for_dossier(dossier, for_expert = false)
+    pjs = [
       dossier.justificatif_motivation,
       dossier.attestation&.pdf,
       dossier.etablissement&.entreprise_attestation_sociale,
-      dossier.etablissement&.entreprise_attestation_fiscale,
-      dossier.dossier_operation_logs.map(&:serialized),
-      bill_signatures.map(&:serialized),
-      bill_signatures.map(&:signature)
+      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),
+        bill_signatures.map(&:serialized),
+        bill_signatures.map(&:signature)
+      ].flatten.compact
+    end
+    pjs
   end
 end

From 9c9eeb8e76409f0c1b1e133b0a8f1a32a81371c0 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 4 Oct 2021 16:02:19 +0200
Subject: [PATCH 07/11] modify groupe instructeurs controller$

---
 .../groupe_instructeurs_controller.rb          | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
index d3e6d3f9f..5ab1b9eb6 100644
--- a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
+++ b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
@@ -236,6 +236,24 @@ module NewAdministrateur
       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
 
     def create_instructeur(email)

From 6251c3369b72553681b39db22ebab8f1b4e83ed9 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 4 Oct 2021 16:02:37 +0200
Subject: [PATCH 08/11] routes

---
 config/routes.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/config/routes.rb b/config/routes.rb
index 2c8bb1c31..3e8b1168b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -427,6 +427,7 @@ Rails.application.routes.draw do
           patch 'update_routing_enabled'
           patch 'update_instructeurs_self_management_enabled'
           post 'import'
+          get 'export_groupe_instructeurs'
         end
       end
 

From 177074bf327225aa12e3aa0e88eb8348732a3fed Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 4 Oct 2021 16:02:50 +0200
Subject: [PATCH 09/11] css

---
 app/assets/stylesheets/labels.scss | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/app/assets/stylesheets/labels.scss b/app/assets/stylesheets/labels.scss
index b41676f3b..50be851d2 100644
--- a/app/assets/stylesheets/labels.scss
+++ b/app/assets/stylesheets/labels.scss
@@ -69,3 +69,10 @@
   position: absolute !important;
   word-wrap: normal !important;
 }
+
+.label-bold {
+  font-size: 18px;
+  margin-bottom: 16px;
+  display: block;
+  font-weight: bold;
+}

From e4b61eae21333a540de20c40d17db69dac0e8258 Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 4 Oct 2021 16:02:58 +0200
Subject: [PATCH 10/11] layout

---
 .../new_administrateur/groupe_instructeurs/_edit.html.haml   | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/app/views/new_administrateur/groupe_instructeurs/_edit.html.haml b/app/views/new_administrateur/groupe_instructeurs/_edit.html.haml
index c72ba1068..98892fd39 100644
--- a/app/views/new_administrateur/groupe_instructeurs/_edit.html.haml
+++ b/app/views/new_administrateur/groupe_instructeurs/_edit.html.haml
@@ -1,3 +1,4 @@
+- groupe_instructeurs_count = procedure.groupe_instructeurs.count
 .card
   = form_for procedure,
     url: { action: :update_routing_criteria_name },
@@ -17,7 +18,7 @@
       Ajouter un groupe
     %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.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
   = 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
                   %span.icon.follow
                   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

From 919d708ec459fe6337fae9e74d66a14dbd44187f Mon Sep 17 00:00:00 2001
From: kara Diaby <kdiaby.pro@gmail.com>
Date: Mon, 4 Oct 2021 18:18:54 +0200
Subject: [PATCH 11/11] tests

---
 .../groupe_instructeurs_controller_spec.rb    | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb
index de9d81cd5..c789f8fc2 100644
--- a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb
+++ b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb
@@ -422,6 +422,31 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
     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
     before do
       patch :update_routing_criteria_name,