summaryrefslogtreecommitdiffstats
path: root/app/models/issue_relation.rb
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-05-05 13:22:27 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-05-05 13:22:27 +0000
commit92b02014d21f0e60230fc7a5c3c5ad71dac6e472 (patch)
tree30fcce490ea2c6decb6ea34b589f11f04bfd381d /app/models/issue_relation.rb
parent987e843cd195edc402c8b2c7c665534ddb02af45 (diff)
downloadredmine-92b02014d21f0e60230fc7a5c3c5ad71dac6e472.tar.gz
redmine-92b02014d21f0e60230fc7a5c3c5ad71dac6e472.zip
Issue relations first commit (not thoroughly tested). 4 kinds of relation are available:
* relates to: do nothing special. Just to know that the 2 issues are related... * duplicates: will close the related issue with the same status when closing the issue (not implemented yet) * blocks: will require to close the blocking issue before closing the blocked issue (not implemented yet) * precedes (end to start relation): start date of the related issue depends on the due date of the preceding issue (implemented). A delay can be set so that the related issue can only start n days after the end of the preceding issue. When setting dates for an issue, dates of all downstream issues are set according to these relations. To set a relation, the 2 issues have to belong to the same project (may change in the future). So if an issue is moved to another project, all its relations are removed. Circular dependencies are checked when creating a relation. git-svn-id: http://redmine.rubyforge.org/svn/trunk@506 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models/issue_relation.rb')
-rw-r--r--app/models/issue_relation.rb79
1 files changed, 79 insertions, 0 deletions
diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb
new file mode 100644
index 000000000..05ea52057
--- /dev/null
+++ b/app/models/issue_relation.rb
@@ -0,0 +1,79 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# 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.
+
+class IssueRelation < ActiveRecord::Base
+ belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
+ belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
+
+ TYPE_RELATES = "relates"
+ TYPE_DUPLICATES = "duplicates"
+ TYPE_BLOCKS = "blocks"
+ TYPE_PRECEDES = "precedes"
+
+ TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
+ TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicates, :order => 2 },
+ TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
+ TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
+ }.freeze
+
+ validates_presence_of :issue_from, :issue_to, :relation_type
+ validates_inclusion_of :relation_type, :in => TYPES.keys
+ validates_numericality_of :delay, :allow_nil => true
+ validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
+
+ def validate
+ if issue_from && issue_to
+ errors.add :issue_to_id, :activerecord_error_invalid if issue_from_id == issue_to_id
+ errors.add :issue_to_id, :activerecord_error_not_same_project unless issue_from.project_id == issue_to.project_id
+ errors.add_to_base :activerecord_error_circular_dependency if issue_to.all_dependent_issues.include? issue_from
+ end
+ end
+
+ def other_issue(issue)
+ (self.issue_from_id == issue.id) ? issue_to : issue_from
+ end
+
+ def label_for(issue)
+ TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
+ end
+
+ def before_save
+ if TYPE_PRECEDES == relation_type
+ self.delay ||= 0
+ else
+ self.delay = nil
+ end
+ set_issue_to_dates
+ end
+
+ def set_issue_to_dates
+ soonest_start = self.successor_soonest_start
+ if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start)
+ issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration
+ issue_to.save
+ end
+ end
+
+ def successor_soonest_start
+ return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
+ (issue_from.due_date || issue_from.start_date) + 1 + delay
+ end
+
+ def <=>(relation)
+ TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
+ end
+end