Merge pull request #4986 from betagouv/dev

2020-04-02-01
This commit is contained in:
Pierre de La Morinerie 2020-04-02 12:46:36 +02:00 committed by GitHub
commit 2108bc7a4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 438 additions and 204 deletions

View file

@ -52,15 +52,14 @@ class Admin::ProceduresController < AdminController
def destroy
procedure = current_administrateur.procedures.find(params[:id])
if procedure.locked?
return render json: {}, status: 401
end
procedure.reset!
procedure.destroy
if procedure.can_be_deleted_by_administrateur?
procedure.discard_and_keep_track!(current_administrateur)
flash.notice = 'Démarche supprimée'
redirect_to admin_procedures_draft_path
else
render json: {}, status: 403
end
end
def publish_validate

View file

@ -116,7 +116,7 @@ module Instructeurs
def deleted_dossiers
@procedure = procedure
@deleted_dossiers = @procedure
.deleted_dossiers.where.not(state: :brouillon)
.deleted_dossiers
.order(:dossier_id)
.page params[:page]
end

View file

@ -30,6 +30,15 @@ module Manager
redirect_to manager_dossier_path(dossier)
end
def restore
dossier = Dossier.with_discarded.find(params[:id])
dossier.restore(current_administration)
flash[:notice] = "Le dossier #{dossier.id} a été restauré."
redirect_to manager_dossier_path(dossier)
end
def repasser_en_instruction
dossier = Dossier.find(params[:id])
dossier.repasser_en_instruction(current_administration)

View file

@ -8,7 +8,7 @@ module Manager
# this will be used to set the records shown on the `index` action.
def scoped_resource
if unfiltered_list?
# Don't display deleted dossiers in the unfiltered list…
# Don't display discarded demarches in the unfiltered list…
Procedure.kept
else
# … but allow them to be searched and displayed.
@ -22,10 +22,21 @@ module Manager
redirect_to manager_procedure_path(procedure)
end
def hide
procedure.hide!
flash[:notice] = "La démarche a bien été supprimée, en cas d'erreur contactez un développeur."
redirect_to manager_procedures_path
def discard
procedure.discard_and_keep_track!(current_administration)
logger.info("La démarche #{procedure.id} est supprimée par #{current_administration.email}")
flash[:notice] = "La démarche #{procedure.id} a été supprimée."
redirect_to manager_procedure_path(procedure)
end
def restore
procedure.restore(current_administration)
flash[:notice] = "La démarche #{procedure.id} a été restauré."
redirect_to manager_procedure_path(procedure)
end
def add_administrateur
@ -51,7 +62,7 @@ module Manager
private
def procedure
Procedure.find(params[:id])
@procedure ||= Procedure.with_discarded.find(params[:id])
end
def type_de_champ

View file

@ -71,7 +71,7 @@ module NewAdministrateur
def reaffecter
target_group = procedure.groupe_instructeurs.find(params[:target_group])
groupe_instructeur.dossiers.find_each do |dossier|
groupe_instructeur.dossiers.with_discarded.find_each do |dossier|
dossier.assign_to_groupe_instructeur(target_group, current_administrateur)
end

View file

@ -0,0 +1,10 @@
class DiscardedProceduresDeletionJob < CronJob
self.cron_expression = "0 7 * * *"
def perform(*args)
Procedure.discarded_expired.find_each do |procedure|
procedure.dossiers.with_discarded.destroy_all
procedure.destroy
end
end
end

View file

@ -70,6 +70,7 @@ class DossierMailer < ApplicationMailer
end
def notify_automatic_deletion_to_user(deleted_dossiers, to_email)
@state = deleted_dossiers.first.state
@subject = default_i18n_subject(count: deleted_dossiers.count)
@deleted_dossiers = deleted_dossiers
@ -83,15 +84,17 @@ class DossierMailer < ApplicationMailer
mail(to: to_email, subject: @subject)
end
def notify_en_construction_near_deletion_to_user(dossiers, to_email)
@subject = default_i18n_subject(count: dossiers.count)
def notify_near_deletion_to_user(dossiers, to_email)
@state = dossiers.first.state
@subject = default_i18n_subject(count: dossiers.count, state: @state)
@dossiers = dossiers
mail(to: to_email, subject: @subject)
end
def notify_en_construction_near_deletion_to_administration(dossiers, to_email)
@subject = default_i18n_subject(count: dossiers.count)
def notify_near_deletion_to_administration(dossiers, to_email)
@state = dossiers.first.state
@subject = default_i18n_subject(count: dossiers.count, state: @state)
@dossiers = dossiers
mail(to: to_email, subject: @subject)
@ -111,4 +114,17 @@ class DossierMailer < ApplicationMailer
mail(to: dossier.user.email, subject: @subject)
end
protected
# This is an override of `default_i18n_subject` method
# https://api.rubyonrails.org/v5.0.0/classes/ActionMailer/Base.html#method-i-default_i18n_subject
def default_i18n_subject(interpolations = {})
if interpolations[:state]
mailer_scope = self.class.mailer_name.tr('/', '.')
I18n.t("subject_#{interpolations[:state]}", interpolations.merge(scope: [mailer_scope, action_name]))
else
super
end
end
end

