Merge pull request #6634 from betagouv/main

2021-11-12-01
This commit is contained in:
Paul Chavard 2021-11-12 11:08:16 +01:00 committed by GitHub
commit 9500bbf7c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 41 deletions

View file

@ -83,6 +83,18 @@ class Dossier < ApplicationRecord
has_many :avis, inverse_of: :dossier, dependent: :destroy
has_many :experts, through: :avis
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy do
def passer_en_construction(processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:en_construction),
process_expired: false,
processed_at: processed_at)
end
def passer_en_instruction(processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:en_instruction),
process_expired: false,
processed_at: processed_at)
end
def accepter_automatiquement(processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:accepte),
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
@ -113,6 +125,7 @@ class Dossier < ApplicationRecord
processed_at: processed_at)
end
end
has_one :traitement, -> { order(processed_at: :desc) }, inverse_of: false
has_many :dossier_operation_logs, -> { order(:created_at) }, inverse_of: :dossier
@ -221,7 +234,7 @@ class Dossier < ApplicationRecord
:user,
:individual,
:followers_instructeurs,
:traitements,
:traitement,
:groupe_instructeur,
procedure: [
:groupe_instructeurs,
@ -258,7 +271,7 @@ class Dossier < ApplicationRecord
justificatif_motivation_attachment: :blob,
attestation: [],
avis: { piece_justificative_file_attachment: :blob },
traitements: [],
traitement: [],
etablissement: [],
individual: [],
user: [])
@ -342,7 +355,7 @@ class Dossier < ApplicationRecord
.where.not(user: users_who_submitted)
end
scope :for_api_v2, -> { includes(procedure: [:administrateurs, :attestation_template], etablissement: [], individual: [], traitements: []) }
scope :for_api_v2, -> { includes(procedure: [:administrateurs, :attestation_template], etablissement: [], individual: [], traitement: []) }
scope :with_notifications, -> do
joins(:follows)
@ -423,12 +436,12 @@ class Dossier < ApplicationRecord
def motivation
return nil if !termine?
traitements.any? ? traitements.last.motivation : read_attribute(:motivation)
traitement&.motivation || read_attribute(:motivation)
end
def processed_at
return nil if !termine?
traitements.any? ? traitements.last.processed_at : read_attribute(:processed_at)
traitement&.processed_at || read_attribute(:processed_at)
end
def update_search_terms
@ -649,6 +662,7 @@ class Dossier < ApplicationRecord
transaction do
if keep_track_on_deletion?
DeletedDossier.create_from_dossier(self, :expired)
dossier_operation_logs.destroy_all
log_automatic_dossier_operation(:supprimer, self)
end
destroy!
@ -708,14 +722,23 @@ class Dossier < ApplicationRecord
end
def after_passer_en_construction
update!(conservation_extension: 0.days)
update!(en_construction_at: Time.zone.now) if self.en_construction_at.nil?
self.conservation_extension = 0.days
self.en_construction_at = self.traitements
.passer_en_construction
.processed_at
save!
end
def after_passer_en_instruction(instructeur, disable_notification: false)
instructeur.follow(self)
update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil?
self.en_construction_close_to_expiration_notice_sent_at = nil
self.conservation_extension = 0.days
self.en_instruction_at = self.traitements
.passer_en_instruction
.processed_at
save!
if !procedure.declarative_accepte? && !disable_notification
NotificationMailer.send_en_instruction_notification(self).deliver_later
end
@ -723,20 +746,32 @@ class Dossier < ApplicationRecord
end
def after_passer_automatiquement_en_instruction
self.en_instruction_at ||= Time.zone.now
self.declarative_triggered_at = Time.zone.now
self.en_construction_close_to_expiration_notice_sent_at = nil
self.conservation_extension = 0.days
self.en_instruction_at = self.declarative_triggered_at = self.traitements
.passer_en_instruction
.processed_at
save!
log_automatic_dossier_operation(:passer_en_instruction)
end
def after_repasser_en_construction(instructeur)
update!(conservation_extension: 0.days)
self.en_construction_close_to_expiration_notice_sent_at = nil
self.conservation_extension = 0.days
self.en_construction_at = self.traitements
.passer_en_construction
.processed_at
save!
log_dossier_operation(instructeur, :repasser_en_construction)
end
def after_repasser_en_instruction(instructeur, disable_notification: false)
self.archived = false
self.en_instruction_at = Time.zone.now
self.termine_close_to_expiration_notice_sent_at = nil
self.conservation_extension = 0.days
self.en_instruction_at = self.traitements
.passer_en_instruction
.processed_at
attestation&.destroy
save!
@ -747,7 +782,10 @@ class Dossier < ApplicationRecord
end
def after_accepter(instructeur, motivation, justificatif: nil, disable_notification: false)
self.traitements.accepter(motivation: motivation, instructeur: instructeur)
self.processed_at = self.traitements
.accepter(motivation: motivation, instructeur: instructeur)
.processed_at
save!
if justificatif
self.justificatif_motivation.attach(justificatif)
@ -767,9 +805,10 @@ class Dossier < ApplicationRecord
end
def after_accepter_automatiquement
self.traitements.accepter_automatiquement
self.en_instruction_at ||= Time.zone.now
self.declarative_triggered_at = Time.zone.now
self.processed_at = self.en_instruction_at = self.declarative_triggered_at = self.traitements
.accepter_automatiquement
.processed_at
save!
if attestation.nil?
self.attestation = build_attestation
@ -782,7 +821,10 @@ class Dossier < ApplicationRecord
end
def after_refuser(instructeur, motivation, justificatif: nil, disable_notification: false)
self.traitements.refuser(motivation: motivation, instructeur: instructeur)
self.processed_at = self.traitements
.refuser(motivation: motivation, instructeur: instructeur)
.processed_at
save!
if justificatif
self.justificatif_motivation.attach(justificatif)
@ -798,7 +840,10 @@ class Dossier < ApplicationRecord
end
def after_classer_sans_suite(instructeur, motivation, justificatif: nil, disable_notification: false)
self.traitements.classer_sans_suite(motivation: motivation, instructeur: instructeur)
self.processed_at = self.traitements
.classer_sans_suite(motivation: motivation, instructeur: instructeur)
.processed_at
save!
if justificatif
self.justificatif_motivation.attach(justificatif)

