refactor(dossier): rebase to use new revisions

This commit is contained in:
Paul Chavard 2022-05-18 15:47:52 +02:00 committed by Paul Chavard
parent bc60247288
commit 0817731d6a
3 changed files with 112 additions and 95 deletions

View file

@ -52,112 +52,134 @@ module DossierRebaseConcern
def rebase def rebase
attachments_to_purge = [] attachments_to_purge = []
geo_areas_to_delete = [] geo_areas_to_delete = []
changes_by_type_de_champ = pending_changes champs_to_delete = []
# revision we are rebasing to
target_revision = procedure.published_revision
# group changes by stable_id
changes_by_stable_id = pending_changes
.filter { |change| change[:model] == :type_de_champ } .filter { |change| change[:model] == :type_de_champ }
.group_by { |change| change[:stable_id] } .group_by { |change| change[:stable_id] }
changes_by_type_de_champ.each do |stable_id, changes| # group current revision types de champ by stable_id
type_de_champ = find_type_de_champ_by_stable_id(stable_id) current_types_de_champ_by_stable_id = revision.types_de_champ.index_by(&:stable_id)
published_type_de_champ = find_type_de_champ_by_stable_id(stable_id, published: true)
# group published types de champ coordinates by stable_id
published_coordinates_by_stable_id = target_revision
.revision_types_de_champ
.includes(:type_de_champ, :parent)
.index_by(&:stable_id)
# add and remove champs
changes_by_stable_id.each do |stable_id, changes|
type_de_champ = current_types_de_champ_by_stable_id[stable_id]
published_coordinate = published_coordinates_by_stable_id[stable_id]
changes.each do |change| changes.each do |change|
case change[:op] case change[:op]
when :add when :add
add_new_champs_for_revision(published_type_de_champ) add_new_champs_for_revision(published_coordinate)
when :remove when :remove
delete_champs_for_revision(type_de_champ) delete_champs_for_revision(type_de_champ)
end end
end end
end end
flattened_all_champs.each do |champ| # find all champs with respective update changes and the published type de champ
changes_by_stable_id = (changes_by_type_de_champ[champ.stable_id] || []) champs_with_changes = Champ.where(dossier: self).includes(:type_de_champ).filter_map do |champ|
.filter { |change| change[:op] == :update } # type de champ from published revision
type_de_champ = published_coordinates_by_stable_id[champ.stable_id]&.type_de_champ
# only update op changes
changes = (changes_by_stable_id[champ.stable_id] || []).filter { |change| change[:op] == :update }
update_champ_for_revision(champ) do |update| if type_de_champ
changes_by_stable_id.each do |change| [champ, type_de_champ, changes]
case change[:attribute]
when :type_champ
update[:type] = "Champs::#{change[:to].classify}Champ"
update[:value] = nil
update[:external_id] = nil
update[:data] = nil
geo_areas_to_delete += champ.geo_areas
if champ.piece_justificative_file.attached?
attachments_to_purge << champ.piece_justificative_file
end
when :drop_down_options
update[:value] = nil
when :carte_layers
if change[:from].include?(:cadastres) && !change[:to].include?(:cadastres)
geo_areas_to_delete += champ.cadastres
end
end
update[:rebased_at] = Time.zone.now
end
end end
end end
self.update_column(:revision_id, procedure.published_revision_id) # apply changes to existing champs and reset values when needed
update_champs_for_revision(champs_with_changes) do |champ, change, update|
case change[:attribute]
when :type_champ
update[:type] = "Champs::#{change[:to].classify}Champ"
update[:value] = nil
update[:external_id] = nil
update[:data] = nil
geo_areas_to_delete += champ.geo_areas
champs_to_delete += champ.champs
if champ.piece_justificative_file.attached?
attachments_to_purge << champ.piece_justificative_file
end
when :drop_down_options
update[:value] = nil
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)
geo_areas_to_delete += champ.cadastres
end
end
update[:rebased_at] = Time.zone.now
end
# update dossier revision
self.update_column(:revision_id, target_revision.id)
# clear orphaned data
attachments_to_purge.each(&:purge_later) attachments_to_purge.each(&:purge_later)
geo_areas_to_delete.each(&:destroy) geo_areas_to_delete.each(&:destroy)
champs_to_delete.each(&:destroy)
end end
def add_new_champs_for_revision(published_type_de_champ) def add_new_champs_for_revision(published_coordinate)
if published_type_de_champ.parent if published_coordinate.child?
find_champs_by_stable_id(published_type_de_champ.parent.stable_id).each do |champ_repetition| # If this type de champ is a child, we create a new champ for each row of the parent
parent_stable_id = published_coordinate.parent.stable_id
champs_repetition = Champ
.includes(:champs, :type_de_champ)
.where(dossier: self, type_de_champ: { stable_id: parent_stable_id })
champs_repetition.each do |champ_repetition|
champ_repetition.champs.map(&:row).uniq.each do |row| champ_repetition.champs.map(&:row).uniq.each do |row|
champ = published_type_de_champ.champ.build(row: row) create_champ(published_coordinate, champ_repetition, row: row)
champ_repetition.champs << champ
end end
end end
else else
champ = published_type_de_champ.build_champ(revision: procedure.published_revision) create_champ(published_coordinate, self)
self.champs << champ
end end
end end
def update_champ_for_revision(champ) def create_champ(published_coordinate, parent, row: nil)
published_type_de_champ = find_type_de_champ_by_stable_id(champ.stable_id, published: true) params = { revision: published_coordinate.revision }
return if !published_type_de_champ params[:row] = row if row
champ = published_coordinate
.type_de_champ
.build_champ(params)
parent.champs << champ
end
update = {} def update_champs_for_revision(champs_with_changes)
champs_with_changes.each do |champ, type_de_champ, changes|
update = {}
yield update changes.each do |change|
yield champ, change, update
end
if champ.type_de_champ != published_type_de_champ # update type de champ to reflect new revision
update[:type_de_champ_id] = published_type_de_champ.id if champ.type_de_champ != type_de_champ
end update[:type_de_champ_id] = type_de_champ.id
end
if update.present? if update.present?
champ.update_columns(update) champ.update_columns(update)
end
end end
end end
def delete_champs_for_revision(published_type_de_champ) def delete_champs_for_revision(type_de_champ)
Champ.where(id: find_champs_by_stable_id(published_type_de_champ.stable_id).map(&:id)) Champ
.where(dossier: self, type_de_champ: type_de_champ)
.destroy_all .destroy_all
end end
def flattened_all_types_de_champ(published: false)
revision = published ? procedure.published_revision : self.revision
types_de_champ = revision.types_de_champ_public + revision.types_de_champ_private
(types_de_champ + types_de_champ.filter(&:repetition?).flat_map(&:types_de_champ))
.index_by(&:stable_id)
end
def flattened_all_champs
all_champs = (champs + champs_private)
all_champs + all_champs.filter(&:repetition?).flat_map(&:champs)
end
def find_type_de_champ_by_stable_id(stable_id, published: false)
flattened_all_types_de_champ(published: published)[stable_id]
end
def find_champs_by_stable_id(stable_id)
flattened_all_champs.filter do |champ|
champ.stable_id == stable_id
end
end
end end

