Merge pull request #5330 from betagouv/4897-display-instructeur-decision

#4897 Affiche l'instructeur qui a rendu la décision sur un dossier
This commit is contained in:
krichtof 2020-07-08 18:39:06 +02:00 committed by GitHub
commit c074864c2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 229 additions and 109 deletions

View file

@ -689,6 +689,7 @@ Rails/CreateTableWithTimestamps:
- db/migrate/2016*.rb - db/migrate/2016*.rb
- db/migrate/2017*.rb - db/migrate/2017*.rb
- db/migrate/2018*.rb - db/migrate/2018*.rb
- db/migrate/20200630140356_create_traitements.rb
Rails/Date: Rails/Date:
Enabled: false Enabled: false

View file

@ -249,9 +249,11 @@ class StatsController < ApplicationController
min_date = 11.months.ago min_date = 11.months.ago
max_date = Time.zone.now.to_date max_date = Time.zone.now.to_date
processed_dossiers = dossiers processed_dossiers = Traitement.includes(:dossier)
.where(dossier_id: dossiers)
.where('dossiers.state' => Dossier::TERMINE)
.where(:processed_at => min_date..max_date) .where(:processed_at => min_date..max_date)
.pluck(:groupe_instructeur_id, :en_construction_at, :processed_at) .pluck('dossiers.groupe_instructeur_id', 'dossiers.en_construction_at', :processed_at)
# Group dossiers by month # Group dossiers by month
processed_dossiers_by_month = processed_dossiers processed_dossiers_by_month = processed_dossiers
@ -290,11 +292,13 @@ class StatsController < ApplicationController
min_date = 11.months.ago min_date = 11.months.ago
max_date = Time.zone.now.to_date max_date = Time.zone.now.to_date
processed_dossiers = dossiers processed_dossiers = Traitement.includes(:dossier)
.where(dossier: dossiers)
.where('dossiers.state' => Dossier::TERMINE)
.where(:processed_at => min_date..max_date) .where(:processed_at => min_date..max_date)
.pluck( .pluck(
:groupe_instructeur_id, 'dossiers.groupe_instructeur_id',
Arel.sql('EXTRACT(EPOCH FROM (en_construction_at - created_at)) / 60 AS processing_time'), Arel.sql('EXTRACT(EPOCH FROM (dossiers.en_construction_at - dossiers.created_at)) / 60 AS processing_time'),
:processed_at :processed_at
) )

View file

@ -149,7 +149,7 @@ module Users
errors = update_dossier_and_compute_errors errors = update_dossier_and_compute_errors
if passage_en_construction? && errors.blank? if passage_en_construction? && errors.blank?
@dossier.en_construction! @dossier.passer_en_construction!
NotificationMailer.send_initiated_notification(@dossier).deliver_later NotificationMailer.send_initiated_notification(@dossier).deliver_later
@dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later

View file

