##// END OF EJS Templates
remove trailing white-spaces excluding SQL from issue model source....
Toshi MARUYAMA -
r5690:42a367b2588e
parent child
Show More
@@ -5,19 +5,19
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 Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 belongs_to :project
21 belongs_to :project
22 belongs_to :tracker
22 belongs_to :tracker
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
@@ -30,10 +30,10 class Issue < ActiveRecord::Base
30 has_many :journals, :as => :journalized, :dependent => :destroy
30 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :time_entries, :dependent => :delete_all
31 has_many :time_entries, :dependent => :delete_all
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33
33
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36
36
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 acts_as_attachable :after_remove => :attachment_removed
38 acts_as_attachable :after_remove => :attachment_removed
39 acts_as_customizable
39 acts_as_customizable
@@ -45,7 +45,7 class Issue < ActiveRecord::Base
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48
48
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 :author_key => :author_id
50 :author_key => :author_id
51
51
@@ -61,7 +61,7 class Issue < ActiveRecord::Base
61
61
62 named_scope :visible, lambda {|*args| { :include => :project,
62 named_scope :visible, lambda {|*args| { :include => :project,
63 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
63 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
64
64
65 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
65 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
66
66
67 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
67 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
@@ -85,7 +85,7 class Issue < ActiveRecord::Base
85 before_save :close_duplicates, :update_done_ratio_from_issue_status
85 before_save :close_duplicates, :update_done_ratio_from_issue_status
86 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
86 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
87 after_destroy :update_parent_attributes
87 after_destroy :update_parent_attributes
88
88
89 # Returns a SQL conditions string used to find all issues visible by the specified user
89 # Returns a SQL conditions string used to find all issues visible by the specified user
90 def self.visible_condition(user, options={})
90 def self.visible_condition(user, options={})
91 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
91 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
@@ -117,7 +117,7 class Issue < ActiveRecord::Base
117 end
117 end
118 end
118 end
119 end
119 end
120
120
121 def after_initialize
121 def after_initialize
122 if new_record?
122 if new_record?
123 # set default values for new records only
123 # set default values for new records only
@@ -125,12 +125,12 class Issue < ActiveRecord::Base
125 self.priority ||= IssuePriority.default
125 self.priority ||= IssuePriority.default
126 end
126 end
127 end
127 end
128
128
129 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
129 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
130 def available_custom_fields
130 def available_custom_fields
131 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
131 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
132 end
132 end
133
133
134 def copy_from(arg)
134 def copy_from(arg)
135 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
135 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
136 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
136 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
@@ -138,7 +138,7 class Issue < ActiveRecord::Base
138 self.status = issue.status
138 self.status = issue.status
139 self
139 self
140 end
140 end
141
141
142 # Moves/copies an issue to a new project and tracker
142 # Moves/copies an issue to a new project and tracker
143 # Returns the moved/copied issue on success, false on failure
143 # Returns the moved/copied issue on success, false on failure
144 def move_to_project(*args)
144 def move_to_project(*args)
@@ -146,11 +146,11 class Issue < ActiveRecord::Base
146 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
146 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
147 end || false
147 end || false
148 end
148 end
149
149
150 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
150 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
151 options ||= {}
151 options ||= {}
152 issue = options[:copy] ? self.class.new.copy_from(self) : self
152 issue = options[:copy] ? self.class.new.copy_from(self) : self
153
153
154 if new_project && issue.project_id != new_project.id
154 if new_project && issue.project_id != new_project.id
155 # delete issue relations
155 # delete issue relations
156 unless Setting.cross_project_issue_relations?
156 unless Setting.cross_project_issue_relations?
@@ -197,7 +197,7 class Issue < ActiveRecord::Base
197 else
197 else
198 # Manually update project_id on related time entries
198 # Manually update project_id on related time entries
199 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
199 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
200
200
201 issue.children.each do |child|
201 issue.children.each do |child|
202 unless child.move_to_project_without_transaction(new_project)
202 unless child.move_to_project_without_transaction(new_project)
203 # Move failed and transaction was rollback'd
203 # Move failed and transaction was rollback'd
@@ -215,7 +215,7 class Issue < ActiveRecord::Base
215 self.status = nil
215 self.status = nil
216 write_attribute(:status_id, sid)
216 write_attribute(:status_id, sid)
217 end
217 end
218
218
219 def priority_id=(pid)
219 def priority_id=(pid)
220 self.priority = nil
220 self.priority = nil
221 write_attribute(:priority_id, pid)
221 write_attribute(:priority_id, pid)
@@ -227,7 +227,7 class Issue < ActiveRecord::Base
227 @custom_field_values = nil
227 @custom_field_values = nil
228 result
228 result
229 end
229 end
230
230
231 # Overrides attributes= so that tracker_id gets assigned first
231 # Overrides attributes= so that tracker_id gets assigned first
232 def attributes_with_tracker_first=(new_attributes, *args)
232 def attributes_with_tracker_first=(new_attributes, *args)
233 return if new_attributes.nil?
233 return if new_attributes.nil?
@@ -239,11 +239,11 class Issue < ActiveRecord::Base
239 end
239 end
240 # Do not redefine alias chain on reload (see #4838)
240 # Do not redefine alias chain on reload (see #4838)
241 alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
241 alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
242
242
243 def estimated_hours=(h)
243 def estimated_hours=(h)
244 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
244 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
245 end
245 end
246
246
247 safe_attributes 'tracker_id',
247 safe_attributes 'tracker_id',
248 'status_id',
248 'status_id',
249 'parent_issue_id',
249 'parent_issue_id',
@@ -261,7 +261,7 class Issue < ActiveRecord::Base
261 'custom_fields',
261 'custom_fields',
262 'lock_version',
262 'lock_version',
263 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
263 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
264
264
265 safe_attributes 'status_id',
265 safe_attributes 'status_id',
266 'assigned_to_id',
266 'assigned_to_id',
267 'fixed_version_id',
267 'fixed_version_id',
@@ -273,7 +273,7 class Issue < ActiveRecord::Base
273 user.allowed_to?(:set_issues_private, issue.project) ||
273 user.allowed_to?(:set_issues_private, issue.project) ||
274 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
274 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
275 }
275 }
276
276
277 # Safely sets attributes
277 # Safely sets attributes
278 # Should be called from controllers instead of #attributes=
278 # Should be called from controllers instead of #attributes=
279 # attr_accessible is too rough because we still want things like
279 # attr_accessible is too rough because we still want things like
@@ -281,26 +281,26 class Issue < ActiveRecord::Base
281 # TODO: move workflow/permission checks from controllers to here
281 # TODO: move workflow/permission checks from controllers to here
282 def safe_attributes=(attrs, user=User.current)
282 def safe_attributes=(attrs, user=User.current)
283 return unless attrs.is_a?(Hash)
283 return unless attrs.is_a?(Hash)
284
284
285 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
285 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
286 attrs = delete_unsafe_attributes(attrs, user)
286 attrs = delete_unsafe_attributes(attrs, user)
287 return if attrs.empty?
287 return if attrs.empty?
288
288
289 # Tracker must be set before since new_statuses_allowed_to depends on it.
289 # Tracker must be set before since new_statuses_allowed_to depends on it.
290 if t = attrs.delete('tracker_id')
290 if t = attrs.delete('tracker_id')
291 self.tracker_id = t
291 self.tracker_id = t
292 end
292 end
293
293
294 if attrs['status_id']
294 if attrs['status_id']
295 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
295 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
296 attrs.delete('status_id')
296 attrs.delete('status_id')
297 end
297 end
298 end
298 end
299
299
300 unless leaf?
300 unless leaf?
301 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
301 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
302 end
302 end
303
303
304 if attrs.has_key?('parent_issue_id')
304 if attrs.has_key?('parent_issue_id')
305 if !user.allowed_to?(:manage_subtasks, project)
305 if !user.allowed_to?(:manage_subtasks, project)
306 attrs.delete('parent_issue_id')
306 attrs.delete('parent_issue_id')
@@ -308,10 +308,10 class Issue < ActiveRecord::Base
308 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
308 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
309 end
309 end
310 end
310 end
311
311
312 self.attributes = attrs
312 self.attributes = attrs
313 end
313 end
314
314
315 def done_ratio
315 def done_ratio
316 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
316 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
317 status.default_done_ratio
317 status.default_done_ratio
@@ -327,20 +327,20 class Issue < ActiveRecord::Base
327 def self.use_field_for_done_ratio?
327 def self.use_field_for_done_ratio?
328 Setting.issue_done_ratio == 'issue_field'
328 Setting.issue_done_ratio == 'issue_field'
329 end
329 end
330
330
331 def validate
331 def validate
332 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
332 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
333 errors.add :due_date, :not_a_date
333 errors.add :due_date, :not_a_date
334 end
334 end
335
335
336 if self.due_date and self.start_date and self.due_date < self.start_date
336 if self.due_date and self.start_date and self.due_date < self.start_date
337 errors.add :due_date, :greater_than_start_date
337 errors.add :due_date, :greater_than_start_date
338 end
338 end
339
339
340 if start_date && soonest_start && start_date < soonest_start
340 if start_date && soonest_start && start_date < soonest_start
341 errors.add :start_date, :invalid
341 errors.add :start_date, :invalid
342 end
342 end
343
343
344 if fixed_version
344 if fixed_version
345 if !assignable_versions.include?(fixed_version)
345 if !assignable_versions.include?(fixed_version)
346 errors.add :fixed_version_id, :inclusion
346 errors.add :fixed_version_id, :inclusion
@@ -348,14 +348,14 class Issue < ActiveRecord::Base
348 errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
348 errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
349 end
349 end
350 end
350 end
351
351
352 # Checks that the issue can not be added/moved to a disabled tracker
352 # Checks that the issue can not be added/moved to a disabled tracker
353 if project && (tracker_id_changed? || project_id_changed?)
353 if project && (tracker_id_changed? || project_id_changed?)
354 unless project.trackers.include?(tracker)
354 unless project.trackers.include?(tracker)
355 errors.add :tracker_id, :inclusion
355 errors.add :tracker_id, :inclusion
356 end
356 end
357 end
357 end
358
358
359 # Checks parent issue assignment
359 # Checks parent issue assignment
360 if @parent_issue
360 if @parent_issue
361 if @parent_issue.project_id != project_id
361 if @parent_issue.project_id != project_id
@@ -372,7 +372,7 class Issue < ActiveRecord::Base
372 end
372 end
373 end
373 end
374 end
374 end
375
375
376 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
376 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
377 # even if the user turns off the setting later
377 # even if the user turns off the setting later
378 def update_done_ratio_from_issue_status
378 def update_done_ratio_from_issue_status
@@ -380,7 +380,7 class Issue < ActiveRecord::Base
380 self.done_ratio = status.default_done_ratio
380 self.done_ratio = status.default_done_ratio
381 end
381 end
382 end
382 end
383
383
384 def init_journal(user, notes = "")
384 def init_journal(user, notes = "")
385 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
385 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
386 @issue_before_change = self.clone
386 @issue_before_change = self.clone
@@ -391,12 +391,12 class Issue < ActiveRecord::Base
391 updated_on_will_change!
391 updated_on_will_change!
392 @current_journal
392 @current_journal
393 end
393 end
394
394
395 # Return true if the issue is closed, otherwise false
395 # Return true if the issue is closed, otherwise false
396 def closed?
396 def closed?
397 self.status.is_closed?
397 self.status.is_closed?
398 end
398 end
399
399
400 # Return true if the issue is being reopened
400 # Return true if the issue is being reopened
401 def reopened?
401 def reopened?
402 if !new_record? && status_id_changed?
402 if !new_record? && status_id_changed?
@@ -420,7 +420,7 class Issue < ActiveRecord::Base
420 end
420 end
421 false
421 false
422 end
422 end
423
423
424 # Returns true if the issue is overdue
424 # Returns true if the issue is overdue
425 def overdue?
425 def overdue?
426 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
426 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
@@ -437,24 +437,24 class Issue < ActiveRecord::Base
437 def children?
437 def children?
438 !leaf?
438 !leaf?
439 end
439 end
440
440
441 # Users the issue can be assigned to
441 # Users the issue can be assigned to
442 def assignable_users
442 def assignable_users
443 users = project.assignable_users
443 users = project.assignable_users
444 users << author if author
444 users << author if author
445 users.uniq.sort
445 users.uniq.sort
446 end
446 end
447
447
448 # Versions that the issue can be assigned to
448 # Versions that the issue can be assigned to
449 def assignable_versions
449 def assignable_versions
450 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
450 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
451 end
451 end
452
452
453 # Returns true if this issue is blocked by another issue that is still open
453 # Returns true if this issue is blocked by another issue that is still open
454 def blocked?
454 def blocked?
455 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
455 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
456 end
456 end
457
457
458 # Returns an array of status that user is able to apply
458 # Returns an array of status that user is able to apply
459 def new_statuses_allowed_to(user, include_default=false)
459 def new_statuses_allowed_to(user, include_default=false)
460 statuses = status.find_new_statuses_allowed_to(
460 statuses = status.find_new_statuses_allowed_to(
@@ -468,7 +468,7 class Issue < ActiveRecord::Base
468 statuses = statuses.uniq.sort
468 statuses = statuses.uniq.sort
469 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
469 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
470 end
470 end
471
471
472 # Returns the mail adresses of users that should be notified
472 # Returns the mail adresses of users that should be notified
473 def recipients
473 def recipients
474 notified = project.notified_users
474 notified = project.notified_users
@@ -481,7 +481,7 class Issue < ActiveRecord::Base
481 notified.reject! {|user| !visible?(user)}
481 notified.reject! {|user| !visible?(user)}
482 notified.collect(&:mail)
482 notified.collect(&:mail)
483 end
483 end
484
484
485 # Returns the total number of hours spent on this issue and its descendants
485 # Returns the total number of hours spent on this issue and its descendants
486 #
486 #
487 # Example:
487 # Example:
@@ -490,50 +490,50 class Issue < ActiveRecord::Base
490 def spent_hours
490 def spent_hours
491 @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
491 @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
492 end
492 end
493
493
494 def relations
494 def relations
495 (relations_from + relations_to).sort
495 (relations_from + relations_to).sort
496 end
496 end
497
497
498 def all_dependent_issues(except=[])
498 def all_dependent_issues(except=[])
499 except << self
499 except << self
500 dependencies = []
500 dependencies = []
501 relations_from.each do |relation|
501 relations_from.each do |relation|
502 if relation.issue_to && !except.include?(relation.issue_to)
502 if relation.issue_to && !except.include?(relation.issue_to)
503 dependencies << relation.issue_to
503 dependencies << relation.issue_to
504 dependencies += relation.issue_to.all_dependent_issues(except)
504 dependencies += relation.issue_to.all_dependent_issues(except)
505 end
505 end
506 end
506 end
507 dependencies
507 dependencies
508 end
508 end
509
509
510 # Returns an array of issues that duplicate this one
510 # Returns an array of issues that duplicate this one
511 def duplicates
511 def duplicates
512 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
512 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
513 end
513 end
514
514
515 # Returns the due date or the target due date if any
515 # Returns the due date or the target due date if any
516 # Used on gantt chart
516 # Used on gantt chart
517 def due_before
517 def due_before
518 due_date || (fixed_version ? fixed_version.effective_date : nil)
518 due_date || (fixed_version ? fixed_version.effective_date : nil)
519 end
519 end
520
520
521 # Returns the time scheduled for this issue.
521 # Returns the time scheduled for this issue.
522 #
522 #
523 # Example:
523 # Example:
524 # Start Date: 2/26/09, End Date: 3/04/09
524 # Start Date: 2/26/09, End Date: 3/04/09
525 # duration => 6
525 # duration => 6
526 def duration
526 def duration
527 (start_date && due_date) ? due_date - start_date : 0
527 (start_date && due_date) ? due_date - start_date : 0
528 end
528 end
529
529
530 def soonest_start
530 def soonest_start
531 @soonest_start ||= (
531 @soonest_start ||= (
532 relations_to.collect{|relation| relation.successor_soonest_start} +
532 relations_to.collect{|relation| relation.successor_soonest_start} +
533 ancestors.collect(&:soonest_start)
533 ancestors.collect(&:soonest_start)
534 ).compact.max
534 ).compact.max
535 end
535 end
536
536
537 def reschedule_after(date)
537 def reschedule_after(date)
538 return if date.nil?
538 return if date.nil?
539 if leaf?
539 if leaf?
@@ -547,7 +547,7 class Issue < ActiveRecord::Base
547 end
547 end
548 end
548 end
549 end
549 end
550
550
551 def <=>(issue)
551 def <=>(issue)
552 if issue.nil?
552 if issue.nil?
553 -1
553 -1
@@ -557,11 +557,11 class Issue < ActiveRecord::Base
557 (lft || 0) <=> (issue.lft || 0)
557 (lft || 0) <=> (issue.lft || 0)
558 end
558 end
559 end
559 end
560
560
561 def to_s
561 def to_s
562 "#{tracker} ##{id}: #{subject}"
562 "#{tracker} ##{id}: #{subject}"
563 end
563 end
564
564
565 # Returns a string of css classes that apply to the issue
565 # Returns a string of css classes that apply to the issue
566 def css_classes
566 def css_classes
567 s = "issue status-#{status.position} priority-#{priority.position}"
567 s = "issue status-#{status.position} priority-#{priority.position}"
@@ -588,10 +588,10 class Issue < ActiveRecord::Base
588 @time_entry.attributes = params[:time_entry]
588 @time_entry.attributes = params[:time_entry]
589 self.time_entries << @time_entry
589 self.time_entries << @time_entry
590 end
590 end
591
591
592 if valid?
592 if valid?
593 attachments = Attachment.attach_files(self, params[:attachments])
593 attachments = Attachment.attach_files(self, params[:attachments])
594
594
595 attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
595 attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
596 # TODO: Rename hook
596 # TODO: Rename hook
597 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
597 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
@@ -616,7 +616,7 class Issue < ActiveRecord::Base
616 # Update issues assigned to the version
616 # Update issues assigned to the version
617 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
617 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
618 end
618 end
619
619
620 # Unassigns issues from versions that are no longer shared
620 # Unassigns issues from versions that are no longer shared
621 # after +project+ was moved
621 # after +project+ was moved
622 def self.update_versions_from_hierarchy_change(project)
622 def self.update_versions_from_hierarchy_change(project)
@@ -634,7 +634,7 class Issue < ActiveRecord::Base
634 nil
634 nil
635 end
635 end
636 end
636 end
637
637
638 def parent_issue_id
638 def parent_issue_id
639 if instance_variable_defined? :@parent_issue
639 if instance_variable_defined? :@parent_issue
640 @parent_issue.nil? ? nil : @parent_issue.id
640 @parent_issue.nil? ? nil : @parent_issue.id
@@ -695,7 +695,7 class Issue < ActiveRecord::Base
695 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
695 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
696 end
696 end
697 # End ReportsController extraction
697 # End ReportsController extraction
698
698
699 # Returns an array of projects that current user can move issues to
699 # Returns an array of projects that current user can move issues to
700 def self.allowed_target_projects_on_move
700 def self.allowed_target_projects_on_move
701 projects = []
701 projects = []
@@ -711,9 +711,9 class Issue < ActiveRecord::Base
711 end
711 end
712 projects
712 projects
713 end
713 end
714
714
715 private
715 private
716
716
717 def update_nested_set_attributes
717 def update_nested_set_attributes
718 if root_id.nil?
718 if root_id.nil?
719 # issue was just created
719 # issue was just created
@@ -760,7 +760,7 class Issue < ActiveRecord::Base
760 end
760 end
761 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
761 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
762 end
762 end
763
763
764 def update_parent_attributes
764 def update_parent_attributes
765 recalculate_attributes_for(parent_id) if parent_id
765 recalculate_attributes_for(parent_id) if parent_id
766 end
766 end
@@ -771,14 +771,14 class Issue < ActiveRecord::Base
771 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
771 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
772 p.priority = IssuePriority.find_by_position(priority_position)
772 p.priority = IssuePriority.find_by_position(priority_position)
773 end
773 end
774
774
775 # start/due dates = lowest/highest dates of children
775 # start/due dates = lowest/highest dates of children
776 p.start_date = p.children.minimum(:start_date)
776 p.start_date = p.children.minimum(:start_date)
777 p.due_date = p.children.maximum(:due_date)
777 p.due_date = p.children.maximum(:due_date)
778 if p.start_date && p.due_date && p.due_date < p.start_date
778 if p.start_date && p.due_date && p.due_date < p.start_date
779 p.start_date, p.due_date = p.due_date, p.start_date
779 p.start_date, p.due_date = p.due_date, p.start_date
780 end
780 end
781
781
782 # done ratio = weighted average ratio of leaves
782 # done ratio = weighted average ratio of leaves
783 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
783 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
784 leaves_count = p.leaves.count
784 leaves_count = p.leaves.count
@@ -792,16 +792,16 class Issue < ActiveRecord::Base
792 p.done_ratio = progress.round
792 p.done_ratio = progress.round
793 end
793 end
794 end
794 end
795
795
796 # estimate = sum of leaves estimates
796 # estimate = sum of leaves estimates
797 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
797 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
798 p.estimated_hours = nil if p.estimated_hours == 0.0
798 p.estimated_hours = nil if p.estimated_hours == 0.0
799
799
800 # ancestors will be recursively updated
800 # ancestors will be recursively updated
801 p.save(false)
801 p.save(false)
802 end
802 end
803 end
803 end
804
804
805 # Update issues so their versions are not pointing to a
805 # Update issues so their versions are not pointing to a
806 # fixed_version that is not shared with the issue's project
806 # fixed_version that is not shared with the issue's project
807 def self.update_versions(conditions=nil)
807 def self.update_versions(conditions=nil)
@@ -821,7 +821,7 class Issue < ActiveRecord::Base
821 end
821 end
822 end
822 end
823 end
823 end
824
824
825 # Callback on attachment deletion
825 # Callback on attachment deletion
826 def attachment_removed(obj)
826 def attachment_removed(obj)
827 journal = init_journal(User.current)
827 journal = init_journal(User.current)
@@ -830,7 +830,7 class Issue < ActiveRecord::Base
830 :old_value => obj.filename)
830 :old_value => obj.filename)
831 journal.save
831 journal.save
832 end
832 end
833
833
834 # Default assignment based on category
834 # Default assignment based on category
835 def default_assign
835 def default_assign
836 if assigned_to.nil? && category && category.assigned_to
836 if assigned_to.nil? && category && category.assigned_to
@@ -863,7 +863,7 class Issue < ActiveRecord::Base
863 end
863 end
864 end
864 end
865 end
865 end
866
866
867 # Saves the changes in a Journal
867 # Saves the changes in a Journal
868 # Called after_save
868 # Called after_save
869 def create_journal
869 def create_journal
@@ -879,11 +879,11 class Issue < ActiveRecord::Base
879 custom_values.each {|c|
879 custom_values.each {|c|
880 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
880 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
881 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
881 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
882 @current_journal.details << JournalDetail.new(:property => 'cf',
882 @current_journal.details << JournalDetail.new(:property => 'cf',
883 :prop_key => c.custom_field_id,
883 :prop_key => c.custom_field_id,
884 :old_value => @custom_values_before_change[c.custom_field_id],
884 :old_value => @custom_values_before_change[c.custom_field_id],
885 :value => c.value)
885 :value => c.value)
886 }
886 }
887 @current_journal.save
887 @current_journal.save
888 # reset current journal
888 # reset current journal
889 init_journal @current_journal.user, @current_journal.notes
889 init_journal @current_journal.user, @current_journal.notes
@@ -903,7 +903,7 class Issue < ActiveRecord::Base
903 joins = options.delete(:joins)
903 joins = options.delete(:joins)
904
904
905 where = "#{Issue.table_name}.#{select_field}=j.id"
905 where = "#{Issue.table_name}.#{select_field}=j.id"
906
906
907 ActiveRecord::Base.connection.select_all("select s.id as status_id,
907 ActiveRecord::Base.connection.select_all("select s.id as status_id,
908 s.is_closed as closed,
908 s.is_closed as closed,
909 j.id as #{select_field},
909 j.id as #{select_field},
General Comments 0
You need to be logged in to leave comments. Login now