demarches-normaliennes/app/models/concerns/dossier_clone_concern.rb
2024-03-26 11:23:23 +01:00

176 lines
5.2 KiB
Ruby

module DossierCloneConcern
extend ActiveSupport::Concern
included do
belongs_to :parent_dossier, class_name: 'Dossier', optional: true
has_many :cloned_dossiers, class_name: 'Dossier', foreign_key: :parent_dossier_id, dependent: :nullify, inverse_of: :parent_dossier
belongs_to :editing_fork_origin, class_name: 'Dossier', optional: true
has_many :editing_forks, -> { where(hidden_by_reason: nil) }, class_name: 'Dossier', foreign_key: :editing_fork_origin_id, dependent: :destroy, inverse_of: :editing_fork_origin
end
def find_or_create_editing_fork(user)
find_editing_fork(user) || clone(user:, fork: true)
end
def find_editing_fork(user, rebase: true)
fork = editing_forks.find_by(user:)
fork.rebase! if rebase && fork
fork
end
def owner_editing_fork
find_or_create_editing_fork(user).tap { DossierPreloader.load_one(_1) }
end
def reset_editing_fork!
if editing_fork? && forked_with_changes?
destroy_editing_fork!
end
end
def destroy_editing_fork!
if editing_fork?
update!(hidden_by_administration_at: Time.current, hidden_by_reason: :stale_fork)
DestroyRecordLaterJob.perform_later(self)
end
end
def editing_fork?
editing_fork_origin_id.present?
end
def make_diff(editing_fork)
origin_champs_index = champs_for_revision(scope: :public).index_by(&:public_id)
forked_champs_index = editing_fork.champs_for_revision(scope: :public).index_by(&:public_id)
updated_champs_index = editing_fork
.champs_for_revision(scope: :public)
.filter { _1.updated_at > editing_fork.created_at }
.index_by(&:public_id)
added = forked_champs_index.keys - origin_champs_index.keys
removed = origin_champs_index.keys - forked_champs_index.keys
updated = updated_champs_index.keys - added
{
added: added.map { forked_champs_index[_1] },
updated: updated.map { forked_champs_index[_1] },
removed: removed.map { origin_champs_index[_1] }
}
end
def merge_fork(editing_fork)
return false if invalid? || editing_fork.invalid?
return false if revision_id > editing_fork.revision_id
transaction do
rebase!(force: true)
diff = make_diff(editing_fork)
apply_diff(diff)
touch(:last_champ_updated_at)
end
reload
update_search_terms_later
editing_fork.destroy_editing_fork!
end
def clone(user: nil, fork: false)
dossier_attributes = [:autorisation_donnees, :revision_id]
dossier_attributes += [:groupe_instructeur_id] if fork
relationships = [:individual, :etablissement]
cloned_champs = champs_for_revision
.index_by(&:id)
.transform_values { [_1, _1.clone(fork)] }
cloned_dossier = deep_clone(only: dossier_attributes, include: relationships) do |original, kopy|
ClonePiecesJustificativesService.clone_attachments(original, kopy)
if original.is_a?(Dossier)
if fork
kopy.editing_fork_origin = original
else
kopy.parent_dossier = original
end
kopy.user = user || original.user
kopy.state = Dossier.states.fetch(:brouillon)
kopy.champs = cloned_champs.values.map do |(_, champ)|
champ.dossier = kopy
champ.parent = cloned_champs[champ.parent_id].second if champ.child?
champ
end
end
end
transaction do
cloned_dossier.save!(validate: !fork)
cloned_dossier.rebase!
end
if fork
cloned_champs.values.each do |(original, champ)|
champ.update_columns(created_at: original.created_at, updated_at: original.updated_at)
end
end
cloned_dossier.reload
end
def forked_with_changes?
if forked_diff.present?
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
end
end
def champ_forked_with_changes?(champ)
if forked_diff.present?
forked_diff.values.any? { _1.include?(champ) }
end
end
private
def forked_diff
@forked_diff ||= editing_fork? ? editing_fork_origin.make_diff(self) : nil
end
def forked_groupe_instructeur_changed?
editing_fork_origin.groupe_instructeur_id != groupe_instructeur_id
end
def apply_diff(diff)
champs_index = (champs_for_revision(scope: :public) + diff[:added]).index_by(&:public_id)
diff[:added].each do |champ|
if champ.child?
champ.update_columns(dossier_id: id, parent_id: champs_index.fetch(champ.parent.public_id).id)
else
champ.update_column(:dossier_id, id)
end
end
champs_to_remove = []
diff[:updated].each do |champ|
old_champ = champs_index.fetch(champ.public_id)
champs_to_remove << old_champ
if champ.child?
# we need to do that in order to avoid a foreign key constraint
old_champ.update(row_id: nil)
champ.update_columns(dossier_id: id, parent_id: champs_index.fetch(champ.parent.public_id).id)
else
champ.update_column(:dossier_id, id)
end
end
champs_to_remove += diff[:removed]
children_champs_to_remove, root_champs_to_remove = champs_to_remove.partition(&:child?)
children_champs_to_remove.each(&:destroy!)
Champ.where(parent_id: root_champs_to_remove.map(&:id)).destroy_all
root_champs_to_remove.each(&:destroy!)
end
end