Merge pull request #2802 from betagouv/frederic/fix_2772-migrate_dossiers
Restore deleted dossiers
This commit is contained in:
commit
758c47343f
6 changed files with 466 additions and 71 deletions
|
@ -21,4 +21,21 @@ class DossierMailer < ApplicationMailer
|
|||
|
||||
mail(to: dossier.user.email, subject: subject)
|
||||
end
|
||||
|
||||
def notify_undelete_to_user(dossier)
|
||||
@dossier = dossier
|
||||
@dossier_kind = dossier.brouillon? ? 'brouillon' : 'dossier'
|
||||
@subject = "Votre #{@dossier_kind} n° #{@dossier.id} est à nouveau accessible"
|
||||
|
||||
mail(to: dossier.user.email, subject: @subject)
|
||||
end
|
||||
|
||||
def notify_unmigrated_to_user(dossier, new_procedure)
|
||||
@dossier = dossier
|
||||
@dossier_kind = dossier.brouillon? ? 'brouillon' : 'dossier'
|
||||
@subject = "Changement de procédure pour votre #{@dossier_kind} n° #{@dossier.id}"
|
||||
@new_procedure = new_procedure
|
||||
|
||||
mail(to: dossier.user.email, subject: @subject)
|
||||
end
|
||||
end
|
||||
|
|
15
app/views/dossier_mailer/notify_undelete_to_user.html.haml
Normal file
15
app/views/dossier_mailer/notify_undelete_to_user.html.haml
Normal file
|
@ -0,0 +1,15 @@
|
|||
- content_for(:title, @subject)
|
||||
|
||||
%h1 Bonjour,
|
||||
|
||||
%p
|
||||
En raison d’un incident, votre
|
||||
= link_to("#{@dossier_kind} n° #{@dossier.id}", dossier_url(@dossier))
|
||||
sur la procédure « #{@dossier.procedure.libelle} » a été inaccessible pendant quelques jours.
|
||||
|
||||
L’accès est à présent à nouveau possible. Nous vous présentons nos excuses pour toute gène occasionnée.
|
||||
%p
|
||||
Bonne journée,
|
||||
|
||||
%p
|
||||
L'équipe demarches-simplifiees.fr
|
26
app/views/dossier_mailer/notify_unmigrated_to_user.html.haml
Normal file
26
app/views/dossier_mailer/notify_unmigrated_to_user.html.haml
Normal file
|
@ -0,0 +1,26 @@
|
|||
- content_for(:title, @subject)
|
||||
|
||||
%h1 Bonjour,
|
||||
|
||||
%p
|
||||
Vous avez commencé un #{@dossier_kind},
|
||||
= link_to("n° #{@dossier.id}", dossier_url(@dossier))
|
||||
sur la procédure « #{@dossier.procedure.libelle} ».
|
||||
En raison d’un changement dans la procédure, votre #{@dossier_kind} a été inaccessible pendant quelques jours.
|
||||
L’accès est à présent à nouveau possible.
|
||||
|
||||
%p
|
||||
Malheureusement, en raison des changements dans le procédure, vous ne pourrez pas mener à terme le #{@dossier_kind} commencé.
|
||||
Si votre démarche est toujours d’actualité, nous vous invitons à la recommencer sur
|
||||
= link_to("la nouvelle procédure", commencer_url(@new_procedure.path))
|
||||
\.
|
||||
|
||||
%p
|
||||
Nous avons pris des mesures pour nous assurer qu’un tel désagrément ne se reproduise pas,
|
||||
et vous présentons nos excuses pour la gène occasionnée.
|
||||
|
||||
%p
|
||||
Bonne journée,
|
||||
|
||||
%p
|
||||
L'équipe demarches-simplifiees.fr
|
|
@ -9,12 +9,7 @@ namespace :'2018_07_31_nutriscore' do
|
|||
destination_procedure = Procedure.find(destination_procedure_id)
|
||||
|
||||
mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
|
||||
def initialize(source_procedure, destination_procedure)
|
||||
super
|
||||
setup_champ_mapping
|
||||
end
|
||||
|
||||
def setup_champ_mapping
|
||||
def setup_mapping
|
||||
siret_order_place = 2
|
||||
fonction_order_place = 9
|
||||
zone_geographique_header_order_place = 18
|
||||
|
@ -100,7 +95,7 @@ namespace :'2018_07_31_nutriscore' do
|
|||
target_tdc.champ.create(dossier: d, value: JSON.unparse(['FRANCE']))
|
||||
end
|
||||
end
|
||||
end.new(source_procedure, destination_procedure)
|
||||
end
|
||||
|
||||
Tasks::DossierProcedureMigrator.new(source_procedure, destination_procedure, mapping).migrate_procedure
|
||||
AutoReceiveDossiersForProcedureJob.set(cron: "* * * * *").perform_later(destination_procedure_id, 'accepte')
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
require Rails.root.join("lib", "tasks", "task_helper")
|
||||
|
||||
namespace :after_party do
|
||||
desc 'Deployment task: restore_deleted_dossiers'
|
||||
task restore_deleted_dossiers: :environment do
|
||||
Class.new do
|
||||
def run
|
||||
rake_puts "Running deploy task 'restore_deleted_dossiers'"
|
||||
restore_candidats_libres_deleted_dossiers
|
||||
restore_neph_deleted_dossiers
|
||||
AfterParty::TaskRecord.create version: '20181009130216'
|
||||
end
|
||||
|
||||
def restore_candidats_libres_deleted_dossiers
|
||||
mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
|
||||
def setup_mapping
|
||||
champ_opts = {
|
||||
16 => {
|
||||
source_overrides: { 'libelle' => 'Adresse postale du candidat' },
|
||||
destination_overrides: { 'libelle' => 'Adresse postale complète du candidat' }
|
||||
}
|
||||
}
|
||||
(0..23).each do |i|
|
||||
map_source_to_destination_champ(i, i, **(champ_opts[i] || {}))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private_mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
|
||||
def setup_mapping
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'datetime',
|
||||
order_place: 0,
|
||||
libelle: 'Date et heure de convocation',
|
||||
mandatory: false
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
target_tdc.champ.create(dossier: d)
|
||||
end
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'text',
|
||||
order_place: 1,
|
||||
libelle: 'Lieu de convocation',
|
||||
mandatory: false
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
target_tdc.champ.create(dossier: d)
|
||||
end
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'address',
|
||||
order_place: 2,
|
||||
libelle: 'Adresse centre examen',
|
||||
mandatory: false
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
target_tdc.champ.create(dossier: d)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pj_mapping = Class.new(Tasks::DossierProcedureMigrator::PieceJustificativeMapping) do
|
||||
def setup_mapping
|
||||
(0..3).each do |i|
|
||||
map_source_to_destination_pj(i, i + 2)
|
||||
end
|
||||
leave_destination_pj_blank(
|
||||
TypeDePieceJustificative.new(
|
||||
order_place: 0,
|
||||
libelle: "Télécharger la Charte de l'accompagnateur"
|
||||
)
|
||||
)
|
||||
leave_destination_pj_blank(
|
||||
TypeDePieceJustificative.new(
|
||||
order_place: 1,
|
||||
libelle: "Télécharger l'attestation d'assurance"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
restore_deleted_dossiers(4860, 8603, mapping, private_mapping, pj_mapping)
|
||||
end
|
||||
|
||||
def restore_neph_deleted_dossiers
|
||||
mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
|
||||
def can_migrate?(dossier)
|
||||
!(dossier.termine? ||
|
||||
dossier.champs.joins(:type_de_champ).find_by(types_de_champ: { order_place: 3 }).value&.include?('"Demande de duplicata de dossier d\'inscription (suite perte)"'))
|
||||
end
|
||||
|
||||
def setup_mapping
|
||||
champ_opts = {
|
||||
3 => {
|
||||
source_overrides: { 'drop_down' => ["", "Demande de réactualisation du numéro NEPH", "Demande de communication du numéro NEPH", "Demande de duplicata de dossier d'inscription (suite perte)", "Demande de correction sur le Fichier National des Permis de conduire"] },
|
||||
destination_overrides: { 'drop_down' => ["", "Demande de réactualisation du numéro NEPH", "Demande de communication du numéro NEPH", "Demande de correction sur le Fichier National des Permis de conduire"] }
|
||||
}
|
||||
}
|
||||
(0..14).each do |i|
|
||||
map_source_to_destination_champ(i, i, **(champ_opts[i] || {}))
|
||||
end
|
||||
(16..22).each do |i|
|
||||
map_source_to_destination_champ(i, i + 2, **(champ_opts[i] || {}))
|
||||
end
|
||||
|
||||
discard_source_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'address',
|
||||
order_place: 15,
|
||||
libelle: 'Adresse du candidat'
|
||||
)
|
||||
)
|
||||
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'address',
|
||||
order_place: 15,
|
||||
libelle: 'Adresse du candidat',
|
||||
mandatory: true
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
value = d.champs.joins(:type_de_champ).find_by(types_de_champ: { order_place: 3 }).value
|
||||
if !d.brouillon?
|
||||
value ||= 'non renseigné'
|
||||
end
|
||||
target_tdc.champ.create(dossier: d, value: value)
|
||||
end
|
||||
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'address',
|
||||
order_place: 16,
|
||||
libelle: 'Code postal',
|
||||
mandatory: true
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
target_tdc.champ.create(dossier: d, value: d.brouillon? ? nil : 'non renseigné')
|
||||
end
|
||||
|
||||
compute_destination_champ(
|
||||
TypeDeChamp.new(
|
||||
type_champ: 'address',
|
||||
order_place: 17,
|
||||
libelle: 'Ville',
|
||||
mandatory: true
|
||||
)
|
||||
) do |d, target_tdc|
|
||||
target_tdc.champ.create(dossier: d, value: d.brouillon? ? nil : 'non renseigné')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private_mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
|
||||
def setup_mapping
|
||||
(0..2).each do |i|
|
||||
map_source_to_destination_champ(i, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pj_mapping = Class.new(Tasks::DossierProcedureMigrator::PieceJustificativeMapping) do
|
||||
def setup_mapping
|
||||
(0..3).each do |i|
|
||||
map_source_to_destination_pj(i, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
restore_deleted_dossiers(6388, 8770, mapping, private_mapping, pj_mapping)
|
||||
end
|
||||
|
||||
def restore_deleted_dossiers(deleted_procedure_id, new_procedure_id, champ_mapping, champ_private_mapping, pj_mapping)
|
||||
source_procedure = Procedure.unscoped.find(deleted_procedure_id)
|
||||
destination_procedure = Procedure.find(new_procedure_id)
|
||||
|
||||
deleted_dossiers = Dossier.unscoped
|
||||
.where(procedure_id: deleted_procedure_id)
|
||||
.where('dossiers.hidden_at >= ?', source_procedure.hidden_at)
|
||||
|
||||
deleted_dossier_ids = deleted_dossiers.pluck(:id).to_a
|
||||
deleted_dossiers.update_all(hidden_at: nil)
|
||||
|
||||
source_procedure
|
||||
.update_columns(
|
||||
hidden_at: nil,
|
||||
archived_at: source_procedure.hidden_at,
|
||||
aasm_state: :archivee
|
||||
)
|
||||
|
||||
migrator = Tasks::DossierProcedureMigrator.new(source_procedure, destination_procedure, champ_mapping, champ_private_mapping, pj_mapping) do |dossier|
|
||||
DossierMailer.notify_undelete_to_user(dossier).deliver_later
|
||||
end
|
||||
migrator.check_consistency
|
||||
migrator.migrate_dossiers
|
||||
|
||||
source_procedure.dossiers.where(id: deleted_dossier_ids).find_each do |dossier|
|
||||
if dossier.termine?
|
||||
DossierMailer.notify_undelete_to_user(dossier).deliver_later
|
||||
else
|
||||
rake_puts "Dossier #{dossier.id} non migré\n"
|
||||
DossierMailer.notify_unmigrated_to_user(dossier, destination_procedure).deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
end.new.run
|
||||
end
|
||||
end
|
|
@ -3,18 +3,54 @@ module Tasks
|
|||
# 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)
|
||||
def initialize(source_procedure, destination_procedure, is_private)
|
||||
@source_procedure = source_procedure
|
||||
@destination_procedure = destination_procedure
|
||||
@is_private = is_private
|
||||
|
||||
@expected_source_types_de_champ = {}
|
||||
@expected_destination_types_de_champ = {}
|
||||
@source_to_destination_mapping = {}
|
||||
@source_champs_to_discard = Set[]
|
||||
@destination_champ_computations = []
|
||||
|
||||
setup_mapping
|
||||
end
|
||||
|
||||
def check_source_destination_consistency
|
||||
check_champs_consistency("#{privacy_label}source", @expected_source_types_de_champ, types_de_champ(@source_procedure))
|
||||
check_champs_consistency("#{privacy_label}destination", @expected_destination_types_de_champ, types_de_champ(@destination_procedure))
|
||||
end
|
||||
|
||||
def can_migrate?(dossier)
|
||||
true
|
||||
end
|
||||
|
||||
def migrate(dossier)
|
||||
# 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 = champs(dossier).to_a
|
||||
|
||||
compute_new_champs(dossier)
|
||||
|
||||
original_champs.each do |c|
|
||||
tdc_to = destination_type_de_champ(c)
|
||||
if tdc_to.present?
|
||||
c.update_columns(type_de_champ_id: tdc_to.id)
|
||||
elsif discard_champ?(c)
|
||||
champs(dossier).destroy(c)
|
||||
else
|
||||
fail "Unhandled source #{privacy_label}type de champ #{c.type_de_champ.order_place}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_new_champs(dossier)
|
||||
@destination_champ_computations.each do |tdc, block|
|
||||
champs(dossier) << block.call(dossier, tdc)
|
||||
end
|
||||
end
|
||||
|
||||
def destination_type_de_champ(champ)
|
||||
|
@ -25,74 +61,19 @@ module Tasks
|
|||
@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
|
||||
def setup_mapping
|
||||
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
|
||||
def champs(dossier)
|
||||
@is_private ? dossier.champs_private : dossier.champs
|
||||
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
|
||||
def types_de_champ(procedure)
|
||||
@is_private ? procedure.types_de_champ_private : procedure.types_de_champ
|
||||
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_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)
|
||||
def privacy_label
|
||||
@is_private ? 'private ' : ''
|
||||
end
|
||||
|
||||
def check_champs_consistency(label, expected_tdcs, actual_tdcs)
|
||||
|
@ -122,27 +103,179 @@ module Tasks
|
|||
end
|
||||
end
|
||||
|
||||
def map_source_to_destination_champ(source_order_place, destination_order_place, source_overrides: {}, destination_overrides: {})
|
||||
destination_type_de_champ = types_de_champ(@destination_procedure).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(types_de_champ(@source_procedure).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 << [types_de_champ(@destination_procedure).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_champ, :mandatory])
|
||||
expectation['drop_down'] = tdc.drop_down_list.presence&.options&.presence
|
||||
expectation
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PieceJustificativeMapping
|
||||
def initialize(source_procedure, destination_procedure)
|
||||
@source_procedure = source_procedure
|
||||
@destination_procedure = destination_procedure
|
||||
|
||||
@expected_source_pj = {}
|
||||
@expected_destination_pj = {}
|
||||
@source_to_destination_mapping = {}
|
||||
|
||||
setup_mapping
|
||||
end
|
||||
|
||||
def check_source_destination_consistency
|
||||
check_pjs_consistency('source', @expected_source_pj, @source_procedure.types_de_piece_justificative)
|
||||
check_pjs_consistency('destination', @expected_destination_pj, @destination_procedure.types_de_piece_justificative)
|
||||
end
|
||||
|
||||
def can_migrate?(dossier)
|
||||
true
|
||||
end
|
||||
|
||||
def migrate(dossier)
|
||||
# Since we’re going to iterate and change the pjs at the same time,
|
||||
# we use to_a to make the list static and avoid nasty surprises
|
||||
original_pjs = dossier.pieces_justificatives.to_a
|
||||
|
||||
original_pjs.each do |pj|
|
||||
pj_to = destination_pj(pj)
|
||||
if pj_to.present?
|
||||
pj.update_columns(type_de_piece_justificative_id: pj_to.id)
|
||||
elsif discard_pj?(pj)
|
||||
dossier.pieces_justificatives.destroy(pj)
|
||||
else
|
||||
fail "Unhandled source pièce justificative #{c.type_de_piece_justificative.order_place}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def destination_pj(pj)
|
||||
@source_to_destination_mapping[pj.order_place]
|
||||
end
|
||||
|
||||
def discard_pj?(champ)
|
||||
@source_pjs_to_discard.member?(pj.order_place)
|
||||
end
|
||||
|
||||
def setup_mapping
|
||||
end
|
||||
|
||||
def map_source_to_destination_pj(source_order_place, destination_order_place, source_overrides: {}, destination_overrides: {})
|
||||
destination_pj = @destination_procedure.types_de_piece_justificative.find_by(order_place: destination_order_place)
|
||||
@expected_source_pj[source_order_place] =
|
||||
pj_to_expectation(destination_pj)
|
||||
.merge!(source_overrides)
|
||||
@expected_destination_pj[destination_order_place] =
|
||||
pj_to_expectation(@source_procedure.types_de_piece_justificative.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_pj
|
||||
end
|
||||
|
||||
def discard_source_pj(source_pj)
|
||||
@expected_source_pj[source_pj.order_place] = pj_to_expectation(source_pj)
|
||||
@source_pjs_to_discard << source_pj.order_place
|
||||
end
|
||||
|
||||
def leave_destination_pj_blank(destination_pj)
|
||||
@expected_destination_pj[destination_pj.order_place] = pj_to_expectation(destination_pj)
|
||||
end
|
||||
|
||||
def pj_to_expectation(pj)
|
||||
pj&.as_json(only: [:libelle, :mandatory]) || {}
|
||||
end
|
||||
|
||||
def check_pjs_consistency(label, expected_pjs, actual_pjs)
|
||||
if actual_pjs.size != expected_pjs.size
|
||||
raise "Incorrect #{label} pièce justificative count #{actual_pjs.size} (expected #{expected_pjs.size})"
|
||||
end
|
||||
actual_pjs.each { |pj| check_pj_consistency(label, expected_pjs[pj.order_place], pj) }
|
||||
end
|
||||
|
||||
def check_pj_consistency(label, expected_pj, actual_pj)
|
||||
errors = []
|
||||
if actual_pj.libelle != expected_pj['libelle']
|
||||
errors.append("incorrect libelle #{actual_pj.libelle} (expected #{expected_pj['libelle']})")
|
||||
end
|
||||
if (!actual_pj.mandatory) && expected_pj['mandatory']
|
||||
errors.append("pj should be mandatory")
|
||||
end
|
||||
if errors.present?
|
||||
fail "On #{label} type de pièce justificative #{actual_pj.order_place} (#{actual_pj.libelle}) " + errors.join(', ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(source_procedure, destination_procedure, champ_mapping, private_champ_mapping = ChampMapping, piece_justificative_mapping = PieceJustificativeMapping, &block)
|
||||
@source_procedure = source_procedure
|
||||
@destination_procedure = destination_procedure
|
||||
@champ_mapping = champ_mapping.new(source_procedure, destination_procedure, false)
|
||||
@private_champ_mapping = private_champ_mapping.new(source_procedure, destination_procedure, true)
|
||||
@piece_justificative_mapping = piece_justificative_mapping.new(source_procedure, destination_procedure)
|
||||
|
||||
if block_given?
|
||||
@on_dossier_migration = block
|
||||
end
|
||||
end
|
||||
|
||||
def migrate_procedure
|
||||
check_consistency
|
||||
migrate_dossiers
|
||||
migrate_gestionnaires
|
||||
publish_destination_procedure_in_place_of_source
|
||||
end
|
||||
|
||||
def check_consistency
|
||||
check_same_administrateur
|
||||
@champ_mapping.check_source_destination_consistency
|
||||
@private_champ_mapping.check_source_destination_consistency
|
||||
@piece_justificative_mapping.check_source_destination_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 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
|
||||
if @champ_mapping.can_migrate?(d) && @private_champ_mapping.can_migrate?(d) && @piece_justificative_mapping.can_migrate?(d)
|
||||
@champ_mapping.migrate(d)
|
||||
@private_champ_mapping.migrate(d)
|
||||
@piece_justificative_mapping.migrate(d)
|
||||
|
||||
# Use update_columns to avoid triggering build_default_champs
|
||||
d.update_columns(procedure_id: @destination_procedure.id)
|
||||
@on_dossier_migration&.call(d)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue