##// END OF EJS Templates
Makes Version#<=> consistent with SQL sort....
Jean-Philippe Lang -
r9969:2275f4613875
parent child
Show More
@@ -1,278 +1,278
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 after_update :update_issues_from_sharing_change
20 after_update :update_issues_from_sharing_change
21 belongs_to :project
21 belongs_to :project
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
23 acts_as_customizable
23 acts_as_customizable
24 acts_as_attachable :view_permission => :view_files,
24 acts_as_attachable :view_permission => :view_files,
25 :delete_permission => :manage_files
25 :delete_permission => :manage_files
26
26
27 VERSION_STATUSES = %w(open locked closed)
27 VERSION_STATUSES = %w(open locked closed)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
29
29
30 validates_presence_of :name
30 validates_presence_of :name
31 validates_uniqueness_of :name, :scope => [:project_id]
31 validates_uniqueness_of :name, :scope => [:project_id]
32 validates_length_of :name, :maximum => 60
32 validates_length_of :name, :maximum => 60
33 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
33 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
34 validates_inclusion_of :status, :in => VERSION_STATUSES
34 validates_inclusion_of :status, :in => VERSION_STATUSES
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
36
36
37 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
37 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
38 scope :open, :conditions => {:status => 'open'}
38 scope :open, :conditions => {:status => 'open'}
39 scope :visible, lambda {|*args| { :include => :project,
39 scope :visible, lambda {|*args| { :include => :project,
40 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
40 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
41
41
42 safe_attributes 'name',
42 safe_attributes 'name',
43 'description',
43 'description',
44 'effective_date',
44 'effective_date',
45 'due_date',
45 'due_date',
46 'wiki_page_title',
46 'wiki_page_title',
47 'status',
47 'status',
48 'sharing',
48 'sharing',
49 'custom_field_values'
49 'custom_field_values'
50
50
51 # Returns true if +user+ or current user is allowed to view the version
51 # Returns true if +user+ or current user is allowed to view the version
52 def visible?(user=User.current)
52 def visible?(user=User.current)
53 user.allowed_to?(:view_issues, self.project)
53 user.allowed_to?(:view_issues, self.project)
54 end
54 end
55
55
56 # Version files have same visibility as project files
56 # Version files have same visibility as project files
57 def attachments_visible?(*args)
57 def attachments_visible?(*args)
58 project.present? && project.attachments_visible?(*args)
58 project.present? && project.attachments_visible?(*args)
59 end
59 end
60
60
61 def start_date
61 def start_date
62 @start_date ||= fixed_issues.minimum('start_date')
62 @start_date ||= fixed_issues.minimum('start_date')
63 end
63 end
64
64
65 def due_date
65 def due_date
66 effective_date
66 effective_date
67 end
67 end
68
68
69 def due_date=(arg)
69 def due_date=(arg)
70 self.effective_date=(arg)
70 self.effective_date=(arg)
71 end
71 end
72
72
73 # Returns the total estimated time for this version
73 # Returns the total estimated time for this version
74 # (sum of leaves estimated_hours)
74 # (sum of leaves estimated_hours)
75 def estimated_hours
75 def estimated_hours
76 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
76 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
77 end
77 end
78
78
79 # Returns the total reported time for this version
79 # Returns the total reported time for this version
80 def spent_hours
80 def spent_hours
81 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
81 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
82 end
82 end
83
83
84 def closed?
84 def closed?
85 status == 'closed'
85 status == 'closed'
86 end
86 end
87
87
88 def open?
88 def open?
89 status == 'open'
89 status == 'open'
90 end
90 end
91
91
92 # Returns true if the version is completed: due date reached and no open issues
92 # Returns true if the version is completed: due date reached and no open issues
93 def completed?
93 def completed?
94 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
94 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
95 end
95 end
96
96
97 def behind_schedule?
97 def behind_schedule?
98 if completed_pourcent == 100
98 if completed_pourcent == 100
99 return false
99 return false
100 elsif due_date && start_date
100 elsif due_date && start_date
101 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
101 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
102 return done_date <= Date.today
102 return done_date <= Date.today
103 else
103 else
104 false # No issues so it's not late
104 false # No issues so it's not late
105 end
105 end
106 end
106 end
107
107
108 # Returns the completion percentage of this version based on the amount of open/closed issues
108 # Returns the completion percentage of this version based on the amount of open/closed issues
109 # and the time spent on the open issues.
109 # and the time spent on the open issues.
110 def completed_pourcent
110 def completed_pourcent
111 if issues_count == 0
111 if issues_count == 0
112 0
112 0
113 elsif open_issues_count == 0
113 elsif open_issues_count == 0
114 100
114 100
115 else
115 else
116 issues_progress(false) + issues_progress(true)
116 issues_progress(false) + issues_progress(true)
117 end
117 end
118 end
118 end
119
119
120 # Returns the percentage of issues that have been marked as 'closed'.
120 # Returns the percentage of issues that have been marked as 'closed'.
121 def closed_pourcent
121 def closed_pourcent
122 if issues_count == 0
122 if issues_count == 0
123 0
123 0
124 else
124 else
125 issues_progress(false)
125 issues_progress(false)
126 end
126 end
127 end
127 end
128
128
129 # Returns true if the version is overdue: due date reached and some open issues
129 # Returns true if the version is overdue: due date reached and some open issues
130 def overdue?
130 def overdue?
131 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
131 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
132 end
132 end
133
133
134 # Returns assigned issues count
134 # Returns assigned issues count
135 def issues_count
135 def issues_count
136 load_issue_counts
136 load_issue_counts
137 @issue_count
137 @issue_count
138 end
138 end
139
139
140 # Returns the total amount of open issues for this version.
140 # Returns the total amount of open issues for this version.
141 def open_issues_count
141 def open_issues_count
142 load_issue_counts
142 load_issue_counts
143 @open_issues_count
143 @open_issues_count
144 end
144 end
145
145
146 # Returns the total amount of closed issues for this version.
146 # Returns the total amount of closed issues for this version.
147 def closed_issues_count
147 def closed_issues_count
148 load_issue_counts
148 load_issue_counts
149 @closed_issues_count
149 @closed_issues_count
150 end
150 end
151
151
152 def wiki_page
152 def wiki_page
153 if project.wiki && !wiki_page_title.blank?
153 if project.wiki && !wiki_page_title.blank?
154 @wiki_page ||= project.wiki.find_page(wiki_page_title)
154 @wiki_page ||= project.wiki.find_page(wiki_page_title)
155 end
155 end
156 @wiki_page
156 @wiki_page
157 end
157 end
158
158
159 def to_s; name end
159 def to_s; name end
160
160
161 def to_s_with_project
161 def to_s_with_project
162 "#{project} - #{name}"
162 "#{project} - #{name}"
163 end
163 end
164
164
165 # Versions are sorted by effective_date and "Project Name - Version name"
165 # Versions are sorted by effective_date and "Project Name - Version name"
166 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
166 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
167 def <=>(version)
167 def <=>(version)
168 if self.effective_date
168 if self.effective_date
169 if version.effective_date
169 if version.effective_date
170 if self.effective_date == version.effective_date
170 if self.effective_date == version.effective_date
171 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
171 name == version.name ? id <=> version.id : name <=> version.name
172 else
172 else
173 self.effective_date <=> version.effective_date
173 self.effective_date <=> version.effective_date
174 end
174 end
175 else
175 else
176 -1
176 -1
177 end
177 end
178 else
178 else
179 if version.effective_date
179 if version.effective_date
180 1
180 1
181 else
181 else
182 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
182 name == version.name ? id <=> version.id : name <=> version.name
183 end
183 end
184 end
184 end
185 end
185 end
186
186
187 def self.fields_for_order_statement(table=nil)
187 def self.fields_for_order_statement(table=nil)
188 table ||= table_name
188 table ||= table_name
189 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
189 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
190 end
190 end
191
191
192 scope :sorted, order(fields_for_order_statement)
192 scope :sorted, order(fields_for_order_statement)
193
193
194 # Returns the sharings that +user+ can set the version to
194 # Returns the sharings that +user+ can set the version to
195 def allowed_sharings(user = User.current)
195 def allowed_sharings(user = User.current)
196 VERSION_SHARINGS.select do |s|
196 VERSION_SHARINGS.select do |s|
197 if sharing == s
197 if sharing == s
198 true
198 true
199 else
199 else
200 case s
200 case s
201 when 'system'
201 when 'system'
202 # Only admin users can set a systemwide sharing
202 # Only admin users can set a systemwide sharing
203 user.admin?
203 user.admin?
204 when 'hierarchy', 'tree'
204 when 'hierarchy', 'tree'
205 # Only users allowed to manage versions of the root project can
205 # Only users allowed to manage versions of the root project can
206 # set sharing to hierarchy or tree
206 # set sharing to hierarchy or tree
207 project.nil? || user.allowed_to?(:manage_versions, project.root)
207 project.nil? || user.allowed_to?(:manage_versions, project.root)
208 else
208 else
209 true
209 true
210 end
210 end
211 end
211 end
212 end
212 end
213 end
213 end
214
214
215 private
215 private
216
216
217 def load_issue_counts
217 def load_issue_counts
218 unless @issue_count
218 unless @issue_count
219 @open_issues_count = 0
219 @open_issues_count = 0
220 @closed_issues_count = 0
220 @closed_issues_count = 0
221 fixed_issues.count(:all, :group => :status).each do |status, count|
221 fixed_issues.count(:all, :group => :status).each do |status, count|
222 if status.is_closed?
222 if status.is_closed?
223 @closed_issues_count += count
223 @closed_issues_count += count
224 else
224 else
225 @open_issues_count += count
225 @open_issues_count += count
226 end
226 end
227 end
227 end
228 @issue_count = @open_issues_count + @closed_issues_count
228 @issue_count = @open_issues_count + @closed_issues_count
229 end
229 end
230 end
230 end
231
231
232 # Update the issue's fixed versions. Used if a version's sharing changes.
232 # Update the issue's fixed versions. Used if a version's sharing changes.
233 def update_issues_from_sharing_change
233 def update_issues_from_sharing_change
234 if sharing_changed?
234 if sharing_changed?
235 if VERSION_SHARINGS.index(sharing_was).nil? ||
235 if VERSION_SHARINGS.index(sharing_was).nil? ||
236 VERSION_SHARINGS.index(sharing).nil? ||
236 VERSION_SHARINGS.index(sharing).nil? ||
237 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
237 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
238 Issue.update_versions_from_sharing_change self
238 Issue.update_versions_from_sharing_change self
239 end
239 end
240 end
240 end
241 end
241 end
242
242
243 # Returns the average estimated time of assigned issues
243 # Returns the average estimated time of assigned issues
244 # or 1 if no issue has an estimated time
244 # or 1 if no issue has an estimated time
245 # Used to weigth unestimated issues in progress calculation
245 # Used to weigth unestimated issues in progress calculation
246 def estimated_average
246 def estimated_average
247 if @estimated_average.nil?
247 if @estimated_average.nil?
248 average = fixed_issues.average(:estimated_hours).to_f
248 average = fixed_issues.average(:estimated_hours).to_f
249 if average == 0
249 if average == 0
250 average = 1
250 average = 1
251 end
251 end
252 @estimated_average = average
252 @estimated_average = average
253 end
253 end
254 @estimated_average
254 @estimated_average
255 end
255 end
256
256
257 # Returns the total progress of open or closed issues. The returned percentage takes into account
257 # Returns the total progress of open or closed issues. The returned percentage takes into account
258 # the amount of estimated time set for this version.
258 # the amount of estimated time set for this version.
259 #
259 #
260 # Examples:
260 # Examples:
261 # issues_progress(true) => returns the progress percentage for open issues.
261 # issues_progress(true) => returns the progress percentage for open issues.
262 # issues_progress(false) => returns the progress percentage for closed issues.
262 # issues_progress(false) => returns the progress percentage for closed issues.
263 def issues_progress(open)
263 def issues_progress(open)
264 @issues_progress ||= {}
264 @issues_progress ||= {}
265 @issues_progress[open] ||= begin
265 @issues_progress[open] ||= begin
266 progress = 0
266 progress = 0
267 if issues_count > 0
267 if issues_count > 0
268 ratio = open ? 'done_ratio' : 100
268 ratio = open ? 'done_ratio' : 100
269
269
270 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
270 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
271 :joins => :status,
271 :joins => :status,
272 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
272 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
273 progress = done / (estimated_average * issues_count)
273 progress = done / (estimated_average * issues_count)
274 end
274 end
275 progress
275 progress
276 end
276 end
277 end
277 end
278 end
278 end
@@ -1,240 +1,241
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class VersionTest < ActiveSupport::TestCase
20 class VersionTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions
21 fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions
22
22
23 def setup
23 def setup
24 end
24 end
25
25
26 def test_create
26 def test_create
27 v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '2011-03-25')
27 v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '2011-03-25')
28 assert v.save
28 assert v.save
29 assert_equal 'open', v.status
29 assert_equal 'open', v.status
30 assert_equal 'none', v.sharing
30 assert_equal 'none', v.sharing
31 end
31 end
32
32
33 def test_invalid_effective_date_validation
33 def test_invalid_effective_date_validation
34 v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '99999-01-01')
34 v = Version.new(:project => Project.find(1), :name => '1.1', :effective_date => '99999-01-01')
35 assert !v.save
35 assert !v.save
36 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
36 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
37 v.errors[:effective_date]
37 v.errors[:effective_date]
38 end
38 end
39
39
40 def test_progress_should_be_0_with_no_assigned_issues
40 def test_progress_should_be_0_with_no_assigned_issues
41 project = Project.find(1)
41 project = Project.find(1)
42 v = Version.create!(:project => project, :name => 'Progress')
42 v = Version.create!(:project => project, :name => 'Progress')
43 assert_equal 0, v.completed_pourcent
43 assert_equal 0, v.completed_pourcent
44 assert_equal 0, v.closed_pourcent
44 assert_equal 0, v.closed_pourcent
45 end
45 end
46
46
47 def test_progress_should_be_0_with_unbegun_assigned_issues
47 def test_progress_should_be_0_with_unbegun_assigned_issues
48 project = Project.find(1)
48 project = Project.find(1)
49 v = Version.create!(:project => project, :name => 'Progress')
49 v = Version.create!(:project => project, :name => 'Progress')
50 add_issue(v)
50 add_issue(v)
51 add_issue(v, :done_ratio => 0)
51 add_issue(v, :done_ratio => 0)
52 assert_progress_equal 0, v.completed_pourcent
52 assert_progress_equal 0, v.completed_pourcent
53 assert_progress_equal 0, v.closed_pourcent
53 assert_progress_equal 0, v.closed_pourcent
54 end
54 end
55
55
56 def test_progress_should_be_100_with_closed_assigned_issues
56 def test_progress_should_be_100_with_closed_assigned_issues
57 project = Project.find(1)
57 project = Project.find(1)
58 status = IssueStatus.find(:first, :conditions => {:is_closed => true})
58 status = IssueStatus.find(:first, :conditions => {:is_closed => true})
59 v = Version.create!(:project => project, :name => 'Progress')
59 v = Version.create!(:project => project, :name => 'Progress')
60 add_issue(v, :status => status)
60 add_issue(v, :status => status)
61 add_issue(v, :status => status, :done_ratio => 20)
61 add_issue(v, :status => status, :done_ratio => 20)
62 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
62 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
63 add_issue(v, :status => status, :estimated_hours => 15)
63 add_issue(v, :status => status, :estimated_hours => 15)
64 assert_progress_equal 100.0, v.completed_pourcent
64 assert_progress_equal 100.0, v.completed_pourcent
65 assert_progress_equal 100.0, v.closed_pourcent
65 assert_progress_equal 100.0, v.closed_pourcent
66 end
66 end
67
67
68 def test_progress_should_consider_done_ratio_of_open_assigned_issues
68 def test_progress_should_consider_done_ratio_of_open_assigned_issues
69 project = Project.find(1)
69 project = Project.find(1)
70 v = Version.create!(:project => project, :name => 'Progress')
70 v = Version.create!(:project => project, :name => 'Progress')
71 add_issue(v)
71 add_issue(v)
72 add_issue(v, :done_ratio => 20)
72 add_issue(v, :done_ratio => 20)
73 add_issue(v, :done_ratio => 70)
73 add_issue(v, :done_ratio => 70)
74 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
74 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
75 assert_progress_equal 0, v.closed_pourcent
75 assert_progress_equal 0, v.closed_pourcent
76 end
76 end
77
77
78 def test_progress_should_consider_closed_issues_as_completed
78 def test_progress_should_consider_closed_issues_as_completed
79 project = Project.find(1)
79 project = Project.find(1)
80 v = Version.create!(:project => project, :name => 'Progress')
80 v = Version.create!(:project => project, :name => 'Progress')
81 add_issue(v)
81 add_issue(v)
82 add_issue(v, :done_ratio => 20)
82 add_issue(v, :done_ratio => 20)
83 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
83 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
84 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent
84 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent
85 assert_progress_equal (100.0)/3, v.closed_pourcent
85 assert_progress_equal (100.0)/3, v.closed_pourcent
86 end
86 end
87
87
88 def test_progress_should_consider_estimated_hours_to_weigth_issues
88 def test_progress_should_consider_estimated_hours_to_weigth_issues
89 project = Project.find(1)
89 project = Project.find(1)
90 v = Version.create!(:project => project, :name => 'Progress')
90 v = Version.create!(:project => project, :name => 'Progress')
91 add_issue(v, :estimated_hours => 10)
91 add_issue(v, :estimated_hours => 10)
92 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
92 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
93 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
93 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
94 add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
94 add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
95 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_pourcent
95 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_pourcent
96 assert_progress_equal 25.0/95.0*100, v.closed_pourcent
96 assert_progress_equal 25.0/95.0*100, v.closed_pourcent
97 end
97 end
98
98
99 def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
99 def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
100 project = Project.find(1)
100 project = Project.find(1)
101 v = Version.create!(:project => project, :name => 'Progress')
101 v = Version.create!(:project => project, :name => 'Progress')
102 add_issue(v, :done_ratio => 20)
102 add_issue(v, :done_ratio => 20)
103 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
103 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
104 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
104 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
105 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
105 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
106 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
106 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
107 assert_progress_equal 25.0/100.0*100, v.closed_pourcent
107 assert_progress_equal 25.0/100.0*100, v.closed_pourcent
108 end
108 end
109
109
110 def test_should_sort_scheduled_then_unscheduled_versions
110 def test_should_sort_scheduled_then_unscheduled_versions
111 Version.delete_all
111 Version.delete_all
112 v4 = Version.create!(:project_id => 1, :name => 'v4')
112 v4 = Version.create!(:project_id => 1, :name => 'v4')
113 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
113 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
114 v2 = Version.create!(:project_id => 1, :name => 'v1')
114 v2 = Version.create!(:project_id => 1, :name => 'v1')
115 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
115 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
116 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
116 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
117
117
118 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
118 assert_equal [v5, v3, v1, v2, v4], Version.sorted.all
119 assert_equal [v5, v3, v1, v2, v4], Version.sorted.all
119 end
120 end
120
121
121 context "#behind_schedule?" do
122 context "#behind_schedule?" do
122 setup do
123 setup do
123 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
124 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
124 @project = Project.create!(:name => 'test0', :identifier => 'test0')
125 @project = Project.create!(:name => 'test0', :identifier => 'test0')
125 @project.trackers << Tracker.create!(:name => 'track')
126 @project.trackers << Tracker.create!(:name => 'track')
126
127
127 @version = Version.create!(:project => @project, :effective_date => nil, :name => 'version')
128 @version = Version.create!(:project => @project, :effective_date => nil, :name => 'version')
128 end
129 end
129
130
130 should "be false if there are no issues assigned" do
131 should "be false if there are no issues assigned" do
131 @version.update_attribute(:effective_date, Date.yesterday)
132 @version.update_attribute(:effective_date, Date.yesterday)
132 assert_equal false, @version.behind_schedule?
133 assert_equal false, @version.behind_schedule?
133 end
134 end
134
135
135 should "be false if there is no effective_date" do
136 should "be false if there is no effective_date" do
136 assert_equal false, @version.behind_schedule?
137 assert_equal false, @version.behind_schedule?
137 end
138 end
138
139
139 should "be false if all of the issues are ahead of schedule" do
140 should "be false if all of the issues are ahead of schedule" do
140 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
141 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
141 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
142 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
142 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
143 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
143 assert_equal 60, @version.completed_pourcent
144 assert_equal 60, @version.completed_pourcent
144 assert_equal false, @version.behind_schedule?
145 assert_equal false, @version.behind_schedule?
145 end
146 end
146
147
147 should "be true if any of the issues are behind schedule" do
148 should "be true if any of the issues are behind schedule" do
148 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
149 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
149 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
150 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
150 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
151 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
151 assert_equal 40, @version.completed_pourcent
152 assert_equal 40, @version.completed_pourcent
152 assert_equal true, @version.behind_schedule?
153 assert_equal true, @version.behind_schedule?
153 end
154 end
154
155
155 should "be false if all of the issues are complete" do
156 should "be false if all of the issues are complete" do
156 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
157 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
157 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
158 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
158 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
159 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
159 assert_equal 100, @version.completed_pourcent
160 assert_equal 100, @version.completed_pourcent
160 assert_equal false, @version.behind_schedule?
161 assert_equal false, @version.behind_schedule?
161 end
162 end
162 end
163 end
163
164
164 context "#estimated_hours" do
165 context "#estimated_hours" do
165 setup do
166 setup do
166 @version = Version.create!(:project_id => 1, :name => '#estimated_hours')
167 @version = Version.create!(:project_id => 1, :name => '#estimated_hours')
167 end
168 end
168
169
169 should "return 0 with no assigned issues" do
170 should "return 0 with no assigned issues" do
170 assert_equal 0, @version.estimated_hours
171 assert_equal 0, @version.estimated_hours
171 end
172 end
172
173
173 should "return 0 with no estimated hours" do
174 should "return 0 with no estimated hours" do
174 add_issue(@version)
175 add_issue(@version)
175 assert_equal 0, @version.estimated_hours
176 assert_equal 0, @version.estimated_hours
176 end
177 end
177
178
178 should "return the sum of estimated hours" do
179 should "return the sum of estimated hours" do
179 add_issue(@version, :estimated_hours => 2.5)
180 add_issue(@version, :estimated_hours => 2.5)
180 add_issue(@version, :estimated_hours => 5)
181 add_issue(@version, :estimated_hours => 5)
181 assert_equal 7.5, @version.estimated_hours
182 assert_equal 7.5, @version.estimated_hours
182 end
183 end
183
184
184 should "return the sum of leaves estimated hours" do
185 should "return the sum of leaves estimated hours" do
185 parent = add_issue(@version)
186 parent = add_issue(@version)
186 add_issue(@version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
187 add_issue(@version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
187 add_issue(@version, :estimated_hours => 5, :parent_issue_id => parent.id)
188 add_issue(@version, :estimated_hours => 5, :parent_issue_id => parent.id)
188 assert_equal 7.5, @version.estimated_hours
189 assert_equal 7.5, @version.estimated_hours
189 end
190 end
190 end
191 end
191
192
192 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
193 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
193 User.current = User.find(1) # Need the admin's permissions
194 User.current = User.find(1) # Need the admin's permissions
194
195
195 @version = Version.find(7)
196 @version = Version.find(7)
196 # Separate hierarchy
197 # Separate hierarchy
197 project_1_issue = Issue.find(1)
198 project_1_issue = Issue.find(1)
198 project_1_issue.fixed_version = @version
199 project_1_issue.fixed_version = @version
199 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
200 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
200
201
201 project_5_issue = Issue.find(6)
202 project_5_issue = Issue.find(6)
202 project_5_issue.fixed_version = @version
203 project_5_issue.fixed_version = @version
203 assert project_5_issue.save
204 assert project_5_issue.save
204
205
205 # Project
206 # Project
206 project_2_issue = Issue.find(4)
207 project_2_issue = Issue.find(4)
207 project_2_issue.fixed_version = @version
208 project_2_issue.fixed_version = @version
208 assert project_2_issue.save
209 assert project_2_issue.save
209
210
210 # Update the sharing
211 # Update the sharing
211 @version.sharing = 'none'
212 @version.sharing = 'none'
212 assert @version.save
213 assert @version.save
213
214
214 # Project 1 now out of the shared scope
215 # Project 1 now out of the shared scope
215 project_1_issue.reload
216 project_1_issue.reload
216 assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
217 assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
217
218
218 # Project 5 now out of the shared scope
219 # Project 5 now out of the shared scope
219 project_5_issue.reload
220 project_5_issue.reload
220 assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
221 assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
221
222
222 # Project 2 issue remains
223 # Project 2 issue remains
223 project_2_issue.reload
224 project_2_issue.reload
224 assert_equal @version, project_2_issue.fixed_version
225 assert_equal @version, project_2_issue.fixed_version
225 end
226 end
226
227
227 private
228 private
228
229
229 def add_issue(version, attributes={})
230 def add_issue(version, attributes={})
230 Issue.create!({:project => version.project,
231 Issue.create!({:project => version.project,
231 :fixed_version => version,
232 :fixed_version => version,
232 :subject => 'Test',
233 :subject => 'Test',
233 :author => User.find(:first),
234 :author => User.find(:first),
234 :tracker => version.project.trackers.find(:first)}.merge(attributes))
235 :tracker => version.project.trackers.find(:first)}.merge(attributes))
235 end
236 end
236
237
237 def assert_progress_equal(expected_float, actual_float, message="")
238 def assert_progress_equal(expected_float, actual_float, message="")
238 assert_in_delta(expected_float, actual_float, 0.000001, message="")
239 assert_in_delta(expected_float, actual_float, 0.000001, message="")
239 end
240 end
240 end
241 end
General Comments 0
You need to be logged in to leave comments. Login now