@ -42,6 +42,7 @@ class Dossier < ApplicationRecord
has_many :followers_instructeurs, through: :follows, source: :instructeur has_many :followers_instructeurs, through: :follows, source: :instructeur
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
has_many :avis, inverse_of: :dossier, dependent: :destroy has_many :avis, inverse_of: :dossier, dependent: :destroy
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy
has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier
@ -128,6 +129,7 @@ class Dossier < ApplicationRecord
:individual, :individual,
:followers_instructeurs, :followers_instructeurs,
:avis, :avis,
:traitements,
etablissement: :champ, etablissement: :champ,
champs: { champs: {
etablissement: :champ, etablissement: :champ,
@ -172,6 +174,7 @@ class Dossier < ApplicationRecord
justificatif_motivation_attachment: :blob, justificatif_motivation_attachment: :blob,
attestation: [], attestation: [],
avis: { piece_justificative_file_attachment: :blob }, avis: { piece_justificative_file_attachment: :blob },
traitements: [],
etablissement: [], etablissement: [],
individual: [], individual: [],
user: []) user: [])
@ -198,10 +201,9 @@ class Dossier < ApplicationRecord
.joins(:procedure) .joins(:procedure)
.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 }) .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 end
scope :termine_close_to_expiration, -> do def self.termine_close_to_expiration
state_termine dossier_ids = Traitement.termine_close_to_expiration.pluck(:dossier_id).uniq
.joins(:procedure) Dossier.where(id: dossier_ids)
.where("dossiers.processed_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end end
scope :brouillon_expired, -> do scope :brouillon_expired, -> do
@ -249,7 +251,7 @@ class Dossier < ApplicationRecord
end end
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) } scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) } scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: [], traitements: []) }
scope :with_notifications, -> do scope :with_notifications, -> do
# This scope is meant to be composed, typically with Instructeur.followed_dossiers, which means that the :follows table is already INNER JOINed; # This scope is meant to be composed, typically with Instructeur.followed_dossiers, which means that the :follows table is already INNER JOINed;
@ -282,7 +284,6 @@ class Dossier < ApplicationRecord
delegate :types_de_champ, to: :procedure delegate :types_de_champ, to: :procedure
delegate :france_connect_information, to: :user delegate :france_connect_information, to: :user
before_validation :update_state_dates, if: -> { state_changed? }
before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? } before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? }
before_save :update_search_terms before_save :update_search_terms
@ -294,6 +295,16 @@ class Dossier < ApplicationRecord
validates :individual, presence: true, if: -> { procedure.for_individual? } validates :individual, presence: true, if: -> { procedure.for_individual? }
validates :groupe_instructeur, presence: true validates :groupe_instructeur, presence: true
def motivation
return nil if !termine?
traitements.any? ? traitements.last.motivation : read_attribute(:motivation)
end
def processed_at
return nil if !termine?
traitements.any? ? traitements.last.processed_at : read_attribute(:processed_at)
end
def update_search_terms def update_search_terms
self.search_terms = [ self.search_terms = [
user&.email, user&.email,
@ -508,27 +519,29 @@ class Dossier < ApplicationRecord
end end
end end
def after_passer_en_construction
update!(en_construction_at: Time.zone.now) if self.en_construction_at.nil?
end
def after_passer_en_instruction(instructeur) def after_passer_en_instruction(instructeur)
instructeur.follow(self) instructeur.follow(self)
update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil?
log_dossier_operation(instructeur, :passer_en_instruction) log_dossier_operation(instructeur, :passer_en_instruction)
end end
def after_passer_automatiquement_en_instruction def after_passer_automatiquement_en_instruction
update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil?
log_automatic_dossier_operation(:passer_en_instruction) log_automatic_dossier_operation(:passer_en_instruction)
end end
def after_repasser_en_construction(instructeur) def after_repasser_en_construction(instructeur)
self.en_instruction_at = nil
save!
log_dossier_operation(instructeur, :repasser_en_construction) log_dossier_operation(instructeur, :repasser_en_construction)
end end
def after_repasser_en_instruction(instructeur) def after_repasser_en_instruction(instructeur)
self.archived = false self.archived = false
self.processed_at = nil self.en_instruction_at = Time.zone.now
self.motivation = nil
attestation&.destroy attestation&.destroy
save! save!
@ -537,7 +550,7 @@ class Dossier < ApplicationRecord
end end
def after_accepter(instructeur, motivation, justificatif = nil) def after_accepter(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:accepte), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -553,6 +566,7 @@ class Dossier < ApplicationRecord
end end
def after_accepter_automatiquement def after_accepter_automatiquement
self.traitements.build(state: Dossier.states.fetch(:accepte), instructeur_email: nil, motivation: nil, processed_at: Time.zone.now)
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
if attestation.nil? if attestation.nil?
@ -565,7 +579,7 @@ class Dossier < ApplicationRecord
end end
def after_refuser(instructeur, motivation, justificatif = nil) def after_refuser(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:refuse), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -577,7 +591,7 @@ class Dossier < ApplicationRecord
end end
def after_classer_sans_suite(instructeur, motivation, justificatif = nil) def after_classer_sans_suite(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:sans_suite), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -766,16 +780,6 @@ class Dossier < ApplicationRecord
end end
end end
def update_state_dates
if en_construction? && !self.en_construction_at
self.en_construction_at = Time.zone.now
elsif en_instruction? && !self.en_instruction_at
self.en_instruction_at = Time.zone.now
elsif TERMINE.include?(state) && !self.processed_at
self.processed_at = Time.zone.now
end
end
def send_dossier_received def send_dossier_received
if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte? if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte?
NotificationMailer.send_dossier_received(self).deliver_later NotificationMailer.send_dossier_received(self).deliver_later

View file

@ -161,7 +161,7 @@ class Instructeur < ApplicationRecord
h = { h = {
nb_en_construction: groupe.dossiers.en_construction.count, nb_en_construction: groupe.dossiers.en_construction.count,
nb_en_instruction: groupe.dossiers.en_instruction.count, nb_en_instruction: groupe.dossiers.en_instruction.count,
nb_accepted: groupe.dossiers.accepte.where(processed_at: Time.zone.yesterday.beginning_of_day..Time.zone.yesterday.end_of_day).count, nb_accepted: Traitement.where(dossier: groupe.dossiers.accepte, processed_at: Time.zone.yesterday.beginning_of_day..Time.zone.yesterday.end_of_day).count,
nb_notification: notifications_for_procedure(procedure, :not_archived).count nb_notification: notifications_for_procedure(procedure, :not_archived).count
} }

View file

@ -438,7 +438,15 @@ class Procedure < ApplicationRecord
end end
def usual_traitement_time def usual_traitement_time
percentile_time(:en_construction_at, :processed_at, 90) times = Traitement.includes(:dossier)
.where(state: Dossier::TERMINE)
.where(processed_at: 1.month.ago..Time.zone.now)
.pluck('dossiers.en_construction_at', :processed_at)
.map { |(en_construction_at, processed_at)| processed_at - en_construction_at }
if times.present?
times.percentile(90).ceil
end
end end
def populate_champ_stable_ids def populate_champ_stable_ids
@ -610,18 +618,6 @@ class Procedure < ApplicationRecord
end end
end end
def percentile_time(start_attribute, end_attribute, p)
times = dossiers
.where.not(start_attribute => nil, end_attribute => nil)
.where(end_attribute => 1.month.ago..Time.zone.now)
.pluck(start_attribute, end_attribute)
.map { |(start_date, end_date)| end_date - start_date }
if times.present?
times.percentile(p).ceil
end
end
def ensure_path_exists def ensure_path_exists
if self.path.blank? if self.path.blank?
self.path = SecureRandom.uuid self.path = SecureRandom.uuid

10
app/models/traitement.rb Normal file
View file

@ -0,0 +1,10 @@
class Traitement < ApplicationRecord
belongs_to :dossier
scope :termine_close_to_expiration, -> do
joins(dossier: :procedure)
.where(state: Dossier::TERMINE)
.where('dossiers.state' => Dossier::TERMINE)
.where("traitements.processed_at + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: Dossier::INTERVAL_BEFORE_EXPIRATION })
end
end

View file

@ -156,7 +156,7 @@ def add_etats_dossier(pdf, dossier)
if dossier.en_instruction_at.present? if dossier.en_instruction_at.present?
format_in_2_columns(pdf, "En instruction le", try_format_date(dossier.en_instruction_at)) format_in_2_columns(pdf, "En instruction le", try_format_date(dossier.en_instruction_at))
end end
if dossier.processed_at?.present? if dossier.processed_at.present?
format_in_2_columns(pdf, "Décision le", try_format_date(dossier.processed_at)) format_in_2_columns(pdf, "Décision le", try_format_date(dossier.processed_at))
end end

View file

@ -0,0 +1,19 @@
.tab-title Décisions rendues
- if traitements.any?
%ul.tab-list
- traitements.each do |traitement|
- if traitement.instructeur_email.present?
%li
= "Le #{l(traitement.processed_at, format: '%d %B %Y à %R')}, "
= traitement.instructeur_email
a
%strong= t(traitement.state, scope: 'activerecord.attributes.traitement.state').downcase
ce dossier
- else
%li
= "Le #{l(traitement.processed_at, format: '%d %B %Y à %R')}, "
ce dossier a été
%strong= t(traitement.state, scope: 'activerecord.attributes.traitement.state').downcase
- else
%p.tab-paragraph Aucune décision n'a été rendue

View file

@ -13,3 +13,5 @@
= render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @avis_emails, title: "Personnes à qui un avis a été demandé", blank: "Aucun avis n'a été demandé" } = render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @avis_emails, title: "Personnes à qui un avis a été demandé", blank: "Aucun avis n'a été demandé" }
= render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @invites_emails, title: "Personnes invitées à consulter ce dossier", blank: "Aucune personne n'a été invitée à consulter ce dossier" } = render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @invites_emails, title: "Personnes invitées à consulter ce dossier", blank: "Aucune personne n'a été invitée à consulter ce dossier" }
= render partial: 'instructeurs/dossiers/decisions_rendues_block', locals: { traitements: @dossier.traitements }