View file

@ -1,10 +1,13 @@
class DeletedDossier < ApplicationRecord
belongs_to :procedure
belongs_to :procedure, -> { with_discarded }, inverse_of: :deleted_dossiers
validates :dossier_id, uniqueness: true
enum reason: {
user_request: 'user_request',
manager_request: 'manager_request',
user_removed: 'user_removed',
procedure_removed: 'procedure_removed',
expired: 'expired'
}
@ -17,4 +20,8 @@ class DeletedDossier < ApplicationRecord
deleted_at: Time.zone.now
)
end
def procedure_removed?
reason == self.class.reasons.fetch(:procedure_removed)
end
end

View file

@ -212,9 +212,7 @@ class Dossier < ApplicationRecord
with_discarded
.discarded
.state_en_construction
.joins(:procedure)
.where('dossiers.hidden_at < ?', 1.month.ago)
.where(procedures: { hidden_at: nil })
end
scope :brouillon_near_procedure_closing_date, -> do
@ -487,6 +485,19 @@ class Dossier < ApplicationRecord
discard!
end
def restore(author, only_discarded_with_procedure = false)
if discarded?
deleted_dossier = DeletedDossier.find_by(dossier_id: id)
if !only_discarded_with_procedure || deleted_dossier&.procedure_removed?
if undiscard && keep_track_on_deletion? && en_construction?
deleted_dossier&.destroy
log_dossier_operation(author, :restaurer, self)
end
end
end
end
def after_passer_en_instruction(instructeur)
instructeur.follow(self)
@ -748,10 +759,12 @@ class Dossier < ApplicationRecord
followers_instructeurs.each do |instructeur|
if instructeur.groupe_instructeurs.exclude?(groupe_instructeur)
instructeur.unfollow(self)
if kept?
DossierMailer.notify_groupe_instructeur_changed(instructeur, self).deliver_later
end
end
end
end
def self.notify_draft_not_submitted
brouillon_near_procedure_closing_date

View file

@ -8,6 +8,7 @@ class DossierOperationLog < ApplicationRecord
refuser: 'refuser',
classer_sans_suite: 'classer_sans_suite',
supprimer: 'supprimer',
restaurer: 'restaurer',
modifier_annotation: 'modifier_annotation',
demander_un_avis: 'demander_un_avis'
}

View file

@ -1,6 +1,6 @@
class GroupeInstructeur < ApplicationRecord
DEFAULT_LABEL = 'défaut'
belongs_to :procedure
belongs_to :procedure, -> { with_discarded }, inverse_of: :groupe_instructeurs
has_many :assign_tos
has_many :instructeurs, through: :assign_tos, dependent: :destroy
has_many :dossiers

View file

