Merge pull request #6700 from betagouv/6671-etq-instructeur-rendre-l-expiration-plus-visible

6671 : ETQ instructeur, je veux savoir quand le dossier arrive a expiration
This commit is contained in:
mfo 2021-12-06 13:15:38 +01:00 committed by GitHub
commit 2bb013afaa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 613 additions and 212 deletions

View file

@ -0,0 +1 @@
<svg height="202" viewBox="-.8 -.5 177 202" width="177" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="30"><path d="m33.7 64.3c-11.6 12.9-18.7 30-18.7 48.7 0 40.1 32.5 72.7 72.7 72.7 40.1 0 72.7-32.5 72.7-72.7 0-18.7-7.1-35.8-18.7-48.7"/><path d="m87.8 15v98"/></g></svg>

After

Width:  |  Height:  |  Size: 334 B

View file

@ -19,6 +19,10 @@
background-image: image-url("icons/unfollow-folder.svg");
}
&.standby {
background-image: image-url("icons/standby.svg");
}
&.archive {
background-image: image-url("icons/archive.svg");
}

View file

@ -19,6 +19,12 @@ module Instructeurs
end
end
def extend_conservation
dossier.update(conservation_extension: dossier.conservation_extension + 1.month)
flash[:notice] = t('views.instructeurs.dossiers.archived_dossier')
redirect_back(fallback_location: instructeur_dossier_path(@dossier.procedure, @dossier))
end
def geo_data
send_data dossier.to_feature_collection.to_json,
type: 'application/json',

View file

@ -17,7 +17,7 @@ module Instructeurs
@dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.termine.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
groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
@followed_dossiers_count_per_procedure = current_instructeur
@ -34,6 +34,7 @@ module Instructeurs
'suivis' => @followed_dossiers_count_per_procedure.sum { |_, v| v },
'traités' => @dossiers_termines_count_per_procedure.sum { |_, v| v },
'dossiers' => @dossiers_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 }
}
@ -50,9 +51,9 @@ module Instructeurs
@current_filters = current_filters
@displayed_fields_options, @displayed_fields_selected = procedure_presentation.displayed_fields_for_select
@a_suivre_count, @suivis_count, @traites_count, @tous_count, @archives_count = current_instructeur
@a_suivre_count, @suivis_count, @traites_count, @tous_count, @archives_count, @expirant_count = current_instructeur
.dossiers_count_summary(groupe_instructeur_ids)
.fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'archives')
.fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'archives', 'expirant')
dossiers_visibles = Dossier
.where(groupe_instructeur_id: groupe_instructeur_ids)
@ -71,6 +72,7 @@ module Instructeurs
@termines_dossiers = dossiers_visibles.termine
@all_state_dossiers = dossiers_visibles.all_state
@archived_dossiers = dossiers_visibles.archived
@expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration
@dossiers = case statut
when 'a-suivre'
@ -88,6 +90,9 @@ module Instructeurs
when 'archives'
dossiers_count = @archives_count
@archived_dossiers
when 'expirant'
dossiers_count = @expirant_count
@expirant_dossiers
end
notifications = current_instructeur.notifications_for_groupe_instructeurs(groupe_instructeur_ids)

View file

@ -168,9 +168,9 @@ module Users
end
def extend_conservation
dossier.update(conservation_extension: dossier.conservation_extension + 1.month)
flash[:notice] = t('.archived_dossier')
redirect_to dossier_path(@dossier)
dossier.update(conservation_extension: dossier.conservation_extension + dossier.procedure.duree_conservation_dossiers_dans_ds.months)
flash[:notice] = t('views.users.dossiers.archived_dossier', duree_conservation_dossiers_dans_ds: dossier.procedure.duree_conservation_dossiers_dans_ds)
redirect_back(fallback_location: dossier_path(@dossier))
end
def modifier

View file

@ -34,7 +34,8 @@ class ProcedureDashboard < Administrate::BaseDashboard
closed_mail_template: MailTemplateField,
refused_mail_template: MailTemplateField,
without_continuation_mail_template: MailTemplateField,
attestation_template: AttestationTemplateField
attestation_template: AttestationTemplateField,
procedure_expires_when_termine_enabled: Field::Boolean
}.freeze
# COLLECTION_ATTRIBUTES
@ -79,13 +80,16 @@ class ProcedureDashboard < Administrate::BaseDashboard
:closed_mail_template,
:refused_mail_template,
:without_continuation_mail_template,
:attestation_template
:attestation_template,
:procedure_expires_when_termine_enabled
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [].freeze
FORM_ATTRIBUTES = [
:procedure_expires_when_termine_enabled
].freeze
# Overwrite this method to customize how procedures are displayed
# across all pages of the admin dashboard.

View file

