demarches-normaliennes/lib/tasks/dossier_procedure_migrator.rb

161 lines
6.5 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module Tasks
class DossierProcedureMigrator
# Migrates dossiers from an old source procedure to a revised destination procedure.
class ChampMapping
attr_reader :expected_source_types_de_champ
attr_reader :expected_destination_types_de_champ
def initialize(source_procedure, destination_procedure)
@source_procedure = source_procedure
@destination_procedure = destination_procedure
@expected_source_types_de_champ = {}
@expected_destination_types_de_champ = {}
@source_to_destination_mapping = {}
@source_champs_to_discard = Set[]
@destination_champ_computations = []
end
def destination_type_de_champ(champ)
@source_to_destination_mapping[champ.type_de_champ.order_place]
end
def discard_champ?(champ)
@source_champs_to_discard.member?(champ.type_de_champ.order_place)
end
def compute_new_champs(dossier)
@destination_champ_computations.each do |tdc, block|
dossier.champs << block.call(dossier, tdc)
end
end
private
def map_source_to_destination_champ(source_order_place, destination_order_place, source_overrides: {}, destination_overrides: {})
destination_type_de_champ = @destination_procedure.types_de_champ.find_by(order_place: destination_order_place)
@expected_source_types_de_champ[source_order_place] =
type_de_champ_to_expectation(destination_type_de_champ)
.merge!(source_overrides)
@expected_destination_types_de_champ[destination_order_place] =
type_de_champ_to_expectation(@source_procedure.types_de_champ.find_by(order_place: source_order_place))
.merge!({ "mandatory" => false }) # Even if the source was mandatory, its ok for the destination to be optional
.merge!(destination_overrides)
@source_to_destination_mapping[source_order_place] = destination_type_de_champ
end
def discard_source_champ(source_type_de_champ)
@expected_source_types_de_champ[source_type_de_champ.order_place] = type_de_champ_to_expectation(source_type_de_champ)
@source_champs_to_discard << source_type_de_champ.order_place
end
def compute_destination_champ(destination_type_de_champ, &block)
@expected_destination_types_de_champ[destination_type_de_champ.order_place] = type_de_champ_to_expectation(destination_type_de_champ)
@destination_champ_computations << [@destination_procedure.types_de_champ.find_by(order_place: destination_type_de_champ.order_place), block]
end
def type_de_champ_to_expectation(tdc)
if tdc.present?
expectation = tdc.as_json(only: [:libelle, :type, :type_champ, :mandatory])
expectation['drop_down'] = tdc.drop_down_list.presence&.options&.presence
expectation
else
{}
end
end
end
def initialize(source_procedure, destination_procedure, champ_mapping)
@source_procedure = source_procedure
@destination_procedure = destination_procedure
@champ_mapping = champ_mapping
end
def migrate_procedure
check_consistency
migrate_dossiers
migrate_gestionnaires
publish_destination_procedure_in_place_of_source
end
def check_consistency
check_same_administrateur
check_source_destination_champs_consistency
end
def check_same_administrateur
if @source_procedure.administrateur != @destination_procedure.administrateur
raise "Mismatching administrateurs #{@source_procedure.administrateur&.email}#{@destination_procedure.administrateur&.email}"
end
end
def check_source_destination_champs_consistency
check_champs_consistency('source', @champ_mapping.expected_source_types_de_champ, @source_procedure.types_de_champ)
check_champs_consistency('destination', @champ_mapping.expected_destination_types_de_champ, @destination_procedure.types_de_champ)
end
def check_champs_consistency(label, expected_tdcs, actual_tdcs)
if actual_tdcs.size != expected_tdcs.size
raise "Incorrect #{label} size #{actual_tdcs.size} (expected #{expected_tdcs.size})"
end
actual_tdcs.each { |tdc| check_champ_consistency(label, expected_tdcs[tdc.order_place], tdc) }
end
def check_champ_consistency(label, expected_tdc, actual_tdc)
errors = []
if actual_tdc.libelle != expected_tdc['libelle']
errors.append("incorrect libelle #{actual_tdc.libelle} (expected #{expected_tdc['libelle']})")
end
if actual_tdc.type != expected_tdc['type']
errors.append("incorrect type #{actual_tdc.type} (expected #{expected_tdc['type']})")
end
if actual_tdc.type_champ != expected_tdc['type_champ']
errors.append("incorrect type champ #{actual_tdc.type_champ} (expected #{expected_tdc['type_champ']})")
end
if (!actual_tdc.mandatory) && expected_tdc['mandatory']
errors.append("champ should be mandatory")
end
drop_down = actual_tdc.drop_down_list.presence&.options&.presence
if drop_down != expected_tdc['drop_down']
errors.append("incorrect drop down list #{drop_down} (expected #{expected_tdc['drop_down']})")
end
if errors.present?
fail "On #{label} type de champ #{actual_tdc.order_place} (#{actual_tdc.libelle}) " + errors.join(', ')
end
end
def migrate_dossiers
@source_procedure.dossiers.find_each(batch_size: 100) do |d|
# Since were going to iterate and change the champs at the same time,
# we use to_a to make the list static and avoid nasty surprises
original_champs = d.champs.to_a
@champ_mapping.compute_new_champs(d)
original_champs.each do |c|
tdc_to = @champ_mapping.destination_type_de_champ(c)
if tdc_to.present?
c.update(type_de_champ: tdc_to)
elsif @champ_mapping.discard_champ?(c)
d.champs.destroy(c)
else
fail "Unhandled source type de champ #{c.type_de_champ.order_place}"
end
end
# Use update_columns to avoid triggering build_default_champs
d.update_columns(procedure_id: @destination_procedure.id)
end
end
def migrate_gestionnaires
@source_procedure.gestionnaires.find_each(batch_size: 100) { |g| g.assign_to_procedure(@destination_procedure) }
end
def publish_destination_procedure_in_place_of_source
@destination_procedure.publish!(@source_procedure.path)
@source_procedure.archive
end
end
end