@ -53,6 +53,12 @@ class Procedure < ApplicationRecord
scope :cloned_from_library, -> { where(cloned_from_library: true) }
scope :declarative, -> { where.not(declarative_with_state: nil) }
scope :discarded_expired, -> do
with_discarded
.discarded
.where('hidden_at < ?', 1.month.ago)
end
scope :for_api, -> {
includes(
:administrateurs,
@ -511,9 +517,34 @@ class Procedure < ApplicationRecord
groupe_instructeurs.count > 1
end
def hide!
def can_be_deleted_by_administrateur?
brouillon? || dossiers.state_instruction_commencee.empty?
end
def can_be_deleted_by_manager?
kept? && can_be_deleted_by_administrateur?
end
def discard_and_keep_track!(author)
if brouillon?
reset!
elsif publiee?
close!
end
dossiers.each do |dossier|
dossier.discard_and_keep_track!(author, :procedure_removed)
end
discard!
dossiers.discard_all
end
def restore(author)
if discarded? && undiscard
dossiers.with_discarded.discarded.find_each do |dossier|
dossier.restore(author, true)
end
end
end
def flipper_id

View file

@ -5,16 +5,30 @@ class ChampPolicy < ApplicationPolicy
return scope.none
end
# The join must be the same for all elements of the WHERE clause.
#
# NB: here we want to do `.left_outer_joins(dossier: [:invites, { :groupe_instructeur: :instructeurs }]))`,
# but for some reasons ActiveRecord <= 5.2 generates bogus SQL. Hence the manual version of it below.
joined_scope = scope
.joins('LEFT OUTER JOIN dossiers ON dossiers.id = champs.dossier_id')
.joins('LEFT OUTER JOIN invites ON invites.dossier_id = dossiers.id')
.joins('LEFT OUTER JOIN groupe_instructeurs ON groupe_instructeurs.id = dossiers.groupe_instructeur_id')
.joins('LEFT OUTER JOIN assign_tos ON assign_tos.groupe_instructeur_id = groupe_instructeurs.id')
.joins('LEFT OUTER JOIN instructeurs ON instructeurs.id = assign_tos.instructeur_id')
# Users can access public champs on their own dossiers.
resolved_scope = scope
.left_outer_joins(dossier: { groupe_instructeur: [:instructeurs] })
resolved_scope = joined_scope
.where('dossiers.user_id': user.id, private: false)
# Invited users can access public champs on dossiers they are invited to
invite_clause = joined_scope
.where('invites.user_id': user.id, private: false)
resolved_scope = resolved_scope.or(invite_clause)
if instructeur.present?
# Additionnaly, instructeurs can access private champs
# on dossiers they are allowed to instruct.
instructeur_clause = scope
.left_outer_joins(dossier: { groupe_instructeur: [:instructeurs] })
instructeur_clause = joined_scope
.where('instructeurs.id': instructeur.id, private: true)
resolved_scope = resolved_scope.or(instructeur_clause)
end

View file

@ -33,23 +33,7 @@ class ExpiredDossiersDeletionService
.en_construction_close_to_expiration
.without_en_construction_expiration_notice_sent
dossiers_close_to_expiration
.with_notifiable_procedure
.includes(:user)
.group_by(&:user)
.each do |(user, dossiers)|
DossierMailer.notify_en_construction_near_deletion_to_user(
dossiers,
user.email
).deliver_later
end
group_by_fonctionnaire_email(dossiers_close_to_expiration).each do |(email, dossiers)|
DossierMailer.notify_en_construction_near_deletion_to_administration(
dossiers,
email
).deliver_later
end
send_expiration_notices(dossiers_close_to_expiration)
dossiers_close_to_expiration.update_all(en_construction_close_to_expiration_notice_sent_at: Time.zone.now)
end
@ -72,7 +56,32 @@ class ExpiredDossiersDeletionService
end
def self.delete_expired_en_construction_and_notify
dossiers_to_remove = Dossier.en_construction_expired
delete_expired_and_notify(Dossier.en_construction_expired)
end
private
def self.send_expiration_notices(dossiers_close_to_expiration)
dossiers_close_to_expiration
.with_notifiable_procedure
.includes(:user)
.group_by(&:user)
.each do |(user, dossiers)|
DossierMailer.notify_near_deletion_to_user(
dossiers,
user.email
).deliver_later
end
group_by_fonctionnaire_email(dossiers_close_to_expiration).each do |(email, dossiers)|
DossierMailer.notify_near_deletion_to_administration(
dossiers.to_a,
email
).deliver_later
end
end
def self.delete_expired_and_notify(dossiers_to_remove)
dossiers_to_remove.each(&:expired_keep_track!)
dossiers_to_remove
@ -96,8 +105,6 @@ class ExpiredDossiersDeletionService
dossiers_to_remove.destroy_all
end
private
def self.group_by_fonctionnaire_email(dossiers)
dossiers
.with_notifiable_procedure

View file

@ -25,7 +25,7 @@
%td= link_to(try_format_datetime(procedure.created_at), admin_procedure_href)
%td
= link_to('Cloner', admin_procedure_clone_path(procedure.id), data: { method: :put }, class: 'btn-sm btn-primary clone-btn')
- if !procedure.locked?
- if !procedure.can_be_deleted_by_administrateur?
= link_to('X', url_for(controller: 'admin/procedures', action: :destroy, id: procedure.id), data: { method: :delete, confirm: "Confirmez-vous la suppression de la démarche ? \n\n Attention : toute suppression est définitive et sappliquera aux éventuels autres administrateurs de cette démarche !" }, class: 'btn-sm btn-danger')
= smart_listing.paginate

View file

@ -9,6 +9,7 @@
- @deleted_dossiers.each do |d|
%li n° #{d.dossier_id} (#{d.procedure.libelle})
%p= t('.footer', count: @deleted_dossiers.count)
- if @state == Dossier.states.fetch(:en_construction)
%p= t('.footer_en_construction', count: @deleted_dossiers.count)
= render partial: "layouts/mailers/signature"

View file

@ -4,13 +4,14 @@
Bonjour,
%p
= t('.header', count: @dossiers.count)
= t('.header_en_construction', count: @dossiers.count)
%ul
- @dossiers.each do |d|
%li
#{link_to("n° #{d.id} (#{d.procedure.libelle})", dossier_url(d))}. Retrouvez le dossier au format #{link_to("PDF", instructeur_dossier_url(d.procedure, d, format: :pdf))}
%p
= sanitize(t('.footer', count: @dossiers.count))
- if @state == Dossier.states.fetch(:en_construction)
= sanitize(t('.footer_en_construction', count: @dossiers.count))
= render partial: "layouts/mailers/signature"

View file

@ -4,7 +4,7 @@
Bonjour,
%p
= t('.header', count: @dossiers.count)
= t('.header_en_construction', count: @dossiers.count)
%ul
- @dossiers.each do |d|
%li

View file

@ -33,6 +33,8 @@ as well as a link to its edit page.
<% end %>
<% if dossier.can_be_deleted_by_manager? %>
<%= link_to 'Supprimer le dossier', discard_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
<% elsif dossier.discarded? && !dossier.procedure.discarded? %>
<%= link_to 'Restaurer le dossier', restore_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la restauration du dossier ?" } %>
<% end %>
</div>
</header>

View file

@ -22,6 +22,9 @@ as well as a link to its edit page.
<header class="main-content__header" role="banner">
<h1 class="main-content__page-title">
<%= content_for(:title) %>
<% if procedure.discarded? %>
(Supprimé)
<% end %>
</h1>
<div>
@ -39,8 +42,10 @@ as well as a link to its edit page.
<%= link_to 'whitelister', whitelist_manager_procedure_path(procedure), method: :post, class: 'button' %>
<% end %>
<% if !procedure.discarded? %>
<%= link_to 'supprimer la démarche', hide_manager_procedure_path(procedure), method: :post, class: 'button', data: { confirm: "Confirmez-vous la suppression de la démarche ?" } %>
<% if procedure.can_be_deleted_by_manager? %>
<%= link_to 'Supprimer la démarche', discard_manager_procedure_path(procedure), method: :post, class: 'button', data: { confirm: "Confirmez-vous la suppression de la démarche ?" } %>
<% elsif procedure.discarded? %>
<%= link_to 'Restaurer la démarche', restore_manager_procedure_path(procedure), method: :post, class: 'button', data: { confirm: "Confirmez-vous la restauration de la démarche ?" } %>
<% end %>
<div>

View file

@ -26,10 +26,10 @@
.flex
.inline-champ
= f.label :prenom, class: "required"
= f.text_field :prenom, class: "small", required: true
= f.text_field :prenom, class: "small", required: true, autocomplete: 'given-name'
.inline-champ
= f.label :nom, class: "required"
= f.text_field :nom, class: "small", required: true
= f.text_field :nom, class: "small", required: true, autocomplete: 'family-name'
- if @dossier.procedure.ask_birthday?
= f.label :birthdate, class: "required"

View file

@ -6,7 +6,7 @@
%h1 Créez-vous un compte demarches-simplifiees.fr
= f.label :email, "Email"
= f.text_field :email, autofocus: true, placeholder: "Votre adresse email"
= f.text_field :email, type: :email, autocomplete: 'username', autofocus: true, placeholder: "Votre adresse email"
.suspect-email.hidden
.email-suggestion-title
@ -20,7 +20,7 @@
Non
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum", 'aria-describedby':'8 caractères minimum'
= f.password_field :password, autocomplete: 'new-password', value: @user.password, placeholder: "8 caractères minimum", 'aria-describedby':'8 caractères minimum'
= f.submit "Créer un compte", class: "button large primary expand"

View file

@ -6,10 +6,10 @@
%h2.huge-title Connectez-vous
= f.label :email, "Email"
= f.text_field :email, autofocus: true
= f.text_field :email, type: :email, autocomplete: 'username', autofocus: true
= f.label :password, "Mot de passe"
= f.password_field :password
= f.password_field :password, autocomplete: 'current-password'
.auth-options
%div

View file

@ -6,6 +6,6 @@ fr:
user_request: Demande dusager
manager_request: Demande dadministration
user_removed: Suppression dun compte usager
procedure_removed: Suppression dune démarche
expired: Expiration
unknown: Inconnue

View file

@ -7,6 +7,6 @@ fr:
header:
one: "Le délai maximum de conservation du dossier suivant a été atteint, il a donc été supprimé :"
other: "Le délai maximum de conservation des dossiers suivants a été atteint, ils ont donc été supprimés :"
footer:
footer_en_construction:
one: "Le dossier ne sera pas traité, nous nous excusons de la gène occasionnée."
other: "Les dossiers ne seront pas traités, nous nous excusons de la gène occasionnée."

View file

@ -1,12 +1,12 @@
fr:
dossier_mailer:
notify_en_construction_near_deletion_to_administration:
subject:
notify_near_deletion_to_administration:
subject_en_construction:
one: Un dossier en construction va bientôt être supprimé
other: Des dossiers en construction vont bientôt être supprimés
header:
header_en_construction:
one: "Le dossier en construction suivant sera bientôt automatiquement supprimé :"
other: "Les dossiers en construction suivant seront bientôt automatiquement supprimés :"
footer:
footer_en_construction:
one: "Vous avez <b>un mois</b> pour commencer linstruction du dossier."
other: "Vous avez <b>un mois</b> pour commencer linstruction des dossiers."

View file

@ -1,10 +1,10 @@
fr:
dossier_mailer:
notify_en_construction_near_deletion_to_user:
subject:
notify_near_deletion_to_user:
subject_en_construction:
one: Un dossier en construction va bientôt être supprimé
other: Des dossiers en construction vont bientôt être supprimés
header:
header_en_construction:
one: "Afin de limiter la conservation de vos données personnelles, le dossier en construction suivant sera bientôt automatiquement supprimé :"
other: "Afin de limiter la conservation de vos données personnelles, les dossiers en construction suivant seront bientôt automatiquement supprimés :"
footer:

View file

@ -9,13 +9,15 @@ Rails.application.routes.draw do
resources :procedures, only: [:index, :show] do
post 'whitelist', on: :member
post 'draft', on: :member
post 'hide', on: :member
post 'discard', on: :member
post 'restore', on: :member
post 'add_administrateur', on: :member
post 'change_piece_justificative_template', on: :member
end
resources :dossiers, only: [:index, :show] do
post 'discard', on: :member
post 'restore', on: :member
post 'repasser_en_instruction', on: :member
end

View file

@ -0,0 +1,10 @@
namespace :after_party do
desc 'Deployment task: cleanup_deleted_dossiers'
task cleanup_deleted_dossiers: :environment do
puts "Running deploy task 'cleanup_deleted_dossiers'"
DeletedDossier.where(state: :brouillon).destroy_all
AfterParty::TaskRecord.create version: '20200326133630'
end
end

View file

@ -95,51 +95,65 @@ describe Admin::ProceduresController, type: :controller do
end
describe 'DELETE #destroy' do
let(:procedure_draft) { create :procedure_with_dossiers, administrateur: admin, instructeurs: [admin.instructeur], published_at: nil, closed_at: nil }
let(:procedure_published) { create :procedure_with_dossiers, administrateur: admin, instructeurs: [admin.instructeur], aasm_state: :publiee, published_at: Time.zone.now, closed_at: nil }
let(:procedure_closed) { create :procedure_with_dossiers, administrateur: admin, instructeurs: [admin.instructeur], aasm_state: :close, published_at: nil, closed_at: Time.zone.now }
let(:procedure_draft) { create(:procedure, administrateurs: [admin]) }
let(:procedure_published) { create(:procedure, :published, administrateurs: [admin]) }
let(:procedure_closed) { create(:procedure, :closed, administrateurs: [admin]) }
let(:procedure) { dossier.procedure }
subject { delete :destroy, params: { id: procedure.id } }
subject { delete :destroy, params: { id: procedure } }
context 'when the procedure is a draft' do
let!(:procedure) { procedure_draft }
context 'when the procedure is a brouillon' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_draft) }
it 'destroys the procedure' do
expect { subject }.to change { Procedure.count }.by(-1)
before { subject }
it 'discard the procedure' do
expect(procedure.reload.discarded?).to be_truthy
end
it 'deletes associated dossiers' do
subject
expect(Dossier.find_by(procedure_id: procedure.id)).to be_blank
expect(procedure.dossiers.with_discarded.count).to eq(0)
end
it 'redirects to the procedure drafts page' do
subject
expect(response).to redirect_to admin_procedures_draft_path
expect(flash[:notice]).to be_present
end
end
context 'when procedure is published' do
let!(:procedure) { procedure_published }
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_published) }
it { expect { subject }.not_to change { Procedure.count } }
it { expect { subject }.not_to change { Dossier.count } }
it { expect(subject.status).to eq 401 }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.close?).to be_truthy }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context 'when procedure is closed' do
let!(:procedure) { procedure_closed }
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_closed) }
it { expect { subject }.not_to change { Procedure.count } }
it { expect { subject }.not_to change { Dossier.count } }
it { expect(subject.status).to eq 401 }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context "when administrateur does not own the procedure" do
let(:procedure_not_owned) { create :procedure, administrateur: create(:administrateur), published_at: nil, closed_at: nil }
subject { delete :destroy, params: { id: procedure_not_owned.id } }
let(:dossier) { create(:dossier) }
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end

