Merge pull request #10900 from tchak/refactor-dossier-rebase

ETQ dev, je ne veux pas qu'un rebase change le type d'un champ
This commit is contained in:
Paul Chavard 2024-11-14 10:40:33 +00:00 committed by GitHub
commit e1e497bd7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 112 additions and 134 deletions

View file

@ -16,8 +16,10 @@ class Champs::CarteChamp < Champ
# We are not using scopes here as we want to access
# the following collections on unsaved records.
def cadastres
geo_areas.filter do |area|
area.source == GeoArea.sources.fetch(:cadastre)
if cadastres?
geo_areas.filter { _1.source == GeoArea.sources.fetch(:cadastre) }
else
[]
end
end

View file

@ -56,14 +56,6 @@ class Champs::DropDownListChamp < Champ
options.include?(value)
end
def remove_option(options, touch = false)
if touch
update(value: nil)
else
update_column(:value, nil)
end
end
private
def value_is_in_options

View file

@ -4,18 +4,18 @@ class Champs::LinkedDropDownListChamp < Champ
delegate :primary_options, :secondary_options, to: :type_de_champ
def primary_value
if value.present?
JSON.parse(value)[0]
else
if type_de_champ.champ_blank?(self)
''
else
JSON.parse(value)[0]
end
end
def secondary_value
if value.present?
JSON.parse(value)[1]
else
if type_de_champ.champ_blank?(self)
''
else
JSON.parse(value)[1]
end
end
@ -47,14 +47,6 @@ class Champs::LinkedDropDownListChamp < Champ
options.include?(primary_value) || options.include?(secondary_value)
end
def remove_option(options, touch = false)
if touch
update(value: nil)
else
update_column(:value, nil)
end
end
private
def pack_value(primary, secondary)

View file

@ -33,15 +33,6 @@ class Champs::MultipleDropDownListChamp < Champ
(selected_options - options).size != selected_options.size
end
def remove_option(options, touch = false)
value = (selected_options - options).to_json
if touch
update(value:)
else
update_columns(value:)
end
end
def focusable_input_id
render_as_checkboxes? ? checkbox_id(drop_down_options.first) : input_id
end
@ -80,10 +71,6 @@ class Champs::MultipleDropDownListChamp < Champ
end
end
def render?
@champ.drop_down_options.any?
end
private
def values_are_in_options

View file

@ -30,7 +30,7 @@ module ChampConditionalConcern
private
def champs_for_condition
dossier.champs.filter { _1.row_id.nil? || _1.row_id == row_id }
dossier.filled_champs.filter { _1.row_id.nil? || _1.row_id == row_id }
end
end
end

View file