@ -91,20 +91,17 @@ class Dossier < ApplicationRecord
def passer_en_construction(instructeur: nil, processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:en_construction),
instructeur_email: instructeur&.email,
process_expired: false,
processed_at: processed_at)
end
def passer_en_instruction(instructeur: nil, processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:en_instruction),
instructeur_email: instructeur&.email,
process_expired: false,
processed_at: processed_at)
end
def accepter_automatiquement(processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:accepte),
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
@ -112,7 +109,6 @@ class Dossier < ApplicationRecord
build(state: Dossier.states.fetch(:accepte),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
@ -120,7 +116,6 @@ class Dossier < ApplicationRecord
build(state: Dossier.states.fetch(:refuse),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
@ -128,7 +123,6 @@ class Dossier < ApplicationRecord
build(state: Dossier.states.fetch(:sans_suite),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
end
@ -300,11 +294,10 @@ class Dossier < ApplicationRecord
scope :interval_en_construction_close_to_expiration, -> do
state_en_construction.where("dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
scope :interval_en_instruction_close_to_expiration, -> do
state_en_instruction.where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
scope :interval_termine_close_to_expiration, -> do
state_termine.where(id: Traitement.termine_close_to_expiration.select(:dossier_id).distinct)
state_termine
.where(procedures: { procedure_expires_when_termine_enabled: true })
.where("dossiers.processed_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end
scope :brouillon_close_to_expiration, -> do
@ -313,9 +306,6 @@ class Dossier < ApplicationRecord
scope :en_construction_close_to_expiration, -> do
joins(:procedure).interval_en_construction_close_to_expiration
end
scope :en_instruction_close_to_expiration, -> do
joins(:procedure).interval_en_instruction_close_to_expiration
end
scope :termine_close_to_expiration, -> do
joins(:procedure).interval_termine_close_to_expiration
end
@ -324,7 +314,13 @@ class Dossier < ApplicationRecord
joins(:procedure).scoping do
interval_brouillon_close_to_expiration
.or(interval_en_construction_close_to_expiration)
.or(interval_en_instruction_close_to_expiration)
.or(interval_termine_close_to_expiration)
end
end
scope :termine_or_en_construction_close_to_expiration, -> do
joins(:procedure).scoping do
interval_en_construction_close_to_expiration
.or(interval_termine_close_to_expiration)
end
end
@ -545,7 +541,11 @@ class Dossier < ApplicationRecord
end
def expirable?
[brouillon?, en_construction?, termine? && procedure.feature_enabled?(:procedure_process_expired_dossiers_termine)].any?
[
brouillon?,
en_construction?,
termine? && procedure.procedure_expires_when_termine_enabled
].any?
end
def approximative_expiration_date_reference
@ -569,6 +569,7 @@ class Dossier < ApplicationRecord
end
def close_to_expiration?
return false if en_instruction?
approximative_expiration_date < Time.zone.now
end
@ -646,6 +647,10 @@ class Dossier < ApplicationRecord
parts.join
end
def duree_totale_conservation_in_months
procedure.duree_conservation_dossiers_dans_ds + (conservation_extension / 1.month.to_i)
end
def avis_for_instructeur(instructeur)
if instructeur.dossiers.include?(self)
avis.order(created_at: :asc)

View file

@ -237,8 +237,22 @@ 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 ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where not archived) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives
COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where
procedures.procedure_expires_when_termine_enabled
AND (
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
) OR (
dossiers.state in ('en_construction')
AND dossiers.en_construction_at + dossiers.conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now
)
) AS expirant
FROM "dossiers"
INNER JOIN "procedure_revisions"
ON "procedure_revisions"."id" = "dossiers"."revision_id"
INNER JOIN "procedures"
ON "procedures"."id" = "procedure_revisions"."procedure_id"
LEFT OUTER JOIN follows
ON follows.dossier_id = dossiers.id
AND follows.unfollowed_at IS NULL
@ -250,7 +264,9 @@ class Instructeur < ApplicationRecord
sanitized_query = ActiveRecord::Base.sanitize_sql([
query,
instructeur_id: id,
groupe_instructeur_ids: groupe_instructeur_ids
groupe_instructeur_ids: groupe_instructeur_ids,
now: Time.zone.now,
expires_in: Dossier::INTERVAL_BEFORE_EXPIRATION
])
Dossier.connection.select_all(sanitized_query).first

View file

@ -34,6 +34,7 @@
# monavis_embed :text
# organisation :string
# path :string not null
# procedure_expires_when_termine_enabled :boolean default(FALSE)
# published_at :datetime
# routing_criteria_name :text default("Votre ville")
# routing_enabled :boolean

View file

@ -2,13 +2,14 @@
#
# Table name: traitements
#
# id :bigint not null, primary key
# instructeur_email :string
# motivation :string
# process_expired :boolean
# processed_at :datetime
# state :string
# dossier_id :bigint
# id :bigint not null, primary key
# instructeur_email :string
# motivation :string
# process_expired :boolean
# process_expired_migrated :boolean default(FALSE)
# processed_at :datetime
# state :string
# dossier_id :bigint
#
class Traitement < ApplicationRecord
belongs_to :dossier, optional: false

View file

@ -95,7 +95,6 @@ class ExpiredDossiersDeletionService
deleted_dossier_ids << dossier.id
end
end
user_notifications.each do |(email, dossier_ids)|
dossier_ids = dossier_ids.intersection(deleted_dossier_ids)
if dossier_ids.present?

View file

@ -0,0 +1,31 @@
-# small expires mention
- if dossier.expirable?
%p.expires_at.mb-2
%small
= t("shared.dossiers.header.expires_at.#{dossier.state}", date: safe_expiration_date(dossier), duree_conservation_totale: dossier.duree_totale_conservation_in_months)
- if dossier.conservation_extension.positive?
= t('instructeurs.dossiers.header.banner.expiration_date_extended')
-# big banner warning
- if dossier.close_to_expiration?
.card.warning.mb-3
.card-title= t('instructeurs.dossiers.header.banner.title')
%p
- if dossier.brouillon?
= t('instructeurs.dossiers.header.banner.states.brouillon')
- elsif dossier.en_construction?
= t('instructeurs.dossiers.header.banner.states.en_construction')
- elsif dossier.termine?
= t('instructeurs.dossiers.header.banner.states.termine')
- if dossier.expiration_can_be_extended?
%br
= button_to repousser_expiration_instructeur_dossier_path(dossier.procedure, dossier), class: 'button mt-2', id: 'test-instructeur-repousser-expiration' do
%span.icon.standby
= t('instructeurs.dossiers.header.banner.button_delay_expiration')
- else
%p.expires_at_en_instruction
%small= t("shared.dossiers.header.expires_at.en_instruction")

View file

@ -9,9 +9,12 @@
= dossier.procedure.libelle.truncate_words(10)
%li
= "Dossier nº #{dossier.id}"
.header-actions
= render partial: 'instructeurs/dossiers/header_actions', locals: { dossier: dossier }
= render(partial: 'instructeurs/dossiers/expiration_banner', locals: {dossier: dossier})
%ul.tabs
- notifications_summary = current_instructeur.notifications_for_dossier(dossier)

View file

@ -27,7 +27,8 @@
dossier_id: dossier.id,
state: dossier.state,
archived: dossier.archived,
dossier_is_followed: current_instructeur&.follow?(dossier) }
dossier_is_followed: current_instructeur&.follow?(dossier),
close_to_expiration: dossier.close_to_expiration? }
.state-button

View file

@ -1,9 +1,14 @@
- if Dossier::TERMINE.include?(state)
- if close_to_expiration || Dossier::TERMINE.include?(state)
.dropdown.user-dossier-actions
%button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'actions-menu' }
Actions
#actions-menu.dropdown-content.fade-in-down
%ul.dropdown-items
- if close_to_expiration
%li
= link_to repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post do
%span.icon.standby
.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration')
- if archived
%li
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do

View file

@ -17,30 +17,3 @@
|
= link_to 'contacter les usagers (brouillon)', email_usagers_instructeur_procedure_path(procedure), class: 'header-link'
%ul.tabs
= tab_item('à suivre',
instructeur_procedure_path(procedure, statut: 'a-suivre'),
active: statut == 'a-suivre',
badge: number_with_html_delimiter(a_suivre_count))
= tab_item(t('pluralize.followed', count: suivis_count),
instructeur_procedure_path(procedure, statut: 'suivis'),
active: statut == 'suivis',
badge: number_with_html_delimiter(suivis_count),
notification: has_en_cours_notifications)
= tab_item(t('pluralize.processed', count: traites_count),
instructeur_procedure_path(procedure, statut: 'traites'),
active: statut == 'traites',
badge: number_with_html_delimiter(traites_count),
notification: has_termine_notifications)
= tab_item('au total',
instructeur_procedure_path(procedure, statut: 'tous'),
active: statut == 'tous',
badge: number_with_html_delimiter(tous_count))
= tab_item(t('pluralize.archived', count: archives_count),
instructeur_procedure_path(procedure, statut: 'archives'),
active: statut == 'archives',
badge: number_with_html_delimiter(archives_count))

View file

@ -44,6 +44,17 @@
= number_with_html_delimiter(dossier_count)
.stats-legend
= t('pluralize.case', count: dossier_count)
- if p.procedure_expires_when_termine_enabled
%li
%object
= link_to(instructeur_procedure_path(p, statut: 'expirant')) do
- expirant_count = dossiers_expirant_count_per_procedure[p.id] || 0
.stats-number
= number_with_html_delimiter(expirant_count)
.stats-legend
= t('pluralize.dossiers_close_to_expiration', count: expirant_count)
%li
%object
= link_to(instructeur_procedure_path(p, statut: 'archives')) do

