Maintenance::CleanDuplicateCompanyGuidTreenodesTask

Source code
# frozen_string_literal: true

class Maintenance::CleanDuplicateCompanyGuidTreenodesTask < MaintenanceTasks::Task
  def collection
    TreeNode.where.not(company_guid: [nil, ''])
            .group(:company_guid)
            .having('count(*) > 1')
            .pluck(:company_guid)
  end

  def process(company_guid)
    with_first_keeper(company_guid) { |keeper| process_with_keeper(company_guid, keeper) }
  end

  def merge_duplicate_into_keeper(keeper:, duplicate:)
    duplicate.mbo_profiles.reload.find_each do |mbo|
      transfer_mbo_to_keeper(mbo: mbo, keeper: keeper)
    end
    reassign_legacy_custom_fields_to_keeper(duplicate, keeper)
  end

  private

  def with_first_keeper(company_guid)
    keeper = TreeNode.where(company_guid: company_guid).order(:created_at).first
    yield keeper if keeper
  end

  def process_with_keeper(company_guid, keeper)
    duplicate_ids = Maintenance::MergeDuplicateTreenodesService.call(
      company_guid: company_guid, keeper: keeper, merger: self
    )
    return if duplicate_ids.empty?

    ensure_single_primary_mbo_profile(keeper)
    log_merge(company_guid: company_guid, keeper: keeper, duplicate_ids: duplicate_ids)
    delete_duplicate_ids(duplicate_ids)
  end

  # :reek:FeatureEnvy
  def transfer_mbo_to_keeper(mbo:, keeper:)
    existing = keeper.mbo_profiles.find_by(
      mbo_id: mbo.mbo_id,
      country_id: mbo.country_id,
      mid_back_office: mbo.mid_back_office
    )
    if existing
      merge_custom_fields_into(source_mbo: mbo, target_mbo: existing)
      mbo.destroy!
    else
      # Move the join record to point to the keeper instead of the duplicate
      join = mbo.tree_node_to_mbo_profiles.where.not(tree_node_id: keeper.id).first
      if join
        join.update!(tree_node_id: keeper.id, primary: false)
      else
        keeper.tree_node_to_mbo_profiles.create!(mbo_profile: mbo, primary: false)
      end
    end
  end

  def merge_custom_fields_into(source_mbo:, target_mbo:)
    source_mbo.custom_fields.find_each do |custom_field|
      CustomFieldToMboProfile.find_or_create_by!(
        custom_field: custom_field,
        mbo_profile: target_mbo
      )
    end
  end

  def reassign_legacy_custom_fields_to_keeper(duplicate, keeper)
    return unless CustomField.connection.column_exists?(:custom_fields, :tree_node_id)

    duplicate.legacy_custom_fields.find_each do |custom_field|
      custom_field.assign_legacy_tree_node_id(keeper)
    end
  end

  def ensure_single_primary_mbo_profile(keeper)
    primary_joins = keeper.tree_node_to_mbo_profiles.reload.where(primary: true).order(:created_at)
    all_joins = keeper.tree_node_to_mbo_profiles.order(:created_at)

    if primary_joins.empty? && all_joins.any?
      all_joins.first.update!(primary: true)
    elsif primary_joins.many?
      primary_joins.offset(1).find_each { |join| join.update!(primary: false) }
    end
  end

  def delete_duplicate_ids(duplicate_ids)
    TreeNode.where(id: duplicate_ids).find_each do |tree_node|
      tree_node.reload
      tree_node.destroy!
    end
  end

  def log_merge(company_guid:, keeper:, duplicate_ids:)
    merged_ids = TreeNode.where(id: duplicate_ids).pluck(:id, :name).map { |id, name| "#{id}(#{name})" }.join(', ')
    Rails.logger.info(
      "[CleanDuplicateCompanyGuidTreenodesTask] company_guid=#{company_guid} " \
      "kept treenode=#{keeper.id}(#{keeper.name}) deleted_duplicates=[#{merged_ids}]"
    )
  end
end

Previous Runs

Succeeded
#89

Processed 224 out of 224 items (100%).

Ran for 2 minutes, finished .

Metadata:
user_id
-1