kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
12 KiB
317 lines
12 KiB
#-- encoding: UTF-8
|
|
#-- copyright
|
|
# OpenProject is a project management system.
|
|
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
|
|
#
|
|
# 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-2017 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 doc/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
module Project::Copy
|
|
def self.included(base)
|
|
base.send :include, CopyModel
|
|
base.send :include, self::CopyMethods
|
|
|
|
# things that are explicitly excluded when copying a project
|
|
base.not_to_copy ['id', 'created_on', 'updated_on', 'name', 'identifier', 'status', 'lft', 'rgt']
|
|
|
|
# specify the order of associations to copy
|
|
base.copy_precedence ['members', 'versions', 'categories', 'work_packages', 'wiki']
|
|
end
|
|
|
|
module CopyMethods
|
|
def copy_attributes(project)
|
|
super
|
|
with_model(project) do |project|
|
|
self.enabled_module_names = project.enabled_module_names
|
|
self.types = project.types
|
|
self.custom_values = project.custom_values.map(&:clone)
|
|
self.work_package_custom_fields = project.work_package_custom_fields
|
|
end
|
|
return self
|
|
rescue ActiveRecord::RecordNotFound
|
|
return nil
|
|
end
|
|
|
|
def copy_associations(from_model, options = {})
|
|
super(from_model, options) if save
|
|
end
|
|
|
|
private
|
|
|
|
# Copies wiki from +project+
|
|
def copy_wiki(project)
|
|
# Check that the source project has a wiki first
|
|
unless project.wiki.nil?
|
|
self.wiki = build_wiki(project.wiki.attributes.dup.except('id', 'project_id'))
|
|
copy_wiki_pages(project)
|
|
copy_wiki_menu_items(project)
|
|
end
|
|
end
|
|
|
|
# Copies wiki pages from +project+, requires a wiki to be already set
|
|
def copy_wiki_pages(project)
|
|
wiki_pages_map = {}
|
|
project.wiki.pages.each do |page|
|
|
# Skip pages without content
|
|
next if page.content.nil?
|
|
new_wiki_content = WikiContent.new(page.content.attributes.dup.except('id', 'page_id', 'updated_at'))
|
|
new_wiki_page = WikiPage.new(page.attributes.dup.except('id', 'wiki_id', 'created_on', 'parent_id'))
|
|
new_wiki_page.content = new_wiki_content
|
|
|
|
wiki.pages << new_wiki_page
|
|
wiki_pages_map[page.id] = new_wiki_page
|
|
end
|
|
wiki.save
|
|
# Reproduce page hierarchy
|
|
project.wiki.pages.each do |page|
|
|
if page.parent_id && wiki_pages_map[page.id]
|
|
wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
|
|
wiki_pages_map[page.id].save
|
|
end
|
|
end
|
|
end
|
|
|
|
# Copies wiki_menu_items from +project+, requires a wiki to be already set
|
|
def copy_wiki_menu_items(project)
|
|
wiki_menu_items_map = {}
|
|
project.wiki.wiki_menu_items.each do |item|
|
|
new_item = MenuItems::WikiMenuItem.new
|
|
new_item.attributes = item.attributes.dup.except('id', 'wiki_id', 'parent_id')
|
|
new_item.wiki = wiki
|
|
(wiki_menu_items_map[item.id] = new_item.reload) if new_item.save
|
|
end
|
|
project.wiki.wiki_menu_items.each do |item|
|
|
if item.parent_id && (copy = wiki_menu_items_map[item.id])
|
|
copy.parent = wiki_menu_items_map[item.parent_id]
|
|
copy.save
|
|
end
|
|
end
|
|
end
|
|
|
|
# Copies versions from +project+
|
|
def copy_versions(project)
|
|
project.versions.each do |version|
|
|
new_version = Version.new
|
|
new_version.attributes = version.attributes.dup.except('id', 'project_id', 'created_on', 'updated_at')
|
|
versions << new_version
|
|
end
|
|
end
|
|
|
|
# Copies issue categories from +project+
|
|
def copy_categories(project)
|
|
project.categories.each do |category|
|
|
new_category = Category.new
|
|
new_category.send(:assign_attributes, category.attributes.dup.except('id', 'project_id'))
|
|
categories << new_category
|
|
end
|
|
end
|
|
|
|
# Copies issues from +project+
|
|
def copy_work_packages(project)
|
|
# Stores the source issue id as a key and the copied issues as the
|
|
# value. Used to map the two together for issue relations.
|
|
work_packages_map = {}
|
|
|
|
# Get issues sorted by root_id, lft so that parent issues
|
|
# get copied before their children
|
|
project.work_packages.reorder('root_id, lft').each do |issue|
|
|
new_issue = WorkPackage.new
|
|
new_issue.copy_from(issue)
|
|
new_issue.project = self
|
|
# Reassign fixed_versions by name, since names are unique per
|
|
# project and the versions for self are not yet saved
|
|
if issue.fixed_version
|
|
new_version = versions.detect { |v| v.name == issue.fixed_version.name }
|
|
if new_version
|
|
new_issue.skip_fixed_version_validation = true
|
|
new_issue.fixed_version = new_version
|
|
end
|
|
end
|
|
# Reassign the category by name, since names are unique per
|
|
# project and the categories for self are not yet saved
|
|
if issue.category
|
|
new_issue.category = categories.detect { |c| c.name == issue.category.name }
|
|
end
|
|
# Parent issue
|
|
if issue.parent_id
|
|
if (copied_parent = work_packages_map[issue.parent_id]) && copied_parent.reload
|
|
new_issue.parent_id = copied_parent.id
|
|
end
|
|
end
|
|
work_packages << new_issue
|
|
|
|
if new_issue.new_record?
|
|
logger.info "Project#copy_work_packages: work unit ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
|
|
else
|
|
work_packages_map[issue.id] = new_issue unless new_issue.new_record?
|
|
end
|
|
end
|
|
|
|
# reload all work_packages in our map, they might be modified by movement in their tree
|
|
work_packages_map.each do |_, v| v.reload end
|
|
|
|
# Relations after in case issues related each other
|
|
project.work_packages.each do |issue|
|
|
new_issue = work_packages_map[issue.id]
|
|
unless new_issue
|
|
# Issue was not copied
|
|
next
|
|
end
|
|
|
|
# Relations
|
|
issue.relations_from.each do |source_relation|
|
|
new_relation = Relation.new
|
|
new_relation.attributes = source_relation.attributes.dup.except('id', 'from_id', 'to_id')
|
|
new_relation.to = work_packages_map[source_relation.to_id]
|
|
if new_relation.to.nil? && Setting.cross_project_work_package_relations?
|
|
new_relation.to = source_relation.to
|
|
end
|
|
new_relation.from = new_issue
|
|
new_relation.save
|
|
end
|
|
|
|
issue.relations_to.each do |source_relation|
|
|
new_relation = Relation.new
|
|
new_relation.attributes = source_relation.attributes.dup.except('id', 'from_id', 'to_id')
|
|
new_relation.from = work_packages_map[source_relation.from_id]
|
|
if new_relation.from.nil? && Setting.cross_project_work_package_relations?
|
|
new_relation.from = source_relation.from
|
|
end
|
|
new_relation.to = new_issue
|
|
new_relation.save
|
|
end
|
|
end
|
|
end
|
|
|
|
# Copies members from +project+
|
|
def copy_members(project)
|
|
# Copy users first, then groups to handle members with inherited and given roles
|
|
members_to_copy = []
|
|
members_to_copy += project.memberships.select { |m| m.principal.is_a?(User) }
|
|
members_to_copy += project.memberships.reject { |m| m.principal.is_a?(User) }
|
|
members_to_copy.each do |member|
|
|
new_member = Member.new
|
|
new_member.send(:assign_attributes, member.attributes.dup.except('id', 'project_id', 'created_on'))
|
|
# only copy non inherited roles
|
|
# inherited roles will be added when copying the group membership
|
|
role_ids = member.member_roles.reject(&:inherited?).map(&:role_id)
|
|
next if role_ids.empty?
|
|
new_member.role_ids = role_ids
|
|
new_member.project = self
|
|
memberships << new_member
|
|
end
|
|
|
|
# Update the omitted attributes for the copied memberships
|
|
memberships.each do |new_member|
|
|
member = project.memberships.find_by(user_id: new_member.user_id)
|
|
Redmine::Hook.call_hook(:copy_project_add_member, new_member: new_member, member: member)
|
|
new_member.save
|
|
end
|
|
end
|
|
|
|
# Copies queries from +project+
|
|
def copy_queries(project)
|
|
project.queries.each do |query|
|
|
new_query = ::Query.new name: '_'
|
|
new_query.attributes = query.attributes.dup.except('id', 'project_id', 'sort_criteria')
|
|
new_query.set_context
|
|
new_query.sort_criteria = query.sort_criteria if query.sort_criteria
|
|
new_query.project = self
|
|
queries << new_query
|
|
end
|
|
end
|
|
|
|
# Copies boards from +project+
|
|
def copy_boards(project)
|
|
project.boards.each do |board|
|
|
new_board = Board.new
|
|
new_board.attributes = board.attributes.dup.except('id',
|
|
'project_id',
|
|
'topics_count',
|
|
'messages_count',
|
|
'last_message_id')
|
|
copy_topics(board, new_board)
|
|
|
|
new_board.project = self
|
|
boards << new_board
|
|
end
|
|
end
|
|
|
|
# Copies project associations from +project+
|
|
def copy_project_associations(project)
|
|
%i(project_a project_b).each do |association_type|
|
|
project.send(:"#{association_type}_associations").each do |association|
|
|
new_association = ProjectAssociation.new
|
|
new_association.attributes = association.attributes.dup.except('id', "#{association_type}_id")
|
|
new_association.send(:"#{association_type}=", self)
|
|
new_association.save
|
|
end
|
|
end
|
|
end
|
|
|
|
# copies timeline associations from +project+
|
|
def copy_timelines(project)
|
|
project.timelines.each do |timeline|
|
|
copied_timeline = Timeline.new
|
|
copied_timeline.attributes = timeline.attributes.dup.except('id', 'project_id', 'options')
|
|
copied_timeline.options = timeline.options if timeline.options.present?
|
|
copied_timeline.project = self
|
|
copied_timeline.save
|
|
end
|
|
end
|
|
|
|
# copies reporting associations from +project+
|
|
def copy_reportings(project)
|
|
project.reportings_via_source.each do |reporting|
|
|
copied_reporting = Reporting.new
|
|
copied_reporting.attributes = reporting.attributes.dup.except('id', 'project_id')
|
|
copied_reporting.project = self
|
|
copied_reporting.save
|
|
end
|
|
project.reportings_via_target.each do |reporting|
|
|
copied_reporting = Reporting.new
|
|
copied_reporting.attributes = reporting.attributes.dup.except('id', 'reporting_to_project')
|
|
copied_reporting.reporting_to_project = self
|
|
copied_reporting.save
|
|
end
|
|
end
|
|
|
|
def copy_topics(board, new_board)
|
|
topics = board.topics.where('parent_id is NULL')
|
|
topics.each do |topic|
|
|
new_topic = Message.new
|
|
new_topic.attributes = topic.attributes.dup.except('id',
|
|
'board_id',
|
|
'author_id',
|
|
'replies_count',
|
|
'last_reply_id',
|
|
'created_on',
|
|
'updated_on')
|
|
new_topic.board = new_board
|
|
new_topic.author_id = topic.author_id
|
|
new_board.topics << new_topic
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|