Merge pull request #6847 from betagouv/main

2022-02-01-01
This commit is contained in:
Kara Diaby 2022-02-01 13:01:38 +01:00 committed by GitHub
commit ce3809a998
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 173 additions and 66 deletions

View file

@ -18,6 +18,7 @@ module Instructeurs
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count @dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_expirant_count_per_procedure = dossiers.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count @dossiers_expirant_count_per_procedure = dossiers.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count
@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
@ -35,7 +36,8 @@ module Instructeurs
'traités' => @dossiers_termines_count_per_procedure.sum { |_, v| v }, 'traités' => @dossiers_termines_count_per_procedure.sum { |_, v| v },
'dossiers' => @dossiers_count_per_procedure.sum { |_, v| v }, 'dossiers' => @dossiers_count_per_procedure.sum { |_, v| v },
'expirant' => @dossiers_expirant_count_per_procedure.sum { |_, v| v }, 'expirant' => @dossiers_expirant_count_per_procedure.sum { |_, v| v },
'archivés' => @dossiers_archived_count_per_procedure.sum { |_, v| v } 'archivés' => @dossiers_archived_count_per_procedure.sum { |_, v| v },
'supprimes_recemment' => @dossiers_supprimes_recemment_count_per_procedure.sum { |_, v| v }
} }
@procedure_ids_en_cours_with_notifications = current_instructeur.procedure_ids_with_notifications(:en_cours) @procedure_ids_en_cours_with_notifications = current_instructeur.procedure_ids_with_notifications(:en_cours)
@ -51,9 +53,9 @@ module Instructeurs
@current_filters = current_filters @current_filters = current_filters
@displayed_fields_options, @displayed_fields_selected = procedure_presentation.displayed_fields_for_select @displayed_fields_options, @displayed_fields_selected = procedure_presentation.displayed_fields_for_select
@a_suivre_count, @suivis_count, @traites_count, @tous_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', 'archives', 'expirant') .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'supprimes_recemment', 'archives', 'expirant')
dossiers_visibles = Dossier dossiers_visibles = Dossier
.where(groupe_instructeur_id: groupe_instructeur_ids) .where(groupe_instructeur_id: groupe_instructeur_ids)
@ -72,6 +74,7 @@ module Instructeurs
@termines_dossiers = dossiers_visibles.termine.visible_by_administration @termines_dossiers = dossiers_visibles.termine.visible_by_administration
@all_state_dossiers = dossiers_visibles.all_state.visible_by_administration @all_state_dossiers = dossiers_visibles.all_state.visible_by_administration
@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
@ -88,6 +91,9 @@ module Instructeurs
when 'tous' when 'tous'
dossiers_count = @tous_count dossiers_count = @tous_count
@all_state_dossiers @all_state_dossiers
when 'supprimes_recemment'
dossiers_count = @supprimes_recemment_count
@supprimes_recemment_dossiers
when 'archives' when 'archives'
dossiers_count = @archives_count dossiers_count = @archives_count
@archived_dossiers @archived_dossiers
@ -282,6 +288,13 @@ module Instructeurs
) )
end end
def restore
dossier = current_instructeur.dossiers.find(params[:dossier_id])
dossier.restore(current_instructeur)
flash.notice = t('instructeurs.dossiers.restore')
redirect_to instructeur_procedure_path(procedure)
end
private private
def assign_to_params def assign_to_params

View file

@ -815,6 +815,10 @@ class Dossier < ApplicationRecord
restore_dossier_and_destroy_deleted_dossier(author) restore_dossier_and_destroy_deleted_dossier(author)
end end
end end
elsif author_is_administration(author) && hidden_by_administration?
transaction do
update(hidden_by_administration_at: nil)
end
end end
end end

View file

