##// END OF EJS Templates
Fixed version date validation (#12359)....
Jean-Philippe Lang -
r10583:8b527ce24783
parent child
Show More
@@ -1,278 +1,285
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 validate :validate_version
36
37
37 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
38 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
38 scope :open, :conditions => {:status => 'open'}
39 scope :open, :conditions => {:status => 'open'}
39 scope :visible, lambda {|*args| { :include => :project,
40 scope :visible, lambda {|*args| { :include => :project,
40 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
41 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
41
42
42 safe_attributes 'name',
43 safe_attributes 'name',
43 'description',
44 'description',
44 'effective_date',
45 'effective_date',
45 'due_date',
46 'due_date',
46 'wiki_page_title',
47 'wiki_page_title',
47 'status',
48 'status',
48 'sharing',
49 'sharing',
49 'custom_field_values'
50 'custom_field_values'
50
51
51 # Returns true if +user+ or current user is allowed to view the version
52 # Returns true if +user+ or current user is allowed to view the version
52 def visible?(user=User.current)
53 def visible?(user=User.current)
53 user.allowed_to?(:view_issues, self.project)
54 user.allowed_to?(:view_issues, self.project)
54 end
55 end
55
56
56 # Version files have same visibility as project files
57 # Version files have same visibility as project files
57 def attachments_visible?(*args)
58 def attachments_visible?(*args)
58 project.present? && project.attachments_visible?(*args)
59 project.present? && project.attachments_visible?(*args)
59 end
60 end
60
61
61 def start_date
62 def start_date
62 @start_date ||= fixed_issues.minimum('start_date')
63 @start_date ||= fixed_issues.minimum('start_date')
63 end
64 end
64
65
65 def due_date
66 def due_date
66 effective_date
67 effective_date
67 end
68 end
68
69
69 def due_date=(arg)
70 def due_date=(arg)
70 self.effective_date=(arg)
71 self.effective_date=(arg)
71 end
72 end
72
73
73 # Returns the total estimated time for this version
74 # Returns the total estimated time for this version
74 # (sum of leaves estimated_hours)
75 # (sum of leaves estimated_hours)
75 def estimated_hours
76 def estimated_hours
76 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
77 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
77 end
78 end
78
79
79 # Returns the total reported time for this version
80 # Returns the total reported time for this version
80 def spent_hours
81 def spent_hours
81 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
82 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
82 end
83 end
83
84
84 def closed?
85 def closed?
85 status == 'closed'
86 status == 'closed'
86 end
87 end
87
88
88 def open?
89 def open?
89 status == 'open'
90 status == 'open'
90 end
91 end
91
92
92 # Returns true if the version is completed: due date reached and no open issues
93 # Returns true if the version is completed: due date reached and no open issues
93 def completed?
94 def completed?
94 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
95 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
95 end
96 end
96
97
97 def behind_schedule?
98 def behind_schedule?
98 if completed_pourcent == 100
99 if completed_pourcent == 100
99 return false
100 return false
100 elsif due_date && start_date
101 elsif due_date && start_date
101 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
102 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
102 return done_date <= Date.today
103 return done_date <= Date.today
103 else
104 else
104 false # No issues so it's not late
105 false # No issues so it's not late
105 end
106 end
106 end
107 end
107
108
108 # Returns the completion percentage of this version based on the amount of open/closed issues
109 # Returns the completion percentage of this version based on the amount of open/closed issues
109 # and the time spent on the open issues.
110 # and the time spent on the open issues.
110 def completed_pourcent
111 def completed_pourcent
111 if issues_count == 0
112 if issues_count == 0
112 0
113 0
113 elsif open_issues_count == 0
114 elsif open_issues_count == 0
114 100
115 100
115 else
116 else
116 issues_progress(false) + issues_progress(true)
117 issues_progress(false) + issues_progress(true)
117 end
118 end
118 end
119 end
119
120
120 # Returns the percentage of issues that have been marked as 'closed'.
121 # Returns the percentage of issues that have been marked as 'closed'.
121 def closed_pourcent
122 def closed_pourcent
122 if issues_count == 0
123 if issues_count == 0
123 0
124 0
124 else
125 else
125 issues_progress(false)
126 issues_progress(false)
126 end
127 end
127 end
128 end
128
129
129 # Returns true if the version is overdue: due date reached and some open issues
130 # Returns true if the version is overdue: due date reached and some open issues
130 def overdue?
131 def overdue?
131 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
132 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
132 end
133 end
133
134
134 # Returns assigned issues count
135 # Returns assigned issues count
135 def issues_count
136 def issues_count
136 load_issue_counts
137 load_issue_counts
137 @issue_count
138 @issue_count
138 end
139 end
139
140
140 # Returns the total amount of open issues for this version.
141 # Returns the total amount of open issues for this version.
141 def open_issues_count
142 def open_issues_count
142 load_issue_counts
143 load_issue_counts
143 @open_issues_count
144 @open_issues_count
144 end
145 end
145
146
146 # Returns the total amount of closed issues for this version.
147 # Returns the total amount of closed issues for this version.
147 def closed_issues_count
148 def closed_issues_count
148 load_issue_counts
149 load_issue_counts
149 @closed_issues_count
150 @closed_issues_count
150 end
151 end
151
152
152 def wiki_page
153 def wiki_page
153 if project.wiki && !wiki_page_title.blank?
154 if project.wiki && !wiki_page_title.blank?
154 @wiki_page ||= project.wiki.find_page(wiki_page_title)
155 @wiki_page ||= project.wiki.find_page(wiki_page_title)
155 end
156 end
156 @wiki_page
157 @wiki_page
157 end
158 end
158
159
159 def to_s; name end
160 def to_s; name end
160
161
161 def to_s_with_project
162 def to_s_with_project
162 "#{project} - #{name}"
163 "#{project} - #{name}"
163 end
164 end
164
165
165 # Versions are sorted by effective_date and name
166 # Versions are sorted by effective_date and name
166 # Those with no effective_date are at the end, sorted by name
167 # Those with no effective_date are at the end, sorted by name
167 def <=>(version)
168 def <=>(version)
168 if self.effective_date
169 if self.effective_date
169 if version.effective_date
170 if version.effective_date
170 if self.effective_date == version.effective_date
171 if self.effective_date == version.effective_date
171 name == version.name ? id <=> version.id : name <=> version.name
172 name == version.name ? id <=> version.id : name <=> version.name
172 else
173 else
173 self.effective_date <=> version.effective_date
174 self.effective_date <=> version.effective_date
174 end
175 end
175 else
176 else
176 -1
177 -1
177 end
178 end
178 else
179 else
179 if version.effective_date
180 if version.effective_date
180 1
181 1
181 else
182 else
182 name == version.name ? id <=> version.id : name <=> version.name
183 name == version.name ? id <=> version.id : name <=> version.name
183 end
184 end
184 end
185 end
185 end
186 end
186
187
187 def self.fields_for_order_statement(table=nil)
188 def self.fields_for_order_statement(table=nil)
188 table ||= table_name
189 table ||= table_name
189 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
190 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
190 end
191 end
191
192
192 scope :sorted, order(fields_for_order_statement)
193 scope :sorted, order(fields_for_order_statement)
193
194
194 # Returns the sharings that +user+ can set the version to
195 # Returns the sharings that +user+ can set the version to
195 def allowed_sharings(user = User.current)
196 def allowed_sharings(user = User.current)
196 VERSION_SHARINGS.select do |s|
197 VERSION_SHARINGS.select do |s|
197 if sharing == s
198 if sharing == s
198 true
199 true
199 else
200 else
200 case s
201 case s
201 when 'system'
202 when 'system'
202 # Only admin users can set a systemwide sharing
203 # Only admin users can set a systemwide sharing
203 user.admin?
204 user.admin?
204 when 'hierarchy', 'tree'
205 when 'hierarchy', 'tree'
205 # Only users allowed to manage versions of the root project can
206 # Only users allowed to manage versions of the root project can
206 # set sharing to hierarchy or tree
207 # set sharing to hierarchy or tree
207 project.nil? || user.allowed_to?(:manage_versions, project.root)
208 project.nil? || user.allowed_to?(:manage_versions, project.root)
208 else
209 else
209 true
210 true
210 end
211 end
211 end
212 end
212 end
213 end
213 end
214 end
214
215
215 private
216 private
216
217
217 def load_issue_counts
218 def load_issue_counts
218 unless @issue_count
219 unless @issue_count
219 @open_issues_count = 0
220 @open_issues_count = 0
220 @closed_issues_count = 0
221 @closed_issues_count = 0
221 fixed_issues.count(:all, :group => :status).each do |status, count|
222 fixed_issues.count(:all, :group => :status).each do |status, count|
222 if status.is_closed?
223 if status.is_closed?
223 @closed_issues_count += count
224 @closed_issues_count += count
224 else
225 else
225 @open_issues_count += count
226 @open_issues_count += count
226 end
227 end
227 end
228 end
228 @issue_count = @open_issues_count + @closed_issues_count
229 @issue_count = @open_issues_count + @closed_issues_count
229 end
230 end
230 end
231 end
231
232
232 # Update the issue's fixed versions. Used if a version's sharing changes.
233 # Update the issue's fixed versions. Used if a version's sharing changes.
233 def update_issues_from_sharing_change
234 def update_issues_from_sharing_change
234 if sharing_changed?
235 if sharing_changed?
235 if VERSION_SHARINGS.index(sharing_was).nil? ||
236 if VERSION_SHARINGS.index(sharing_was).nil? ||
236 VERSION_SHARINGS.index(sharing).nil? ||
237 VERSION_SHARINGS.index(sharing).nil? ||
237 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
238 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
238 Issue.update_versions_from_sharing_change self
239 Issue.update_versions_from_sharing_change self
239 end
240 end
240 end
241 end
241 end
242 end
242
243
243 # Returns the average estimated time of assigned issues
244 # Returns the average estimated time of assigned issues
244 # or 1 if no issue has an estimated time
245 # or 1 if no issue has an estimated time
245 # Used to weigth unestimated issues in progress calculation
246 # Used to weigth unestimated issues in progress calculation
246 def estimated_average
247 def estimated_average
247 if @estimated_average.nil?
248 if @estimated_average.nil?
248 average = fixed_issues.average(:estimated_hours).to_f
249 average = fixed_issues.average(:estimated_hours).to_f
249 if average == 0
250 if average == 0
250 average = 1
251 average = 1
251 end
252 end
252 @estimated_average = average
253 @estimated_average = average
253 end
254 end
254 @estimated_average
255 @estimated_average
255 end
256 end
256
257
257 # Returns the total progress of open or closed issues. The returned percentage takes into account
258 # 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.
259 # the amount of estimated time set for this version.
259 #
260 #
260 # Examples:
261 # Examples:
261 # issues_progress(true) => returns the progress percentage for open issues.
262 # issues_progress(true) => returns the progress percentage for open issues.
262 # issues_progress(false) => returns the progress percentage for closed issues.
263 # issues_progress(false) => returns the progress percentage for closed issues.
263 def issues_progress(open)
264 def issues_progress(open)
264 @issues_progress ||= {}
265 @issues_progress ||= {}
265 @issues_progress[open] ||= begin
266 @issues_progress[open] ||= begin
266 progress = 0
267 progress = 0
267 if issues_count > 0
268 if issues_count > 0
268 ratio = open ? 'done_ratio' : 100
269 ratio = open ? 'done_ratio' : 100
269
270
270 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
271 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
271 :joins => :status,
272 :joins => :status,
272 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
273 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
273 progress = done / (estimated_average * issues_count)
274 progress = done / (estimated_average * issues_count)
274 end
275 end
275 progress
276 progress
276 end
277 end
277 end
278 end
279
280 def validate_version
281 if effective_date.nil? && @attributes['effective_date'].present?
282 errors.add :effective_date, :not_a_date
283 end
284 end
278 end
285 end
@@ -1,246 +1,252
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, :projects_trackers
21 fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions, :projects_trackers
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.valid?
36 v.effective_date = '2012-11-33'
37 assert !v.valid?
38 v.effective_date = '2012-31-11'
39 assert !v.valid?
40 v.effective_date = 'ABC'
41 assert !v.valid?
36 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
42 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
37 v.errors[:effective_date]
43 v.errors[:effective_date]
38 end
44 end
39
45
40 def test_progress_should_be_0_with_no_assigned_issues
46 def test_progress_should_be_0_with_no_assigned_issues
41 project = Project.find(1)
47 project = Project.find(1)
42 v = Version.create!(:project => project, :name => 'Progress')
48 v = Version.create!(:project => project, :name => 'Progress')
43 assert_equal 0, v.completed_pourcent
49 assert_equal 0, v.completed_pourcent
44 assert_equal 0, v.closed_pourcent
50 assert_equal 0, v.closed_pourcent
45 end
51 end
46
52
47 def test_progress_should_be_0_with_unbegun_assigned_issues
53 def test_progress_should_be_0_with_unbegun_assigned_issues
48 project = Project.find(1)
54 project = Project.find(1)
49 v = Version.create!(:project => project, :name => 'Progress')
55 v = Version.create!(:project => project, :name => 'Progress')
50 add_issue(v)
56 add_issue(v)
51 add_issue(v, :done_ratio => 0)
57 add_issue(v, :done_ratio => 0)
52 assert_progress_equal 0, v.completed_pourcent
58 assert_progress_equal 0, v.completed_pourcent
53 assert_progress_equal 0, v.closed_pourcent
59 assert_progress_equal 0, v.closed_pourcent
54 end
60 end
55
61
56 def test_progress_should_be_100_with_closed_assigned_issues
62 def test_progress_should_be_100_with_closed_assigned_issues
57 project = Project.find(1)
63 project = Project.find(1)
58 status = IssueStatus.find(:first, :conditions => {:is_closed => true})
64 status = IssueStatus.find(:first, :conditions => {:is_closed => true})
59 v = Version.create!(:project => project, :name => 'Progress')
65 v = Version.create!(:project => project, :name => 'Progress')
60 add_issue(v, :status => status)
66 add_issue(v, :status => status)
61 add_issue(v, :status => status, :done_ratio => 20)
67 add_issue(v, :status => status, :done_ratio => 20)
62 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
68 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
63 add_issue(v, :status => status, :estimated_hours => 15)
69 add_issue(v, :status => status, :estimated_hours => 15)
64 assert_progress_equal 100.0, v.completed_pourcent
70 assert_progress_equal 100.0, v.completed_pourcent
65 assert_progress_equal 100.0, v.closed_pourcent
71 assert_progress_equal 100.0, v.closed_pourcent
66 end
72 end
67
73
68 def test_progress_should_consider_done_ratio_of_open_assigned_issues
74 def test_progress_should_consider_done_ratio_of_open_assigned_issues
69 project = Project.find(1)
75 project = Project.find(1)
70 v = Version.create!(:project => project, :name => 'Progress')
76 v = Version.create!(:project => project, :name => 'Progress')
71 add_issue(v)
77 add_issue(v)
72 add_issue(v, :done_ratio => 20)
78 add_issue(v, :done_ratio => 20)
73 add_issue(v, :done_ratio => 70)
79 add_issue(v, :done_ratio => 70)
74 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
80 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
75 assert_progress_equal 0, v.closed_pourcent
81 assert_progress_equal 0, v.closed_pourcent
76 end
82 end
77
83
78 def test_progress_should_consider_closed_issues_as_completed
84 def test_progress_should_consider_closed_issues_as_completed
79 project = Project.find(1)
85 project = Project.find(1)
80 v = Version.create!(:project => project, :name => 'Progress')
86 v = Version.create!(:project => project, :name => 'Progress')
81 add_issue(v)
87 add_issue(v)
82 add_issue(v, :done_ratio => 20)
88 add_issue(v, :done_ratio => 20)
83 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
89 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
90 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent
85 assert_progress_equal (100.0)/3, v.closed_pourcent
91 assert_progress_equal (100.0)/3, v.closed_pourcent
86 end
92 end
87
93
88 def test_progress_should_consider_estimated_hours_to_weigth_issues
94 def test_progress_should_consider_estimated_hours_to_weigth_issues
89 project = Project.find(1)
95 project = Project.find(1)
90 v = Version.create!(:project => project, :name => 'Progress')
96 v = Version.create!(:project => project, :name => 'Progress')
91 add_issue(v, :estimated_hours => 10)
97 add_issue(v, :estimated_hours => 10)
92 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
98 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
93 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
99 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
94 add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
100 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
101 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
102 assert_progress_equal 25.0/95.0*100, v.closed_pourcent
97 end
103 end
98
104
99 def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
105 def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
100 project = Project.find(1)
106 project = Project.find(1)
101 v = Version.create!(:project => project, :name => 'Progress')
107 v = Version.create!(:project => project, :name => 'Progress')
102 add_issue(v, :done_ratio => 20)
108 add_issue(v, :done_ratio => 20)
103 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
109 add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
104 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
110 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
105 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
111 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
112 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
113 assert_progress_equal 25.0/100.0*100, v.closed_pourcent
108 end
114 end
109
115
110 def test_should_sort_scheduled_then_unscheduled_versions
116 def test_should_sort_scheduled_then_unscheduled_versions
111 Version.delete_all
117 Version.delete_all
112 v4 = Version.create!(:project_id => 1, :name => 'v4')
118 v4 = Version.create!(:project_id => 1, :name => 'v4')
113 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
119 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
114 v2 = Version.create!(:project_id => 1, :name => 'v1')
120 v2 = Version.create!(:project_id => 1, :name => 'v1')
115 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
121 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')
122 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
117
123
118 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
124 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
119 assert_equal [v5, v3, v1, v2, v4], Version.sorted.all
125 assert_equal [v5, v3, v1, v2, v4], Version.sorted.all
120 end
126 end
121
127
122 def test_completed_should_be_false_when_due_today
128 def test_completed_should_be_false_when_due_today
123 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
129 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
124 assert_equal false, version.completed?
130 assert_equal false, version.completed?
125 end
131 end
126
132
127 context "#behind_schedule?" do
133 context "#behind_schedule?" do
128 setup do
134 setup do
129 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
135 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
130 @project = Project.create!(:name => 'test0', :identifier => 'test0')
136 @project = Project.create!(:name => 'test0', :identifier => 'test0')
131 @project.trackers << Tracker.create!(:name => 'track')
137 @project.trackers << Tracker.create!(:name => 'track')
132
138
133 @version = Version.create!(:project => @project, :effective_date => nil, :name => 'version')
139 @version = Version.create!(:project => @project, :effective_date => nil, :name => 'version')
134 end
140 end
135
141
136 should "be false if there are no issues assigned" do
142 should "be false if there are no issues assigned" do
137 @version.update_attribute(:effective_date, Date.yesterday)
143 @version.update_attribute(:effective_date, Date.yesterday)
138 assert_equal false, @version.behind_schedule?
144 assert_equal false, @version.behind_schedule?
139 end
145 end
140
146
141 should "be false if there is no effective_date" do
147 should "be false if there is no effective_date" do
142 assert_equal false, @version.behind_schedule?
148 assert_equal false, @version.behind_schedule?
143 end
149 end
144
150
145 should "be false if all of the issues are ahead of schedule" do
151 should "be false if all of the issues are ahead of schedule" do
146 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
152 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
147 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
153 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
148 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
154 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
149 assert_equal 60, @version.completed_pourcent
155 assert_equal 60, @version.completed_pourcent
150 assert_equal false, @version.behind_schedule?
156 assert_equal false, @version.behind_schedule?
151 end
157 end
152
158
153 should "be true if any of the issues are behind schedule" do
159 should "be true if any of the issues are behind schedule" do
154 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
160 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
155 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
161 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
156 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
162 add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
157 assert_equal 40, @version.completed_pourcent
163 assert_equal 40, @version.completed_pourcent
158 assert_equal true, @version.behind_schedule?
164 assert_equal true, @version.behind_schedule?
159 end
165 end
160
166
161 should "be false if all of the issues are complete" do
167 should "be false if all of the issues are complete" do
162 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
168 @version.update_attribute(:effective_date, 7.days.from_now.to_date)
163 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
169 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
164 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
170 add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
165 assert_equal 100, @version.completed_pourcent
171 assert_equal 100, @version.completed_pourcent
166 assert_equal false, @version.behind_schedule?
172 assert_equal false, @version.behind_schedule?
167 end
173 end
168 end
174 end
169
175
170 context "#estimated_hours" do
176 context "#estimated_hours" do
171 setup do
177 setup do
172 @version = Version.create!(:project_id => 1, :name => '#estimated_hours')
178 @version = Version.create!(:project_id => 1, :name => '#estimated_hours')
173 end
179 end
174
180
175 should "return 0 with no assigned issues" do
181 should "return 0 with no assigned issues" do
176 assert_equal 0, @version.estimated_hours
182 assert_equal 0, @version.estimated_hours
177 end
183 end
178
184
179 should "return 0 with no estimated hours" do
185 should "return 0 with no estimated hours" do
180 add_issue(@version)
186 add_issue(@version)
181 assert_equal 0, @version.estimated_hours
187 assert_equal 0, @version.estimated_hours
182 end
188 end
183
189
184 should "return the sum of estimated hours" do
190 should "return the sum of estimated hours" do
185 add_issue(@version, :estimated_hours => 2.5)
191 add_issue(@version, :estimated_hours => 2.5)
186 add_issue(@version, :estimated_hours => 5)
192 add_issue(@version, :estimated_hours => 5)
187 assert_equal 7.5, @version.estimated_hours
193 assert_equal 7.5, @version.estimated_hours
188 end
194 end
189
195
190 should "return the sum of leaves estimated hours" do
196 should "return the sum of leaves estimated hours" do
191 parent = add_issue(@version)
197 parent = add_issue(@version)
192 add_issue(@version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
198 add_issue(@version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
193 add_issue(@version, :estimated_hours => 5, :parent_issue_id => parent.id)
199 add_issue(@version, :estimated_hours => 5, :parent_issue_id => parent.id)
194 assert_equal 7.5, @version.estimated_hours
200 assert_equal 7.5, @version.estimated_hours
195 end
201 end
196 end
202 end
197
203
198 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
204 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
199 User.current = User.find(1) # Need the admin's permissions
205 User.current = User.find(1) # Need the admin's permissions
200
206
201 @version = Version.find(7)
207 @version = Version.find(7)
202 # Separate hierarchy
208 # Separate hierarchy
203 project_1_issue = Issue.find(1)
209 project_1_issue = Issue.find(1)
204 project_1_issue.fixed_version = @version
210 project_1_issue.fixed_version = @version
205 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
211 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
206
212
207 project_5_issue = Issue.find(6)
213 project_5_issue = Issue.find(6)
208 project_5_issue.fixed_version = @version
214 project_5_issue.fixed_version = @version
209 assert project_5_issue.save
215 assert project_5_issue.save
210
216
211 # Project
217 # Project
212 project_2_issue = Issue.find(4)
218 project_2_issue = Issue.find(4)
213 project_2_issue.fixed_version = @version
219 project_2_issue.fixed_version = @version
214 assert project_2_issue.save
220 assert project_2_issue.save
215
221
216 # Update the sharing
222 # Update the sharing
217 @version.sharing = 'none'
223 @version.sharing = 'none'
218 assert @version.save
224 assert @version.save
219
225
220 # Project 1 now out of the shared scope
226 # Project 1 now out of the shared scope
221 project_1_issue.reload
227 project_1_issue.reload
222 assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
228 assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
223
229
224 # Project 5 now out of the shared scope
230 # Project 5 now out of the shared scope
225 project_5_issue.reload
231 project_5_issue.reload
226 assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
232 assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
227
233
228 # Project 2 issue remains
234 # Project 2 issue remains
229 project_2_issue.reload
235 project_2_issue.reload
230 assert_equal @version, project_2_issue.fixed_version
236 assert_equal @version, project_2_issue.fixed_version
231 end
237 end
232
238
233 private
239 private
234
240
235 def add_issue(version, attributes={})
241 def add_issue(version, attributes={})
236 Issue.create!({:project => version.project,
242 Issue.create!({:project => version.project,
237 :fixed_version => version,
243 :fixed_version => version,
238 :subject => 'Test',
244 :subject => 'Test',
239 :author => User.find(:first),
245 :author => User.find(:first),
240 :tracker => version.project.trackers.find(:first)}.merge(attributes))
246 :tracker => version.project.trackers.find(:first)}.merge(attributes))
241 end
247 end
242
248
243 def assert_progress_equal(expected_float, actual_float, message="")
249 def assert_progress_equal(expected_float, actual_float, message="")
244 assert_in_delta(expected_float, actual_float, 0.000001, message="")
250 assert_in_delta(expected_float, actual_float, 0.000001, message="")
245 end
251 end
246 end
252 end
General Comments 0
You need to be logged in to leave comments. Login now