View file

@ -413,7 +413,6 @@ describe Instructeurs::ProceduresController, type: :controller do
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, instructeurs: [instructeur]) }
let(:deleted_dossier) { create(:deleted_dossier, procedure: procedure, state: :en_construction) }
let!(:deleted_dossier_brouillon) { create(:deleted_dossier, procedure: procedure, state: :brouillon) }
before do
sign_in(instructeur.user)

View file

@ -1,23 +1,43 @@
describe Manager::DossiersController, type: :controller do
describe '#discard' do
let(:administration) { create :administration }
let(:dossier) { create(:dossier) }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before { sign_in administration }
describe '#discard' do
let(:dossier) { create(:dossier, :en_construction) }
before do
sign_in administration
post :discard, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.discarded?).to be_truthy }
it { expect(deleted_dossier).not_to be_nil }
it { expect(deleted_dossier.reason).to eq("manager_request") }
it { expect(operations).to eq([:supprimer]) }
end
describe '#restore' do
let(:dossier) { create(:dossier, :en_construction) }
before do
dossier.discard_and_keep_track!(administration, :manager_request)
post :restore, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.kept?).to be_truthy }
it { expect(deleted_dossier).to be_nil }
it { expect(operations).to eq([:supprimer, :restaurer]) }
end
describe '#repasser_en_instruction' do
let(:administration) { create :administration }
let(:dossier) { create(:dossier, :accepte) }
before do
sign_in administration
post :repasser_en_instruction, params: { id: dossier.id }
dossier.reload
end