@ -236,6 +236,7 @@ class Instructeur < ApplicationRecord
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction')) AS tous, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction')) 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 archived) AS archives, COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where COUNT(DISTINCT dossiers.id) FILTER (where
procedures.procedure_expires_when_termine_enabled procedures.procedure_expires_when_termine_enabled
@ -256,7 +257,6 @@ class Instructeur < ApplicationRecord
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"."hidden_at" IS NULL
AND "dossiers"."hidden_by_administration_at" IS NULL
AND "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)
EOF EOF

View file

@ -245,9 +245,13 @@ class Procedure < ApplicationRecord
validates :description, presence: true, allow_blank: false, allow_nil: false validates :description, presence: true, allow_blank: false, allow_nil: false
validates :administrateurs, presence: true validates :administrateurs, presence: true
validates :lien_site_web, presence: true, if: :publiee? validates :lien_site_web, presence: true, if: :publiee?
validates :draft_revision, validates :draft_types_de_champ,
'revisions/no_empty_repetition': true, 'types_de_champ/no_empty_repetition': true,
'revisions/no_empty_drop_down': true, 'types_de_champ/no_empty_drop_down': true,
if: :validate_for_publication?
validates :draft_types_de_champ_private,
'types_de_champ/no_empty_repetition': true,
'types_de_champ/no_empty_drop_down': true,
if: :validate_for_publication? if: :validate_for_publication?
validate :check_juridique validate :check_juridique
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }

View file

@ -1,10 +1,6 @@
class Revisions::NoEmptyDropDownValidator < ActiveModel::EachValidator class TypesDeChamp::NoEmptyDropDownValidator < ActiveModel::EachValidator
def validate_each(procedure, attribute, revision) def validate_each(procedure, attribute, types_de_champ)
return if revision.nil? types_de_champ.filter(&:drop_down_list?).each do |drop_down|
tdcs = revision.types_de_champ + revision.types_de_champ_private
drop_downs = tdcs.filter(&:drop_down_list?)
drop_downs.each do |drop_down|
validate_drop_down_not_empty(procedure, attribute, drop_down) validate_drop_down_not_empty(procedure, attribute, drop_down)
end end
end end

View file

@ -1,10 +1,6 @@
class Revisions::NoEmptyRepetitionValidator < ActiveModel::EachValidator class TypesDeChamp::NoEmptyRepetitionValidator < ActiveModel::EachValidator
def validate_each(procedure, attribute, revision) def validate_each(procedure, attribute, types_de_champ)
return if revision.nil? types_de_champ.filter(&:repetition?).each do |repetition|
revision_tdcs = revision.types_de_champ + revision.types_de_champ_private
repetitions = revision_tdcs.filter(&:repetition?)
repetitions.each do |repetition|
validate_repetition_not_empty(procedure, attribute, repetition) validate_repetition_not_empty(procedure, attribute, repetition)
end end
end end

View file

@ -53,7 +53,7 @@
- if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions) - if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions)
- @procedure.validate(:publication) - @procedure.validate(:publication)
- error_messages = @procedure.errors.messages_for(:draft_revision).to_sentence - error_messages = @procedure.errors.messages_for(:draft_types_de_champ).to_sentence
= link_to champs_admin_procedure_path(@procedure), class: 'card-admin', title: error_messages do = link_to champs_admin_procedure_path(@procedure), class: 'card-admin', title: error_messages do
- if @procedure.draft_types_de_champ.count == 0 - if @procedure.draft_types_de_champ.count == 0
@ -171,11 +171,18 @@
%p.button Modifier %p.button Modifier
- if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions) - if !@procedure.locked? || @procedure.feature_enabled?(:procedure_revisions)
= link_to annotations_admin_procedure_path(@procedure), class: 'card-admin' do - @procedure.validate(:publication)
- error_messages = @procedure.errors.messages_for(:draft_types_de_champ_private).to_sentence
= link_to annotations_admin_procedure_path(@procedure), class: 'card-admin', title: error_messages do
- if @procedure.draft_types_de_champ_private.present? - if @procedure.draft_types_de_champ_private.present?
%div %div
%span.icon.accept %span.icon.accept
%p.card-admin-status-accept Validé %p.card-admin-status-accept Validé
- elsif error_messages.present?
%div
%span.icon.refuse
%p.card-admin-status-error À modifier
- else - else
%div %div
%span.icon.clock %span.icon.clock

View file

@ -28,7 +28,8 @@
state: dossier.state, state: dossier.state,
archived: dossier.archived, archived: dossier.archived,
dossier_is_followed: current_instructeur&.follow?(dossier), dossier_is_followed: current_instructeur&.follow?(dossier),
close_to_expiration: dossier.close_to_expiration? } close_to_expiration: dossier.close_to_expiration?,
supprimes_recemment: dossier.hidden_by_administration? }
.state-button .state-button

View file