View file

@ -13,9 +13,13 @@
class Traitement < ApplicationRecord
belongs_to :dossier, optional: false
scope :en_construction, -> { where(state: Dossier.states.fetch(:en_construction)) }
scope :en_instruction, -> { where(state: Dossier.states.fetch(:en_instruction)) }
scope :termine, -> { where(state: Dossier::TERMINE) }
scope :termine_close_to_expiration, -> do
joins(dossier: :procedure)
.where(state: Dossier::TERMINE)
.termine
.where(process_expired: true)
.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 })
@ -23,6 +27,7 @@ class Traitement < ApplicationRecord
scope :for_traitement_time_stats, -> (procedure) do
includes(:dossier)
.termine
.where(dossier: procedure.dossiers)
.where.not('dossiers.en_construction_at' => nil, :processed_at => nil)
.order(:processed_at)
@ -31,6 +36,7 @@ class Traitement < ApplicationRecord
def self.count_dossiers_termines_by_month(groupe_instructeurs)
last_traitements_per_dossier = Traitement
.select('max(traitements.processed_at) as processed_at')
.termine
.where(dossier: Dossier.state_termine.where(groupe_instructeur: groupe_instructeurs))
.group(:dossier_id)
.to_sql

View file

@ -297,13 +297,12 @@ class TypeDeChamp < ApplicationRecord
# otherwise return all types_de_champ in their latest state
types_de_champ = TypeDeChamp
.fillable
.joins(:parent)
.where(parent: { stable_id: stable_id })
.joins(parent: :revision_types_de_champ)
.where(parent: { stable_id: stable_id }, revision_types_de_champ: { revision_id: revision })
TypeDeChamp
.where(id: types_de_champ.group(:stable_id).select('MAX(types_de_champ.id)'))
.joins(parent: :revision_types_de_champ)
.order(:order_place, 'procedure_revision_types_de_champ.revision_id': :desc)
.order(:order_place, id: :desc)
end
end

View file

@ -0,0 +1,22 @@
namespace :after_party do
desc 'Deployment task: set_dossiers_processed_at'
task set_dossiers_processed_at: :environment do
puts "Running deploy task 'set_dossiers_processed_at'"
dossiers = Dossier.termine.includes(:traitement)
progress = ProgressReport.new(dossiers.count)
dossiers.find_each do |dossier|
if dossier.processed_at != dossier.traitement.processed_at
dossier.update_column(:processed_at, dossier.traitement.processed_at)
end
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: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -340,15 +340,11 @@ describe Instructeurs::DossiersController, type: :controller do
end
before do
Timecop.freeze(Time.zone.now)
expect_any_instance_of(AttestationTemplate)
.to receive(:attestation_for)
.with(have_attributes(motivation: "Yallah", processed_at: Time.zone.now))
.with(have_attributes(motivation: "Yallah"))
end
after { Timecop.return }
it { subject }
end

View file