View file

@ -0,0 +1,34 @@
%ul.tabs.mt-3
= tab_item('à suivre',
instructeur_procedure_path(procedure, statut: 'a-suivre'),
active: statut == 'a-suivre',
badge: number_with_html_delimiter(a_suivre_count))
= tab_item(t('pluralize.followed', count: suivis_count),
instructeur_procedure_path(procedure, statut: 'suivis'),
active: statut == 'suivis',
badge: number_with_html_delimiter(suivis_count),
notification: has_en_cours_notifications)
= tab_item(t('pluralize.processed', count: traites_count),
instructeur_procedure_path(procedure, statut: 'traites'),
active: statut == 'traites',
badge: number_with_html_delimiter(traites_count),
notification: has_termine_notifications)
= tab_item('au total',
instructeur_procedure_path(procedure, statut: 'tous'),
active: statut == 'tous',
badge: number_with_html_delimiter(tous_count))
- if procedure.procedure_expires_when_termine_enabled
= tab_item(t('pluralize.dossiers_close_to_expiration', count: expirant_count),
instructeur_procedure_path(procedure, statut: 'expirant'),
active: statut == 'expirant',
badge: number_with_html_delimiter(expirant_count))
= tab_item(t('pluralize.archived', count: archives_count),
instructeur_procedure_path(procedure, statut: 'archives'),
active: statut == 'archives',
badge: number_with_html_delimiter(archives_count))

View file

@ -12,6 +12,7 @@
dossiers_a_suivre_count_per_procedure: @dossiers_a_suivre_count_per_procedure,
dossiers_archived_count_per_procedure: @dossiers_archived_count_per_procedure,
dossiers_termines_count_per_procedure: @dossiers_termines_count_per_procedure,
dossiers_expirant_count_per_procedure: @dossiers_expirant_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_termines_with_notifications: @procedure_ids_termines_with_notifications }

View file

@ -7,19 +7,21 @@
.procedure-logo{ style: "background-image: url(#{@procedure.logo_url})",
role: 'img', 'aria-label': "logo de la démarche #{@procedure.libelle}" }
= render partial: 'header', locals: { procedure: @procedure,
= render partial: 'header', locals: { procedure: @procedure, statut: @statut }
.procedure-actions
= render partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports, dossier_count: @tous_count + @archives_count }
.container.flex= render partial: "tabs", locals: { procedure: @procedure,
statut: @statut,
a_suivre_count: @a_suivre_count,
suivis_count: @suivis_count,
traites_count: @traites_count,
tous_count: @tous_count,
archives_count: @archives_count,
expirant_count: @expirant_count,
has_en_cours_notifications: @has_en_cours_notifications,
has_termine_notifications: @has_termine_notifications }
.procedure-actions
= render partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports, dossier_count: @tous_count + @archives_count }
.container
- if @statut == 'a-suivre'
%p.explication-onglet Aucun instructeur nest affecté au suivi de ces dossiers. Soyez le premier !
@ -42,6 +44,9 @@
= link_to deleted_dossiers_instructeur_procedure_path(@procedure) do
%span.icon.delete
Afficher les dossiers supprimés
- if @statut == 'expirant'
%p.explication-onglet Les dossiers n'expireront pas avant la période de conservation des données.
- if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0
- pagination = paginate @filtered_sorted_paginated_ids
@ -120,11 +125,12 @@
%a.cell-link{ href: path }= status_badge(p.state)
%td.action-col.follow-col= render partial: 'dossier_actions',
locals: { procedure_id: @procedure.id,
dossier_id: p.dossier_id,
state: p.state,
archived: p.archived,
dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) }
locals: { procedure_id: @procedure.id,
dossier_id: p.dossier_id,
state: p.state,
archived: p.archived,
dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id),
close_to_expiration: @statut == 'expirant' }
= pagination
- else

View file

@ -81,7 +81,8 @@
dossier_id: p.dossier_id,
state: p.state,
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: nil }
- else
%td

View file

@ -1,25 +0,0 @@
-# small expires mention
- if dossier.expirable?
%p.expires_at
%small= t("shared.dossiers.header.expires_at.#{dossier.state}", date: safe_expiration_date(dossier))
-# big banner warning
- if dossier.close_to_expiration?
.card.warning.mt-2.mb-3
.card-title= t('shared.dossiers.header.banner.title')
%p
- if dossier.brouillon?
= t('shared.dossiers.header.banner.states.brouillon')
- elsif dossier.en_construction?
= t('shared.dossiers.header.banner.states.en_construction')
- elsif dossier.termine?
= t('shared.dossiers.header.banner.states.termine')
- if dossier.expiration_can_be_extended?
%br
= button_to t('shared.dossiers.header.banner.button_delay_expiration'), users_dossier_repousser_expiration_path(dossier), class: 'button secondary mt-2'
- else
%p.expires_at_en_instruction
%small= t("shared.dossiers.header.expires_at.en_instruction")

View file

@ -6,7 +6,7 @@
= t('views.users.dossiers.show.header.dossier_number', dossier_id: dossier.id)
= t('views.users.dossiers.show.header.created_date', date_du_dossier: I18n.l(dossier.created_at))
= render(partial: 'shared/dossiers/expiration_banner', locals: {dossier: dossier})
= render(partial: 'users/dossiers/expiration_banner', locals: {dossier: dossier})
.header-actions
- if current_user.owns?(dossier)

View file

@ -20,8 +20,8 @@
= link_to(url_for_dossier(dossier), class: 'cell-link') do
= procedure_libelle(dossier.procedure)
- if dossiers.present?
%td.cell-link
= demandeur_dossier(dossier)
%td
%span.cell-link= demandeur_dossier(dossier)
%td.status-col
= status_badge(dossier.state)
%td.updated-at-col.cell-link

View file

@ -0,0 +1,27 @@
-# small expires mention
- if dossier.expirable?
%p.expires_at.mb-2
%small= t("shared.dossiers.header.expires_at.#{dossier.state}", date: safe_expiration_date(dossier), duree_conservation_totale: dossier.duree_totale_conservation_in_months)
-# big banner warning
- if dossier.close_to_expiration?
.card.warning.mb-3
.card-title= t('users.dossiers.header.banner.title')
%p
- if dossier.brouillon?
= t('users.dossiers.header.banner.states.brouillon')
- elsif dossier.en_construction?
= t('users.dossiers.header.banner.states.en_construction')
- elsif dossier.termine?
= t('users.dossiers.header.banner.states.termine')
- if dossier.expiration_can_be_extended?
%br
= button_to users_dossier_repousser_expiration_path(dossier), class: 'button mt-2', id: 'test-user-repousser-expiration' do
%span.icon.standby
= t('users.dossiers.header.banner.button_delay_expiration', duree_conservation_dossiers_dans_ds: dossier.procedure.duree_conservation_dossiers_dans_ds)
- else
%p.expires_at_en_instruction
%small= t("shared.dossiers.header.expires_at.en_instruction")

View file

