##// END OF EJS Templates
Fixed Version#issues_progress for Rails 3.1....
Jean-Philippe Lang -
r8866:f04c7a76800e
parent child
Show More
@@ -1,243 +1,243
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 126 @issue_count ||= fixed_issues.count
127 127 end
128 128
129 129 # Returns the total amount of open issues for this version.
130 130 def open_issues_count
131 131 @open_issues_count ||= Issue.open.count(:all, :conditions => ["fixed_version_id = ?", self.id])
132 132 end
133 133
134 134 # Returns the total amount of closed issues for this version.
135 135 def closed_issues_count
136 136 @closed_issues_count ||= Issue.open(false).count(:all, :conditions => ["fixed_version_id = ?", self.id])
137 137 end
138 138
139 139 def wiki_page
140 140 if project.wiki && !wiki_page_title.blank?
141 141 @wiki_page ||= project.wiki.find_page(wiki_page_title)
142 142 end
143 143 @wiki_page
144 144 end
145 145
146 146 def to_s; name end
147 147
148 148 def to_s_with_project
149 149 "#{project} - #{name}"
150 150 end
151 151
152 152 # Versions are sorted by effective_date and "Project Name - Version name"
153 153 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
154 154 def <=>(version)
155 155 if self.effective_date
156 156 if version.effective_date
157 157 if self.effective_date == version.effective_date
158 158 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
159 159 else
160 160 self.effective_date <=> version.effective_date
161 161 end
162 162 else
163 163 -1
164 164 end
165 165 else
166 166 if version.effective_date
167 167 1
168 168 else
169 169 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
170 170 end
171 171 end
172 172 end
173 173
174 174 # Returns the sharings that +user+ can set the version to
175 175 def allowed_sharings(user = User.current)
176 176 VERSION_SHARINGS.select do |s|
177 177 if sharing == s
178 178 true
179 179 else
180 180 case s
181 181 when 'system'
182 182 # Only admin users can set a systemwide sharing
183 183 user.admin?
184 184 when 'hierarchy', 'tree'
185 185 # Only users allowed to manage versions of the root project can
186 186 # set sharing to hierarchy or tree
187 187 project.nil? || user.allowed_to?(:manage_versions, project.root)
188 188 else
189 189 true
190 190 end
191 191 end
192 192 end
193 193 end
194 194
195 195 private
196 196
197 197 # Update the issue's fixed versions. Used if a version's sharing changes.
198 198 def update_issues_from_sharing_change
199 199 if sharing_changed?
200 200 if VERSION_SHARINGS.index(sharing_was).nil? ||
201 201 VERSION_SHARINGS.index(sharing).nil? ||
202 202 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
203 203 Issue.update_versions_from_sharing_change self
204 204 end
205 205 end
206 206 end
207 207
208 208 # Returns the average estimated time of assigned issues
209 209 # or 1 if no issue has an estimated time
210 210 # Used to weigth unestimated issues in progress calculation
211 211 def estimated_average
212 212 if @estimated_average.nil?
213 213 average = fixed_issues.average(:estimated_hours).to_f
214 214 if average == 0
215 215 average = 1
216 216 end
217 217 @estimated_average = average
218 218 end
219 219 @estimated_average
220 220 end
221 221
222 222 # Returns the total progress of open or closed issues. The returned percentage takes into account
223 223 # the amount of estimated time set for this version.
224 224 #
225 225 # Examples:
226 226 # issues_progress(true) => returns the progress percentage for open issues.
227 227 # issues_progress(false) => returns the progress percentage for closed issues.
228 228 def issues_progress(open)
229 229 @issues_progress ||= {}
230 230 @issues_progress[open] ||= begin
231 231 progress = 0
232 232 if issues_count > 0
233 233 ratio = open ? 'done_ratio' : 100
234 234
235 235 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
236 :include => :status,
237 :conditions => ["is_closed = ?", !open]).to_f
236 :joins => :status,
237 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
238 238 progress = done / (estimated_average * issues_count)
239 239 end
240 240 progress
241 241 end
242 242 end
243 243 end
General Comments 0
You need to be logged in to leave comments. Login now