@ -387,13 +387,18 @@ describe Dossier do
it { expect(dossier.state).to eq(Dossier.states.fetch(:en_construction)) }
it { expect(dossier.en_construction_at).to eq(beginning_of_day) }
it { expect(dossier.traitement.state).to eq(Dossier.states.fetch(:en_construction)) }
it { expect(dossier.traitement.processed_at).to eq(beginning_of_day) }
it 'should keep first en_construction_at date' do
Timecop.return
dossier.passer_en_instruction!(instructeur)
dossier.repasser_en_construction!(instructeur)
expect(dossier.en_construction_at).to eq(beginning_of_day)
expect(dossier.traitements.size).to eq(3)
expect(dossier.traitements.first.processed_at).to eq(beginning_of_day)
expect(dossier.traitement.processed_at.round).to eq(dossier.en_construction_at.round)
expect(dossier.en_construction_at).to be > beginning_of_day
end
end
@ -408,13 +413,18 @@ describe Dossier do
it { expect(dossier.state).to eq(Dossier.states.fetch(:en_instruction)) }
it { expect(dossier.en_instruction_at).to eq(beginning_of_day) }
it { expect(dossier.traitement.state).to eq(Dossier.states.fetch(:en_instruction)) }
it { expect(dossier.traitement.processed_at).to eq(beginning_of_day) }
it 'should keep first en_instruction_at date if dossier is set to en_construction again' do
Timecop.return
dossier.repasser_en_construction!(instructeur)
dossier.passer_en_instruction!(instructeur)
expect(dossier.en_instruction_at).to eq(beginning_of_day)
expect(dossier.traitements.size).to eq(3)
expect(dossier.traitements.first.processed_at).to eq(beginning_of_day)
expect(dossier.traitement.processed_at.round).to eq(dossier.en_instruction_at.round)
expect(dossier.en_instruction_at).to be > beginning_of_day
end
end
@ -427,8 +437,9 @@ describe Dossier do
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) }
it { expect(dossier.traitement.state).to eq(Dossier.states.fetch(:accepte)) }
it { expect(dossier.traitement.processed_at).to eq(beginning_of_day) }
end
context 'when dossier is refuse' do
@ -441,6 +452,8 @@ describe Dossier do
it { expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
it { expect(dossier.traitement.state).to eq(Dossier.states.fetch(:refuse)) }
it { expect(dossier.traitement.processed_at).to eq(beginning_of_day) }
end
context 'when dossier is sans_suite' do
@ -453,6 +466,8 @@ describe Dossier do
it { expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
it { expect(dossier.traitement.state).to eq(Dossier.states.fetch(:sans_suite)) }
it { expect(dossier.traitement.processed_at).to eq(beginning_of_day) }
end
end
@ -997,7 +1012,7 @@ describe Dossier do
end
describe '#passer_en_instruction!' do
let(:dossier) { create(:dossier, :en_construction) }
let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) }
let(:last_operation) { dossier.dossier_operation_logs.last }
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) }
let(:instructeur) { create(:instructeur) }
@ -1006,6 +1021,7 @@ describe Dossier do
it { expect(dossier.state).to eq('en_instruction') }
it { expect(dossier.followers_instructeurs).to include(instructeur) }
it { expect(dossier.en_construction_close_to_expiration_notice_sent_at).to be_nil }
it { expect(last_operation.operation).to eq('passer_en_instruction') }
it { expect(last_operation.automatic_operation?).to be_falsey }
it { expect(operation_serialized['operation']).to eq('passer_en_instruction') }
@ -1014,7 +1030,7 @@ describe Dossier do
end
describe '#passer_automatiquement_en_instruction!' do
let(:dossier) { create(:dossier, :en_construction) }
let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) }
let(:last_operation) { dossier.dossier_operation_logs.last }
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) }
let(:instructeur) { create(:instructeur) }
@ -1022,6 +1038,7 @@ describe Dossier do
before { dossier.passer_automatiquement_en_instruction! }
it { expect(dossier.followers_instructeurs).not_to include(instructeur) }
it { expect(dossier.en_construction_close_to_expiration_notice_sent_at).to be_nil }
it { expect(last_operation.operation).to eq('passer_en_instruction') }
it { expect(last_operation.automatic_operation?).to be_truthy }
it { expect(operation_serialized['operation']).to eq('passer_en_instruction') }
@ -1119,7 +1136,7 @@ describe Dossier do
end
describe '#repasser_en_instruction!' do
let(:dossier) { create(:dossier, :refuse, :with_attestation, archived: true) }
let(:dossier) { create(:dossier, :refuse, :with_attestation, archived: true, termine_close_to_expiration_notice_sent_at: Time.zone.now) }
let!(:instructeur) { create(:instructeur) }
let(:last_operation) { dossier.dossier_operation_logs.last }
@ -1136,6 +1153,7 @@ describe Dossier do
it { expect(dossier.processed_at).to be_nil }
it { expect(dossier.motivation).to be_nil }
it { expect(dossier.attestation).to be_nil }
it { expect(dossier.termine_close_to_expiration_notice_sent_at).to be_nil }
it { expect(last_operation.operation).to eq('repasser_en_instruction') }
it { expect(JSON.parse(last_operation.serialized.download)['author']['email']).to eq(instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_revert_to_instruction).with(dossier) }

View file

@ -342,13 +342,22 @@ describe ProcedureExportService do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export])
end
it 'should have headers' do
expect(repetition_sheet.headers).to eq([
"Dossier ID",
"Ligne",
"Nom",
"Age"
])
context 'with cloned procedure' do
let(:other_parent) { create(:type_de_champ_repetition, stable_id: champ_repetition.stable_id) }
before do
create(:procedure_revision_type_de_champ, type_de_champ: other_parent, revision: create(:procedure).active_revision)
create(:type_de_champ, parent: other_parent)
end
it 'should have headers' do
expect(repetition_sheet.headers).to eq([
"Dossier ID",
"Ligne",
"Nom",
"Age"
])
end
end
it 'should have data' do