View file

@ -1,40 +1,76 @@
describe Manager::ProceduresController, type: :controller do
describe '#whitelist' do
let(:administration) { create :administration }
let!(:procedure) { create(:procedure) }
before { sign_in administration }
describe '#whitelist' do
let(:procedure) { create(:procedure) }
before do
sign_in administration
post :whitelist, params: { id: procedure.id }
procedure.reload
end
it { expect(procedure.whitelisted_at).not_to be_nil }
it { expect(procedure.whitelisted?).to be_truthy }
end
describe '#show' do
render_views
let(:administration) { create(:administration) }
let!(:procedure) { create(:procedure, :with_repetition) }
let(:procedure) { create(:procedure, :with_repetition) }
before do
sign_in(administration)
get :show, params: { id: procedure.id }
end
it { expect(response.body).to include('sub type de champ') }
end
describe '#discard' do
let(:dossier) { create(:dossier, :en_construction) }
let(:procedure) { dossier.procedure }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before do
post :discard, params: { id: procedure.id }
procedure.reload
dossier.reload
end
it { expect(procedure.discarded?).to be_truthy }
it { expect(dossier.discarded?).to be_truthy }
it { expect(deleted_dossier).not_to be_nil }
it { expect(deleted_dossier.reason).to eq("procedure_removed") }
it { expect(operations).to eq([:supprimer]) }
end
describe '#restore' do
let(:dossier) { create(:dossier, :en_construction) }
let(:procedure) { dossier.procedure }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before do
procedure.discard_and_keep_track!(administration)
post :restore, params: { id: procedure.id }
procedure.reload
end
it { expect(procedure.kept?).to be_truthy }
it { expect(dossier.kept?).to be_truthy }
it { expect(deleted_dossier).to be_nil }
it { expect(operations).to eq([:supprimer, :restaurer]) }
end
describe '#index' do
render_views
let(:administration) { create(:administration) }
context 'sort by dossiers' do
let!(:dossier) { create(:dossier) }
context 'sort by dossiers' do
before do
sign_in(administration)
get :index, params: { procedure: { direction: 'asc', order: 'dossiers' } }
end