@ -6,23 +6,26 @@ module DossierChampsConcern
def project_champ(type_de_champ, row_id)
check_valid_row_id?(type_de_champ, row_id)
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
if champ.nil?
type_de_champ.build_champ(dossier: self, row_id:, updated_at: depose_at || created_at)
if champ.nil? || !champ.is_type?(type_de_champ.type_champ)
value = type_de_champ.champ_blank?(champ) ? nil : champ.value
updated_at = champ&.updated_at || depose_at || created_at
rebased_at = champ&.rebased_at
type_de_champ.build_champ(dossier: self, row_id:, updated_at:, rebased_at:, value:)
else
champ
end
end
def project_champs_public
revision.types_de_champ_public.map { project_champ(_1, nil) }
@project_champs_public ||= revision.types_de_champ_public.map { project_champ(_1, nil) }
end
def project_champs_private
revision.types_de_champ_private.map { project_champ(_1, nil) }
@project_champs_private ||= revision.types_de_champ_private.map { project_champ(_1, nil) }
end
def filled_champs_public
project_champs_public.flat_map do |champ|
@filled_champs_public ||= project_champs_public.flat_map do |champ|
if champ.repetition?
champ.rows.flatten.filter { _1.persisted? && _1.fillable? }
elsif champ.persisted? && champ.fillable?
@ -34,7 +37,7 @@ module DossierChampsConcern
end
def filled_champs_private
project_champs_private.flat_map do |champ|
@filled_champs_private ||= project_champs_private.flat_map do |champ|
if champ.repetition?
champ.rows.flatten.filter { _1.persisted? && _1.fillable? }
elsif champ.persisted? && champ.fillable?
@ -129,8 +132,7 @@ module DossierChampsConcern
row_id = ULID.generate
types_de_champ = revision.children_of(type_de_champ)
self.champs += types_de_champ.map { _1.build_champ(row_id:, updated_by:) }
champs.reload if persisted?
@champs_by_public_id = nil
reload_champs_cache
row_id
end
@ -138,14 +140,11 @@ module DossierChampsConcern
raise "Can't remove row from non-repetition type de champ" if !type_de_champ.repetition?
champs.where(row_id:).destroy_all
champs.reload if persisted?
@champs_by_public_id = nil
reload_champs_cache
end
def reload
super.tap do
@champs_by_public_id = nil
end
super.tap { reset_champs_cache }
end
private
@ -156,7 +155,7 @@ module DossierChampsConcern
def filled_champ(type_de_champ, row_id)
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
if champ.blank? || !champ.visible?
if type_de_champ.champ_blank?(champ) || !champ.visible?
nil
else
champ
@ -188,7 +187,7 @@ module DossierChampsConcern
attributes[:data] = nil
end
@champs_by_public_id = nil
reset_champs_cache
[champ, attributes]
end
@ -202,4 +201,17 @@ module DossierChampsConcern
raise "type_de_champ #{type_de_champ.stable_id} in revision #{revision_id} can not have a row_id because it is not part of a repetition"
end
end
def reset_champs_cache
@champs_by_public_id = nil
@filled_champs_public = nil
@filled_champs_private = nil
@project_champs_public = nil
@project_champs_private = nil
end
def reload_champs_cache
champs.reload if persisted?
reset_champs_cache
end
end

View file

@ -68,7 +68,7 @@ module DossierRebaseConcern
changes_by_op[:remove].each { champs_by_stable_id[_1.stable_id].destroy_all }
# update champ
changes_by_op[:update].each { apply(_1, champs_by_stable_id[_1.stable_id]) }
changes_by_op[:update].each { champs_by_stable_id[_1.stable_id].update_all(rebased_at: Time.zone.now) }
# update dossier revision
update_column(:revision_id, target_revision.id)
@ -79,40 +79,6 @@ module DossierRebaseConcern
.each { add_new_champs_for_revision(_1) }
end
def apply(change, champs)
case change.attribute
when :type_champ
champs.each { purge_piece_justificative_file(_1) }
GeoArea.where(champ: champs).destroy_all
Etablissement.where(champ: champs).destroy_all
champs.update_all(type: "Champs::#{change.to.classify}Champ",
value: nil,
value_json: nil,
external_id: nil,
data: nil,
rebased_at: Time.zone.now)
when :drop_down_options
# we are removing options, we need to remove the value if it contains one of the removed options
removed_options = change.from - change.to
if removed_options.present? && champs.any? { _1.in?(removed_options) }
champs.filter { _1.in?(removed_options) }.each do
_1.remove_option(removed_options)
_1.update_column(:rebased_at, Time.zone.now)
end
end
when :carte_layers
# if we are removing cadastres layer, we need to remove cadastre geo areas
if change.from.include?(:cadastres) && !change.to.include?(:cadastres)
champs.filter { _1.cadastres.present? }.each do
_1.cadastres.each(&:destroy)
_1.update_column(:rebased_at, Time.zone.now)
end
end
else
champs.update_all(rebased_at: Time.zone.now)
end
end
def add_new_champs_for_revision(target_coordinate)
if target_coordinate.child?
row_ids = repetition_row_ids(target_coordinate.parent.type_de_champ)

View file

@ -92,5 +92,7 @@ class DossierPreloader
if dossier.etablissement
dossier.etablissement.association(:champ).target = nil
end
dossier.send(:reset_champs_cache)
end
end

View file

@ -5,7 +5,7 @@ module RoutingEngine
return if dossier.forced_groupe_instructeur
matching_groupe = dossier.procedure.groupe_instructeurs.active.reject(&:invalid_rule?).find do |gi|
gi.routing_rule&.compute(dossier.champs)
gi.routing_rule&.compute(dossier.filled_champs)
end
matching_groupe ||= dossier.procedure.defaut_groupe_instructeur

View file