@ -32,6 +32,12 @@
active: @statut == 'dossiers-invites',
badge: number_with_html_delimiter(@dossiers_invites.count))
- if @dossiers_close_to_expiration.count > 0
= tab_item(t('pluralize.dossiers_close_to_expiration', count: @dossiers_close_to_expiration.count),
dossiers_path(statut: 'dossiers-expirant'),
active: @statut == 'dossiers-expirant',
badge: number_with_html_delimiter(@dossiers_close_to_expiration.count))
- if @dossiers_supprimes.present?
= tab_item(t('pluralize.dossiers_supprimes', count: @dossiers_supprimes.count),
dossiers_path(statut: 'dossiers-supprimes'),
@ -44,12 +50,6 @@
active: @statut == 'dossiers-transferes',
badge: number_with_html_delimiter(@dossier_transfers.count))
- if @dossiers_close_to_expiration.count > 0
= tab_item(t('pluralize.dossiers_close_to_expiration', count: @dossiers_close_to_expiration.count),
dossiers_path(statut: 'dossiers-expirant'),
active: @statut == 'dossiers-expirant',
badge: number_with_html_delimiter(@dossiers_close_to_expiration.count))
.container
- if @statut == "en-cours"
= render partial: "dossiers_list", locals: { dossiers: @user_dossiers }

View file

@ -10,7 +10,7 @@
- if dossier.en_construction_at.present?
= t('views.users.dossiers.show.header.submit_date', date_du_dossier: I18n.l(dossier.en_construction_at))
= render(partial: 'shared/dossiers/expiration_banner', locals: {dossier: dossier})
= render(partial: 'users/dossiers/expiration_banner', locals: {dossier: dossier})
- if current_user.owns?(dossier)

View file

@ -134,11 +134,13 @@ en:
edit_identity: "Edit identity data"
instructeurs:
dossiers:
archived_dossier: "This file will be kept for an additional month"
deleted_by_user: "File deleted by user"
avis:
introduction_file_explaination: "File attached to the request for advice"
users:
dossiers:
archived_dossier: "Your file will be kept %{duree_conservation_dossiers_dans_ds} more months"
autosave:
autosave_draft: Your draft is automatically saved.
more_infos: More informations
@ -382,8 +384,6 @@ en:
ask_deletion:
undergoingreview: "Your file is undergoing review. It is no longer possible to delete your file. To cancel the undergoingreview contact the adminitration via the mailbox."
deleted_dossier: "Your file has been successfully deleted"
extend_conservation:
archived_dossier: "Your file will be archived for an additional month"
update_brouillon:
draft_saved: "Your draft has been saved."
etablissement:

View file

@ -130,11 +130,13 @@ fr:
edit_identity: "Modifier lidentité"
instructeurs:
dossiers:
archived_dossier: "Le dossier sera conservé 1 mois supplémentaire"
deleted_by_user: "Dossier supprimé par l'usager"
avis:
introduction_file_explaination: "Fichier joint à la demande davis"
users:
dossiers:
archived_dossier: "Votre dossier sera conservé %{duree_conservation_dossiers_dans_ds} mois supplémentaire"
autosave:
autosave_draft: Votre brouillon est automatiquement enregistré.
more_infos: En savoir plus
@ -365,9 +367,9 @@ fr:
one: demande de transfert
other: demandes de transfert
dossiers_close_to_expiration:
zero: dossier expirant
one: dossier expirant
other: dossiers expirant
zero: expirant
one: expirant
other: expirant
dossier_trouve:
zero: 0 dossier trouvé
one: 1 dossier trouvé
@ -389,13 +391,12 @@ fr:
test_procedure: "Ce dossier est déposé sur une démarche en test. Toute modification de la démarche par ladministrateur (ajout d'un champ, publication de la démarche...) entraînera sa suppression."
no_access: "Vous navez pas accès à ce dossier"
no_longer_editable: "Votre dossier ne peut plus être modifié"
create_commentaire:
message_send: "Votre message a bien été envoyé à linstructeur en charge de votre dossier."
ask_deletion:
undergoingreview: "Linstruction de votre dossier a commencé, il nest plus possible de supprimer votre dossier. Si vous souhaitez annuler linstruction contactez votre administration par la messagerie de votre dossier."
deleted_dossier: "Votre dossier a bien été supprimé."
extend_conservation:
archived_dossier: "Votre dossier sera conservé un mois supplémentaire"
update_brouillon:
draft_saved: "Votre brouillon a bien été sauvegardé."
etablissement:

View file

@ -9,19 +9,12 @@ en:
code_postal_notice: It is usually composed of 5 digits.
header:
expires_at:
brouillon: "Expires at %{date}"
en_construction: "Expires at %{date}"
brouillon: "Expires at %{date} (%{duree_conservation_totale} months after the creation of this file)"
en_construction: "Expires at %{date} (%{duree_conservation_totale} months after the last la edition of this file)"
en_instruction: "This file is being instructed, the administration will answer as soon as possible"
accepte: "Expires at %{date}"
refuse: "Expires at %{date}"
sans_suite: "Expires at %{date}"
banner:
title: Your file will expire
states:
brouillon: Your file is still in draft and will soon expire. So it will be deleted soon without being instructed. If you want to pursue your procedure you can submit it now. Otherwise you are able to delay its expiration by clicking on the underneath button.
en_construction: Your file is pending for instruction. The maximum delay is 6 months, but your can extend the duration by a month by clicking on the underneath button.
termine: Your file had been processed and will soon expire.So it will be deleted soon. If you want to keep it, your can dowload a PDF file of it.
button_delay_expiration: "Delay deletion"
accepte: "Expires at %{date} (%{duree_conservation_totale} months after the acceptation of this file)"
refuse: "Expires at %{date} (%{duree_conservation_totale} months after the rejection of this file)"
sans_suite: "Expires at %{date} (%{duree_conservation_totale} months after this file had been closed)"
champs:
cnaf:
show:

View file

@ -9,19 +9,13 @@ fr:
code_postal_notice: Il est généralement composé de 5 chiffres.
header:
expires_at:
brouillon: "Expirera le %{date}"
en_construction: "Expirera le %{date}"
brouillon: "Expirera le %{date} (%{duree_conservation_totale} mois après la création du dossier)"
en_construction: "Expirera le %{date} (%{duree_conservation_totale} mois après la dernière date d'édition)"
en_instruction: "Ce dossier est en instruction, il n'expirera pas"
accepte: "Expirera le %{date}"
refuse: "Expirera le %{date}"
sans_suite: "Expirera le %{date}"
banner:
title: Votre dossier va expirer
states:
brouillon: Votre dossier est en brouillon, mais va bientôt expirer. Cela signifie quil va bientôt être supprimé sans avoir été déposé. Si vous souhaitez le conserver afin de poursuivre la démarche, vous pouvez le conserver un mois de plus en cliquant sur le bouton ci-dessous.
en_construction: Votre dossier est en attente de prise en charge par l'administration. Le delais de prise en charge maximale est de 6 mois. Vous pouvez toutefois entendre cette durée d'un mois en cliquant sur le bouton suivant.
termine: Le traitement de votre dossier est terminé, mais il va bientôt expirer. Cela signifie quil va bientôt être supprimé. Si vous souhaitez conserver une trace, vous pouvez le télécharger au format PDF.
button_delay_expiration: "Repousser sa suppression"
accepte: "Expirera le %{date} (%{duree_conservation_totale} mois après l'acceptation du dossier)"
refuse: "Expirera le %{date} (%{duree_conservation_totale} mois après le refus du dossier)"
sans_suite: "Expirera le %{date} (%{duree_conservation_totale} mois après que le dossier aie été classé sans suite)"
champs:
cnaf:

