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