Newer
Older
dmpopidor / app / services / template / upgrade_customization_service.rb
# frozen_string_literal: true

class Template

  # Service object to upgrade a customization Template with new changes from the original
  # funder Template. Remember: {target_template} is a customization of funder Template.
  #
  # - Duplicate the init template (Duplication called {#customized_template})
  #
  # - Create a new customisation of funder template (Customization called
  #   {#target_template})
  #
  # - Take each phase on the {#target_template} and iterate to find if there's a
  #   corresponding one in {#customized_template}
  #   - Test for each of  corresponding phase in source
  #   - Copy over each of the modifiable sections from source to the target
  #     - Re-number the sections if necessary to keep the display order (number) the same
  #     - Copy each of the questions and annotations exactly
  #   - For each unmodifiable section, copy over any modifiable questions from target
  #
  # - Copy each of the modifiable sections from the {#customized_template} to the
  #   {#target_template}
  #
  class UpgradeCustomizationService

    # Exception raised when the Template is not a customization.
    class NotACustomizationError < StandardError
    end

    # Exception raised when no published funder Template can be found.
    class NoFunderTemplateError < StandardError
    end


    ##
    # The Template we're upgrading
    #
    # Returns {Template}
    attr_reader :init_template

    # Initialize a new instance and run the script
    #
    # template - The Template we're upgrading
    #
    # Returns {Template}
    def self.call(template)
      new(template).call
    end

    private_class_method :new

    # Initialize a new record
    #
    # template - The Template we're upgrading. Sets the value for {#init_template}
    #
    def initialize(template)
      @init_template = template
    end

    # Run the script
    #
    # Returns {Template}
    def call
      Template.transaction do
        if init_template.customization_of.blank?
          raise NotACustomizationError,
            _("upgrade_customization! requires a customised template")
        end
        if funder_template.nil?
          # rubocop:disable Metrics/LineLength
          raise NoFunderTemplateError,
            _("upgrade cannot be carried out since there is no published template of its current funder")
          # rubocop:enable Metrics/LineLength
        end

        # Merges modifiable sections or questions from source into target_template object
        target_template.phases.map do |funder_phase|
          # Search for the phase in the source template whose versionable_id matches the
          # customization_phase
          #
          # a) If the Org's template ({#customized_template}) has the Phase...
          if customized_phase = find_matching_record_in_collection(
            record: funder_phase,
            collection: customized_template.phases)

          # b) If the Org's template ({#customized_template}) doesn't have this Phase.
          #    This is not a problem, since {#customization_template} should have this
          #    Phase copied over from {#template_phase}.
          else
            next
          end
          copy_modifiable_sections_for_phase(customized_phase, funder_phase)
          sort_sections_within_phase(funder_phase)
        end
        copy_custom_annotations_for_questions
      end
      target_template
    end

    private

    # The funder Template for this {#template}
    #
    # Returns Template
    def funder_template
      @funder_template ||= Template.published(init_template.customization_of).first
    end

    # A copy of the Template we're currently upgrading. Preserves modifiable flags from
    # the self template copied
    #
    #
    # Returns {Template}
    def customized_template
      @customized_template ||= init_template.deep_copy(attributes: {
                                 version: init_template.version + 1,
                                 published: false
                               })
    end

    # Creates a new customisation for the published template whose family_id {#template}
    # is a customization of
    #
    # Returns {Template}
    def target_template
      @target_template ||= funder_template.deep_copy(
        attributes: {
          version: customized_template.version,
          published: customized_template.published,
          family_id: customized_template.family_id,
          customization_of: customized_template.customization_of,
          org: customized_template.org,
          visibility: Template.visibilities[:organisationally_visible],
          is_default: false
        }, modifiable: false, save: true
      )
    end

    # Find an item within collection that has the same versionable_id as record
    #
    # record      - The record we're searching for a match of
    # collection  - The collection of records we're searching in
    #
    # Returns Positionable
    #
    # Returns nil
    def find_matching_record_in_collection(record:, collection:)
      collection.detect { |item| item.versionable_id == record.versionable_id }
    end

    # Attach modifiable sections into the customization phase
    #
    # source_phase - A Phase to copy sections for.
    # target_phase - A Phase to copy Sections to.
    #
    # Returns Array of Sections
    def copy_modifiable_sections_for_phase(source_phase, target_phase)
      target_section_ids = target_phase.sections.pluck(:versionable_id)
      source_phase.sections.select(&:modifiable?).each do |section|
        if section.number.in?(target_phase.sections.pluck(:number))
          section.number = target_phase.sections.maximum(:number) + 1
        end
        target_phase.sections.append(section) or
          raise("Unable to add Section##{section.id} to Phase##{target_phase.id}")
      end
    end

    def copy_custom_annotations_for_questions
      init_template.annotations.where(org: template_org).each do |custom_annotation|
        target_question = target_template.questions.find_by(
          versionable_id: custom_annotation.question.versionable_id
        )
        if target_question.present?
          target_question.annotations << custom_annotation
        end
      end
    end

    def sort_sections_within_phase(phase)
      phase.sections = SectionSorter.new(*phase.sections).sort!
    end

    def template_org
      init_template.org
    end

  end

end