@ -1,4 +1,17 @@
# frozen_string_literal: true
class TypesDeChamp::DropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBase
def champ_blank?(champ)
super || !champ_value_in_options?(champ)
end
private
def champ_value_in_options?(champ)
champ_with_other_value?(champ) || drop_down_options.include?(champ.value)
end
def champ_with_other_value?(champ)
drop_down_other? && champ.value_json['other']
end
end

View file

@ -3,7 +3,6 @@
class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBase
PRIMARY_PATTERN = /^--(.*)--$/
delegate :drop_down_options, to: :@type_de_champ
validate :check_presence_of_primary_options
def libelles_for_export
@ -112,9 +111,17 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
options.unshift('')
end
def primary_value(champ) = unpack_value(champ.value, 0)
def secondary_value(champ) = unpack_value(champ.value, 1)
def unpack_value(value, index) = value&.then { JSON.parse(_1)[index] rescue nil }
def primary_value(champ) = unpack_value(champ.value, 0, primary_options)
def secondary_value(champ) = unpack_value(champ.value, 1, secondary_options.values.flatten)
def unpack_value(value, index, options)
value&.then do
unpacked_value = JSON.parse(_1)[index]
unpacked_value if options.include?(unpacked_value)
rescue
nil
end
end
def has_secondary_options_for_primary?(champ)
primary_value(champ).present? && secondary_options[primary_value(champ)]&.any?(&:present?)

View file

@ -24,7 +24,7 @@ class TypesDeChamp::MultipleDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampB
[champ.value]
else
JSON.parse(champ.value)
end
end.filter { drop_down_options.include?(_1) }
rescue JSON::ParserError
[]
end

View file

@ -3,7 +3,7 @@
class TypesDeChamp::TypeDeChampBase
include ActiveModel::Validations
delegate :description, :libelle, :mandatory, :mandatory?, :stable_id, :fillable?, :public?, :type_champ, :options_for_select, to: :@type_de_champ
delegate :description, :libelle, :mandatory, :mandatory?, :stable_id, :fillable?, :public?, :type_champ, :options_for_select, :drop_down_options, :drop_down_other?, to: :@type_de_champ
FILL_DURATION_SHORT = 10.seconds
FILL_DURATION_MEDIUM = 1.minute

View file

@ -135,11 +135,8 @@ class PiecesJustificativesService
end
def pjs_for_champs(dossiers)
champs = dossiers.flat_map(&:champs).filter { _1.type == "Champs::PieceJustificativeChamp" }
if !liste_documents_allows?(:with_champs_private)
champs = champs.reject(&:private?)
end
champs = liste_documents_allows?(:with_champs_private) ? dossiers.flat_map(&:filled_champs) : dossiers.flat_map(&:filled_champs_public)
champs = champs.filter { _1.piece_justificative? && _1.is_type?(_1.type_de_champ.type_champ) }
champs_id_row_index = compute_champ_id_row_index(champs)

View file

@ -69,7 +69,7 @@ FactoryBot.define do
end
factory :champ_do_not_use_linked_drop_down_list, class: 'Champs::LinkedDropDownListChamp' do
value { '["categorie 1", "choix 1"]' }
value { '["primary", "secondary"]' }
end
factory :champ_do_not_use_pays, class: 'Champs::PaysChamp' do

View file

@ -218,7 +218,7 @@ describe Champ do
context 'when type_de_champ is multiple_drop_down_list' do
let(:champ) { Champs::MultipleDropDownListChamp.new(value:, dossier: build(:dossier)) }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_multiple_drop_down_list)) }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_multiple_drop_down_list, drop_down_options: ["Crétinier", "Mousserie"])) }
let(:value) { '["Crétinier", "Mousserie"]' }
@ -308,7 +308,7 @@ describe Champ do
context 'for drop down list champ' do
let(:champ) { Champs::DropDownListChamp.new(value:) }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_drop_down_list)) }
let(:value) { "HLM" }
let(:value) { "val1" }
it { is_expected.to eq([value]) }
end
@ -334,13 +334,15 @@ describe Champ do
end
context 'for linked drop down list champ' do
let(:champ) { Champs::LinkedDropDownListChamp.new(primary_value: "hello", secondary_value: "world") }
let(:champ) { Champs::LinkedDropDownListChamp.new(value: '["hello","world"]') }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_linked_drop_down_list, drop_down_options: ['--hello--', 'world'])) }
it { is_expected.to eq(["hello", "world"]) }
end
context 'for multiple drop down list champ' do
let(:champ) { Champs::MultipleDropDownListChamp.new(value:) }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_multiple_drop_down_list, drop_down_options: ['goodbye', 'cruel', 'world'])) }
context 'when there are multiple values selected' do
let(:value) { JSON.generate(['goodbye', 'cruel', 'world']) }

