Merge pull request #7025 from betagouv/main

2022-03-11-01
This commit is contained in:
Paul Chavard 2022-03-11 12:11:03 +01:00 committed by GitHub
commit 7c75a25552
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 581 additions and 402 deletions

7
.gitignore vendored
View file

@ -35,3 +35,10 @@ yarn-debug.log*
# Local Netlify folder # Local Netlify folder
.netlify .netlify
# Custom locales
/config/custom_locales/*
!/config/custom_locales/.keep
# Custom views
/app/custom_views/*
!/app/custom_views/.keep

View file

@ -63,7 +63,7 @@ module Administrateurs
end end
def destroy def destroy
if !groupe_instructeur.dossiers.with_discarded.empty? if !groupe_instructeur.dossiers.empty?
flash[:alert] = "Impossible de supprimer un groupe avec des dossiers. Il faut le réaffecter avant" flash[:alert] = "Impossible de supprimer un groupe avec des dossiers. Il faut le réaffecter avant"
elsif procedure.groupe_instructeurs.one? elsif procedure.groupe_instructeurs.one?
flash[:alert] = "Suppression impossible : il doit y avoir au moins un groupe instructeur sur chaque procédure" flash[:alert] = "Suppression impossible : il doit y avoir au moins un groupe instructeur sur chaque procédure"
@ -95,7 +95,7 @@ module Administrateurs
def reaffecter def reaffecter
target_group = procedure.groupe_instructeurs.find(params[:target_group]) target_group = procedure.groupe_instructeurs.find(params[:target_group])
reaffecter_bulk_messages(target_group) reaffecter_bulk_messages(target_group)
groupe_instructeur.dossiers.with_discarded.find_each do |dossier| groupe_instructeur.dossiers.find_each do |dossier|
dossier.assign_to_groupe_instructeur(target_group, current_administrateur) dossier.assign_to_groupe_instructeur(target_group, current_administrateur)
end end

View file

@ -17,6 +17,7 @@ class ApplicationController < ActionController::Base
before_action :set_active_storage_host before_action :set_active_storage_host
before_action :setup_javascript_settings before_action :setup_javascript_settings
before_action :setup_tracking before_action :setup_tracking
before_action :set_customizable_view_path
around_action :switch_locale around_action :switch_locale
@ -359,4 +360,8 @@ class ApplicationController < ActionController::Base
http_accept_language.compatible_language_from(I18n.available_locales) http_accept_language.compatible_language_from(I18n.available_locales)
end end
end end
def set_customizable_view_path
prepend_view_path "app/custom_views"
end
end end

View file

@ -228,7 +228,7 @@ module Instructeurs
def delete_dossier def delete_dossier
if dossier.termine? if dossier.termine?
dossier.discard_and_keep_track!(current_instructeur, :instructeur_request) dossier.hide_and_keep_track!(current_instructeur, :instructeur_request)
flash.notice = t('instructeurs.dossiers.deleted_by_instructeur') flash.notice = t('instructeurs.dossiers.deleted_by_instructeur')
redirect_to instructeur_procedure_path(procedure) redirect_to instructeur_procedure_path(procedure)
else else
@ -242,6 +242,7 @@ module Instructeurs
def dossier def dossier
@dossier ||= current_instructeur @dossier ||= current_instructeur
.dossiers .dossiers
.visible_by_administration
.includes(champs: :type_de_champ) .includes(champs: :type_de_champ)
.find(params[:dossier_id]) .find(params[:dossier_id])
end end

View file

@ -13,19 +13,21 @@ module Instructeurs
.order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc) .order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc)
dossiers = current_instructeur.dossiers.joins(:groupe_instructeur) dossiers = current_instructeur.dossiers.joins(:groupe_instructeur)
@dossiers_count_per_procedure = dossiers.all_state.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count dossiers_visibles = dossiers.visible_by_administration
@dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_count_per_procedure = dossiers_visibles.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count @dossiers_a_suivre_count_per_procedure = dossiers_visibles.without_followers.en_cours.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_archived_count_per_procedure = dossiers_visibles.archived.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 @dossiers_termines_count_per_procedure = dossiers_visibles.termine.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_expirant_count_per_procedure = dossiers_visibles.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count
@dossiers_supprimes_recemment_count_per_procedure = dossiers.hidden_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_supprimes_recemment_count_per_procedure = dossiers.hidden_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
@followed_dossiers_count_per_procedure = current_instructeur @followed_dossiers_count_per_procedure = current_instructeur
.followed_dossiers .followed_dossiers
.joins(:groupe_instructeur) .joins(:groupe_instructeur)
.en_cours .en_cours
.where(groupe_instructeur_id: groupe_ids) .where(groupe_instructeur_id: groupe_ids)
.visible_by_administration
.group('groupe_instructeurs.procedure_id') .group('groupe_instructeurs.procedure_id')
.reorder(nil) .reorder(nil)
.count .count
@ -56,27 +58,27 @@ module Instructeurs
@a_suivre_count, @suivis_count, @traites_count, @tous_count, @supprimes_recemment_count, @archives_count, @expirant_count = current_instructeur @a_suivre_count, @suivis_count, @traites_count, @tous_count, @supprimes_recemment_count, @archives_count, @expirant_count = current_instructeur
.dossiers_count_summary(groupe_instructeur_ids) .dossiers_count_summary(groupe_instructeur_ids)
.fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'supprimes_recemment', 'archives', 'expirant') .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'supprimes_recemment', 'archives', 'expirant')
@can_download_dossiers = (@tous_count + @archives_count) > 0
dossiers_visibles = Dossier dossiers = Dossier.where(groupe_instructeur_id: groupe_instructeur_ids)
.where(groupe_instructeur_id: groupe_instructeur_ids) dossiers_visibles = dossiers.visible_by_administration
@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
.where(groupe_instructeur_id: groupe_instructeur_ids)
.en_cours .en_cours
.merge(dossiers_visibles)
@followed_dossiers_id = @followed_dossiers.pluck(:id) @followed_dossiers_id = @followed_dossiers.pluck(:id)
@termines_dossiers = dossiers_visibles.termine.visible_by_administration @termines_dossiers = dossiers_visibles.termine
@all_state_dossiers = dossiers_visibles.all_state.visible_by_administration @all_state_dossiers = dossiers_visibles.all_state
@supprimes_recemment_dossiers = dossiers_visibles.termine.hidden_by_administration
@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
@supprimes_recemment_dossiers = dossiers.hidden_by_administration.termine
@dossiers = case statut @dossiers = case statut
when 'a-suivre' when 'a-suivre'
@ -178,10 +180,10 @@ module Instructeurs
.groupe_instructeurs .groupe_instructeurs
.where(procedure: procedure) .where(procedure: procedure)
@dossier_count = current_instructeur @can_download_dossiers = current_instructeur
.dossiers_count_summary(groupe_instructeur_ids) .dossiers
.fetch_values('tous', 'archives') .visible_by_administration
.sum .exists?(groupe_instructeur_id: groupe_instructeur_ids)
export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs) export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs)

View file

@ -4,7 +4,7 @@ class InvitesController < ApplicationController
def create def create
email = params[:invite_email].downcase email = params[:invite_email].downcase
dossier = current_user.dossiers.find(params[:dossier_id]) dossier = current_user.dossiers.visible_by_user.find(params[:dossier_id])
invite = Invite.create( invite = Invite.create(
dossier: dossier, dossier: dossier,

View file

@ -9,10 +9,10 @@ module Manager
def scoped_resource def scoped_resource
if unfiltered_list? if unfiltered_list?
# Don't display discarded dossiers in the unfiltered list… # Don't display discarded dossiers in the unfiltered list…
Dossier.kept Dossier.visible_by_administration
else else
# … but allow them to be searched and displayed. # … but allow them to be searched and displayed.
Dossier.with_discarded Dossier
end end
end end

View file

@ -5,10 +5,12 @@ 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, :restore] ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE_HIDDEN = [: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 + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE_HIDDEN
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
before_action :ensure_ownership_or_invitation_hidden!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE_HIDDEN
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update] before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update]
before_action :forbid_invite_submission!, only: [:update_brouillon] before_action :forbid_invite_submission!, only: [:update_brouillon]
before_action :forbid_closed_submission!, only: [:update_brouillon] before_action :forbid_closed_submission!, only: [:update_brouillon]
@ -16,17 +18,20 @@ 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.visible_by_user.order_by_updated_at.page(page) dossiers = Dossier.includes(:procedure).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_visibles = dossiers.visible_by_user
@dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.order_by_updated_at.page(page) @user_dossiers = current_user.dossiers.state_not_termine.merge(dossiers_visibles)
@dossiers_traites = current_user.dossiers.state_termine.merge(dossiers_visibles)
@dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.merge(dossiers_visibles)
@dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers)
@dossiers_supprimes_definitivement = current_user.deleted_dossiers.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)
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @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
@ -214,9 +219,8 @@ module Users
end end
def delete_dossier def delete_dossier
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.discard_and_keep_track!(current_user, :user_request) dossier.hide_and_keep_track!(current_user, :user_request)
flash.notice = t('users.dossiers.ask_deletion.soft_deleted_dossier') flash.notice = t('users.dossiers.ask_deletion.soft_deleted_dossier')
redirect_to dossiers_path redirect_to dossiers_path
else else
@ -277,7 +281,7 @@ module Users
def dossier_for_help def dossier_for_help
dossier_id = params[:id] || params[:dossier_id] dossier_id = params[:id] || params[:dossier_id]
@dossier || (dossier_id.present? && Dossier.find_by(id: dossier_id.to_i)) @dossier || (dossier_id.present? && Dossier.visible_by_user.find_by(id: dossier_id.to_i))
end end
def transferer def transferer
@ -289,7 +293,7 @@ module Users
end end
def restore def restore
dossier.restore(current_user) hidden_dossier.restore(current_user)
flash.notice = t('users.dossiers.restore') flash.notice = t('users.dossiers.restore')
redirect_to dossiers_path redirect_to dossiers_path
end end
@ -355,11 +359,15 @@ module Users
end end
def dossier def dossier
@dossier ||= Dossier.find(params[:id] || params[:dossier_id]) @dossier ||= Dossier.visible_by_user.find(params[:id] || params[:dossier_id])
end
def hidden_dossier
@hidden_dossier ||= Dossier.hidden_by_user.find(params[:id] || params[:dossier_id])
end end
def dossier_with_champs def dossier_with_champs
Dossier.with_champs.find(params[:id]) Dossier.with_champs.visible_by_user.find(params[:id])
end end
def should_change_groupe_instructeur? def should_change_groupe_instructeur?
@ -436,6 +444,12 @@ module Users
end end
end end
def ensure_ownership_or_invitation_hidden!
if !current_user.owns_or_invite?(hidden_dossier)
forbidden!
end
end
def forbid_invite_submission! def forbid_invite_submission!
if passage_en_construction? && !current_user.owns?(dossier) if passage_en_construction? && !current_user.owns?(dossier)
forbidden! forbidden!

0
app/custom_views/.keep Normal file
View file

View file

@ -15,7 +15,8 @@ class DossierDashboard < Administrate::BaseDashboard
text_summary: Field::String.with_options(searchable: false), text_summary: Field::String.with_options(searchable: false),
created_at: Field::DateTime, created_at: Field::DateTime,
updated_at: Field::DateTime, updated_at: Field::DateTime,
hidden_at: Field::DateTime, hidden_by_user_at: Field::DateTime,
hidden_by_administration_at: Field::DateTime,
champs: ChampCollectionField champs: ChampCollectionField
}.freeze }.freeze
@ -41,7 +42,8 @@ class DossierDashboard < Administrate::BaseDashboard
:champs, :champs,
:created_at, :created_at,
:updated_at, :updated_at,
:hidden_at :hidden_by_user_at,
:hidden_by_administration_at
].freeze ].freeze
# FORM_ATTRIBUTES # FORM_ATTRIBUTES

View file

@ -20,9 +20,9 @@ module Types
def dossier(number:) def dossier(number:)
if context.internal_use? if context.internal_use?
Dossier.state_not_brouillon.with_discarded.for_api_v2.find(number)
else
Dossier.state_not_brouillon.for_api_v2.find(number) Dossier.state_not_brouillon.for_api_v2.find(number)
else
Dossier.visible_by_administration.for_api_v2.find(number)
end end
rescue => e rescue => e
raise GraphQL::ExecutionError.new(e.message, extensions: { code: :not_found }) raise GraphQL::ExecutionError.new(e.message, extensions: { code: :not_found })

View file

@ -5,7 +5,7 @@ class AvisMailer < ApplicationMailer
layout 'mailers/layout' layout 'mailers/layout'
def avis_invitation(avis) def avis_invitation(avis)
if avis.dossier.present? if avis.dossier.visible_by_administration?
@avis = avis @avis = avis
email = @avis.expert&.email email = @avis.expert&.email
subject = "Donnez votre avis sur le dossier nº #{@avis.dossier.id} (#{@avis.dossier.procedure.libelle})" subject = "Donnez votre avis sur le dossier nº #{@avis.dossier.id} (#{@avis.dossier.procedure.libelle})"

View file

@ -48,8 +48,8 @@ class Avis < ApplicationRecord
scope :for_dossier, -> (dossier_id) { where(dossier_id: dossier_id) } scope :for_dossier, -> (dossier_id) { where(dossier_id: dossier_id) }
scope :by_latest, -> { order(updated_at: :desc) } scope :by_latest, -> { order(updated_at: :desc) }
scope :updated_since?, -> (date) { where('avis.updated_at > ?', date) } scope :updated_since?, -> (date) { where('avis.updated_at > ?', date) }
scope :discarded_termine_expired, -> { unscope(:joins).where(dossier: Dossier.discarded_termine_expired) } scope :termine_expired, -> { unscope(:joins).where(dossier: Dossier.termine_expired) }
scope :discarded_en_construction_expired, -> { unscope(:joins).where(dossier: Dossier.discarded_en_construction_expired) } scope :en_construction_expired, -> { unscope(:joins).where(dossier: Dossier.en_construction_expired) }
scope :not_hidden_by_administration, -> { where(dossiers: { hidden_by_administration_at: nil }) } scope :not_hidden_by_administration, -> { where(dossiers: { hidden_by_administration_at: nil }) }
# The form allows subtmitting avis requests to several emails at once, # The form allows subtmitting avis requests to several emails at once,
# hence this virtual attribute. # hence this virtual attribute.

View file

@ -20,7 +20,7 @@
# type_de_champ_id :integer # type_de_champ_id :integer
# #
class Champ < ApplicationRecord class Champ < ApplicationRecord
belongs_to :dossier, -> { with_discarded }, inverse_of: false, touch: true, optional: false belongs_to :dossier, inverse_of: false, touch: true, optional: false
belongs_to :type_de_champ, inverse_of: :champ, optional: false belongs_to :type_de_champ, inverse_of: :champ, optional: false
belongs_to :parent, class_name: 'Champ', optional: true belongs_to :parent, class_name: 'Champ', optional: true
has_one_attached :piece_justificative_file has_one_attached :piece_justificative_file

View file

@ -30,6 +30,8 @@ class DeletedDossier < ApplicationRecord
} }
def self.create_from_dossier(dossier, reason) def self.create_from_dossier(dossier, reason)
return if !dossier.log_operations?
# We have some bad data because of partially deleted dossiers in the past. # We have some bad data because of partially deleted dossiers in the past.
# For now use find_or_create_by! to avoid errors. # For now use find_or_create_by! to avoid errors.
create_with( create_with(

View file

@ -42,10 +42,6 @@ class Dossier < ApplicationRecord
include DossierFilteringConcern include DossierFilteringConcern
include DossierRebaseConcern include DossierRebaseConcern
include Discard::Model
self.discard_column = :hidden_at
default_scope -> { kept }
enum state: { enum state: {
brouillon: 'brouillon', brouillon: 'brouillon',
en_construction: 'en_construction', en_construction: 'en_construction',
@ -200,6 +196,7 @@ class Dossier < ApplicationRecord
scope :state_brouillon, -> { where(state: states.fetch(:brouillon)) } scope :state_brouillon, -> { where(state: states.fetch(:brouillon)) }
scope :state_not_brouillon, -> { where.not(state: states.fetch(:brouillon)) } scope :state_not_brouillon, -> { where.not(state: states.fetch(:brouillon)) }
scope :state_en_construction, -> { where(state: states.fetch(:en_construction)) } scope :state_en_construction, -> { where(state: states.fetch(:en_construction)) }
scope :state_not_en_construction, -> { where.not(state: states.fetch(:en_construction)) }
scope :state_en_instruction, -> { where(state: states.fetch(:en_instruction)) } scope :state_en_instruction, -> { where(state: states.fetch(:en_instruction)) }
scope :state_en_construction_ou_instruction, -> { where(state: EN_CONSTRUCTION_OU_INSTRUCTION) } scope :state_en_construction_ou_instruction, -> { where(state: EN_CONSTRUCTION_OU_INSTRUCTION) }
scope :state_instruction_commencee, -> { where(state: INSTRUCTION_COMMENCEE) } scope :state_instruction_commencee, -> { where(state: INSTRUCTION_COMMENCEE) }
@ -211,7 +208,11 @@ class Dossier < ApplicationRecord
scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) } 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 :visible_by_user, -> { where(hidden_by_user_at: nil) } scope :visible_by_user, -> { where(hidden_by_user_at: nil) }
scope :visible_by_administration, -> { where("hidden_by_administration_at IS NULL AND NOT (hidden_by_user_at IS NOT NULL AND dossiers.state = 'en_construction')") } scope :visible_by_administration, -> {
state_not_brouillon
.where(hidden_by_administration_at: nil)
.merge(visible_by_user.or(state_not_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) }
@ -349,27 +350,11 @@ 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 :deleted_by_user_expired, -> { where('dossiers.hidden_by_user_at < ?', 1.week.ago) }
scope :discarded_by_user_expired, -> { discarded.where('dossiers.hidden_by_user_at < ?', 1.week.ago) } scope :deleted_by_administration_expired, -> { where('dossiers.hidden_by_administration_at < ?', 1.week.ago) }
scope :discarded_by_administration_expired, -> { discarded.where('dossiers.hidden_by_administration_at < ?', 1.week.ago) } scope :en_brouillon_expired_to_delete, -> { state_brouillon.deleted_by_user_expired }
scope :discarded_brouillon_expired, -> do scope :en_construction_expired_to_delete, -> { state_en_construction.deleted_by_user_expired }
with_discarded scope :termine_expired_to_delete, -> { state_termine.deleted_by_user_expired.deleted_by_administration_expired }
.state_brouillon
.discarded_expired
.or(state_brouillon.discarded_by_user_expired)
end
scope :discarded_en_construction_expired, -> do
with_discarded
.state_en_construction
.discarded_expired
.or(state_en_construction.discarded_by_user_expired)
end
scope :discarded_termine_expired, -> do
with_discarded
.state_termine
.discarded_expired
.or(state_termine.discarded_by_user_expired.discarded_by_administration_expired)
end
scope :brouillon_near_procedure_closing_date, -> do scope :brouillon_near_procedure_closing_date, -> do
# select users who have submitted dossier for the given 'procedures.id' # select users who have submitted dossier for the given 'procedures.id'
@ -537,8 +522,8 @@ class Dossier < ApplicationRecord
brouillon? || en_construction? || termine? brouillon? || en_construction? || termine?
end end
def can_be_hidden_by_user? def can_be_deleted_by_administration?(reason)
en_construction? || termine? termine? || reason == :procedure_removed
end end
def messagerie_available? def messagerie_available?
@ -686,10 +671,6 @@ class Dossier < ApplicationRecord
!procedure.brouillon? && !brouillon? !procedure.brouillon? && !brouillon?
end end
def keep_track_on_deletion?
!procedure.brouillon? && !brouillon?
end
def hidden_by_user? def hidden_by_user?
hidden_by_user_at.present? hidden_by_user_at.present?
end end
@ -698,8 +679,16 @@ class Dossier < ApplicationRecord
hidden_by_administration_at.present? hidden_by_administration_at.present?
end end
def deleted_by_instructeur_and_user? def hidden_for_administration?
termine? && hidden_by_administration? && hidden_by_user? hidden_by_administration? || (hidden_by_user? && en_construction?) || brouillon?
end
def visible_by_administration?
!hidden_for_administration?
end
def hidden_for_administration_and_user?
hidden_for_administration? && hidden_by_user?
end end
def expose_legacy_carto_api? def expose_legacy_carto_api?
@ -738,11 +727,9 @@ class Dossier < ApplicationRecord
def expired_keep_track_and_destroy! def expired_keep_track_and_destroy!
transaction do transaction do
if keep_track_on_deletion?
DeletedDossier.create_from_dossier(self, :expired) DeletedDossier.create_from_dossier(self, :expired)
dossier_operation_logs.destroy_all dossier_operation_logs.destroy_all
log_automatic_dossier_operation(:supprimer, self) log_automatic_dossier_operation(:supprimer, self)
end
destroy! destroy!
end end
true true
@ -758,37 +745,20 @@ class Dossier < ApplicationRecord
author.is_a?(Instructeur) || author.is_a?(Administrateur) || author.is_a?(SuperAdmin) author.is_a?(Instructeur) || author.is_a?(Administrateur) || author.is_a?(SuperAdmin)
end end
def restore_dossier_and_destroy_deleted_dossier(author) def hide_and_keep_track!(author, reason)
if deleted_dossier.present?
deleted_dossier&.destroy!
end
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, hidden_by_reason: reason)
end
if can_be_hidden_by_user? && author_is_user(author)
update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil, hidden_by_reason: reason)
end
transaction do transaction do
if deleted_by_instructeur_and_user? || en_construction? || brouillon? if author_is_administration(author) && can_be_deleted_by_administration?(reason)
if keep_track_on_deletion? update(hidden_by_administration_at: Time.zone.now, hidden_by_reason: reason)
elsif author_is_user(author) && can_be_deleted_by_user?
update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil, hidden_by_reason: reason)
else
raise "Unauthorized dossier hide attempt Dossier##{id} by #{author} for reason #{reason}"
end
log_dossier_operation(author, :supprimer, self) log_dossier_operation(author, :supprimer, self)
end end
if !(en_construction? && author_is_user(author)) if en_construction? && !hidden_by_administration?
discard!
end
end
end
if en_construction?
update(hidden_by_reason: reason)
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email) administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
administration_emails.each do |email| administration_emails.each do |email|
DossierMailer.notify_en_construction_deletion_to_administration(self, email).deliver_later DossierMailer.notify_en_construction_deletion_to_administration(self, email).deliver_later
@ -797,30 +767,18 @@ class Dossier < ApplicationRecord
end end
def restore(author) def restore(author)
if discarded?
transaction do transaction do
if author_is_administration(author) && hidden_by_administration? if author_is_administration(author)
update(hidden_by_administration_at: nil) update(hidden_by_administration_at: nil)
end elsif author_is_user(author)
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) update(hidden_by_user_at: nil)
!hidden_by_administration? && update(hidden_by_reason: nil) end
if en_construction? if !hidden_by_user? && !hidden_by_administration?
restore_dossier_and_destroy_deleted_dossier(author) update(hidden_by_reason: nil)
end
end
elsif author_is_administration(author) && hidden_by_administration?
transaction do
update(hidden_by_administration_at: nil)
!hidden_by_user? && update(hidden_by_reason: nil)
end end
log_dossier_operation(author, :restaurer, self)
end end
end end
@ -1152,19 +1110,16 @@ class Dossier < ApplicationRecord
def purge_discarded def purge_discarded
transaction do transaction do
if keep_track_on_deletion?
DeletedDossier.create_from_dossier(self, hidden_by_reason) DeletedDossier.create_from_dossier(self, hidden_by_reason)
end
dossier_operation_logs.not_deletion.destroy_all dossier_operation_logs.not_deletion.destroy_all
destroy destroy
end end
end end
def self.purge_discarded def self.purge_discarded
discarded_brouillon_expired.find_each(&:purge_discarded) en_brouillon_expired_to_delete.find_each(&:purge_discarded)
discarded_en_construction_expired.find_each(&:purge_discarded) en_construction_expired_to_delete.find_each(&:purge_discarded)
discarded_termine_expired.find_each(&:purge_discarded) termine_expired_to_delete.find_each(&:purge_discarded)
end end
private private
@ -1243,7 +1198,7 @@ class Dossier < ApplicationRecord
followers_instructeurs.each do |instructeur| followers_instructeurs.each do |instructeur|
if instructeur.groupe_instructeurs.exclude?(groupe_instructeur) if instructeur.groupe_instructeurs.exclude?(groupe_instructeur)
instructeur.unfollow(self) instructeur.unfollow(self)
if kept? if visible_by_administration?
DossierMailer.notify_groupe_instructeur_changed(instructeur, self).deliver_later DossierMailer.notify_groupe_instructeur_changed(instructeur, self).deliver_later
end end
end end

View file

@ -37,8 +37,9 @@ class DossierOperationLog < ApplicationRecord
belongs_to :bill_signature, optional: true belongs_to :bill_signature, optional: true
scope :not_deletion, -> { where.not(operation: operations.fetch(:supprimer)) } scope :not_deletion, -> { where.not(operation: operations.fetch(:supprimer)) }
scope :discarded_en_construction_expired, -> { where(dossier: Dossier.discarded_en_construction_expired).not_deletion } scope :brouillon_expired, -> { where(dossier: Dossier.brouillon_expired).not_deletion }
scope :discarded_termine_expired, -> { where(dossier: Dossier.discarded_termine_expired).not_deletion } scope :en_construction_expired, -> { where(dossier: Dossier.en_construction_expired).not_deletion }
scope :termine_expired, -> { where(dossier: Dossier.termine_expired).not_deletion }
def self.create_and_serialize(params) def self.create_and_serialize(params)
dossier = params.fetch(:dossier) dossier = params.fetch(:dossier)

View file

@ -14,7 +14,7 @@ class DossierTransfer < ApplicationRecord
scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) } scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :with_dossiers, -> { joins(:dossiers) } scope :with_dossiers, -> { joins(:dossiers).merge(Dossier.visible_by_user) }
after_create_commit :send_notification after_create_commit :send_notification
@ -48,7 +48,7 @@ class DossierTransfer < ApplicationRecord
def destroy_and_nullify def destroy_and_nullify
transaction do transaction do
# Rails cascading is not working with default scopes. Doing nullify cascade manually. # Rails cascading is not working with default scopes. Doing nullify cascade manually.
dossiers.with_discarded.update_all(dossier_transfer_id: nil) dossiers.update_all(dossier_transfer_id: nil)
destroy destroy
end end
end end
@ -56,7 +56,7 @@ class DossierTransfer < ApplicationRecord
def self.destroy_stale def self.destroy_stale
transaction do transaction do
# Rails cascading is not working with default scopes. Doing nullify cascade manually. # Rails cascading is not working with default scopes. Doing nullify cascade manually.
Dossier.with_discarded.where(transfer: stale).update_all(dossier_transfer_id: nil) Dossier.where(transfer: stale).update_all(dossier_transfer_id: nil)
stale.destroy_all stale.destroy_all
end end
end end

View file

@ -231,14 +231,13 @@ 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 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 dossiers.hidden_by_administration_at IS NULL AND 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 dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis, COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND 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 dossiers.hidden_by_administration_at IS NULL AND not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND NOT (dossiers.hidden_by_administration_at IS NOT NULL)) AS tous, COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND not archived) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND (dossiers.hidden_by_administration_at IS NOT NULL AND dossiers.state in ('accepte', 'refuse', 'sans_suite') )) AS supprimes_recemment, COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives, COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NOT NULL AND not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS supprimes_recemment,
COUNT(DISTINCT dossiers.id) FILTER (where COUNT(DISTINCT dossiers.id) FILTER (where dossiers.hidden_by_administration_at IS NULL AND procedures.procedure_expires_when_termine_enabled
procedures.procedure_expires_when_termine_enabled
AND ( AND (
dossiers.state in ('accepte', 'refuse', 'sans_suite') dossiers.state in ('accepte', 'refuse', 'sans_suite')
AND dossiers.processed_at + dossiers.conservation_extension + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now AND dossiers.processed_at + dossiers.conservation_extension + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now
@ -247,17 +246,17 @@ class Instructeur < ApplicationRecord
AND dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now AND dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now
) )
) AS expirant ) AS expirant
FROM "dossiers" FROM dossiers
INNER JOIN "procedure_revisions" INNER JOIN procedure_revisions
ON "procedure_revisions"."id" = "dossiers"."revision_id" ON procedure_revisions.id = dossiers.revision_id
INNER JOIN "procedures" INNER JOIN procedures
ON "procedures"."id" = "procedure_revisions"."procedure_id" ON procedures.id = procedure_revisions.procedure_id
LEFT OUTER JOIN follows LEFT OUTER JOIN follows
ON follows.dossier_id = dossiers.id ON follows.dossier_id = dossiers.id
AND follows.unfollowed_at IS NULL AND follows.unfollowed_at IS NULL
WHERE "dossiers"."hidden_at" IS NULL WHERE dossiers.state != 'brouillon'
AND "dossiers"."state" != 'brouillon' AND dossiers.groupe_instructeur_id in (:groupe_instructeur_ids)
AND "dossiers"."groupe_instructeur_id" in (:groupe_instructeur_ids) AND (dossiers.hidden_by_user_at IS NULL OR dossiers.state != 'en_construction')
EOF EOF
sanitized_query = ActiveRecord::Base.sanitize_sql([ sanitized_query = ActiveRecord::Base.sanitize_sql([

View file

@ -26,14 +26,9 @@ class Invite < ApplicationRecord
validates :email, format: { with: Devise.email_regexp, message: "n'est pas valide" }, allow_nil: true validates :email, format: { with: Devise.email_regexp, message: "n'est pas valide" }, allow_nil: true
# #1619 When an administrateur deletes a `Procedure`, its `hidden_at` field, and scope :with_dossiers, -> { joins(:dossier).merge(Dossier.visible_by_user) }
# the `hidden_at` field of its `Dossier`s, get set, effectively removing the Procedure
# and Dossier from their respective `default_scope`s.
# Therefore, we also remove `Invite`s for such effectively deleted `Dossier`s
# from their default scope.
scope :kept, -> { joins(:dossier).merge(Dossier.kept) }
default_scope { kept } default_scope { with_dossiers }
def send_notification def send_notification
if self.user.present? if self.user.present?

View file

@ -682,15 +682,15 @@ class Procedure < ApplicationRecord
close! close!
end end
dossiers.termine.visible_by_administration.each do |dossier| dossiers.visible_by_administration.each do |dossier|
dossier.discard_and_keep_track!(author, :procedure_removed) dossier.hide_and_keep_track!(author, :procedure_removed)
end end
discard! discard!
end end
def purge_discarded def purge_discarded
if !dossiers.with_discarded.exists? if dossiers.empty?
destroy destroy
end end
end end

View file

@ -190,20 +190,34 @@ class User < ApplicationRecord
end end
transaction do transaction do
Invite.where(dossier: dossiers.with_discarded).destroy_all # delete invites
dossiers.state_en_construction.each do |dossier| Invite.where(dossier: dossiers).destroy_all
dossier.discard_and_keep_track!(administration, :user_removed)
# delete dossiers brouillon
dossiers.state_brouillon.each do |dossier|
dossier.hide_and_keep_track!(dossier.user, :user_removed)
end
dossiers.state_brouillon.find_each(&:purge_discarded)
# delete dossiers en_construction
dossiers.state_en_construction.each do |dossier|
dossier.hide_and_keep_track!(dossier.user, :user_removed)
end
dossiers.state_en_construction.find_each(&:purge_discarded)
# delete dossiers terminé
dossiers.state_termine.each do |dossier|
dossier.hide_and_keep_track!(dossier.user, :user_removed)
end end
DossierOperationLog.where(dossier: dossiers.with_discarded.discarded).not_deletion.destroy_all
dossiers.with_discarded.discarded.destroy_all
dossiers.update_all(deleted_user_email_never_send: email, user_id: nil, dossier_transfer_id: nil) dossiers.update_all(deleted_user_email_never_send: email, user_id: nil, dossier_transfer_id: nil)
destroy! destroy!
end end
end end
def merge(old_user) def merge(old_user)
transaction do transaction do
old_user.dossiers.with_discarded.update_all(user_id: id) old_user.dossiers.update_all(user_id: id)
old_user.invites.update_all(user_id: id) old_user.invites.update_all(user_id: id)
old_user.merge_logs.update_all(user_id: id) old_user.merge_logs.update_all(user_id: id)

View file

@ -18,7 +18,7 @@ class DossierSearchService
def self.dossier_by_exact_id(dossiers, search_terms) def self.dossier_by_exact_id(dossiers, search_terms)
id = search_terms.to_i id = search_terms.to_i
if id != 0 && id_compatible?(id) # Sometimes instructeur is searching dossiers with a big number (ex: SIRET), ActiveRecord can't deal with them and throws ActiveModel::RangeError. id_compatible? prevents this. if id != 0 && id_compatible?(id) # Sometimes instructeur is searching dossiers with a big number (ex: SIRET), ActiveRecord can't deal with them and throws ActiveModel::RangeError. id_compatible? prevents this.
dossiers.where(id: id).ids dossiers.visible_by_administration.where(id: id).ids
else else
[] []
end end
@ -29,6 +29,7 @@ class DossierSearchService
ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})"
dossiers dossiers
.visible_by_administration
.where("#{ts_vector} @@ #{ts_query}") .where("#{ts_vector} @@ #{ts_query}")
.order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC")) .order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC"))
.pluck('id') .pluck('id')
@ -40,6 +41,7 @@ class DossierSearchService
ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})" ts_query = "to_tsquery('french', #{Dossier.connection.quote(to_tsquery(search_terms))})"
dossiers dossiers
.visible_by_user
.where("#{ts_vector} @@ #{ts_query}") .where("#{ts_vector} @@ #{ts_query}")
.order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC")) .order(Arel.sql("COALESCE(ts_rank(#{ts_vector}, #{ts_query}), 0) DESC"))
end end
@ -47,7 +49,7 @@ class DossierSearchService
def self.dossier_by_exact_id_for_user(search_terms, user) def self.dossier_by_exact_id_for_user(search_terms, user)
id = search_terms.to_i id = search_terms.to_i
if id != 0 && id_compatible?(id) # Sometimes user is searching dossiers with a big number (ex: SIRET), ActiveRecord can't deal with them and throws ActiveModel::RangeError. id_compatible? prevents this. if id != 0 && id_compatible?(id) # Sometimes user is searching dossiers with a big number (ex: SIRET), ActiveRecord can't deal with them and throws ActiveModel::RangeError. id_compatible? prevents this.
Dossier.where(id: user.dossiers.where(id: id) + user.dossiers_invites.where(id: id)).distinct Dossier.where(id: user.dossiers.visible_by_user.where(id: id) + user.dossiers_invites.visible_by_user.where(id: id)).distinct
else else
Dossier.none Dossier.none
end end

View file

@ -1,5 +1,4 @@
- if dossier_count > 0 %span.dropdown
%span.dropdown
%button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'download-menu' } %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'download-menu' }
Télécharger tous les dossiers Télécharger tous les dossiers
#download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' } #download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' }

View file

@ -1,4 +1,6 @@
<%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports, dossier_count: @dossier_count }) %> <% if @can_download_dossiers %>
<%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }) %>
<% end %>
<% @exports.each do |format, exports| %> <% @exports.each do |format, exports| %>
<% exports.each do |time_span_type, export| %> <% exports.each do |time_span_type, export| %>

View file

@ -10,7 +10,8 @@
= render partial: 'header', locals: { procedure: @procedure, statut: @statut } = render partial: 'header', locals: { procedure: @procedure, statut: @statut }
.procedure-actions .procedure-actions
= render partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports, dossier_count: @tous_count + @archives_count } - if @can_download_dossiers
= render partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }
.container.flex= render partial: "tabs", locals: { procedure: @procedure, .container.flex= render partial: "tabs", locals: { procedure: @procedure,
statut: @statut, statut: @statut,
a_suivre_count: @a_suivre_count, a_suivre_count: @a_suivre_count,

View file

@ -1,16 +1,16 @@
#footer #footer
%p{ class: "copyright col-md-push-#{12-main_container_size} col-md-#{main_container_size} col-lg-push-#{12-main_container_size} col-lg-#{main_container_size} text-muted small" } %p{ class: "copyright col-md-push-#{12-main_container_size} col-md-#{main_container_size} col-lg-push-#{12-main_container_size} col-lg-#{main_container_size} text-muted small" }
= link_to PROVIDER_NAME, PROVIDER_URL = link_to t("links.provider.name"), t("links.provider.url")
= Time.zone.now.year = Time.zone.now.year
\- \-
= link_to 'Nouveautés', DOC_NOUVEAUTES_URL, target: '_blank' = link_to t("links.footer.doc_nouveautes.label"), t("links.footer.doc_nouveautes.url"), title: t("links.footer.doc_nouveautes.title"), target: '_blank'
\- \-
= link_to 'Statistiques', stats_path = link_to t("links.footer.stats.label"), stats_path
\- \-
= link_to 'CGU / Mentions légales', CGU_URL, target: '_blank' = link_to "#{t("links.footer.cgu.label")} / #{t("links.footer.mentions_legales.label")}", t("links.footer.cgu.url"), title: t("links.footer.cgu.title"), target: '_blank'
\- \-
= link_to 'Documentation', DOC_URL, target: '_blank' = link_to t("links.footer.doc.label"), t("links.footer.doc.url"), title: t("links.footer.doc.title"), target: '_blank'
\- \-
= link_to 'FAQ', FAQ_ADMIN_URL, target: '_blank' = link_to t("links.footer.faq_admin.label"), t("links.footer.faq_admin.url"), title: t("links.footer.faq_admin.title"), target: '_blank'
\- \-
= link_to 'Inscription ateliers en ligne', WEBINAIRE_URL, target: '_blank' = link_to t("links.footer.webinaire.label"), t("links.footer.webinaire.url"), title: t("links.footer.webinaire.title"), target: '_blank'

View file

@ -145,7 +145,7 @@
<tr> <tr>
<td style="word-wrap:break-word;font-size:0px;padding:0px 20px 0px 20px;padding-top:0px;padding-bottom:0px;" align="center"> <td style="word-wrap:break-word;font-size:0px;padding:0px 20px 0px 20px;padding-top:0px;padding-bottom:0px;" align="center">
<div class="" style="cursor:auto;color:#55575d;font-family:Helvetica, Arial, sans-serif;font-size:11px;line-height:22px;text-align:center;"> <div class="" style="cursor:auto;color:#55575d;font-family:Helvetica, Arial, sans-serif;font-size:11px;line-height:22px;text-align:center;">
<%= "#{APPLICATION_NAME}" %> est un service fourni par <%= PROVIDED_BY %> <%= "#{APPLICATION_NAME}" %> est un service fourni par <%= t("links.provider.provided_by") %>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -22,7 +22,7 @@ as well as a link to its edit page.
<header class="main-content__header" role="banner"> <header class="main-content__header" role="banner">
<h1 class="main-content__page-title"> <h1 class="main-content__page-title">
<%= content_for(:title) %> <%= content_for(:title) %>
<% if dossier.discarded? %> <% if dossier.hidden_for_administration? %>
(Supprimé) (Supprimé)
<% end %> <% end %>
</h1> </h1>

View file

@ -6,44 +6,41 @@
%ul.footer-logos %ul.footer-logos
%li.footer-text %li.footer-text
Un service fourni par Un service fourni par
= link_to PROVIDED_BY, PROVIDER_URL, title: PROVIDER_TITLE = link_to t("links.provider.provided_by"), t("links.provider.url"), title: t("links.provider.title")
%br %br
%li %li
= link_to PROVIDER_URL, title: PROVIDER_NAME, 'aria-label': PROVIDER_NAME do = link_to t("links.provider.url"), title: t("links.provider.name"), 'aria-label': t("links.provider.name") do
%span.footer-logo{ role: 'img', 'aria-label': PROVIDER_LOGO_ALT } %span.footer-logo{ role: 'img', 'aria-label': t("links.provider.logo.alt") }
= image_tag PROVIDER_LOGO_SRC, alt: PROVIDER_LOGO_ALT, width: PROVIDER_LOGO_WIDTH, height: PROVIDER_LOGO_HEIGHT, loading: 'lazy' = image_tag t("links.provider.logo.src"), alt: t("links.provider.logo.alt"), width: t("links.provider.logo.width"), height: t("links.provider.logo.height"), loading: 'lazy'
= link_to "https://beta.gouv.fr", title: "le site de Beta.gouv.fr", 'aria-label': 'beta.gouv.fr' do = link_to t("links.footer.betagouv.url"), title: t("links.footer.betagouv.title"), 'aria-label': t("links.footer.betagouv.label") do
%span.footer-logo.footer-logo-beta-gouv-fr{ role: 'img', 'aria-label': 'Logo beta.gouv.fr' } %span.footer-logo.footer-logo-beta-gouv-fr{ role: 'img', 'aria-label': "Logo #{t('links.footer.betagouv.label')}" }
%li.footer-column %li.footer-column
%ul.footer-links %ul.footer-links
%li.footer-link %li.footer-link
= link_to "Newsletter", "https://my.sendinblue.com/users/subscribe/js_id/3s2q1/id/1", :title => "Notre newsletter", :class => "footer-link", :target => "_blank", rel: "noopener" = link_to t("links.footer.newsletter.label"), t("links.footer.newsletter.url"), title: t("links.footer.newsletter.title"), class: "footer-link", target: "_blank", rel: "noopener"
%li.footer-link %li.footer-link
-# haml-lint:disable ApplicationNameLinter = link_to t("links.footer.releases.label"), t("links.footer.releases.url"), title: t("links.footer.releases.title"), class: "footer-link"
= link_to "Nouveautés", "https://github.com/betagouv/demarches-simplifiees.fr/releases", :class => "footer-link", :title => "Nos nouveautés"
-# haml-lint:enable ApplicationNameLinter
%li.footer-link %li.footer-link
= link_to "Statistiques", stats_path, :class => "footer-link" = link_to t("links.footer.stats.label"), stats_path, title: t("links.footer.stats.title"), class: "footer-link"
%li.footer-link %li.footer-link
= link_to "CGU", CGU_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.cgu.label"), t("links.footer.cgu.url"), title: t("links.footer.cgu.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link %li.footer-link
= link_to "Mentions légales", MENTIONS_LEGALES_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.mentions_legales.label"), t("links.footer.mentions_legales.url"), title: t("links.footer.mentions_legales.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link %li.footer-link
= link_to "Suivi daudience et vie privée", suivi_path, :class => "footer-link" = link_to t("links.footer.suivi.label"), suivi_path, title: t("links.footer.suivi.title"), class: "footer-link"
%li.footer-column %li.footer-column
%ul.footer-links %ul.footer-links
%li.footer-link %li.footer-link
= contact_link "Contact", class: "footer-link" = contact_link t("links.footer.contact.label"), title: t("links.footer.contact.title"), class: "footer-link"
%li.footer-link %li.footer-link
= link_to "Documentation", DOC_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.doc.label"), t("links.footer.doc.url"), title: t("links.footer.doc.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link %li.footer-link
= link_to "Documentation de l'API", API_DOC_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.api_doc.label"), t("links.footer.api_doc.url"), title: t("links.footer.api_doc.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link %li.footer-link
= link_to "FAQ", FAQ_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.faq.label"), t("links.footer.faq.url"), title: t("links.footer.faq.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link %li.footer-link
= link_to ACCESSIBILITE_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" do = link_to t("links.footer.accessibilite.label"), t("links.footer.accessibilite.url"), title: t("links.footer.accessibilite.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
Accessibilité&nbsp;: non conforme
%li.footer-link %li.footer-link
= link_to "Disponibilité", STATUS_PAGE_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" = link_to t("links.footer.status_page.label"), t("links.footer.status_page.url"), title: t("links.footer.status_page.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"

View file

@ -1,9 +1,13 @@
%ul.footer-row.footer-bottom-line.footer-site-links %ul.footer-row.footer-bottom-line.footer-site-links
%li.footer-link-accessibilite> %li.footer-link-accessibilite>
= link_to ACCESSIBILITE_URL, target: "_blank", rel: "noopener noreferrer" do = link_to t("links.footer.accessibilite.label"), t("links.footer.accessibilite.url"), title: t("links.footer.accessibilite.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
Accessibilité&nbsp;: non conforme %li.footer-link-cgu>
%li.footer-link-cgu>= link_to "CGU", CGU_URL, target: "_blank", rel: "noopener noreferrer" = link_to t("links.footer.cgu.label"), t("links.footer.cgu.url"), title: t("links.footer.cgu.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link-mentions-legales>= link_to "Mentions légales", MENTIONS_LEGALES_URL, target: "_blank", rel: "noopener noreferrer" %li.footer-link-mentions-legales>
%li.footer-link-doc>= link_to 'Documentation', DOC_URL, target: "_blank", rel: "noopener noreferrer" = link_to t("links.footer.mentions_legales.label"), t("links.footer.mentions_legales.url"), title: t("links.footer.mentions_legales.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link-contact>= contact_link "Contact technique", dossier_id: dossier&.id %li.footer-link-doc>
%li.footer-link-aide>= link_to 'Aide', FAQ_URL, target: "_blank", rel: "noopener noreferrer" = link_to t("links.footer.doc.label"), t("links.footer.doc.url"), title: t("links.footer.doc.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"
%li.footer-link-contact>
= contact_link t("links.footer.contact_technique.label"), dossier_id: dossier&.id, title: t("links.footer.contact_technique.title"), class: "footer-link"
%li.footer-link-aide>
= link_to t("links.footer.aide.label"), t("links.footer.aide.url"), title: t("links.footer.aide.title"), class: "footer-link", target: "_blank", rel: "noopener noreferrer"

View file

@ -31,6 +31,7 @@ module TPS
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :fr config.i18n.default_locale = :fr
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.load_path += Dir[Rails.root.join('config', 'custom_locales', '**', '*.{rb,yml}')]
config.i18n.available_locales = [:fr, :en] config.i18n.available_locales = [:fr, :en]
config.i18n.fallbacks = [:fr] config.i18n.fallbacks = [:fr]

View file

View file

@ -96,16 +96,6 @@ MATOMO_IFRAME_URL="https://matomo.example.org/index.php?module=CoreAdminHome&act
# An URI used to report requests breaking the Content Security Policy # An URI used to report requests breaking the Content Security Policy
# CSP_REPORT_URI="https://myappname.report-uri.com/r/d/csp/reportOnly" # CSP_REPORT_URI="https://myappname.report-uri.com/r/d/csp/reportOnly"
# Instance provider
# PROVIDED_BY="la DINUM"
# PROVIDER_NAME="DINUM"
# PROVIDER_TITLE="Direction Interministérielle au Numérique"
# PROVIDER_URL="https://numerique.gouv.fr/"
# PROVIDER_LOGO_SRC="footer/logo-dinum.png"
# PROVIDER_LOGO_ALT="Logo DINUM"
# PROVIDER_LOGO_HEIGHT="161"
# PROVIDER_LOGO_WIDTH="138"
# Landing page sections # Landing page sections
# LANDING_TESTIMONIALS_ENABLED="enabled" # LANDING_TESTIMONIALS_ENABLED="enabled"
# LANDING_USERS_ENABLED="enabled" # LANDING_USERS_ENABLED="enabled"

View file

@ -48,5 +48,4 @@ STATUS_PAGE_URL = ENV.fetch("STATUS_PAGE_URL", "https://status.demarches-simplif
DEMANDE_INSCRIPTION_ADMIN_PAGE_URL = ENV.fetch("DEMANDE_INSCRIPTION_ADMIN_PAGE_URL", "https://www.demarches-simplifiees.fr/commencer/demande-d-inscription-a-demarches-simplifiees") DEMANDE_INSCRIPTION_ADMIN_PAGE_URL = ENV.fetch("DEMANDE_INSCRIPTION_ADMIN_PAGE_URL", "https://www.demarches-simplifiees.fr/commencer/demande-d-inscription-a-demarches-simplifiees")
MATOMO_IFRAME_URL = ENV.fetch("MATOMO_IFRAME_URL", "https://#{ENV.fetch('MATOMO_HOST', 'stats.data.gouv.fr')}/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli") MATOMO_IFRAME_URL = ENV.fetch("MATOMO_IFRAME_URL", "https://#{ENV.fetch('MATOMO_HOST', 'stats.data.gouv.fr')}/index.php?module=CoreAdminHome&action=optOut&language=fr&&fontColor=333333&fontSize=16px&fontFamily=Muli")
CSP_REPORT_URI = ENV.fetch("CSP_REPORT_URI", "") CSP_REPORT_URI = ENV.fetch("CSP_REPORT_URI", "")
# rubocop:enable DS/ApplicationName # rubocop:enable DS/ApplicationName

View file

@ -1,10 +0,0 @@
# Instance provider
PROVIDED_BY = ENV.fetch("PROVIDED_BY", "la DINUM")
PROVIDER_NAME = ENV.fetch("PROVIDER_NAME", "DINUM")
PROVIDER_TITLE = ENV.fetch("PROVIDER_TITLE", "Direction Interministérielle au Numérique")
PROVIDER_URL = ENV.fetch("PROVIDER_URL", "https://numerique.gouv.fr/")
PROVIDER_LOGO_SRC = ENV.fetch("PROVIDER_LOGO_SRC", "footer/logo-dinum.png")
PROVIDER_LOGO_ALT = ENV.fetch("PROVIDER_LOGO_ALT", "Logo DINUM")
PROVIDER_LOGO_HEIGHT = ENV.fetch("PROVIDER_LOGO_HEIGHT", "161")
PROVIDER_LOGO_WIDTH = ENV.fetch("PROVIDER_LOGO_WIDTH", "138")

View file

@ -0,0 +1,81 @@
en:
links:
provider:
logo:
src: "footer/logo-dinum.png"
alt: "Logo DINUM"
height: 161
width: 138
name: "DINUM"
provided_by: "la DINUM"
title: "Direction Interministérielle au Numérique"
url: "https://numerique.gouv.fr"
footer:
accessibilite:
label: "Accessibility: not compliant"
title: "Accessibility: not compliant"
url: "https://doc.demarches-simplifiees.fr/declaration-daccessibilite"
aide:
label: "Help"
title: "Frequently Asked Questions"
url: "https://faq.demarches-simplifiees.fr"
api_doc:
label: "API Documentation"
title: "API Documentation"
url: "https://doc.demarches-simplifiees.fr/pour-aller-plus-loin/graphql"
betagouv:
label: "Beta.gouv.fr"
title: "The Beta.gouv.fr website"
url: "https://beta.gouv.fr"
cgu:
label: "ToS"
title: "Terms of Service"
url: "https://doc.demarches-simplifiees.fr/cgu"
contact:
label: "Contact"
title: "Contact"
contact_technique:
label: "Technical contact"
title: "Technical contact"
doc:
label: "Documentation"
title: "Documentation"
url: "https://doc.demarches-simplifiees.fr"
doc_nouveautes:
label: "News"
title: "News"
url: "https://doc.demarches-simplifiees.fr/nouveautes"
faq:
label: "FAQ"
title: "Frequently Asked Questions"
url: "https://faq.demarches-simplifiees.fr"
faq_admin:
label: "FAQ"
title: "Frequently Asked Questions"
url: "https:///faq.demarches-simplifiees.fr/collection/1-administrateur-creation-dun-formulaire"
mentions_legales:
label: "Legal notices"
title: "Legal notices"
url: "https//doc.demarches-simplifiees.fr/mentions-legales"
newsletter:
label: "Newsletter"
title: "Notre newsletter"
url: "https://my.sendinblue.com/users/subscribe/js_id/3s2q1/id/1"
releases:
label: "Releases"
title: "Releases"
url: "https://github.com/betagouv/demarches-simplifiees.fr/releases"
stats:
label: "Statistics"
title: "Statistics"
status_page:
label: "Disponibility"
title: "Disponibility"
url: "https://status.demarches-simplifiees.fr"
suivi:
label: "Audience tracking and privacy"
title: "Audience tracking and privacy"
webinaire:
label: "Online workshop registration"
title: "Online workshop registration"
url: "https://app.livestorm.co/demarches-simplifiees"

View file

@ -0,0 +1,81 @@
fr:
links:
provider:
logo:
src: "footer/logo-dinum.png"
alt: "Logo DINUM"
height: 161
width: 138
name: "DINUM"
provided_by: "la DINUM"
title: "Direction Interministérielle au Numérique"
url: "https://numerique.gouv.fr"
footer:
accessibilite:
label: "Accessibilité : non conforme"
title: "Accessibilité : non conforme"
url: "https://doc.demarches-simplifiees.fr/declaration-daccessibilite"
aide:
label: "Aide"
title: "Foire aux Questions"
url: "https://faq.demarches-simplifiees.fr"
api_doc:
label: "Documentation de l'API"
title: "Documentation de l'API"
url: "https://doc.demarches-simplifiees.fr/pour-aller-plus-loin/graphql"
betagouv:
label: "Beta.gouv.fr"
title: "Le site de Beta.gouv.fr"
url: "https://beta.gouv.fr"
cgu:
label: "CGU"
title: "Conditions Générales d'Utilisation"
url: "https://doc.demarches-simplifiees.fr/cgu"
contact:
label: "Contact"
title: "Contact"
contact_technique:
label: "Contact technique"
title: "Contact technique"
doc:
label: "Documentation"
title: "Documentation"
url: "https://doc.demarches-simplifiees.fr"
doc_nouveautes:
label: "Nouveautés"
title: "Nouveautés"
url: "https://doc.demarches-simplifiees.fr/nouveautes"
faq:
label: "FAQ"
title: "Foire aux Questions"
url: "https://faq.demarches-simplifiees.fr"
faq_admin:
label: "FAQ"
title: "Foire aux Questions"
url: "https:///faq.demarches-simplifiees.fr/collection/1-administrateur-creation-dun-formulaire"
mentions_legales:
label: "Mentions légales"
title: "Mentions légales"
url: "https//doc.demarches-simplifiees.fr/mentions-legales"
newsletter:
label: "Newsletter"
title: "Notre newsletter"
url: "https://my.sendinblue.com/users/subscribe/js_id/3s2q1/id/1"
releases:
label: "Nouveautés"
title: "Nos nouveautés"
url: "https://github.com/betagouv/demarches-simplifiees.fr/releases"
stats:
label: "Statistiques"
title: "Statistiques"
status_page:
label: "Disponibilité"
title: "Disponibilité"
url: "https://status.demarches-simplifiees.fr"
suivi:
label: "Suivi d'audience et vie privée"
title: "Suivi d'audience et vie privée"
webinaire:
label: "Inscription ateliers en ligne"
title: "Inscription ateliers en ligne"
url: "https://app.livestorm.co/demarches-simplifiees"

36
doc/PRIVACY-POLICY.md Normal file
View file

@ -0,0 +1,36 @@
# Privacy policy documentation
This document describes various privacy consideration that should be considered when deploying an instance of demarches-simplifiees.fr.
## Matomo and or Analytics service
In order to prevent Matomo to store personnal information, you should set it up with some additional configurations options.
### Exclude some query parameters from matomo
* how : [see the matomo doc](https://matomo.org/faq/how-to/faq_81/)
* what :
We recommend to ignore the following query parameters
```
fbclid
*token
/.*token/
*email*
```
* why : some pages use URL query parameters to transmit the user email address. To avoid these being logged by Matomo, they should be excluded from the logged parameters.
## Forms data requested by user :
Depending on your local regulations/laws, **beware** : you can't collect some data, others requires special infrastructure.
### Risky forms inputs in France :
* unless your instance is running on a HDS infrastructure, you can't collect any health data. This includes Social Security number, health records, etc. [Source : CNIL](https://www.cnil.fr/fr/quest-ce-ce-quune-donnee-de-sante)
* in France, a form can't ask for the race or religion. [Source : INSEE](https://www.insee.fr/fr/information/2108548)
## Data expirations :
Data retention **must not exceed 36 months**. Depending on your instance configuration, you should check that all records of the `procedures` table have the column `procedure_expires_when_termine_enabled` set to `true`. Also make sure the default value of `procedures.procedure_expires_when_termine_enabled` is true.
This flag ensures that processed file will be deleted when expired.

35
doc/customization.md Normal file
View file

@ -0,0 +1,35 @@
# Front end customization
Do you want to customize your instance ? Here is a step by step guide.
## Step 1. Understanding
For your information, you can overide any view in our app by replicating the
view structure from `app/views` to `app/custom_views/`.
You can also overide locales by replicating the locales structure from
`config/locales` to `config/custom_locales`.
## Step 2. Customize the views
So let's imagine you want to customize the `app/views/root/_footer.html.haml`.
Here is how to do:
```
$ mkdir app/custom_views/root
$ cp app/views/root/_footer.html.haml app/custom_views/root
```
And _voila!_ You can edit your own template. No need for env var, no need to
worry about conflicts.
## Step 3. Customize the locales
Now let's imagine you want to customize the `config/locales/links.fr.yml`.
Here is how to do:
```
$ cp config/locales/links.fr.yml config/custom_locales
```
And _voila!_ You can now edit your own locales.

View file

@ -139,11 +139,6 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
let!(:gi_1_3) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 3') } let!(:gi_1_3) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 3') }
let!(:dossier12) { create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1) } let!(:dossier12) { create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1) }
let!(:dossier_discarded) do
dossier = create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1)
dossier.discard!
dossier
end
let!(:instructeur) { create(:instructeur) } let!(:instructeur) { create(:instructeur) }
let!(:bulk_message) { BulkMessage.create(dossier_count: 2, dossier_state: "brouillon", body: "hello", sent_at: Time.zone.now, groupe_instructeurs: [gi_1_1, gi_1_3], instructeur: instructeur) } let!(:bulk_message) { BulkMessage.create(dossier_count: 2, dossier_state: "brouillon", body: "hello", sent_at: Time.zone.now, groupe_instructeurs: [gi_1_1, gi_1_3], instructeur: instructeur) }
@ -160,8 +155,6 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
end end
it { expect(response).to redirect_to(admin_procedure_groupe_instructeurs_path(procedure)) } it { expect(response).to redirect_to(admin_procedure_groupe_instructeurs_path(procedure)) }
it { expect(gi_1_1.dossiers.with_discarded.count).to be(0) }
it { expect(gi_1_2.dossiers.with_discarded.count).to be(2) }
it { expect(gi_1_2.dossiers.last.id).to be(dossier12.id) } it { expect(gi_1_2.dossiers.last.id).to be(dossier12.id) }
it { expect(dossier12.groupe_instructeur.id).to be(gi_1_2.id) } it { expect(dossier12.groupe_instructeur.id).to be(gi_1_2.id) }
it { expect(bulk_message.groupe_instructeurs).to contain_exactly(gi_1_2, gi_1_3) } it { expect(bulk_message.groupe_instructeurs).to contain_exactly(gi_1_2, gi_1_3) }

View file

@ -387,12 +387,8 @@ describe Administrateurs::ProceduresController, type: :controller do
before { subject } before { subject }
it 'discard the procedure' do
expect(procedure.reload.discarded?).to be_truthy
end
it 'deletes associated dossiers' do it 'deletes associated dossiers' do
expect(procedure.dossiers.with_discarded.count).to eq(0) expect(procedure.dossiers.count).to eq(0)
end end
it 'redirects to the procedure drafts page' do it 'redirects to the procedure drafts page' do
@ -414,7 +410,7 @@ describe Administrateurs::ProceduresController, type: :controller do
it do it do
expect(procedure.reload.close?).to be_truthy expect(procedure.reload.close?).to be_truthy
expect(procedure.discarded?).to be_truthy expect(procedure.discarded?).to be_truthy
expect(dossier.reload.kept?).to be_truthy expect(dossier.reload.visible_by_administration?).to be_falsy
end end
end end
@ -441,7 +437,7 @@ describe Administrateurs::ProceduresController, type: :controller do
it do it do
expect(procedure.reload.discarded?).to be_truthy expect(procedure.reload.discarded?).to be_truthy
expect(dossier.reload.kept?).to be_truthy expect(dossier.reload.visible_by_administration?).to be_falsy
end end
end end

View file

@ -778,8 +778,8 @@ describe Instructeurs::DossiersController, type: :controller do
expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0)
end end
it 'discard the dossier' do it 'is not visible by administration' do
expect(dossier.reload.hidden_at).not_to eq(nil) expect(dossier.reload.visible_by_administration?).to be_falsy
end end
end end
@ -789,9 +789,9 @@ describe Instructeurs::DossiersController, type: :controller do
subject subject
end end
it 'does not deletes previous logs and does not add a suppression log' do it 'does not deletes previous logs and adds a suppression log' do
expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(2) expect(DossierOperationLog.where(dossier_id: dossier.id).count).to eq(3)
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).not_to eq('supprimer') expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer')
end end
it 'add a record into deleted_dossiers table' do it 'add a record into deleted_dossiers table' do
@ -845,8 +845,8 @@ describe Instructeurs::DossiersController, type: :controller do
describe '#restore' do describe '#restore' do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) } let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) }
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let!(:procedure) { create(:procedure, :published, :for_individual, instructeurs: [instructeur]) }
let!(:dossier) { create(:dossier, state: 'accepte', procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first, hidden_by_administration_at: 1.hour.ago) } let!(:dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first, hidden_by_administration_at: 1.hour.ago) }
before do before do
sign_in(instructeur.user) sign_in(instructeur.user)

View file

@ -76,7 +76,7 @@ describe Instructeurs::ProceduresController, type: :controller do
end end
context "with dossiers" do context "with dossiers" do
let(:procedure) { create(:procedure, :published) } let(:procedure) { create(:procedure, :published, :expirable) }
let(:dossier) { create(:dossier, state: state, procedure: procedure) } let(:dossier) { create(:dossier, state: state, procedure: procedure) }
before do before do
@ -93,37 +93,54 @@ describe Instructeurs::ProceduresController, type: :controller do
it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(nil) } it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) } it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(nil) } it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_expirant_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:all_dossiers_counts)['à suivre']).to eq(0) } it { expect(assigns(:all_dossiers_counts)['à suivre']).to eq(0) }
it { expect(assigns(:all_dossiers_counts)['suivis']).to eq(0) } it { expect(assigns(:all_dossiers_counts)['suivis']).to eq(0) }
it { expect(assigns(:all_dossiers_counts)['traités']).to eq(0) } it { expect(assigns(:all_dossiers_counts)['traités']).to eq(0) }
it { expect(assigns(:all_dossiers_counts)['dossiers']).to eq(0) } it { expect(assigns(:all_dossiers_counts)['dossiers']).to eq(0) }
it { expect(assigns(:all_dossiers_counts)['archivés']).to eq(0) } it { expect(assigns(:all_dossiers_counts)['archivés']).to eq(0) }
it { expect(assigns(:all_dossiers_counts)['expirant']).to eq(0) }
end end
context "with not draft state on multiple procedures" do context "with not draft state on multiple procedures" do
let(:procedure2) { create(:procedure, :published) } let(:procedure2) { create(:procedure, :published, :expirable) }
let(:state) { Dossier.states.fetch(:en_construction) } let(:state) { Dossier.states.fetch(:en_construction) }
before do before do
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction))
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), hidden_by_user_at: 1.hour.ago) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), hidden_by_user_at: 1.hour.ago)
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction))
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true)
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true,
hidden_by_administration_at: 1.day.ago)
instructeur.groupe_instructeurs << procedure2.defaut_groupe_instructeur instructeur.groupe_instructeurs << procedure2.defaut_groupe_instructeur
create(:dossier, :followed, procedure: procedure2, state: Dossier.states.fetch(:en_construction)) create(:dossier, :followed, procedure: procedure2, state: Dossier.states.fetch(:en_construction))
create(:dossier, procedure: procedure2, state: Dossier.states.fetch(:accepte)) create(:dossier, procedure: procedure2, state: Dossier.states.fetch(:accepte))
instructeur.followed_dossiers << create(:dossier, procedure: procedure2, state: Dossier.states.fetch(:en_instruction)) instructeur.followed_dossiers << create(:dossier, procedure: procedure2, state: Dossier.states.fetch(:en_instruction))
create(:dossier, procedure: procedure,
state: Dossier.states.fetch(:sans_suite),
processed_at: 8.months.ago) # counted as expirable
create(:dossier, procedure: procedure,
state: Dossier.states.fetch(:sans_suite),
processed_at: 8.months.ago,
hidden_by_administration_at: 1.day.ago) # not counted as expirable since its removed by instructeur
create(:dossier, procedure: procedure,
state: Dossier.states.fetch(:sans_suite),
processed_at: 8.months.ago,
hidden_by_user_at: 1.day.ago) # counted as expirable because even if user remove it, instructeur see it
subject subject
end end
it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(3) } it { expect(assigns(:dossiers_count_per_procedure)[procedure.id]).to eq(5) }
it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(3) } it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure.id]).to eq(3) }
it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) } it { expect(assigns(:followed_dossiers_count_per_procedure)[procedure.id]).to eq(nil) }
it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(1) } it { expect(assigns(:dossiers_archived_count_per_procedure)[procedure.id]).to eq(1) }
it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(nil) } it { expect(assigns(:dossiers_termines_count_per_procedure)[procedure.id]).to eq(2) }
it { expect(assigns(:dossiers_expirant_count_per_procedure)[procedure.id]).to eq(2) }
it { expect(assigns(:dossiers_count_per_procedure)[procedure2.id]).to eq(3) } it { expect(assigns(:dossiers_count_per_procedure)[procedure2.id]).to eq(3) }
it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure2.id]).to eq(nil) } it { expect(assigns(:dossiers_a_suivre_count_per_procedure)[procedure2.id]).to eq(nil) }
@ -133,9 +150,10 @@ describe Instructeurs::ProceduresController, type: :controller do
it { expect(assigns(:all_dossiers_counts)['à suivre']).to eq(3 + 0) } it { expect(assigns(:all_dossiers_counts)['à suivre']).to eq(3 + 0) }
it { expect(assigns(:all_dossiers_counts)['suivis']).to eq(0 + 1) } it { expect(assigns(:all_dossiers_counts)['suivis']).to eq(0 + 1) }
it { expect(assigns(:all_dossiers_counts)['traités']).to eq(0 + 1) } it { expect(assigns(:all_dossiers_counts)['traités']).to eq(2 + 1) }
it { expect(assigns(:all_dossiers_counts)['dossiers']).to eq(3 + 3) } it { expect(assigns(:all_dossiers_counts)['dossiers']).to eq(5 + 3) }
it { expect(assigns(:all_dossiers_counts)['archivés']).to eq(1 + 0) } it { expect(assigns(:all_dossiers_counts)['archivés']).to eq(1 + 0) }
it { expect(assigns(:all_dossiers_counts)['expirant']).to eq(2 + 0) }
end end
end end
@ -205,7 +223,7 @@ describe Instructeurs::ProceduresController, type: :controller do
describe "#show" do describe "#show" do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let!(:procedure) { create(:procedure, instructeurs: [instructeur]) } let!(:procedure) { create(:procedure, :expirable, instructeurs: [instructeur]) }
let!(:gi_2) { procedure.groupe_instructeurs.create(label: '2') } let!(:gi_2) { procedure.groupe_instructeurs.create(label: '2') }
let!(:gi_3) { procedure.groupe_instructeurs.create(label: '3') } let!(:gi_3) { procedure.groupe_instructeurs.create(label: '3') }
let(:statut) { nil } let(:statut) { nil }
@ -323,6 +341,7 @@ describe Instructeurs::ProceduresController, type: :controller do
context 'with an archived dossier' do context 'with an archived dossier' do
let!(:archived_dossier) { create(:dossier, :en_instruction, procedure: procedure, archived: true) } let!(:archived_dossier) { create(:dossier, :en_instruction, procedure: procedure, archived: true) }
let!(:archived_dossier_deleted) { create(:dossier, :en_instruction, procedure: procedure, archived: true, hidden_by_administration_at: 2.days.ago) }
before { subject } before { subject }
@ -342,6 +361,16 @@ describe Instructeurs::ProceduresController, type: :controller do
end end
end end
context 'with an expirants dossier' do
let!(:expiring_dossier_termine_deleted) { create(:dossier, :accepte, procedure: procedure, processed_at: 175.days.ago, hidden_by_administration_at: 2.days.ago) }
let!(:expiring_dossier_termine) { create(:dossier, :accepte, procedure: procedure, processed_at: 175.days.ago) }
let!(:expiring_dossier_en_construction) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 175.days.ago) }
before { subject }
it { expect(assigns(:expirant_dossiers)).to match_array([expiring_dossier_termine, expiring_dossier_en_construction]) }
end
describe 'statut' do describe 'statut' do
let!(:a_suivre__dossier) { Timecop.freeze(1.day.ago) { create(:dossier, :en_instruction, procedure: procedure) } } let!(:a_suivre__dossier) { Timecop.freeze(1.day.ago) { create(:dossier, :en_instruction, procedure: procedure) } }
let!(:new_followed_dossier) { Timecop.freeze(2.days.ago) { create(:dossier, :en_instruction, procedure: procedure) } } let!(:new_followed_dossier) { Timecop.freeze(2.days.ago) { create(:dossier, :en_instruction, procedure: procedure) } }

View file

@ -1069,15 +1069,6 @@ describe Users::DossiersController, type: :controller do
it_behaves_like "the dossier can not be deleted" it_behaves_like "the dossier can not be deleted"
it { is_expected.to redirect_to(root_path) } it { is_expected.to redirect_to(root_path) }
end end
context 'when the dossier is already deleted by instructeur' do
let!(:dossier) { create(:dossier, :with_individual, state: :accepte, en_construction_at: Time.zone.yesterday.beginning_of_day.utc, user: user, autorisation_donnees: true, hidden_by_administration_at: Time.zone.now.beginning_of_day.utc) }
before { subject }
it 'discard the dossier' do
expect(dossier.reload.hidden_at).to be_present
end
end
end end
describe '#restore' do describe '#restore' do

View file

@ -6,5 +6,17 @@ FactoryBot.define do
deleted_at { Time.zone.now } deleted_at { Time.zone.now }
association :procedure, :published association :procedure, :published
transient do
dossier { nil }
end
after(:build) do |deleted_dossier, evaluator|
if evaluator.dossier
deleted_dossier.dossier_id = evaluator.dossier.id
deleted_dossier.state = evaluator.dossier.state
deleted_dossier.procedure = evaluator.dossier.procedure
end
end
end end
end end

View file

@ -81,7 +81,9 @@ FactoryBot.define do
trait :with_logo do trait :with_logo do
logo { Rack::Test::UploadedFile.new('spec/fixtures/files/logo_test_procedure.png', 'image/png') } logo { Rack::Test::UploadedFile.new('spec/fixtures/files/logo_test_procedure.png', 'image/png') }
end end
trait :expirable do
procedure_expires_when_termine_enabled { true }
end
trait :with_path do trait :with_path do
path { generate(:published_path) } path { generate(:published_path) }
end end

View file

@ -7,7 +7,7 @@ RSpec.describe Cron::DiscardedDossiersDeletionJob, type: :job do
# hack to add passer_en_instruction and supprimer to dossier.dossier_operation_logs # hack to add passer_en_instruction and supprimer to dossier.dossier_operation_logs
dossier.send(:log_dossier_operation, instructeur, :passer_en_instruction, dossier) dossier.send(:log_dossier_operation, instructeur, :passer_en_instruction, dossier)
dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier) dossier.send(:log_dossier_operation, instructeur, :supprimer, dossier)
dossier.update_column(:hidden_at, hidden_at) dossier.update_columns(hidden_by_user_at: hidden_at, hidden_by_administration_at: hidden_at)
dossier.update_column(:hidden_by_reason, "user_request") dossier.update_column(:hidden_by_reason, "user_request")
Cron::DiscardedDossiersDeletionJob.perform_now Cron::DiscardedDossiersDeletionJob.perform_now

View file

@ -2,7 +2,7 @@ RSpec.describe AvisMailer, type: :mailer do
describe '.avis_invitation' do describe '.avis_invitation' do
let(:claimant) { create(:instructeur) } let(:claimant) { create(:instructeur) }
let(:expert) { create(:expert) } let(:expert) { create(:expert) }
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, :en_construction) }
let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: dossier.procedure) } let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: dossier.procedure) }
let(:avis) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, introduction: 'intro') } let(:avis) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, introduction: 'intro') }
@ -18,7 +18,7 @@ RSpec.describe AvisMailer, type: :mailer do
end end
context 'when the dossier has been deleted before the avis was sent' do context 'when the dossier has been deleted before the avis was sent' do
before { dossier.update(hidden_at: Time.zone.now) } before { dossier.update(hidden_by_user_at: 1.hour.ago) }
it 'doesnt send the email' do it 'doesnt send the email' do
expect(subject.body).to be_blank expect(subject.body).to be_blank

View file

@ -117,9 +117,10 @@ RSpec.describe DossierMailer, type: :mailer do
end end
describe '.notify_automatic_deletion_to_user' do describe '.notify_automatic_deletion_to_user' do
let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
describe 'en_construction' do describe 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) }
subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) } subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) }
@ -132,7 +133,6 @@ RSpec.describe DossierMailer, type: :mailer do
describe 'termine' do describe 'termine' do
let(:dossier) { create(:dossier, :accepte) } let(:dossier) { create(:dossier, :accepte) }
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) }
subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) } subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) }
@ -145,8 +145,8 @@ RSpec.describe DossierMailer, type: :mailer do
end end
describe '.notify_automatic_deletion_to_administration' do describe '.notify_automatic_deletion_to_administration' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier, :en_construction) }
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) } let(:deleted_dossier) { create(:deleted_dossier, dossier: dossier, reason: :expired) }
subject { described_class.notify_automatic_deletion_to_administration([deleted_dossier], dossier.user.email) } subject { described_class.notify_automatic_deletion_to_administration([deleted_dossier], dossier.user.email) }

View file

@ -6,7 +6,6 @@ describe Dossier do
describe 'scopes' do describe 'scopes' do
describe '.default_scope' do describe '.default_scope' do
let!(:dossier) { create(:dossier) } let!(:dossier) { create(:dossier) }
let!(:discarded_dossier) { create(:dossier, :discarded) }
subject { Dossier.all } subject { Dossier.all }
@ -791,7 +790,7 @@ describe Dossier do
end end
end end
describe "#discard_and_keep_track!" do describe "#hide_and_keep_track!" do
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :en_construction) }
let(:user) { dossier.user } let(:user) { dossier.user }
let(:last_operation) { dossier.dossier_operation_logs.last } let(:last_operation) { dossier.dossier_operation_logs.last }
@ -801,17 +800,17 @@ 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!(user, reason) } subject! { dossier.hide_and_keep_track!(user, reason) }
context 'brouillon' do context 'brouillon' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
it 'hides the dossier' do it 'hide the dossier' do
expect(dossier.discarded?).to be_truthy expect(dossier.reload.hidden_by_user_at).to be_present
end end
it 'do not records the operation in the log' do it 'does not records operation in the log' do
expect(last_operation).to be_nil expect(dossier.reload.dossier_operation_logs.last).to eq(nil)
end end
end end
@ -842,30 +841,16 @@ describe Dossier do
end end
end end
context 'with reason: manager_request' do
let(:user) { dossier.procedure.administrateurs.first }
let(:reason) { :manager_request }
it 'hides the dossier' do
expect(dossier.discarded?).to be_truthy
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
end
context 'with reason: user_removed' do context 'with reason: user_removed' do
let(:reason) { :user_removed } let(:reason) { :user_removed }
it 'does not discard the dossier' do
expect(dossier.discarded?).to be_falsy
end
it 'hide the dossier' do it 'hide the dossier' do
expect(dossier.hidden_by_user_at).to be_present expect(dossier.hidden_by_user_at).to be_present
end end
it 'write the good reason to hidden_by_reason' do
expect(dossier.hidden_by_reason).to eq("user_removed")
end
end end
end end
@ -876,10 +861,6 @@ describe Dossier do
it 'affect the right deletion reason to the dossier' do it 'affect the right deletion reason to the dossier' do
expect(dossier.hidden_by_reason).to eq("user_request") expect(dossier.hidden_by_reason).to eq("user_request")
end end
it 'discard the dossier' do
expect(dossier.discarded?).to be_truthy
end
end end
end end
@ -1308,7 +1289,7 @@ describe Dossier do
end end
end end
describe 'discarded_brouillon_expired and discarded_en_construction_expired' do describe 'brouillon_expired and en_construction_expired' do
let(:administrateur) { create(:administrateur) } let(:administrateur) { create(:administrateur) }
let(:user) { administrateur.user } let(:user) { administrateur.user }
let(:reason) { DeletedDossier.reasons.fetch(:user_request) } let(:reason) { DeletedDossier.reasons.fetch(:user_request) }
@ -1316,24 +1297,25 @@ describe Dossier do
before do before do
create(:dossier, user: user) create(:dossier, user: user)
create(:dossier, :en_construction, user: user) create(:dossier, :en_construction, user: user)
create(:dossier, user: user).discard_and_keep_track!(user, reason) create(:dossier, user: user).hide_and_keep_track!(user, reason)
create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason) create(:dossier, :en_construction, user: user).hide_and_keep_track!(user, reason)
Timecop.travel(2.months.ago) do Timecop.travel(2.months.ago) do
create(:dossier, user: user).discard_and_keep_track!(user, reason) create(:dossier, user: user).hide_and_keep_track!(user, reason)
create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason) create(:dossier, :en_construction, user: user).hide_and_keep_track!(user, reason)
create(:dossier, user: user).procedure.discard_and_keep_track!(administrateur) create(:dossier, user: user).procedure.discard_and_keep_track!(administrateur)
create(:dossier, :en_construction, user: user).procedure.discard_and_keep_track!(administrateur) create(:dossier, :en_construction, user: user).procedure.discard_and_keep_track!(administrateur)
end end
Timecop.travel(1.week.ago) do Timecop.travel(1.week.ago) do
create(:dossier, user: user).discard_and_keep_track!(user, reason) create(:dossier, user: user).hide_and_keep_track!(user, reason)
create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason) create(:dossier, :en_construction, user: user).hide_and_keep_track!(user, reason)
end end
end end
it { expect(Dossier.discarded_brouillon_expired.count).to eq(2) } it { expect(Dossier.en_brouillon_expired_to_delete.count).to eq(2) }
it { expect(Dossier.discarded_en_construction_expired.count).to eq(0) } it { expect(Dossier.en_construction_expired_to_delete.count).to eq(2) }
end end
describe "discarded procedure dossier should be able to access it's procedure" do describe "discarded procedure dossier should be able to access it's procedure" do
@ -1529,42 +1511,6 @@ describe Dossier do
expect(dossier.destroy).to be_truthy expect(dossier.destroy).to be_truthy
expect(transfer.reload).not_to be_nil expect(transfer.reload).not_to be_nil
end end
context 'discarded' do
context 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) }
before do
create(:avis, dossier: dossier)
Timecop.travel(2.weeks.ago) do
dossier.discard!
end
dossier.reload
end
it "can destroy dossier with avis" do
Avis.discarded_en_construction_expired.destroy_all
expect(dossier.destroy).to be_truthy
end
end
context 'termine' do
let(:dossier) { create(:dossier, :accepte) }
before do
create(:avis, dossier: dossier)
Timecop.travel(2.weeks.ago) do
dossier.discard!
end
dossier.reload
end
it "can destroy dossier with avis" do
Avis.discarded_termine_expired.destroy_all
expect(dossier.destroy).to be_truthy
end
end
end
end end
describe "#spreadsheet_columns" do describe "#spreadsheet_columns" do

View file

@ -40,8 +40,8 @@ RSpec.describe DossierTransfer, type: :model do
it { expect(DossierTransfer.with_dossiers.count).to eq(1) } it { expect(DossierTransfer.with_dossiers.count).to eq(1) }
context "when dossier discarded" do context "when dossier deleted" do
before { dossier.discard! } before { dossier.update(hidden_by_user_at: 1.hour.ago) }
it { expect(DossierTransfer.with_dossiers.count).to eq(0) } it { expect(DossierTransfer.with_dossiers.count).to eq(0) }
end end
@ -51,10 +51,10 @@ RSpec.describe DossierTransfer, type: :model do
describe '#destroy_and_nullify' do describe '#destroy_and_nullify' do
let(:transfer) { create(:dossier_transfer) } let(:transfer) { create(:dossier_transfer) }
let(:dossier) { create(:dossier, user: user, transfer: transfer) } let(:dossier) { create(:dossier, user: user, transfer: transfer) }
let(:discarded_dossier) { create(:dossier, user: user, transfer: dossier.transfer) } let(:deleted_dossier) { create(:dossier, user: user, transfer: dossier.transfer) }
before do before do
discarded_dossier.discard! deleted_dossier.update(hidden_by_user_at: 1.hour.ago)
end end
it 'nullify transfer relationship on dossier' do it 'nullify transfer relationship on dossier' do
@ -67,10 +67,10 @@ RSpec.describe DossierTransfer, type: :model do
describe '#destroy_stale' do describe '#destroy_stale' do
let(:transfer) { create(:dossier_transfer, created_at: 1.month.ago) } let(:transfer) { create(:dossier_transfer, created_at: 1.month.ago) }
let(:dossier) { create(:dossier, user: user, transfer: transfer) } let(:dossier) { create(:dossier, user: user, transfer: transfer) }
let(:discarded_dossier) { create(:dossier, user: user, transfer: dossier.transfer) } let(:deleted_dossier) { create(:dossier, user: user, transfer: dossier.transfer) }
before do before do
discarded_dossier.discard! deleted_dossier.update(hidden_by_user_at: 1.hour.ago)
end end
it 'nullify the transfer on discarded dossier' do it 'nullify the transfer on discarded dossier' do

View file

@ -721,6 +721,7 @@ describe Instructeur, type: :model do
end end
context 'with an expirants dossier' do context 'with an expirants dossier' do
let!(:expiring_dossier_termine_deleted) { create(:dossier, :accepte, procedure: procedure, processed_at: 175.days.ago, hidden_by_administration_at: 2.days.ago) }
let!(:expiring_dossier_termine) { create(:dossier, :accepte, procedure: procedure, processed_at: 175.days.ago) } let!(:expiring_dossier_termine) { create(:dossier, :accepte, procedure: procedure, processed_at: 175.days.ago) }
let!(:expiring_dossier_en_construction) { create(:dossier, :en_construction, en_construction_at: 175.days.ago, procedure: procedure) } let!(:expiring_dossier_en_construction) { create(:dossier, :en_construction, en_construction_at: 175.days.ago, procedure: procedure) }
before { subject } before { subject }

View file

@ -56,18 +56,18 @@ describe Invite do
end end
describe "#default_scope" do describe "#default_scope" do
let(:dossier) { create(:dossier, hidden_at: hidden_at) } let!(:dossier) { create(:dossier, hidden_by_user_at: hidden_by_user_at) }
let!(:invite) { create(:invite, email: "email@totor.com", dossier: dossier) } let!(:invite) { create(:invite, email: "email@totor.com", dossier: dossier) }
context "when dossier is not discarded" do context "when dossier is not discarded" do
let(:hidden_at) { nil } let(:hidden_by_user_at) { nil }
it { expect(Invite.count).to eq(1) } it { expect(Invite.count).to eq(1) }
it { expect(Invite.all).to include(invite) } it { expect(Invite.all).to include(invite) }
end end
context "when dossier is discarded" do context "when dossier is discarded" do
let(:hidden_at) { 1.day.ago } let(:hidden_by_user_at) { 1.hour.ago }
it { expect(Invite.count).to eq(0) } it { expect(Invite.count).to eq(0) }
end end

View file

@ -310,21 +310,21 @@ describe User, type: :model do
it "keep track of dossiers and delete user" do it "keep track of dossiers and delete user" do
user.delete_and_keep_track_dossiers(super_admin) user.delete_and_keep_track_dossiers(super_admin)
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_nil expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
expect(User.find_by(id: user.id)).to be_nil expect(User.find_by(id: user.id)).to be_nil
end end
end end
context 'with a discarded dossier' do context 'with a deleted dossier' do
let(:dossier_to_discard) { create(:dossier, :en_construction, user: user) } let(:dossier_to_delete) { create(:dossier, :en_construction, user: user) }
let!(:dossier_from_another_user) { create(:dossier, :en_construction, user: create(:user)) } let!(:dossier_from_another_user) { create(:dossier, :en_construction, user: create(:user)) }
it "keep track of dossiers and delete user" do it "keep track of dossiers and delete user" do
dossier_to_discard.discard_and_keep_track!(super_admin, :user_request) dossier_to_delete.hide_and_keep_track!(user, :user_request)
user.delete_and_keep_track_dossiers(super_admin) user.delete_and_keep_track_dossiers(super_admin)
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_nil expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
expect(User.find_by(id: user.id)).to be_nil expect(User.find_by(id: user.id)).to be_nil
@ -426,14 +426,14 @@ describe User, type: :model do
context 'and the old account has some stuff' do context 'and the old account has some stuff' do
let!(:dossier) { create(:dossier, user: old_user) } let!(:dossier) { create(:dossier, user: old_user) }
let!(:hidden_dossier) { create(:dossier, user: old_user, hidden_at: Time.zone.now) } let!(:hidden_dossier) { create(:dossier, user: old_user, hidden_by_user_at: 1.hour.ago) }
let!(:invite) { create(:invite, user: old_user) } let!(:invite) { create(:invite, user: old_user) }
let!(:merge_log) { MergeLog.create(user: old_user, from_user_id: 1, from_user_email: 'a') } let!(:merge_log) { MergeLog.create(user: old_user, from_user_id: 1, from_user_email: 'a') }
it 'transfers the dossier' do it 'transfers the dossier' do
subject subject
expect(targeted_user.dossiers.with_discarded).to contain_exactly(dossier, hidden_dossier) expect(targeted_user.dossiers).to contain_exactly(dossier, hidden_dossier)
expect(targeted_user.invites).to match([invite]) expect(targeted_user.invites).to match([invite])
expect(targeted_user.merge_logs.first).to eq(merge_log) expect(targeted_user.merge_logs.first).to eq(merge_log)

View file

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