View file

@ -9,13 +9,13 @@ fr:
montant_projet: 'Le montant du projet' montant_projet: 'Le montant du projet'
montant_aide_demande: "Le montant daide demandée" montant_aide_demande: "Le montant daide demandée"
date_previsionnelle: "La date de début prévisionnelle" date_previsionnelle: "La date de début prévisionnelle"
state: state: &state
brouillon: "Brouillon" brouillon: "Brouillon"
en_construction: "En construction" en_construction: "En construction"
en_instruction: "En instruction" en_instruction: "En instruction"
accepte: "Accepté" accepte: "Accepté"
refuse: "Refusé" refuse: "Refusé"
sans_suite: "Sans suite" sans_suite: "Classé sans suite"
autorisation_donnees: Acceptation des CGU autorisation_donnees: Acceptation des CGU
state/brouillon: Brouillon state/brouillon: Brouillon
state/en_construction: En construction state/en_construction: En construction
@ -23,3 +23,6 @@ fr:
state/accepte: Accepté state/accepte: Accepté
state/refuse: Refusé state/refuse: Refusé
state/sans_suite: Sans suite state/sans_suite: Sans suite
traitement:
state:
<<: *state

View file

@ -0,0 +1,11 @@
class CreateTraitements < ActiveRecord::Migration[5.2]
def change
create_table :traitements do |t|
t.references :dossier, foreign_key: true
t.references :instructeur, foreign_key: true
t.string :motivation
t.string :state
t.timestamp :processed_at
end
end
end

