]> source.dussan.org Git - redmine.git/commitdiff
Use estimated hours to weight issues in version completion calculation (#2182).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 1 Feb 2009 18:54:05 +0000 (18:54 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 1 Feb 2009 18:54:05 +0000 (18:54 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2349 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/models/version.rb
test/unit/version_test.rb

index 1fd0d171009e08580d3c8c23fa691f7629e611cd..7f96cea6bb8f701de18e07ea851cfc4363d88c44 100644 (file)
@@ -51,20 +51,20 @@ class Version < ActiveRecord::Base
   end
   
   def completed_pourcent
-    if fixed_issues.count == 0
+    if issues_count == 0
       0
     elsif open_issues_count == 0
       100
     else
-      (closed_issues_count * 100 + Issue.sum('done_ratio', :include => 'status', :conditions => ["fixed_version_id = ? AND is_closed = ?", id, false]).to_f) / fixed_issues.count
+      issues_progress(false) + issues_progress(true)
     end
   end
   
   def closed_pourcent
-    if fixed_issues.count == 0
+    if issues_count == 0
       0
     else
-      closed_issues_count * 100.0 / fixed_issues.count
+      issues_progress(false)
     end
   end
   
@@ -73,6 +73,11 @@ class Version < ActiveRecord::Base
     effective_date && (effective_date < Date.today) && (open_issues_count > 0)
   end
   
+  # Returns assigned issues count
+  def issues_count
+    @issue_count ||= fixed_issues.count
+  end
+  
   def open_issues_count
     @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
   end
@@ -104,4 +109,35 @@ private
   def check_integrity
     raise "Can't delete version" if self.fixed_issues.find(:first)
   end
+  
+  # Returns the average estimated time of assigned issues
+  # or 1 if no issue has an estimated time
+  # Used to weigth unestimated issues in progress calculation
+  def estimated_average
+    if @estimated_average.nil?
+      average = fixed_issues.average(:estimated_hours).to_f
+      if average == 0
+        average = 1
+      end
+      @estimated_average = average
+    end
+    @estimated_average
+  end
+  
+  # Returns the total progress of open or closed issues
+  def issues_progress(open)
+    @issues_progress ||= {}
+    @issues_progress[open] ||= begin
+      progress = 0
+      if issues_count > 0
+        ratio = open ? 'done_ratio' : 100
+        done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
+                                  :include => :status,
+                                  :conditions => ["is_closed = ?", !open]).to_f
+                                  
+        progress = done / (estimated_average * issues_count)
+      end
+      progress
+    end
+  end
 end
index 29bdc0379cf9b3a44b0767769bb8b765fffe6e00..72f049335840d97b3b5526b6ca69126b227a259e 100644 (file)
@@ -18,7 +18,7 @@
 require File.dirname(__FILE__) + '/../test_helper'
 
 class VersionTest < Test::Unit::TestCase
-  fixtures :projects, :issues, :issue_statuses, :versions
+  fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions
 
   def setup
   end
@@ -33,4 +33,88 @@ class VersionTest < Test::Unit::TestCase
     assert !v.save
     assert_equal 'activerecord_error_not_a_date', v.errors.on(:effective_date)
   end
+  
+  def test_progress_should_be_0_with_no_assigned_issues
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    assert_equal 0, v.completed_pourcent
+    assert_equal 0, v.closed_pourcent
+  end
+  
+  def test_progress_should_be_0_with_unbegun_assigned_issues
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v)
+    add_issue(v, :done_ratio => 0)
+    assert_progress_equal 0, v.completed_pourcent
+    assert_progress_equal 0, v.closed_pourcent
+  end
+  
+  def test_progress_should_be_100_with_closed_assigned_issues
+    project = Project.find(1)
+    status = IssueStatus.find(:first, :conditions => {:is_closed => true})
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v, :status => status)
+    add_issue(v, :status => status, :done_ratio => 20)
+    add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
+    add_issue(v, :status => status, :estimated_hours => 15)
+    assert_progress_equal 100.0, v.completed_pourcent
+    assert_progress_equal 100.0, v.closed_pourcent
+  end
+  
+  def test_progress_should_consider_done_ratio_of_open_assigned_issues
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v)
+    add_issue(v, :done_ratio => 20)
+    add_issue(v, :done_ratio => 70)
+    assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
+    assert_progress_equal 0, v.closed_pourcent
+  end
+  
+  def test_progress_should_consider_closed_issues_as_completed
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v)
+    add_issue(v, :done_ratio => 20)
+    add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+    assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent
+    assert_progress_equal (100.0)/3, v.closed_pourcent
+  end
+  
+  def test_progress_should_consider_estimated_hours_to_weigth_issues
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v, :estimated_hours => 10)
+    add_issue(v, :estimated_hours => 20, :done_ratio => 30)
+    add_issue(v, :estimated_hours => 40, :done_ratio => 10)
+    add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+    assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_pourcent
+    assert_progress_equal 25.0/95.0*100, v.closed_pourcent
+  end
+  
+  def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
+    project = Project.find(1)
+    v = Version.create!(:project => project, :name => 'Progress')
+    add_issue(v, :done_ratio => 20)
+    add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+    add_issue(v, :estimated_hours => 10, :done_ratio => 30)
+    add_issue(v, :estimated_hours => 40, :done_ratio => 10)
+    assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
+    assert_progress_equal 25.0/100.0*100, v.closed_pourcent
+  end
+  
+  private
+  
+  def add_issue(version, attributes={})
+    Issue.create!({:project => version.project,
+                   :fixed_version => version,
+                   :subject => 'Test',
+                   :author => User.find(:first),
+                   :tracker => version.project.trackers.find(:first)}.merge(attributes))
+  end
+  
+  def assert_progress_equal(expected_float, actual_float, message="")
+    assert_in_delta(expected_float, actual_float, 0.000001, message="")
+  end
 end