View file

@ -18,6 +18,8 @@ fr:
archive_pending_html: Archive en cours de création<br>(demandée il y a %{created_period})
archive_ready_html: Télécharger larchive<br>(demandée il y a %{generated_period})
dossiers:
extend_conservation:
archived_dossier: "Le dossier sera conservé 1 mois supplémentaire"
decisions_rendues_block:
without_email:
en_construction: Le %{processed_at} ce dossier a été passé en construction

View file

@ -0,0 +1,12 @@
en:
instructeurs:
dossiers:
header:
banner:
expiration_date_extended: " the expiration date had already been extended"
title: This file will expire
states:
brouillon: "" # not applicable, instructeur does not see brouillons
en_construction: This file is pending for instruction. The maximum delay is 6 months, you can extend the duration by a month by clicking on the underneath button.
termine: This file had been processed and will soon expire. So it will be deleted soon. If you want to keep it, you can dowload a PDF file of it.
button_delay_expiration: "Keep for one more month"

View file

@ -0,0 +1,12 @@
fr:
instructeurs:
dossiers:
header:
banner:
expiration_date_extended: " la date de conservation a déjà été etendue"
title: Ce dossier va expirer
states:
brouillon: "" # not applicable, instructeur does not see brouillons
en_construction: Ce dossier est en attente de prise en charge. Vous pouvez toutefois entendre cette durée d'un mois en cliquant sur le bouton suivant.
termine: Le traitement de ce dossier est terminé, mais il va bientôt expirer. Cela signifie quil va bientôt être supprimé. Si vous souhaitez conserver une trace, vous pouvez le télécharger au format PDF.
button_delay_expiration: "Conserver un mois de plus"

View file

@ -0,0 +1,11 @@
en:
users:
dossiers:
header:
banner:
title: Your file will expire
states:
brouillon: Your file is still in draft and will soon expire. So it will be deleted soon without being instructed. If you want to pursue your procedure you can submit it now. Otherwise you are able to delay its expiration by clicking on the underneath button.
en_construction: Your file is pending for instruction. The maximum delay is 6 months, but you can extend the duration by a month by clicking on the underneath button.
termine: Your file had been processed and will soon expire. So it will be deleted soon. If you want to keep it, you can dowload a PDF file of it.
button_delay_expiration: "Keep for %{duree_conservation_dossiers_dans_ds} more months"

View file

@ -0,0 +1,11 @@
fr:
users:
dossiers:
header:
banner:
title: Votre dossier va expirer
states:
brouillon: Votre dossier est en brouillon, mais va bientôt expirer. Cela signifie quil va bientôt être supprimé sans avoir été déposé. Si vous souhaitez le conserver afin de poursuivre la démarche, vous pouvez le conserver un mois de plus en cliquant sur le bouton ci-dessous.
en_construction: Votre dossier est en attente de prise en charge par l'administration. Le delais de prise en charge maximale est de 6 mois. Vous pouvez toutefois entendre cette durée d'un mois en cliquant sur le bouton suivant.
termine: Le traitement de votre dossier est terminé, mais il va bientôt expirer. Cela signifie quil va bientôt être supprimé. Si vous souhaitez conserver une trace, vous pouvez le télécharger au format PDF.
button_delay_expiration: "Conserver %{duree_conservation_dossiers_dans_ds} mois supplémentaires "

View file

@ -10,7 +10,7 @@ Rails.application.routes.draw do
#
namespace :manager do
resources :procedures, only: [:index, :show] do
resources :procedures, only: [:index, :show, :edit, :update] do
post 'whitelist', on: :member
post 'draft', on: :member
post 'discard', on: :member
@ -361,7 +361,7 @@ Rails.application.routes.draw do
resources :dossiers, only: [:show], param: :dossier_id do
member do
resources :commentaires, only: [:destroy]
post 'repousser-expiration' => 'dossiers#extend_conservation'
get 'attestation'
get 'geo_data'
get 'apercu_attestation'

View file

@ -0,0 +1,17 @@
class MoveProcessExpireToProcedures < ActiveRecord::Migration[6.1]
include Database::MigrationHelpers
disable_ddl_transaction!
def up
add_column :procedures, :procedure_expires_when_termine_enabled, :boolean, default: false
add_column :traitements, :process_expired_migrated, :boolean, default: false
add_concurrent_index :procedures, :procedure_expires_when_termine_enabled
end
def down
remove_index :procedures, name: :index_procedures_on_process_expired
remove_column :traitements, :process_expired_migrated
remove_column :procedures, :procedure_expires_when_termine_enabled
end
end

View file

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

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_11_27_143736) do
ActiveRecord::Schema.define(version: 2021_12_01_135804) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -320,12 +320,12 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
t.interval "conservation_extension", default: "PT0S"
t.string "deleted_user_email_never_send"
t.datetime "declarative_triggered_at"
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
t.bigint "dossier_transfer_id"
t.datetime "identity_updated_at"
t.datetime "depose_at"
t.datetime "hidden_by_user_at"
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
t.index ["archived"], name: "index_dossiers_on_archived"
t.index ["dossier_transfer_id"], name: "index_dossiers_on_dossier_transfer_id"
t.index ["groupe_instructeur_id"], name: "index_dossiers_on_groupe_instructeur_id"
@ -554,14 +554,6 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
t.index ["user_id"], name: "index_merge_logs_on_user_id"
end
create_table "zones", force: :cascade do |t|
t.string "acronym", null: false
t.string "label"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["acronym"], name: "index_zones_on_acronym", unique: true
end
create_table "module_api_cartos", id: :serial, force: :cascade do |t|
t.integer "procedure_id"
t.boolean "use_api_carto", default: false
@ -576,7 +568,7 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
create_table "procedure_presentations", id: :serial, force: :cascade do |t|
t.integer "assign_to_id"
t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false
t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[]}, null: false
t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[]}, null: false
t.datetime "created_at"
t.datetime "updated_at"
t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false
@ -646,15 +638,17 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
t.text "api_particulier_scopes", default: [], array: true
t.jsonb "api_particulier_sources", default: {}
t.boolean "instructeurs_self_management_enabled"
t.boolean "routing_enabled"
t.boolean "procedure_expires_when_termine_enabled", default: false
t.bigint "zone_id"
t.index ["api_particulier_sources"], name: "index_procedures_on_api_particulier_sources", using: :gin
t.boolean "routing_enabled"
t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state"
t.index ["draft_revision_id"], name: "index_procedures_on_draft_revision_id"
t.index ["hidden_at"], name: "index_procedures_on_hidden_at"
t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id"
t.index ["path", "closed_at", "hidden_at", "unpublished_at"], name: "procedure_path_uniqueness", unique: true
t.index ["path", "closed_at", "hidden_at"], name: "index_procedures_on_path_and_closed_at_and_hidden_at", unique: true
t.index ["procedure_expires_when_termine_enabled"], name: "index_procedures_on_procedure_expires_when_termine_enabled"
t.index ["published_revision_id"], name: "index_procedures_on_published_revision_id"
t.index ["service_id"], name: "index_procedures_on_service_id"
t.index ["zone_id"], name: "index_procedures_on_zone_id"
@ -745,6 +739,7 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
t.datetime "processed_at"
t.string "instructeur_email"
t.boolean "process_expired"
t.boolean "process_expired_migrated", default: false
t.index ["dossier_id"], name: "index_traitements_on_dossier_id"
t.index ["process_expired"], name: "index_traitements_on_process_expired"
end
@ -833,6 +828,14 @@ ActiveRecord::Schema.define(version: 2021_11_27_143736) do
t.index ["procedure_id"], name: "index_without_continuation_mails_on_procedure_id"
end
create_table "zones", force: :cascade do |t|
t.string "acronym", null: false
t.string "label"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["acronym"], name: "index_zones_on_acronym", unique: true
end
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "archives_groupe_instructeurs", "archives"
add_foreign_key "archives_groupe_instructeurs", "groupe_instructeurs"

