app/services/map_project_to_orbf_project.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
95%
# frozen_string_literal: true

require "orbf/rules_engine"

class MapProjectToOrbfProject
  def initialize(project, indicators, category_combos, data_elements, engine_version = nil)
    @project = project
    @dhis2_indicators_by_id = indicators.index_by(&:id)
    @category_combos_by_id = category_combos ? category_combos.index_by(&:id) : {}
    @data_elements_by_id = data_elements ? data_elements.index_by(&:id) : {}
    @engine_version = engine_version || project.engine_version
  end

  def map
    Orbf::RulesEngine::Project.new(
      packages:                              map_packages(project.packages),
      payment_rules:                         map_payment_rules,
      dhis2_params:                          project.dhis_configuration,
      engine_version:                        @engine_version,
      default_category_combo_ext_id:         project.default_coc_reference,
      default_attribute_option_combo_ext_id: project.default_aoc_reference,
      calendar:                              project.calendar,
      contract_settings:                     project.contract_settings,
      read_through_deg:                      project.read_through_deg
    )
  end

  private

  attr_reader :project, :packages, :dhis2_indicators_by_id, :data_elements_by_id, :category_combos_by_id

  PACKAGE_KINDS = {
    "multi-groupset" => "subcontract"
  }.freeze

  def map_packages(packages)
    packages.map do |package|
      map_package(package)
    end
  end

  def map_package(package)
    @cache_package ||= {}
    from_cache = @cache_package[package]
    return from_cache if from_cache

    category_combo = category_combos_by_id[package.loop_over_combo_ext_id]

    @cache_package[package] = Orbf::RulesEngine::Package.new(
      code:                          package.code,
      kind:                          PACKAGE_KINDS[package.kind] || package.kind,
      frequency:                     package.frequency,
      main_org_unit_group_ext_ids:   package.main_entity_groups
                                        .map(&:organisation_unit_group_ext_ref).compact,

      target_org_unit_group_ext_ids: package.target_entity_groups
                                      .map(&:organisation_unit_group_ext_ref).compact,
      groupset_ext_id:               package.ogs_reference,
      matching_groupset_ext_ids:     package.groupsets_ext_refs,
      dataset_ext_ids:               package.package_states.map(&:ds_external_reference).compact,
      deg_ext_id:                    package.deg_external_reference,
      activities:                    map_activities(package.activities, package.states),
      rules:                         map_rules(package.rules),
      include_main_orgunit:          package.include_main_orgunit,
      loop_over_combo:               category_combo
    )
  end

  def map_activities(package_activities, package_states)
    package_activities.map do |activity|
      Orbf::RulesEngine::Activity.with(
        name:            activity.name,
        activity_code:   activity.code,
        activity_states: map_activity_states(activity.activity_states, package_states)
      )
    end
  end

  ACTIVITY_STATE_KIND = {
    "formula" => "constant"
  }.freeze

  def map_activity_states(activity_states, package_states)
    activity_states.select { |activity_state| package_states.include?(activity_state.state) }
                   .map do |activity_state|
      kind = ACTIVITY_STATE_KIND[activity_state.kind] || activity_state.kind
      formula = activity_state.formula ||
                dhis2_indicators_by_id[activity_state.external_reference]&.numerator
      ext_id = activity_state.external_reference
      if activity_state.kind_data_element_coc?
        # fake date_element_coc as indicator
        kind = "indicator"
        formula = '#{' + activity_state.external_reference + "}"
        ext_id = "inlined-" + ext_id
      end
      category_combo_ext_id = data_elements_by_id[activity_state.external_reference] ? data_elements_by_id[activity_state.external_reference].category_combo["id"] : nil

      Orbf::RulesEngine::ActivityState.with(
        state:                 activity_state.state.code,
        name:                  activity_state.name,
        formula:               formula,
        kind:                  kind,
        ext_id:                ext_id,
        origin:                activity_state.origin,
        category_combo_ext_id: category_combo_ext_id
      )
    end
  end

  RULE_KINDS = { "multi-entities" => "entities_aggregation" }.freeze

  def map_rules(rules)
    rules.map do |rule|
      map_rule(rule)
    end
  end

  def map_rule(rule)
    Orbf::RulesEngine::Rule.new(
      kind:            RULE_KINDS[rule.kind] || rule.kind,
      formulas:        map_formulas(rule.formulas),
      decision_tables: map_decision_tables(rule.decision_tables)
    )
  end

  def map_formulas(formulas)
    formulas.map do |formula|
      Orbf::RulesEngine::Formula.new(
        formula.code,
        formula.expression,
        formula.description,
        map_formula_mappings(formula)
      )
    end
  end

  def map_formula_mappings(formula)
    formula_mappings = {}
    formula_mappings[:frequency] = formula.frequency if formula.frequency
    formula_mappings[:exportable_formula_code] = formula.exportable_formula_code if formula.exportable_formula_code
    if formula.rule.activity_related_kind? && formula.formula_mappings.any?
      formula_mappings[:activity_mappings] = formula.formula_mappings
                                                    .each_with_object({}) do |mapping, hash|
        hash[mapping.activity.code] = mapping.external_reference
      end
    elsif !formula.rule.activity_kind? && formula.formula_mappings.size == 1
      formula_mappings[:single_mapping] = formula.formula_mappings.first.external_reference
    end
    formula_mappings
  end

  def map_decision_tables(decision_tables)
    decision_tables.map do |decision_table|
      Orbf::RulesEngine::DecisionTable.new(
        decision_table.content,
        start_period: decision_table.start_period,
        end_period:   decision_table.end_period
      )
    end
  end

  def map_payment_rules
    project.payment_rules.map do |payment_rule|
      Orbf::RulesEngine::PaymentRule.new(
        code:      payment_rule.code,
        frequency: payment_rule.frequency,
        packages:  map_packages(payment_rule.packages),
        rule:      map_rule(payment_rule.rule)
      )
    end
  end
end