diff --git a/app/javascript/components/ComboSearch.jsx b/app/javascript/components/ComboSearch.jsx index 80c926f8d..a257181a4 100644 --- a/app/javascript/components/ComboSearch.jsx +++ b/app/javascript/components/ComboSearch.jsx @@ -83,6 +83,9 @@ function ComboSearch({ setExternalId(''); setExternalValue(value); } + } else if (!value) { + setExternalId(''); + setExternalValue(''); } }, [minimumInputLength] diff --git a/app/jobs/cron/expired_dossiers_deletion_job.rb b/app/jobs/cron/expired_dossiers_deletion_job.rb index 565c66975..05ee53632 100644 --- a/app/jobs/cron/expired_dossiers_deletion_job.rb +++ b/app/jobs/cron/expired_dossiers_deletion_job.rb @@ -4,5 +4,6 @@ class Cron::ExpiredDossiersDeletionJob < Cron::CronJob def perform(*args) ExpiredDossiersDeletionService.process_expired_dossiers_brouillon ExpiredDossiersDeletionService.process_expired_dossiers_en_construction + ExpiredDossiersDeletionService.process_expired_dossiers_termine end end diff --git a/app/models/champs/commune_champ.rb b/app/models/champs/commune_champ.rb index 2ebab8e41..34569584f 100644 --- a/app/models/champs/commune_champ.rb +++ b/app/models/champs/commune_champ.rb @@ -18,4 +18,7 @@ # type_de_champ_id :integer # class Champs::CommuneChamp < Champs::TextChamp + def for_export + [value, external_id] + end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 60961a224..902dada56 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -79,7 +79,37 @@ class Dossier < ApplicationRecord has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur has_many :avis, inverse_of: :dossier, dependent: :destroy 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 @@ -271,7 +301,7 @@ class Dossier < ApplicationRecord scope :termine_close_to_expiration, -> do state_termine .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 scope :brouillon_expired, -> do @@ -676,7 +706,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -694,7 +724,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.traitements.accepter_automatiquement self.en_instruction_at ||= Time.zone.now self.declarative_triggered_at = Time.zone.now @@ -709,7 +739,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -723,7 +753,7 @@ class Dossier < ApplicationRecord end 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 self.justificatif_motivation.attach(justificatif) @@ -846,15 +876,16 @@ class Dossier < ApplicationRecord def champs_for_export(types_de_champ) # Index values by stable_id - values = (champs + champs_private).reject(&:exclude_from_export?).reduce({}) do |champs, champ| - champs[champ.stable_id] = champ.for_export - champs - end + values = (champs + champs_private).reject(&:exclude_from_export?) + .index_by(&:stable_id) + .transform_values(&:for_export) # Get all the champs values for the types de champ in the final list. # Dossier might not have corresponding champ – display nil. - types_de_champ.map do |type_de_champ| - [type_de_champ.libelle, values[type_de_champ.stable_id]] + types_de_champ.flat_map do |type_de_champ| + 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 diff --git a/app/models/traitement.rb b/app/models/traitement.rb index c382b9e79..22a837597 100644 --- a/app/models/traitement.rb +++ b/app/models/traitement.rb @@ -5,6 +5,7 @@ # id :bigint not null, primary key # instructeur_email :string # motivation :string +# process_expired :boolean # processed_at :datetime # state :string # dossier_id :bigint @@ -15,6 +16,7 @@ class Traitement < ApplicationRecord scope :termine_close_to_expiration, -> do joins(dossier: :procedure) .where(state: Dossier::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 }) end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 999d47f23..7f580f1b0 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -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 :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 def self.load(options) diff --git a/app/models/types_de_champ/commune_type_de_champ.rb b/app/models/types_de_champ/commune_type_de_champ.rb index 3d7c8a855..80994bf53 100644 --- a/app/models/types_de_champ/commune_type_de_champ.rb +++ b/app/models/types_de_champ/commune_type_de_champ.rb @@ -1,2 +1,5 @@ class TypesDeChamp::CommuneTypeDeChamp < TypesDeChamp::TypeDeChampBase + def libelle_for_export(index) + [libelle, "#{libelle} (Code insee)"][index] + end end diff --git a/app/models/types_de_champ/type_de_champ_base.rb b/app/models/types_de_champ/type_de_champ_base.rb index 10dbc74fb..57f9e0370 100644 --- a/app/models/types_de_champ/type_de_champ_base.rb +++ b/app/models/types_de_champ/type_de_champ_base.rb @@ -21,6 +21,10 @@ class TypesDeChamp::TypeDeChampBase ] end + def libelle_for_export(index) + libelle + end + def build_champ @type_de_champ.champ.build end diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 17c876839..07f7db25d 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -33,7 +33,8 @@ features = [ :instructeur_bypass_email_login_token, :make_experts_notifiable, :procedure_revisions, - :procedure_routage_api + :procedure_routage_api, + :procedure_process_expired_dossiers_termine ] def database_exists? diff --git a/db/migrate/20210818083349_add_process_expired_to_traitements.rb b/db/migrate/20210818083349_add_process_expired_to_traitements.rb new file mode 100644 index 000000000..04a8dbe00 --- /dev/null +++ b/db/migrate/20210818083349_add_process_expired_to_traitements.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index e3c349b3e..2cc0a24a3 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: 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 enable_extension "plpgsql" @@ -695,7 +695,9 @@ ActiveRecord::Schema.define(version: 2021_07_27_172504) do t.string "state" t.datetime "processed_at" t.string "instructeur_email" + t.boolean "process_expired" t.index ["dossier_id"], name: "index_traitements_on_dossier_id" + t.index ["process_expired"], name: "index_traitements_on_process_expired" end create_table "trusted_device_tokens", force: :cascade do |t| diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index 29d01759b..7eb38ce3f 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -146,11 +146,11 @@ FactoryBot.define do 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) + dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: processed_at) 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) + dossier.traitements.accepter(motivation: evaluator.motivation, processed_at: dossier.en_instruction_at + 1.minute) end dossier.save! end @@ -162,7 +162,7 @@ FactoryBot.define do dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur 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(:refuse), processed_at: dossier.en_instruction_at + 1.minute) + dossier.traitements.refuser(processed_at: dossier.en_instruction_at + 1.minute) dossier.save! end end @@ -173,7 +173,7 @@ FactoryBot.define do dossier.groupe_instructeur ||= dossier.procedure&.defaut_groupe_instructeur 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(: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! end end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 646f39c93..ef670a43b 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -163,6 +163,12 @@ FactoryBot.define do 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 after(:build) do |procedure, _evaluator| build(:type_de_champ_piece_justificative, procedure: procedure) diff --git a/spec/models/champs/commune_champ_spec.rb b/spec/models/champs/commune_champ_spec.rb new file mode 100644 index 000000000..fda2cb373 --- /dev/null +++ b/spec/models/champs/commune_champ_spec.rb @@ -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 diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 97caeeceb..1a3521090 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1341,11 +1341,12 @@ describe Dossier do end 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(: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(: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_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.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(commune_type_de_champ.stable_id).update(libelle: 'Commune de naissance') procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision) dossier.reload procedure.reload end 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_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.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.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, '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", "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)) end end diff --git a/spec/models/types_de_champ/commune_type_de_champ_spec.rb b/spec/models/types_de_champ/commune_type_de_champ_spec.rb new file mode 100644 index 000000000..8acd652de --- /dev/null +++ b/spec/models/types_de_champ/commune_type_de_champ_spec.rb @@ -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 diff --git a/spec/services/expired_dossiers_deletion_service_spec.rb b/spec/services/expired_dossiers_deletion_service_spec.rb index c24a7fb7a..97de70859 100644 --- a/spec/services/expired_dossiers_deletion_service_spec.rb +++ b/spec/services/expired_dossiers_deletion_service_spec.rb @@ -274,6 +274,11 @@ describe ExpiredDossiersDeletionService do before { Timecop.freeze(reference_date) } 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 allow(DossierMailer).to receive(:notify_near_deletion_to_user).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) } 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 allow(DossierMailer).to receive(:notify_automatic_deletion_to_user).and_call_original allow(DossierMailer).to receive(:notify_automatic_deletion_to_administration).and_call_original diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb index ac66c0a4b..bca98ba9c 100644 --- a/spec/services/procedure_export_service_spec.rb +++ b/spec/services/procedure_export_service_spec.rb @@ -68,6 +68,7 @@ describe ProcedureExportService do "regions", "departements", "communes", + "communes (Code insee)", "engagement", "dossier_link", "piece_justificative", @@ -154,6 +155,7 @@ describe ProcedureExportService do "regions", "departements", "communes", + "communes (Code insee)", "engagement", "dossier_link", "piece_justificative", @@ -236,6 +238,7 @@ describe ProcedureExportService do "regions", "departements", "communes", + "communes (Code insee)", "engagement", "dossier_link", "piece_justificative",