##// END OF EJS Templates
Code cleanup....
Jean-Philippe Lang -
r10660:9a270c46c0aa
parent child
Show More
@@ -1,48 +1,48
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 IssueCategory < ActiveRecord::Base
18 class IssueCategory < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 belongs_to :project
20 belongs_to :project
21 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
21 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
22 has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
22 has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
23
23
24 validates_presence_of :name
24 validates_presence_of :name
25 validates_uniqueness_of :name, :scope => [:project_id]
25 validates_uniqueness_of :name, :scope => [:project_id]
26 validates_length_of :name, :maximum => 30
26 validates_length_of :name, :maximum => 30
27
27
28 safe_attributes 'name', 'assigned_to_id'
28 safe_attributes 'name', 'assigned_to_id'
29
29
30 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
30 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
31
31
32 alias :destroy_without_reassign :destroy
32 alias :destroy_without_reassign :destroy
33
33
34 # Destroy the category
34 # Destroy the category
35 # If a category is specified, issues are reassigned to this category
35 # If a category is specified, issues are reassigned to this category
36 def destroy(reassign_to = nil)
36 def destroy(reassign_to = nil)
37 if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
37 if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
38 Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
38 Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
39 end
39 end
40 destroy_without_reassign
40 destroy_without_reassign
41 end
41 end
42
42
43 def <=>(category)
43 def <=>(category)
44 name <=> category.name
44 name <=> category.name
45 end
45 end
46
46
47 def to_s; name end
47 def to_s; name end
48 end
48 end
@@ -1,105 +1,105
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 IssueStatus < ActiveRecord::Base
18 class IssueStatus < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
20 has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
21 acts_as_list
21 acts_as_list
22
22
23 before_destroy :delete_workflow_rules
23 before_destroy :delete_workflow_rules
24 after_save :update_default
24 after_save :update_default
25
25
26 validates_presence_of :name
26 validates_presence_of :name
27 validates_uniqueness_of :name
27 validates_uniqueness_of :name
28 validates_length_of :name, :maximum => 30
28 validates_length_of :name, :maximum => 30
29 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
29 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
30
30
31 scope :sorted, order("#{table_name}.position ASC")
31 scope :sorted, order("#{table_name}.position ASC")
32 scope :named, lambda {|arg| where(["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip])}
32 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
33
33
34 def update_default
34 def update_default
35 IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?
35 IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?
36 end
36 end
37
37
38 # Returns the default status for new issues
38 # Returns the default status for new issues
39 def self.default
39 def self.default
40 where(:is_default => true).first
40 where(:is_default => true).first
41 end
41 end
42
42
43 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
43 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
44 def self.update_issue_done_ratios
44 def self.update_issue_done_ratios
45 if Issue.use_status_for_done_ratio?
45 if Issue.use_status_for_done_ratio?
46 IssueStatus.where("default_done_ratio >= 0").all.each do |status|
46 IssueStatus.where("default_done_ratio >= 0").all.each do |status|
47 Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id})
47 Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id})
48 end
48 end
49 end
49 end
50
50
51 return Issue.use_status_for_done_ratio?
51 return Issue.use_status_for_done_ratio?
52 end
52 end
53
53
54 # Returns an array of all statuses the given role can switch to
54 # Returns an array of all statuses the given role can switch to
55 # Uses association cache when called more than one time
55 # Uses association cache when called more than one time
56 def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
56 def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
57 if roles && tracker
57 if roles && tracker
58 role_ids = roles.collect(&:id)
58 role_ids = roles.collect(&:id)
59 transitions = workflows.select do |w|
59 transitions = workflows.select do |w|
60 role_ids.include?(w.role_id) &&
60 role_ids.include?(w.role_id) &&
61 w.tracker_id == tracker.id &&
61 w.tracker_id == tracker.id &&
62 ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee))
62 ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee))
63 end
63 end
64 transitions.map(&:new_status).compact.sort
64 transitions.map(&:new_status).compact.sort
65 else
65 else
66 []
66 []
67 end
67 end
68 end
68 end
69
69
70 # Same thing as above but uses a database query
70 # Same thing as above but uses a database query
71 # More efficient than the previous method if called just once
71 # More efficient than the previous method if called just once
72 def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
72 def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
73 if roles.present? && tracker
73 if roles.present? && tracker
74 conditions = "(author = :false AND assignee = :false)"
74 conditions = "(author = :false AND assignee = :false)"
75 conditions << " OR author = :true" if author
75 conditions << " OR author = :true" if author
76 conditions << " OR assignee = :true" if assignee
76 conditions << " OR assignee = :true" if assignee
77
77
78 workflows.
78 workflows.
79 includes(:new_status).
79 includes(:new_status).
80 where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})",
80 where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})",
81 {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false}
81 {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false}
82 ]).all.
82 ]).all.
83 map(&:new_status).compact.sort
83 map(&:new_status).compact.sort
84 else
84 else
85 []
85 []
86 end
86 end
87 end
87 end
88
88
89 def <=>(status)
89 def <=>(status)
90 position <=> status.position
90 position <=> status.position
91 end
91 end
92
92
93 def to_s; name end
93 def to_s; name end
94
94
95 private
95 private
96
96
97 def check_integrity
97 def check_integrity
98 raise "Can't delete status" if Issue.where(:status_id => id).any?
98 raise "Can't delete status" if Issue.where(:status_id => id).any?
99 end
99 end
100
100
101 # Deletes associated workflows
101 # Deletes associated workflows
102 def delete_workflow_rules
102 def delete_workflow_rules
103 WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
103 WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
104 end
104 end
105 end
105 end
@@ -1,285 +1,286
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
33 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => 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 validate :validate_version
36 validate :validate_version
37
37
38 scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
38 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
39 scope :open, :conditions => {:status => 'open'}
39 scope :open, where(:status => 'open')
40 scope :visible, lambda {|*args| { :include => :project,
40 scope :visible, lambda {|*args|
41 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
41 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
42 }
42
43
43 safe_attributes 'name',
44 safe_attributes 'name',
44 'description',
45 'description',
45 'effective_date',
46 'effective_date',
46 'due_date',
47 'due_date',
47 'wiki_page_title',
48 'wiki_page_title',
48 'status',
49 'status',
49 'sharing',
50 'sharing',
50 'custom_field_values'
51 'custom_field_values'
51
52
52 # 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
53 def visible?(user=User.current)
54 def visible?(user=User.current)
54 user.allowed_to?(:view_issues, self.project)
55 user.allowed_to?(:view_issues, self.project)
55 end
56 end
56
57
57 # Version files have same visibility as project files
58 # Version files have same visibility as project files
58 def attachments_visible?(*args)
59 def attachments_visible?(*args)
59 project.present? && project.attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
60 end
61 end
61
62
62 def start_date
63 def start_date
63 @start_date ||= fixed_issues.minimum('start_date')
64 @start_date ||= fixed_issues.minimum('start_date')
64 end
65 end
65
66
66 def due_date
67 def due_date
67 effective_date
68 effective_date
68 end
69 end
69
70
70 def due_date=(arg)
71 def due_date=(arg)
71 self.effective_date=(arg)
72 self.effective_date=(arg)
72 end
73 end
73
74
74 # Returns the total estimated time for this version
75 # Returns the total estimated time for this version
75 # (sum of leaves estimated_hours)
76 # (sum of leaves estimated_hours)
76 def estimated_hours
77 def estimated_hours
77 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 end
79 end
79
80
80 # Returns the total reported time for this version
81 # Returns the total reported time for this version
81 def spent_hours
82 def spent_hours
82 @spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
83 end
84 end
84
85
85 def closed?
86 def closed?
86 status == 'closed'
87 status == 'closed'
87 end
88 end
88
89
89 def open?
90 def open?
90 status == 'open'
91 status == 'open'
91 end
92 end
92
93
93 # 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
94 def completed?
95 def completed?
95 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 end
97 end
97
98
98 def behind_schedule?
99 def behind_schedule?
99 if completed_pourcent == 100
100 if completed_pourcent == 100
100 return false
101 return false
101 elsif due_date && start_date
102 elsif due_date && start_date
102 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
103 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
103 return done_date <= Date.today
104 return done_date <= Date.today
104 else
105 else
105 false # No issues so it's not late
106 false # No issues so it's not late
106 end
107 end
107 end
108 end
108
109
109 # 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
110 # and the time spent on the open issues.
111 # and the time spent on the open issues.
111 def completed_pourcent
112 def completed_pourcent
112 if issues_count == 0
113 if issues_count == 0
113 0
114 0
114 elsif open_issues_count == 0
115 elsif open_issues_count == 0
115 100
116 100
116 else
117 else
117 issues_progress(false) + issues_progress(true)
118 issues_progress(false) + issues_progress(true)
118 end
119 end
119 end
120 end
120
121
121 # Returns the percentage of issues that have been marked as 'closed'.
122 # Returns the percentage of issues that have been marked as 'closed'.
122 def closed_pourcent
123 def closed_pourcent
123 if issues_count == 0
124 if issues_count == 0
124 0
125 0
125 else
126 else
126 issues_progress(false)
127 issues_progress(false)
127 end
128 end
128 end
129 end
129
130
130 # Returns true if the version is overdue: due date reached and some open issues
131 # Returns true if the version is overdue: due date reached and some open issues
131 def overdue?
132 def overdue?
132 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
133 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
133 end
134 end
134
135
135 # Returns assigned issues count
136 # Returns assigned issues count
136 def issues_count
137 def issues_count
137 load_issue_counts
138 load_issue_counts
138 @issue_count
139 @issue_count
139 end
140 end
140
141
141 # Returns the total amount of open issues for this version.
142 # Returns the total amount of open issues for this version.
142 def open_issues_count
143 def open_issues_count
143 load_issue_counts
144 load_issue_counts
144 @open_issues_count
145 @open_issues_count
145 end
146 end
146
147
147 # Returns the total amount of closed issues for this version.
148 # Returns the total amount of closed issues for this version.
148 def closed_issues_count
149 def closed_issues_count
149 load_issue_counts
150 load_issue_counts
150 @closed_issues_count
151 @closed_issues_count
151 end
152 end
152
153
153 def wiki_page
154 def wiki_page
154 if project.wiki && !wiki_page_title.blank?
155 if project.wiki && !wiki_page_title.blank?
155 @wiki_page ||= project.wiki.find_page(wiki_page_title)
156 @wiki_page ||= project.wiki.find_page(wiki_page_title)
156 end
157 end
157 @wiki_page
158 @wiki_page
158 end
159 end
159
160
160 def to_s; name end
161 def to_s; name end
161
162
162 def to_s_with_project
163 def to_s_with_project
163 "#{project} - #{name}"
164 "#{project} - #{name}"
164 end
165 end
165
166
166 # Versions are sorted by effective_date and name
167 # Versions are sorted by effective_date and name
167 # Those with no effective_date are at the end, sorted by name
168 # Those with no effective_date are at the end, sorted by name
168 def <=>(version)
169 def <=>(version)
169 if self.effective_date
170 if self.effective_date
170 if version.effective_date
171 if version.effective_date
171 if self.effective_date == version.effective_date
172 if self.effective_date == version.effective_date
172 name == version.name ? id <=> version.id : name <=> version.name
173 name == version.name ? id <=> version.id : name <=> version.name
173 else
174 else
174 self.effective_date <=> version.effective_date
175 self.effective_date <=> version.effective_date
175 end
176 end
176 else
177 else
177 -1
178 -1
178 end
179 end
179 else
180 else
180 if version.effective_date
181 if version.effective_date
181 1
182 1
182 else
183 else
183 name == version.name ? id <=> version.id : name <=> version.name
184 name == version.name ? id <=> version.id : name <=> version.name
184 end
185 end
185 end
186 end
186 end
187 end
187
188
188 def self.fields_for_order_statement(table=nil)
189 def self.fields_for_order_statement(table=nil)
189 table ||= table_name
190 table ||= table_name
190 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
191 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
191 end
192 end
192
193
193 scope :sorted, order(fields_for_order_statement)
194 scope :sorted, order(fields_for_order_statement)
194
195
195 # Returns the sharings that +user+ can set the version to
196 # Returns the sharings that +user+ can set the version to
196 def allowed_sharings(user = User.current)
197 def allowed_sharings(user = User.current)
197 VERSION_SHARINGS.select do |s|
198 VERSION_SHARINGS.select do |s|
198 if sharing == s
199 if sharing == s
199 true
200 true
200 else
201 else
201 case s
202 case s
202 when 'system'
203 when 'system'
203 # Only admin users can set a systemwide sharing
204 # Only admin users can set a systemwide sharing
204 user.admin?
205 user.admin?
205 when 'hierarchy', 'tree'
206 when 'hierarchy', 'tree'
206 # Only users allowed to manage versions of the root project can
207 # Only users allowed to manage versions of the root project can
207 # set sharing to hierarchy or tree
208 # set sharing to hierarchy or tree
208 project.nil? || user.allowed_to?(:manage_versions, project.root)
209 project.nil? || user.allowed_to?(:manage_versions, project.root)
209 else
210 else
210 true
211 true
211 end
212 end
212 end
213 end
213 end
214 end
214 end
215 end
215
216
216 private
217 private
217
218
218 def load_issue_counts
219 def load_issue_counts
219 unless @issue_count
220 unless @issue_count
220 @open_issues_count = 0
221 @open_issues_count = 0
221 @closed_issues_count = 0
222 @closed_issues_count = 0
222 fixed_issues.count(:all, :group => :status).each do |status, count|
223 fixed_issues.count(:all, :group => :status).each do |status, count|
223 if status.is_closed?
224 if status.is_closed?
224 @closed_issues_count += count
225 @closed_issues_count += count
225 else
226 else
226 @open_issues_count += count
227 @open_issues_count += count
227 end
228 end
228 end
229 end
229 @issue_count = @open_issues_count + @closed_issues_count
230 @issue_count = @open_issues_count + @closed_issues_count
230 end
231 end
231 end
232 end
232
233
233 # Update the issue's fixed versions. Used if a version's sharing changes.
234 # Update the issue's fixed versions. Used if a version's sharing changes.
234 def update_issues_from_sharing_change
235 def update_issues_from_sharing_change
235 if sharing_changed?
236 if sharing_changed?
236 if VERSION_SHARINGS.index(sharing_was).nil? ||
237 if VERSION_SHARINGS.index(sharing_was).nil? ||
237 VERSION_SHARINGS.index(sharing).nil? ||
238 VERSION_SHARINGS.index(sharing).nil? ||
238 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
239 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
239 Issue.update_versions_from_sharing_change self
240 Issue.update_versions_from_sharing_change self
240 end
241 end
241 end
242 end
242 end
243 end
243
244
244 # Returns the average estimated time of assigned issues
245 # Returns the average estimated time of assigned issues
245 # or 1 if no issue has an estimated time
246 # or 1 if no issue has an estimated time
246 # Used to weigth unestimated issues in progress calculation
247 # Used to weigth unestimated issues in progress calculation
247 def estimated_average
248 def estimated_average
248 if @estimated_average.nil?
249 if @estimated_average.nil?
249 average = fixed_issues.average(:estimated_hours).to_f
250 average = fixed_issues.average(:estimated_hours).to_f
250 if average == 0
251 if average == 0
251 average = 1
252 average = 1
252 end
253 end
253 @estimated_average = average
254 @estimated_average = average
254 end
255 end
255 @estimated_average
256 @estimated_average
256 end
257 end
257
258
258 # Returns the total progress of open or closed issues. The returned percentage takes into account
259 # Returns the total progress of open or closed issues. The returned percentage takes into account
259 # the amount of estimated time set for this version.
260 # the amount of estimated time set for this version.
260 #
261 #
261 # Examples:
262 # Examples:
262 # issues_progress(true) => returns the progress percentage for open issues.
263 # issues_progress(true) => returns the progress percentage for open issues.
263 # issues_progress(false) => returns the progress percentage for closed issues.
264 # issues_progress(false) => returns the progress percentage for closed issues.
264 def issues_progress(open)
265 def issues_progress(open)
265 @issues_progress ||= {}
266 @issues_progress ||= {}
266 @issues_progress[open] ||= begin
267 @issues_progress[open] ||= begin
267 progress = 0
268 progress = 0
268 if issues_count > 0
269 if issues_count > 0
269 ratio = open ? 'done_ratio' : 100
270 ratio = open ? 'done_ratio' : 100
270
271
271 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
272 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
272 :joins => :status,
273 :joins => :status,
273 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
274 :conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
274 progress = done / (estimated_average * issues_count)
275 progress = done / (estimated_average * issues_count)
275 end
276 end
276 progress
277 progress
277 end
278 end
278 end
279 end
279
280
280 def validate_version
281 def validate_version
281 if effective_date.nil? && @attributes['effective_date'].present?
282 if effective_date.nil? && @attributes['effective_date'].present?
282 errors.add :effective_date, :not_a_date
283 errors.add :effective_date, :not_a_date
283 end
284 end
284 end
285 end
285 end
286 end
General Comments 0
You need to be logged in to leave comments. Login now