commit
d9a6180c02
28 changed files with 299 additions and 62 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -30,6 +30,7 @@ yarn-debug.log*
|
||||||
/public/packs
|
/public/packs
|
||||||
/public/packs-test
|
/public/packs-test
|
||||||
/node_modules
|
/node_modules
|
||||||
|
vendor/*
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -39,6 +39,7 @@ gem 'lograge'
|
||||||
gem 'logstash-event'
|
gem 'logstash-event'
|
||||||
gem 'mailjet'
|
gem 'mailjet'
|
||||||
gem 'omniauth-github'
|
gem 'omniauth-github'
|
||||||
|
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||||
gem 'openid_connect'
|
gem 'openid_connect'
|
||||||
gem 'openstack'
|
gem 'openstack'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
|
@ -66,6 +67,7 @@ gem 'turbolinks' # Turbolinks makes following links in your web application fast
|
||||||
gem 'typhoeus'
|
gem 'typhoeus'
|
||||||
gem 'warden'
|
gem 'warden'
|
||||||
gem 'webpacker'
|
gem 'webpacker'
|
||||||
|
gem 'zipline'
|
||||||
gem 'zxcvbn-ruby', require: 'zxcvbn'
|
gem 'zxcvbn-ruby', require: 'zxcvbn'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -170,6 +170,7 @@ GEM
|
||||||
crass (1.0.4)
|
crass (1.0.4)
|
||||||
css_parser (1.6.0)
|
css_parser (1.6.0)
|
||||||
addressable
|
addressable
|
||||||
|
curb (0.9.10)
|
||||||
daemons (1.3.1)
|
daemons (1.3.1)
|
||||||
database_cleaner (1.7.0)
|
database_cleaner (1.7.0)
|
||||||
datetime_picker_rails (0.0.7)
|
datetime_picker_rails (0.0.7)
|
||||||
|
@ -386,6 +387,9 @@ GEM
|
||||||
omniauth-oauth2 (1.6.0)
|
omniauth-oauth2 (1.6.0)
|
||||||
oauth2 (~> 1.1)
|
oauth2 (~> 1.1)
|
||||||
omniauth (~> 1.9)
|
omniauth (~> 1.9)
|
||||||
|
omniauth-rails_csrf_protection (0.1.2)
|
||||||
|
actionpack (>= 4.2)
|
||||||
|
omniauth (>= 1.3.1)
|
||||||
open4 (1.3.4)
|
open4 (1.3.4)
|
||||||
openid_connect (1.1.6)
|
openid_connect (1.1.6)
|
||||||
activemodel
|
activemodel
|
||||||
|
@ -671,6 +675,11 @@ GEM
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
xray-rails (0.3.1)
|
xray-rails (0.3.1)
|
||||||
rails (>= 3.1.0)
|
rails (>= 3.1.0)
|
||||||
|
zip_tricks (4.7.4)
|
||||||
|
zipline (1.1.0)
|
||||||
|
curb (>= 0.8.0, < 0.10)
|
||||||
|
rails (>= 3.2.1, < 6.1)
|
||||||
|
zip_tricks (>= 4.2.1, <= 5.0.0)
|
||||||
zxcvbn-ruby (0.1.2)
|
zxcvbn-ruby (0.1.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
@ -731,6 +740,7 @@ DEPENDENCIES
|
||||||
mailjet
|
mailjet
|
||||||
mina!
|
mina!
|
||||||
omniauth-github
|
omniauth-github
|
||||||
|
omniauth-rails_csrf_protection (~> 0.1)
|
||||||
openid_connect
|
openid_connect
|
||||||
openstack
|
openstack
|
||||||
pg
|
pg
|
||||||
|
@ -775,6 +785,7 @@ DEPENDENCIES
|
||||||
webmock
|
webmock
|
||||||
webpacker
|
webpacker
|
||||||
xray-rails
|
xray-rails
|
||||||
|
zipline
|
||||||
zxcvbn-ruby
|
zxcvbn-ruby
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
|
|
@ -5,6 +5,9 @@ module Gestionnaires
|
||||||
include CreateAvisConcern
|
include CreateAvisConcern
|
||||||
include DossierHelper
|
include DossierHelper
|
||||||
|
|
||||||
|
include ActionController::Streaming
|
||||||
|
include Zipline
|
||||||
|
|
||||||
after_action :mark_demande_as_read, only: :show
|
after_action :mark_demande_as_read, only: :show
|
||||||
after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire]
|
after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire]
|
||||||
after_action :mark_avis_as_read, only: [:avis, :create_avis]
|
after_action :mark_avis_as_read, only: [:avis, :create_avis]
|
||||||
|
@ -172,6 +175,14 @@ module Gestionnaires
|
||||||
render layout: "print"
|
render layout: "print"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def telecharger_pjs
|
||||||
|
return head(:forbidden) if !Flipflop.download_as_zip_enabled? || !dossier.attachments_downloadable?
|
||||||
|
|
||||||
|
files = ActiveStorage::DownloadableFile.create_list_from_dossier(dossier)
|
||||||
|
|
||||||
|
zipline(files, "dossier-#{dossier.id}.zip")
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dossier
|
def dossier
|
||||||
|
|
|
@ -22,10 +22,10 @@ module Manager
|
||||||
|
|
||||||
def hide
|
def hide
|
||||||
dossier = Dossier.find(params[:id])
|
dossier = Dossier.find(params[:id])
|
||||||
dossier.hide!(current_administration)
|
dossier.delete_and_keep_track(current_administration)
|
||||||
|
|
||||||
logger.info("Le dossier #{dossier.id} est supprimé par #{current_administration.email}")
|
logger.info("Le dossier #{dossier.id} est supprimé par #{current_administration.email}")
|
||||||
flash[:notice] = "Le dossier #{dossier.id} est supprimé"
|
flash[:notice] = "Le dossier #{dossier.id} a été supprimé."
|
||||||
|
|
||||||
redirect_to manager_dossier_path(dossier)
|
redirect_to manager_dossier_path(dossier)
|
||||||
end
|
end
|
||||||
|
|
|
@ -193,7 +193,7 @@ module Users
|
||||||
dossier = current_user.dossiers.includes(:user, procedure: :administrateurs).find(params[:id])
|
dossier = current_user.dossiers.includes(:user, procedure: :administrateurs).find(params[:id])
|
||||||
|
|
||||||
if dossier.can_be_deleted_by_user?
|
if dossier.can_be_deleted_by_user?
|
||||||
dossier.delete_and_keep_track
|
dossier.delete_and_keep_track(current_user)
|
||||||
flash.notice = 'Votre dossier a bien été supprimé.'
|
flash.notice = 'Votre dossier a bien été supprimé.'
|
||||||
redirect_to dossiers_path
|
redirect_to dossiers_path
|
||||||
else
|
else
|
||||||
|
|
29
app/lib/active_storage/downloadable_file.rb
Normal file
29
app/lib/active_storage/downloadable_file.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class ActiveStorage::DownloadableFile
|
||||||
|
def initialize(attached)
|
||||||
|
if using_local_backend?
|
||||||
|
@url = 'file://' + ActiveStorage::Blob.service.path_for(attached.key)
|
||||||
|
else
|
||||||
|
@url = attached.service_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def url
|
||||||
|
@url
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_list_from_dossier(dossier)
|
||||||
|
pjs = PiecesJustificativesService.liste_pieces_justificatives(dossier)
|
||||||
|
pjs.map do |pj|
|
||||||
|
[
|
||||||
|
ActiveStorage::DownloadableFile.new(pj.piece_justificative_file),
|
||||||
|
pj.piece_justificative_file.filename.to_s
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def using_local_backend?
|
||||||
|
[:local, :local_test, :test].include?(Rails.application.config.active_storage.service)
|
||||||
|
end
|
||||||
|
end
|
|
@ -317,7 +317,7 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_and_keep_track
|
def delete_and_keep_track(author)
|
||||||
deleted_dossier = DeletedDossier.create_from_dossier(self)
|
deleted_dossier = DeletedDossier.create_from_dossier(self)
|
||||||
update(hidden_at: deleted_dossier.deleted_at)
|
update(hidden_at: deleted_dossier.deleted_at)
|
||||||
|
|
||||||
|
@ -328,6 +328,8 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
||||||
|
|
||||||
|
log_dossier_operation(author, :supprimer, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_passer_en_instruction(gestionnaire)
|
def after_passer_en_instruction(gestionnaire)
|
||||||
|
@ -409,14 +411,6 @@ class Dossier < ApplicationRecord
|
||||||
log_dossier_operation(gestionnaire, :classer_sans_suite, self)
|
log_dossier_operation(gestionnaire, :classer_sans_suite, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide!(administration)
|
|
||||||
update(hidden_at: Time.zone.now)
|
|
||||||
|
|
||||||
deleted_dossier = DeletedDossier.create_from_dossier(self)
|
|
||||||
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
|
||||||
log_dossier_operation(administration, :supprimer, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_mandatory_champs
|
def check_mandatory_champs
|
||||||
(champs + champs.select(&:repetition?).flat_map(&:champs))
|
(champs + champs.select(&:repetition?).flat_map(&:champs))
|
||||||
.select(&:mandatory_and_blank?)
|
.select(&:mandatory_and_blank?)
|
||||||
|
@ -466,6 +460,10 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attachments_downloadable?
|
||||||
|
!PiecesJustificativesService.liste_pieces_justificatives(self).empty? && PiecesJustificativesService.pieces_justificatives_total_size(self) < 50.megabytes
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def log_dossier_operation(author, operation, subject = nil)
|
def log_dossier_operation(author, operation, subject = nil)
|
||||||
|
|
|
@ -154,10 +154,20 @@ class CarrierwaveActiveStorageMigrationService
|
||||||
attachment
|
attachment
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_content_type(blob)
|
def fix_content_type(blob, retry_delay: 5)
|
||||||
|
retries ||= 0
|
||||||
# In OpenStack, ActiveStorage cannot inject the MIME type on the fly during direct
|
# In OpenStack, ActiveStorage cannot inject the MIME type on the fly during direct
|
||||||
# download. Instead, the MIME type needs to be stored statically on the file object
|
# download. Instead, the MIME type needs to be stored statically on the file object
|
||||||
# in OpenStack. This is what this call does.
|
# in OpenStack. This is what this call does.
|
||||||
blob.service.change_content_type(blob.key, blob.content_type)
|
blob.service.change_content_type(blob.key, blob.content_type)
|
||||||
|
rescue
|
||||||
|
# When we quickly create a new attachment, and then change its content type,
|
||||||
|
# the Object Storage may not be synchronized yet. It this cas, it will return a
|
||||||
|
# "409 Conflict" error.
|
||||||
|
#
|
||||||
|
# Wait for a while, then try again twice (before giving up).
|
||||||
|
sleep(retry_delay)
|
||||||
|
retry if (retries += 1) < 3
|
||||||
|
raise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -70,6 +70,17 @@ class PiecesJustificativesService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.liste_pieces_justificatives(dossier)
|
||||||
|
dossier.champs
|
||||||
|
.select { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:piece_justificative) }
|
||||||
|
.filter { |pj| pj.piece_justificative_file.attached? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pieces_justificatives_total_size(dossier)
|
||||||
|
liste_pieces_justificatives(dossier)
|
||||||
|
.sum { |pj| pj.piece_justificative_file.byte_size }
|
||||||
|
end
|
||||||
|
|
||||||
def self.serialize_types_de_champ_as_type_pj(procedure)
|
def self.serialize_types_de_champ_as_type_pj(procedure)
|
||||||
tdcs = procedure.types_de_champ.select { |type_champ| type_champ.old_pj.present? }
|
tdcs = procedure.types_de_champ.select { |type_champ| type_champ.old_pj.present? }
|
||||||
tdcs.map.with_index do |type_champ, order_place|
|
tdcs.map.with_index do |type_champ, order_place|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.super-admin.flex.justify-center
|
.super-admin.flex.justify-center
|
||||||
%div
|
%div
|
||||||
%h2 Espace Admin
|
%h2 Espace Admin
|
||||||
= link_to administration_github_omniauth_authorize_path, class: "button large" do
|
= link_to administration_github_omniauth_authorize_path, method: :post, class: "button large" do
|
||||||
%span.icon.lock
|
%span.icon.lock
|
||||||
Connexion avec GitHub
|
Connexion avec GitHub
|
||||||
|
|
|
@ -18,6 +18,14 @@
|
||||||
= link_to "Tout le dossier", print_gestionnaire_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
= link_to "Tout le dossier", print_gestionnaire_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
||||||
%li
|
%li
|
||||||
= link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link"
|
= link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link"
|
||||||
|
- if Flipflop.download_as_zip_enabled? && dossier.attachments_downloadable?
|
||||||
|
%span.dropdown.print-menu-opener
|
||||||
|
%button.button.dropdown-button.icon-only
|
||||||
|
%span.icon.attachment
|
||||||
|
%ul.print-menu.dropdown-content
|
||||||
|
%li
|
||||||
|
= link_to "Télécharger toutes les pièces jointes", telecharger_pjs_gestionnaire_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
||||||
|
|
||||||
|
|
||||||
= render partial: "gestionnaires/procedures/dossier_actions", locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: current_gestionnaire&.follow?(dossier) }
|
= render partial: "gestionnaires/procedures/dossier_actions", locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: current_gestionnaire&.follow?(dossier) }
|
||||||
%span.state-button
|
%span.state-button
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
%li= invite.email
|
%li= invite.email
|
||||||
%p Ces personnes peuvent modifier ce dossier.
|
%p Ces personnes peuvent modifier ce dossier.
|
||||||
- if dossier.brouillon?
|
- if dossier.brouillon?
|
||||||
%p Une fois le dossier complet, vous devez le soumettre vous-même.
|
%p Une fois le dossier complet, vous devez le déposer vous-même.
|
||||||
|
|
||||||
- else
|
- else
|
||||||
%p Vous pouvez inviter quelqu’un à remplir ce dossier avec vous.
|
%p Vous pouvez inviter quelqu’un à remplir ce dossier avec vous.
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
data: { 'disable-with': "Envoi en cours…" }
|
data: { 'disable-with': "Envoi en cours…" }
|
||||||
|
|
||||||
- if dossier.can_transition_to_en_construction?
|
- if dossier.can_transition_to_en_construction?
|
||||||
= f.button 'Soumettre le dossier',
|
= f.button 'Déposer le dossier',
|
||||||
class: 'button send primary',
|
class: 'button send primary',
|
||||||
disabled: !current_user.owns?(dossier),
|
disabled: !current_user.owns?(dossier),
|
||||||
data: { 'disable-with': "Envoi en cours…" }
|
data: { 'disable-with': "Envoi en cours…" }
|
||||||
|
@ -92,6 +92,6 @@
|
||||||
|
|
||||||
- if dossier.brouillon? && !current_user.owns?(dossier)
|
- if dossier.brouillon? && !current_user.owns?(dossier)
|
||||||
.send-notice.invite-cannot-submit
|
.send-notice.invite-cannot-submit
|
||||||
En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le soumettre lui-même.
|
En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le déposer lui-même.
|
||||||
|
|
||||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
|
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
|
||||||
|
|
|
@ -7,3 +7,5 @@
|
||||||
- if procedure.usual_traitement_time && show_time_means
|
- if procedure.usual_traitement_time && show_time_means
|
||||||
%p
|
%p
|
||||||
Habituellement, les dossiers de cette démarche sont traités dans un délai de #{distance_of_time_in_words(procedure.usual_traitement_time)}.
|
Habituellement, les dossiers de cette démarche sont traités dans un délai de #{distance_of_time_in_words(procedure.usual_traitement_time)}.
|
||||||
|
%p
|
||||||
|
Cette estimation est calculée automatiquement à partir des délais d’instruction constatés précédemment. Le délai réel peut être différent, en fonction du type de démarche (par exemple pour un appel à projet avec date de décision fixe).
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
.status-overview
|
.status-overview
|
||||||
- if !dossier.termine?
|
- if !dossier.termine?
|
||||||
%ul.status-timeline
|
%ul.status-timeline
|
||||||
|
@ -16,28 +14,37 @@
|
||||||
.status-explanation
|
.status-explanation
|
||||||
- if dossier.brouillon?
|
- if dossier.brouillon?
|
||||||
.brouillon
|
.brouillon
|
||||||
%p Vous pouvez remplir votre dossier tranquillement : il n’est pas encore visible par l’administration.
|
%p Votre dossier n’est pas encore visible par l’administration.
|
||||||
%p Quand vous aurez terminé, soumettez votre dossier pour qu’il soit examiné.
|
%p Vous pouvez déposer votre dossier afin de le transmettre à l’administration. Une fois soumis, vous pourrez toujours modifier votre dossier.
|
||||||
|
|
||||||
- elsif dossier.en_construction?
|
- elsif dossier.en_construction?
|
||||||
.en-construction
|
.en-construction
|
||||||
%p Un instructeur de l’administration est en train de vérifier que votre dossier est bien complet. Si des modifications sont nécessaires, vous recevrez un message avec les modifications à effectuer.
|
|
||||||
%p
|
%p
|
||||||
Sinon,
|
Votre dossier est en construction. Cela signifie que
|
||||||
= succeed '.' do
|
= succeed '.' do
|
||||||
%strong votre dossier passera directement en instruction
|
%strong vous pouvez encore le modifier
|
||||||
|
Vous ne pourrez plus modifier votre dossier lorsque l’administration le passera « en instruction ».
|
||||||
|
|
||||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||||
|
|
||||||
|
%p
|
||||||
|
%strong Vous avez une question ?
|
||||||
|
Utilisez la messagerie pour
|
||||||
|
= succeed '.' do
|
||||||
|
= link_to 'contacter l’administration directement', messagerie_dossier_url(dossier)
|
||||||
|
|
||||||
- elsif dossier.en_instruction?
|
- elsif dossier.en_instruction?
|
||||||
.en-instruction
|
.en-instruction
|
||||||
%p Votre dossier est complet. Il est en cours d’examen par les instructeurs de l’administration.
|
%p Votre dossier est en cours d’instruction par l’administration. Vous ne pouvez plus le modifier.
|
||||||
%p
|
|
||||||
Dès que l’administration aura statué sur votre dossier,
|
|
||||||
%strong
|
|
||||||
vous recevrez un email
|
|
||||||
avec le résultat.
|
|
||||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||||
|
|
||||||
|
%p
|
||||||
|
%strong Vous avez une question ?
|
||||||
|
Utilisez la messagerie pour
|
||||||
|
= succeed '.' do
|
||||||
|
= link_to 'contacter l’administration directement', messagerie_dossier_url(dossier)
|
||||||
|
|
||||||
- elsif dossier.accepte?
|
- elsif dossier.accepte?
|
||||||
.accepte
|
.accepte
|
||||||
%p.decision
|
%p.decision
|
||||||
|
|
|
@ -18,6 +18,8 @@ Flipflop.configure do
|
||||||
|
|
||||||
feature :procedure_export_v2_enabled
|
feature :procedure_export_v2_enabled
|
||||||
feature :operation_log_serialize_subject
|
feature :operation_log_serialize_subject
|
||||||
|
feature :download_as_zip_enabled,
|
||||||
|
default: false
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
feature :mini_profiler_enabled,
|
feature :mini_profiler_enabled,
|
||||||
|
|
4
config/initializers/omniauth.rb
Normal file
4
config/initializers/omniauth.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# OmniAuth GET requests may be vulnerable to CSRF.
|
||||||
|
# Ensure that OmniAuth only uses POST requests.
|
||||||
|
# See https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284
|
||||||
|
OmniAuth.config.allowed_request_methods = [:post]
|
|
@ -335,6 +335,7 @@ Rails.application.routes.draw do
|
||||||
post 'send-to-instructeurs' => 'dossiers#send_to_instructeurs'
|
post 'send-to-instructeurs' => 'dossiers#send_to_instructeurs'
|
||||||
post 'avis' => 'dossiers#create_avis'
|
post 'avis' => 'dossiers#create_avis'
|
||||||
get 'print' => 'dossiers#print'
|
get 'print' => 'dossiers#print'
|
||||||
|
get 'telecharger_pjs' => 'dossiers#telecharger_pjs'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,19 +2,26 @@ require Rails.root.join("lib", "tasks", "task_helper")
|
||||||
|
|
||||||
namespace :support do
|
namespace :support do
|
||||||
desc <<~EOD
|
desc <<~EOD
|
||||||
Delete the user account for a given USER_EMAIL.
|
Delete the user account for a given USER_EMAIL on the behalf of ADMIN_EMAIL.
|
||||||
Only works if the user has no dossier where the instruction has started.
|
Only works if the user has no dossier where the instruction has started.
|
||||||
EOD
|
EOD
|
||||||
task delete_user_account: :environment do
|
task delete_user_account: :environment do
|
||||||
user_email = ENV['USER_EMAIL']
|
user_email = ENV['USER_EMAIL']
|
||||||
if user_email.nil?
|
fail "Must specify a USER_EMAIL" if user_email.nil?
|
||||||
fail "Must specify a USER_EMAIL"
|
|
||||||
end
|
administration_email = ENV['ADMIN_EMAIL']
|
||||||
user = User.find_by(email: user_email)
|
fail "Must specify the ADMIN_EMAIL of the operator performing the deletion (yourself)" if administration_email.nil?
|
||||||
|
|
||||||
|
user = User.find_by!(email: user_email)
|
||||||
|
administration = Administration.find_by!(email: administration_email)
|
||||||
|
|
||||||
if user.dossiers.state_instruction_commencee.any?
|
if user.dossiers.state_instruction_commencee.any?
|
||||||
fail "Cannot delete this user because instruction has started for some dossiers"
|
fail "Cannot delete this user because instruction has started for some dossiers"
|
||||||
end
|
end
|
||||||
user.dossiers.each(&:delete_and_keep_track)
|
|
||||||
|
user.dossiers.each do |dossier|
|
||||||
|
dossier.delete_and_keep_track(administration)
|
||||||
|
end
|
||||||
user.destroy
|
user.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -524,4 +524,24 @@ describe Gestionnaires::DossiersController, type: :controller do
|
||||||
it { expect(champ_repetition.champs.first.value).to eq('text') }
|
it { expect(champ_repetition.champs.first.value).to eq('text') }
|
||||||
it { expect(response).to redirect_to(annotations_privees_gestionnaire_dossier_path(dossier.procedure, dossier)) }
|
it { expect(response).to redirect_to(annotations_privees_gestionnaire_dossier_path(dossier.procedure, dossier)) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#telecharger_pjs" do
|
||||||
|
subject do
|
||||||
|
get :telecharger_pjs, params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
dossier_id: dossier.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when zip download is disabled through flipflop' do
|
||||||
|
before do
|
||||||
|
Flipflop::FeatureSet.current.test!.switch!(:download_as_zip_enabled, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is forbidden' do
|
||||||
|
subject
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -137,14 +137,14 @@ feature 'The user' do
|
||||||
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
||||||
|
|
||||||
# Check an incomplete dossier cannot be submitted when mandatory fields are missing
|
# Check an incomplete dossier cannot be submitted when mandatory fields are missing
|
||||||
click_on 'Soumettre le dossier'
|
click_on 'Déposer le dossier'
|
||||||
expect(user_dossier.reload.brouillon?).to be(true)
|
expect(user_dossier.reload.brouillon?).to be(true)
|
||||||
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
|
||||||
|
|
||||||
# Check a dossier can be submitted when all mandatory fields are filled
|
# Check a dossier can be submitted when all mandatory fields are filled
|
||||||
fill_in('texte obligatoire', with: 'super texte')
|
fill_in('texte obligatoire', with: 'super texte')
|
||||||
|
|
||||||
click_on 'Soumettre le dossier'
|
click_on 'Déposer le dossier'
|
||||||
expect(user_dossier.reload.en_construction?).to be(true)
|
expect(user_dossier.reload.en_construction?).to be(true)
|
||||||
expect(champ_value_for('texte obligatoire')).to eq('super texte')
|
expect(champ_value_for('texte obligatoire')).to eq('super texte')
|
||||||
expect(page).to have_current_path(merci_dossier_path(user_dossier))
|
expect(page).to have_current_path(merci_dossier_path(user_dossier))
|
||||||
|
|
|
@ -63,7 +63,7 @@ feature 'Invitations' do
|
||||||
navigate_to_invited_dossier(invite)
|
navigate_to_invited_dossier(invite)
|
||||||
expect(page).to have_current_path(brouillon_dossier_path(dossier))
|
expect(page).to have_current_path(brouillon_dossier_path(dossier))
|
||||||
|
|
||||||
expect(page).to have_button('Soumettre le dossier', disabled: true)
|
expect(page).to have_button('Déposer le dossier', disabled: true)
|
||||||
expect(page).to have_selector('.invite-cannot-submit')
|
expect(page).to have_selector('.invite-cannot-submit')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
26
spec/lib/active_storage/downloadable_file_spec.rb
Normal file
26
spec/lib/active_storage/downloadable_file_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
describe ActiveStorage::DownloadableFile do
|
||||||
|
let(:tpjs) { [tpj_not_mandatory] }
|
||||||
|
let!(:tpj_not_mandatory) do
|
||||||
|
TypeDePieceJustificative.create(libelle: 'not mandatory', mandatory: false)
|
||||||
|
end
|
||||||
|
let(:procedure) { Procedure.create(types_de_piece_justificative: tpjs) }
|
||||||
|
let(:dossier) { Dossier.create(procedure: procedure) }
|
||||||
|
let(:procedure) { Procedure.create(types_de_piece_justificative: tpjs) }
|
||||||
|
let(:dossier) { Dossier.create(procedure: procedure) }
|
||||||
|
let(:list) { ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) }
|
||||||
|
|
||||||
|
describe 'create_list_from_dossier' do
|
||||||
|
context 'when no piece_justificative is present' do
|
||||||
|
it { expect(list).to match([]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a piece_justificative' do
|
||||||
|
let (:pj) { create(:champ, :piece_justificative, :with_piece_justificative_file) }
|
||||||
|
before do
|
||||||
|
dossier.champs = [pj]
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(list.length).to be 1 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -99,6 +99,14 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#types_de_piece_justificative' do
|
||||||
|
subject { dossier.types_de_piece_justificative }
|
||||||
|
it 'returns list of required piece justificative' do
|
||||||
|
expect(subject.size).to eq(2)
|
||||||
|
expect(subject).to include(TypeDePieceJustificative.find(TypeDePieceJustificative.first.id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#retrieve_last_piece_justificative_by_type', vcr: { cassette_name: 'models_dossier_retrieve_last_piece_justificative_by_type' } do
|
describe '#retrieve_last_piece_justificative_by_type', vcr: { cassette_name: 'models_dossier_retrieve_last_piece_justificative_by_type' } do
|
||||||
let(:types_de_pj_dossier) { dossier.procedure.types_de_piece_justificative }
|
let(:types_de_pj_dossier) { dossier.procedure.types_de_piece_justificative }
|
||||||
|
|
||||||
|
@ -113,6 +121,20 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#retrieve_all_piece_justificative_by_type' do
|
||||||
|
let(:types_de_pj_dossier) { dossier.procedure.types_de_piece_justificative }
|
||||||
|
|
||||||
|
subject { dossier.retrieve_all_piece_justificative_by_type types_de_pj_dossier.first }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create :piece_justificative, :rib, dossier: dossier, type_de_piece_justificative: types_de_pj_dossier.first
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a list of the piece justificative' do
|
||||||
|
expect(subject).not_to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#build_default_champs' do
|
describe '#build_default_champs' do
|
||||||
context 'when dossier is linked to a procedure with type_de_champ_public and private' do
|
context 'when dossier is linked to a procedure with type_de_champ_public and private' do
|
||||||
let(:dossier) { create(:dossier, user: user) }
|
let(:dossier) { create(:dossier, user: user) }
|
||||||
|
@ -610,13 +632,14 @@ describe Dossier do
|
||||||
describe "#delete_and_keep_track" do
|
describe "#delete_and_keep_track" do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:dossier) { create(:dossier) }
|
||||||
let(:deleted_dossier) { DeletedDossier.find_by!(dossier_id: dossier.id) }
|
let(:deleted_dossier) { DeletedDossier.find_by!(dossier_id: dossier.id) }
|
||||||
|
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(DossierMailer).to receive(:notify_deletion_to_user).and_return(double(deliver_later: nil))
|
allow(DossierMailer).to receive(:notify_deletion_to_user).and_return(double(deliver_later: nil))
|
||||||
allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil))
|
allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil))
|
||||||
end
|
end
|
||||||
|
|
||||||
subject! { dossier.delete_and_keep_track }
|
subject! { dossier.delete_and_keep_track(dossier.user) }
|
||||||
|
|
||||||
it 'hides the dossier' do
|
it 'hides the dossier' do
|
||||||
expect(dossier.hidden_at).to be_present
|
expect(dossier.hidden_at).to be_present
|
||||||
|
@ -633,6 +656,11 @@ describe Dossier do
|
||||||
expect(DossierMailer).to have_received(:notify_deletion_to_user).with(deleted_dossier, dossier.user.email)
|
expect(DossierMailer).to have_received(:notify_deletion_to_user).with(deleted_dossier, dossier.user.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'records the operation in the log' do
|
||||||
|
expect(last_operation.operation).to eq("supprimer")
|
||||||
|
expect(last_operation.automatic_operation?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
context 'where gestionnaires are following the dossier' do
|
context 'where gestionnaires are following the dossier' do
|
||||||
let(:dossier) { create(:dossier, :en_construction, :followed) }
|
let(:dossier) { create(:dossier, :en_construction, :followed) }
|
||||||
let!(:non_following_gestionnaire) do
|
let!(:non_following_gestionnaire) do
|
||||||
|
@ -968,23 +996,6 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#hide!' do
|
|
||||||
let(:dossier) { create(:dossier) }
|
|
||||||
let(:administration) { create(:administration) }
|
|
||||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
|
||||||
|
|
||||||
before do
|
|
||||||
Timecop.freeze
|
|
||||||
dossier.hide!(administration)
|
|
||||||
end
|
|
||||||
|
|
||||||
after { Timecop.return }
|
|
||||||
|
|
||||||
it { expect(dossier.hidden_at).to eq(Time.zone.now) }
|
|
||||||
it { expect(last_operation.operation).to eq('supprimer') }
|
|
||||||
it { expect(last_operation.automatic_operation?).to be_falsey }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#repasser_en_instruction!' do
|
describe '#repasser_en_instruction!' do
|
||||||
let(:dossier) { create(:dossier, :refuse, :with_attestation) }
|
let(:dossier) { create(:dossier, :refuse, :with_attestation) }
|
||||||
let!(:gestionnaire) { create(:gestionnaire) }
|
let!(:gestionnaire) { create(:gestionnaire) }
|
||||||
|
@ -1008,4 +1019,32 @@ describe Dossier do
|
||||||
|
|
||||||
after { Timecop.return }
|
after { Timecop.return }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#attachments_downloadable?' do
|
||||||
|
let(:dossier) { create(:dossier, user: user) }
|
||||||
|
# subject { dossier.attachments_downloadable? }
|
||||||
|
|
||||||
|
context "no attachments" do
|
||||||
|
it {
|
||||||
|
expect(PiecesJustificativesService).to receive(:liste_pieces_justificatives).and_return([])
|
||||||
|
expect(dossier.attachments_downloadable?).to be false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a small attachment" do
|
||||||
|
it {
|
||||||
|
expect(PiecesJustificativesService).to receive(:liste_pieces_justificatives).and_return([Champ.new])
|
||||||
|
expect(PiecesJustificativesService).to receive(:pieces_justificatives_total_size).and_return(4.megabytes)
|
||||||
|
expect(dossier.attachments_downloadable?).to be true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a too large attachment" do
|
||||||
|
it {
|
||||||
|
expect(PiecesJustificativesService).to receive(:liste_pieces_justificatives).and_return([Champ.new])
|
||||||
|
expect(PiecesJustificativesService).to receive(:pieces_justificatives_total_size).and_return(100.megabytes)
|
||||||
|
expect(dossier.attachments_downloadable?).to be false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,4 +69,31 @@ describe CarrierwaveActiveStorageMigrationService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.fix_content_type' do
|
||||||
|
let(:pj) { create(:piece_justificative, :rib, updated_at: Time.zone.local(2019, 01, 01, 12, 00)) }
|
||||||
|
let(:blob) { service.make_empty_blob(pj.content, pj.updated_at.iso8601, filename: pj.original_filename) }
|
||||||
|
|
||||||
|
context 'when the request is ok' do
|
||||||
|
it 'succeeds' do
|
||||||
|
expect(blob.service).to receive(:change_content_type).and_return(true)
|
||||||
|
expect { service.fix_content_type(blob) }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the request fails initially' do
|
||||||
|
it 'retries the request' do
|
||||||
|
expect(blob.service).to receive(:change_content_type).and_raise(StandardError).ordered
|
||||||
|
expect(blob.service).to receive(:change_content_type).and_return(true).ordered
|
||||||
|
expect { service.fix_content_type(blob, retry_delay: 0.01) }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the request fails too many times' do
|
||||||
|
it 'gives up' do
|
||||||
|
expect(blob.service).to receive(:change_content_type).and_raise(StandardError).thrice
|
||||||
|
expect { service.fix_content_type(blob, retry_delay: 0.01) }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,9 @@ describe PiecesJustificativesService do
|
||||||
let(:errors) { PiecesJustificativesService.upload!(dossier, user, hash) }
|
let(:errors) { PiecesJustificativesService.upload!(dossier, user, hash) }
|
||||||
let(:tpjs) { [tpj_not_mandatory] }
|
let(:tpjs) { [tpj_not_mandatory] }
|
||||||
|
|
||||||
|
let(:attachment_list) { PiecesJustificativesService.liste_pieces_justificatives(dossier) }
|
||||||
|
let(:poids_total) { PiecesJustificativesService.pieces_justificatives_total_size(dossier) }
|
||||||
|
|
||||||
describe 'self.upload!' do
|
describe 'self.upload!' do
|
||||||
context 'when no params are given' do
|
context 'when no params are given' do
|
||||||
it { expect(errors).to eq([]) }
|
it { expect(errors).to eq([]) }
|
||||||
|
@ -81,12 +84,30 @@ describe PiecesJustificativesService do
|
||||||
before :each do
|
before :each do
|
||||||
# we are messing around piece_justificative
|
# we are messing around piece_justificative
|
||||||
# because directly doubling carrierwave params seems complicated
|
# because directly doubling carrierwave params seems complicated
|
||||||
|
|
||||||
piece_justificative_double = double(type_de_piece_justificative: tpj_mandatory)
|
piece_justificative_double = double(type_de_piece_justificative: tpj_mandatory)
|
||||||
expect(dossier).to receive(:pieces_justificatives).and_return([piece_justificative_double])
|
expect(dossier).to receive(:pieces_justificatives).and_return([piece_justificative_double])
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(errors).to match([]) }
|
it {
|
||||||
|
expect(errors).to match([])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#attachment_list' do
|
||||||
|
context 'when no piece_justificative is present' do
|
||||||
|
it { expect(attachment_list).to match([]) }
|
||||||
|
it { expect(poids_total).to be 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a piece_justificative' do
|
||||||
|
let (:pj) { create(:champ, :piece_justificative, :with_piece_justificative_file) }
|
||||||
|
before do
|
||||||
|
dossier.champs = [pj]
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(attachment_list).not_to be_empty }
|
||||||
|
it { expect(poids_total).to be pj.piece_justificative_file.byte_size }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5114,9 +5114,9 @@ lodash.uniq@^4.5.0:
|
||||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||||
|
|
||||||
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@~4.17.10:
|
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@~4.17.10:
|
||||||
version "4.17.11"
|
version "4.17.14"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
|
||||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
|
||||||
|
|
||||||
loglevel@^1.6.3:
|
loglevel@^1.6.3:
|
||||||
version "1.6.3"
|
version "1.6.3"
|
||||||
|
|
Loading…
Reference in a new issue