View file

@ -0,0 +1,6 @@
class RemoveInstructeurIdAndAddInstructeurEmailToTraitements < ActiveRecord::Migration[5.2]
def change
add_column :traitements, :instructeur_email, :string
remove_column :traitements, :instructeur_id
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_06_11_122406) do ActiveRecord::Schema.define(version: 2020_07_07_082256) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -560,6 +560,15 @@ ActiveRecord::Schema.define(version: 2020_06_11_122406) do
t.string "version", null: false t.string "version", null: false
end end
create_table "traitements", force: :cascade do |t|
t.bigint "dossier_id"
t.string "motivation"
t.string "state"
t.datetime "processed_at"
t.string "instructeur_email"
t.index ["dossier_id"], name: "index_traitements_on_dossier_id"
end
create_table "trusted_device_tokens", force: :cascade do |t| create_table "trusted_device_tokens", force: :cascade do |t|
t.string "token", null: false t.string "token", null: false
t.bigint "instructeur_id" t.bigint "instructeur_id"
@ -660,6 +669,7 @@ ActiveRecord::Schema.define(version: 2020_06_11_122406) do
add_foreign_key "received_mails", "procedures" add_foreign_key "received_mails", "procedures"
add_foreign_key "refused_mails", "procedures" add_foreign_key "refused_mails", "procedures"
add_foreign_key "services", "administrateurs" add_foreign_key "services", "administrateurs"
add_foreign_key "traitements", "dossiers"
add_foreign_key "trusted_device_tokens", "instructeurs" add_foreign_key "trusted_device_tokens", "instructeurs"
add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id" add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id"
add_foreign_key "users", "administrateurs" add_foreign_key "users", "administrateurs"

View file