View file

@ -2,24 +2,28 @@
describe Champs::LinkedDropDownListChamp do
describe '#unpack_value' do
let(:champ) { Champs::LinkedDropDownListChamp.new(value: '["tata", "tutu"]') }
let(:champ) { Champs::LinkedDropDownListChamp.new(value: '["primary", "secondary"]', dossier: build(:dossier)) }
before { allow(champ).to receive(:type_de_champ).and_return(build(:type_de_champ_linked_drop_down_list)) }
it { expect(champ.primary_value).to eq('tata') }
it { expect(champ.secondary_value).to eq('tutu') }
end
describe '#pack_value' do
let(:champ) { Champs::LinkedDropDownListChamp.new(primary_value: 'tata', secondary_value: 'tutu') }
it { expect(champ.value).to eq('["tata","tutu"]') }
it { expect(champ.primary_value).to eq('primary') }
it { expect(champ.secondary_value).to eq('secondary') }
end
describe '#primary_value=' do
let(:champ) { Champs::LinkedDropDownListChamp.new(primary_value: 'tata', secondary_value: 'tutu') }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :linked_drop_down_list }]) }
let(:dossier) { create(:dossier, procedure:) }
let(:champ) { dossier.champs.first }
before { champ.primary_value = '' }
it { expect(champ.value).to eq('["",""]') }
it {
champ.primary_value = 'primary'
expect(champ.value).to eq('["primary",""]')
champ.secondary_value = 'secondary'
expect(champ.value).to eq('["primary","secondary"]')
champ.primary_value = ''
expect(champ.value).to eq('["",""]')
}
end
describe '#to_s' do

View file

@ -30,7 +30,7 @@ describe Columns::ChampColumn do
expect_type_de_champ_values('checkbox', eq([true]))
expect_type_de_champ_values('drop_down_list', eq(['val1']))
expect_type_de_champ_values('multiple_drop_down_list', eq([["val1", "val2"]]))
expect_type_de_champ_values('linked_drop_down_list', eq([nil, "categorie 1", "choix 1"]))
expect_type_de_champ_values('linked_drop_down_list', eq([nil, "primary", "secondary"]))
expect_type_de_champ_values('yes_no', eq([true]))
expect_type_de_champ_values('annuaire_education', eq([nil]))
expect_type_de_champ_values('piece_justificative', be_an_instance_of(Array))

View file