@ -21,7 +21,13 @@
%span.icon.archive %span.icon.archive
.dropdown-description .dropdown-description
Archiver le dossier Archiver le dossier
- if supprimes_recemment
%li.danger
= link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment restaurer le dossier #{dossier_id}" } do
%span.icon.reply
.dropdown-description
= t('views.instructeurs.dossiers.restore')
- else
%li.danger %li.danger
= link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do = link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do
%span.icon.delete %span.icon.delete

View file

@ -45,6 +45,15 @@
.stats-legend .stats-legend
= t('pluralize.case', count: dossier_count) = t('pluralize.case', count: dossier_count)
%li
%object
= link_to(instructeur_procedure_path(p, statut: 'supprimes_recemment')) do
- dossier_count = dossiers_supprimes_recemment_count_per_procedure[p.id] || 0
.stats-number
= number_with_html_delimiter(dossier_count)
.stats-legend
= t('pluralize.dossiers_supprimes_recemment', count: dossier_count)
- if p.procedure_expires_when_termine_enabled - if p.procedure_expires_when_termine_enabled
%li %li
%object %object

View file

@ -22,6 +22,11 @@
active: statut == 'tous', active: statut == 'tous',
badge: number_with_html_delimiter(tous_count)) badge: number_with_html_delimiter(tous_count))
= tab_item(t('pluralize.dossiers_supprimes_recemment', count: supprimes_recemment_count),
instructeur_procedure_path(procedure, statut: 'supprimes_recemment'),
active: statut == 'supprimes_recemment',
badge: number_with_html_delimiter(supprimes_recemment_count))
- if procedure.procedure_expires_when_termine_enabled - if procedure.procedure_expires_when_termine_enabled
= tab_item(t('pluralize.dossiers_close_to_expiration', count: expirant_count), = tab_item(t('pluralize.dossiers_close_to_expiration', count: expirant_count),
instructeur_procedure_path(procedure, statut: 'expirant'), instructeur_procedure_path(procedure, statut: 'expirant'),

View file

@ -13,6 +13,7 @@
dossiers_archived_count_per_procedure: @dossiers_archived_count_per_procedure, dossiers_archived_count_per_procedure: @dossiers_archived_count_per_procedure,
dossiers_termines_count_per_procedure: @dossiers_termines_count_per_procedure, dossiers_termines_count_per_procedure: @dossiers_termines_count_per_procedure,
dossiers_expirant_count_per_procedure: @dossiers_expirant_count_per_procedure, dossiers_expirant_count_per_procedure: @dossiers_expirant_count_per_procedure,
dossiers_supprimes_recemment_count_per_procedure: @dossiers_supprimes_recemment_count_per_procedure,
followed_dossiers_count_per_procedure: @followed_dossiers_count_per_procedure, followed_dossiers_count_per_procedure: @followed_dossiers_count_per_procedure,
procedure_ids_en_cours_with_notifications: @procedure_ids_en_cours_with_notifications, procedure_ids_en_cours_with_notifications: @procedure_ids_en_cours_with_notifications,
procedure_ids_termines_with_notifications: @procedure_ids_termines_with_notifications } procedure_ids_termines_with_notifications: @procedure_ids_termines_with_notifications }

View file

@ -17,6 +17,7 @@
suivis_count: @suivis_count, suivis_count: @suivis_count,
traites_count: @traites_count, traites_count: @traites_count,
tous_count: @tous_count, tous_count: @tous_count,
supprimes_recemment_count: @supprimes_recemment_count,
archives_count: @archives_count, archives_count: @archives_count,
expirant_count: @expirant_count, expirant_count: @expirant_count,
has_en_cours_notifications: @has_en_cours_notifications, has_en_cours_notifications: @has_en_cours_notifications,
@ -31,6 +32,8 @@
%p.explication-onglet Les dossiers dans cet onglet sont terminés : ils ont été acceptés, refusés ou classés sans suite. %p.explication-onglet Les dossiers dans cet onglet sont terminés : ils ont été acceptés, refusés ou classés sans suite.
- if @statut == 'tous' - if @statut == 'tous'
%p.explication-onglet Tous les dossiers qui ont été déposés sur cette démarche, quel que soit le statut. %p.explication-onglet Tous les dossiers qui ont été déposés sur cette démarche, quel que soit le statut.
- if @statut == 'supprimes_recemment'
%p.explication-onglet Tous les dossiers terminés et supprimés par les instructeurs sur cette démarche
- if @statut == 'archives' - if @statut == 'archives'
%p.explication-onglet %p.explication-onglet
Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier. Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier.
@ -135,7 +138,8 @@
state: p.state, state: p.state,
archived: p.archived, archived: p.archived,
dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id), dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id),
close_to_expiration: @statut == 'expirant' } close_to_expiration: @statut == 'expirant',
supprimes_recemment: @statut == 'supprimes_recemment' }
= pagination = pagination
- else - else

