Merge pull request #6399 from betagouv/main

2021-08-19-01
This commit is contained in:
krichtof 2021-08-19 09:13:20 +02:00 committed by GitHub
commit 7177d3781b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 116 additions and 23 deletions

View file

@ -83,6 +83,9 @@ function ComboSearch({
setExternalId(''); setExternalId('');
setExternalValue(value); setExternalValue(value);
} }
} else if (!value) {
setExternalId('');
setExternalValue('');
} }
}, },
[minimumInputLength] [minimumInputLength]

View file

@ -4,5 +4,6 @@ class Cron::ExpiredDossiersDeletionJob < Cron::CronJob
def perform(*args) def perform(*args)
ExpiredDossiersDeletionService.process_expired_dossiers_brouillon ExpiredDossiersDeletionService.process_expired_dossiers_brouillon
ExpiredDossiersDeletionService.process_expired_dossiers_en_construction ExpiredDossiersDeletionService.process_expired_dossiers_en_construction
ExpiredDossiersDeletionService.process_expired_dossiers_termine
end end
end end

View file

@ -18,4 +18,7 @@
# type_de_champ_id :integer # type_de_champ_id :integer
# #
class Champs::CommuneChamp < Champs::TextChamp class Champs::CommuneChamp < Champs::TextChamp
def for_export
[value, external_id]
end
end end

View file

@ -79,7 +79,37 @@ class Dossier < ApplicationRecord
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 :experts, through: :avis has_many :experts, through: :avis
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy do
def accepter_automatiquement(processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:accepte),
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
def accepter(motivation: nil, instructeur: nil, processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:accepte),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
def refuser(motivation: nil, instructeur: nil, processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:refuse),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
def classer_sans_suite(motivation: nil, instructeur: nil, processed_at: Time.zone.now)
build(state: Dossier.states.fetch(:sans_suite),
instructeur_email: instructeur&.email,
motivation: motivation,
process_expired: proxy_association.owner.procedure.feature_enabled?(:procedure_process_expired_dossiers_termine),
processed_at: processed_at)
end
end
has_many :dossier_operation_logs, -> { order(:created_at) }, inverse_of: :dossier has_many :dossier_operation_logs, -> { order(:created_at) }, inverse_of: :dossier
@ -271,7 +301,7 @@ class Dossier < ApplicationRecord
scope :termine_close_to_expiration, -> do scope :termine_close_to_expiration, -> do
state_termine state_termine
.joins(:procedure) .joins(:procedure)
.where(id: Traitement.termine_close_to_expiration.pluck(:dossier_id).uniq) .where(id: Traitement.termine_close_to_expiration.select(:dossier_id).distinct)
end end
scope :brouillon_expired, -> do scope :brouillon_expired, -> do
@ -676,7 +706,7 @@ class Dossier < ApplicationRecord
end end
def after_accepter(instructeur, motivation, justificatif = nil) def after_accepter(instructeur, motivation, justificatif = nil)
self.traitements.build(state: Dossier.states.fetch(:accepte), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now) self.traitements.accepter(motivation: motivation, instructeur: instructeur)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -694,7 +724,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.traitements.accepter_automatiquement
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
self.declarative_triggered_at = Time.zone.now self.declarative_triggered_at = Time.zone.now
@ -709,7 +739,7 @@ class Dossier < ApplicationRecord
end end
def after_refuser(instructeur, motivation, justificatif = nil) def after_refuser(instructeur, motivation, justificatif = nil)
self.traitements.build(state: Dossier.states.fetch(:refuse), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now) self.traitements.refuser(motivation: motivation, instructeur: instructeur)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -723,7 +753,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.traitements.build(state: Dossier.states.fetch(:sans_suite), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now) self.traitements.classer_sans_suite(motivation: motivation, instructeur: instructeur)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -846,15 +876,16 @@ class Dossier < ApplicationRecord
def champs_for_export(types_de_champ) def champs_for_export(types_de_champ)
# Index values by stable_id # Index values by stable_id
values = (champs + champs_private).reject(&:exclude_from_export?).reduce({}) do |champs, champ| values = (champs + champs_private).reject(&:exclude_from_export?)
champs[champ.stable_id] = champ.for_export .index_by(&:stable_id)
champs .transform_values(&:for_export)
end
# Get all the champs values for the types de champ in the final list. # Get all the champs values for the types de champ in the final list.
# Dossier might not have corresponding champ display nil. # Dossier might not have corresponding champ display nil.
types_de_champ.map do |type_de_champ| types_de_champ.flat_map do |type_de_champ|
[type_de_champ.libelle, values[type_de_champ.stable_id]] Array.wrap(values[type_de_champ.stable_id] || [nil]).map.with_index do |champ_value, index|
[type_de_champ.libelle_for_export(index), champ_value]
end
end end
end end

View file

