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