View file

@ -25,7 +25,7 @@ as defined by the routes in the `admin/` namespace
<%= link_to "Delayed Jobs", manager_delayed_job_path, class: "navigation__link" %> <%= link_to "Delayed Jobs", manager_delayed_job_path, class: "navigation__link" %>
<%= link_to "Features", manager_flipper_path, class: "navigation__link" %> <%= link_to "Features", manager_flipper_path, class: "navigation__link" %>
<% if Rails.env.production? && ENV['SENDINBLUE_ENABLED'] == 'enabled'%> <% if ENV["SENDINBLUE_ENABLED"] == "enabled" && ENV["SAML_IDP_ENABLED"] == "enabled" %>
<%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %> <%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
<% end %> <% end %>
</nav> </nav>

View file

@ -16,11 +16,6 @@ SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93
# Secret key for One-Time-Password codes, used for 2-factors authentication # Secret key for One-Time-Password codes, used for 2-factors authentication
OTP_SECRET_KEY="" OTP_SECRET_KEY=""
# SAML IdP
# SAML_IDP_CERTIFICATE="billybop"
# SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"
# Database credentials # Database credentials
DB_DATABASE="tps_development" DB_DATABASE="tps_development"
DB_HOST="localhost" DB_HOST="localhost"
@ -41,6 +36,11 @@ FOG_OPENSTACK_URL=""
FOG_OPENSTACK_REGION="" FOG_OPENSTACK_REGION=""
DS_PROXY_URL="" DS_PROXY_URL=""
# SAML Identity provider
SAML_IDP_ENABLED="disabled"
SAML_IDP_CERTIFICATE=""
SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"
# External service: authentication through France Connect # External service: authentication through France Connect
FC_PARTICULIER_ID="" FC_PARTICULIER_ID=""
FC_PARTICULIER_SECRET="" FC_PARTICULIER_SECRET=""

View file

@ -1,7 +1,7 @@
# The certificate and secret key are not fetched from secrets.yml because there is a problem to set a secret key from a multiline env var" # The certificate and secret key are not fetched from secrets.yml because there is a problem to set a secret key from a multiline env var"
# So we fetch env var directly here # So we fetch env var directly here
if Rails.env.production? if ENV['SAML_IDP_ENABLED'] == 'enabled'
SamlIdp.config.x509_certificate = ENV.fetch("SAML_IDP_CERTIFICATE") SamlIdp.config.x509_certificate = ENV.fetch("SAML_IDP_CERTIFICATE")
SamlIdp.config.secret_key = ENV.fetch("SAML_IDP_SECRET_KEY") SamlIdp.config.secret_key = ENV.fetch("SAML_IDP_SECRET_KEY")
end end

View file

@ -140,6 +140,7 @@ en:
archived_dossier: "This file will be kept for an additional month" archived_dossier: "This file will be kept for an additional month"
delete_dossier: "Delete file" delete_dossier: "Delete file"
deleted_by_user: "File deleted by user" deleted_by_user: "File deleted by user"
restore: "Restore the file"
avis: avis:
introduction_file_explaination: "File attached to the request for advice" introduction_file_explaination: "File attached to the request for advice"
users: users:
@ -419,6 +420,7 @@ en:
dossiers: dossiers:
deleted_by_instructeur: "The folder has been deleted" deleted_by_instructeur: "The folder has been deleted"
impossible_deletion: "Unable to delete : the folder is not processed" impossible_deletion: "Unable to delete : the folder is not processed"
restore: "The folder has been restored"
france_connect: france_connect:
particulier: particulier:
password_confirmation: password_confirmation:

View file

