Merge pull request #7099 from betagouv/main

2022-03-31-01
This commit is contained in:
Paul Chavard 2022-03-31 12:36:28 +02:00 committed by GitHub
commit d375da1fd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 116 additions and 112 deletions

View file

@ -479,7 +479,7 @@ GEM
byebug (~> 11.0)
pry (~> 0.13.0)
public_suffix (4.0.6)
puma (5.6.2)
puma (5.6.4)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)

View file

@ -53,7 +53,10 @@ class API::V1::DossiersController < APIController
end
order = ORDER_DIRECTIONS.fetch(params[:order], :asc)
@dossiers = @procedure.dossiers.state_not_brouillon.order_by_created_at(order)
@dossiers = @procedure
.dossiers
.visible_by_administration
.order_by_created_at(order)
rescue ActiveRecord::RecordNotFound
render json: {}, status: :not_found

View file

@ -1,6 +1,6 @@
module Instructeurs
class ArchivesController < InstructeurController
before_action :ensure_procedure_enabled
before_action :ensure_procedure_enabled, only: [:create]
def index
@procedure = procedure

View file

@ -72,7 +72,10 @@ module Types
end
def dossiers(updated_since: nil, created_since: nil, state: nil, archived: nil, revision: nil, max_revision: nil, min_revision: nil, order:)
dossiers = object.dossiers.state_not_brouillon.for_api_v2
dossiers = object
.dossiers
.visible_by_administration
.for_api_v2
if state.present?
dossiers = dossiers.where(state: state)

View file

@ -10,7 +10,10 @@ module Types
end
def dossiers(updated_since: nil, created_since: nil, state: nil, order:)
dossiers = object.dossiers.state_not_brouillon.for_api_v2
dossiers = object
.dossiers
.visible_by_administration
.for_api_v2
if state.present?
dossiers = dossiers.where(state: state)

View file

@ -14,6 +14,7 @@ class Archive < ApplicationRecord
include AASM
RETENTION_DURATION = 4.days
MAX_SIZE = 100.gigabytes
has_and_belongs_to_many :groupe_instructeurs

View file

