Merge pull request #6812 from betagouv/main

2022-01-13-01
This commit is contained in:
Kara Diaby 2022-01-13 12:30:07 +01:00 committed by GitHub
commit 252a2c3a4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 654 additions and 354 deletions

View file

@ -535,6 +535,13 @@
padding: $default-spacer; padding: $default-spacer;
} }
[data-reach-combobox-no-results] {
font-size: 16px;
color: $dark-grey;
background: $light-grey;
padding: $default-spacer;
}
[data-reach-combobox-token] button { [data-reach-combobox-token] button {
cursor: pointer; cursor: pointer;
background-color: transparent; background-color: transparent;

View file

@ -32,7 +32,8 @@ module Administrateurs
# Prevent self-removal (Also enforced in the UI) # Prevent self-removal (Also enforced in the UI)
if administrateur == current_administrateur if administrateur == current_administrateur
flash.error = "Vous ne pouvez pas vous retirer vous-même dune démarche." flash.alert = "Vous ne pouvez pas vous retirer vous-même dune démarche."
return
end end
# Actually remove the admin # Actually remove the admin

View file

@ -14,9 +14,9 @@ module Instructeurs
dossiers = current_instructeur.dossiers.joins(:groupe_instructeur) dossiers = current_instructeur.dossiers.joins(:groupe_instructeur)
@dossiers_count_per_procedure = dossiers.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_count_per_procedure = dossiers.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count @dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.termine.not_hidden_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_expirant_count_per_procedure = dossiers.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count @dossiers_expirant_count_per_procedure = dossiers.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count
groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id) groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
@ -61,6 +61,7 @@ module Instructeurs
@a_suivre_dossiers = dossiers_visibles @a_suivre_dossiers = dossiers_visibles
.without_followers .without_followers
.en_cours .en_cours
.visible_by_administration
@followed_dossiers = current_instructeur @followed_dossiers = current_instructeur
.followed_dossiers .followed_dossiers
@ -69,7 +70,7 @@ module Instructeurs
@followed_dossiers_id = @followed_dossiers.pluck(:id) @followed_dossiers_id = @followed_dossiers.pluck(:id)
@termines_dossiers = dossiers_visibles.termine.not_hidden_by_administration @termines_dossiers = dossiers_visibles.termine.visible_by_administration
@all_state_dossiers = dossiers_visibles.all_state @all_state_dossiers = dossiers_visibles.all_state
@archived_dossiers = dossiers_visibles.archived @archived_dossiers = dossiers_visibles.archived
@expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration @expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration

View file

@ -5,7 +5,7 @@ module Users
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret] layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all] ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire] ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire, :restore]
before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
@ -16,17 +16,18 @@ module Users
before_action :store_user_location!, only: :new before_action :store_user_location!, only: :new
def index def index
@user_dossiers = current_user.dossiers.includes(:procedure).state_not_termine.order_by_updated_at.page(page) @user_dossiers = current_user.dossiers.includes(:procedure).state_not_termine.visible_by_user.order_by_updated_at.page(page)
@dossiers_traites = current_user.dossiers.includes(:procedure).state_termine.not_hidden_by_user.order_by_updated_at.page(page) @dossiers_traites = current_user.dossiers.includes(:procedure).state_termine.visible_by_user.order_by_updated_at.page(page)
@dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page) @dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page)
@dossiers_supprimes = current_user.deleted_dossiers.order_by_updated_at.page(page) @dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.order_by_updated_at.page(page)
@dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page)
@dossier_transfers = DossierTransfer @dossier_transfers = DossierTransfer
.includes(dossiers: :user) .includes(dossiers: :user)
.with_dossiers .with_dossiers
.where(email: current_user.email) .where(email: current_user.email)
.page(page) .page(page)
@dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.page(page) @dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.page(page)
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes, @dossier_transfers, @dossiers_close_to_expiration, params[:statut]) @statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut])
end end
def show def show
@ -287,17 +288,24 @@ module Users
@transfer = DossierTransfer.new(dossiers: current_user.dossiers) @transfer = DossierTransfer.new(dossiers: current_user.dossiers)
end end
def restore
dossier.restore(current_user)
flash.notice = t('users.dossiers.restore')
redirect_to dossiers_path
end
private private
# if the status tab is filled, then this tab # if the status tab is filled, then this tab
# else first filled tab # else first filled tab
# else en-cours # else en-cours
def statut(mes_dossiers, dossiers_traites, dossiers_invites, dossiers_supprimes, dossier_transfers, dossiers_close_to_expiration, params_statut) def statut(mes_dossiers, dossiers_traites, dossiers_invites, dossiers_supprimes_recemment, dossiers_supprimes_definitivement, dossier_transfers, dossiers_close_to_expiration, params_statut)
tabs = { tabs = {
'en-cours' => mes_dossiers.present?, 'en-cours' => mes_dossiers.present?,
'traites' => dossiers_traites.present?, 'traites' => dossiers_traites.present?,
'dossiers-invites' => dossiers_invites.present?, 'dossiers-invites' => dossiers_invites.present?,
'dossiers-supprimes' => dossiers_supprimes.present?, 'dossiers-supprimes-recemment' => dossiers_supprimes_recemment.present?,
'dossiers-supprimes-definitivement' => dossiers_supprimes_definitivement.present?,
'dossiers-transferes' => dossier_transfers.present?, 'dossiers-transferes' => dossier_transfers.present?,
'dossiers-expirant' => dossiers_close_to_expiration.present? 'dossiers-expirant' => dossiers_close_to_expiration.present?
} }

View file

@ -8,12 +8,15 @@ module ApplicationHelper
end end
def flash_class(level, sticky: false, fixed: false) def flash_class(level, sticky: false, fixed: false)
class_names = case level class_names = []
case level
when 'notice' when 'notice'
['alert-success'] class_names << 'alert-success'
when 'alert' when 'alert', 'error'
['alert-danger'] class_names << 'alert-danger'
end end
if sticky if sticky
class_names << 'sticky' class_names << 'sticky'
end end

View file