@ -0,0 +1,18 @@
namespace :after_party do
desc 'Deployment task: add_traitements_from_dossiers'
task add_traitements_from_dossiers: :environment do
puts "Running deploy task 'add_traitements_from_dossiers'"
dossiers_termines = Dossier.state_termine
progress = ProgressReport.new(dossiers_termines.count)
dossiers_termines.find_each do |dossier|
dossier.traitements.create!(state: dossier.state, motivation: dossier.motivation, processed_at: dossier.processed_at)
progress.inc
end
progress.finish
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord.create version: '20200630154829'
end
end

View file

@ -596,7 +596,7 @@ describe API::V2::GraphqlController do
it "should fail" do it "should fail" do
expect(gql_errors).to eq(nil) expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierRefuser: { expect(gql_data).to eq(dossierRefuser: {
errors: [{ message: "Le dossier est déjà sans suite" }], errors: [{ message: "Le dossier est déjà classé sans suite" }],
dossier: nil dossier: nil
}) })
end end

View file

@ -194,7 +194,7 @@ describe Instructeurs::DossiersController, type: :controller do
describe '#terminer' do describe '#terminer' do
context "with refuser" do context "with refuser" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
end end
@ -235,7 +235,7 @@ describe Instructeurs::DossiersController, type: :controller do
context "with classer_sans_suite" do context "with classer_sans_suite" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
end end
context 'without attachment' do context 'without attachment' do
@ -277,7 +277,7 @@ describe Instructeurs::DossiersController, type: :controller do
context "with accepter" do context "with accepter" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
expect(NotificationMailer).to receive(:send_closed_notification) expect(NotificationMailer).to receive(:send_closed_notification)

View file