View file

@ -0,0 +1,21 @@
namespace :after_party do
desc 'Deployment task: move_traitement_process_expired_to_procedure'
task move_traitement_process_expired_to_procedure: :environment do
procedures = Procedure.joins(dossiers: :traitements).where(dossiers: { traitements: { process_expired: true, process_expired_migrated: false } })
progress = ProgressReport.new(procedures.count)
procedures.group(:id).find_each do |procedure|
ActiveRecord::Base.transaction do
puts "update traitements from dossier_ids: #{procedure.dossiers.ids}"
Traitement.where(id: procedure.dossiers.ids).update_all(process_expired_migrated: true)
procedure.update(procedure_expires_when_termine_enabled: true)
progress.inc
end
end
progress.finish
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -804,4 +804,23 @@ describe Instructeurs::DossiersController, type: :controller do
end
end
end
describe '#extend_conservation' do
subject { post :extend_conservation, params: { procedure_id: procedure.id, dossier_id: dossier.id } }
context 'when user logged in' do
it 'works' do
expect(subject).to redirect_to(instructeur_dossier_path(procedure, dossier))
end
it 'extends conservation_extension by 1 month' do
subject
expect(dossier.reload.conservation_extension).to eq(1.month)
end
it 'flashed notice success' do
subject
expect(flash[:notice]).to eq(I18n.t('views.instructeurs.dossiers.archived_dossier'))
end
end
end
end

View file

@ -1161,4 +1161,33 @@ describe Users::DossiersController, type: :controller do
expect(response).to have_http_status(:ok)
end
end
describe '#extend_conservation' do
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 3) }
let(:dossier) { create(:dossier, procedure: procedure, user: user) }
subject { post :extend_conservation, params: { dossier_id: dossier.id } }
context 'when user logged in' do
before { sign_in(user) }
it 'works' do
expect(subject).to redirect_to(dossier_path(dossier))
end
it 'extends conservation_extension by duree_conservation_dossiers_dans_ds' do
subject
expect(dossier.reload.conservation_extension).to eq(procedure.duree_conservation_dossiers_dans_ds.months)
end
it 'flashed notice success' do
subject
expect(flash[:notice]).to eq(I18n.t('views.users.dossiers.archived_dossier', duree_conservation_dossiers_dans_ds: procedure.duree_conservation_dossiers_dans_ds))
end
end
context 'when not logged in' do
it 'fails' do
subject
expect { expect(response).to redirect_to(new_user_session_path) }
end
end
end
end

View file

@ -135,10 +135,12 @@ FactoryBot.define do
if processed_at.present?
dossier.en_construction_at ||= processed_at - 2.minutes
dossier.en_instruction_at ||= processed_at - 1.minute
dossier.processed_at = processed_at
dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: processed_at)
else
dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.processed_at = dossier.en_instruction_at + 1.minute
dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: dossier.en_instruction_at + 1.minute)
end
dossier.save!

View file

@ -107,16 +107,25 @@ describe Dossier do
is_expected.to include(long_expired_dossier)
end
end
context 'when .termine_or_en_construction_close_to_expiration' do
subject { Dossier.termine_or_en_construction_close_to_expiration }
it do
is_expected.not_to include(young_dossier)
is_expected.to include(expiring_dossier)
is_expected.to include(just_expired_dossier)
is_expected.to include(long_expired_dossier)
end
end
end
describe 'en_instruction_close_to_expiration' do
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
let!(:young_dossier) { create(:dossier, procedure: procedure) }
let!(:expiring_dossier) { create(:dossier, :en_instruction, en_instruction_at: 175.days.ago, procedure: procedure) }
let!(:just_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
let!(:long_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: 1.year.ago, procedure: procedure) }
describe 'termine_close_to_expiration' do
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6, procedure_expires_when_termine_enabled: true) }
let!(:young_dossier) { create(:dossier, state: :accepte, procedure: procedure, processed_at: 2.days.ago) }
let!(:expiring_dossier) { create(:dossier, state: :accepte, procedure: procedure, processed_at: 175.days.ago) }
let!(:just_expired_dossier) { create(:dossier, state: :accepte, procedure: procedure, processed_at: (6.months + 1.hour + 10.seconds).ago) }
let!(:long_expired_dossier) { create(:dossier, state: :accepte, procedure: procedure, processed_at: 1.year.ago) }
subject { Dossier.en_instruction_close_to_expiration }
subject { Dossier.termine_close_to_expiration }
it do
is_expected.not_to include(young_dossier)
@ -134,26 +143,9 @@ describe Dossier do
is_expected.to include(long_expired_dossier)
end
end
end
describe 'termine_close_to_expiration' do
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
let!(:young_dossier) { create(:dossier, :accepte, procedure: procedure, traitements: [build(:traitement, :accepte)]) }
let!(:expiring_dossier) { create(:dossier, :accepte, procedure: procedure, traitements: [build(:traitement, :accepte, processed_at: 175.days.ago)]) }
let!(:just_expired_dossier) { create(:dossier, :accepte, procedure: procedure, traitements: [build(:traitement, :accepte, processed_at: (6.months + 1.hour + 10.seconds).ago)]) }
let!(:long_expired_dossier) { create(:dossier, :accepte, procedure: procedure, traitements: [build(:traitement, :accepte, processed_at: 1.year.ago)]) }
subject { Dossier.termine_close_to_expiration }
it do
is_expected.not_to include(young_dossier)
is_expected.to include(expiring_dossier)
is_expected.to include(just_expired_dossier)
is_expected.to include(long_expired_dossier)
end
context 'when .close_to_expiration' do
subject { Dossier.close_to_expiration }
subject { Dossier.termine_or_en_construction_close_to_expiration }
it do
is_expected.not_to include(young_dossier)
is_expected.to include(expiring_dossier)

View file

