##// END OF EJS Templates
Closed versions should be marked as completed (#21433)....
Jean-Philippe Lang -
r14637:5e5506587e95
parent child
Show More
@@ -1,182 +1,182
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 menu_item :roadmap
19 menu_item :roadmap
20 model_object Version
20 model_object Version
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
23 before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
23 before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
24 before_filter :authorize
24 before_filter :authorize
25
25
26 accept_api_auth :index, :show, :create, :update, :destroy
26 accept_api_auth :index, :show, :create, :update, :destroy
27
27
28 helper :custom_fields
28 helper :custom_fields
29 helper :projects
29 helper :projects
30
30
31 def index
31 def index
32 respond_to do |format|
32 respond_to do |format|
33 format.html {
33 format.html {
34 @trackers = @project.trackers.sorted.to_a
34 @trackers = @project.trackers.sorted.to_a
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
38
38
39 @versions = @project.shared_versions || []
39 @versions = @project.shared_versions || []
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
41 @versions = @versions.uniq.sort
41 @versions = @versions.uniq.sort
42 unless params[:completed]
42 unless params[:completed]
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
43 @completed_versions = @versions.select(&:completed?)
44 @versions -= @completed_versions
44 @versions -= @completed_versions
45 end
45 end
46
46
47 @issues_by_version = {}
47 @issues_by_version = {}
48 if @selected_tracker_ids.any? && @versions.any?
48 if @selected_tracker_ids.any? && @versions.any?
49 issues = Issue.visible.
49 issues = Issue.visible.
50 includes(:project, :tracker).
50 includes(:project, :tracker).
51 preload(:status, :priority, :fixed_version).
51 preload(:status, :priority, :fixed_version).
52 where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
52 where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
53 order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
53 order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
54 @issues_by_version = issues.group_by(&:fixed_version)
54 @issues_by_version = issues.group_by(&:fixed_version)
55 end
55 end
56 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
56 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
57 }
57 }
58 format.api {
58 format.api {
59 @versions = @project.shared_versions.to_a
59 @versions = @project.shared_versions.to_a
60 }
60 }
61 end
61 end
62 end
62 end
63
63
64 def show
64 def show
65 respond_to do |format|
65 respond_to do |format|
66 format.html {
66 format.html {
67 @issues = @version.fixed_issues.visible.
67 @issues = @version.fixed_issues.visible.
68 includes(:status, :tracker, :priority).
68 includes(:status, :tracker, :priority).
69 reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
69 reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
70 to_a
70 to_a
71 }
71 }
72 format.api
72 format.api
73 end
73 end
74 end
74 end
75
75
76 def new
76 def new
77 @version = @project.versions.build
77 @version = @project.versions.build
78 @version.safe_attributes = params[:version]
78 @version.safe_attributes = params[:version]
79
79
80 respond_to do |format|
80 respond_to do |format|
81 format.html
81 format.html
82 format.js
82 format.js
83 end
83 end
84 end
84 end
85
85
86 def create
86 def create
87 @version = @project.versions.build
87 @version = @project.versions.build
88 if params[:version]
88 if params[:version]
89 attributes = params[:version].dup
89 attributes = params[:version].dup
90 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
90 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
91 @version.safe_attributes = attributes
91 @version.safe_attributes = attributes
92 end
92 end
93
93
94 if request.post?
94 if request.post?
95 if @version.save
95 if @version.save
96 respond_to do |format|
96 respond_to do |format|
97 format.html do
97 format.html do
98 flash[:notice] = l(:notice_successful_create)
98 flash[:notice] = l(:notice_successful_create)
99 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
99 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
100 end
100 end
101 format.js
101 format.js
102 format.api do
102 format.api do
103 render :action => 'show', :status => :created, :location => version_url(@version)
103 render :action => 'show', :status => :created, :location => version_url(@version)
104 end
104 end
105 end
105 end
106 else
106 else
107 respond_to do |format|
107 respond_to do |format|
108 format.html { render :action => 'new' }
108 format.html { render :action => 'new' }
109 format.js { render :action => 'new' }
109 format.js { render :action => 'new' }
110 format.api { render_validation_errors(@version) }
110 format.api { render_validation_errors(@version) }
111 end
111 end
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def edit
116 def edit
117 end
117 end
118
118
119 def update
119 def update
120 if params[:version]
120 if params[:version]
121 attributes = params[:version].dup
121 attributes = params[:version].dup
122 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
122 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
123 @version.safe_attributes = attributes
123 @version.safe_attributes = attributes
124 if @version.save
124 if @version.save
125 respond_to do |format|
125 respond_to do |format|
126 format.html {
126 format.html {
127 flash[:notice] = l(:notice_successful_update)
127 flash[:notice] = l(:notice_successful_update)
128 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
128 redirect_back_or_default settings_project_path(@project, :tab => 'versions')
129 }
129 }
130 format.api { render_api_ok }
130 format.api { render_api_ok }
131 end
131 end
132 else
132 else
133 respond_to do |format|
133 respond_to do |format|
134 format.html { render :action => 'edit' }
134 format.html { render :action => 'edit' }
135 format.api { render_validation_errors(@version) }
135 format.api { render_validation_errors(@version) }
136 end
136 end
137 end
137 end
138 end
138 end
139 end
139 end
140
140
141 def close_completed
141 def close_completed
142 if request.put?
142 if request.put?
143 @project.close_completed_versions
143 @project.close_completed_versions
144 end
144 end
145 redirect_to settings_project_path(@project, :tab => 'versions')
145 redirect_to settings_project_path(@project, :tab => 'versions')
146 end
146 end
147
147
148 def destroy
148 def destroy
149 if @version.deletable?
149 if @version.deletable?
150 @version.destroy
150 @version.destroy
151 respond_to do |format|
151 respond_to do |format|
152 format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') }
152 format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') }
153 format.api { render_api_ok }
153 format.api { render_api_ok }
154 end
154 end
155 else
155 else
156 respond_to do |format|
156 respond_to do |format|
157 format.html {
157 format.html {
158 flash[:error] = l(:notice_unable_delete_version)
158 flash[:error] = l(:notice_unable_delete_version)
159 redirect_to settings_project_path(@project, :tab => 'versions')
159 redirect_to settings_project_path(@project, :tab => 'versions')
160 }
160 }
161 format.api { head :unprocessable_entity }
161 format.api { head :unprocessable_entity }
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 def status_by
166 def status_by
167 respond_to do |format|
167 respond_to do |format|
168 format.html { render :action => 'show' }
168 format.html { render :action => 'show' }
169 format.js
169 format.js
170 end
170 end
171 end
171 end
172
172
173 private
173 private
174
174
175 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
175 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
176 if ids = params[:tracker_ids]
176 if ids = params[:tracker_ids]
177 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
177 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
178 else
178 else
179 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
179 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
180 end
180 end
181 end
181 end
182 end
182 end
@@ -1,307 +1,307
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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
20
21 after_update :update_issues_from_sharing_change
21 after_update :update_issues_from_sharing_change
22 before_destroy :nullify_projects_default_version
22 before_destroy :nullify_projects_default_version
23
23
24 belongs_to :project
24 belongs_to :project
25 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
25 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
26 acts_as_customizable
26 acts_as_customizable
27 acts_as_attachable :view_permission => :view_files,
27 acts_as_attachable :view_permission => :view_files,
28 :edit_permission => :manage_files,
28 :edit_permission => :manage_files,
29 :delete_permission => :manage_files
29 :delete_permission => :manage_files
30
30
31 VERSION_STATUSES = %w(open locked closed)
31 VERSION_STATUSES = %w(open locked closed)
32 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
32 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
33
33
34 validates_presence_of :name
34 validates_presence_of :name
35 validates_uniqueness_of :name, :scope => [:project_id]
35 validates_uniqueness_of :name, :scope => [:project_id]
36 validates_length_of :name, :maximum => 60
36 validates_length_of :name, :maximum => 60
37 validates_length_of :description, :maximum => 255
37 validates_length_of :description, :maximum => 255
38 validates :effective_date, :date => true
38 validates :effective_date, :date => true
39 validates_inclusion_of :status, :in => VERSION_STATUSES
39 validates_inclusion_of :status, :in => VERSION_STATUSES
40 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
40 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
41 attr_protected :id
41 attr_protected :id
42
42
43 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
43 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
44 scope :open, lambda { where(:status => 'open') }
44 scope :open, lambda { where(:status => 'open') }
45 scope :visible, lambda {|*args|
45 scope :visible, lambda {|*args|
46 joins(:project).
46 joins(:project).
47 where(Project.allowed_to_condition(args.first || User.current, :view_issues))
47 where(Project.allowed_to_condition(args.first || User.current, :view_issues))
48 }
48 }
49
49
50 safe_attributes 'name',
50 safe_attributes 'name',
51 'description',
51 'description',
52 'effective_date',
52 'effective_date',
53 'due_date',
53 'due_date',
54 'wiki_page_title',
54 'wiki_page_title',
55 'status',
55 'status',
56 'sharing',
56 'sharing',
57 'custom_field_values',
57 'custom_field_values',
58 'custom_fields'
58 'custom_fields'
59
59
60 # Returns true if +user+ or current user is allowed to view the version
60 # Returns true if +user+ or current user is allowed to view the version
61 def visible?(user=User.current)
61 def visible?(user=User.current)
62 user.allowed_to?(:view_issues, self.project)
62 user.allowed_to?(:view_issues, self.project)
63 end
63 end
64
64
65 # Version files have same visibility as project files
65 # Version files have same visibility as project files
66 def attachments_visible?(*args)
66 def attachments_visible?(*args)
67 project.present? && project.attachments_visible?(*args)
67 project.present? && project.attachments_visible?(*args)
68 end
68 end
69
69
70 def attachments_deletable?(usr=User.current)
70 def attachments_deletable?(usr=User.current)
71 project.present? && project.attachments_deletable?(usr)
71 project.present? && project.attachments_deletable?(usr)
72 end
72 end
73
73
74 def start_date
74 def start_date
75 @start_date ||= fixed_issues.minimum('start_date')
75 @start_date ||= fixed_issues.minimum('start_date')
76 end
76 end
77
77
78 def due_date
78 def due_date
79 effective_date
79 effective_date
80 end
80 end
81
81
82 def due_date=(arg)
82 def due_date=(arg)
83 self.effective_date=(arg)
83 self.effective_date=(arg)
84 end
84 end
85
85
86 # Returns the total estimated time for this version
86 # Returns the total estimated time for this version
87 # (sum of leaves estimated_hours)
87 # (sum of leaves estimated_hours)
88 def estimated_hours
88 def estimated_hours
89 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
89 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
90 end
90 end
91
91
92 # Returns the total reported time for this version
92 # Returns the total reported time for this version
93 def spent_hours
93 def spent_hours
94 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
94 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
95 end
95 end
96
96
97 def closed?
97 def closed?
98 status == 'closed'
98 status == 'closed'
99 end
99 end
100
100
101 def open?
101 def open?
102 status == 'open'
102 status == 'open'
103 end
103 end
104
104
105 # Returns true if the version is completed: due date reached and no open issues
105 # Returns true if the version is completed: closed or due date reached and no open issues
106 def completed?
106 def completed?
107 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
107 closed? || (effective_date && (effective_date < Date.today) && (open_issues_count == 0))
108 end
108 end
109
109
110 def behind_schedule?
110 def behind_schedule?
111 if completed_percent == 100
111 if completed_percent == 100
112 return false
112 return false
113 elsif due_date && start_date
113 elsif due_date && start_date
114 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
114 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
115 return done_date <= Date.today
115 return done_date <= Date.today
116 else
116 else
117 false # No issues so it's not late
117 false # No issues so it's not late
118 end
118 end
119 end
119 end
120
120
121 # Returns the completion percentage of this version based on the amount of open/closed issues
121 # Returns the completion percentage of this version based on the amount of open/closed issues
122 # and the time spent on the open issues.
122 # and the time spent on the open issues.
123 def completed_percent
123 def completed_percent
124 if issues_count == 0
124 if issues_count == 0
125 0
125 0
126 elsif open_issues_count == 0
126 elsif open_issues_count == 0
127 100
127 100
128 else
128 else
129 issues_progress(false) + issues_progress(true)
129 issues_progress(false) + issues_progress(true)
130 end
130 end
131 end
131 end
132
132
133 # Returns the percentage of issues that have been marked as 'closed'.
133 # Returns the percentage of issues that have been marked as 'closed'.
134 def closed_percent
134 def closed_percent
135 if issues_count == 0
135 if issues_count == 0
136 0
136 0
137 else
137 else
138 issues_progress(false)
138 issues_progress(false)
139 end
139 end
140 end
140 end
141
141
142 # Returns true if the version is overdue: due date reached and some open issues
142 # Returns true if the version is overdue: due date reached and some open issues
143 def overdue?
143 def overdue?
144 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
144 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 end
145 end
146
146
147 # Returns assigned issues count
147 # Returns assigned issues count
148 def issues_count
148 def issues_count
149 load_issue_counts
149 load_issue_counts
150 @issue_count
150 @issue_count
151 end
151 end
152
152
153 # Returns the total amount of open issues for this version.
153 # Returns the total amount of open issues for this version.
154 def open_issues_count
154 def open_issues_count
155 load_issue_counts
155 load_issue_counts
156 @open_issues_count
156 @open_issues_count
157 end
157 end
158
158
159 # Returns the total amount of closed issues for this version.
159 # Returns the total amount of closed issues for this version.
160 def closed_issues_count
160 def closed_issues_count
161 load_issue_counts
161 load_issue_counts
162 @closed_issues_count
162 @closed_issues_count
163 end
163 end
164
164
165 def wiki_page
165 def wiki_page
166 if project.wiki && !wiki_page_title.blank?
166 if project.wiki && !wiki_page_title.blank?
167 @wiki_page ||= project.wiki.find_page(wiki_page_title)
167 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 end
168 end
169 @wiki_page
169 @wiki_page
170 end
170 end
171
171
172 def to_s; name end
172 def to_s; name end
173
173
174 def to_s_with_project
174 def to_s_with_project
175 "#{project} - #{name}"
175 "#{project} - #{name}"
176 end
176 end
177
177
178 # Versions are sorted by effective_date and name
178 # Versions are sorted by effective_date and name
179 # Those with no effective_date are at the end, sorted by name
179 # Those with no effective_date are at the end, sorted by name
180 def <=>(version)
180 def <=>(version)
181 if self.effective_date
181 if self.effective_date
182 if version.effective_date
182 if version.effective_date
183 if self.effective_date == version.effective_date
183 if self.effective_date == version.effective_date
184 name == version.name ? id <=> version.id : name <=> version.name
184 name == version.name ? id <=> version.id : name <=> version.name
185 else
185 else
186 self.effective_date <=> version.effective_date
186 self.effective_date <=> version.effective_date
187 end
187 end
188 else
188 else
189 -1
189 -1
190 end
190 end
191 else
191 else
192 if version.effective_date
192 if version.effective_date
193 1
193 1
194 else
194 else
195 name == version.name ? id <=> version.id : name <=> version.name
195 name == version.name ? id <=> version.id : name <=> version.name
196 end
196 end
197 end
197 end
198 end
198 end
199
199
200 def self.fields_for_order_statement(table=nil)
200 def self.fields_for_order_statement(table=nil)
201 table ||= table_name
201 table ||= table_name
202 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
202 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 end
203 end
204
204
205 scope :sorted, lambda { order(fields_for_order_statement) }
205 scope :sorted, lambda { order(fields_for_order_statement) }
206
206
207 # Returns the sharings that +user+ can set the version to
207 # Returns the sharings that +user+ can set the version to
208 def allowed_sharings(user = User.current)
208 def allowed_sharings(user = User.current)
209 VERSION_SHARINGS.select do |s|
209 VERSION_SHARINGS.select do |s|
210 if sharing == s
210 if sharing == s
211 true
211 true
212 else
212 else
213 case s
213 case s
214 when 'system'
214 when 'system'
215 # Only admin users can set a systemwide sharing
215 # Only admin users can set a systemwide sharing
216 user.admin?
216 user.admin?
217 when 'hierarchy', 'tree'
217 when 'hierarchy', 'tree'
218 # Only users allowed to manage versions of the root project can
218 # Only users allowed to manage versions of the root project can
219 # set sharing to hierarchy or tree
219 # set sharing to hierarchy or tree
220 project.nil? || user.allowed_to?(:manage_versions, project.root)
220 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 else
221 else
222 true
222 true
223 end
223 end
224 end
224 end
225 end
225 end
226 end
226 end
227
227
228 # Returns true if the version is shared, otherwise false
228 # Returns true if the version is shared, otherwise false
229 def shared?
229 def shared?
230 sharing != 'none'
230 sharing != 'none'
231 end
231 end
232
232
233 def deletable?
233 def deletable?
234 fixed_issues.empty? && !referenced_by_a_custom_field?
234 fixed_issues.empty? && !referenced_by_a_custom_field?
235 end
235 end
236
236
237 private
237 private
238
238
239 def load_issue_counts
239 def load_issue_counts
240 unless @issue_count
240 unless @issue_count
241 @open_issues_count = 0
241 @open_issues_count = 0
242 @closed_issues_count = 0
242 @closed_issues_count = 0
243 fixed_issues.group(:status).count.each do |status, count|
243 fixed_issues.group(:status).count.each do |status, count|
244 if status.is_closed?
244 if status.is_closed?
245 @closed_issues_count += count
245 @closed_issues_count += count
246 else
246 else
247 @open_issues_count += count
247 @open_issues_count += count
248 end
248 end
249 end
249 end
250 @issue_count = @open_issues_count + @closed_issues_count
250 @issue_count = @open_issues_count + @closed_issues_count
251 end
251 end
252 end
252 end
253
253
254 # Update the issue's fixed versions. Used if a version's sharing changes.
254 # Update the issue's fixed versions. Used if a version's sharing changes.
255 def update_issues_from_sharing_change
255 def update_issues_from_sharing_change
256 if sharing_changed?
256 if sharing_changed?
257 if VERSION_SHARINGS.index(sharing_was).nil? ||
257 if VERSION_SHARINGS.index(sharing_was).nil? ||
258 VERSION_SHARINGS.index(sharing).nil? ||
258 VERSION_SHARINGS.index(sharing).nil? ||
259 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
259 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
260 Issue.update_versions_from_sharing_change self
260 Issue.update_versions_from_sharing_change self
261 end
261 end
262 end
262 end
263 end
263 end
264
264
265 # Returns the average estimated time of assigned issues
265 # Returns the average estimated time of assigned issues
266 # or 1 if no issue has an estimated time
266 # or 1 if no issue has an estimated time
267 # Used to weight unestimated issues in progress calculation
267 # Used to weight unestimated issues in progress calculation
268 def estimated_average
268 def estimated_average
269 if @estimated_average.nil?
269 if @estimated_average.nil?
270 average = fixed_issues.average(:estimated_hours).to_f
270 average = fixed_issues.average(:estimated_hours).to_f
271 if average == 0
271 if average == 0
272 average = 1
272 average = 1
273 end
273 end
274 @estimated_average = average
274 @estimated_average = average
275 end
275 end
276 @estimated_average
276 @estimated_average
277 end
277 end
278
278
279 # Returns the total progress of open or closed issues. The returned percentage takes into account
279 # Returns the total progress of open or closed issues. The returned percentage takes into account
280 # the amount of estimated time set for this version.
280 # the amount of estimated time set for this version.
281 #
281 #
282 # Examples:
282 # Examples:
283 # issues_progress(true) => returns the progress percentage for open issues.
283 # issues_progress(true) => returns the progress percentage for open issues.
284 # issues_progress(false) => returns the progress percentage for closed issues.
284 # issues_progress(false) => returns the progress percentage for closed issues.
285 def issues_progress(open)
285 def issues_progress(open)
286 @issues_progress ||= {}
286 @issues_progress ||= {}
287 @issues_progress[open] ||= begin
287 @issues_progress[open] ||= begin
288 progress = 0
288 progress = 0
289 if issues_count > 0
289 if issues_count > 0
290 ratio = open ? 'done_ratio' : 100
290 ratio = open ? 'done_ratio' : 100
291
291
292 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
292 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
293 progress = done / (estimated_average * issues_count)
293 progress = done / (estimated_average * issues_count)
294 end
294 end
295 progress
295 progress
296 end
296 end
297 end
297 end
298
298
299 def referenced_by_a_custom_field?
299 def referenced_by_a_custom_field?
300 CustomValue.joins(:custom_field).
300 CustomValue.joins(:custom_field).
301 where(:value => id.to_s, :custom_fields => {:field_format => 'version'}).any?
301 where(:value => id.to_s, :custom_fields => {:field_format => 'version'}).any?
302 end
302 end
303
303
304 def nullify_projects_default_version
304 def nullify_projects_default_version
305 Project.where(:default_version_id => id).update_all(:default_version_id => nil)
305 Project.where(:default_version_id => id).update_all(:default_version_id => nil)
306 end
306 end
307 end
307 end
@@ -1,272 +1,277
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class VersionTest < ActiveSupport::TestCase
20 class VersionTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :issues, :issue_statuses, :trackers,
21 fixtures :projects, :users, :issues, :issue_statuses, :trackers,
22 :enumerations, :versions, :projects_trackers
22 :enumerations, :versions, :projects_trackers
23
23
24 def test_create
24 def test_create
25 v = Version.new(:project => Project.find(1), :name => '1.1',
25 v = Version.new(:project => Project.find(1), :name => '1.1',
26 :effective_date => '2011-03-25')
26 :effective_date => '2011-03-25')
27 assert v.save
27 assert v.save
28 assert_equal 'open', v.status
28 assert_equal 'open', v.status
29 assert_equal 'none', v.sharing
29 assert_equal 'none', v.sharing
30 end
30 end
31
31
32 def test_invalid_effective_date_validation
32 def test_invalid_effective_date_validation
33 v = Version.new(:project => Project.find(1), :name => '1.1',
33 v = Version.new(:project => Project.find(1), :name => '1.1',
34 :effective_date => '99999-01-01')
34 :effective_date => '99999-01-01')
35 assert !v.valid?
35 assert !v.valid?
36 v.effective_date = '2012-11-33'
36 v.effective_date = '2012-11-33'
37 assert !v.valid?
37 assert !v.valid?
38 v.effective_date = '2012-31-11'
38 v.effective_date = '2012-31-11'
39 assert !v.valid?
39 assert !v.valid?
40 v.effective_date = '-2012-31-11'
40 v.effective_date = '-2012-31-11'
41 assert !v.valid?
41 assert !v.valid?
42 v.effective_date = 'ABC'
42 v.effective_date = 'ABC'
43 assert !v.valid?
43 assert !v.valid?
44 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
44 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
45 v.errors[:effective_date]
45 v.errors[:effective_date]
46 end
46 end
47
47
48 def test_progress_should_be_0_with_no_assigned_issues
48 def test_progress_should_be_0_with_no_assigned_issues
49 project = Project.find(1)
49 project = Project.find(1)
50 v = Version.create!(:project => project, :name => 'Progress')
50 v = Version.create!(:project => project, :name => 'Progress')
51 assert_equal 0, v.completed_percent
51 assert_equal 0, v.completed_percent
52 assert_equal 0, v.closed_percent
52 assert_equal 0, v.closed_percent
53 end
53 end
54
54
55 def test_progress_should_be_0_with_unbegun_assigned_issues
55 def test_progress_should_be_0_with_unbegun_assigned_issues
56 project = Project.find(1)
56 project = Project.find(1)
57 v = Version.create!(:project => project, :name => 'Progress')
57 v = Version.create!(:project => project, :name => 'Progress')
58 add_issue(v)
58 add_issue(v)
59 add_issue(v, :done_ratio => 0)
59 add_issue(v, :done_ratio => 0)
60 assert_progress_equal 0, v.completed_percent
60 assert_progress_equal 0, v.completed_percent
61 assert_progress_equal 0, v.closed_percent
61 assert_progress_equal 0, v.closed_percent
62 end
62 end
63
63
64 def test_progress_should_be_100_with_closed_assigned_issues
64 def test_progress_should_be_100_with_closed_assigned_issues
65 project = Project.find(1)
65 project = Project.find(1)
66 status = IssueStatus.where(:is_closed => true).first
66 status = IssueStatus.where(:is_closed => true).first
67 v = Version.create!(:project => project, :name => 'Progress')
67 v = Version.create!(:project => project, :name => 'Progress')
68 add_issue(v, :status => status)
68 add_issue(v, :status => status)
69 add_issue(v, :status => status, :done_ratio => 20)
69 add_issue(v, :status => status, :done_ratio => 20)
70 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
70 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
71 add_issue(v, :status => status, :estimated_hours => 15)
71 add_issue(v, :status => status, :estimated_hours => 15)
72 assert_progress_equal 100.0, v.completed_percent
72 assert_progress_equal 100.0, v.completed_percent
73 assert_progress_equal 100.0, v.closed_percent
73 assert_progress_equal 100.0, v.closed_percent
74 end
74 end
75
75
76 def test_progress_should_consider_done_ratio_of_open_assigned_issues
76 def test_progress_should_consider_done_ratio_of_open_assigned_issues
77 project = Project.find(1)
77 project = Project.find(1)
78 v = Version.create!(:project => project, :name => 'Progress')
78 v = Version.create!(:project => project, :name => 'Progress')
79 add_issue(v)
79 add_issue(v)
80 add_issue(v, :done_ratio => 20)
80 add_issue(v, :done_ratio => 20)
81 add_issue(v, :done_ratio => 70)
81 add_issue(v, :done_ratio => 70)
82 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent
82 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent
83 assert_progress_equal 0, v.closed_percent
83 assert_progress_equal 0, v.closed_percent
84 end
84 end
85
85
86 def test_progress_should_consider_closed_issues_as_completed
86 def test_progress_should_consider_closed_issues_as_completed
87 project = Project.find(1)
87 project = Project.find(1)
88 v = Version.create!(:project => project, :name => 'Progress')
88 v = Version.create!(:project => project, :name => 'Progress')
89 add_issue(v)
89 add_issue(v)
90 add_issue(v, :done_ratio => 20)
90 add_issue(v, :done_ratio => 20)
91 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
91 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
92 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent
92 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent
93 assert_progress_equal (100.0)/3, v.closed_percent
93 assert_progress_equal (100.0)/3, v.closed_percent
94 end
94 end
95
95
96 def test_progress_should_consider_estimated_hours_to_weight_issues
96 def test_progress_should_consider_estimated_hours_to_weight_issues
97 project = Project.find(1)
97 project = Project.find(1)
98 v = Version.create!(:project => project, :name => 'Progress')
98 v = Version.create!(:project => project, :name => 'Progress')
99 add_issue(v, :estimated_hours => 10)
99 add_issue(v, :estimated_hours => 10)
100 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
100 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
101 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
101 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
102 add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first)
102 add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first)
103 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent
103 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent
104 assert_progress_equal 25.0/95.0*100, v.closed_percent
104 assert_progress_equal 25.0/95.0*100, v.closed_percent
105 end
105 end
106
106
107 def test_progress_should_consider_average_estimated_hours_to_weight_unestimated_issues
107 def test_progress_should_consider_average_estimated_hours_to_weight_unestimated_issues
108 project = Project.find(1)
108 project = Project.find(1)
109 v = Version.create!(:project => project, :name => 'Progress')
109 v = Version.create!(:project => project, :name => 'Progress')
110 add_issue(v, :done_ratio => 20)
110 add_issue(v, :done_ratio => 20)
111 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
111 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
112 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
112 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
113 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
113 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
114 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent
114 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent
115 assert_progress_equal 25.0/100.0*100, v.closed_percent
115 assert_progress_equal 25.0/100.0*100, v.closed_percent
116 end
116 end
117
117
118 def test_should_sort_scheduled_then_unscheduled_versions
118 def test_should_sort_scheduled_then_unscheduled_versions
119 Version.delete_all
119 Version.delete_all
120 v4 = Version.create!(:project_id => 1, :name => 'v4')
120 v4 = Version.create!(:project_id => 1, :name => 'v4')
121 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
121 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
122 v2 = Version.create!(:project_id => 1, :name => 'v1')
122 v2 = Version.create!(:project_id => 1, :name => 'v1')
123 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
123 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
124 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
124 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
125
125
126 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
126 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
127 assert_equal [v5, v3, v1, v2, v4], Version.sorted.to_a
127 assert_equal [v5, v3, v1, v2, v4], Version.sorted.to_a
128 end
128 end
129
129
130 def test_should_sort_versions_with_same_date_by_name
130 def test_should_sort_versions_with_same_date_by_name
131 v1 = Version.new(:effective_date => '2014-12-03', :name => 'v2')
131 v1 = Version.new(:effective_date => '2014-12-03', :name => 'v2')
132 v2 = Version.new(:effective_date => '2014-12-03', :name => 'v1')
132 v2 = Version.new(:effective_date => '2014-12-03', :name => 'v1')
133 assert_equal [v2, v1], [v1, v2].sort
133 assert_equal [v2, v1], [v1, v2].sort
134 end
134 end
135
135
136 def test_completed_should_be_false_when_due_today
136 def test_completed_should_be_false_when_due_today
137 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
137 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
138 assert_equal false, version.completed?
138 assert_equal false, version.completed?
139 end
139 end
140
140
141 def test_completed_should_be_true_when_closed
142 version = Version.create!(:project_id => 1, :status => 'closed', :name => 'Closed')
143 assert_equal true, version.completed?
144 end
145
141 test "#behind_schedule? should be false if there are no issues assigned" do
146 test "#behind_schedule? should be false if there are no issues assigned" do
142 version = Version.generate!(:effective_date => Date.yesterday)
147 version = Version.generate!(:effective_date => Date.yesterday)
143 assert_equal false, version.behind_schedule?
148 assert_equal false, version.behind_schedule?
144 end
149 end
145
150
146 test "#behind_schedule? should be false if there is no effective_date" do
151 test "#behind_schedule? should be false if there is no effective_date" do
147 version = Version.generate!(:effective_date => nil)
152 version = Version.generate!(:effective_date => nil)
148 assert_equal false, version.behind_schedule?
153 assert_equal false, version.behind_schedule?
149 end
154 end
150
155
151 test "#behind_schedule? should be false if all of the issues are ahead of schedule" do
156 test "#behind_schedule? should be false if all of the issues are ahead of schedule" do
152 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
157 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
153 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
158 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
154 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
159 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
155 assert_equal 60, version.completed_percent
160 assert_equal 60, version.completed_percent
156 assert_equal false, version.behind_schedule?
161 assert_equal false, version.behind_schedule?
157 end
162 end
158
163
159 test "#behind_schedule? should be true if any of the issues are behind schedule" do
164 test "#behind_schedule? should be true if any of the issues are behind schedule" do
160 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
165 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
161 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
166 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
162 add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
167 add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
163 assert_equal 40, version.completed_percent
168 assert_equal 40, version.completed_percent
164 assert_equal true, version.behind_schedule?
169 assert_equal true, version.behind_schedule?
165 end
170 end
166
171
167 test "#behind_schedule? should be false if all of the issues are complete" do
172 test "#behind_schedule? should be false if all of the issues are complete" do
168 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
173 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
169 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
174 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
170 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
175 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
171 assert_equal 100, version.completed_percent
176 assert_equal 100, version.completed_percent
172 assert_equal false, version.behind_schedule?
177 assert_equal false, version.behind_schedule?
173 end
178 end
174
179
175 test "#estimated_hours should return 0 with no assigned issues" do
180 test "#estimated_hours should return 0 with no assigned issues" do
176 version = Version.generate!
181 version = Version.generate!
177 assert_equal 0, version.estimated_hours
182 assert_equal 0, version.estimated_hours
178 end
183 end
179
184
180 test "#estimated_hours should return 0 with no estimated hours" do
185 test "#estimated_hours should return 0 with no estimated hours" do
181 version = Version.create!(:project_id => 1, :name => 'test')
186 version = Version.create!(:project_id => 1, :name => 'test')
182 add_issue(version)
187 add_issue(version)
183 assert_equal 0, version.estimated_hours
188 assert_equal 0, version.estimated_hours
184 end
189 end
185
190
186 test "#estimated_hours should return return the sum of estimated hours" do
191 test "#estimated_hours should return return the sum of estimated hours" do
187 version = Version.create!(:project_id => 1, :name => 'test')
192 version = Version.create!(:project_id => 1, :name => 'test')
188 add_issue(version, :estimated_hours => 2.5)
193 add_issue(version, :estimated_hours => 2.5)
189 add_issue(version, :estimated_hours => 5)
194 add_issue(version, :estimated_hours => 5)
190 assert_equal 7.5, version.estimated_hours
195 assert_equal 7.5, version.estimated_hours
191 end
196 end
192
197
193 test "#estimated_hours should return the sum of leaves estimated hours" do
198 test "#estimated_hours should return the sum of leaves estimated hours" do
194 version = Version.create!(:project_id => 1, :name => 'test')
199 version = Version.create!(:project_id => 1, :name => 'test')
195 parent = add_issue(version)
200 parent = add_issue(version)
196 add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
201 add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
197 add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id)
202 add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id)
198 assert_equal 7.5, version.estimated_hours
203 assert_equal 7.5, version.estimated_hours
199 end
204 end
200
205
201 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
206 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
202 User.current = User.find(1) # Need the admin's permissions
207 User.current = User.find(1) # Need the admin's permissions
203
208
204 @version = Version.find(7)
209 @version = Version.find(7)
205 # Separate hierarchy
210 # Separate hierarchy
206 project_1_issue = Issue.find(1)
211 project_1_issue = Issue.find(1)
207 project_1_issue.fixed_version = @version
212 project_1_issue.fixed_version = @version
208 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
213 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
209
214
210 project_5_issue = Issue.find(6)
215 project_5_issue = Issue.find(6)
211 project_5_issue.fixed_version = @version
216 project_5_issue.fixed_version = @version
212 assert project_5_issue.save
217 assert project_5_issue.save
213
218
214 # Project
219 # Project
215 project_2_issue = Issue.find(4)
220 project_2_issue = Issue.find(4)
216 project_2_issue.fixed_version = @version
221 project_2_issue.fixed_version = @version
217 assert project_2_issue.save
222 assert project_2_issue.save
218
223
219 # Update the sharing
224 # Update the sharing
220 @version.sharing = 'none'
225 @version.sharing = 'none'
221 assert @version.save
226 assert @version.save
222
227
223 # Project 1 now out of the shared scope
228 # Project 1 now out of the shared scope
224 project_1_issue.reload
229 project_1_issue.reload
225 assert_equal nil, project_1_issue.fixed_version,
230 assert_equal nil, project_1_issue.fixed_version,
226 "Fixed version is still set after changing the Version's sharing"
231 "Fixed version is still set after changing the Version's sharing"
227
232
228 # Project 5 now out of the shared scope
233 # Project 5 now out of the shared scope
229 project_5_issue.reload
234 project_5_issue.reload
230 assert_equal nil, project_5_issue.fixed_version,
235 assert_equal nil, project_5_issue.fixed_version,
231 "Fixed version is still set after changing the Version's sharing"
236 "Fixed version is still set after changing the Version's sharing"
232
237
233 # Project 2 issue remains
238 # Project 2 issue remains
234 project_2_issue.reload
239 project_2_issue.reload
235 assert_equal @version, project_2_issue.fixed_version
240 assert_equal @version, project_2_issue.fixed_version
236 end
241 end
237
242
238 def test_deletable_should_return_true_when_not_referenced
243 def test_deletable_should_return_true_when_not_referenced
239 version = Version.generate!
244 version = Version.generate!
240
245
241 assert_equal true, version.deletable?
246 assert_equal true, version.deletable?
242 end
247 end
243
248
244 def test_deletable_should_return_false_when_referenced_by_an_issue
249 def test_deletable_should_return_false_when_referenced_by_an_issue
245 version = Version.generate!
250 version = Version.generate!
246 Issue.generate!(:fixed_version => version)
251 Issue.generate!(:fixed_version => version)
247
252
248 assert_equal false, version.deletable?
253 assert_equal false, version.deletable?
249 end
254 end
250
255
251 def test_deletable_should_return_false_when_referenced_by_a_custom_field
256 def test_deletable_should_return_false_when_referenced_by_a_custom_field
252 version = Version.generate!
257 version = Version.generate!
253 field = IssueCustomField.generate!(:field_format => 'version')
258 field = IssueCustomField.generate!(:field_format => 'version')
254 value = CustomValue.create!(:custom_field => field, :customized => Issue.first, :value => version.id)
259 value = CustomValue.create!(:custom_field => field, :customized => Issue.first, :value => version.id)
255
260
256 assert_equal false, version.deletable?
261 assert_equal false, version.deletable?
257 end
262 end
258
263
259 private
264 private
260
265
261 def add_issue(version, attributes={})
266 def add_issue(version, attributes={})
262 Issue.create!({:project => version.project,
267 Issue.create!({:project => version.project,
263 :fixed_version => version,
268 :fixed_version => version,
264 :subject => 'Test',
269 :subject => 'Test',
265 :author => User.first,
270 :author => User.first,
266 :tracker => version.project.trackers.first}.merge(attributes))
271 :tracker => version.project.trackers.first}.merge(attributes))
267 end
272 end
268
273
269 def assert_progress_equal(expected_float, actual_float, message="")
274 def assert_progress_equal(expected_float, actual_float, message="")
270 assert_in_delta(expected_float, actual_float, 0.000001, message="")
275 assert_in_delta(expected_float, actual_float, 0.000001, message="")
271 end
276 end
272 end
277 end
General Comments 0
You need to be logged in to leave comments. Login now