@ -106,26 +106,23 @@ describe StatsController, type: :controller do
before do before do
procedure_1 = FactoryBot.create(:procedure) procedure_1 = FactoryBot.create(:procedure)
procedure_2 = FactoryBot.create(:procedure) procedure_2 = FactoryBot.create(:procedure)
dossier_p1_a = FactoryBot.create(:dossier, dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 3.days) :processed_at => 2.months.ago.beginning_of_month + 3.days)
dossier_p1_b = FactoryBot.create(:dossier, dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 1.month.ago.beginning_of_month, :en_construction_at => 1.month.ago.beginning_of_month,
:processed_at => 1.month.ago.beginning_of_month + 5.days) :processed_at => 1.month.ago.beginning_of_month + 5.days)
dossier_p2_a = FactoryBot.create(:dossier, dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2, :procedure => procedure_2,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 4.days) :processed_at => 2.months.ago.beginning_of_month + 4.days)
# Write directly in the DB to avoid the before_validation hook
Dossier.update_all(state: Dossier.states.fetch(:accepte))
@expected_hash = { @expected_hash = {
(2.months.ago.beginning_of_month).to_s => 3.0, (2.months.ago.beginning_of_month).to_s => 3.0,
(1.month.ago.beginning_of_month).to_s => 5.0 (1.month.ago.beginning_of_month).to_s => 5.0
@ -154,30 +151,27 @@ describe StatsController, type: :controller do
before do before do
procedure_1 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24) procedure_1 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24)
procedure_2 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48) procedure_2 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48)
dossier_p1_a = FactoryBot.create(:dossier, dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 30.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 30.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_b = FactoryBot.create(:dossier, dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 10.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 10.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 1.month.ago.beginning_of_month, :created_at => 1.month.ago.beginning_of_month,
:en_construction_at => 1.month.ago.beginning_of_month + 50.minutes, :en_construction_at => 1.month.ago.beginning_of_month + 50.minutes,
:processed_at => 1.month.ago.beginning_of_month + 1.day) :processed_at => 1.month.ago.beginning_of_month + 1.day)
dossier_p2_a = FactoryBot.create(:dossier, dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2, :procedure => procedure_2,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 80.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 80.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
# Write directly in the DB to avoid the before_validation hook
Dossier.update_all(state: Dossier.states.fetch(:accepte))
@expected_hash = { @expected_hash = {
(2.months.ago.beginning_of_month).to_s => 30.0, (2.months.ago.beginning_of_month).to_s => 30.0,
(1.month.ago.beginning_of_month).to_s => 50.0 (1.month.ago.beginning_of_month).to_s => 50.0

View file

@ -657,7 +657,7 @@ describe Users::DossiersController, type: :controller do
let!(:invite) { create(:invite, dossier: dossier, user: user) } let!(:invite) { create(:invite, dossier: dossier, user: user) }
before do before do
dossier.en_construction! dossier.passer_en_construction!
subject subject
end end

View file

@ -127,11 +127,23 @@ FactoryBot.define do
end end
trait :accepte do trait :accepte do
after(:create) do |dossier, _evaluator| transient do
motivation { nil }
processed_at { nil }
end
after(:create) do |dossier, evaluator|
dossier.state = Dossier.states.fetch(:accepte) dossier.state = Dossier.states.fetch(:accepte)
dossier.en_construction_at ||= dossier.created_at + 1.minute processed_at = evaluator.processed_at
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute if processed_at.present?
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.en_construction_at ||= processed_at - 2.minutes
dossier.en_instruction_at ||= processed_at - 1.minute
dossier.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: processed_at, motivation: evaluator.motivation)
else
dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: dossier.en_instruction_at + 1.minute, motivation: evaluator.motivation)
end
dossier.save! dossier.save!
end end
end end
@ -141,7 +153,7 @@ FactoryBot.define do
dossier.state = Dossier.states.fetch(:refuse) dossier.state = Dossier.states.fetch(:refuse)
dossier.en_construction_at ||= dossier.created_at + 1.minute dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.traitements.build(state: Dossier.states.fetch(:refuse), processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end
@ -151,14 +163,14 @@ FactoryBot.define do
dossier.state = Dossier.states.fetch(:sans_suite) dossier.state = Dossier.states.fetch(:sans_suite)
dossier.en_construction_at ||= dossier.created_at + 1.minute dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.traitements.build(state: Dossier.states.fetch(:sans_suite), processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end
trait :with_motivation do trait :with_motivation do
after(:create) do |dossier, _evaluator| after(:create) do |dossier, _evaluator|
dossier.motivation = case dossier.state motivation = case dossier.state
when Dossier.states.fetch(:refuse) when Dossier.states.fetch(:refuse)
'Lentreprise concernée nest pas agréée.' 'Lentreprise concernée nest pas agréée.'
when Dossier.states.fetch(:sans_suite) when Dossier.states.fetch(:sans_suite)
@ -166,6 +178,7 @@ FactoryBot.define do
else else
'Vous avez validé les conditions.' 'Vous avez validé les conditions.'
end end
dossier.traitements.last.update!(motivation: motivation)
end end
end end

View file

@ -156,7 +156,7 @@ RSpec.describe DossierHelper, type: :helper do
it 'sans_suite is traité' do it 'sans_suite is traité' do
dossier.sans_suite! dossier.sans_suite!
expect(subject).to eq('Sans suite') expect(subject).to eq('Classé sans suite')
end end
it 'refuse is traité' do it 'refuse is traité' do

View file

@ -35,6 +35,7 @@ describe TagsSubstitutionConcern, type: :model do
let(:individual) { nil } let(:individual) { nil }
let(:etablissement) { create(:etablissement) } let(:etablissement) { create(:etablissement) }
let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) } let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) }
let(:instructeur) { create(:instructeur) }
before { Timecop.freeze(Time.zone.now) } before { Timecop.freeze(Time.zone.now) }
@ -242,7 +243,7 @@ describe TagsSubstitutionConcern, type: :model do
end end
context 'when the dossier has a motivation' do context 'when the dossier has a motivation' do
let(:dossier) { create(:dossier, motivation: 'motivation') } let(:dossier) { create(:dossier, :accepte, motivation: 'motivation') }
context 'and the template has some dossier tags' do context 'and the template has some dossier tags' do
let(:template) { '--motivation-- --numéro du dossier--' } let(:template) { '--motivation-- --numéro du dossier--' }
@ -318,9 +319,13 @@ describe TagsSubstitutionConcern, type: :model do
context "when using a date tag" do context "when using a date tag" do
before do before do
dossier.en_construction_at = Time.zone.local(2001, 2, 3) Timecop.freeze(Time.zone.local(2001, 2, 3))
dossier.en_instruction_at = Time.zone.local(2004, 5, 6) dossier.passer_en_construction!
dossier.processed_at = Time.zone.local(2007, 8, 9) Timecop.freeze(Time.zone.local(2004, 5, 6))
dossier.passer_en_instruction!(instructeur)
Timecop.freeze(Time.zone.local(2007, 8, 9))
dossier.accepter!(instructeur, nil, nil)
Timecop.return
end end
context "with date de dépôt" do context "with date de dépôt" do