@ -594,7 +594,7 @@ describe Instructeur, type: :model do
describe "#dossiers_count_summary" do
let(:instructeur_2) { create(:instructeur) }
let(:instructeur_3) { create(:instructeur) }
let(:procedure) { create(:procedure, instructeurs: [instructeur_2, instructeur_3]) }
let(:procedure) { create(:procedure, instructeurs: [instructeur_2, instructeur_3], procedure_expires_when_termine_enabled: true) }
let(:gi_1) { procedure.groupe_instructeurs.first }
let(:gi_2) { procedure.groupe_instructeurs.create(label: '2') }
let(:gi_3) { procedure.groupe_instructeurs.create(label: '3') }
@ -614,6 +614,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(0) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
end
context 'with a new brouillon dossier' do
@ -624,6 +625,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(0) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
end
context 'with a new dossier without follower' do
@ -634,6 +636,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(1) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
context 'and dossiers without follower on each of the others groups' do
let!(:new_unfollow_dossier_on_gi_2) { create(:dossier, :en_instruction, groupe_instructeur: gi_2) }
@ -658,6 +661,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(1) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
context 'and another one follows the same dossier' do
before do
@ -669,6 +673,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(1) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
end
context 'and dossier with a follower on each of the others groups' do
@ -692,6 +697,7 @@ describe Instructeur, type: :model do
it { expect(subject['a_suivre']).to eq(1) }
it { expect(subject['suivis']).to eq(0) }
it { expect(subject['tous']).to eq(1) }
it { expect(subject['expirant']).to eq(0) }
end
end
@ -703,6 +709,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(1) }
it { expect(subject['tous']).to eq(1) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
context 'and terminer dossiers on each of the others groups' do
let!(:termine_dossier_on_gi_2) { create(:dossier, :accepte, groupe_instructeur: gi_2) }
@ -715,6 +722,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(2) }
it { expect(subject['tous']).to eq(2) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(0) }
end
end
@ -726,6 +734,7 @@ describe Instructeur, type: :model do
it { expect(subject['traites']).to eq(0) }
it { expect(subject['tous']).to eq(0) }
it { expect(subject['archives']).to eq(1) }
it { expect(subject['expirant']).to eq(0) }
context 'and terminer dossiers on each of the others groups' do
let!(:archives_dossier_on_gi_2) { create(:dossier, :en_instruction, groupe_instructeur: gi_2, archived: true) }
@ -734,6 +743,19 @@ describe Instructeur, type: :model do
it { expect(subject['archives']).to eq(2) }
end
end
context 'with an expirants dossier' do
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) }
before { subject }
it { expect(subject['a_suivre']).to eq(1) }
it { expect(subject['suivis']).to eq(0) }
it { expect(subject['traites']).to eq(1) }
it { expect(subject['tous']).to eq(2) }
it { expect(subject['archives']).to eq(0) }
it { expect(subject['expirant']).to eq(2) }
end
end
end

View file

