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