##// END OF EJS Templates
Use a single query to retrieve issues_count, open_issues_count and closed_issues_count....
Jean-Philippe Lang -
r8936:1c0988cad384
parent child
Show More
@@ -1,243 +1,261
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 after_update :update_issues_from_sharing_change
20 20 belongs_to :project
21 21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
22 22 acts_as_customizable
23 23 acts_as_attachable :view_permission => :view_files,
24 24 :delete_permission => :manage_files
25 25
26 26 VERSION_STATUSES = %w(open locked closed)
27 27 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
28 28
29 29 validates_presence_of :name
30 30 validates_uniqueness_of :name, :scope => [:project_id]
31 31 validates_length_of :name, :maximum => 60
32 32 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
33 33 validates_inclusion_of :status, :in => VERSION_STATUSES
34 34 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
35 35
36 36 named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
37 37 named_scope :open, :conditions => {:status => 'open'}
38 38 named_scope :visible, lambda {|*args| { :include => :project,
39 39 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
40 40
41 41 # Returns true if +user+ or current user is allowed to view the version
42 42 def visible?(user=User.current)
43 43 user.allowed_to?(:view_issues, self.project)
44 44 end
45 45
46 46 # Version files have same visibility as project files
47 47 def attachments_visible?(*args)
48 48 project.present? && project.attachments_visible?(*args)
49 49 end
50 50
51 51 def start_date
52 52 @start_date ||= fixed_issues.minimum('start_date')
53 53 end
54 54
55 55 def due_date
56 56 effective_date
57 57 end
58 58
59 59 def due_date=(arg)
60 60 self.effective_date=(arg)
61 61 end
62 62
63 63 # Returns the total estimated time for this version
64 64 # (sum of leaves estimated_hours)
65 65 def estimated_hours
66 66 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
67 67 end
68 68
69 69 # Returns the total reported time for this version
70 70 def spent_hours
71 71 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
72 72 end
73 73
74 74 def closed?
75 75 status == 'closed'
76 76 end
77 77
78 78 def open?
79 79 status == 'open'
80 80 end
81 81
82 82 # Returns true if the version is completed: due date reached and no open issues
83 83 def completed?
84 84 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
85 85 end
86 86
87 87 def behind_schedule?
88 88 if completed_pourcent == 100
89 89 return false
90 90 elsif due_date && start_date
91 91 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
92 92 return done_date <= Date.today
93 93 else
94 94 false # No issues so it's not late
95 95 end
96 96 end
97 97
98 98 # Returns the completion percentage of this version based on the amount of open/closed issues
99 99 # and the time spent on the open issues.
100 100 def completed_pourcent
101 101 if issues_count == 0
102 102 0
103 103 elsif open_issues_count == 0
104 104 100
105 105 else
106 106 issues_progress(false) + issues_progress(true)
107 107 end
108 108 end
109 109
110 110 # Returns the percentage of issues that have been marked as 'closed'.
111 111 def closed_pourcent
112 112 if issues_count == 0
113 113 0
114 114 else
115 115 issues_progress(false)
116 116 end
117 117 end
118 118
119 119 # Returns true if the version is overdue: due date reached and some open issues
120 120 def overdue?
121 121 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
122 122 end
123 123
124 124 # Returns assigned issues count
125 125 def issues_count
126 @issue_count ||= fixed_issues.count
126 load_issue_counts
127 @issue_count
127 128 end
128 129
129 130 # Returns the total amount of open issues for this version.
130 131 def open_issues_count
131 @open_issues_count ||= Issue.open.count(:all, :conditions => ["fixed_version_id = ?", self.id])
132 load_issue_counts
133 @open_issues_count
132 134 end
133 135
134 136 # Returns the total amount of closed issues for this version.
135 137 def closed_issues_count
136 @closed_issues_count ||= Issue.open(false).count(:all, :conditions => ["fixed_version_id = ?", self.id])
138 load_issue_counts
139 @closed_issues_count
137 140 end
138 141
139 142 def wiki_page
140 143 if project.wiki && !wiki_page_title.blank?
141 144 @wiki_page ||= project.wiki.find_page(wiki_page_title)
142 145 end
143 146 @wiki_page
144 147 end
145 148
146 149 def to_s; name end
147 150
148 151 def to_s_with_project
149 152 "#{project} - #{name}"
150 153 end
151 154
152 155 # Versions are sorted by effective_date and "Project Name - Version name"
153 156 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
154 157 def <=>(version)
155 158 if self.effective_date
156 159 if version.effective_date
157 160 if self.effective_date == version.effective_date
158 161 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
159 162 else
160 163 self.effective_date <=> version.effective_date
161 164 end
162 165 else
163 166 -1
164 167 end
165 168 else
166 169 if version.effective_date
167 170 1
168 171 else
169 172 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
170 173 end
171 174 end
172 175 end
173 176
174 177 # Returns the sharings that +user+ can set the version to
175 178 def allowed_sharings(user = User.current)
176 179 VERSION_SHARINGS.select do |s|
177 180 if sharing == s
178 181 true
179 182 else
180 183 case s
181 184 when 'system'
182 185 # Only admin users can set a systemwide sharing
183 186 user.admin?
184 187 when 'hierarchy', 'tree'
185 188 # Only users allowed to manage versions of the root project can
186 189 # set sharing to hierarchy or tree
187 190 project.nil? || user.allowed_to?(:manage_versions, project.root)
188 191 else
189 192 true
190 193 end
191 194 end
192 195 end
193 196 end
194 197
195 198 private
196 199
200 def load_issue_counts
201 unless @issue_count
202 @open_issues_count = 0
203 @closed_issues_count = 0
204 fixed_issues.count(:all, :group => :status).each do |status, count|
205 if status.is_closed?
206 @closed_issues_count += count
207 else
208 @open_issues_count += count
209 end
210 end
211 @issue_count = @open_issues_count + @closed_issues_count
212 end
213 end
214
197 215 # Update the issue's fixed versions. Used if a version's sharing changes.
198 216 def update_issues_from_sharing_change
199 217 if sharing_changed?
200 218 if VERSION_SHARINGS.index(sharing_was).nil? ||
201 219 VERSION_SHARINGS.index(sharing).nil? ||
202 220 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
203 221 Issue.update_versions_from_sharing_change self
204 222 end
205 223 end
206 224 end
207 225
208 226 # Returns the average estimated time of assigned issues
209 227 # or 1 if no issue has an estimated time
210 228 # Used to weigth unestimated issues in progress calculation
211 229 def estimated_average
212 230 if @estimated_average.nil?
213 231 average = fixed_issues.average(:estimated_hours).to_f
214 232 if average == 0
215 233 average = 1
216 234 end
217 235 @estimated_average = average
218 236 end
219 237 @estimated_average
220 238 end
221 239
222 240 # Returns the total progress of open or closed issues. The returned percentage takes into account
223 241 # the amount of estimated time set for this version.
224 242 #
225 243 # Examples:
226 244 # issues_progress(true) => returns the progress percentage for open issues.
227 245 # issues_progress(false) => returns the progress percentage for closed issues.
228 246 def issues_progress(open)
229 247 @issues_progress ||= {}
230 248 @issues_progress[open] ||= begin
231 249 progress = 0
232 250 if issues_count > 0
233 251 ratio = open ? 'done_ratio' : 100
234 252
235 253 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
236 254 :joins => :status,
237 255 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
238 256 progress = done / (estimated_average * issues_count)
239 257 end
240 258 progress
241 259 end
242 260 end
243 261 end
General Comments 0
You need to be logged in to leave comments. Login now