@ -2,8 +2,9 @@ describe ExpiredDossiersDeletionService do
let(:warning_period) { 1.month + 5.days }
let(:conservation_par_defaut) { 3.months }
let(:user) { create(:user) }
let(:procedure) { create(:procedure, :published) }
let(:procedure_2) { create(:procedure, :published) }
let(:procedure_opts) { {} }
let(:procedure) { create(:procedure, :published, procedure_opts) }
let(:procedure_2) { create(:procedure, :published, procedure_opts) }
let(:reference_date) { Date.parse("March 8") }
describe '#process_expired_dossiers_brouillon' do
@ -273,19 +274,18 @@ describe ExpiredDossiersDeletionService do
describe '#send_termine_expiration_notices' do
before { Timecop.freeze(reference_date) }
after { Timecop.return }
before do
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure)
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure_2)
let(:procedure_opts) do
{
procedure_expires_when_termine_enabled: true
}
end
before do
allow(DossierMailer).to receive(:notify_near_deletion_to_user).and_call_original
allow(DossierMailer).to receive(:notify_near_deletion_to_administration).and_call_original
end
context 'with a single dossier' do
let!(:dossier) { create(:dossier, :accepte, :followed, procedure: procedure, processed_at: processed_at) }
let!(:dossier) { create(:dossier, :followed, state: :accepte, procedure: procedure, processed_at: processed_at) }
before { ExpiredDossiersDeletionService.send_termine_expiration_notices }
@ -311,8 +311,8 @@ describe ExpiredDossiersDeletionService do
end
context 'with 2 dossiers to notice' do
let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
let!(:dossier_2) { create(:dossier, :accepte, procedure: procedure_2, user: user, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
let!(:dossier_1) { create(:dossier, state: :accepte, procedure: procedure, user: user, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
let!(:dossier_2) { create(:dossier, state: :accepte, procedure: procedure_2, user: user, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
let!(:instructeur) { create(:instructeur) }
@ -331,7 +331,7 @@ describe ExpiredDossiersDeletionService do
context 'when an instructeur is also administrateur' do
let!(:administrateur) { procedure.administrateurs.first }
let!(:dossier) { create(:dossier, :accepte, procedure: procedure, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
let!(:dossier) { create(:dossier, state: :accepte, procedure: procedure, processed_at: (conservation_par_defaut - 2.weeks + 1.day).ago) }
before do
administrateur.instructeur.followed_dossiers << dossier
@ -348,9 +348,10 @@ describe ExpiredDossiersDeletionService do
before { Timecop.freeze(reference_date) }
after { Timecop.return }
before do
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure)
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure_2)
let(:procedure_opts) do
{
procedure_expires_when_termine_enabled: true
}
end
before do
@ -359,7 +360,7 @@ describe ExpiredDossiersDeletionService do
end
context 'with a single dossier' do
let!(:dossier) { create(:dossier, :accepte, :followed, procedure: procedure, termine_close_to_expiration_notice_sent_at: notice_sent_at) }
let!(:dossier) { create(:dossier, :followed, :accepte, procedure: procedure, termine_close_to_expiration_notice_sent_at: notice_sent_at) }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
before { ExpiredDossiersDeletionService.delete_expired_termine_and_notify }

View file

@ -166,7 +166,7 @@ describe 'The user' do
end
scenario 'extends dossier experation date more than one time, ', js: true do
Flipper.enable(:procedure_process_expired_dossiers_termine)
simple_procedure.update(procedure_expires_when_termine_enabled: true)
allow(simple_procedure).to receive(:feature_enabled?).with(:procedure_process_expired_dossiers_termine).and_return(true)
user_old_dossier = create(:dossier,
procedure: simple_procedure,
@ -176,15 +176,14 @@ describe 'The user' do
visit brouillon_dossier_path(user_old_dossier)
expect(page).to have_css('.card-title', text: 'Votre dossier va expirer', visible: true)
click_on "Repousser sa suppression"
expect(page).not_to have_button("Repousser sa suppression")
find('#test-user-repousser-expiration').click
expect(page).not_to have_selector('#test-user-repousser-expiration')
Timecop.freeze(1.month.from_now) do
Timecop.freeze(simple_procedure.duree_conservation_dossiers_dans_ds.month.from_now) do
visit brouillon_dossier_path(user_old_dossier)
expect(page).to have_css('.card-title', text: 'Votre dossier va expirer', visible: true)
click_on "Repousser sa suppression"
expect(page).not_to have_button("Repousser sa suppression")
find('#test-user-repousser-expiration').click
expect(page).not_to have_selector('#test-user-repousser-expiration')
end
end

View file

@ -0,0 +1,66 @@
describe 'instructeur/dossiers/expiration_banner.html.haml', type: :view do
include DossierHelper
let(:duree_conservation_dossiers_dans_ds) { 3 }
let(:dossier) do
create(:dossier, state, attributes.merge(
id: 1,
state: state,
procedure: create(:procedure, procedure_expires_when_termine_enabled: expiration_enabled, duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds)
))
end
let(:i18n_key_state) { state }
subject do
render('instructeurs/dossiers/expiration_banner.html.haml',
dossier: dossier,
current_user: build(:user))
end
context 'with procedure having procedure_expires_when_termine_enabled not enabled' do
let(:expiration_enabled) { false }
let(:attributes) { { processed_at: 6.months.ago } }
let(:state) { :accepte }
it 'render estimated expiration date' do
expect(subject).not_to have_selector('.expires_at')
end
end
context 'with procedure having procedure_expires_when_termine_enabled enabled' do
let(:expiration_enabled) { true }
context 'with dossier.en_construction?' do
let(:attributes) { { en_construction_at: 6.months.ago } }
let(:state) { :en_construction }
it 'render estimated expiration date' do
expect(subject).to have_selector('.expires_at',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}",
date: safe_expiration_date(dossier),
duree_conservation_totale: duree_conservation_dossiers_dans_ds))
end
end
context 'with dossier.en_instruction?' do
let(:state) { :en_instruction }
let(:attributes) { {} }
it 'render estimated expiration date' do
expect(subject).to have_selector('p.expires_at_en_instruction',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}"))
end
end
context 'with dossier.en_processed_at?' do
let(:state) { :accepte }
let(:attributes) { {} }
it 'render estimated expiration date' do
allow(dossier).to receive(:processed_at).and_return(6.months.ago)
expect(subject).to have_selector('.expires_at',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}",
date: safe_expiration_date(dossier),
duree_conservation_totale: duree_conservation_dossiers_dans_ds))
end
end
end
end

View file

@ -0,0 +1,30 @@
describe 'instructeurs/procedures/_list.html.haml', type: :view do
let(:procedure) { create(:procedure, id: 1, procedure_expires_when_termine_enabled: expiration_enabled) }
subject do
render('instructeurs/procedures/list.html.haml',
p: procedure,
dossiers_count_per_procedure: 5,
dossiers_a_suivre_count_per_procedure: 2,
dossiers_archived_count_per_procedure: 1,
dossiers_termines_count_per_procedure: 1,
dossiers_expirant_count_per_procedure: 0,
followed_dossiers_count_per_procedure: 0,
procedure_ids_en_cours_with_notifications: [],
procedure_ids_termines_with_notifications: [])
end
context 'when procedure_expires_when_termine_enabled is true' do
let(:expiration_enabled) { true }
it 'contains link to expiring dossiers within procedure' do
expect(subject).to have_selector(%Q(a[href="#{instructeur_procedure_path(procedure, statut: 'expirant')}"]), count: 1)
end
end
context 'when procedure_expires_when_termine_enabled is false' do
let(:expiration_enabled) { false }
it 'does not contain link to expiring dossiers within procedure' do
expect(subject).to have_selector(%Q(a[href="#{instructeur_procedure_path(procedure, statut: 'expirant')}"]), count: 0)
end
end
end

View file

@ -0,0 +1,33 @@
describe 'instructeurs/procedures/_tabs.html.haml', type: :view do
let(:procedure) { create(:procedure, id: 1, procedure_expires_when_termine_enabled: expiration_enabled) }
before { allow(view).to receive(:current_instructeur).and_return(create(:instructeur)) }
subject do
render('instructeurs/procedures/tabs.html.haml',
procedure: procedure,
statut: 'tous',
a_suivre_count: 0,
suivis_count: 0,
traites_count: 0,
tous_count: 0,
archives_count: 0,
expirant_count: 0,
has_en_cours_notifications: false,
has_termine_notifications: false)
end
context 'when procedure_expires_when_termine_enabled is true' do
let(:expiration_enabled) { true }
it 'contains link to expiring dossiers within procedure' do
expect(subject).to have_selector(%Q(a[href="#{instructeur_procedure_path(procedure, statut: 'expirant')}"]), count: 1)
end
end
context 'when procedure_expires_when_termine_enabled is false' do
let(:expiration_enabled) { false }
it 'does not contain link to expiring dossiers within procedure' do
expect(subject).to have_selector(%Q(a[href="#{instructeur_procedure_path(procedure, statut: 'expirant')}"]), count: 0)
end
end
end

View file

@ -1,15 +1,23 @@
describe 'shared/dossiers/expiration_banner.html.haml', type: :view do
describe 'users/dossiers/expiration_banner.html.haml', type: :view do
include DossierHelper
let(:dossier) { build(:dossier, state, attributes.merge(id: 1, state: state)) }
let(:duree_conservation_dossiers_dans_ds) { 3 }
let(:dossier) do
create(:dossier, state, attributes.merge(
id: 1,
state: state,
conservation_extension: 1,
procedure: create(:procedure, procedure_expires_when_termine_enabled: expiration_enabled, duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds)
))
end
let(:i18n_key_state) { state }
subject do
render('shared/dossiers/expiration_banner.html.haml',
render('users/dossiers/expiration_banner.html.haml',
dossier: dossier,
current_user: build(:user))
end
context 'with procedure having procedure_process_expired_dossiers_termine not enabled' do
before { allow(dossier.procedure).to receive(:feature_enabled?).with(:procedure_process_expired_dossiers_termine).and_return(false) }
context 'with procedure having procedure_expires_when_termine_enabled not enabled' do
let(:expiration_enabled) { false }
let(:attributes) { { processed_at: 6.months.ago } }
let(:state) { :accepte }
@ -18,8 +26,8 @@ describe 'shared/dossiers/expiration_banner.html.haml', type: :view do
end
end
context 'with procedure having procedure_process_expired_dossiers_termine enabled' do
before { allow(dossier.procedure).to receive(:feature_enabled?).with(:procedure_process_expired_dossiers_termine).and_return(true) }
context 'with procedure having procedure_expires_when_termine_enabled enabled' do
let(:expiration_enabled) { true }
context 'with dossier.brouillon?' do
let(:attributes) { { created_at: 6.months.ago } }
@ -28,7 +36,8 @@ describe 'shared/dossiers/expiration_banner.html.haml', type: :view do
it 'render estimated expiration date' do
expect(subject).to have_selector('.expires_at',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}",
date: safe_expiration_date(dossier)))
date: safe_expiration_date(dossier),
duree_conservation_totale: duree_conservation_dossiers_dans_ds))
end
end
@ -39,7 +48,8 @@ describe 'shared/dossiers/expiration_banner.html.haml', type: :view do
it 'render estimated expiration date' do
expect(subject).to have_selector('.expires_at',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}",
date: safe_expiration_date(dossier)))
date: safe_expiration_date(dossier),
duree_conservation_totale: duree_conservation_dossiers_dans_ds))
end
end
@ -61,7 +71,8 @@ describe 'shared/dossiers/expiration_banner.html.haml', type: :view do
allow(dossier).to receive(:processed_at).and_return(6.months.ago)
expect(subject).to have_selector('.expires_at',
text: I18n.t("shared.dossiers.header.expires_at.#{i18n_key_state}",
date: safe_expiration_date(dossier)))
date: safe_expiration_date(dossier),
duree_conservation_totale: duree_conservation_dossiers_dans_ds))
end
end
end