View file

@ -18,7 +18,7 @@ class ProcedureRevision < ApplicationRecord
has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id
has_many :revision_types_de_champ, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision has_many :revision_types_de_champ, -> { order(:position, :id) }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
has_many :revision_types_de_champ_public, -> { root.public_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision has_many :revision_types_de_champ_public, -> { root.public_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
has_many :revision_types_de_champ_private, -> { root.private_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision has_many :revision_types_de_champ_private, -> { root.private_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
has_many :types_de_champ, through: :revision_types_de_champ, source: :type_de_champ has_many :types_de_champ, through: :revision_types_de_champ, source: :type_de_champ
@ -125,14 +125,13 @@ class ProcedureRevision < ApplicationRecord
end end
def different_from?(revision) def different_from?(revision)
types_de_champ != revision.types_de_champ || revision_types_de_champ != revision.revision_types_de_champ ||
attestation_template != revision.attestation_template attestation_template != revision.attestation_template
end end
def compare(revision) def compare(revision)
changes = [] changes = []
changes += compare_types_de_champ(types_de_champ_public, revision.types_de_champ_public) changes += compare_revision_types_de_champ(revision_types_de_champ, revision.revision_types_de_champ)
changes += compare_types_de_champ(types_de_champ_private, revision.types_de_champ_private)
changes += compare_attestation_template(attestation_template, revision.attestation_template) changes += compare_attestation_template(attestation_template, revision.attestation_template)
changes changes
end end
@ -266,43 +265,43 @@ class ProcedureRevision < ApplicationRecord
changes changes
end end
def compare_types_de_champ(from_tdc, to_tdc) def compare_revision_types_de_champ(from_coordinates, to_coordinates)
if from_tdc == to_tdc if from_coordinates == to_coordinates
[] []
else else
from_h = from_tdc.index_by(&:stable_id) from_h = from_coordinates.index_by(&:stable_id)
to_h = to_tdc.index_by(&:stable_id) to_h = to_coordinates.index_by(&:stable_id)
from_sids = from_h.keys from_sids = from_h.keys
to_sids = to_h.keys to_sids = to_h.keys
removed = (from_sids - to_sids).map do |sid| removed = (from_sids - to_sids).map do |sid|
{ model: :type_de_champ, op: :remove, label: from_h[sid].libelle, private: from_h[sid].private?, position: from_sids.index(sid), stable_id: sid } { model: :type_de_champ, op: :remove, label: from_h[sid].libelle, private: from_h[sid].private?, _position: from_sids.index(sid), stable_id: sid }
end end
added = (to_sids - from_sids).map do |sid| added = (to_sids - from_sids).map do |sid|
{ model: :type_de_champ, op: :add, label: to_h[sid].libelle, private: to_h[sid].private?, position: to_sids.index(sid), stable_id: sid } { model: :type_de_champ, op: :add, label: to_h[sid].libelle, private: to_h[sid].private?, _position: to_sids.index(sid), stable_id: sid }
end end
kept = from_sids.intersection(to_sids) kept = from_sids.intersection(to_sids)
moved = kept moved = kept
.map { |sid| [sid, from_sids.index(sid), to_sids.index(sid)] } .map { |sid| [sid, from_h[sid], to_h[sid]] }
.filter { |_, from_index, to_index| from_index != to_index } .filter { |_, from, to| from.position != to.position }
.map do |sid, from_index, to_index| .map do |sid, from, to|
{ model: :type_de_champ, op: :move, label: from_h[sid].libelle, private: from_h[sid].private?, from: from_index, to: to_index, position: to_index, stable_id: sid } { model: :type_de_champ, op: :move, label: from.libelle, private: from.private?, from: from.position, to: to.position, _position: to_sids.index(sid), stable_id: sid }
end end
changed = kept changed = kept
.map { |sid| [sid, from_h[sid], to_h[sid]] } .map { |sid| [sid, from_h[sid], to_h[sid]] }
.flat_map do |sid, from, to| .flat_map do |sid, from, to|
compare_type_de_champ(from, to) compare_type_de_champ(from.type_de_champ, to.type_de_champ)
.each { |h| h[:position] = to_sids.index(sid) } .each { |h| h[:_position] = to_sids.index(sid) }
end end
(removed + added + moved + changed) (removed + added + moved + changed)
.sort_by { |h| h[:position] } .sort_by { |h| h[:_position] }
.each { |h| h.delete(:position) } .each { |h| h.delete(:_position) }
end end
end end
@ -431,10 +430,6 @@ class ProcedureRevision < ApplicationRecord
stable_id: from_type_de_champ.stable_id stable_id: from_type_de_champ.stable_id
} }
end end
elsif to_type_de_champ.repetition?
if from_type_de_champ.types_de_champ != to_type_de_champ.types_de_champ
changes += compare_types_de_champ(from_type_de_champ.types_de_champ, to_type_de_champ.types_de_champ)
end
end end
changes changes
end end

View file

@ -300,7 +300,7 @@ describe ProcedureRevision do
let(:first_tdc) { draft.types_de_champ_public.first } let(:first_tdc) { draft.types_de_champ_public.first }
let(:new_draft) { procedure.create_new_revision } let(:new_draft) { procedure.create_new_revision }
subject { procedure.active_revision.compare(new_draft) } subject { procedure.active_revision.compare(new_draft.reload) }
context 'when a type de champ is added' do context 'when a type de champ is added' do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }