comparison app/models/project.rb @ 1295:622f24f53b42 redmine-2.3

Update to Redmine SVN revision 11972 on 2.3-stable branch
author Chris Cannam
date Fri, 14 Jun 2013 09:02:21 +0100
parents 3e4c3460b6ca
children 4f746d8966dd
comparison
equal deleted inserted replaced
1294:3e4c3460b6ca 1295:622f24f53b42
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang 2 # Copyright (C) 2006-2013 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.
26 # Maximum length for project identifiers 26 # Maximum length for project identifiers
27 IDENTIFIER_MAX_LENGTH = 100 27 IDENTIFIER_MAX_LENGTH = 100
28 28
29 # Specific overidden Activities 29 # Specific overidden Activities
30 has_many :time_entry_activities 30 has_many :time_entry_activities
31 has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}" 31 has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}"
32 has_many :memberships, :class_name => 'Member' 32 has_many :memberships, :class_name => 'Member'
33 has_many :member_principals, :class_name => 'Member', 33 has_many :member_principals, :class_name => 'Member',
34 :include => :principal, 34 :include => :principal,
35 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})" 35 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})"
36 has_many :users, :through => :members 36 has_many :users, :through => :members
37 has_many :principals, :through => :member_principals, :source => :principal 37 has_many :principals, :through => :member_principals, :source => :principal
38 38
39 has_many :enabled_modules, :dependent => :delete_all 39 has_many :enabled_modules, :dependent => :delete_all
40 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" 40 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
41 has_many :issues, :dependent => :destroy, :include => [:status, :tracker] 41 has_many :issues, :dependent => :destroy, :include => [:status, :tracker]
42 has_many :issue_changes, :through => :issues, :source => :journals 42 has_many :issue_changes, :through => :issues, :source => :journals
43 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" 43 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
44 has_many :time_entries, :dependent => :delete_all 44 has_many :time_entries, :dependent => :delete_all
45 has_many :queries, :dependent => :delete_all 45 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
46 has_many :documents, :dependent => :destroy 46 has_many :documents, :dependent => :destroy
47 has_many :news, :dependent => :destroy, :include => :author 47 has_many :news, :dependent => :destroy, :include => :author
48 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" 48 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
49 has_many :boards, :dependent => :destroy, :order => "position ASC" 49 has_many :boards, :dependent => :destroy, :order => "position ASC"
50 has_one :repository, :conditions => ["is_default = ?", true] 50 has_one :repository, :conditions => ["is_default = ?", true]
75 validates_associated :repository, :wiki 75 validates_associated :repository, :wiki
76 validates_length_of :name, :maximum => 255 76 validates_length_of :name, :maximum => 255
77 validates_length_of :homepage, :maximum => 255 77 validates_length_of :homepage, :maximum => 255
78 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH 78 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
79 # donwcase letters, digits, dashes but not digits only 79 # donwcase letters, digits, dashes but not digits only
80 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :if => Proc.new { |p| p.identifier_changed? } 80 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
81 # reserved words 81 # reserved words
82 validates_exclusion_of :identifier, :in => %w( new ) 82 validates_exclusion_of :identifier, :in => %w( new )
83 83
84 after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?} 84 after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?}
85 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
85 before_destroy :delete_all_members 86 before_destroy :delete_all_members
86 87
87 scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } 88 scope :has_module, lambda {|mod|
88 scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} 89 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
89 scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } 90 }
90 scope :all_public, { :conditions => { :is_public => true } } 91 scope :active, lambda { where(:status => STATUS_ACTIVE) }
91 scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} 92 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
93 scope :all_public, lambda { where(:is_public => true) }
94 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
92 scope :allowed_to, lambda {|*args| 95 scope :allowed_to, lambda {|*args|
93 user = User.current 96 user = User.current
94 permission = nil 97 permission = nil
95 if args.first.is_a?(Symbol) 98 if args.first.is_a?(Symbol)
96 permission = args.shift 99 permission = args.shift
97 else 100 else
98 user = args.shift 101 user = args.shift
99 permission = args.shift 102 permission = args.shift
100 end 103 end
101 { :conditions => Project.allowed_to_condition(user, permission, *args) } 104 where(Project.allowed_to_condition(user, permission, *args))
102 } 105 }
103 scope :like, lambda {|arg| 106 scope :like, lambda {|arg|
104 if arg.blank? 107 if arg.blank?
105 {} 108 where(nil)
106 else 109 else
107 pattern = "%#{arg.to_s.strip.downcase}%" 110 pattern = "%#{arg.to_s.strip.downcase}%"
108 {:conditions => ["LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", {:p => pattern}]} 111 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
109 end 112 end
110 } 113 }
111 114
112 def initialize(attributes=nil, *args) 115 def initialize(attributes=nil, *args)
113 super 116 super
121 end 124 end
122 if !initialized.key?('enabled_module_names') 125 if !initialized.key?('enabled_module_names')
123 self.enabled_module_names = Setting.default_projects_modules 126 self.enabled_module_names = Setting.default_projects_modules
124 end 127 end
125 if !initialized.key?('trackers') && !initialized.key?('tracker_ids') 128 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
126 self.trackers = Tracker.sorted.all 129 default = Setting.default_projects_tracker_ids
130 if default.is_a?(Array)
131 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.all
132 else
133 self.trackers = Tracker.sorted.all
134 end
127 end 135 end
128 end 136 end
129 137
130 def identifier=(identifier) 138 def identifier=(identifier)
131 super unless identifier_frozen? 139 super unless identifier_frozen?
136 end 144 end
137 145
138 # returns latest created projects 146 # returns latest created projects
139 # non public projects will be returned only if user is a member of those 147 # non public projects will be returned only if user is a member of those
140 def self.latest(user=nil, count=5) 148 def self.latest(user=nil, count=5)
141 visible(user).find(:all, :limit => count, :order => "created_on DESC") 149 visible(user).limit(count).order("created_on DESC").all
142 end 150 end
143 151
144 # Returns true if the project is visible to +user+ or to the current user. 152 # Returns true if the project is visible to +user+ or to the current user.
145 def visible?(user=User.current) 153 def visible?(user=User.current)
146 user.allowed_to?(:view_project, self) 154 user.allowed_to?(:view_project, self)
147 end 155 end
275 283
276 def self.find_by_param(*args) 284 def self.find_by_param(*args)
277 self.find(*args) 285 self.find(*args)
278 end 286 end
279 287
288 alias :base_reload :reload
280 def reload(*args) 289 def reload(*args)
281 @shared_versions = nil 290 @shared_versions = nil
282 @rolled_up_versions = nil 291 @rolled_up_versions = nil
283 @rolled_up_trackers = nil 292 @rolled_up_trackers = nil
284 @all_issue_custom_fields = nil 293 @all_issue_custom_fields = nil
285 @all_time_entry_custom_fields = nil 294 @all_time_entry_custom_fields = nil
286 @to_param = nil 295 @to_param = nil
287 @allowed_parents = nil 296 @allowed_parents = nil
288 @allowed_permissions = nil 297 @allowed_permissions = nil
289 @actions_allowed = nil 298 @actions_allowed = nil
290 super 299 @start_date = nil
300 @due_date = nil
301 base_reload(*args)
291 end 302 end
292 303
293 def to_param 304 def to_param
294 # id is used for projects with a numeric identifier (compatibility) 305 # id is used for projects with a numeric identifier (compatibility)
295 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier) 306 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
306 # Archives the project and its descendants 317 # Archives the project and its descendants
307 def archive 318 def archive
308 # Check that there is no issue of a non descendant project that is assigned 319 # Check that there is no issue of a non descendant project that is assigned
309 # to one of the project or descendant versions 320 # to one of the project or descendant versions
310 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten 321 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
311 if v_ids.any? && Issue.find(:first, :include => :project, 322 if v_ids.any? &&
312 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" + 323 Issue.
313 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids]) 324 includes(:project).
325 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
326 where("#{Issue.table_name}.fixed_version_id IN (?)", v_ids).
327 exists?
314 return false 328 return false
315 end 329 end
316 Project.transaction do 330 Project.transaction do
317 archive! 331 archive!
318 end 332 end
336 350
337 # Returns an array of projects the project can be moved to 351 # Returns an array of projects the project can be moved to
338 # by the current user 352 # by the current user
339 def allowed_parents 353 def allowed_parents
340 return @allowed_parents if @allowed_parents 354 return @allowed_parents if @allowed_parents
341 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) 355 @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).all
342 @allowed_parents = @allowed_parents - self_and_descendants 356 @allowed_parents = @allowed_parents - self_and_descendants
343 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) 357 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
344 @allowed_parents << nil 358 @allowed_parents << nil
345 end 359 end
346 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent) 360 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
404 end 418 end
405 419
406 # Returns an array of the trackers used by the project and its active sub projects 420 # Returns an array of the trackers used by the project and its active sub projects
407 def rolled_up_trackers 421 def rolled_up_trackers
408 @rolled_up_trackers ||= 422 @rolled_up_trackers ||=
409 Tracker.find(:all, :joins => :projects, 423 Tracker.
410 :select => "DISTINCT #{Tracker.table_name}.*", 424 joins(:projects).
411 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt], 425 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
412 :order => "#{Tracker.table_name}.position") 426 select("DISTINCT #{Tracker.table_name}.*").
427 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt).
428 sorted.
429 all
413 end 430 end
414 431
415 # Closes open and locked project versions that are completed 432 # Closes open and locked project versions that are completed
416 def close_completed_versions 433 def close_completed_versions
417 Version.transaction do 434 Version.transaction do
418 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version| 435 versions.where(:status => %w(open locked)).all.each do |version|
419 if version.completed? 436 if version.completed?
420 version.update_attribute(:status, 'closed') 437 version.update_attribute(:status, 'closed')
421 end 438 end
422 end 439 end
423 end 440 end
450 end 467 end
451 end 468 end
452 469
453 # Returns a hash of project users grouped by role 470 # Returns a hash of project users grouped by role
454 def users_by_role 471 def users_by_role
455 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| 472 members.includes(:user, :roles).all.inject({}) do |h, m|
456 m.roles.each do |r| 473 m.roles.each do |r|
457 h[r] ||= [] 474 h[r] ||= []
458 h[r] << m.user 475 h[r] << m.user
459 end 476 end
460 h 477 h
529 s 546 s
530 end 547 end
531 548
532 # The earliest start date of a project, based on it's issues and versions 549 # The earliest start date of a project, based on it's issues and versions
533 def start_date 550 def start_date
534 [ 551 @start_date ||= [
535 issues.minimum('start_date'), 552 issues.minimum('start_date'),
536 shared_versions.collect(&:effective_date), 553 shared_versions.minimum('effective_date'),
537 shared_versions.collect(&:start_date) 554 Issue.fixed_version(shared_versions).minimum('start_date')
538 ].flatten.compact.min 555 ].compact.min
539 end 556 end
540 557
541 # The latest due date of an issue or version 558 # The latest due date of an issue or version
542 def due_date 559 def due_date
543 [ 560 @due_date ||= [
544 issues.maximum('due_date'), 561 issues.maximum('due_date'),
545 shared_versions.collect(&:effective_date), 562 shared_versions.maximum('effective_date'),
546 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} 563 Issue.fixed_version(shared_versions).maximum('due_date')
547 ].flatten.compact.max 564 ].compact.max
548 end 565 end
549 566
550 def overdue? 567 def overdue?
551 active? && !due_date.nil? && (due_date < Date.today) 568 active? && !due_date.nil? && (due_date < Date.today)
552 end 569 end
558 total = self_and_descendants.collect(&:completed_percent).sum 575 total = self_and_descendants.collect(&:completed_percent).sum
559 576
560 total / self_and_descendants.count 577 total / self_and_descendants.count
561 else 578 else
562 if versions.count > 0 579 if versions.count > 0
563 total = versions.collect(&:completed_pourcent).sum 580 total = versions.collect(&:completed_percent).sum
564 581
565 total / versions.count 582 total / versions.count
566 else 583 else
567 100 584 100
568 end 585 end
640 'issue_custom_field_ids' 657 'issue_custom_field_ids'
641 658
642 safe_attributes 'enabled_module_names', 659 safe_attributes 'enabled_module_names',
643 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } 660 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
644 661
662 safe_attributes 'inherit_members',
663 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
664
645 # Returns an array of projects that are in this project's hierarchy 665 # Returns an array of projects that are in this project's hierarchy
646 # 666 #
647 # Example: parents, children, siblings 667 # Example: parents, children, siblings
648 def hierarchy 668 def hierarchy
649 parents = project.self_and_ancestors || [] 669 parents = project.self_and_ancestors || []
651 project_hierarchy = parents | descendants # Set union 671 project_hierarchy = parents | descendants # Set union
652 end 672 end
653 673
654 # Returns an auto-generated project identifier based on the last identifier used 674 # Returns an auto-generated project identifier based on the last identifier used
655 def self.next_identifier 675 def self.next_identifier
656 p = Project.find(:first, :order => 'created_on DESC') 676 p = Project.order('id DESC').first
657 p.nil? ? nil : p.identifier.to_s.succ 677 p.nil? ? nil : p.identifier.to_s.succ
658 end 678 end
659 679
660 # Copies and saves the Project instance based on the +project+. 680 # Copies and saves the Project instance based on the +project+.
661 # Duplicates the source project's: 681 # Duplicates the source project's:
688 save 708 save
689 end 709 end
690 end 710 end
691 end 711 end
692 712
693 713 # Returns a new unsaved Project instance with attributes copied from +project+
694 # Copies +project+ and returns the new instance. This will not save
695 # the copy
696 def self.copy_from(project) 714 def self.copy_from(project)
697 begin 715 project = project.is_a?(Project) ? project : Project.find(project)
698 project = project.is_a?(Project) ? project : Project.find(project) 716 # clear unique attributes
699 if project 717 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
700 # clear unique attributes 718 copy = Project.new(attributes)
701 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') 719 copy.enabled_modules = project.enabled_modules
702 copy = Project.new(attributes) 720 copy.trackers = project.trackers
703 copy.enabled_modules = project.enabled_modules 721 copy.custom_values = project.custom_values.collect {|v| v.clone}
704 copy.trackers = project.trackers 722 copy.issue_custom_fields = project.issue_custom_fields
705 copy.custom_values = project.custom_values.collect {|v| v.clone} 723 copy
706 copy.issue_custom_fields = project.issue_custom_fields
707 return copy
708 else
709 return nil
710 end
711 rescue ActiveRecord::RecordNotFound
712 return nil
713 end
714 end 724 end
715 725
716 # Yields the given block for each project with its level in the tree 726 # Yields the given block for each project with its level in the tree
717 def self.project_tree(projects, &block) 727 def self.project_tree(projects, &block)
718 ancestors = [] 728 ancestors = []
724 ancestors << project 734 ancestors << project
725 end 735 end
726 end 736 end
727 737
728 private 738 private
739
740 def after_parent_changed(parent_was)
741 remove_inherited_member_roles
742 add_inherited_member_roles
743 end
744
745 def update_inherited_members
746 if parent
747 if inherit_members? && !inherit_members_was
748 remove_inherited_member_roles
749 add_inherited_member_roles
750 elsif !inherit_members? && inherit_members_was
751 remove_inherited_member_roles
752 end
753 end
754 end
755
756 def remove_inherited_member_roles
757 member_roles = memberships.map(&:member_roles).flatten
758 member_role_ids = member_roles.map(&:id)
759 member_roles.each do |member_role|
760 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
761 member_role.destroy
762 end
763 end
764 end
765
766 def add_inherited_member_roles
767 if inherit_members? && parent
768 parent.memberships.each do |parent_member|
769 member = Member.find_or_new(self.id, parent_member.user_id)
770 parent_member.member_roles.each do |parent_member_role|
771 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
772 end
773 member.save!
774 end
775 end
776 end
729 777
730 # Copies wiki from +project+ 778 # Copies wiki from +project+
731 def copy_wiki(project) 779 def copy_wiki(project)
732 # Check that the source project has a wiki first 780 # Check that the source project has a wiki first
733 unless project.wiki.nil? 781 unless project.wiki.nil?
786 version.update_attribute :status, 'open' 834 version.update_attribute :status, 'open'
787 end 835 end
788 836
789 # Get issues sorted by root_id, lft so that parent issues 837 # Get issues sorted by root_id, lft so that parent issues
790 # get copied before their children 838 # get copied before their children
791 project.issues.find(:all, :order => 'root_id, lft').each do |issue| 839 project.issues.reorder('root_id, lft').all.each do |issue|
792 new_issue = Issue.new 840 new_issue = Issue.new
793 new_issue.copy_from(issue, :subtasks => false, :link => false) 841 new_issue.copy_from(issue, :subtasks => false, :link => false)
794 new_issue.project = self 842 new_issue.project = self
843 # Changing project resets the custom field values
844 # TODO: handle this in Issue#project=
845 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
795 # Reassign fixed_versions by name, since names are unique per project 846 # Reassign fixed_versions by name, since names are unique per project
796 if issue.fixed_version && issue.fixed_version.project == project 847 if issue.fixed_version && issue.fixed_version.project == project
797 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name} 848 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
798 end 849 end
799 # Reassign the category by name, since names are unique per project 850 # Reassign the category by name, since names are unique per project
872 end 923 end
873 924
874 # Copies queries from +project+ 925 # Copies queries from +project+
875 def copy_queries(project) 926 def copy_queries(project)
876 project.queries.each do |query| 927 project.queries.each do |query|
877 new_query = ::Query.new 928 new_query = IssueQuery.new
878 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") 929 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
879 new_query.sort_criteria = query.sort_criteria if query.sort_criteria 930 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
880 new_query.project = self 931 new_query.project = self
881 new_query.user_id = query.user_id 932 new_query.user_id = query.user_id
882 self.queries << new_query 933 self.queries << new_query
929 980
930 # Returns the systemwide active activities merged with the project specific overrides 981 # Returns the systemwide active activities merged with the project specific overrides
931 def system_activities_and_project_overrides(include_inactive=false) 982 def system_activities_and_project_overrides(include_inactive=false)
932 if include_inactive 983 if include_inactive
933 return TimeEntryActivity.shared. 984 return TimeEntryActivity.shared.
934 find(:all, 985 where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
935 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
936 self.time_entry_activities 986 self.time_entry_activities
937 else 987 else
938 return TimeEntryActivity.shared.active. 988 return TimeEntryActivity.shared.active.
939 find(:all, 989 where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
940 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
941 self.time_entry_activities.active 990 self.time_entry_activities.active
942 end 991 end
943 end 992 end
944 993
945 # Archives subprojects recursively 994 # Archives subprojects recursively
954 set_or_update_position_under(parent) 1003 set_or_update_position_under(parent)
955 end 1004 end
956 1005
957 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted 1006 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted
958 def set_or_update_position_under(target_parent) 1007 def set_or_update_position_under(target_parent)
1008 parent_was = parent
959 sibs = (target_parent.nil? ? self.class.roots : target_parent.children) 1009 sibs = (target_parent.nil? ? self.class.roots : target_parent.children)
960 to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase } 1010 to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
961 1011
962 if to_be_inserted_before 1012 if to_be_inserted_before
963 move_to_left_of(to_be_inserted_before) 1013 move_to_left_of(to_be_inserted_before)
970 end 1020 end
971 else 1021 else
972 # move_to_child_of adds the project in last (ie.right) position 1022 # move_to_child_of adds the project in last (ie.right) position
973 move_to_child_of(target_parent) 1023 move_to_child_of(target_parent)
974 end 1024 end
1025 if parent_was != target_parent
1026 after_parent_changed(parent_was)
1027 end
975 end 1028 end
976 end 1029 end