@ -137,6 +137,7 @@ fr:
archived_dossier: "Le dossier sera conservé 1 mois supplémentaire" archived_dossier: "Le dossier sera conservé 1 mois supplémentaire"
delete_dossier: "Supprimer le dossier" delete_dossier: "Supprimer le dossier"
deleted_by_user: "Dossier supprimé par l'usager" deleted_by_user: "Dossier supprimé par l'usager"
restore: "Restaurer le dossier"
avis: avis:
introduction_file_explaination: "Fichier joint à la demande davis" introduction_file_explaination: "Fichier joint à la demande davis"
users: users:
@ -327,8 +328,8 @@ fr:
connexion: "Erreur lors de la connexion à France Connect." connexion: "Erreur lors de la connexion à France Connect."
forbidden_html: "Seul-e-s les usagers peuvent se connecter via France Connect. En tant quinstructeur ou administrateur, nous vous invitons à <a href='%{reset_link}'>réininitialiser votre mot de passe</a>." forbidden_html: "Seul-e-s les usagers peuvent se connecter via France Connect. En tant quinstructeur ou administrateur, nous vous invitons à <a href='%{reset_link}'>réininitialiser votre mot de passe</a>."
procedure_archived: "Cette démarche en ligne a été close, il nest plus possible de déposer de dossier." procedure_archived: "Cette démarche en ligne a été close, il nest plus possible de déposer de dossier."
empty_repetition: 'Le bloc répétable « %{value} » doit comporter au moins un champ' empty_repetition: '« %{value} » doit comporter au moins un champ répétable'
empty_drop_down: 'La liste de choix « %{value} » doit comporter au moins un choix sélectionnable' empty_drop_down: '« %{value} » doit comporter au moins un choix sélectionnable'
# procedure_not_draft: "Cette démarche nest maintenant plus en brouillon." # procedure_not_draft: "Cette démarche nest maintenant plus en brouillon."
cadastres_empty: cadastres_empty:
one: "Aucune parcelle cadastrale sur la zone sélectionnée" one: "Aucune parcelle cadastrale sur la zone sélectionnée"
@ -372,13 +373,13 @@ fr:
one: dossier invité one: dossier invité
other: dossiers invités other: dossiers invités
dossiers_supprimes_recemment: dossiers_supprimes_recemment:
zero: dossier supprimé recemment zero: supprimé recemment
one: dossier supprimé recemment one: supprimé recemment
other: dossiers supprimés recemment other: supprimés recemment
dossiers_supprimes_definitivement: dossiers_supprimes_definitivement:
zero: dossier supprimé définitivement zero: supprimé définitivement
one: dossier supprimé définitivement one: supprimé définitivement
other: dossiers supprimés définitivement other: supprimés définitivement
dossiers_transferes: dossiers_transferes:
zero: demande de transfert zero: demande de transfert
one: demande de transfert one: demande de transfert
@ -427,6 +428,7 @@ fr:
dossiers: dossiers:
deleted_by_instructeur: "Le dossier a bien été supprimé de votre interface" deleted_by_instructeur: "Le dossier a bien été supprimé de votre interface"
impossible_deletion: "Supression impossible : le dossier n'est pas traité" impossible_deletion: "Supression impossible : le dossier n'est pas traité"
restore: "Le dossier a bien été restauré"
administrateurs: administrateurs:
procedures: procedures:
show: show:

View file

@ -23,5 +23,7 @@ fr:
attributes: attributes:
api_particulier_token: api_particulier_token:
invalid: 'na pas le bon format' invalid: 'na pas le bon format'
draft_revision: draft_types_de_champ:
format: '%{message}' format: 'Le champ %{message}'
draft_types_de_champ_private:
format: 'Lannotation privée %{message}'

View file

@ -386,6 +386,7 @@ Rails.application.routes.draw do
post 'avis' => 'dossiers#create_avis' post 'avis' => 'dossiers#create_avis'
get 'print' => 'dossiers#print' get 'print' => 'dossiers#print'
get 'telecharger_pjs' => 'dossiers#telecharger_pjs' get 'telecharger_pjs' => 'dossiers#telecharger_pjs'
patch 'restore'
end end
end end

View file