View file

@ -121,7 +121,12 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
describe '#reaffecter' do
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
let!(:dossier12) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), groupe_instructeur: gi_1_1) }
let!(:dossier12) { create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: gi_1_1) }
let!(:dossier_discarded) do
dossier = create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: gi_1_1)
dossier.discard!
dossier
end
describe 'when the new group is a group of the procedure' do
before do
@ -135,8 +140,8 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end
it { expect(response).to redirect_to(procedure_groupe_instructeurs_path(procedure)) }
it { expect(gi_1_1.dossiers.count).to be(0) }
it { expect(gi_1_2.dossiers.count).to be(1) }
it { expect(gi_1_1.dossiers.with_discarded.count).to be(0) }
it { expect(gi_1_2.dossiers.with_discarded.count).to be(2) }
it { expect(gi_1_2.dossiers.last.id).to be(dossier12.id) }
it { expect(dossier12.groupe_instructeur.id).to be(gi_1_2.id) }
end

View file

@ -2,6 +2,7 @@ FactoryBot.define do
factory :deleted_dossier do
dossier_id { 1111 }
state { Dossier.states.fetch(:en_construction) }
reason { DeletedDossier.reasons.fetch(:user_request) }
deleted_at { Time.zone.now }
association :procedure, :published

View file

@ -194,11 +194,7 @@ FactoryBot.define do
end
trait :discarded do
after(:build) do |procedure, _evaluator|
procedure.path = generate(:published_path)
procedure.publish!
procedure.hide!
end
hidden_at { Time.zone.now }
end
trait :whitelisted do

View file

@ -74,11 +74,6 @@ RSpec.describe DossierMailer, type: :mailer do
describe '.notify_brouillon_near_deletion' do
let(:dossier) { create(:dossier) }
before do
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
@date_suppression = dossier.created_at + duree.months
end
subject { described_class.notify_brouillon_near_deletion([dossier], dossier.user.email) }
it { expect(subject.body).to include(" #{dossier.id} ") }
@ -95,14 +90,10 @@ RSpec.describe DossierMailer, type: :mailer do
end
describe '.notify_automatic_deletion_to_user' do
let(:dossier) { create(:dossier) }
describe 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) }
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) }
before do
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
@date_suppression = dossier.created_at + duree.months
end
subject { described_class.notify_automatic_deletion_to_user([deleted_dossier], dossier.user.email) }
it { expect(subject.to).to eq([dossier.user.email]) }
@ -111,6 +102,7 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.body).to include(dossier.procedure.libelle) }
it { expect(subject.body).to include("nous nous excusons de la gène occasionnée") }
end
end
describe '.notify_automatic_deletion_to_administration' do
let(:dossier) { create(:dossier) }
@ -122,15 +114,11 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.body).to include("#{dossier.id} (#{dossier.procedure.libelle})") }
end
describe '.notify_en_construction_near_deletion_to_administration' do
let(:dossier) { create(:dossier) }
describe '.notify_near_deletion_to_administration' do
describe 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) }
before do
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
@date_suppression = dossier.created_at + duree.months
end
subject { described_class.notify_en_construction_near_deletion_to_administration([dossier], dossier.user.email) }
subject { described_class.notify_near_deletion_to_administration([dossier], dossier.user.email) }
it { expect(subject.subject).to eq("Un dossier en construction va bientôt être supprimé") }
it { expect(subject.body).to include("#{dossier.id} ") }
@ -138,16 +126,13 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.body).to include("PDF") }
it { expect(subject.body).to include("Vous avez <b>un mois</b> pour commencer linstruction du dossier.") }
end
describe '.notify_en_construction_near_deletion_to_user' do
let(:dossier) { create(:dossier) }
before do
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
@date_suppression = dossier.created_at + duree.months
end
subject { described_class.notify_en_construction_near_deletion_to_user([dossier], dossier.user.email) }
describe '.notify_near_deletion_to_user' do
describe 'en_construction' do
let(:dossier) { create(:dossier, :en_construction) }
subject { described_class.notify_near_deletion_to_user([dossier], dossier.user.email) }
it { expect(subject.to).to eq([dossier.user.email]) }
it { expect(subject.subject).to eq("Un dossier en construction va bientôt être supprimé") }
@ -156,6 +141,7 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.body).to include("PDF") }
it { expect(subject.body).to include("Vous pouvez retrouver votre dossier pendant encore <b>un mois</b>. Vous navez rien à faire.") }
end
end
describe '.notify_groupe_instructeur_changed_to_instructeur' do
let(:dossier) { create(:dossier) }

View file

