23 |
23 |
before_destroy :nullify_projects_default_version
|
24 |
24 |
|
25 |
25 |
belongs_to :project
|
26 |
|
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
|
|
26 |
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify do
|
|
27 |
# Returns the total estimated time for this version
|
|
28 |
# (sum of leaves estimated_hours)
|
|
29 |
def estimated_hours
|
|
30 |
@estimated_hours ||= sum(:estimated_hours).to_f
|
|
31 |
end
|
|
32 |
#
|
|
33 |
# Returns the total amount of open issues for this version.
|
|
34 |
def open_count
|
|
35 |
load_counts
|
|
36 |
@open_count
|
|
37 |
end
|
|
38 |
|
|
39 |
# Returns the total amount of closed issues for this version.
|
|
40 |
def closed_count
|
|
41 |
load_counts
|
|
42 |
@closed_count
|
|
43 |
end
|
|
44 |
|
|
45 |
# Returns the completion percentage of this version based on the amount of open/closed issues
|
|
46 |
# and the time spent on the open issues.
|
|
47 |
def completed_percent
|
|
48 |
if count == 0
|
|
49 |
0
|
|
50 |
elsif open_count == 0
|
|
51 |
100
|
|
52 |
else
|
|
53 |
issues_progress(false) + issues_progress(true)
|
|
54 |
end
|
|
55 |
end
|
|
56 |
|
|
57 |
# Returns the percentage of issues that have been marked as 'closed'.
|
|
58 |
def closed_percent
|
|
59 |
if count == 0
|
|
60 |
0
|
|
61 |
else
|
|
62 |
issues_progress(false)
|
|
63 |
end
|
|
64 |
end
|
|
65 |
|
|
66 |
private
|
|
67 |
|
|
68 |
def load_counts
|
|
69 |
unless @open_count
|
|
70 |
@open_count = 0
|
|
71 |
@closed_count = 0
|
|
72 |
self.group(:status).count.each do |status, count|
|
|
73 |
if status.is_closed?
|
|
74 |
@closed_count += count
|
|
75 |
else
|
|
76 |
@open_count += count
|
|
77 |
end
|
|
78 |
end
|
|
79 |
end
|
|
80 |
end
|
|
81 |
|
|
82 |
# Returns the average estimated time of assigned issues
|
|
83 |
# or 1 if no issue has an estimated time
|
|
84 |
# Used to weight unestimated issues in progress calculation
|
|
85 |
def estimated_average
|
|
86 |
if @estimated_average.nil?
|
|
87 |
average = average(:estimated_hours).to_f
|
|
88 |
if average == 0
|
|
89 |
average = 1
|
|
90 |
end
|
|
91 |
@estimated_average = average
|
|
92 |
end
|
|
93 |
@estimated_average
|
|
94 |
end
|
|
95 |
|
|
96 |
# Returns the total progress of open or closed issues. The returned percentage takes into account
|
|
97 |
# the amount of estimated time set for this version.
|
|
98 |
#
|
|
99 |
# Examples:
|
|
100 |
# issues_progress(true) => returns the progress percentage for open issues.
|
|
101 |
# issues_progress(false) => returns the progress percentage for closed issues.
|
|
102 |
def issues_progress(open)
|
|
103 |
@issues_progress ||= {}
|
|
104 |
@issues_progress[open] ||= begin
|
|
105 |
progress = 0
|
|
106 |
if count > 0
|
|
107 |
ratio = open ? 'done_ratio' : 100
|
|
108 |
|
|
109 |
done = open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
|
|
110 |
progress = done / (estimated_average * count)
|
|
111 |
end
|
|
112 |
progress
|
|
113 |
end
|
|
114 |
end
|
|
115 |
end
|
|
116 |
|
27 |
117 |
acts_as_customizable
|
28 |
118 |
acts_as_attachable :view_permission => :view_files,
|
29 |
119 |
:edit_permission => :manage_files,
|
... | ... | |
104 |
194 |
# Returns the total estimated time for this version
|
105 |
195 |
# (sum of leaves estimated_hours)
|
106 |
196 |
def estimated_hours
|
107 |
|
@estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
|
|
197 |
fixed_issues.estimated_hours
|
108 |
198 |
end
|
109 |
199 |
|
110 |
200 |
# Returns the total reported time for this version
|
... | ... | |
139 |
229 |
# Returns the completion percentage of this version based on the amount of open/closed issues
|
140 |
230 |
# and the time spent on the open issues.
|
141 |
231 |
def completed_percent
|
142 |
|
if issues_count == 0
|
143 |
|
0
|
144 |
|
elsif open_issues_count == 0
|
145 |
|
100
|
146 |
|
else
|
147 |
|
issues_progress(false) + issues_progress(true)
|
148 |
|
end
|
|
232 |
fixed_issues.completed_percent
|
149 |
233 |
end
|
150 |
234 |
|
151 |
235 |
# Returns the percentage of issues that have been marked as 'closed'.
|
152 |
236 |
def closed_percent
|
153 |
|
if issues_count == 0
|
154 |
|
0
|
155 |
|
else
|
156 |
|
issues_progress(false)
|
157 |
|
end
|
|
237 |
fixed_issues.closed_percent
|
158 |
238 |
end
|
159 |
239 |
|
160 |
240 |
# Returns true if the version is overdue: due date reached and some open issues
|
... | ... | |
164 |
244 |
|
165 |
245 |
# Returns assigned issues count
|
166 |
246 |
def issues_count
|
167 |
|
load_issue_counts
|
168 |
|
@issue_count
|
|
247 |
fixed_issues.count
|
169 |
248 |
end
|
170 |
249 |
|
171 |
250 |
# Returns the total amount of open issues for this version.
|
172 |
251 |
def open_issues_count
|
173 |
|
load_issue_counts
|
174 |
|
@open_issues_count
|
|
252 |
fixed_issues.open_count
|
175 |
253 |
end
|
176 |
254 |
|
177 |
255 |
# Returns the total amount of closed issues for this version.
|
178 |
256 |
def closed_issues_count
|
179 |
|
load_issue_counts
|
180 |
|
@closed_issues_count
|
|
257 |
fixed_issues.closed_count
|
181 |
258 |
end
|
182 |
259 |
|
183 |
260 |
def wiki_page
|
... | ... | |
284 |
361 |
|
285 |
362 |
private
|
286 |
363 |
|
287 |
|
def load_issue_counts
|
288 |
|
unless @issue_count
|
289 |
|
@open_issues_count = 0
|
290 |
|
@closed_issues_count = 0
|
291 |
|
fixed_issues.group(:status).count.each do |status, count|
|
292 |
|
if status.is_closed?
|
293 |
|
@closed_issues_count += count
|
294 |
|
else
|
295 |
|
@open_issues_count += count
|
296 |
|
end
|
297 |
|
end
|
298 |
|
@issue_count = @open_issues_count + @closed_issues_count
|
299 |
|
end
|
300 |
|
end
|
301 |
|
|
302 |
364 |
# Update the issue's fixed versions. Used if a version's sharing changes.
|
303 |
365 |
def update_issues_from_sharing_change
|
304 |
366 |
if saved_change_to_sharing?
|
... | ... | |
316 |
378 |
end
|
317 |
379 |
end
|
318 |
380 |
|
319 |
|
# Returns the average estimated time of assigned issues
|
320 |
|
# or 1 if no issue has an estimated time
|
321 |
|
# Used to weight unestimated issues in progress calculation
|
322 |
|
def estimated_average
|
323 |
|
if @estimated_average.nil?
|
324 |
|
average = fixed_issues.average(:estimated_hours).to_f
|
325 |
|
if average == 0
|
326 |
|
average = 1
|
327 |
|
end
|
328 |
|
@estimated_average = average
|
329 |
|
end
|
330 |
|
@estimated_average
|
331 |
|
end
|
332 |
|
|
333 |
|
# Returns the total progress of open or closed issues. The returned percentage takes into account
|
334 |
|
# the amount of estimated time set for this version.
|
335 |
|
#
|
336 |
|
# Examples:
|
337 |
|
# issues_progress(true) => returns the progress percentage for open issues.
|
338 |
|
# issues_progress(false) => returns the progress percentage for closed issues.
|
339 |
|
def issues_progress(open)
|
340 |
|
@issues_progress ||= {}
|
341 |
|
@issues_progress[open] ||= begin
|
342 |
|
progress = 0
|
343 |
|
if issues_count > 0
|
344 |
|
ratio = open ? 'done_ratio' : 100
|
345 |
|
|
346 |
|
done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
|
347 |
|
progress = done / (estimated_average * issues_count)
|
348 |
|
end
|
349 |
|
progress
|
350 |
|
end
|
351 |
|
end
|
352 |
|
|
353 |
381 |
def referenced_by_a_custom_field?
|
354 |
382 |
CustomValue.joins(:custom_field).
|
355 |
383 |
where(:value => id.to_s, :custom_fields => {:field_format => 'version'}).any?
|