##// END OF EJS Templates
Merged r3668 from trunk....
Jean-Philippe Lang -
r3583:4a295e723e37
parent child
Show More
@@ -1,76 +1,78
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 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 VersionsController < ApplicationController
19 19 menu_item :roadmap
20 20 before_filter :find_version, :except => :close_completed
21 21 before_filter :find_project, :only => :close_completed
22 22 before_filter :authorize
23 23
24 24 helper :custom_fields
25 25 helper :projects
26 26
27 27 def show
28 28 end
29 29
30 30 def edit
31 31 if request.post? && params[:version]
32 32 attributes = params[:version].dup
33 33 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
34 34 if @version.update_attributes(attributes)
35 35 flash[:notice] = l(:notice_successful_update)
36 36 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
37 37 end
38 38 end
39 39 end
40 40
41 41 def close_completed
42 42 if request.post?
43 43 @project.close_completed_versions
44 44 end
45 45 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
46 46 end
47 47
48 48 def destroy
49 @version.destroy
50 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
51 rescue
52 flash[:error] = l(:notice_unable_delete_version)
53 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
49 if @version.fixed_issues.empty?
50 @version.destroy
51 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
52 else
53 flash[:error] = l(:notice_unable_delete_version)
54 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
55 end
54 56 end
55 57
56 58 def status_by
57 59 respond_to do |format|
58 60 format.html { render :action => 'show' }
59 61 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
60 62 end
61 63 end
62 64
63 65 private
64 66 def find_version
65 67 @version = Version.find(params[:id])
66 68 @project = @version.project
67 69 rescue ActiveRecord::RecordNotFound
68 70 render_404
69 71 end
70 72
71 73 def find_project
72 74 @project = Project.find(params[:project_id])
73 75 rescue ActiveRecord::RecordNotFound
74 76 render_404
75 77 end
76 78 end
@@ -1,209 +1,205
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 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 before_destroy :check_integrity
20 19 after_update :update_issues_from_sharing_change
21 20 belongs_to :project
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
23 22 acts_as_customizable
24 23 acts_as_attachable :view_permission => :view_files,
25 24 :delete_permission => :manage_files
26 25
27 26 VERSION_STATUSES = %w(open locked closed)
28 27 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
29 28
30 29 validates_presence_of :name
31 30 validates_uniqueness_of :name, :scope => [:project_id]
32 31 validates_length_of :name, :maximum => 60
33 32 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
34 33 validates_inclusion_of :status, :in => VERSION_STATUSES
35 34 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
36 35
37 36 named_scope :open, :conditions => {:status => 'open'}
38 37 named_scope :visible, lambda {|*args| { :include => :project,
39 38 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
40 39
41 40 # Returns true if +user+ or current user is allowed to view the version
42 41 def visible?(user=User.current)
43 42 user.allowed_to?(:view_issues, self.project)
44 43 end
45 44
46 45 def start_date
47 46 effective_date
48 47 end
49 48
50 49 def due_date
51 50 effective_date
52 51 end
53 52
54 53 # Returns the total estimated time for this version
55 54 def estimated_hours
56 55 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
57 56 end
58 57
59 58 # Returns the total reported time for this version
60 59 def spent_hours
61 60 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
62 61 end
63 62
64 63 def closed?
65 64 status == 'closed'
66 65 end
67 66
68 67 def open?
69 68 status == 'open'
70 69 end
71 70
72 71 # Returns true if the version is completed: due date reached and no open issues
73 72 def completed?
74 73 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
75 74 end
76 75
77 76 # Returns the completion percentage of this version based on the amount of open/closed issues
78 77 # and the time spent on the open issues.
79 78 def completed_pourcent
80 79 if issues_count == 0
81 80 0
82 81 elsif open_issues_count == 0
83 82 100
84 83 else
85 84 issues_progress(false) + issues_progress(true)
86 85 end
87 86 end
88 87
89 88 # Returns the percentage of issues that have been marked as 'closed'.
90 89 def closed_pourcent
91 90 if issues_count == 0
92 91 0
93 92 else
94 93 issues_progress(false)
95 94 end
96 95 end
97 96
98 97 # Returns true if the version is overdue: due date reached and some open issues
99 98 def overdue?
100 99 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
101 100 end
102 101
103 102 # Returns assigned issues count
104 103 def issues_count
105 104 @issue_count ||= fixed_issues.count
106 105 end
107 106
108 107 # Returns the total amount of open issues for this version.
109 108 def open_issues_count
110 109 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
111 110 end
112 111
113 112 # Returns the total amount of closed issues for this version.
114 113 def closed_issues_count
115 114 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
116 115 end
117 116
118 117 def wiki_page
119 118 if project.wiki && !wiki_page_title.blank?
120 119 @wiki_page ||= project.wiki.find_page(wiki_page_title)
121 120 end
122 121 @wiki_page
123 122 end
124 123
125 124 def to_s; name end
126 125
127 126 # Versions are sorted by effective_date and name
128 127 # Those with no effective_date are at the end, sorted by name
129 128 def <=>(version)
130 129 if self.effective_date
131 130 version.effective_date ? (self.effective_date == version.effective_date ? self.name <=> version.name : self.effective_date <=> version.effective_date) : -1
132 131 else
133 132 version.effective_date ? 1 : (self.name <=> version.name)
134 133 end
135 134 end
136 135
137 136 # Returns the sharings that +user+ can set the version to
138 137 def allowed_sharings(user = User.current)
139 138 VERSION_SHARINGS.select do |s|
140 139 if sharing == s
141 140 true
142 141 else
143 142 case s
144 143 when 'system'
145 144 # Only admin users can set a systemwide sharing
146 145 user.admin?
147 146 when 'hierarchy', 'tree'
148 147 # Only users allowed to manage versions of the root project can
149 148 # set sharing to hierarchy or tree
150 149 project.nil? || user.allowed_to?(:manage_versions, project.root)
151 150 else
152 151 true
153 152 end
154 153 end
155 154 end
156 155 end
157 156
158 private
159 def check_integrity
160 raise "Can't delete version" if self.fixed_issues.find(:first)
161 end
157 private
162 158
163 159 # Update the issue's fixed versions. Used if a version's sharing changes.
164 160 def update_issues_from_sharing_change
165 161 if sharing_changed?
166 162 if VERSION_SHARINGS.index(sharing_was).nil? ||
167 163 VERSION_SHARINGS.index(sharing).nil? ||
168 164 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
169 165 Issue.update_versions_from_sharing_change self
170 166 end
171 167 end
172 168 end
173 169
174 170 # Returns the average estimated time of assigned issues
175 171 # or 1 if no issue has an estimated time
176 172 # Used to weigth unestimated issues in progress calculation
177 173 def estimated_average
178 174 if @estimated_average.nil?
179 175 average = fixed_issues.average(:estimated_hours).to_f
180 176 if average == 0
181 177 average = 1
182 178 end
183 179 @estimated_average = average
184 180 end
185 181 @estimated_average
186 182 end
187 183
188 184 # Returns the total progress of open or closed issues. The returned percentage takes into account
189 185 # the amount of estimated time set for this version.
190 186 #
191 187 # Examples:
192 188 # issues_progress(true) => returns the progress percentage for open issues.
193 189 # issues_progress(false) => returns the progress percentage for closed issues.
194 190 def issues_progress(open)
195 191 @issues_progress ||= {}
196 192 @issues_progress[open] ||= begin
197 193 progress = 0
198 194 if issues_count > 0
199 195 ratio = open ? 'done_ratio' : 100
200 196
201 197 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
202 198 :include => :status,
203 199 :conditions => ["is_closed = ?", !open]).to_f
204 200 progress = done / (estimated_average * issues_count)
205 201 end
206 202 progress
207 203 end
208 204 end
209 205 end
General Comments 0
You need to be logged in to leave comments. Login now