@ -5,6 +5,7 @@
# id :bigint not null, primary key # id :bigint not null, primary key
# instructeur_email :string # instructeur_email :string
# motivation :string # motivation :string
# process_expired :boolean
# processed_at :datetime # processed_at :datetime
# state :string # state :string
# dossier_id :bigint # dossier_id :bigint
@ -15,6 +16,7 @@ class Traitement < ApplicationRecord
scope :termine_close_to_expiration, -> do scope :termine_close_to_expiration, -> do
joins(dossier: :procedure) joins(dossier: :procedure)
.where(state: Dossier::TERMINE) .where(state: Dossier::TERMINE)
.where(process_expired: true)
.where('dossiers.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 }) .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

@ -61,7 +61,7 @@ class TypeDeChamp < ApplicationRecord
has_many :revision_types_de_champ, class_name: 'ProcedureRevisionTypeDeChamp', dependent: :destroy, inverse_of: :type_de_champ has_many :revision_types_de_champ, class_name: 'ProcedureRevisionTypeDeChamp', dependent: :destroy, inverse_of: :type_de_champ
has_many :revisions, through: :revision_types_de_champ has_many :revisions, through: :revision_types_de_champ
delegate :tags_for_template, to: :dynamic_type delegate :tags_for_template, :libelle_for_export, to: :dynamic_type
class WithIndifferentAccess class WithIndifferentAccess
def self.load(options) def self.load(options)

View file

@ -1,2 +1,5 @@
class TypesDeChamp::CommuneTypeDeChamp < TypesDeChamp::TypeDeChampBase class TypesDeChamp::CommuneTypeDeChamp < TypesDeChamp::TypeDeChampBase
def libelle_for_export(index)
[libelle, "#{libelle} (Code insee)"][index]
end
end end

View file

@ -21,6 +21,10 @@ class TypesDeChamp::TypeDeChampBase
] ]
end end
def libelle_for_export(index)
libelle
end
def build_champ def build_champ
@type_de_champ.champ.build @type_de_champ.champ.build
end end

View file

@ -33,7 +33,8 @@ features = [
:instructeur_bypass_email_login_token, :instructeur_bypass_email_login_token,
:make_experts_notifiable, :make_experts_notifiable,
:procedure_revisions, :procedure_revisions,
:procedure_routage_api :procedure_routage_api,
:procedure_process_expired_dossiers_termine
] ]
def database_exists? def database_exists?

View file

@ -0,0 +1,6 @@
class AddProcessExpiredToTraitements < ActiveRecord::Migration[6.1]
def change
add_column :traitements, :process_expired, :boolean
add_index :traitements, :process_expired
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: 2021_07_27_172504) do ActiveRecord::Schema.define(version: 2021_08_18_083349) 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"
@ -695,7 +695,9 @@ ActiveRecord::Schema.define(version: 2021_07_27_172504) do
t.string "state" t.string "state"
t.datetime "processed_at" t.datetime "processed_at"
t.string "instructeur_email" t.string "instructeur_email"
t.boolean "process_expired"
t.index ["dossier_id"], name: "index_traitements_on_dossier_id" t.index ["dossier_id"], name: "index_traitements_on_dossier_id"
t.index ["process_expired"], name: "index_traitements_on_process_expired"
end end
create_table "trusted_device_tokens", force: :cascade do |t| create_table "trusted_device_tokens", force: :cascade do |t|

View file

@ -146,11 +146,11 @@ FactoryBot.define do
if processed_at.present? if processed_at.present?
dossier.en_construction_at ||= processed_at - 2.minutes dossier.en_construction_at ||= processed_at - 2.minutes
dossier.en_instruction_at ||= processed_at - 1.minute dossier.en_instruction_at ||= processed_at - 1.minute
dossier.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: processed_at, motivation: evaluator.motivation) dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: processed_at)
else else
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.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: dossier.en_instruction_at + 1.minute, motivation: evaluator.motivation) dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: dossier.en_instruction_at + 1.minute)
end end
dossier.save! dossier.save!
end end
@ -162,7 +162,7 @@ FactoryBot.define do
dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur
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.traitements.build(state: Dossier.states.fetch(:refuse), processed_at: dossier.en_instruction_at + 1.minute) dossier.traitements.refuser(processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end
@ -173,7 +173,7 @@ FactoryBot.define do
dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur
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.traitements.build(state: Dossier.states.fetch(:sans_suite), processed_at: dossier.en_instruction_at + 1.minute) dossier.traitements.classer_sans_suite(processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end

View file

@ -163,6 +163,12 @@ FactoryBot.define do
end end
end end
trait :with_commune do
after(:build) do |procedure, _evaluator|
build(:type_de_champ_communes, procedure: procedure)
end
end
trait :with_piece_justificative do trait :with_piece_justificative do
after(:build) do |procedure, _evaluator| after(:build) do |procedure, _evaluator|
build(:type_de_champ_piece_justificative, procedure: procedure) build(:type_de_champ_piece_justificative, procedure: procedure)

View file

@ -0,0 +1,10 @@
describe Champs::CommuneChamp do
let(:type_de_champ) { create(:type_de_champ_communes, libelle: 'Ma commune') }
let(:champ) { Champs::CommuneChamp.new(value: value, external_id: code_insee, type_de_champ: type_de_champ) }
let(:value) { 'Châteldon (63290)' }
let(:code_insee) { '63102' }
it { expect(champ.value).to eq('Châteldon (63290)') }
it { expect(champ.external_id).to eq('63102') }
it { expect(champ.for_export).to eq(['Châteldon (63290)', '63102']) }
end

View file

@ -1341,11 +1341,12 @@ describe Dossier do
end end
describe "champs_for_export" do describe "champs_for_export" do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_datetime, :with_yes_no, :with_explication) } let(:procedure) { create(:procedure, :with_type_de_champ, :with_datetime, :with_yes_no, :with_explication, :with_commune) }
let(:text_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:text) } } let(:text_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
let(:yes_no_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } } let(:yes_no_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
let(:datetime_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } } let(:datetime_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:explication_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:explication) } } let(:explication_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:explication) } }
let(:commune_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:communes) } }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
let(:dossier_second_revision) { create(:dossier, procedure: procedure) } let(:dossier_second_revision) { create(:dossier, procedure: procedure) }
@ -1356,15 +1357,16 @@ describe Dossier do
procedure.draft_revision.remove_type_de_champ(text_type_de_champ.stable_id) procedure.draft_revision.remove_type_de_champ(text_type_de_champ.stable_id)
procedure.draft_revision.add_type_de_champ(type_champ: TypeDeChamp.type_champs.fetch(:text), libelle: 'New text field') procedure.draft_revision.add_type_de_champ(type_champ: TypeDeChamp.type_champs.fetch(:text), libelle: 'New text field')
procedure.draft_revision.find_or_clone_type_de_champ(yes_no_type_de_champ.stable_id).update(libelle: 'Updated yes/no') procedure.draft_revision.find_or_clone_type_de_champ(yes_no_type_de_champ.stable_id).update(libelle: 'Updated yes/no')
procedure.draft_revision.find_or_clone_type_de_champ(commune_type_de_champ.stable_id).update(libelle: 'Commune de naissance')
procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision) procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision)
dossier.reload dossier.reload
procedure.reload procedure.reload
end end
it "should have champs from all revisions" do it "should have champs from all revisions" do
expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle]) expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle, commune_type_de_champ.libelle])
expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, "New text field"]) expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, 'Commune de naissance', "New text field"])
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Updated yes/no", "New text field"]) expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Updated yes/no", "Commune de naissance", "Commune de naissance (Code insee)", "New text field"])
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation)).to eq(dossier_second_revision.champs_for_export(dossier_second_revision.procedure.types_de_champ_for_procedure_presentation)) expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation)).to eq(dossier_second_revision.champs_for_export(dossier_second_revision.procedure.types_de_champ_for_procedure_presentation))
end end
end end

View file

@ -0,0 +1,6 @@
describe TypesDeChamp::CommuneTypeDeChamp do
let(:subject) { create(:type_de_champ_communes, libelle: 'Ma commune') }
it { expect(subject.libelle_for_export(0)).to eq('Ma commune') }
it { expect(subject.libelle_for_export(1)).to eq('Ma commune (Code insee)') }
end

View file

@ -274,6 +274,11 @@ describe ExpiredDossiersDeletionService do
before { Timecop.freeze(reference_date) } before { Timecop.freeze(reference_date) }
after { Timecop.return } after { Timecop.return }
before do
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure)
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure_2)
end
before do before do
allow(DossierMailer).to receive(:notify_near_deletion_to_user).and_call_original allow(DossierMailer).to receive(:notify_near_deletion_to_user).and_call_original
allow(DossierMailer).to receive(:notify_near_deletion_to_administration).and_call_original allow(DossierMailer).to receive(:notify_near_deletion_to_administration).and_call_original
@ -343,6 +348,11 @@ describe ExpiredDossiersDeletionService do
before { Timecop.freeze(reference_date) } before { Timecop.freeze(reference_date) }
after { Timecop.return } after { Timecop.return }
before do
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure)
Flipper.enable(:procedure_process_expired_dossiers_termine, procedure_2)
end
before do before do
allow(DossierMailer).to receive(:notify_automatic_deletion_to_user).and_call_original allow(DossierMailer).to receive(:notify_automatic_deletion_to_user).and_call_original
allow(DossierMailer).to receive(:notify_automatic_deletion_to_administration).and_call_original allow(DossierMailer).to receive(:notify_automatic_deletion_to_administration).and_call_original

View file

@ -68,6 +68,7 @@ describe ProcedureExportService do
"regions", "regions",
"departements", "departements",
"communes", "communes",
"communes (Code insee)",
"engagement", "engagement",
"dossier_link", "dossier_link",
"piece_justificative", "piece_justificative",
@ -154,6 +155,7 @@ describe ProcedureExportService do
"regions", "regions",
"departements", "departements",
"communes", "communes",
"communes (Code insee)",
"engagement", "engagement",
"dossier_link", "dossier_link",
"piece_justificative", "piece_justificative",
@ -236,6 +238,7 @@ describe ProcedureExportService do
"regions", "regions",
"departements", "departements",
"communes", "communes",
"communes (Code insee)",
"engagement", "engagement",
"dossier_link", "dossier_link",
"piece_justificative", "piece_justificative",