app/workers/output_dataset_worker.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
91%
# frozen_string_literal: true

class OutputDatasetWorker
  include Sidekiq::Worker

  sidekiq_options retry: 0

  ADD_MISSING_DE = "add_missing_de"
  ADD_MISSING_OU = "add_missing_ou"
  REMOVE_EXTRA_DE = "remove_extra_de"
  REMOVE_EXTRA_OU = "remove_extra_ou"

  MODES = [ADD_MISSING_DE, ADD_MISSING_OU, REMOVE_EXTRA_DE, REMOVE_EXTRA_OU].freeze

  MODE_OPTIONS = [
    ["Add missing data elements", ADD_MISSING_DE],
    ["Remove extra data elements", REMOVE_EXTRA_DE],
    ["Add missing orgunits", ADD_MISSING_OU],
    ["Remove extra org units", REMOVE_EXTRA_OU]
  ].freeze

  def self.trigger_sync_for_project(project)
    project.payment_rules.each do |payment_rule|
      payment_rule.datasets.each do |dataset|
        OutputDatasetWorker.perform_async(
          project.id,
          payment_rule.code,
          dataset.frequency, "modes" => [ADD_MISSING_DE]
        )
      end
    end
  end

  def perform(project_id, payment_rule_code, frequency, options)
    modes = options.fetch("modes")
    @legacy_project = Project.find(project_id)

    Datasets::BuildDatasets.new(legacy_project).call

    @payment_rule = legacy_project.payment_rule_for_code(payment_rule_code)
    @dataset_code = "hesabu-outputs-" + payment_rule.id.to_s + "-" + frequency
    @dataset_name = "ORBF - " + @payment_rule.rule.name + " (#{@payment_rule.id}) - #{frequency.capitalize}"

    dataset = payment_rule.dataset(frequency)
    begin
      dhis2_dataset = load_dhis2_dataset(payment_rule, frequency, dataset)
      if modes.include?("create") || dhis2_dataset.nil?
        if dhis2_dataset
          Rails.logger.warn("not creating the dataset seem "\
            "to already exist : #{dataset.external_reference}")
          return
        end
        dhis2_dataset = create_dataset(dataset, frequency)
      end

      update(dhis2_dataset, dataset, modes)
      dataset.update(last_error: nil)
    rescue StandardError => e
      Rails.logger.error([e.class.name, e.message, e.backtrace.join("\n")].join("\n"))
      dataset.update(last_error: e.class.name + " " + e.message)
    ensure
      dataset.update(last_synched_at: DateTime.now)
    end
  end

  private

  attr_reader :legacy_project, :payment_rule

  def load_dhis2_dataset(payment_rule, frequency, dataset)
    return nil if dataset.external_reference.blank?

    dhis2_connection.data_sets.find_by(code: @dataset_code)
  rescue RestClient::NotFound
    nil
  end

  def dhis2_connection
    @dhis2_connection ||= payment_rule.project.dhis2_connection
  end

  def create_dataset(dataset, frequency)
    dataset_hash = Datasets::ToDhis2Datasets.new(dataset).call
    dataset_hash[:code] = @dataset_code
    dataset_hash[:name] = @dataset_name
    dataset_hash[:short_name] = @dataset_code
    dhis2_status = dhis2_connection.data_sets.create(dataset_hash)
    Rails.logger.info dhis2_status.to_json
    dhis2_dataset = dhis2_connection.data_sets.find_by(code: dataset_hash[:code])
    dataset.external_reference = dhis2_dataset.id
    dataset.save!
    update(dhis2_dataset, dataset, MODES)
    dhis2_dataset
  end

  def update(dhis2_dataset, dataset, modes)
    diffs = Datasets::CalculateDesyncDatasets.new(legacy_project)
                                             .diff_actual_theorical(dhis2_dataset, dataset)
    add_orgunit_ids(dhis2_dataset, diffs.ou_diff.removed) if modes.include?(ADD_MISSING_OU)
    remove_orgunit_ids(dhis2_dataset, diffs.ou_diff.added) if modes.include?(REMOVE_EXTRA_OU)

    add_de_ids(dhis2_dataset, diffs.de_diff.removed) if modes.include?(ADD_MISSING_DE)
    remove_de_ids(dhis2_dataset, diffs.de_diff.added) if modes.include?(REMOVE_EXTRA_DE)

    dhis2_dataset.update
    dhis2_dataset
  end

  def add_orgunit_ids(dhis2_dataset, orgunit_ids)
    orgunits = dhis2_dataset.organisation_units
    orgunit_ids.each do |id_to_add|
      next if orgunits.include?("id" => id_to_add)

      orgunits.push("id" => id_to_add)
    end
  end

  def add_de_ids(dhis2_dataset, de_ids)
    if dhis2_dataset.data_set_elements
      add_de_ids_newer(dhis2_dataset, de_ids)
    else
      add_de_ids_legacy(dhis2_dataset, de_ids)
    end
  end

  def add_de_ids_newer(dhis2_dataset, de_ids)
    data_set_elements = dhis2_dataset.data_set_elements
    de_ids.each do |id_to_add|
      next if data_set_elements.any? { |de| de["data_element"] == { "id" => id_to_add } }

      dse = { "data_element" => { "id" => id_to_add } }
      dse["data_set"] = { "id" => dhis2_dataset.id } if dhis2_dataset.id
      puts "adding #{dse}"
      data_set_elements.push(dse)
    end
  end

  def add_de_ids_legacy(dhis2_dataset, de_ids)
    data_elements = dhis2_dataset.data_elements
    de_ids.each do |id_to_add|
      next if data_elements.any? { |de| de["id"] == id_to_add }

      data_elements.push("id" => id_to_add)
    end
  end

  def remove_orgunit_ids(dhis2_dataset, orgunit_ids)
    dhis2_dataset.organisation_units.delete_if { |ou| orgunit_ids.include?(ou["id"]) }
  end

  def remove_de_ids(dhis2_dataset, de_ids)
    if dhis2_dataset.data_set_elements
      dhis2_dataset.data_set_elements.delete_if { |de| de_ids.include?(de["data_element"]["id"]) }
    else
      dhis2_dataset.data_elements.delete_if { |de| de_ids.include?(de["id"]) }
    end
  end
end