@ -21,11 +21,11 @@ class DossierMailerPreview < ActionMailer::Preview
end
def notify_en_construction_near_deletion_to_user
DossierMailer.notify_en_construction_near_deletion_to_user([dossier], usager_email)
DossierMailer.notify_near_deletion_to_user([dossier_en_construction], usager_email)
end
def notify_en_construction_near_deletion_to_administration
DossierMailer.notify_en_construction_near_deletion_to_administration([dossier, dossier], administration_email)
DossierMailer.notify_near_deletion_to_administration([dossier_en_construction, dossier_en_construction], administration_email)
end
def notify_brouillon_deletion
@ -79,6 +79,10 @@ class DossierMailerPreview < ActionMailer::Preview
Dossier.new(id: 47882, state: :en_instruction, procedure: procedure, user: User.new(email: "usager@example.com"))
end
def dossier_en_construction
Dossier.new(id: 47882, state: :en_construction, procedure: procedure, user: User.new(email: "usager@example.com"))
end
def procedure
Procedure.new(id: 1234, libelle: 'Dotation dÉquipement des Territoires Ruraux - Exercice 2019', service: service, logo: Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png'), auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days)
end

View file

@ -0,0 +1,14 @@
require 'spec_helper'
describe DeletedDossier do
let(:deleted_dossier) { create(:deleted_dossier) }
describe 'with discarded procedure' do
before do
deleted_dossier.procedure.discard!
deleted_dossier.reload
end
it { expect(deleted_dossier.procedure).not_to be_nil }
end
end

View file

@ -1226,6 +1226,8 @@ describe Dossier do
end
describe 'discarded_brouillon_expired and discarded_en_construction_expired' do
let(:administration) { create(:administration) }
before do
create(:dossier)
create(:dossier, :en_construction)
@ -1236,8 +1238,8 @@ describe Dossier do
create(:dossier).discard!
create(:dossier, :en_construction).discard!
create(:dossier).procedure.hide!
create(:dossier, :en_construction).procedure.hide!
create(:dossier).procedure.discard_and_keep_track!(administration)
create(:dossier, :en_construction).procedure.discard_and_keep_track!(administration)
end
Timecop.travel(1.week.ago) do
create(:dossier).discard!
@ -1246,6 +1248,16 @@ describe Dossier do
end
it { expect(Dossier.discarded_brouillon_expired.count).to eq(2) }
it { expect(Dossier.discarded_en_construction_expired.count).to eq(1) }
it { expect(Dossier.discarded_en_construction_expired.count).to eq(2) }
end
describe "discarded procedure dossier should be able to access it's procedure" do
let(:dossier) { create(:dossier) }
let(:procedure) { dossier.reload.procedure }
before { dossier.procedure.discard! }
it { expect(procedure).not_to be_nil }
it { expect(procedure.discarded?).to be_truthy }
end
end

View file

@ -836,7 +836,8 @@ describe Procedure do
end
end
describe "#hide!" do
describe "#discard_and_keep_track!" do
let(:administration) { create(:administration) }
let(:procedure) { create(:procedure) }
let!(:dossier) { create(:dossier, procedure: procedure) }
let!(:dossier2) { create(:dossier, procedure: procedure) }
@ -845,10 +846,10 @@ describe Procedure do
it { expect(Dossier.count).to eq(2) }
it { expect(Dossier.all).to include(dossier, dossier2) }
context "when hidding procedure" do
context "when discarding procedure" do
before do
instructeur.followed_dossiers << dossier
procedure.hide!
procedure.discard_and_keep_track!(administration)
instructeur.reload
end

View file

@ -1,6 +1,6 @@
describe ChampPolicy do
let(:champ) { create(:champ_text, private: private, dossier: dossier) }
let(:dossier) { create(:dossier, user: dossier_owner) }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
let(:dossier) { create(:dossier, procedure: procedure, user: dossier_owner) }
let(:dossier_owner) { create(:user) }
let(:signed_in_user) { create(:user) }
@ -8,24 +8,23 @@ describe ChampPolicy do
subject { Pundit.policy_scope(account, Champ) }
let(:champ) { dossier.champs.first }
let(:champ_private) { dossier.champs_private.first }
shared_examples_for 'they can access a public champ' do
let(:private) { false }
it { expect(subject.find_by(id: champ.id)).to eq(champ) }
end
shared_examples_for 'they cant access a public champ' do
let(:private) { false }
it { expect(subject.find_by(id: champ.id)).to eq(nil) }
end
shared_examples_for 'they can access a private champ' do
let(:private) { true }
it { expect(subject.find_by(id: champ.id)).to eq(champ) }
it { expect(subject.find_by(id: champ_private.id)).to eq(champ_private) }
end
shared_examples_for 'they cant access a private champ' do
let(:private) { true }
it { expect(subject.find_by(id: champ.id)).to eq(nil) }
it { expect(subject.find_by(id: champ_private.id)).to eq(nil) }
end
context 'when an user only has user rights' do
@ -36,6 +35,14 @@ describe ChampPolicy do
it_behaves_like 'they cant access a private champ'
end
context 'as a person invited on the dossier' do
let(:invite) { create(:invite, :with_user, dossier: dossier) }
let(:signed_in_user) { invite.user }
it_behaves_like 'they can access a public champ'
it_behaves_like 'they cant access a private champ'
end
context 'as another user' do
let(:signed_in_user) { create(:user) }

View file

@ -139,8 +139,8 @@ describe ExpiredDossiersDeletionService do
after { Timecop.return }
before do
allow(DossierMailer).to receive(:notify_en_construction_near_deletion_to_user).and_return(double(deliver_later: nil))
allow(DossierMailer).to receive(:notify_en_construction_near_deletion_to_administration).and_return(double(deliver_later: nil))
allow(DossierMailer).to receive(:notify_near_deletion_to_user).and_return(double(deliver_later: nil))
allow(DossierMailer).to receive(:notify_near_deletion_to_administration).and_return(double(deliver_later: nil))
end
context 'with a single dossier' do
@ -152,8 +152,8 @@ describe ExpiredDossiersDeletionService do
let(:en_construction_at) { (conservation_par_defaut - 1.month - 1.day).ago }
it { expect(dossier.reload.en_construction_close_to_expiration_notice_sent_at).to be_nil }
it { expect(DossierMailer).not_to have_received(:notify_en_construction_near_deletion_to_user) }
it { expect(DossierMailer).not_to have_received(:notify_en_construction_near_deletion_to_administration) }
it { expect(DossierMailer).not_to have_received(:notify_near_deletion_to_user) }
it { expect(DossierMailer).not_to have_received(:notify_near_deletion_to_administration) }
end
context 'when the dossier is near deletion' do
@ -161,11 +161,11 @@ describe ExpiredDossiersDeletionService do
it { expect(dossier.reload.en_construction_close_to_expiration_notice_sent_at).not_to be_nil }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with([dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with([dossier], dossier.followers_instructeurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], dossier.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], dossier.followers_instructeurs.first.email) }
end
end
@ -181,12 +181,12 @@ describe ExpiredDossiersDeletionService do
ExpiredDossiersDeletionService.send_en_construction_expiration_notices
end
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).exactly(3).times }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).with(match_array([dossier_1, dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with(match_array([dossier_1, dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with([dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).exactly(3).times }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with(match_array([dossier_1, dossier_2]), user.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with(match_array([dossier_1, dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier_2], dossier_2.procedure.administrateurs.first.email) }
end
context 'when an instructeur is also administrateur' do
@ -199,9 +199,9 @@ describe ExpiredDossiersDeletionService do
ExpiredDossiersDeletionService.send_en_construction_expiration_notices
end
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_en_construction_near_deletion_to_administration).with([dossier], administrateur.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_user).with([dossier], dossier.user.email) }
it { expect(DossierMailer).to have_received(:notify_near_deletion_to_administration).with([dossier], administrateur.email) }
end
end