@ -213,6 +213,7 @@ class Dossier < ApplicationRecord
.where(hidden_by_administration_at: nil)
.merge(visible_by_user.or(state_not_en_construction))
}
scope :visible_by_user_or_administration, -> { visible_by_user.or(visible_by_administration) }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) }
@ -298,13 +299,18 @@ class Dossier < ApplicationRecord
end
scope :interval_brouillon_close_to_expiration, -> do
state_brouillon.where("dossiers.created_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
state_brouillon
.visible_by_user
.where("dossiers.created_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
scope :interval_en_construction_close_to_expiration, -> do
state_en_construction.where("dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
state_en_construction
.visible_by_user_or_administration
.where("dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
scope :interval_termine_close_to_expiration, -> do
state_termine
.visible_by_user_or_administration
.where(procedures: { procedure_expires_when_termine_enabled: true })
.where("dossiers.processed_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
@ -336,14 +342,17 @@ class Dossier < ApplicationRecord
scope :brouillon_expired, -> do
state_brouillon
.visible_by_user
.where("brouillon_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
end
scope :en_construction_expired, -> do
state_en_construction
.visible_by_user_or_administration
.where("en_construction_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
end
scope :termine_expired, -> do
state_termine
.visible_by_user_or_administration
.where("termine_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
end
@ -361,11 +370,13 @@ class Dossier < ApplicationRecord
# select users who have submitted dossier for the given 'procedures.id'
users_who_submitted =
state_not_brouillon
.visible_by_user
.joins(:revision)
.where("procedure_revisions.procedure_id = procedures.id")
.select(:user_id)
# select dossier in brouillon where procedure closes in two days and for which the user has not submitted a Dossier
state_brouillon
.visible_by_user
.with_notifiable_procedure
.where("procedures.auto_archive_on - INTERVAL :before_closing = :now", { now: Time.zone.today, before_closing: INTERVAL_BEFORE_CLOSING })
.where.not(user: users_who_submitted)

View file

@ -161,6 +161,7 @@ class Instructeur < ApplicationRecord
groupe_instructeur_ids = Dossier
.send(scope) # :en_cours or :termine (or any other Dossier scope)
.merge(followed_dossiers)
.visible_by_administration
.with_notifications
.select(:groupe_instructeur_id)

View file

@ -26,7 +26,7 @@ class ProcedureArchiveService
end
attachments = create_list_of_attachments(dossiers)
download_and_zip(attachments) do |zip_filepath|
download_and_zip(archive, attachments) do |zip_filepath|
ArchiveUploader.new(procedure: @procedure, archive: archive, filepath: zip_filepath)
.upload
end
@ -46,10 +46,10 @@ class ProcedureArchiveService
private
def download_and_zip(attachments, &block)
def download_and_zip(archive, attachments, &block)
Dir.mktmpdir(nil, ARCHIVE_CREATION_DIR) do |tmp_dir|
archive_dir = File.join(tmp_dir, zip_root_folder)
zip_path = File.join(ARCHIVE_CREATION_DIR, "#{zip_root_folder}.zip")
archive_dir = File.join(tmp_dir, zip_root_folder(archive))
zip_path = File.join(ARCHIVE_CREATION_DIR, "#{zip_root_folder(archive)}.zip")
begin
FileUtils.remove_entry_secure(archive_dir) if Dir.exist?(archive_dir)
@ -60,7 +60,7 @@ class ProcedureArchiveService
Dir.chdir(tmp_dir) do
File.delete(zip_path) if File.exist?(zip_path)
system 'zip', '-0', '-r', zip_path, zip_root_folder
system 'zip', '-0', '-r', zip_path, zip_root_folder(archive)
end
yield(zip_path)
ensure
@ -70,8 +70,8 @@ class ProcedureArchiveService
end
end
def zip_root_folder
"procedure-#{@procedure.id}"
def zip_root_folder(archive)
"procedure-#{@procedure.id}-#{archive.id}"
end
def create_list_of_attachments(dossiers)

View file

@ -14,7 +14,7 @@
%p
Ce fichier est
%b valide une semaine
%b valide #{distance_of_time_in_words(Archive::RETENTION_DURATION)}
et peut-être téléchargé
%b plusieurs fois.

View file

@ -10,51 +10,53 @@
.card.featured
.card-title Gestion de vos archives
%p
Vous pouvez télécharger les archives des dossiers terminés depuis la publication de la procédure au format Zip.
L'archivage de votre démarche se fait mensuellement. Ainsi pour chaque mois depuis la publication de votre démarche vous pouvez faire une demande de création d'archive.
%p
Cet export contient les demande déposée par l'usager et la liste des pièces justificatives transmises.
Cette archive contient uniquement les dossiers terminés, les demandes déposées par l'usager et la liste des pièces justificatives transmises.
%p
Cet export nest pas possible pour le moment pour les démarches à forte volumétrie.
Les archives dont le poid est estimé à plus de #{number_to_human_size(Archive::MAX_SIZE)} ne sont pas supportées.
Nous vous invitons à regarder
= link_to 'la documentation', ARCHIVAGE_DOC_URL
afin de voir les options à votre disposition pour mettre en place un système darchive.
- if @procedure.feature_enabled?(:archive_zip_globale)
%table.table.hoverable.archive-table
%thead
%tr
%th &nbsp;
%th.text-right Nombre de dossiers terminés
%th.text-right Poids estimé
%th.center Télécharger
%table.table.hoverable.archive-table
%thead
%tr
%th &nbsp;
%th.text-right Nombre de dossiers terminés
%th.text-right Poids estimé
%th.center Télécharger
%tbody
- @count_dossiers_termines_by_month.each do |count_by_month|
- month = count_by_month["month"].to_date
- nb_dossiers_termines = count_by_month["count"]
- matching_archive = @archives.find { |archive| archive.time_span_type == 'monthly' && archive.month == month }
- weight = estimate_weight(matching_archive, nb_dossiers_termines, @average_dossier_weight)
%tbody
- @count_dossiers_termines_by_month.each do |count_by_month|
- month = count_by_month["month"].to_date
- nb_dossiers_termines = count_by_month["count"]
- matching_archive = @archives.find { |archive| archive.time_span_type == 'monthly' && archive.month == month }
- weight = estimate_weight(matching_archive, nb_dossiers_termines, @average_dossier_weight)
%tr
%td
= I18n.l(month, format: "%B %Y").capitalize
%td.text-right
= nb_dossiers_termines
%td.text-right
= number_to_human_size(weight)
%td.center
- if matching_archive.present?
- if matching_archive.status == 'generated' && matching_archive.file.attached?
= link_to url_for(matching_archive.file), class: 'button primary' do
%span.icon.download-white
= t(:archive_ready_html, scope: [:instructeurs, :procedure], generated_period: time_ago_in_words(matching_archive.updated_at))
%tr
%td
= I18n.l(month, format: "%B %Y").capitalize
%td.text-right
= nb_dossiers_termines
%td.text-right
= number_to_human_size(weight)
%td.center
- if matching_archive.present?
- if matching_archive.status == 'generated' && matching_archive.file.attached?
= link_to url_for(matching_archive.file), class: 'button primary' do
%span.icon.download-white
= t(:archive_ready_html, scope: [:instructeurs, :procedure], generated_period: time_ago_in_words(matching_archive.updated_at))
- else
%span.icon.retry
= t(:archive_pending_html, scope: [:instructeurs, :procedure], created_period: time_ago_in_words(matching_archive.created_at))
- elsif weight < Archive::MAX_SIZE
= link_to instructeur_archives_path(@procedure, type:'monthly', month: month.strftime('%Y-%m')), method: :post, class: "button" do
%span.icon.new-folder
Demander la création
- else
%span.icon.retry
= t(:archive_pending_html, scope: [:instructeurs, :procedure], created_period: time_ago_in_words(matching_archive.created_at))
- elsif weight < 1.gigabyte
= link_to instructeur_archives_path(@procedure, type:'monthly', month: month.strftime('%Y-%m')), method: :post, class: "button" do
%span.icon.new-folder
Demander la création
- else
Archive trop volumineuse
Archive trop volumineuse
- else
%p Cet fonctionnalité est en cours de deploiement, merci de faire une demande à notre support pour que nous l'activions pour votre démarche

View file

@ -15,6 +15,5 @@
- else
%span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, no_progress_notification: true) }
= t("export_#{time_span_type}_pending_html", export_time: time_ago_in_words(export.created_at), export_format: ".#{format}", scope: [:instructeurs, :procedure])
- if procedure.feature_enabled?(:archive_zip_globale)
%li
= link_to t(:download_archive, scope: [:instructeurs, :procedure]), instructeur_archives_path(procedure)
%li
= link_to t(:download_archive, scope: [:instructeurs, :procedure]), instructeur_archives_path(procedure)

View file

@ -1,7 +1,7 @@
= form_for(commentaire, url: form_url, html: { class: 'form', data: { persisted_content_id: @dossier.present? ? @dossier.id : "bulk-message-#{@procedure.id}" } }) do |f|
- dossier = commentaire.dossier
- placeholder = t('views.shared.dossiers.messages.form.write_message_to_administration_placeholder')
- if instructeur_signed_in? || administrateur_signed_in?
- if instructeur_signed_in? || administrateur_signed_in? || expert_signed_in?
- placeholder = t('views.shared.dossiers.messages.form.write_message_placeholder')
= f.text_area :body, rows: 5, placeholder: placeholder, title: placeholder, required: true, class: 'message-textarea persisted-input'
.flex.justify-between.wrap

View file

@ -55,11 +55,11 @@ describe ProcedureArchiveService do
files = ZipTricks::FileReader.read_zip_structure(io: f)
structure = [
"procedure-#{procedure.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/attestation-dossier--05-03-2021-00-00-#{dossier.attestation.pdf.id % 10000}.pdf",
"procedure-#{procedure.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf"
"procedure-#{procedure.id}-#{archive.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/attestation-dossier--05-03-2021-00-00-#{dossier.attestation.pdf.id % 10000}.pdf",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf"
]
expect(files.map(&:filename)).to match_array(structure)
end
@ -75,11 +75,11 @@ describe ProcedureArchiveService do
archive.file.open do |f|
files = ZipTricks::FileReader.read_zip_structure(io: f)
structure = [
"procedure-#{procedure.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}/LISEZMOI.txt"
"procedure-#{procedure.id}-#{archive.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}-#{archive.id}/LISEZMOI.txt"
]
expect(files.map(&:filename)).to match_array(structure)
end
@ -122,16 +122,16 @@ describe ProcedureArchiveService do
archive.file.open do |f|
zip_entries = ZipTricks::FileReader.read_zip_structure(io: f)
structure = [
"procedure-#{procedure.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/export-dossier-05-03-2020-00-00-1.pdf",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}/LISEZMOI.txt"
"procedure-#{procedure.id}-#{archive.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/export-dossier-05-03-2020-00-00-1.pdf",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2021-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}-#{archive.id}/LISEZMOI.txt"
]
expect(zip_entries.map(&:filename)).to match_array(structure)
zip_entries.map do |entry|
next unless entry.filename == "procedure-#{procedure.id}/LISEZMOI.txt"
next unless entry.filename == "procedure-#{procedure.id}-#{archive.id}/LISEZMOI.txt"
extracted_content = ""
extractor = entry.extractor_from(f)
extracted_content << extractor.extract(1024 * 1024) until extractor.eof?
@ -158,15 +158,15 @@ describe ProcedureArchiveService do
archive.file.open do |f|
files = ZipTricks::FileReader.read_zip_structure(io: f)
structure = [
"procedure-#{procedure.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}/dossier-#{dossier.id}/pieces_justificatives/attestation-dossier--05-03-2020-00-00-#{dossier.attestation.pdf.id % 10000}.pdf",
"procedure-#{procedure.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2020-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}/dossier-#{dossier_2020.id}/",
"procedure-#{procedure.id}/dossier-#{dossier_2020.id}/export-#{dossier_2020.id}-05-03-2020-00-00-#{dossier_2020.id}.pdf",
"procedure-#{procedure.id}/dossier-#{dossier_2020.id}/pieces_justificatives/",
"procedure-#{procedure.id}/dossier-#{dossier_2020.id}/pieces_justificatives/attestation-dossier--05-03-2020-00-00-#{dossier_2020.attestation.pdf.id % 10000}.pdf"
"procedure-#{procedure.id}-#{archive.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/pieces_justificatives/attestation-dossier--05-03-2020-00-00-#{dossier.attestation.pdf.id % 10000}.pdf",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier.id}/export-#{dossier.id}-05-03-2020-00-00-#{dossier.id}.pdf",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier_2020.id}/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier_2020.id}/export-#{dossier_2020.id}-05-03-2020-00-00-#{dossier_2020.id}.pdf",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier_2020.id}/pieces_justificatives/",
"procedure-#{procedure.id}-#{archive.id}/dossier-#{dossier_2020.id}/pieces_justificatives/attestation-dossier--05-03-2020-00-00-#{dossier_2020.attestation.pdf.id % 10000}.pdf"
]
expect(files.map(&:filename)).to match_array(structure)
end
@ -176,29 +176,30 @@ describe ProcedureArchiveService do
end
describe '#download_and_zip' do
let(:archive) { build(:archive, id: '3') }
it 'create a tmpdir while block is running' do
previous_dir_list = Dir.entries(ProcedureArchiveService::ARCHIVE_CREATION_DIR)
service.send(:download_and_zip, []) do |_zip_file|
service.send(:download_and_zip, archive, []) do |_zip_file|
new_dir_list = Dir.entries(ProcedureArchiveService::ARCHIVE_CREATION_DIR)
expect(previous_dir_list).not_to eq(new_dir_list)
end
end
it 'cleans up its tmpdir after block execution' do
expect { service.send(:download_and_zip, []) { |zip_file| } }
expect { service.send(:download_and_zip, archive, []) { |zip_file| } }
.not_to change { Dir.entries(ProcedureArchiveService::ARCHIVE_CREATION_DIR) }
end
it 'creates a zip with zip utility' do
expected_zip_path = File.join(ProcedureArchiveService::ARCHIVE_CREATION_DIR, "#{service.send(:zip_root_folder)}.zip")
expected_zip_path = File.join(ProcedureArchiveService::ARCHIVE_CREATION_DIR, "#{service.send(:zip_root_folder, archive)}.zip")
expect(service).to receive(:system).with('zip', '-0', '-r', expected_zip_path, an_instance_of(String))
service.send(:download_and_zip, []) { |zip_path| }
service.send(:download_and_zip, archive, []) { |zip_path| }
end
it 'cleans up its generated zip' do
expected_zip_path = File.join(ProcedureArchiveService::ARCHIVE_CREATION_DIR, "#{service.send(:zip_root_folder)}.zip")
service.send(:download_and_zip, []) do |_zip_path|
expected_zip_path = File.join(ProcedureArchiveService::ARCHIVE_CREATION_DIR, "#{service.send(:zip_root_folder, archive)}.zip")
service.send(:download_and_zip, archive, []) do |_zip_path|
expect(File.exist?(expected_zip_path)).to be_truthy
end
expect(File.exist?(expected_zip_path)).to be_falsey

View file

@ -1,20 +0,0 @@
describe 'instructeurs/procedures/_download_dossiers.html.haml', type: :view do
let(:current_instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure) }
subject { render 'instructeurs/procedures/download_dossiers.html.haml', procedure: procedure, exports: {} }
context "when procedure has at least 1 dossier" do
it { is_expected.to include("Télécharger tous les dossiers") }
context "With zip archive enabled" do
before { Flipper.enable(:archive_zip_globale, procedure) }
it { is_expected.to include("Télécharger une archive au format .zip") }
end
context "With zip archive disabled" do
before { Flipper.disable(:archive_zip_globale, procedure) }
it { is_expected.not_to include("Télécharger une archive au format .zip") }
end
end
end

View file

@ -8940,9 +8940,9 @@ minimist-options@4.1.0:
kind-of "^6.0.3"
minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass-collect@^1.0.2:
version "1.0.2"