diff --git a/.rubocop.yml b/.rubocop.yml index bdeea2d8f..1ab77533c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -689,6 +689,7 @@ Rails/CreateTableWithTimestamps: - db/migrate/2016*.rb - db/migrate/2017*.rb - db/migrate/2018*.rb + - db/migrate/20200630140356_create_traitements.rb Rails/Date: Enabled: false diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 9e3934c35..d676b5b40 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -249,9 +249,11 @@ class StatsController < ApplicationController min_date = 11.months.ago 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) - .pluck(:groupe_instructeur_id, :en_construction_at, :processed_at) + .pluck('dossiers.groupe_instructeur_id', 'dossiers.en_construction_at', :processed_at) # Group dossiers by month processed_dossiers_by_month = processed_dossiers @@ -290,11 +292,13 @@ class StatsController < ApplicationController min_date = 11.months.ago 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) .pluck( - :groupe_instructeur_id, - Arel.sql('EXTRACT(EPOCH FROM (en_construction_at - created_at)) / 60 AS processing_time'), + 'dossiers.groupe_instructeur_id', + Arel.sql('EXTRACT(EPOCH FROM (dossiers.en_construction_at - dossiers.created_at)) / 60 AS processing_time'), :processed_at ) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 4d9c75b04..531a30db1 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -149,7 +149,7 @@ module Users errors = update_dossier_and_compute_errors if passage_en_construction? && errors.blank? - @dossier.en_construction! + @dossier.passer_en_construction! NotificationMailer.send_initiated_notification(@dossier).deliver_later @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later diff --git a/app/models/dossier.rb b/app/models/dossier.rb index b1898d2e2..987d8fa32 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -42,6 +42,7 @@ class Dossier < ApplicationRecord has_many :followers_instructeurs, through: :follows, source: :instructeur has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur 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 @@ -128,6 +129,7 @@ class Dossier < ApplicationRecord :individual, :followers_instructeurs, :avis, + :traitements, etablissement: :champ, champs: { etablissement: :champ, @@ -172,6 +174,7 @@ class Dossier < ApplicationRecord justificatif_motivation_attachment: :blob, attestation: [], avis: { piece_justificative_file_attachment: :blob }, + traitements: [], etablissement: [], individual: [], user: []) @@ -198,10 +201,9 @@ class Dossier < ApplicationRecord .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 }) end - scope :termine_close_to_expiration, -> do - state_termine - .joins(:procedure) - .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 }) + def self.termine_close_to_expiration + dossier_ids = Traitement.termine_close_to_expiration.pluck(:dossier_id).uniq + Dossier.where(id: dossier_ids) end scope :brouillon_expired, -> do @@ -249,7 +251,7 @@ class Dossier < ApplicationRecord end 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 # 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 :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 :update_search_terms @@ -294,6 +295,16 @@ class Dossier < ApplicationRecord validates :individual, presence: true, if: -> { procedure.for_individual? } 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 self.search_terms = [ user&.email, @@ -508,27 +519,29 @@ class Dossier < ApplicationRecord 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) instructeur.follow(self) + update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil? log_dossier_operation(instructeur, :passer_en_instruction) end 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) end def after_repasser_en_construction(instructeur) - self.en_instruction_at = nil - - save! log_dossier_operation(instructeur, :repasser_en_construction) end def after_repasser_en_instruction(instructeur) self.archived = false - self.processed_at = nil - self.motivation = nil + self.en_instruction_at = Time.zone.now attestation&.destroy save! @@ -537,7 +550,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -553,6 +566,7 @@ class Dossier < ApplicationRecord end 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 if attestation.nil? @@ -565,7 +579,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -577,7 +591,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -766,16 +780,6 @@ class Dossier < ApplicationRecord 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 if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte? NotificationMailer.send_dossier_received(self).deliver_later diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index 27fc5223e..b1e6db2a1 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -161,7 +161,7 @@ class Instructeur < ApplicationRecord h = { nb_en_construction: groupe.dossiers.en_construction.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 } diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 5da6426ad..126062de2 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -438,7 +438,15 @@ class Procedure < ApplicationRecord end 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 def populate_champ_stable_ids @@ -610,18 +618,6 @@ class Procedure < ApplicationRecord 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 if self.path.blank? self.path = SecureRandom.uuid diff --git a/app/models/traitement.rb b/app/models/traitement.rb new file mode 100644 index 000000000..cbbdd2464 --- /dev/null +++ b/app/models/traitement.rb @@ -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 diff --git a/app/views/dossiers/show.pdf.prawn b/app/views/dossiers/show.pdf.prawn index 475bf1274..36c1c6329 100644 --- a/app/views/dossiers/show.pdf.prawn +++ b/app/views/dossiers/show.pdf.prawn @@ -156,7 +156,7 @@ def add_etats_dossier(pdf, dossier) if dossier.en_instruction_at.present? format_in_2_columns(pdf, "En instruction le", try_format_date(dossier.en_instruction_at)) 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)) end diff --git a/app/views/instructeurs/dossiers/_decisions_rendues_block.html.haml b/app/views/instructeurs/dossiers/_decisions_rendues_block.html.haml new file mode 100644 index 000000000..fd329cf0a --- /dev/null +++ b/app/views/instructeurs/dossiers/_decisions_rendues_block.html.haml @@ -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 + diff --git a/app/views/instructeurs/dossiers/personnes_impliquees.html.haml b/app/views/instructeurs/dossiers/personnes_impliquees.html.haml index 7a5ac4cde..e30a3e70e 100644 --- a/app/views/instructeurs/dossiers/personnes_impliquees.html.haml +++ b/app/views/instructeurs/dossiers/personnes_impliquees.html.haml @@ -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: @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 } diff --git a/config/locales/models/dossier/fr.yml b/config/locales/models/dossier/fr.yml index 5ed0cb43d..d79072067 100644 --- a/config/locales/models/dossier/fr.yml +++ b/config/locales/models/dossier/fr.yml @@ -9,13 +9,13 @@ fr: montant_projet: 'Le montant du projet' montant_aide_demande: "Le montant d’aide demandée" date_previsionnelle: "La date de début prévisionnelle" - state: + state: &state brouillon: "Brouillon" en_construction: "En construction" en_instruction: "En instruction" accepte: "Accepté" refuse: "Refusé" - sans_suite: "Sans suite" + sans_suite: "Classé sans suite" autorisation_donnees: Acceptation des CGU state/brouillon: Brouillon state/en_construction: En construction @@ -23,3 +23,6 @@ fr: state/accepte: Accepté state/refuse: Refusé state/sans_suite: Sans suite + traitement: + state: + <<: *state diff --git a/db/migrate/20200630140356_create_traitements.rb b/db/migrate/20200630140356_create_traitements.rb new file mode 100644 index 000000000..d2dc10748 --- /dev/null +++ b/db/migrate/20200630140356_create_traitements.rb @@ -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 diff --git a/db/migrate/20200707082256_remove_instructeur_id_and_add_instructeur_email_to_traitements.rb b/db/migrate/20200707082256_remove_instructeur_id_and_add_instructeur_email_to_traitements.rb new file mode 100644 index 000000000..33f954f60 --- /dev/null +++ b/db/migrate/20200707082256_remove_instructeur_id_and_add_instructeur_email_to_traitements.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index a3aaf999f..5e8ef8db9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 enable_extension "plpgsql" @@ -560,6 +560,15 @@ ActiveRecord::Schema.define(version: 2020_06_11_122406) do t.string "version", null: false 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| t.string "token", null: false 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 "refused_mails", "procedures" add_foreign_key "services", "administrateurs" + add_foreign_key "traitements", "dossiers" add_foreign_key "trusted_device_tokens", "instructeurs" add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id" add_foreign_key "users", "administrateurs" diff --git a/lib/tasks/deployment/20200630154829_add_traitements_from_dossiers.rake b/lib/tasks/deployment/20200630154829_add_traitements_from_dossiers.rake new file mode 100644 index 000000000..9940c9843 --- /dev/null +++ b/lib/tasks/deployment/20200630154829_add_traitements_from_dossiers.rake @@ -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 diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 8f80a0019..67fa3d713 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -596,7 +596,7 @@ describe API::V2::GraphqlController do it "should fail" do expect(gql_errors).to eq(nil) 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 }) end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 88a228c27..ec2d68386 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -194,7 +194,7 @@ describe Instructeurs::DossiersController, type: :controller do describe '#terminer' do context "with refuser" do before do - dossier.en_instruction! + dossier.passer_en_instruction!(instructeur) sign_in(instructeur.user) end @@ -235,7 +235,7 @@ describe Instructeurs::DossiersController, type: :controller do context "with classer_sans_suite" do before do - dossier.en_instruction! + dossier.passer_en_instruction!(instructeur) sign_in(instructeur.user) end context 'without attachment' do @@ -277,7 +277,7 @@ describe Instructeurs::DossiersController, type: :controller do context "with accepter" do before do - dossier.en_instruction! + dossier.passer_en_instruction!(instructeur) sign_in(instructeur.user) expect(NotificationMailer).to receive(:send_closed_notification) diff --git a/spec/controllers/stats_controller_spec.rb b/spec/controllers/stats_controller_spec.rb index d4547e81c..a16a9281d 100644 --- a/spec/controllers/stats_controller_spec.rb +++ b/spec/controllers/stats_controller_spec.rb @@ -106,26 +106,23 @@ describe StatsController, type: :controller do before do procedure_1 = FactoryBot.create(:procedure) procedure_2 = FactoryBot.create(:procedure) - dossier_p1_a = FactoryBot.create(:dossier, + dossier_p1_a = FactoryBot.create(:dossier, :accepte, :procedure => procedure_1, :en_construction_at => 2.months.ago.beginning_of_month, :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, :en_construction_at => 2.months.ago.beginning_of_month, :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, :en_construction_at => 1.month.ago.beginning_of_month, :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, :en_construction_at => 2.months.ago.beginning_of_month, :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 = { (2.months.ago.beginning_of_month).to_s => 3.0, (1.month.ago.beginning_of_month).to_s => 5.0 @@ -154,30 +151,27 @@ describe StatsController, type: :controller do before do 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) - dossier_p1_a = FactoryBot.create(:dossier, + dossier_p1_a = FactoryBot.create(:dossier, :accepte, :procedure => procedure_1, :created_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month + 30.minutes, :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, :created_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month + 10.minutes, :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, :created_at => 1.month.ago.beginning_of_month, :en_construction_at => 1.month.ago.beginning_of_month + 50.minutes, :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, :created_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month + 80.minutes, :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 = { (2.months.ago.beginning_of_month).to_s => 30.0, (1.month.ago.beginning_of_month).to_s => 50.0 diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index b98d4a090..9dd0cdfd5 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -657,7 +657,7 @@ describe Users::DossiersController, type: :controller do let!(:invite) { create(:invite, dossier: dossier, user: user) } before do - dossier.en_construction! + dossier.passer_en_construction! subject end diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index 222f87b1c..51c2995e7 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -127,11 +127,23 @@ FactoryBot.define do end 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.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 + processed_at = evaluator.processed_at + if processed_at.present? + 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! end end @@ -141,7 +153,7 @@ FactoryBot.define do dossier.state = Dossier.states.fetch(:refuse) 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.build(state: Dossier.states.fetch(:refuse), processed_at: dossier.en_instruction_at + 1.minute) dossier.save! end end @@ -151,14 +163,14 @@ FactoryBot.define do dossier.state = Dossier.states.fetch(:sans_suite) 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.build(state: Dossier.states.fetch(:sans_suite), processed_at: dossier.en_instruction_at + 1.minute) dossier.save! end end trait :with_motivation do after(:create) do |dossier, _evaluator| - dossier.motivation = case dossier.state + motivation = case dossier.state when Dossier.states.fetch(:refuse) 'L’entreprise concernée n’est pas agréée.' when Dossier.states.fetch(:sans_suite) @@ -166,6 +178,7 @@ FactoryBot.define do else 'Vous avez validé les conditions.' end + dossier.traitements.last.update!(motivation: motivation) end end diff --git a/spec/helpers/dossier_helper_spec.rb b/spec/helpers/dossier_helper_spec.rb index 7e90e8807..77ed77905 100644 --- a/spec/helpers/dossier_helper_spec.rb +++ b/spec/helpers/dossier_helper_spec.rb @@ -156,7 +156,7 @@ RSpec.describe DossierHelper, type: :helper do it 'sans_suite is traité' do dossier.sans_suite! - expect(subject).to eq('Sans suite') + expect(subject).to eq('Classé sans suite') end it 'refuse is traité' do diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index 9488976fd..37acae977 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -35,6 +35,7 @@ describe TagsSubstitutionConcern, type: :model do let(:individual) { nil } let(:etablissement) { create(:etablissement) } let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) } + let(:instructeur) { create(:instructeur) } before { Timecop.freeze(Time.zone.now) } @@ -242,7 +243,7 @@ describe TagsSubstitutionConcern, type: :model do end 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 let(:template) { '--motivation-- --numéro du dossier--' } @@ -318,9 +319,13 @@ describe TagsSubstitutionConcern, type: :model do context "when using a date tag" do before do - dossier.en_construction_at = Time.zone.local(2001, 2, 3) - dossier.en_instruction_at = Time.zone.local(2004, 5, 6) - dossier.processed_at = Time.zone.local(2007, 8, 9) + Timecop.freeze(Time.zone.local(2001, 2, 3)) + dossier.passer_en_construction! + 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 context "with date de dépôt" do diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 4af2276c8..732209a4b 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -209,8 +209,17 @@ describe Dossier do let(:date1) { 1.day.ago } let(:date2) { 1.hour.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!(:follow) { create(:follow, instructeur: instructeur, dossier: dossier) } + let(:dossier) do + 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 let(:non_following_instructeur) { create(:instructeur) } @@ -346,13 +355,14 @@ describe Dossier do let(:state) { Dossier.states.fetch(:brouillon) } let(:dossier) { create(:dossier, state: state) } let(:beginning_of_day) { Time.zone.now.beginning_of_day } + let(:instructeur) { create(:instructeur) } before { Timecop.freeze(beginning_of_day) } after { Timecop.return } context 'when dossier is en_construction' do before do - dossier.en_construction! + dossier.passer_en_construction! dossier.reload end @@ -361,8 +371,8 @@ describe Dossier do it 'should keep first en_construction_at date' do Timecop.return - dossier.en_instruction! - dossier.en_construction! + dossier.passer_en_instruction!(instructeur) + dossier.repasser_en_construction!(instructeur) expect(dossier.en_construction_at).to eq(beginning_of_day) end @@ -370,9 +380,10 @@ describe Dossier do context 'when dossier is en_instruction' do let(:state) { Dossier.states.fetch(:en_construction) } + let(:instructeur) { create(:instructeur) } before do - dossier.en_instruction! + dossier.passer_en_instruction!(instructeur) dossier.reload 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 Timecop.return - dossier.en_construction! - dossier.en_instruction! + dossier.repasser_en_construction!(instructeur) + dossier.passer_en_instruction!(instructeur) expect(dossier.en_instruction_at).to eq(beginning_of_day) 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 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 context 'when dossier is refuse' do 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 context 'when dossier is sans_suite' do 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 @@ -478,13 +498,14 @@ describe Dossier do describe "#send_dossier_received" do let(:procedure) { create(:procedure) } let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } + let(:instructeur) { create(:instructeur) } before do allow(NotificationMailer).to receive(:send_dossier_received).and_return(double(deliver_later: nil)) end 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) end @@ -777,6 +798,7 @@ describe Dossier do describe 'webhook' do let(:dossier) { create(:dossier) } + let(:instructeur) { create(:instructeur) } it 'should not call webhook' do expect { @@ -788,19 +810,19 @@ describe Dossier do dossier.procedure.update_column(:web_hook_url, '/webhook.json') expect { - dossier.update_column(:motivation, 'bonjour') + dossier.update_column(:search_terms, 'bonjour') }.to_not have_enqueued_job(WebHookJob) expect { - dossier.en_construction! + dossier.passer_en_construction! }.to have_enqueued_job(WebHookJob) expect { - dossier.update_column(:motivation, 'bonjour2') + dossier.update_column(:search_terms, 'bonjour2') }.to_not have_enqueued_job(WebHookJob) expect { - dossier.en_instruction! + dossier.passer_en_instruction!(instructeur) }.to have_enqueued_job(WebHookJob) end end @@ -891,8 +913,11 @@ describe Dossier do after { Timecop.return } + it { expect(dossier.traitements.last.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.traitements.last.processed_at).to eq(now) } it { expect(dossier.processed_at).to eq(now) } it { expect(dossier.state).to eq('accepte') } it { expect(last_operation.operation).to eq('accepter') } diff --git a/spec/models/instructeur_spec.rb b/spec/models/instructeur_spec.rb index cdef3e0d9..2e69ce40c 100644 --- a/spec/models/instructeur_spec.rb +++ b/spec/models/instructeur_spec.rb @@ -478,7 +478,7 @@ describe Instructeur, type: :model do before do procedure_to_assign.update(declarative_with_state: "accepte") 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 end diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 73c96c84b..6d4b5149f 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -958,8 +958,7 @@ describe Procedure do let(:procedure) { create(:procedure) } def create_dossier(construction_date:, instruction_date:, processed_date:) - dossier = create(:dossier, :accepte, procedure: procedure) - dossier.update!(en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date) + dossier = create(:dossier, :accepte, procedure: procedure, en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date) end before do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 9cb1952ef..59266eab2 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -66,7 +66,7 @@ describe NotificationService do before do procedure.update(declarative_with_state: "accepte") 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 end