@ -116,6 +116,7 @@ function ComboMultiple({
} }
setTerm(''); setTerm('');
awaitFormSubmit.done(); awaitFormSubmit.done();
hidePopover();
}; };
const onRemove = (label) => { const onRemove = (label) => {
@ -148,6 +149,18 @@ function ComboMultiple({
} }
}; };
const hidePopover = () => {
document
.querySelector(`[data-reach-combobox-popover-id="${inputId}"]`)
?.setAttribute('hidden', 'true');
};
const showPopover = () => {
document
.querySelector(`[data-reach-combobox-popover-id="${inputId}"]`)
?.removeAttribute('hidden');
};
const onBlur = () => { const onBlur = () => {
if ( if (
term && term &&
@ -156,6 +169,10 @@ function ComboMultiple({
awaitFormSubmit(() => { awaitFormSubmit(() => {
onSelect(term); onSelect(term);
}); });
} else {
setTimeout(() => {
hidePopover();
}, 200);
} }
}; };
@ -185,6 +202,7 @@ function ComboMultiple({
onChange={handleChange} onChange={handleChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onBlur={onBlur} onBlur={onBlur}
onClick={showPopover}
autocomplete={false} autocomplete={false}
id={inputId} id={inputId}
aria-label={label} aria-label={label}
@ -195,14 +213,25 @@ function ComboMultiple({
/> />
</ComboboxTokenLabel> </ComboboxTokenLabel>
{results && (results.length > 0 || !acceptNewValues) && ( {results && (results.length > 0 || !acceptNewValues) && (
<ComboboxPopover className="shadow-popup"> <ComboboxPopover
{results.length === 0 && ( className="shadow-popup"
<p> data-reach-combobox-popover-id={inputId}
Aucun résultat{' '} >
<button onClick={() => setTerm('')}>Effacer</button>
</p>
)}
<ComboboxList> <ComboboxList>
{results.length === 0 && (
<li data-reach-combobox-no-results>
Aucun résultat{' '}
<button
onClick={() => {
setTerm('');
inputRef.current?.focus();
}}
className="button"
>
Effacer
</button>
</li>
)}
{results.map(([label, value], index) => { {results.map(([label, value], index) => {
if (label.startsWith('--')) { if (label.startsWith('--')) {
return <ComboboxSeparator key={index} value={label} />; return <ComboboxSeparator key={index} value={label} />;

View file

@ -1,6 +1,5 @@
module DownloadManager module DownloadManager
class ParallelDownloadQueue class ParallelDownloadQueue
include Utils::Retryable
DOWNLOAD_MAX_PARALLEL = ENV.fetch('DOWNLOAD_MAX_PARALLEL') { 10 } DOWNLOAD_MAX_PARALLEL = ENV.fetch('DOWNLOAD_MAX_PARALLEL') { 10 }
attr_accessor :attachments, attr_accessor :attachments,
@ -17,11 +16,9 @@ module DownloadManager
attachments.map do |attachment, path| attachments.map do |attachment, path|
begin begin
with_retry(max_attempt: 1) do download_one(attachment: attachment,
download_one(attachment: attachment, path_in_download_dir: path,
path_in_download_dir: path, http_client: hydra)
http_client: hydra)
end
rescue => e rescue => e
on_error.call(attachment, path, e) on_error.call(attachment, path, e)
end end
@ -47,14 +44,12 @@ module DownloadManager
request.on_complete do |response| request.on_complete do |response|
fd.close fd.close
unless response.success? unless response.success?
raise 'ko' File.delete(attachment_path) if File.exist?(attachment_path) # -> case of retries failed, must cleanup partialy downloaded file
on_error.call(attachment, path_in_download_dir, response.code)
end end
end end
http_client.queue(request) http_client.queue(request)
end end
rescue
File.delete(attachment_path) if File.exist?(attachment_path) # -> case of retries failed, must cleanup partialy downloaded file
raise
end end
# rubocop:enable Style/AutoResourceCleanup # rubocop:enable Style/AutoResourceCleanup
end end

View file

@ -9,18 +9,20 @@ module DownloadManager
@procedure = procedure @procedure = procedure
@errors = {} @errors = {}
@queue = ParallelDownloadQueue.new(attachments, destination) @queue = ParallelDownloadQueue.new(attachments, destination)
@queue.on_error = proc do |_attachment, path, error| @queue.on_error = proc do |attachment, path, error|
errors[path] = true errors[path] = [attachment, path]
Rails.logger.error("Fail to download filename #{path} in procedure##{@procedure.id}, reason: #{error}") Rails.logger.error("Fail to download filename #{path} in procedure##{@procedure.id}, reason: #{error}")
end end
end
def download_all
@queue.download_all
write_report if !errors.empty?
end end
private def download_all(attempt_left: 1)
@queue.download_all
if !errors.empty? && attempt_left.positive?
retryable_queue = self.class.new(@procedure, errors.values, destination)
retryable_queue.download_all(attempt_left: 0)
retryable_queue.write_report if !retryable_queue.errors.empty?
end
end
def write_report def write_report
manifest_path = File.join(destination, 'LISEZMOI.txt') manifest_path = File.join(destination, 'LISEZMOI.txt')

View file

@ -1,20 +0,0 @@
module Utils
module Retryable
# usage:
# max_attempt : retry count
# errors : only retry those errors
# with_retry(max_attempt: 10, errors: [StandardError]) do
# do_something_which_can_fail
# end
def with_retry(max_attempt: 1, errors: [StandardError], &block)
limiter = 0
begin
yield
rescue *errors
limiter += 1
retry if limiter <= max_attempt
raise
end
end
end
end

View file

@ -206,9 +206,10 @@ class Dossier < ApplicationRecord
scope :archived, -> { where(archived: true) } scope :archived, -> { where(archived: true) }
scope :not_archived, -> { where(archived: false) } scope :not_archived, -> { where(archived: false) }
scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) }
scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) } scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) }
scope :not_hidden_by_user, -> { where(hidden_by_user_at: nil) } scope :visible_by_user, -> { where(hidden_by_user_at: nil) }
scope :not_hidden_by_administration, -> { where(hidden_by_administration_at: nil) } scope :visible_by_administration, -> { where("hidden_by_administration_at IS NULL AND NOT (hidden_by_user_at IS NOT NULL AND state = 'en_construction')") }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } 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) } scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) }
@ -237,7 +238,7 @@ class Dossier < ApplicationRecord
end end
scope :downloadable_sorted, -> { scope :downloadable_sorted, -> {
state_not_brouillon state_not_brouillon
.not_hidden_by_administration .visible_by_administration
.includes( .includes(
:user, :user,
:individual, :individual,
@ -346,23 +347,26 @@ class Dossier < ApplicationRecord
scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) } scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) }
scope :without_termine_expiration_notice_sent, -> { where(termine_close_to_expiration_notice_sent_at: nil) } scope :without_termine_expiration_notice_sent, -> { where(termine_close_to_expiration_notice_sent_at: nil) }
scope :discarded_expired, -> { discarded.where('dossiers.hidden_at < ?', 1.week.ago) }
scope :discarded_by_user_expired, -> { discarded.where('dossiers.hidden_by_user_at < ?', 1.week.ago) }
scope :discarded_by_administration_expired, -> { discarded.where('dossiers.hidden_by_administration_at < ?', 1.week.ago) }
scope :discarded_brouillon_expired, -> do scope :discarded_brouillon_expired, -> do
with_discarded with_discarded
.discarded
.state_brouillon .state_brouillon
.where('hidden_at < ?', 1.week.ago) .discarded_expired
.or(state_brouillon.discarded_by_user_expired)
end end
scope :discarded_en_construction_expired, -> do scope :discarded_en_construction_expired, -> do
with_discarded with_discarded
.discarded
.state_en_construction .state_en_construction
.where('dossiers.hidden_at < ?', 1.week.ago) .discarded_expired
.or(state_en_construction.discarded_by_user_expired)
end end
scope :discarded_termine_expired, -> do scope :discarded_termine_expired, -> do
with_discarded with_discarded
.discarded
.state_termine .state_termine
.where('dossiers.hidden_at < ?', 1.week.ago) .discarded_expired
.or(state_termine.discarded_by_user_expired.discarded_by_administration_expired)
end end
scope :brouillon_near_procedure_closing_date, -> do scope :brouillon_near_procedure_closing_date, -> do
@ -531,6 +535,10 @@ class Dossier < ApplicationRecord
brouillon? || en_construction? || termine? brouillon? || en_construction? || termine?
end end
def can_be_hidden_by_user?
en_construction? || termine?
end
def can_be_deleted_by_manager? def can_be_deleted_by_manager?
kept? && can_be_deleted_by_user? kept? && can_be_deleted_by_user?
end end
@ -750,16 +758,26 @@ class Dossier < ApplicationRecord
false false
end end
def discard_and_keep_track!(author, reason) def author_is_user(author)
author_is_user = author.is_a?(User) author.is_a?(User)
author_is_administration = author.is_a?(Instructeur) || author.is_a?(Administrateur) || author.is_a?(SuperAdmin) end
if termine? && author_is_administration def author_is_administration(author)
author.is_a?(Instructeur) || author.is_a?(Administrateur) || author.is_a?(SuperAdmin)
end
def restore_dossier_and_destroy_deleted_dossier(author)
deleted_dossier&.destroy!
log_dossier_operation(author, :restaurer, self)
end
def discard_and_keep_track!(author, reason)
if termine? && author_is_administration(author)
update(hidden_by_administration_at: Time.zone.now) update(hidden_by_administration_at: Time.zone.now)
end end
if termine? && author_is_user if can_be_hidden_by_user? && author_is_user(author)
update(hidden_by_user_at: Time.zone.now) update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil)
end end
user_email = user_deleted? ? nil : user_email_for(:notification) user_email = user_deleted? ? nil : user_email_for(:notification)
@ -772,8 +790,9 @@ class Dossier < ApplicationRecord
deleted_dossier = DeletedDossier.create_from_dossier(self, reason) deleted_dossier = DeletedDossier.create_from_dossier(self, reason)
end end
update!(dossier_transfer_id: nil) if !(en_construction? && author_is_user(author))
discard! discard!
end
end end
end end
@ -798,11 +817,19 @@ class Dossier < ApplicationRecord
def restore(author) def restore(author)
if discarded? if discarded?
transaction do transaction do
if hidden_by_administration? if author_is_administration(author) && hidden_by_administration?
update(hidden_by_administration_at: nil) update(hidden_by_administration_at: nil)
elsif undiscard && keep_track_on_deletion? end
deleted_dossier&.destroy!
log_dossier_operation(author, :restaurer, self) if undiscard && keep_track_on_deletion?
restore_dossier_and_destroy_deleted_dossier(author)
end
end
elsif author_is_user(author) && hidden_by_user?
transaction do
update(hidden_by_user_at: nil)
if en_construction?
restore_dossier_and_destroy_deleted_dossier(author)
end end
end end
end end

View file

@ -232,7 +232,7 @@ class Instructeur < ApplicationRecord
def dossiers_count_summary(groupe_instructeur_ids) def dossiers_count_summary(groupe_instructeur_ids)
query = <<~EOF query = <<~EOF
SELECT SELECT
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where not archived) AS tous, COUNT(DISTINCT dossiers.id) FILTER (where not archived) AS tous,

View file

@ -9,6 +9,7 @@
%tbody %tbody
- deleted_dossiers.each do |dossier| - deleted_dossiers.each do |dossier|
- libelle_demarche = Procedure.find(dossier.procedure_id).libelle - libelle_demarche = Procedure.find(dossier.procedure_id).libelle
%tr{ data: { 'dossier-id': dossier.dossier_id } } %tr{ data: { 'dossier-id': dossier.dossier_id } }
%td.number-col %td.number-col
%span.icon.folder %span.icon.folder

View file

@ -0,0 +1,36 @@
- if hidden_dossiers.present?
%table.table.dossiers-table.hoverable
%thead
%tr
%th.number-col Nº dossier
%th Démarche
%th Raison de suppression
%th Date de suppression
%tbody
- hidden_dossiers.each do |dossier|
- libelle_demarche = dossier.procedure.libelle
%tr{ data: { 'dossier-id': dossier.id } }
%td.number-col
%span.icon.folder
= dossier.id
%td
= libelle_demarche
%td.cell-link
= deletion_reason_badge("user_request")
%td
= dossier.updated_at.strftime('%d/%m/%Y')
%td
= link_to restore_dossier_path(dossier.id), method: :patch, class: "button primary" do
Restaurer
= paginate(hidden_dossiers)
- else
.blank-tab
%h2.empty-text Aucun dossier.
%p.empty-text-details
Pour remplir une démarche, contactez votre administration en lui demandant le lien de la démarche.
%br
Celui ci doit ressembler à #{APPLICATION_BASE_URL}/commencer/xxx.

View file

@ -39,11 +39,17 @@
active: @statut == 'dossiers-expirant', active: @statut == 'dossiers-expirant',
badge: number_with_html_delimiter(@dossiers_close_to_expiration.count)) badge: number_with_html_delimiter(@dossiers_close_to_expiration.count))
- if @dossiers_supprimes.present? - if @dossiers_supprimes_recemment.present?
= tab_item(t('pluralize.dossiers_supprimes', count: @dossiers_supprimes.count), = tab_item(t('pluralize.dossiers_supprimes_recemment', count: @dossiers_supprimes_recemment.count),
dossiers_path(statut: 'dossiers-supprimes'), dossiers_path(statut: 'dossiers-supprimes-recemment'),
active: @statut == 'dossiers-supprimes', active: @statut == 'dossiers-supprimes-recemment',
badge: number_with_html_delimiter(@dossiers_supprimes.count)) badge: number_with_html_delimiter(@dossiers_supprimes_recemment.count))
- if @dossiers_supprimes_definitivement.present?
= tab_item(t('pluralize.dossiers_supprimes_definitivement', count: @dossiers_supprimes_definitivement.count),
dossiers_path(statut: 'dossiers-supprimes-definitivement'),
active: @statut == 'dossiers-supprimes-definitivement',
badge: number_with_html_delimiter(@dossiers_supprimes_definitivement.count))
- if @dossier_transfers.present? - if @dossier_transfers.present?
= tab_item(t('pluralize.dossiers_transferes', count: @dossier_transfers.count), = tab_item(t('pluralize.dossiers_transferes', count: @dossier_transfers.count),
@ -61,8 +67,12 @@
- if @statut == "dossiers-invites" - if @statut == "dossiers-invites"
= render partial: "dossiers_list", locals: { dossiers: @dossiers_invites } = render partial: "dossiers_list", locals: { dossiers: @dossiers_invites }
- if @statut == "dossiers-supprimes" - if @statut == "dossiers-supprimes-recemment"
= render partial: "deleted_dossiers_list", locals: { deleted_dossiers: @dossiers_supprimes } = render partial: "hidden_dossiers_list", locals: { hidden_dossiers: @dossiers_supprimes_recemment }
- if @statut == "dossiers-supprimes-definitivement"
= render partial: "deleted_dossiers_list", locals: { deleted_dossiers: @dossiers_supprimes_definitivement }
- if @statut == "dossiers-transferes" - if @statut == "dossiers-transferes"
= render partial: "transfered_dossiers_list", locals: { dossier_transfers: @dossier_transfers } = render partial: "transfered_dossiers_list", locals: { dossier_transfers: @dossier_transfers }

View file

@ -364,10 +364,14 @@ en:
zero: guest file zero: guest file
one: guest file one: guest file
other: guest files other: guest files
dossiers_supprimes: dossiers_supprimes_recemment:
zero: deleted file zero: recently deleted file
one: deleted file one: recently deleted file
other: deleted files other: recently deleted files
dossiers_supprimes_definitivement:
zero: permanently deleted file
one: permanently deleted file
other: permanently deleted files
dossiers_transferes: dossiers_transferes:
zero: transfer request zero: transfer request
one: transfer request one: transfer request
@ -402,6 +406,7 @@ en:
ask_deletion: ask_deletion:
undergoingreview: "Your file is undergoing review. It is no longer possible to delete your file. To cancel the undergoingreview contact the adminitration via the mailbox." undergoingreview: "Your file is undergoing review. It is no longer possible to delete your file. To cancel the undergoingreview contact the adminitration via the mailbox."
soft_deleted_dossier: "Your file has been successfully deleted from your interface" soft_deleted_dossier: "Your file has been successfully deleted from your interface"
restore: "Your file has been successfully restored"
update_brouillon: update_brouillon:
draft_saved: "Your draft has been saved." draft_saved: "Your draft has been saved."
etablissement: etablissement:

View file

@ -371,10 +371,14 @@ fr:
zero: dossier invité zero: dossier invité
one: dossier invité one: dossier invité
other: dossiers invités other: dossiers invités
dossiers_supprimes: dossiers_supprimes_recemment:
zero: dossier supprimé zero: dossier supprimé recemment
one: dossier supprimé one: dossier supprimé recemment
other: dossiers supprimés other: dossiers supprimés recemment
dossiers_supprimes_definitivement:
zero: dossier supprimé définitivement
one: dossier supprimé définitivement
other: dossiers supprimés définitivement
dossiers_transferes: dossiers_transferes:
zero: demande de transfert zero: demande de transfert
one: demande de transfert one: demande de transfert
@ -410,6 +414,7 @@ fr:
ask_deletion: ask_deletion:
undergoingreview: "Linstruction de votre dossier a commencé, il nest plus possible de supprimer votre dossier. Si vous souhaitez annuler linstruction contactez votre administration par la messagerie de votre dossier." undergoingreview: "Linstruction de votre dossier a commencé, il nest plus possible de supprimer votre dossier. Si vous souhaitez annuler linstruction contactez votre administration par la messagerie de votre dossier."
soft_deleted_dossier: "Votre dossier a bien été supprimé de votre interface" soft_deleted_dossier: "Votre dossier a bien été supprimé de votre interface"
restore: "Votre dossier a bien été restauré"
update_brouillon: update_brouillon:
draft_saved: "Votre brouillon a bien été sauvegardé." draft_saved: "Votre brouillon a bien été sauvegardé."
etablissement: etablissement:

View file

@ -269,6 +269,7 @@ Rails.application.routes.draw do
get 'messagerie' get 'messagerie'
post 'commentaire' => 'dossiers#create_commentaire' post 'commentaire' => 'dossiers#create_commentaire'
patch 'delete_dossier' patch 'delete_dossier'
patch 'restore', to: 'dossiers#restore'
get 'attestation' get 'attestation'
get 'transferer', to: 'dossiers#transferer' get 'transferer', to: 'dossiers#transferer'
end end

View file

@ -0,0 +1,37 @@
describe Administrateurs::ProcedureAdministrateursController, type: :controller do
let(:signed_in_admin) { create(:administrateur) }
let(:other_admin) { create(:administrateur) }
let(:procedure) { create(:procedure, administrateurs: [signed_in_admin, other_admin]) }
before do
sign_in(signed_in_admin.user)
end
describe '#destroy' do
subject do
delete :destroy, params: { procedure_id: procedure.id, id: admin_to_remove.id }, format: :js, xhr: true
end
context 'when removing another admin' do
let(:admin_to_remove) { other_admin }
it 'removes the admin from the procedure' do
subject
expect(response.status).to eq(200)
expect(flash[:notice]).to be_present
expect(admin_to_remove.procedures.reload).not_to include(procedure)
end
end
context 'when removing oneself from a procedure' do
let(:admin_to_remove) { signed_in_admin }
it 'denies the right for an admin to remove itself' do
subject
expect(response.status).to eq(200)
expect(flash[:alert]).to be_present
expect(admin_to_remove.procedures.reload).to include(procedure)
end
end
end
end

View file

@ -1028,13 +1028,13 @@ describe Users::DossiersController, type: :controller do
subject subject
end end
it "deletes the dossier" do it "hide the dossier and create a deleted dossier" do
procedure = dossier.procedure procedure = dossier.procedure
dossier_id = dossier.id dossier_id = dossier.id
subject subject
expect(Dossier.find_by(id: dossier_id)).to eq(nil) expect(Dossier.find_by(id: dossier_id)).to be_present
expect(Dossier.find_by(id: dossier_id).hidden_by_user_at).to be_present
expect(procedure.deleted_dossiers.count).to eq(1) expect(procedure.deleted_dossiers.count).to eq(1)
expect(procedure.deleted_dossiers.first.dossier_id).to eq(dossier_id)
end end
it { is_expected.to redirect_to(dossiers_path) } it { is_expected.to redirect_to(dossiers_path) }
@ -1065,6 +1065,21 @@ describe Users::DossiersController, type: :controller do
end end
end end
describe '#restore' do
before { sign_in(user) }
subject { patch :restore, params: { id: dossier.id } }
context 'when the user want to restore his dossier' do
let!(:dossier) { create(:dossier, :with_individual, state: :accepte, en_construction_at: Time.zone.yesterday.beginning_of_day.utc, hidden_by_user_at: Time.zone.yesterday.beginning_of_day.utc, user: user, autorisation_donnees: true) }
before { subject }
it 'must have hidden_by_user_at nil' do
expect(dossier.reload.hidden_by_user_at).to be_nil
end
end
end
describe '#new' do describe '#new' do
let(:procedure) { create(:procedure, :published) } let(:procedure) { create(:procedure, :published) }
let(:procedure_id) { procedure.id } let(:procedure_id) { procedure.id }

View file

@ -1,51 +0,0 @@
---
http_interactions:
- request:
method: get
uri: http://file.to/get.ext
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Last-Modified:
- Thu, 16 Dec 2021 13:04:07 GMT
X-Trans-Id:
- tx62bf43b03d7e4b60b3f25-0061d45dbd
Accept-Ranges:
- bytes
Expires:
- Tue, 04 Jan 2022 15:20:54 GMT
X-Openstack-Request-Id:
- tx62bf43b03d7e4b60b3f25-0061d45dbd
Content-Type:
- image/png
Date:
- Tue, 04 Jan 2022 14:46:21 GMT
X-Iplb-Request-Id:
- 877D6D0C:B8E4_5762BBC9:01BB_61D45DBD_104936A5:293F4
X-Timestamp:
- '1639659846.52947'
Etag:
- 49961feab1c277af65fcb876c379cebf
X-Iplb-Instance:
- '42085'
Content-Length:
- '494761'
Content-Disposition:
- inline; filename="Screen Shot 2021-12-09 at 9.42.44 AM.png"; filename*=UTF-8''Screen%20Shot%202021-12-09%20at%209.42.44%20AM.png
Strict-Transport-Security:
- max-age=63072000
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
recorded_with: VCR 6.0.0

View file

@ -1,145 +0,0 @@
---
http_interactions:
- request:
method: get
uri: https://i.etsystatic.com/6212702/r/il/744d2c/470726480/il_1588xN.470726480_bpk5.jpg
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Cache-Control:
- public, max-age=365000000, immutable
Content-Type:
- image/jpeg
Etag:
- '"J9Qt3QnUKZIbmKehfH+pmv/rYxafyM81ENfBKsCN6Qw"'
Expires:
- Mon, 02 Jan 2023 05:24:30 GMT
Fastly-Io-Info:
- ifsz=14677 idim=600x600 ifmt=jpeg ofsz=43339 odim=1588x1588 ofmt=jpeg
Fastly-Stats:
- io=1
Server:
- UploadServer
X-Goog-Generation:
- '1514228438620441'
X-Goog-Hash:
- crc32c=ZohETA==
- md5=iKPGsaOoUN0hhNZYYzYDLQ==
X-Goog-Metageneration:
- '1'
X-Goog-Storage-Class:
- MULTI_REGIONAL
X-Goog-Stored-Content-Encoding:
- identity
X-Goog-Stored-Content-Length:
- '14677'
X-Guploader-Uploadid:
- ADPycdvXmKF1KUStMVeN1v5TUKBQA_YezSueBDRwp4qiVKTn5IDoW7f3_t6_tyvJwjkOoUE4lO1cC_NxSl-LkM5ukthJcv6JkA
Via:
- 1.1 varnish, 1.1 varnish
Accept-Ranges:
- bytes
Date:
- Tue, 04 Jan 2022 14:59:40 GMT
Age:
- '207310'
X-Served-By:
- cache-mdw17340-MDW, cache-cdg20755-CDG
X-Cache:
- HIT, HIT
X-Cache-Hits:
- 1, 1
X-Timer:
- S1641308380.238785,VS0,VE1
Vary:
- Accept
Strict-Transport-Security:
- max-age=300
Content-Length:
- '43339'
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
- request:
method: get
uri: https://i.etsystatic.com/6212702/r/il/744d2c/470726480/il_1588xN.470726480_bpk5.jpg
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Cache-Control:
- public, max-age=365000000, immutable
Content-Type:
- image/jpeg
Etag:
- '"J9Qt3QnUKZIbmKehfH+pmv/rYxafyM81ENfBKsCN6Qw"'
Expires:
- Mon, 02 Jan 2023 05:24:30 GMT
Fastly-Io-Info:
- ifsz=14677 idim=600x600 ifmt=jpeg ofsz=43339 odim=1588x1588 ofmt=jpeg
Fastly-Stats:
- io=1
Server:
- UploadServer
X-Goog-Generation:
- '1514228438620441'
X-Goog-Hash:
- crc32c=ZohETA==
- md5=iKPGsaOoUN0hhNZYYzYDLQ==
X-Goog-Metageneration:
- '1'
X-Goog-Storage-Class:
- MULTI_REGIONAL
X-Goog-Stored-Content-Encoding:
- identity
X-Goog-Stored-Content-Length:
- '14677'
X-Guploader-Uploadid:
- ADPycdvXmKF1KUStMVeN1v5TUKBQA_YezSueBDRwp4qiVKTn5IDoW7f3_t6_tyvJwjkOoUE4lO1cC_NxSl-LkM5ukthJcv6JkA
Via:
- 1.1 varnish, 1.1 varnish
Accept-Ranges:
- bytes
Date:
- Tue, 04 Jan 2022 14:59:40 GMT
Age:
- '207310'
X-Served-By:
- cache-mdw17340-MDW, cache-cdg20737-CDG
X-Cache:
- HIT, HIT
X-Cache-Hits:
- 1, 1
X-Timer:
- S1641308380.241689,VS0,VE1
Vary:
- Accept
Strict-Transport-Security:
- max-age=300
Content-Length:
- '43339'
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
recorded_with: VCR 6.0.0

View file

@ -0,0 +1,80 @@
---
http_interactions:
- request:
method: get
uri: https://opengraph.githubassets.com/d0e7862b24d8026a3c03516d865b28151eb3859029c6c6c2e86605891fbdcd7a/socketry/async-io
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
X-Ratelimit-Limit:
- '100'
X-Ratelimit-Remaining:
- '31'
X-Ratelimit-Reset:
- '1641406997'
Access-Control-Allow-Origin:
- "*"
Content-Security-Policy:
- default-src 'none';style-src 'unsafe-inline';font-src https://github.github.com;img-src
https://avatars.githubusercontent.com https://github.githubassets.com https://camo.githubusercontent.com
X-Dns-Prefetch-Control:
- 'off'
Expect-Ct:
- max-age=0
X-Frame-Options:
- SAMEORIGIN
X-Download-Options:
- noopen
X-Content-Type-Options:
- nosniff
X-Permitted-Cross-Domain-Policies:
- none
Referrer-Policy:
- no-referrer
X-Xss-Protection:
- '0'
Cache-Control:
- public, max-age=21600, immutable
Content-Type:
- image/png
Etag:
- W/"106ec-qZkXmv4Ygfd8LZzQoW8mwToCU7k"
X-Github-Backend:
- Kubernetes
X-Github-Request-Id:
- 2BDC:1C4E:5B580:368C45:61D5E1B1
Via:
- 1.1 varnish, 1.1 varnish
Accept-Ranges:
- bytes
Date:
- Thu, 06 Jan 2022 14:38:48 GMT
Age:
- '3435'
X-Served-By:
- cache-iad-kiad7000127-IAD, cache-cdg20776-CDG
X-Cache:
- HIT, HIT
X-Cache-Hits:
- 1, 1
Strict-Transport-Security:
- max-age=31536000
X-Fastly-Request-Id:
- '064738cfe513e9a05fc3af47b513b4f63b3f199c'
Content-Length:
- '67308'
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
recorded_with: VCR 6.0.0

View file

@ -0,0 +1,105 @@
---
http_interactions:
- request:
method: get
uri: https://www.demarches-simplifiees.fr/error_2
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 404
message: ''
headers:
Server:
- nginx
Date:
- Thu, 06 Jan 2022 14:39:31 GMT
Content-Type:
- text/html; charset=UTF-8
Content-Length:
- '1583'
X-Request-Id:
- 8881aea9-7dfc-442b-b818-a2988a8a39ee
X-Runtime:
- '0.003713'
Strict-Transport-Security:
- max-age=63072000
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
- request:
method: get
uri: https://www.demarches-simplifiees.fr/error_1
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 404
message: ''
headers:
Server:
- nginx
Date:
- Thu, 06 Jan 2022 14:53:09 GMT
Content-Type:
- text/html; charset=UTF-8
Content-Length:
- '1583'
X-Request-Id:
- ea764501-134e-49a4-bd65-7fb96f772908
X-Runtime:
- '0.005202'
Strict-Transport-Security:
- max-age=63072000
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
- request:
method: get
uri: https://www.demarches-simplifiees.fr/error_1
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 404
message: ''
headers:
Server:
- nginx
Date:
- Thu, 06 Jan 2022 14:53:09 GMT
Content-Type:
- text/html; charset=UTF-8
Content-Length:
- '1583'
X-Request-Id:
- 27892c10-cc06-4b63-80ea-b14428d4585c
X-Runtime:
- '0.003955'
Strict-Transport-Security:
- max-age=63072000
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
recorded_with: VCR 6.0.0

View file

@ -0,0 +1,157 @@
---
http_interactions:
- request:
method: get
uri: https://opengraph.githubassets.com/5e61989aecb78e369c93674f877d7bf4ecde378850114a9563cdf8b6a2472536/typhoeus/typhoeus/issues/110
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
X-Ratelimit-Limit:
- '100'
X-Ratelimit-Remaining:
- '83'
X-Ratelimit-Reset:
- '1641476165'
Access-Control-Allow-Origin:
- "*"
Content-Security-Policy:
- default-src 'none';style-src 'unsafe-inline';font-src https://github.github.com;img-src
https://avatars.githubusercontent.com https://github.githubassets.com https://camo.githubusercontent.com
X-Dns-Prefetch-Control:
- 'off'
Expect-Ct:
- max-age=0
X-Frame-Options:
- SAMEORIGIN
X-Download-Options:
- noopen
X-Content-Type-Options:
- nosniff
X-Permitted-Cross-Domain-Policies:
- none
Referrer-Policy:
- no-referrer
X-Xss-Protection:
- '0'
Cache-Control:
- public, max-age=21600, immutable
Content-Type:
- image/png
Etag:
- W/"1066a-R45DSLyb/5W3DaEyAWdsUpLVmr4"
X-Github-Backend:
- Kubernetes
X-Github-Request-Id:
- CB64:6748:E2B78:86C62A:61D6EE15
Via:
- 1.1 varnish, 1.1 varnish
Accept-Ranges:
- bytes
Date:
- Thu, 06 Jan 2022 14:38:47 GMT
Age:
- '4322'
X-Served-By:
- cache-iad-kiad7000110-IAD, cache-cdg20721-CDG
X-Cache:
- MISS, HIT
X-Cache-Hits:
- 0, 1
Strict-Transport-Security:
- max-age=31536000
X-Fastly-Request-Id:
- b6b7ae28f1e40734296daed0187e36df9f25de8d
Content-Length:
- '67178'
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
- request:
method: get
uri: https://opengraph.githubassets.com/5e61989aecb78e369c93674f877d7bf4ecde378850114a9563cdf8b6a2472536/typhoeus/typhoeus/issues/110
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
X-Ratelimit-Limit:
- '100'
X-Ratelimit-Remaining:
- '83'
X-Ratelimit-Reset:
- '1641476165'
Access-Control-Allow-Origin:
- "*"
Content-Security-Policy:
- default-src 'none';style-src 'unsafe-inline';font-src https://github.github.com;img-src
https://avatars.githubusercontent.com https://github.githubassets.com https://camo.githubusercontent.com
X-Dns-Prefetch-Control:
- 'off'
Expect-Ct:
- max-age=0
X-Frame-Options:
- SAMEORIGIN
X-Download-Options:
- noopen
X-Content-Type-Options:
- nosniff
X-Permitted-Cross-Domain-Policies:
- none
Referrer-Policy:
- no-referrer
X-Xss-Protection:
- '0'
Cache-Control:
- public, max-age=21600, immutable
Content-Type:
- image/png
Etag:
- W/"1066a-R45DSLyb/5W3DaEyAWdsUpLVmr4"
X-Github-Backend:
- Kubernetes
X-Github-Request-Id:
- CB64:6748:E2B78:86C62A:61D6EE15
Via:
- 1.1 varnish, 1.1 varnish
Accept-Ranges:
- bytes
Date:
- Thu, 06 Jan 2022 14:38:47 GMT
Age:
- '4322'
X-Served-By:
- cache-iad-kiad7000110-IAD, cache-cdg20767-CDG
X-Cache:
- MISS, HIT
X-Cache-Hits:
- 0, 1
Strict-Transport-Security:
- max-age=31536000
X-Fastly-Request-Id:
- 58c062baca0e760a7a6c348ab9c64cceb965dd1a
Content-Length:
- '67178'
body:
encoding: ASCII-8BIT
string: ''
recorded_at: Wed, 04 Mar 2020 23:00:00 GMT
recorded_with: VCR 6.0.0

View file

@ -18,6 +18,13 @@ describe ApplicationHelper do
end end
end end
describe "#flash_class" do
it { expect(flash_class('notice')).to eq 'alert-success' }
it { expect(flash_class('alert', sticky: true, fixed: true)).to eq 'alert-danger sticky alert-fixed' }
it { expect(flash_class('error')).to eq 'alert-danger' }
it { expect(flash_class('unknown-level')).to eq '' }
end
describe "#try_format_date" do describe "#try_format_date" do
subject { try_format_date(date) } subject { try_format_date(date) }

View file

@ -1,36 +0,0 @@
describe Utils::Retryable do
Includer = Struct.new(:something) do
include Utils::Retryable
def caller(max_attempt:, errors:)
with_retry(max_attempt: max_attempt, errors: errors) do
yield
end
end
end
subject { Includer.new("test") }
let(:spy) { double() }
describe '#with_retry' do
it 'works while retry count is less than max attempts' do
divider_that_raise_error = 0
divider_that_works = 1
expect(spy).to receive(:divider).and_return(divider_that_raise_error, divider_that_works)
result = subject.caller(max_attempt: 2, errors: [ZeroDivisionError]) { 10 / spy.divider }
expect(result).to eq(10 / divider_that_works)
end
it 're raise error if it occures more than max_attempt' do
expect(spy).to receive(:divider).and_return(0, 0)
expect { subject.caller(max_attempt: 1, errors: [ZeroDivisionError]) { 0 / spy.divider } }
.to raise_error(ZeroDivisionError)
end
it 'does not retry other errors' do
expect(spy).to receive(:divider).and_raise(StandardError).once
expect { subject.caller(max_attempt: 2, errors: [ZeroDivisionError]) { 0 / spy.divider } }
.to raise_error(StandardError)
end
end
end

View file

@ -793,6 +793,7 @@ describe Dossier do
describe "#discard_and_keep_track!" do describe "#discard_and_keep_track!" do
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
let(:user) { dossier.user }
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 } let(:last_operation) { dossier.dossier_operation_logs.last }
let(:reason) { :user_request } let(:reason) { :user_request }
@ -802,7 +803,7 @@ describe Dossier do
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.discard_and_keep_track!(dossier.user, reason) } subject! { dossier.discard_and_keep_track!(user, reason) }
context 'brouillon' do context 'brouillon' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
@ -821,8 +822,9 @@ describe Dossier do
end end
context 'en_construction' do context 'en_construction' do
it 'hides the dossier' do it 'hide the dossier but does not discard' do
expect(dossier.hidden_at).to be_present expect(dossier.hidden_at).to be_nil
expect(dossier.hidden_by_user_at).to be_present
end end
it 'creates a DeletedDossier record' do it 'creates a DeletedDossier record' do
@ -872,6 +874,7 @@ describe Dossier do
end end
context 'with reason: manager_request' do context 'with reason: manager_request' do
let(:user) { dossier.procedure.administrateurs.first }
let(:reason) { :manager_request } let(:reason) { :manager_request }
it 'hides the dossier' do it 'hides the dossier' do
@ -887,13 +890,12 @@ describe Dossier do
context 'with reason: user_removed' do context 'with reason: user_removed' do
let(:reason) { :user_removed } let(:reason) { :user_removed }
it 'hides the dossier' do it 'does not discard the dossier' do
expect(dossier.discarded?).to be_truthy expect(dossier.discarded?).to be_falsy
end end
it 'records the operation in the log' do it 'hide the dossier' do
expect(last_operation.operation).to eq("supprimer") expect(dossier.hidden_by_user_at).to be_present
expect(last_operation.automatic_operation?).to be_falsey
end end
end end
end end

View file

@ -128,12 +128,11 @@ describe ProcedureArchiveService do
let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month) } let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month) }
let(:year) { 2021 } let(:year) { 2021 }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
before do
allow_any_instance_of(ActiveStorage::Attached::One).to receive(:url).and_return("http://file.to/get.ext") it 'collects files with success' do
end allow_any_instance_of(ActiveStorage::Attached::One).to receive(:url).and_return("https://opengraph.githubassets.com/d0e7862b24d8026a3c03516d865b28151eb3859029c6c6c2e86605891fbdcd7a/socketry/async-io")
it 'collect files' do
expect(InstructeurMailer).to receive(:send_archive).and_return(mailer) expect(InstructeurMailer).to receive(:send_archive).and_return(mailer)
VCR.use_cassette('archive/file_to_get') do VCR.use_cassette('archive/new_file_to_get_200') do
service.collect_files_archive(archive, instructeur) service.collect_files_archive(archive, instructeur)
end end
@ -152,6 +151,26 @@ describe ProcedureArchiveService do
expect(archive.file.attached?).to be_truthy expect(archive.file.attached?).to be_truthy
end end
it 'retry errors files with errors' do
allow_any_instance_of(ActiveStorage::Attached::One).to receive(:url).and_return("https://www.demarches-simplifiees.fr/error_1")
expect(InstructeurMailer).to receive(:send_archive).and_return(mailer)
VCR.use_cassette('archive/new_file_to_get_400.html') do
service.collect_files_archive(archive, instructeur)
end
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"
]
expect(files.map(&:filename)).to match_array(structure)
end
expect(archive.file.attached?).to be_truthy
end
context 'with a missing file' do context 'with a missing file' do
let(:pj) do let(:pj) do
PiecesJustificativesService::FakeAttachment.new( PiecesJustificativesService::FakeAttachment.new(
@ -211,14 +230,12 @@ describe ProcedureArchiveService do
context 'for all months' do context 'for all months' do
let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending') } let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending') }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
before do
allow_any_instance_of(ActiveStorage::Attached::One).to receive(:url).and_return("https://i.etsystatic.com/6212702/r/il/744d2c/470726480/il_1588xN.470726480_bpk5.jpg")
end
it 'collect files' do it 'collect files' do
allow_any_instance_of(ActiveStorage::Attached::One).to receive(:url).and_return("https://opengraph.githubassets.com/5e61989aecb78e369c93674f877d7bf4ecde378850114a9563cdf8b6a2472536/typhoeus/typhoeus/issues/110")
expect(InstructeurMailer).to receive(:send_archive).and_return(mailer) expect(InstructeurMailer).to receive(:send_archive).and_return(mailer)
VCR.use_cassette('archive/file_to_get_typhoeus') do VCR.use_cassette('archive/old_file_to_get_200') do
service.collect_files_archive(archive, instructeur) service.collect_files_archive(archive, instructeur)
end end

