##// END OF EJS Templates
Closed versions should be marked as completed (#21433)....
Jean-Philippe Lang -
r14637:5e5506587e95
parent child
Show More
@@ -1,182 +1,182
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class VersionsController < ApplicationController
19 19 menu_item :roadmap
20 20 model_object Version
21 21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
22 22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
23 23 before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
24 24 before_filter :authorize
25 25
26 26 accept_api_auth :index, :show, :create, :update, :destroy
27 27
28 28 helper :custom_fields
29 29 helper :projects
30 30
31 31 def index
32 32 respond_to do |format|
33 33 format.html {
34 34 @trackers = @project.trackers.sorted.to_a
35 35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
36 36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
37 37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
38 38
39 39 @versions = @project.shared_versions || []
40 40 @versions += @project.rolled_up_versions.visible if @with_subprojects
41 41 @versions = @versions.uniq.sort
42 42 unless params[:completed]
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
43 @completed_versions = @versions.select(&:completed?)
44 44 @versions -= @completed_versions
45 45 end
46 46
47 47 @issues_by_version = {}
48 48 if @selected_tracker_ids.any? && @versions.any?
49 49 issues = Issue.visible.
50 50 includes(:project, :tracker).
51 51 preload(:status, :priority, :fixed_version).
52 52 where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
53 53 order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
54 54 @issues_by_version = issues.group_by(&:fixed_version)
55 55 end
56 56 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
57 57 }
58 58 format.api {
59 59 @versions = @project.shared_versions.to_a
60 60 }
61 61 end
62 62 end
63 63
64 64 def show
65 65 respond_to do |format|
66 66 format.html {
67 67 @issues = @version.fixed_issues.visible.
68 68 includes(:status, :tracker, :priority).
69 69 reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
70 70 to_a
71 71 }
72 72 format.api
73 73 end
74 74 end
75 75
76 76 def new
77 77 @version = @project.versions.build
78 78 @version.safe_attributes = params[:version]
79 79
80 80 respond_to do |format|
81 81 format.html
82 82 format.js
83 83 end
84 84 end
85 85
86 86 def create
87 87 @version = @project.versions.build
88 88 if params[:version]
89 89 attributes = params[:version].dup
90 90 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
91 91 @version.safe_attributes = attributes
92 92 end
93 93
94 94 if request.post?
95 95 if @version.save
96 96 respond_to do |format|
97 97 format.html do
98 98 flash[:notice] = l(:notice_successful_create)
99 99 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
100 100 end
101 101 format.js
102 102 format.api do
103 103 render :action => 'show', :status => :created, :location => version_url(@version)
104 104 end
105 105 end
106 106 else
107 107 respond_to do |format|
108 108 format.html { render :action => 'new' }
109 109 format.js { render :action => 'new' }
110 110 format.api { render_validation_errors(@version) }
111 111 end
112 112 end
113 113 end
114 114 end
115 115
116 116 def edit
117 117 end
118 118
119 119 def update
120 120 if params[:version]
121 121 attributes = params[:version].dup
122 122 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
123 123 @version.safe_attributes = attributes
124 124 if @version.save
125 125 respond_to do |format|
126 126 format.html {
127 127 flash[:notice] = l(:notice_successful_update)
128 128 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
129 129 }
130 130 format.api { render_api_ok }
131 131 end
132 132 else
133 133 respond_to do |format|
134 134 format.html { render :action => 'edit' }
135 135 format.api { render_validation_errors(@version) }
136 136 end
137 137 end
138 138 end
139 139 end
140 140
141 141 def close_completed
142 142 if request.put?
143 143 @project.close_completed_versions
144 144 end
145 145 redirect_to settings_project_path(@project, :tab => 'versions')
146 146 end
147 147
148 148 def destroy
149 149 if @version.deletable?
150 150 @version.destroy
151 151 respond_to do |format|
152 152 format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') }
153 153 format.api { render_api_ok }
154 154 end
155 155 else
156 156 respond_to do |format|
157 157 format.html {
158 158 flash[:error] = l(:notice_unable_delete_version)
159 159 redirect_to settings_project_path(@project, :tab => 'versions')
160 160 }
161 161 format.api { head :unprocessable_entity }
162 162 end
163 163 end
164 164 end
165 165
166 166 def status_by
167 167 respond_to do |format|
168 168 format.html { render :action => 'show' }
169 169 format.js
170 170 end
171 171 end
172 172
173 173 private
174 174
175 175 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
176 176 if ids = params[:tracker_ids]
177 177 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
178 178 else
179 179 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
180 180 end
181 181 end
182 182 end
@@ -1,307 +1,307
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Version < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20
21 21 after_update :update_issues_from_sharing_change
22 22 before_destroy :nullify_projects_default_version
23 23
24 24 belongs_to :project
25 25 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
26 26 acts_as_customizable
27 27 acts_as_attachable :view_permission => :view_files,
28 28 :edit_permission => :manage_files,
29 29 :delete_permission => :manage_files
30 30
31 31 VERSION_STATUSES = %w(open locked closed)
32 32 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
33 33
34 34 validates_presence_of :name
35 35 validates_uniqueness_of :name, :scope => [:project_id]
36 36 validates_length_of :name, :maximum => 60
37 37 validates_length_of :description, :maximum => 255
38 38 validates :effective_date, :date => true
39 39 validates_inclusion_of :status, :in => VERSION_STATUSES
40 40 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
41 41 attr_protected :id
42 42
43 43 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
44 44 scope :open, lambda { where(:status => 'open') }
45 45 scope :visible, lambda {|*args|
46 46 joins(:project).
47 47 where(Project.allowed_to_condition(args.first || User.current, :view_issues))
48 48 }
49 49
50 50 safe_attributes 'name',
51 51 'description',
52 52 'effective_date',
53 53 'due_date',
54 54 'wiki_page_title',
55 55 'status',
56 56 'sharing',
57 57 'custom_field_values',
58 58 'custom_fields'
59 59
60 60 # Returns true if +user+ or current user is allowed to view the version
61 61 def visible?(user=User.current)
62 62 user.allowed_to?(:view_issues, self.project)
63 63 end
64 64
65 65 # Version files have same visibility as project files
66 66 def attachments_visible?(*args)
67 67 project.present? && project.attachments_visible?(*args)
68 68 end
69 69
70 70 def attachments_deletable?(usr=User.current)
71 71 project.present? && project.attachments_deletable?(usr)
72 72 end
73 73
74 74 def start_date
75 75 @start_date ||= fixed_issues.minimum('start_date')
76 76 end
77 77
78 78 def due_date
79 79 effective_date
80 80 end
81 81
82 82 def due_date=(arg)
83 83 self.effective_date=(arg)
84 84 end
85 85
86 86 # Returns the total estimated time for this version
87 87 # (sum of leaves estimated_hours)
88 88 def estimated_hours
89 89 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
90 90 end
91 91
92 92 # Returns the total reported time for this version
93 93 def spent_hours
94 94 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
95 95 end
96 96
97 97 def closed?
98 98 status == 'closed'
99 99 end
100 100
101 101 def open?
102 102 status == 'open'
103 103 end
104 104
105 # Returns true if the version is completed: due date reached and no open issues
105 # Returns true if the version is completed: closed or due date reached and no open issues
106 106 def completed?
107 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
107 closed? || (effective_date && (effective_date < Date.today) && (open_issues_count == 0))
108 108 end
109 109
110 110 def behind_schedule?
111 111 if completed_percent == 100
112 112 return false
113 113 elsif due_date && start_date
114 114 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
115 115 return done_date <= Date.today
116 116 else
117 117 false # No issues so it's not late
118 118 end
119 119 end
120 120
121 121 # Returns the completion percentage of this version based on the amount of open/closed issues
122 122 # and the time spent on the open issues.
123 123 def completed_percent
124 124 if issues_count == 0
125 125 0
126 126 elsif open_issues_count == 0
127 127 100
128 128 else
129 129 issues_progress(false) + issues_progress(true)
130 130 end
131 131 end
132 132
133 133 # Returns the percentage of issues that have been marked as 'closed'.
134 134 def closed_percent
135 135 if issues_count == 0
136 136 0
137 137 else
138 138 issues_progress(false)
139 139 end
140 140 end
141 141
142 142 # Returns true if the version is overdue: due date reached and some open issues
143 143 def overdue?
144 144 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 145 end
146 146
147 147 # Returns assigned issues count
148 148 def issues_count
149 149 load_issue_counts
150 150 @issue_count
151 151 end
152 152
153 153 # Returns the total amount of open issues for this version.
154 154 def open_issues_count
155 155 load_issue_counts
156 156 @open_issues_count
157 157 end
158 158
159 159 # Returns the total amount of closed issues for this version.
160 160 def closed_issues_count
161 161 load_issue_counts
162 162 @closed_issues_count
163 163 end
164 164
165 165 def wiki_page
166 166 if project.wiki && !wiki_page_title.blank?
167 167 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 168 end
169 169 @wiki_page
170 170 end
171 171
172 172 def to_s; name end
173 173
174 174 def to_s_with_project
175 175 "#{project} - #{name}"
176 176 end
177 177
178 178 # Versions are sorted by effective_date and name
179 179 # Those with no effective_date are at the end, sorted by name
180 180 def <=>(version)
181 181 if self.effective_date
182 182 if version.effective_date
183 183 if self.effective_date == version.effective_date
184 184 name == version.name ? id <=> version.id : name <=> version.name
185 185 else
186 186 self.effective_date <=> version.effective_date
187 187 end
188 188 else
189 189 -1
190 190 end
191 191 else
192 192 if version.effective_date
193 193 1
194 194 else
195 195 name == version.name ? id <=> version.id : name <=> version.name
196 196 end
197 197 end
198 198 end
199 199
200 200 def self.fields_for_order_statement(table=nil)
201 201 table ||= table_name
202 202 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 203 end
204 204
205 205 scope :sorted, lambda { order(fields_for_order_statement) }
206 206
207 207 # Returns the sharings that +user+ can set the version to
208 208 def allowed_sharings(user = User.current)
209 209 VERSION_SHARINGS.select do |s|
210 210 if sharing == s
211 211 true
212 212 else
213 213 case s
214 214 when 'system'
215 215 # Only admin users can set a systemwide sharing
216 216 user.admin?
217 217 when 'hierarchy', 'tree'
218 218 # Only users allowed to manage versions of the root project can
219 219 # set sharing to hierarchy or tree
220 220 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 221 else
222 222 true
223 223 end
224 224 end
225 225 end
226 226 end
227 227
228 228 # Returns true if the version is shared, otherwise false
229 229 def shared?
230 230 sharing != 'none'
231 231 end
232 232
233 233 def deletable?
234 234 fixed_issues.empty? && !referenced_by_a_custom_field?
235 235 end
236 236
237 237 private
238 238
239 239 def load_issue_counts
240 240 unless @issue_count
241 241 @open_issues_count = 0
242 242 @closed_issues_count = 0
243 243 fixed_issues.group(:status).count.each do |status, count|
244 244 if status.is_closed?
245 245 @closed_issues_count += count
246 246 else
247 247 @open_issues_count += count
248 248 end
249 249 end
250 250 @issue_count = @open_issues_count + @closed_issues_count
251 251 end
252 252 end
253 253
254 254 # Update the issue's fixed versions. Used if a version's sharing changes.
255 255 def update_issues_from_sharing_change
256 256 if sharing_changed?
257 257 if VERSION_SHARINGS.index(sharing_was).nil? ||
258 258 VERSION_SHARINGS.index(sharing).nil? ||
259 259 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
260 260 Issue.update_versions_from_sharing_change self
261 261 end
262 262 end
263 263 end
264 264
265 265 # Returns the average estimated time of assigned issues
266 266 # or 1 if no issue has an estimated time
267 267 # Used to weight unestimated issues in progress calculation
268 268 def estimated_average
269 269 if @estimated_average.nil?
270 270 average = fixed_issues.average(:estimated_hours).to_f
271 271 if average == 0
272 272 average = 1
273 273 end
274 274 @estimated_average = average
275 275 end
276 276 @estimated_average
277 277 end
278 278
279 279 # Returns the total progress of open or closed issues. The returned percentage takes into account
280 280 # the amount of estimated time set for this version.
281 281 #
282 282 # Examples:
283 283 # issues_progress(true) => returns the progress percentage for open issues.
284 284 # issues_progress(false) => returns the progress percentage for closed issues.
285 285 def issues_progress(open)
286 286 @issues_progress ||= {}
287 287 @issues_progress[open] ||= begin
288 288 progress = 0
289 289 if issues_count > 0
290 290 ratio = open ? 'done_ratio' : 100
291 291
292 292 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
293 293 progress = done / (estimated_average * issues_count)
294 294 end
295 295 progress
296 296 end
297 297 end
298 298
299 299 def referenced_by_a_custom_field?
300 300 CustomValue.joins(:custom_field).
301 301 where(:value => id.to_s, :custom_fields => {:field_format => 'version'}).any?
302 302 end
303 303
304 304 def nullify_projects_default_version
305 305 Project.where(:default_version_id => id).update_all(:default_version_id => nil)
306 306 end
307 307 end
@@ -1,272 +1,277
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class VersionTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :issues, :issue_statuses, :trackers,
22 22 :enumerations, :versions, :projects_trackers
23 23
24 24 def test_create
25 25 v = Version.new(:project => Project.find(1), :name => '1.1',
26 26 :effective_date => '2011-03-25')
27 27 assert v.save
28 28 assert_equal 'open', v.status
29 29 assert_equal 'none', v.sharing
30 30 end
31 31
32 32 def test_invalid_effective_date_validation
33 33 v = Version.new(:project => Project.find(1), :name => '1.1',
34 34 :effective_date => '99999-01-01')
35 35 assert !v.valid?
36 36 v.effective_date = '2012-11-33'
37 37 assert !v.valid?
38 38 v.effective_date = '2012-31-11'
39 39 assert !v.valid?
40 40 v.effective_date = '-2012-31-11'
41 41 assert !v.valid?
42 42 v.effective_date = 'ABC'
43 43 assert !v.valid?
44 44 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
45 45 v.errors[:effective_date]
46 46 end
47 47
48 48 def test_progress_should_be_0_with_no_assigned_issues
49 49 project = Project.find(1)
50 50 v = Version.create!(:project => project, :name => 'Progress')
51 51 assert_equal 0, v.completed_percent
52 52 assert_equal 0, v.closed_percent
53 53 end
54 54
55 55 def test_progress_should_be_0_with_unbegun_assigned_issues
56 56 project = Project.find(1)
57 57 v = Version.create!(:project => project, :name => 'Progress')
58 58 add_issue(v)
59 59 add_issue(v, :done_ratio => 0)
60 60 assert_progress_equal 0, v.completed_percent
61 61 assert_progress_equal 0, v.closed_percent
62 62 end
63 63
64 64 def test_progress_should_be_100_with_closed_assigned_issues
65 65 project = Project.find(1)
66 66 status = IssueStatus.where(:is_closed => true).first
67 67 v = Version.create!(:project => project, :name => 'Progress')
68 68 add_issue(v, :status => status)
69 69 add_issue(v, :status => status, :done_ratio => 20)
70 70 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
71 71 add_issue(v, :status => status, :estimated_hours => 15)
72 72 assert_progress_equal 100.0, v.completed_percent
73 73 assert_progress_equal 100.0, v.closed_percent
74 74 end
75 75
76 76 def test_progress_should_consider_done_ratio_of_open_assigned_issues
77 77 project = Project.find(1)
78 78 v = Version.create!(:project => project, :name => 'Progress')
79 79 add_issue(v)
80 80 add_issue(v, :done_ratio => 20)
81 81 add_issue(v, :done_ratio => 70)
82 82 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent
83 83 assert_progress_equal 0, v.closed_percent
84 84 end
85 85
86 86 def test_progress_should_consider_closed_issues_as_completed
87 87 project = Project.find(1)
88 88 v = Version.create!(:project => project, :name => 'Progress')
89 89 add_issue(v)
90 90 add_issue(v, :done_ratio => 20)
91 91 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
92 92 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent
93 93 assert_progress_equal (100.0)/3, v.closed_percent
94 94 end
95 95
96 96 def test_progress_should_consider_estimated_hours_to_weight_issues
97 97 project = Project.find(1)
98 98 v = Version.create!(:project => project, :name => 'Progress')
99 99 add_issue(v, :estimated_hours => 10)
100 100 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
101 101 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
102 102 add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first)
103 103 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent
104 104 assert_progress_equal 25.0/95.0*100, v.closed_percent
105 105 end
106 106
107 107 def test_progress_should_consider_average_estimated_hours_to_weight_unestimated_issues
108 108 project = Project.find(1)
109 109 v = Version.create!(:project => project, :name => 'Progress')
110 110 add_issue(v, :done_ratio => 20)
111 111 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
112 112 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
113 113 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
114 114 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent
115 115 assert_progress_equal 25.0/100.0*100, v.closed_percent
116 116 end
117 117
118 118 def test_should_sort_scheduled_then_unscheduled_versions
119 119 Version.delete_all
120 120 v4 = Version.create!(:project_id => 1, :name => 'v4')
121 121 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
122 122 v2 = Version.create!(:project_id => 1, :name => 'v1')
123 123 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
124 124 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
125 125
126 126 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
127 127 assert_equal [v5, v3, v1, v2, v4], Version.sorted.to_a
128 128 end
129 129
130 130 def test_should_sort_versions_with_same_date_by_name
131 131 v1 = Version.new(:effective_date => '2014-12-03', :name => 'v2')
132 132 v2 = Version.new(:effective_date => '2014-12-03', :name => 'v1')
133 133 assert_equal [v2, v1], [v1, v2].sort
134 134 end
135 135
136 136 def test_completed_should_be_false_when_due_today
137 137 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
138 138 assert_equal false, version.completed?
139 139 end
140 140
141 def test_completed_should_be_true_when_closed
142 version = Version.create!(:project_id => 1, :status => 'closed', :name => 'Closed')
143 assert_equal true, version.completed?
144 end
145
141 146 test "#behind_schedule? should be false if there are no issues assigned" do
142 147 version = Version.generate!(:effective_date => Date.yesterday)
143 148 assert_equal false, version.behind_schedule?
144 149 end
145 150
146 151 test "#behind_schedule? should be false if there is no effective_date" do
147 152 version = Version.generate!(:effective_date => nil)
148 153 assert_equal false, version.behind_schedule?
149 154 end
150 155
151 156 test "#behind_schedule? should be false if all of the issues are ahead of schedule" do
152 157 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
153 158 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
154 159 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
155 160 assert_equal 60, version.completed_percent
156 161 assert_equal false, version.behind_schedule?
157 162 end
158 163
159 164 test "#behind_schedule? should be true if any of the issues are behind schedule" do
160 165 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
161 166 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
162 167 add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
163 168 assert_equal 40, version.completed_percent
164 169 assert_equal true, version.behind_schedule?
165 170 end
166 171
167 172 test "#behind_schedule? should be false if all of the issues are complete" do
168 173 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
169 174 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
170 175 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
171 176 assert_equal 100, version.completed_percent
172 177 assert_equal false, version.behind_schedule?
173 178 end
174 179
175 180 test "#estimated_hours should return 0 with no assigned issues" do
176 181 version = Version.generate!
177 182 assert_equal 0, version.estimated_hours
178 183 end
179 184
180 185 test "#estimated_hours should return 0 with no estimated hours" do
181 186 version = Version.create!(:project_id => 1, :name => 'test')
182 187 add_issue(version)
183 188 assert_equal 0, version.estimated_hours
184 189 end
185 190
186 191 test "#estimated_hours should return return the sum of estimated hours" do
187 192 version = Version.create!(:project_id => 1, :name => 'test')
188 193 add_issue(version, :estimated_hours => 2.5)
189 194 add_issue(version, :estimated_hours => 5)
190 195 assert_equal 7.5, version.estimated_hours
191 196 end
192 197
193 198 test "#estimated_hours should return the sum of leaves estimated hours" do
194 199 version = Version.create!(:project_id => 1, :name => 'test')
195 200 parent = add_issue(version)
196 201 add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
197 202 add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id)
198 203 assert_equal 7.5, version.estimated_hours
199 204 end
200 205
201 206 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
202 207 User.current = User.find(1) # Need the admin's permissions
203 208
204 209 @version = Version.find(7)
205 210 # Separate hierarchy
206 211 project_1_issue = Issue.find(1)
207 212 project_1_issue.fixed_version = @version
208 213 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
209 214
210 215 project_5_issue = Issue.find(6)
211 216 project_5_issue.fixed_version = @version
212 217 assert project_5_issue.save
213 218
214 219 # Project
215 220 project_2_issue = Issue.find(4)
216 221 project_2_issue.fixed_version = @version
217 222 assert project_2_issue.save
218 223
219 224 # Update the sharing
220 225 @version.sharing = 'none'
221 226 assert @version.save
222 227
223 228 # Project 1 now out of the shared scope
224 229 project_1_issue.reload
225 230 assert_equal nil, project_1_issue.fixed_version,
226 231 "Fixed version is still set after changing the Version's sharing"
227 232
228 233 # Project 5 now out of the shared scope
229 234 project_5_issue.reload
230 235 assert_equal nil, project_5_issue.fixed_version,
231 236 "Fixed version is still set after changing the Version's sharing"
232 237
233 238 # Project 2 issue remains
234 239 project_2_issue.reload
235 240 assert_equal @version, project_2_issue.fixed_version
236 241 end
237 242
238 243 def test_deletable_should_return_true_when_not_referenced
239 244 version = Version.generate!
240 245
241 246 assert_equal true, version.deletable?
242 247 end
243 248
244 249 def test_deletable_should_return_false_when_referenced_by_an_issue
245 250 version = Version.generate!
246 251 Issue.generate!(:fixed_version => version)
247 252
248 253 assert_equal false, version.deletable?
249 254 end
250 255
251 256 def test_deletable_should_return_false_when_referenced_by_a_custom_field
252 257 version = Version.generate!
253 258 field = IssueCustomField.generate!(:field_format => 'version')
254 259 value = CustomValue.create!(:custom_field => field, :customized => Issue.first, :value => version.id)
255 260
256 261 assert_equal false, version.deletable?
257 262 end
258 263
259 264 private
260 265
261 266 def add_issue(version, attributes={})
262 267 Issue.create!({:project => version.project,
263 268 :fixed_version => version,
264 269 :subject => 'Test',
265 270 :author => User.first,
266 271 :tracker => version.project.trackers.first}.merge(attributes))
267 272 end
268 273
269 274 def assert_progress_equal(expected_float, actual_float, message="")
270 275 assert_in_delta(expected_float, actual_float, 0.000001, message="")
271 276 end
272 277 end
General Comments 0
You need to be logged in to leave comments. Login now