##// END OF EJS Templates
Rails4 compatibility of version group count...
Toshi MARUYAMA -
r12190:25a0bde31115
parent child
Show More
@@ -1,290 +1,290
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 :effective_date, :date => true
33 validates :effective_date, :date => 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
36
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
38 scope :open, lambda { where(:status => 'open') }
38 scope :open, lambda { where(:status => 'open') }
39 scope :visible, lambda {|*args|
39 scope :visible, lambda {|*args|
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
41 }
41 }
42
42
43 safe_attributes 'name',
43 safe_attributes 'name',
44 'description',
44 'description',
45 'effective_date',
45 'effective_date',
46 'due_date',
46 'due_date',
47 'wiki_page_title',
47 'wiki_page_title',
48 'status',
48 'status',
49 'sharing',
49 'sharing',
50 'custom_field_values',
50 'custom_field_values',
51 'custom_fields'
51 'custom_fields'
52
52
53 # Returns true if +user+ or current user is allowed to view the version
53 # Returns true if +user+ or current user is allowed to view the version
54 def visible?(user=User.current)
54 def visible?(user=User.current)
55 user.allowed_to?(:view_issues, self.project)
55 user.allowed_to?(:view_issues, self.project)
56 end
56 end
57
57
58 # Version files have same visibility as project files
58 # Version files have same visibility as project files
59 def attachments_visible?(*args)
59 def attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
61 end
61 end
62
62
63 def start_date
63 def start_date
64 @start_date ||= fixed_issues.minimum('start_date')
64 @start_date ||= fixed_issues.minimum('start_date')
65 end
65 end
66
66
67 def due_date
67 def due_date
68 effective_date
68 effective_date
69 end
69 end
70
70
71 def due_date=(arg)
71 def due_date=(arg)
72 self.effective_date=(arg)
72 self.effective_date=(arg)
73 end
73 end
74
74
75 # Returns the total estimated time for this version
75 # Returns the total estimated time for this version
76 # (sum of leaves estimated_hours)
76 # (sum of leaves estimated_hours)
77 def estimated_hours
77 def estimated_hours
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
79 end
79 end
80
80
81 # Returns the total reported time for this version
81 # Returns the total reported time for this version
82 def spent_hours
82 def spent_hours
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
84 end
84 end
85
85
86 def closed?
86 def closed?
87 status == 'closed'
87 status == 'closed'
88 end
88 end
89
89
90 def open?
90 def open?
91 status == 'open'
91 status == 'open'
92 end
92 end
93
93
94 # Returns true if the version is completed: due date reached and no open issues
94 # Returns true if the version is completed: due date reached and no open issues
95 def completed?
95 def completed?
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
97 end
97 end
98
98
99 def behind_schedule?
99 def behind_schedule?
100 if completed_percent == 100
100 if completed_percent == 100
101 return false
101 return false
102 elsif due_date && start_date
102 elsif due_date && start_date
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
104 return done_date <= Date.today
104 return done_date <= Date.today
105 else
105 else
106 false # No issues so it's not late
106 false # No issues so it's not late
107 end
107 end
108 end
108 end
109
109
110 # Returns the completion percentage of this version based on the amount of open/closed issues
110 # Returns the completion percentage of this version based on the amount of open/closed issues
111 # and the time spent on the open issues.
111 # and the time spent on the open issues.
112 def completed_percent
112 def completed_percent
113 if issues_count == 0
113 if issues_count == 0
114 0
114 0
115 elsif open_issues_count == 0
115 elsif open_issues_count == 0
116 100
116 100
117 else
117 else
118 issues_progress(false) + issues_progress(true)
118 issues_progress(false) + issues_progress(true)
119 end
119 end
120 end
120 end
121
121
122 # TODO: remove in Redmine 3.0
122 # TODO: remove in Redmine 3.0
123 def completed_pourcent
123 def completed_pourcent
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
125 completed_percent
125 completed_percent
126 end
126 end
127
127
128 # Returns the percentage of issues that have been marked as 'closed'.
128 # Returns the percentage of issues that have been marked as 'closed'.
129 def closed_percent
129 def closed_percent
130 if issues_count == 0
130 if issues_count == 0
131 0
131 0
132 else
132 else
133 issues_progress(false)
133 issues_progress(false)
134 end
134 end
135 end
135 end
136
136
137 # TODO: remove in Redmine 3.0
137 # TODO: remove in Redmine 3.0
138 def closed_pourcent
138 def closed_pourcent
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
140 closed_percent
140 closed_percent
141 end
141 end
142
142
143 # Returns true if the version is overdue: due date reached and some open issues
143 # Returns true if the version is overdue: due date reached and some open issues
144 def overdue?
144 def overdue?
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
146 end
146 end
147
147
148 # Returns assigned issues count
148 # Returns assigned issues count
149 def issues_count
149 def issues_count
150 load_issue_counts
150 load_issue_counts
151 @issue_count
151 @issue_count
152 end
152 end
153
153
154 # Returns the total amount of open issues for this version.
154 # Returns the total amount of open issues for this version.
155 def open_issues_count
155 def open_issues_count
156 load_issue_counts
156 load_issue_counts
157 @open_issues_count
157 @open_issues_count
158 end
158 end
159
159
160 # Returns the total amount of closed issues for this version.
160 # Returns the total amount of closed issues for this version.
161 def closed_issues_count
161 def closed_issues_count
162 load_issue_counts
162 load_issue_counts
163 @closed_issues_count
163 @closed_issues_count
164 end
164 end
165
165
166 def wiki_page
166 def wiki_page
167 if project.wiki && !wiki_page_title.blank?
167 if project.wiki && !wiki_page_title.blank?
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
169 end
169 end
170 @wiki_page
170 @wiki_page
171 end
171 end
172
172
173 def to_s; name end
173 def to_s; name end
174
174
175 def to_s_with_project
175 def to_s_with_project
176 "#{project} - #{name}"
176 "#{project} - #{name}"
177 end
177 end
178
178
179 # Versions are sorted by effective_date and name
179 # Versions are sorted by effective_date and name
180 # Those with no effective_date are at the end, sorted by name
180 # Those with no effective_date are at the end, sorted by name
181 def <=>(version)
181 def <=>(version)
182 if self.effective_date
182 if self.effective_date
183 if version.effective_date
183 if version.effective_date
184 if self.effective_date == version.effective_date
184 if self.effective_date == version.effective_date
185 name == version.name ? id <=> version.id : name <=> version.name
185 name == version.name ? id <=> version.id : name <=> version.name
186 else
186 else
187 self.effective_date <=> version.effective_date
187 self.effective_date <=> version.effective_date
188 end
188 end
189 else
189 else
190 -1
190 -1
191 end
191 end
192 else
192 else
193 if version.effective_date
193 if version.effective_date
194 1
194 1
195 else
195 else
196 name == version.name ? id <=> version.id : name <=> version.name
196 name == version.name ? id <=> version.id : name <=> version.name
197 end
197 end
198 end
198 end
199 end
199 end
200
200
201 def self.fields_for_order_statement(table=nil)
201 def self.fields_for_order_statement(table=nil)
202 table ||= table_name
202 table ||= table_name
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
204 end
204 end
205
205
206 scope :sorted, order(fields_for_order_statement)
206 scope :sorted, order(fields_for_order_statement)
207
207
208 # Returns the sharings that +user+ can set the version to
208 # Returns the sharings that +user+ can set the version to
209 def allowed_sharings(user = User.current)
209 def allowed_sharings(user = User.current)
210 VERSION_SHARINGS.select do |s|
210 VERSION_SHARINGS.select do |s|
211 if sharing == s
211 if sharing == s
212 true
212 true
213 else
213 else
214 case s
214 case s
215 when 'system'
215 when 'system'
216 # Only admin users can set a systemwide sharing
216 # Only admin users can set a systemwide sharing
217 user.admin?
217 user.admin?
218 when 'hierarchy', 'tree'
218 when 'hierarchy', 'tree'
219 # Only users allowed to manage versions of the root project can
219 # Only users allowed to manage versions of the root project can
220 # set sharing to hierarchy or tree
220 # set sharing to hierarchy or tree
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
222 else
222 else
223 true
223 true
224 end
224 end
225 end
225 end
226 end
226 end
227 end
227 end
228
228
229 private
229 private
230
230
231 def load_issue_counts
231 def load_issue_counts
232 unless @issue_count
232 unless @issue_count
233 @open_issues_count = 0
233 @open_issues_count = 0
234 @closed_issues_count = 0
234 @closed_issues_count = 0
235 fixed_issues.count(:all, :group => :status).each do |status, count|
235 fixed_issues.group(:status).count.each do |status, count|
236 if status.is_closed?
236 if status.is_closed?
237 @closed_issues_count += count
237 @closed_issues_count += count
238 else
238 else
239 @open_issues_count += count
239 @open_issues_count += count
240 end
240 end
241 end
241 end
242 @issue_count = @open_issues_count + @closed_issues_count
242 @issue_count = @open_issues_count + @closed_issues_count
243 end
243 end
244 end
244 end
245
245
246 # Update the issue's fixed versions. Used if a version's sharing changes.
246 # Update the issue's fixed versions. Used if a version's sharing changes.
247 def update_issues_from_sharing_change
247 def update_issues_from_sharing_change
248 if sharing_changed?
248 if sharing_changed?
249 if VERSION_SHARINGS.index(sharing_was).nil? ||
249 if VERSION_SHARINGS.index(sharing_was).nil? ||
250 VERSION_SHARINGS.index(sharing).nil? ||
250 VERSION_SHARINGS.index(sharing).nil? ||
251 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
251 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
252 Issue.update_versions_from_sharing_change self
252 Issue.update_versions_from_sharing_change self
253 end
253 end
254 end
254 end
255 end
255 end
256
256
257 # Returns the average estimated time of assigned issues
257 # Returns the average estimated time of assigned issues
258 # or 1 if no issue has an estimated time
258 # or 1 if no issue has an estimated time
259 # Used to weigth unestimated issues in progress calculation
259 # Used to weigth unestimated issues in progress calculation
260 def estimated_average
260 def estimated_average
261 if @estimated_average.nil?
261 if @estimated_average.nil?
262 average = fixed_issues.average(:estimated_hours).to_f
262 average = fixed_issues.average(:estimated_hours).to_f
263 if average == 0
263 if average == 0
264 average = 1
264 average = 1
265 end
265 end
266 @estimated_average = average
266 @estimated_average = average
267 end
267 end
268 @estimated_average
268 @estimated_average
269 end
269 end
270
270
271 # Returns the total progress of open or closed issues. The returned percentage takes into account
271 # Returns the total progress of open or closed issues. The returned percentage takes into account
272 # the amount of estimated time set for this version.
272 # the amount of estimated time set for this version.
273 #
273 #
274 # Examples:
274 # Examples:
275 # issues_progress(true) => returns the progress percentage for open issues.
275 # issues_progress(true) => returns the progress percentage for open issues.
276 # issues_progress(false) => returns the progress percentage for closed issues.
276 # issues_progress(false) => returns the progress percentage for closed issues.
277 def issues_progress(open)
277 def issues_progress(open)
278 @issues_progress ||= {}
278 @issues_progress ||= {}
279 @issues_progress[open] ||= begin
279 @issues_progress[open] ||= begin
280 progress = 0
280 progress = 0
281 if issues_count > 0
281 if issues_count > 0
282 ratio = open ? 'done_ratio' : 100
282 ratio = open ? 'done_ratio' : 100
283
283
284 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
284 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
285 progress = done / (estimated_average * issues_count)
285 progress = done / (estimated_average * issues_count)
286 end
286 end
287 progress
287 progress
288 end
288 end
289 end
289 end
290 end
290 end
General Comments 0
You need to be logged in to leave comments. Login now