@ -0,0 +1,10 @@
class AddSupprimesRecemmentToProcedurePresentations < ActiveRecord::Migration[6.1]
def up
ProcedurePresentation.update_all(%Q(filters = filters || '{"supprimes_recemment": []}'))
change_column_default :procedure_presentations, :filters, { "tous" => [], "suivis" => [], "traites" => [], "a-suivre" => [], "archives" => [], "supprimes_recemment" => [], "expirant": [] }
end
def down
change_column_default :procedure_presentations, :filters, { "tous" => [], "suivis" => [], "traites" => [], "a-suivre" => [], "archives" => [], "expirant": [] }
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_12_02_133139) do ActiveRecord::Schema.define(version: 2022_01_27_135056) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -569,7 +569,7 @@ ActiveRecord::Schema.define(version: 2021_12_02_133139) do
create_table "procedure_presentations", id: :serial, force: :cascade do |t| create_table "procedure_presentations", id: :serial, force: :cascade do |t|
t.integer "assign_to_id" t.integer "assign_to_id"
t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false
t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[]}, null: false t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[], "supprimes_recemment"=>[]}, null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false

View file

@ -839,4 +839,25 @@ describe Instructeurs::DossiersController, type: :controller do
end end
end end
end end
describe '#restore' do
let(:instructeur) { create(:instructeur) }
let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) }
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
let!(:dossier) { create(:dossier, state: 'accepte', procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first, hidden_by_administration_at: 1.hour.ago) }
before do
sign_in(instructeur.user)
instructeur.groupe_instructeurs << gi_p1_1
patch :restore,
params: {
procedure_id: procedure.id,
dossier_id: dossier.id
}
end
it "puts hidden_by_administration_at to nil" do
expect(dossier.reload.hidden_by_administration_at).to eq(nil)
end
end
end end

View file

@ -299,20 +299,20 @@ describe Procedure do
it_behaves_like 'duree de conservation' it_behaves_like 'duree de conservation'
end end
describe 'draft_revision' do describe 'draft_types_de_champ validations' do
let(:repetition) { build(:type_de_champ_repetition, libelle: 'Enfants') } let(:repetition) { build(:type_de_champ_repetition, libelle: 'Enfants') }
let(:text_field) { build(:type_de_champ_text) } let(:text_field) { build(:type_de_champ_text) }
let(:invalid_repetition_error_message) { 'Le bloc répétable « Enfants » doit comporter au moins un champ' } let(:invalid_repetition_error_message) { 'Le champ « Enfants » doit comporter au moins un champ répétable' }
let(:drop_down) { build(:type_de_champ_drop_down_list, :without_selectable_values, libelle: 'Civilité') } let(:drop_down) { build(:type_de_champ_drop_down_list, :without_selectable_values, libelle: 'Civilité') }
let(:invalid_drop_down_error_message) { 'La liste de choix « Civilité » doit comporter au moins un choix sélectionnable' } let(:invalid_drop_down_error_message) { 'Le champ « Civilité » doit comporter au moins un choix sélectionnable' }
let(:procedure) { create(:procedure, types_de_champ: [repetition, drop_down]) } let(:procedure) { create(:procedure, types_de_champ: [repetition, drop_down]) }
context 'on a draft procedure' do context 'on a draft procedure' do
it 'doesnt validate the draft revision' do it 'doesnt validate the types de champs' do
procedure.validate procedure.validate
expect(procedure.errors[:draft_revision]).not_to be_present expect(procedure.errors[:draft_types_de_champ]).not_to be_present
end end
end end
@ -321,35 +321,52 @@ describe Procedure do
it 'validates that no repetition type de champ is empty' do it 'validates that no repetition type de champ is empty' do
procedure.validate procedure.validate
expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_repetition_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message)
text_field.revision = repetition.revision text_field.revision = repetition.revision
text_field.order_place = repetition.types_de_champ.size text_field.order_place = repetition.types_de_champ.size
procedure.draft_revision.types_de_champ.find(&:repetition?).types_de_champ << text_field procedure.draft_types_de_champ.find(&:repetition?).types_de_champ << text_field
procedure.validate procedure.validate
expect(procedure.errors.full_messages_for(:draft_revision)).not_to include(invalid_repetition_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_repetition_error_message)
end end
it 'validates that no drop-down type de champ is empty' do it 'validates that no drop-down type de champ is empty' do
procedure.validate procedure.validate
expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_drop_down_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message)
drop_down.update!(drop_down_list_value: "--title--\r\nsome value") drop_down.update!(drop_down_list_value: "--title--\r\nsome value")
procedure.reload.validate procedure.reload.validate
expect(procedure.errors.full_messages_for(:draft_revision)).not_to include(invalid_drop_down_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_drop_down_error_message)
end end
end end
context 'when validating for publication' do context 'when validating for publication' do
it 'validates that no repetition type de champ is empty' do it 'validates that no repetition type de champ is empty' do
procedure.validate(:publication) procedure.validate(:publication)
expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_repetition_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message)
end end
it 'validates that no drop-down type de champ is empty' do it 'validates that no drop-down type de champ is empty' do
procedure.validate(:publication) procedure.validate(:publication)
expect(procedure.errors.full_messages_for(:draft_revision)).to include(invalid_drop_down_error_message) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message)
end
end
context 'when the champ is private' do
let(:procedure) { create(:procedure, types_de_champ_private: [repetition, drop_down]) }
let(:invalid_repetition_error_message) { 'Lannotation privée « Enfants » doit comporter au moins un champ répétable' }
let(:invalid_drop_down_error_message) { 'Lannotation privée « Civilité » doit comporter au moins un choix sélectionnable' }
it 'validates that no repetition type de champ is empty' do
procedure.validate(:publication)
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to include(invalid_repetition_error_message)
end
it 'validates that no drop-down type de champ is empty' do
procedure.validate(:publication)
expect(procedure.errors.full_messages_for(:draft_types_de_champ_private)).to include(invalid_drop_down_error_message)
end end
end end
end end

