module DossierRebaseConcern extend ActiveSupport::Concern def rebase!(force: false) ProcedureRevisionPreloader.new([procedure.published_revision, revision].compact).all return if procedure.published_revision.blank? if force || can_rebase? transaction do rebase end end end def rebase_later DossierRebaseJob.perform_later(self) end def can_rebase? procedure.published_revision.present? && revision != procedure.published_revision && (brouillon? || accepted_en_construction_changes? || accepted_en_instruction_changes?) end def pending_changes procedure.published_revision.present? ? revision.compare(procedure.published_revision) : [] end def can_rebase_mandatory_change?(stable_id) !champs.filter { _1.stable_id == stable_id }.any?(&:blank?) end def can_rebase_drop_down_options_change?(stable_id, options) !champs.filter { _1.stable_id == stable_id }.any? { _1.in?(options) } end private def accepted_en_construction_changes? en_construction? && pending_changes.all? { _1.can_rebase?(self) } end def accepted_en_instruction_changes? en_instruction? && pending_changes.all? { _1.can_rebase?(self) } end def rebase # revision we are rebasing to target_revision = procedure.published_revision # index published types de champ coordinates by stable_id target_coordinates_by_stable_id = target_revision .revision_types_de_champ .includes(:type_de_champ, :parent) .index_by(&:stable_id) changes_by_op = pending_changes .group_by(&:op) .tap { _1.default = [] } champs_by_stable_id = champs .includes(:type_de_champ) .group_by(&:stable_id) .transform_values { Champ.where(id: _1) } .tap { _1.default = Champ.none } # add champ changes_by_op[:add] .map { target_coordinates_by_stable_id[_1.stable_id] } # add parent champs first so we can then add children .sort_by { _1.child? ? 1 : 0 } .each { add_new_champs_for_revision(_1) } # remove champ children_champ, root_champ = changes_by_op[:remove].partition(&:child?) children_champ.each { champs_by_stable_id[_1.stable_id].destroy_all } root_champ.each { champs_by_stable_id[_1.stable_id].destroy_all } # update champ changes_by_op[:update].each { apply(_1, champs_by_stable_id[_1.stable_id]) } # due to repetition tdc clone on update or erase # we must reassign tdc to the latest version champs_by_stable_id.each do |stable_id, champs| if target_coordinates_by_stable_id[stable_id].present? && champs.present? champs.update_all(type_de_champ_id: target_coordinates_by_stable_id[stable_id].type_de_champ_id) end end # update dossier revision update_column(:revision_id, target_revision.id) end def apply(change, champs) case change.attribute when :type_champ champs.each { purge_piece_justificative_file(_1) } GeoArea.where(champ: champs).destroy_all Etablissement.where(champ: champs).destroy_all champs.update_all(type: "Champs::#{change.to.classify}Champ", value: nil, value_json: nil, external_id: nil, data: nil, rebased_at: Time.zone.now) when :drop_down_options # we are removing options, we need to remove the value if it contains one of the removed options removed_options = change.from - change.to if removed_options.present? && champs.any? { _1.in?(removed_options) } champs.filter { _1.in?(removed_options) }.each do _1.remove_option(removed_options) _1.update_column(:rebased_at, Time.zone.now) end end 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) champs.filter { _1.cadastres.present? }.each do _1.cadastres.each(&:destroy) _1.update_column(:rebased_at, Time.zone.now) end end else champs.update_all(rebased_at: Time.zone.now) end end def add_new_champs_for_revision(target_coordinate) if target_coordinate.child? # If this type de champ is a child, we create a new champ for each row of the parent parent_stable_id = target_coordinate.parent.stable_id champs.filter { _1.stable_id == parent_stable_id }.each do |champ_repetition| if champ_repetition.champs.present? champ_repetition.champs.map(&:row_id).uniq.each do |row_id| champs << create_champ(target_coordinate, champ_repetition, row_id:) end elsif champ_repetition.mandatory? champs << create_champ(target_coordinate, champ_repetition, row_id: ULID.generate) end end else create_champ(target_coordinate, self) end end def create_champ(target_coordinate, parent, row_id: nil) target_coordinate .type_de_champ .build_champ(rebased_at: Time.zone.now, row_id:) .tap { parent.champs << _1 } end def purge_piece_justificative_file(champ) ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all end end