@ -438,7 +438,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["option", "updated", "v1"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
context 'when a dropdown option is removed' do
@ -450,7 +450,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["option", "updated"])
end
it { expect { subject }.to change { dossier.project_champs_public.first.value }.from('v1').to(nil) }
it { expect { subject }.to change { dossier.project_champs_public.first.to_s }.from('v1').to('') }
end
context 'when a dropdown unused option is removed' do
@ -462,7 +462,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["v1", "updated"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
end
@ -484,7 +484,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["option", "updated", "v1"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
context 'when a dropdown option is removed' do
@ -496,7 +496,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["option", "updated"])
end
it { expect { subject }.to change { dossier.project_champs_public.first.value }.from('["v1","option"]').to('["option"]') }
it { expect { subject }.to change { dossier.project_champs_public.first.to_s }.from('v1, option').to('option') }
end
context 'when a dropdown unused option is removed' do
@ -508,7 +508,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_options: ["v1", "updated"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
end
@ -523,38 +523,38 @@ describe DossierRebaseConcern do
context 'when a dropdown option is added' do
before do
dossier.project_champs_public.first.update(value: '["v1",""]')
dossier.project_champs_public.first.update(value: '["titre1",""]')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_and_ensure_exclusive_use(stable_id)
tdc_to_update.update(drop_down_options: ["--titre1--", "option", "v1", "updated", "--titre2--", "option2", "v2"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
context 'when a dropdown option is removed' do
before do
dossier.project_champs_public.first.update(value: '["v1","option2"]')
dossier.project_champs_public.first.update(value: '["titre2","option2"]')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_and_ensure_exclusive_use(stable_id)
tdc_to_update.update(drop_down_options: ["--titre1--", "option", "updated", "--titre2--", "option2", "v2"])
tdc_to_update.update(drop_down_options: ["--titre1--", "option", "updated", "--titre2--", "v2"])
end
it { expect { subject }.to change { dossier.project_champs_public.first.value }.from('["v1","option2"]').to(nil) }
it { expect { subject }.to change { dossier.project_champs_public.first.to_s }.from('titre2 / option2').to('titre2') }
end
context 'when a dropdown unused option is removed' do
before do
dossier.project_champs_public.first.update(value: '["v1",""]')
dossier.project_champs_public.first.update(value: '["titre2",""]')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_and_ensure_exclusive_use(stable_id)
tdc_to_update.update(drop_down_options: ["--titre1--", "v1", "updated", "--titre2--", "option2", "v2"])
end
it { expect { subject }.not_to change { dossier.project_champs_public.first.value } }
it { expect { subject }.not_to change { dossier.project_champs_public.first.to_s } }
end
end
@ -650,7 +650,7 @@ describe DossierRebaseConcern do
it { expect { subject }.to change { dossier.revision.types_de_champ_public.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
it { expect { subject }.to change { first_champ.class }.from(Champs::TextChamp).to(Champs::IntegerNumberChamp) }
it { expect { subject }.to change { first_champ.value }.from('v1').to(nil) }
it { expect { subject }.to change { first_champ.to_s }.from('v1').to('') }
it { expect { subject }.to change { first_champ.external_id }.from('123').to(nil) }
it { expect { subject }.to change { first_champ.data }.from({ 'a' => 1 }).to(nil) }
it { expect { subject }.to change { first_champ.geo_areas.count }.from(1).to(0) }
@ -730,6 +730,7 @@ describe DossierRebaseConcern do
it { expect { subject }.to change { dossier.champs.filter(&:child?).count }.from(2).to(0) }
it { expect { subject }.to change { Champ.count }.from(3).to(1) }
it { expect { subject }.to change { dossier.project_champs_public.find(&:repetition?)&.libelle }.from('p1').to(nil) }
end
end
end

View file

@ -249,7 +249,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'when the procedure has a linked drop down menus type de champ' do
let(:type_de_champ) { procedure.draft_revision.types_de_champ.first }
let(:types_de_champ_public) { [{ type: :linked_drop_down_list, libelle: 'libelle' }] }
let(:types_de_champ_public) { [{ type: :linked_drop_down_list, libelle: 'libelle', options: ["--primo--", "secundo"] }] }
let(:template) { 'tout : --libelle--, primaire : --libelle/primaire--, secondaire : --libelle/secondaire--' }
context 'and the champ has no value' do
@ -275,7 +275,7 @@ describe TagsSubstitutionConcern, type: :model do
context 'and the same libelle is used by a header' do
let(:types_de_champ_public) do
[
{ type: :linked_drop_down_list, libelle: 'libelle' },
{ type: :linked_drop_down_list, libelle: 'libelle', options: ["--primo--", "secundo"] },
{ type: :header_section, libelle: 'libelle' }
]
end

View file

@ -77,6 +77,7 @@ describe PiecesJustificativesService do
expect(export_template).to receive(:attachment_path)
.with(dossier, second_child_attachments.first, index: 0, row_index: 1, champ: second_champ)
DossierPreloader.new(dossiers).all
count = 0
callback = lambda { |*_args| count += 1 }
@ -84,7 +85,7 @@ describe PiecesJustificativesService do
subject
end
expect(count).to eq(10)
expect(count).to eq(0)
end
end
end

View file

@ -5,7 +5,7 @@ describe 'views/shared/champs/multiple_drop_down_list/_show', type: :view do
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
let(:champ) { dossier.champs.first }
before { champ.update(value: ['abc', '2, 3, 4']) }
before { champ.update(value: champ.drop_down_options) }
subject { render partial: 'shared/champs/multiple_drop_down_list/show', locals: { champ: } }
it 'renders the view' do