View file

@ -12,7 +12,8 @@ describe 'users/dossiers/index.html.haml', type: :view do
allow(controller).to receive(:current_user) { user } allow(controller).to receive(:current_user) { user }
assign(:user_dossiers, Kaminari.paginate_array(user_dossiers).page(1)) assign(:user_dossiers, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1)) assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1))
assign(:dossiers_supprimes, Kaminari.paginate_array(user_dossiers).page(1)) assign(:dossiers_supprimes_recemment, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossiers_supprimes_definitivement, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossiers_traites, Kaminari.paginate_array(user_dossiers).page(1)) assign(:dossiers_traites, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossier_transfers, Kaminari.paginate_array([]).page(1)) assign(:dossier_transfers, Kaminari.paginate_array([]).page(1))
assign(:dossiers_close_to_expiration, Kaminari.paginate_array([]).page(1)) assign(:dossiers_close_to_expiration, Kaminari.paginate_array([]).page(1))
@ -69,7 +70,7 @@ describe 'users/dossiers/index.html.haml', type: :view do
it 'affiche la barre donglets' do it 'affiche la barre donglets' do
expect(rendered).to have_selector('nav.tabs') expect(rendered).to have_selector('nav.tabs')
expect(rendered).to have_selector('nav.tabs li', count: 4) expect(rendered).to have_selector('nav.tabs li', count: 5)
expect(rendered).to have_selector('nav.tabs li.active', count: 1) expect(rendered).to have_selector('nav.tabs li.active', count: 1)
end end
end end

View file

@ -6038,9 +6038,9 @@ folder-walker@^3.2.0:
from2 "^2.1.0" from2 "^2.1.0"
follow-redirects@^1.0.0: follow-redirects@^1.0.0:
version "1.14.6" version "1.14.7"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
for-in@^1.0.2: for-in@^1.0.2:
version "1.0.2" version "1.0.2"