Migrate nutriscore dossiers from old procedure to new procedure
This commit is contained in:
parent
feb5105b18
commit
580c6145d0
3 changed files with 395 additions and 0 deletions
161
lib/tasks/dossier_procedure_migrator.rb
Normal file
161
lib/tasks/dossier_procedure_migrator.rb
Normal file
|
@ -0,0 +1,161 @@
|
|||
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, it’s 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 we’re 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
|
Loading…
Add table
Add a link
Reference in a new issue