View file

@ -209,8 +209,17 @@ describe Dossier do
let(:date1) { 1.day.ago } let(:date1) { 1.day.ago }
let(:date2) { 1.hour.ago } let(:date2) { 1.hour.ago }
let(:date3) { 1.minute.ago } let(:date3) { 1.minute.ago }
let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure, en_construction_at: date1, en_instruction_at: date2, processed_at: date3, motivation: "Motivation") } let(:dossier) do
let!(:follow) { create(:follow, instructeur: instructeur, dossier: dossier) } d = create(:dossier, :with_entreprise, user: user, procedure: procedure)
Timecop.freeze(date1)
d.passer_en_construction!
Timecop.freeze(date2)
d.passer_en_instruction!(instructeur)
Timecop.freeze(date3)
d.accepter!(instructeur, "Motivation", nil)
Timecop.return
d
end
describe "followers_instructeurs" do describe "followers_instructeurs" do
let(:non_following_instructeur) { create(:instructeur) } let(:non_following_instructeur) { create(:instructeur) }
@ -346,13 +355,14 @@ describe Dossier do
let(:state) { Dossier.states.fetch(:brouillon) } let(:state) { Dossier.states.fetch(:brouillon) }
let(:dossier) { create(:dossier, state: state) } let(:dossier) { create(:dossier, state: state) }
let(:beginning_of_day) { Time.zone.now.beginning_of_day } let(:beginning_of_day) { Time.zone.now.beginning_of_day }
let(:instructeur) { create(:instructeur) }
before { Timecop.freeze(beginning_of_day) } before { Timecop.freeze(beginning_of_day) }
after { Timecop.return } after { Timecop.return }
context 'when dossier is en_construction' do context 'when dossier is en_construction' do
before do before do
dossier.en_construction! dossier.passer_en_construction!
dossier.reload dossier.reload
end end
@ -361,8 +371,8 @@ describe Dossier do
it 'should keep first en_construction_at date' do it 'should keep first en_construction_at date' do
Timecop.return Timecop.return
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
dossier.en_construction! dossier.repasser_en_construction!(instructeur)
expect(dossier.en_construction_at).to eq(beginning_of_day) expect(dossier.en_construction_at).to eq(beginning_of_day)
end end
@ -370,9 +380,10 @@ describe Dossier do
context 'when dossier is en_instruction' do context 'when dossier is en_instruction' do
let(:state) { Dossier.states.fetch(:en_construction) } let(:state) { Dossier.states.fetch(:en_construction) }
let(:instructeur) { create(:instructeur) }
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
dossier.reload dossier.reload
end end
@ -381,39 +392,48 @@ describe Dossier do
it 'should keep first en_instruction_at date if dossier is set to en_construction again' do it 'should keep first en_instruction_at date if dossier is set to en_construction again' do
Timecop.return Timecop.return
dossier.en_construction! dossier.repasser_en_construction!(instructeur)
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
expect(dossier.en_instruction_at).to eq(beginning_of_day) expect(dossier.en_instruction_at).to eq(beginning_of_day)
end end
end end
shared_examples 'dossier is processed' do |new_state|
before do
dossier.update(state: new_state)
dossier.reload
end
it { expect(dossier.state).to eq(new_state) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end
context 'when dossier is accepte' do context 'when dossier is accepte' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:accepte) before do
dossier.accepter!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) }
it { expect(dossier.traitements.last.processed_at).to eq(beginning_of_day) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
context 'when dossier is refuse' do context 'when dossier is refuse' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:refuse) before do
dossier.refuser!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
context 'when dossier is sans_suite' do context 'when dossier is sans_suite' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:sans_suite) before do
dossier.classer_sans_suite!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
end end
@ -478,13 +498,14 @@ describe Dossier do
describe "#send_dossier_received" do describe "#send_dossier_received" do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) }
let(:instructeur) { create(:instructeur) }
before do before do
allow(NotificationMailer).to receive(:send_dossier_received).and_return(double(deliver_later: nil)) allow(NotificationMailer).to receive(:send_dossier_received).and_return(double(deliver_later: nil))
end end
it "sends an email when the dossier becomes en_instruction" do it "sends an email when the dossier becomes en_instruction" do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
expect(NotificationMailer).to have_received(:send_dossier_received).with(dossier) expect(NotificationMailer).to have_received(:send_dossier_received).with(dossier)
end end
@ -777,6 +798,7 @@ describe Dossier do
describe 'webhook' do describe 'webhook' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
let(:instructeur) { create(:instructeur) }
it 'should not call webhook' do it 'should not call webhook' do
expect { expect {
@ -788,19 +810,19 @@ describe Dossier do
dossier.procedure.update_column(:web_hook_url, '/webhook.json') dossier.procedure.update_column(:web_hook_url, '/webhook.json')
expect { expect {
dossier.update_column(:motivation, 'bonjour') dossier.update_column(:search_terms, 'bonjour')
}.to_not have_enqueued_job(WebHookJob) }.to_not have_enqueued_job(WebHookJob)
expect { expect {
dossier.en_construction! dossier.passer_en_construction!
}.to have_enqueued_job(WebHookJob) }.to have_enqueued_job(WebHookJob)
expect { expect {
dossier.update_column(:motivation, 'bonjour2') dossier.update_column(:search_terms, 'bonjour2')
}.to_not have_enqueued_job(WebHookJob) }.to_not have_enqueued_job(WebHookJob)
expect { expect {
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
}.to have_enqueued_job(WebHookJob) }.to have_enqueued_job(WebHookJob)
end end
end end
@ -891,8 +913,11 @@ describe Dossier do
after { Timecop.return } after { Timecop.return }
it { expect(dossier.traitements.last.motivation).to eq('motivation') }
it { expect(dossier.motivation).to eq('motivation') } it { expect(dossier.motivation).to eq('motivation') }
it { expect(dossier.traitements.last.instructeur_email).to eq(instructeur.email) }
it { expect(dossier.en_instruction_at).to eq(dossier.en_instruction_at) } it { expect(dossier.en_instruction_at).to eq(dossier.en_instruction_at) }
it { expect(dossier.traitements.last.processed_at).to eq(now) }
it { expect(dossier.processed_at).to eq(now) } it { expect(dossier.processed_at).to eq(now) }
it { expect(dossier.state).to eq('accepte') } it { expect(dossier.state).to eq('accepte') }
it { expect(last_operation.operation).to eq('accepter') } it { expect(last_operation.operation).to eq('accepter') }

View file

@ -478,7 +478,7 @@ describe Instructeur, type: :model do
before do before do
procedure_to_assign.update(declarative_with_state: "accepte") procedure_to_assign.update(declarative_with_state: "accepte")
DeclarativeProceduresJob.new.perform DeclarativeProceduresJob.new.perform
dossier.update(processed_at: Time.zone.yesterday.beginning_of_day) dossier.traitements.last.update(processed_at: Time.zone.yesterday.beginning_of_day)
dossier.reload dossier.reload
end end

View file

@ -958,8 +958,7 @@ describe Procedure do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
def create_dossier(construction_date:, instruction_date:, processed_date:) def create_dossier(construction_date:, instruction_date:, processed_date:)
dossier = create(:dossier, :accepte, procedure: procedure) dossier = create(:dossier, :accepte, procedure: procedure, en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
dossier.update!(en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
end end
before do before do

View file

@ -66,7 +66,7 @@ describe NotificationService do
before do before do
procedure.update(declarative_with_state: "accepte") procedure.update(declarative_with_state: "accepte")
DeclarativeProceduresJob.new.perform DeclarativeProceduresJob.new.perform
dossier.update(processed_at: Time.zone.yesterday.beginning_of_day) dossier.traitements.last.update!(processed_at: Time.zone.yesterday.beginning_of_day)
dossier.reload dossier.reload
end end