View file

@ -47,19 +47,23 @@ describe 'Publishing a procedure', js: true do
end end
context 'when the procedure has invalid champs' do context 'when the procedure has invalid champs' do
let(:empty_repetition) { build(:type_de_champ_repetition, types_de_champ: []) } let(:empty_repetition) { build(:type_de_champ_repetition, types_de_champ: [], libelle: 'Enfants') }
let(:empty_drop_down) { build(:type_de_champ_drop_down_list, :without_selectable_values, libelle: 'Civilité') }
let!(:procedure) do let!(:procedure) do
create(:procedure, create(:procedure,
:with_path, :with_path,
:with_service, :with_service,
instructeurs: instructeurs, instructeurs: instructeurs,
administrateur: administrateur, administrateur: administrateur,
types_de_champ: [empty_repetition]) types_de_champ: [empty_repetition],
types_de_champ_private: [empty_drop_down])
end end
scenario 'an error message prevents the publication' do scenario 'an error message prevents the publication' do
expect(page).to have_content('Des problèmes empêchent la publication de la démarche') expect(page).to have_content('Des problèmes empêchent la publication de la démarche')
expect(page).to have_content("Le bloc répétable « #{empty_repetition.libelle} » doit comporter au moins un champ") expect(page).to have_content("Le champ « Enfants » doit comporter au moins un champ répétable")
expect(page).to have_content("Lannotation privée « Civilité » doit comporter au moins un choix sélectionnable")
expect(find_field('procedure_path').value).to eq procedure.path expect(find_field('procedure_path').value).to eq procedure.path
fill_in 'lien_site_web', with: 'http://some.website' fill_in 'lien_site_web', with: 'http://some.website'

View file

@ -8,6 +8,7 @@ describe 'instructeurs/procedures/_list.html.haml', type: :view do
dossiers_a_suivre_count_per_procedure: 2, dossiers_a_suivre_count_per_procedure: 2,
dossiers_archived_count_per_procedure: 1, dossiers_archived_count_per_procedure: 1,
dossiers_termines_count_per_procedure: 1, dossiers_termines_count_per_procedure: 1,
dossiers_supprimes_recemment_count_per_procedure: 0,
dossiers_expirant_count_per_procedure: 0, dossiers_expirant_count_per_procedure: 0,
followed_dossiers_count_per_procedure: 0, followed_dossiers_count_per_procedure: 0,
procedure_ids_en_cours_with_notifications: [], procedure_ids_en_cours_with_notifications: [],

View file

@ -11,6 +11,7 @@ describe 'instructeurs/procedures/_tabs.html.haml', type: :view do
suivis_count: 0, suivis_count: 0,
traites_count: 0, traites_count: 0,
tous_count: 0, tous_count: 0,
supprimes_recemment_count: 0,
archives_count: 0, archives_count: 0,
expirant_count: 0, expirant_count: 0,
has_en_cours_notifications: false, has_en_cours_notifications: false,