opf/openproject

View on GitHub
app/seeders/demo_data/work_package_seeder.rb

Summary

Maintainability
A
0 mins
Test Coverage
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
module DemoData
  class WorkPackageSeeder < Seeder
    include CreateAttachments
    include References

    self.needs = [
      BasicData::StatusSeeder,
      BasicData::TypeSeeder,
      BasicData::PrioritySeeder,
      AdminUserSeeder
    ]

    attr_reader :project, :statuses, :repository, :types
    alias_method :project_data, :seed_data

    def initialize(project, project_data)
      super(project_data)
      @project = project
      @project_data = project_data
      @statuses = Status.all
      @repository = Repository.first
      @types = project.types.all.reject(&:is_milestone?)
      @relations_to_create = []
    end

    def seed_data!
      print_status "    ↳ Creating work_packages" do
        seed_demo_work_packages
        set_work_package_relations
      end
    end

    private

    RelationData = Data.define(:from, :to_reference, :type)

    attr_reader :relations_to_create

    def seed_demo_work_packages
      project_data.each("work_packages") do |attributes|
        create_or_update_work_package(attributes)
      end
    end

    # Decides what to do with work package seed data.
    # The default here is to create the work package.
    # Modules may patch this method.
    def create_or_update_work_package(attributes)
      create_work_package(attributes)
    end

    def create_work_package(attributes)
      wp_attr = base_work_package_attributes attributes

      set_version! wp_attr, attributes
      set_time_tracking_attributes! wp_attr, attributes
      set_backlogs_attributes! wp_attr, attributes

      work_package = WorkPackage.create! wp_attr

      create_children! work_package, attributes
      create_attachments! work_package, attributes
      update_description! work_package
      add_relations_to_create work_package, attributes
      memorize_work_package work_package, attributes

      work_package
    end

    def base_work_package_attributes(attributes)
      {
        project:,
        author: admin_user,
        assigned_to: find_principal(attributes["assigned_to"]),
        subject: attributes["subject"],
        description: attributes["description"],
        status: find_status(attributes),
        type: find_type(attributes),
        priority: IssuePriority.default,
        parent: find_work_package(attributes["parent"])
      }
    end

    def create_children!(work_package, attributes)
      Array(attributes["children"]).each do |child_attributes|
        child = create_work_package child_attributes

        child.parent = work_package
        child.save!
      end
    end

    def update_description!(work_package)
      description = work_package.description
      description = link_attachments description, work_package.attachments
      description = with_references description

      work_package.update(description:)
    end

    def add_relations_to_create(work_package, attributes)
      Array(attributes["relations"]).each do |relation|
        relation_data = RelationData.new(from: work_package, to_reference: relation["to"], type: relation["type"])
        relations_to_create.push(relation_data)
      end
    end

    def memorize_work_package(work_package, attributes)
      project_data.store_reference(attributes["reference"], work_package)
    end

    def find_work_package(reference)
      seed_data.find_reference(reference)
    end

    def find_principal(reference)
      seed_data.find_reference(reference) || admin_user
    end

    def find_status(attributes)
      seed_data.find_reference(attributes["status"].to_sym)
    end

    def find_type(attributes)
      seed_data.find_reference(attributes["type"].to_sym)
    end

    def set_version!(wp_attr, attributes)
      version = seed_data.find_reference(attributes["version"])
      if version
        wp_attr[:version] = version
      end
    end

    def set_time_tracking_attributes!(wp_attr, attributes)
      wp_attr.merge!(time_tracking_attributes(attributes))
    end

    def time_tracking_attributes(attributes)
      TimeTrackingAttributes.for(attributes)
    end

    def set_backlogs_attributes!(wp_attr, attributes)
      wp_attr[:position] = attributes["position"].presence&.to_i
      wp_attr[:story_points] = attributes["story_points"].presence&.to_i
    end

    def set_work_package_relations
      while relations_to_create.any?
        relation_data = relations_to_create.pop
        from_work_package = relation_data.from
        to_work_package = find_work_package(relation_data.to_reference)
        create_relation(
          to: to_work_package,
          from: from_work_package,
          type: relation_data.type
        )
      end
    end

    def create_relation(to:, from:, type:)
      Relation.create!(from:, to:, relation_type: type)
    end

    class TimeTrackingAttributes
      def self.for(attributes)
        new(attributes).work_package_attributes
      end

      def work_package_attributes
        {
          start_date:,
          due_date:,
          duration:,
          ignore_non_working_days:,
          estimated_hours:
        }
      end

      private

      attr_reader :attributes

      def initialize(attributes)
        @attributes = attributes
      end

      def start_date
        days_ahead = attributes["start"] || 0
        Time.zone.today.monday + days_ahead.days
      end

      def due_date
        all_days.due_date(start_date, attributes["duration"])
      end

      def duration
        days.duration(start_date, due_date)
      end

      def ignore_non_working_days
        [start_date, due_date]
          .compact
          .any? { |date| working_days.non_working?(date) }
      end

      def estimated_hours
        attributes["estimated_hours"]&.to_i
      end

      def all_days
        @all_days ||= WorkPackages::Shared::AllDays.new
      end

      def working_days
        @working_days ||= WorkPackages::Shared::WorkingDays.new
      end

      def days
        ignore_non_working_days ? all_days : working_days
      end
    end
  end
end