##// END OF EJS Templates
Project#activities should check all overridden activities, not just active ones....
Eric Davis -
r3125:9fb40b1a2f70
parent child
Show More
@@ -1,672 +1,672
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities
24 has_many :time_entry_activities
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
26 has_many :member_principals, :class_name => 'Member',
26 has_many :member_principals, :class_name => 'Member',
27 :include => :principal,
27 :include => :principal,
28 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
28 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
29 has_many :users, :through => :members
29 has_many :users, :through => :members
30 has_many :principals, :through => :member_principals, :source => :principal
30 has_many :principals, :through => :member_principals, :source => :principal
31
31
32 has_many :enabled_modules, :dependent => :delete_all
32 has_many :enabled_modules, :dependent => :delete_all
33 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
33 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
34 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
34 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
35 has_many :issue_changes, :through => :issues, :source => :journals
35 has_many :issue_changes, :through => :issues, :source => :journals
36 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
36 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
37 has_many :time_entries, :dependent => :delete_all
37 has_many :time_entries, :dependent => :delete_all
38 has_many :queries, :dependent => :delete_all
38 has_many :queries, :dependent => :delete_all
39 has_many :documents, :dependent => :destroy
39 has_many :documents, :dependent => :destroy
40 has_many :news, :dependent => :delete_all, :include => :author
40 has_many :news, :dependent => :delete_all, :include => :author
41 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
41 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
42 has_many :boards, :dependent => :destroy, :order => "position ASC"
42 has_many :boards, :dependent => :destroy, :order => "position ASC"
43 has_one :repository, :dependent => :destroy
43 has_one :repository, :dependent => :destroy
44 has_many :changesets, :through => :repository
44 has_many :changesets, :through => :repository
45 has_one :wiki, :dependent => :destroy
45 has_one :wiki, :dependent => :destroy
46 # Custom field for the project issues
46 # Custom field for the project issues
47 has_and_belongs_to_many :issue_custom_fields,
47 has_and_belongs_to_many :issue_custom_fields,
48 :class_name => 'IssueCustomField',
48 :class_name => 'IssueCustomField',
49 :order => "#{CustomField.table_name}.position",
49 :order => "#{CustomField.table_name}.position",
50 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
50 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
51 :association_foreign_key => 'custom_field_id'
51 :association_foreign_key => 'custom_field_id'
52
52
53 acts_as_nested_set :order => 'name', :dependent => :destroy
53 acts_as_nested_set :order => 'name', :dependent => :destroy
54 acts_as_attachable :view_permission => :view_files,
54 acts_as_attachable :view_permission => :view_files,
55 :delete_permission => :manage_files
55 :delete_permission => :manage_files
56
56
57 acts_as_customizable
57 acts_as_customizable
58 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
58 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
59 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
59 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
60 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
60 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
61 :author => nil
61 :author => nil
62
62
63 attr_protected :status, :enabled_module_names
63 attr_protected :status, :enabled_module_names
64
64
65 validates_presence_of :name, :identifier
65 validates_presence_of :name, :identifier
66 validates_uniqueness_of :name, :identifier
66 validates_uniqueness_of :name, :identifier
67 validates_associated :repository, :wiki
67 validates_associated :repository, :wiki
68 validates_length_of :name, :maximum => 30
68 validates_length_of :name, :maximum => 30
69 validates_length_of :homepage, :maximum => 255
69 validates_length_of :homepage, :maximum => 255
70 validates_length_of :identifier, :in => 1..20
70 validates_length_of :identifier, :in => 1..20
71 # donwcase letters, digits, dashes but not digits only
71 # donwcase letters, digits, dashes but not digits only
72 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
72 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
73 # reserved words
73 # reserved words
74 validates_exclusion_of :identifier, :in => %w( new )
74 validates_exclusion_of :identifier, :in => %w( new )
75
75
76 before_destroy :delete_all_members
76 before_destroy :delete_all_members
77
77
78 named_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] } }
78 named_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] } }
79 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
79 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
80 named_scope :all_public, { :conditions => { :is_public => true } }
80 named_scope :all_public, { :conditions => { :is_public => true } }
81 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
81 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
82
82
83 def identifier=(identifier)
83 def identifier=(identifier)
84 super unless identifier_frozen?
84 super unless identifier_frozen?
85 end
85 end
86
86
87 def identifier_frozen?
87 def identifier_frozen?
88 errors[:identifier].nil? && !(new_record? || identifier.blank?)
88 errors[:identifier].nil? && !(new_record? || identifier.blank?)
89 end
89 end
90
90
91 # returns latest created projects
91 # returns latest created projects
92 # non public projects will be returned only if user is a member of those
92 # non public projects will be returned only if user is a member of those
93 def self.latest(user=nil, count=5)
93 def self.latest(user=nil, count=5)
94 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
94 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
95 end
95 end
96
96
97 # Returns a SQL :conditions string used to find all active projects for the specified user.
97 # Returns a SQL :conditions string used to find all active projects for the specified user.
98 #
98 #
99 # Examples:
99 # Examples:
100 # Projects.visible_by(admin) => "projects.status = 1"
100 # Projects.visible_by(admin) => "projects.status = 1"
101 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
101 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
102 def self.visible_by(user=nil)
102 def self.visible_by(user=nil)
103 user ||= User.current
103 user ||= User.current
104 if user && user.admin?
104 if user && user.admin?
105 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
105 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
106 elsif user && user.memberships.any?
106 elsif user && user.memberships.any?
107 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
107 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
108 else
108 else
109 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
109 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
110 end
110 end
111 end
111 end
112
112
113 def self.allowed_to_condition(user, permission, options={})
113 def self.allowed_to_condition(user, permission, options={})
114 statements = []
114 statements = []
115 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
115 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
116 if perm = Redmine::AccessControl.permission(permission)
116 if perm = Redmine::AccessControl.permission(permission)
117 unless perm.project_module.nil?
117 unless perm.project_module.nil?
118 # If the permission belongs to a project module, make sure the module is enabled
118 # If the permission belongs to a project module, make sure the module is enabled
119 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
119 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
120 end
120 end
121 end
121 end
122 if options[:project]
122 if options[:project]
123 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
123 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
124 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
124 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
125 base_statement = "(#{project_statement}) AND (#{base_statement})"
125 base_statement = "(#{project_statement}) AND (#{base_statement})"
126 end
126 end
127 if user.admin?
127 if user.admin?
128 # no restriction
128 # no restriction
129 else
129 else
130 statements << "1=0"
130 statements << "1=0"
131 if user.logged?
131 if user.logged?
132 if Role.non_member.allowed_to?(permission) && !options[:member]
132 if Role.non_member.allowed_to?(permission) && !options[:member]
133 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
133 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
134 end
134 end
135 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
135 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
136 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
136 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
137 else
137 else
138 if Role.anonymous.allowed_to?(permission) && !options[:member]
138 if Role.anonymous.allowed_to?(permission) && !options[:member]
139 # anonymous user allowed on public project
139 # anonymous user allowed on public project
140 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
140 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
141 end
141 end
142 end
142 end
143 end
143 end
144 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
144 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
145 end
145 end
146
146
147 # Returns the Systemwide and project specific activities
147 # Returns the Systemwide and project specific activities
148 def activities(include_inactive=false)
148 def activities(include_inactive=false)
149 if include_inactive
149 if include_inactive
150 return all_activities
150 return all_activities
151 else
151 else
152 return active_activities
152 return active_activities
153 end
153 end
154 end
154 end
155
155
156 # Will create a new Project specific Activity or update an existing one
156 # Will create a new Project specific Activity or update an existing one
157 #
157 #
158 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
158 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
159 # does not successfully save.
159 # does not successfully save.
160 def update_or_create_time_entry_activity(id, activity_hash)
160 def update_or_create_time_entry_activity(id, activity_hash)
161 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
161 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
162 self.create_time_entry_activity_if_needed(activity_hash)
162 self.create_time_entry_activity_if_needed(activity_hash)
163 else
163 else
164 activity = project.time_entry_activities.find_by_id(id.to_i)
164 activity = project.time_entry_activities.find_by_id(id.to_i)
165 activity.update_attributes(activity_hash) if activity
165 activity.update_attributes(activity_hash) if activity
166 end
166 end
167 end
167 end
168
168
169 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
169 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
170 #
170 #
171 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
171 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
172 # does not successfully save.
172 # does not successfully save.
173 def create_time_entry_activity_if_needed(activity)
173 def create_time_entry_activity_if_needed(activity)
174 if activity['parent_id']
174 if activity['parent_id']
175
175
176 parent_activity = TimeEntryActivity.find(activity['parent_id'])
176 parent_activity = TimeEntryActivity.find(activity['parent_id'])
177 activity['name'] = parent_activity.name
177 activity['name'] = parent_activity.name
178 activity['position'] = parent_activity.position
178 activity['position'] = parent_activity.position
179
179
180 if Enumeration.overridding_change?(activity, parent_activity)
180 if Enumeration.overridding_change?(activity, parent_activity)
181 project_activity = self.time_entry_activities.create(activity)
181 project_activity = self.time_entry_activities.create(activity)
182
182
183 if project_activity.new_record?
183 if project_activity.new_record?
184 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
184 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
185 else
185 else
186 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
186 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
187 end
187 end
188 end
188 end
189 end
189 end
190 end
190 end
191
191
192 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
192 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
193 #
193 #
194 # Examples:
194 # Examples:
195 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
195 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
196 # project.project_condition(false) => "projects.id = 1"
196 # project.project_condition(false) => "projects.id = 1"
197 def project_condition(with_subprojects)
197 def project_condition(with_subprojects)
198 cond = "#{Project.table_name}.id = #{id}"
198 cond = "#{Project.table_name}.id = #{id}"
199 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
199 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
200 cond
200 cond
201 end
201 end
202
202
203 def self.find(*args)
203 def self.find(*args)
204 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
204 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
205 project = find_by_identifier(*args)
205 project = find_by_identifier(*args)
206 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
206 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
207 project
207 project
208 else
208 else
209 super
209 super
210 end
210 end
211 end
211 end
212
212
213 def to_param
213 def to_param
214 # id is used for projects with a numeric identifier (compatibility)
214 # id is used for projects with a numeric identifier (compatibility)
215 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
215 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
216 end
216 end
217
217
218 def active?
218 def active?
219 self.status == STATUS_ACTIVE
219 self.status == STATUS_ACTIVE
220 end
220 end
221
221
222 # Archives the project and its descendants
222 # Archives the project and its descendants
223 def archive
223 def archive
224 # Check that there is no issue of a non descendant project that is assigned
224 # Check that there is no issue of a non descendant project that is assigned
225 # to one of the project or descendant versions
225 # to one of the project or descendant versions
226 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
226 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
227 if v_ids.any? && Issue.find(:first, :include => :project,
227 if v_ids.any? && Issue.find(:first, :include => :project,
228 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
228 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
229 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
229 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
230 return false
230 return false
231 end
231 end
232 Project.transaction do
232 Project.transaction do
233 archive!
233 archive!
234 end
234 end
235 true
235 true
236 end
236 end
237
237
238 # Unarchives the project
238 # Unarchives the project
239 # All its ancestors must be active
239 # All its ancestors must be active
240 def unarchive
240 def unarchive
241 return false if ancestors.detect {|a| !a.active?}
241 return false if ancestors.detect {|a| !a.active?}
242 update_attribute :status, STATUS_ACTIVE
242 update_attribute :status, STATUS_ACTIVE
243 end
243 end
244
244
245 # Returns an array of projects the project can be moved to
245 # Returns an array of projects the project can be moved to
246 # by the current user
246 # by the current user
247 def allowed_parents
247 def allowed_parents
248 return @allowed_parents if @allowed_parents
248 return @allowed_parents if @allowed_parents
249 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
249 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
250 @allowed_parents = @allowed_parents - self_and_descendants
250 @allowed_parents = @allowed_parents - self_and_descendants
251 if User.current.allowed_to?(:add_project, nil, :global => true)
251 if User.current.allowed_to?(:add_project, nil, :global => true)
252 @allowed_parents << nil
252 @allowed_parents << nil
253 end
253 end
254 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
254 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
255 @allowed_parents << parent
255 @allowed_parents << parent
256 end
256 end
257 @allowed_parents
257 @allowed_parents
258 end
258 end
259
259
260 # Sets the parent of the project with authorization check
260 # Sets the parent of the project with authorization check
261 def set_allowed_parent!(p)
261 def set_allowed_parent!(p)
262 unless p.nil? || p.is_a?(Project)
262 unless p.nil? || p.is_a?(Project)
263 if p.to_s.blank?
263 if p.to_s.blank?
264 p = nil
264 p = nil
265 else
265 else
266 p = Project.find_by_id(p)
266 p = Project.find_by_id(p)
267 return false unless p
267 return false unless p
268 end
268 end
269 end
269 end
270 if p.nil?
270 if p.nil?
271 if !new_record? && allowed_parents.empty?
271 if !new_record? && allowed_parents.empty?
272 return false
272 return false
273 end
273 end
274 elsif !allowed_parents.include?(p)
274 elsif !allowed_parents.include?(p)
275 return false
275 return false
276 end
276 end
277 set_parent!(p)
277 set_parent!(p)
278 end
278 end
279
279
280 # Sets the parent of the project
280 # Sets the parent of the project
281 # Argument can be either a Project, a String, a Fixnum or nil
281 # Argument can be either a Project, a String, a Fixnum or nil
282 def set_parent!(p)
282 def set_parent!(p)
283 unless p.nil? || p.is_a?(Project)
283 unless p.nil? || p.is_a?(Project)
284 if p.to_s.blank?
284 if p.to_s.blank?
285 p = nil
285 p = nil
286 else
286 else
287 p = Project.find_by_id(p)
287 p = Project.find_by_id(p)
288 return false unless p
288 return false unless p
289 end
289 end
290 end
290 end
291 if p == parent && !p.nil?
291 if p == parent && !p.nil?
292 # Nothing to do
292 # Nothing to do
293 true
293 true
294 elsif p.nil? || (p.active? && move_possible?(p))
294 elsif p.nil? || (p.active? && move_possible?(p))
295 # Insert the project so that target's children or root projects stay alphabetically sorted
295 # Insert the project so that target's children or root projects stay alphabetically sorted
296 sibs = (p.nil? ? self.class.roots : p.children)
296 sibs = (p.nil? ? self.class.roots : p.children)
297 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
297 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
298 if to_be_inserted_before
298 if to_be_inserted_before
299 move_to_left_of(to_be_inserted_before)
299 move_to_left_of(to_be_inserted_before)
300 elsif p.nil?
300 elsif p.nil?
301 if sibs.empty?
301 if sibs.empty?
302 # move_to_root adds the project in first (ie. left) position
302 # move_to_root adds the project in first (ie. left) position
303 move_to_root
303 move_to_root
304 else
304 else
305 move_to_right_of(sibs.last) unless self == sibs.last
305 move_to_right_of(sibs.last) unless self == sibs.last
306 end
306 end
307 else
307 else
308 # move_to_child_of adds the project in last (ie.right) position
308 # move_to_child_of adds the project in last (ie.right) position
309 move_to_child_of(p)
309 move_to_child_of(p)
310 end
310 end
311 Issue.update_versions_from_hierarchy_change(self)
311 Issue.update_versions_from_hierarchy_change(self)
312 true
312 true
313 else
313 else
314 # Can not move to the given target
314 # Can not move to the given target
315 false
315 false
316 end
316 end
317 end
317 end
318
318
319 # Returns an array of the trackers used by the project and its active sub projects
319 # Returns an array of the trackers used by the project and its active sub projects
320 def rolled_up_trackers
320 def rolled_up_trackers
321 @rolled_up_trackers ||=
321 @rolled_up_trackers ||=
322 Tracker.find(:all, :include => :projects,
322 Tracker.find(:all, :include => :projects,
323 :select => "DISTINCT #{Tracker.table_name}.*",
323 :select => "DISTINCT #{Tracker.table_name}.*",
324 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
324 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
325 :order => "#{Tracker.table_name}.position")
325 :order => "#{Tracker.table_name}.position")
326 end
326 end
327
327
328 # Closes open and locked project versions that are completed
328 # Closes open and locked project versions that are completed
329 def close_completed_versions
329 def close_completed_versions
330 Version.transaction do
330 Version.transaction do
331 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
331 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
332 if version.completed?
332 if version.completed?
333 version.update_attribute(:status, 'closed')
333 version.update_attribute(:status, 'closed')
334 end
334 end
335 end
335 end
336 end
336 end
337 end
337 end
338
338
339 # Returns a scope of the Versions used by the project
339 # Returns a scope of the Versions used by the project
340 def shared_versions
340 def shared_versions
341 @shared_versions ||=
341 @shared_versions ||=
342 Version.scoped(:include => :project,
342 Version.scoped(:include => :project,
343 :conditions => "#{Project.table_name}.id = #{id}" +
343 :conditions => "#{Project.table_name}.id = #{id}" +
344 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
344 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
345 " #{Version.table_name}.sharing = 'system'" +
345 " #{Version.table_name}.sharing = 'system'" +
346 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
346 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
347 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
347 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
348 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
348 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
349 "))")
349 "))")
350 end
350 end
351
351
352 # Returns a hash of project users grouped by role
352 # Returns a hash of project users grouped by role
353 def users_by_role
353 def users_by_role
354 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
354 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
355 m.roles.each do |r|
355 m.roles.each do |r|
356 h[r] ||= []
356 h[r] ||= []
357 h[r] << m.user
357 h[r] << m.user
358 end
358 end
359 h
359 h
360 end
360 end
361 end
361 end
362
362
363 # Deletes all project's members
363 # Deletes all project's members
364 def delete_all_members
364 def delete_all_members
365 me, mr = Member.table_name, MemberRole.table_name
365 me, mr = Member.table_name, MemberRole.table_name
366 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
366 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
367 Member.delete_all(['project_id = ?', id])
367 Member.delete_all(['project_id = ?', id])
368 end
368 end
369
369
370 # Users issues can be assigned to
370 # Users issues can be assigned to
371 def assignable_users
371 def assignable_users
372 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
372 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
373 end
373 end
374
374
375 # Returns the mail adresses of users that should be always notified on project events
375 # Returns the mail adresses of users that should be always notified on project events
376 def recipients
376 def recipients
377 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
377 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
378 end
378 end
379
379
380 # Returns the users that should be notified on project events
380 # Returns the users that should be notified on project events
381 def notified_users
381 def notified_users
382 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
382 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
383 end
383 end
384
384
385 # Returns an array of all custom fields enabled for project issues
385 # Returns an array of all custom fields enabled for project issues
386 # (explictly associated custom fields and custom fields enabled for all projects)
386 # (explictly associated custom fields and custom fields enabled for all projects)
387 def all_issue_custom_fields
387 def all_issue_custom_fields
388 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
388 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
389 end
389 end
390
390
391 def project
391 def project
392 self
392 self
393 end
393 end
394
394
395 def <=>(project)
395 def <=>(project)
396 name.downcase <=> project.name.downcase
396 name.downcase <=> project.name.downcase
397 end
397 end
398
398
399 def to_s
399 def to_s
400 name
400 name
401 end
401 end
402
402
403 # Returns a short description of the projects (first lines)
403 # Returns a short description of the projects (first lines)
404 def short_description(length = 255)
404 def short_description(length = 255)
405 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
405 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
406 end
406 end
407
407
408 # Return true if this project is allowed to do the specified action.
408 # Return true if this project is allowed to do the specified action.
409 # action can be:
409 # action can be:
410 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
410 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
411 # * a permission Symbol (eg. :edit_project)
411 # * a permission Symbol (eg. :edit_project)
412 def allows_to?(action)
412 def allows_to?(action)
413 if action.is_a? Hash
413 if action.is_a? Hash
414 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
414 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
415 else
415 else
416 allowed_permissions.include? action
416 allowed_permissions.include? action
417 end
417 end
418 end
418 end
419
419
420 def module_enabled?(module_name)
420 def module_enabled?(module_name)
421 module_name = module_name.to_s
421 module_name = module_name.to_s
422 enabled_modules.detect {|m| m.name == module_name}
422 enabled_modules.detect {|m| m.name == module_name}
423 end
423 end
424
424
425 def enabled_module_names=(module_names)
425 def enabled_module_names=(module_names)
426 if module_names && module_names.is_a?(Array)
426 if module_names && module_names.is_a?(Array)
427 module_names = module_names.collect(&:to_s)
427 module_names = module_names.collect(&:to_s)
428 # remove disabled modules
428 # remove disabled modules
429 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
429 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
430 # add new modules
430 # add new modules
431 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
431 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
432 else
432 else
433 enabled_modules.clear
433 enabled_modules.clear
434 end
434 end
435 end
435 end
436
436
437 # Returns an auto-generated project identifier based on the last identifier used
437 # Returns an auto-generated project identifier based on the last identifier used
438 def self.next_identifier
438 def self.next_identifier
439 p = Project.find(:first, :order => 'created_on DESC')
439 p = Project.find(:first, :order => 'created_on DESC')
440 p.nil? ? nil : p.identifier.to_s.succ
440 p.nil? ? nil : p.identifier.to_s.succ
441 end
441 end
442
442
443 # Copies and saves the Project instance based on the +project+.
443 # Copies and saves the Project instance based on the +project+.
444 # Duplicates the source project's:
444 # Duplicates the source project's:
445 # * Wiki
445 # * Wiki
446 # * Versions
446 # * Versions
447 # * Categories
447 # * Categories
448 # * Issues
448 # * Issues
449 # * Members
449 # * Members
450 # * Queries
450 # * Queries
451 #
451 #
452 # Accepts an +options+ argument to specify what to copy
452 # Accepts an +options+ argument to specify what to copy
453 #
453 #
454 # Examples:
454 # Examples:
455 # project.copy(1) # => copies everything
455 # project.copy(1) # => copies everything
456 # project.copy(1, :only => 'members') # => copies members only
456 # project.copy(1, :only => 'members') # => copies members only
457 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
457 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
458 def copy(project, options={})
458 def copy(project, options={})
459 project = project.is_a?(Project) ? project : Project.find(project)
459 project = project.is_a?(Project) ? project : Project.find(project)
460
460
461 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
461 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
462 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
462 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
463
463
464 Project.transaction do
464 Project.transaction do
465 if save
465 if save
466 reload
466 reload
467 to_be_copied.each do |name|
467 to_be_copied.each do |name|
468 send "copy_#{name}", project
468 send "copy_#{name}", project
469 end
469 end
470 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
470 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
471 save
471 save
472 end
472 end
473 end
473 end
474 end
474 end
475
475
476
476
477 # Copies +project+ and returns the new instance. This will not save
477 # Copies +project+ and returns the new instance. This will not save
478 # the copy
478 # the copy
479 def self.copy_from(project)
479 def self.copy_from(project)
480 begin
480 begin
481 project = project.is_a?(Project) ? project : Project.find(project)
481 project = project.is_a?(Project) ? project : Project.find(project)
482 if project
482 if project
483 # clear unique attributes
483 # clear unique attributes
484 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
484 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
485 copy = Project.new(attributes)
485 copy = Project.new(attributes)
486 copy.enabled_modules = project.enabled_modules
486 copy.enabled_modules = project.enabled_modules
487 copy.trackers = project.trackers
487 copy.trackers = project.trackers
488 copy.custom_values = project.custom_values.collect {|v| v.clone}
488 copy.custom_values = project.custom_values.collect {|v| v.clone}
489 copy.issue_custom_fields = project.issue_custom_fields
489 copy.issue_custom_fields = project.issue_custom_fields
490 return copy
490 return copy
491 else
491 else
492 return nil
492 return nil
493 end
493 end
494 rescue ActiveRecord::RecordNotFound
494 rescue ActiveRecord::RecordNotFound
495 return nil
495 return nil
496 end
496 end
497 end
497 end
498
498
499 private
499 private
500
500
501 # Copies wiki from +project+
501 # Copies wiki from +project+
502 def copy_wiki(project)
502 def copy_wiki(project)
503 # Check that the source project has a wiki first
503 # Check that the source project has a wiki first
504 unless project.wiki.nil?
504 unless project.wiki.nil?
505 self.wiki ||= Wiki.new
505 self.wiki ||= Wiki.new
506 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
506 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
507 project.wiki.pages.each do |page|
507 project.wiki.pages.each do |page|
508 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
508 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
509 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
509 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
510 new_wiki_page.content = new_wiki_content
510 new_wiki_page.content = new_wiki_content
511 wiki.pages << new_wiki_page
511 wiki.pages << new_wiki_page
512 end
512 end
513 end
513 end
514 end
514 end
515
515
516 # Copies versions from +project+
516 # Copies versions from +project+
517 def copy_versions(project)
517 def copy_versions(project)
518 project.versions.each do |version|
518 project.versions.each do |version|
519 new_version = Version.new
519 new_version = Version.new
520 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
520 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
521 self.versions << new_version
521 self.versions << new_version
522 end
522 end
523 end
523 end
524
524
525 # Copies issue categories from +project+
525 # Copies issue categories from +project+
526 def copy_issue_categories(project)
526 def copy_issue_categories(project)
527 project.issue_categories.each do |issue_category|
527 project.issue_categories.each do |issue_category|
528 new_issue_category = IssueCategory.new
528 new_issue_category = IssueCategory.new
529 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
529 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
530 self.issue_categories << new_issue_category
530 self.issue_categories << new_issue_category
531 end
531 end
532 end
532 end
533
533
534 # Copies issues from +project+
534 # Copies issues from +project+
535 def copy_issues(project)
535 def copy_issues(project)
536 # Stores the source issue id as a key and the copied issues as the
536 # Stores the source issue id as a key and the copied issues as the
537 # value. Used to map the two togeather for issue relations.
537 # value. Used to map the two togeather for issue relations.
538 issues_map = {}
538 issues_map = {}
539
539
540 project.issues.each do |issue|
540 project.issues.each do |issue|
541 new_issue = Issue.new
541 new_issue = Issue.new
542 new_issue.copy_from(issue)
542 new_issue.copy_from(issue)
543 # Reassign fixed_versions by name, since names are unique per
543 # Reassign fixed_versions by name, since names are unique per
544 # project and the versions for self are not yet saved
544 # project and the versions for self are not yet saved
545 if issue.fixed_version
545 if issue.fixed_version
546 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
546 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
547 end
547 end
548 # Reassign the category by name, since names are unique per
548 # Reassign the category by name, since names are unique per
549 # project and the categories for self are not yet saved
549 # project and the categories for self are not yet saved
550 if issue.category
550 if issue.category
551 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
551 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
552 end
552 end
553 self.issues << new_issue
553 self.issues << new_issue
554 issues_map[issue.id] = new_issue
554 issues_map[issue.id] = new_issue
555 end
555 end
556
556
557 # Relations after in case issues related each other
557 # Relations after in case issues related each other
558 project.issues.each do |issue|
558 project.issues.each do |issue|
559 new_issue = issues_map[issue.id]
559 new_issue = issues_map[issue.id]
560
560
561 # Relations
561 # Relations
562 issue.relations_from.each do |source_relation|
562 issue.relations_from.each do |source_relation|
563 new_issue_relation = IssueRelation.new
563 new_issue_relation = IssueRelation.new
564 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
564 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
565 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
565 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
566 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
566 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
567 new_issue_relation.issue_to = source_relation.issue_to
567 new_issue_relation.issue_to = source_relation.issue_to
568 end
568 end
569 new_issue.relations_from << new_issue_relation
569 new_issue.relations_from << new_issue_relation
570 end
570 end
571
571
572 issue.relations_to.each do |source_relation|
572 issue.relations_to.each do |source_relation|
573 new_issue_relation = IssueRelation.new
573 new_issue_relation = IssueRelation.new
574 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
574 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
575 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
575 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
576 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
576 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
577 new_issue_relation.issue_from = source_relation.issue_from
577 new_issue_relation.issue_from = source_relation.issue_from
578 end
578 end
579 new_issue.relations_to << new_issue_relation
579 new_issue.relations_to << new_issue_relation
580 end
580 end
581 end
581 end
582 end
582 end
583
583
584 # Copies members from +project+
584 # Copies members from +project+
585 def copy_members(project)
585 def copy_members(project)
586 project.members.each do |member|
586 project.members.each do |member|
587 new_member = Member.new
587 new_member = Member.new
588 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
588 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
589 new_member.role_ids = member.role_ids.dup
589 new_member.role_ids = member.role_ids.dup
590 new_member.project = self
590 new_member.project = self
591 self.members << new_member
591 self.members << new_member
592 end
592 end
593 end
593 end
594
594
595 # Copies queries from +project+
595 # Copies queries from +project+
596 def copy_queries(project)
596 def copy_queries(project)
597 project.queries.each do |query|
597 project.queries.each do |query|
598 new_query = Query.new
598 new_query = Query.new
599 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
599 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
600 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
600 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
601 new_query.project = self
601 new_query.project = self
602 self.queries << new_query
602 self.queries << new_query
603 end
603 end
604 end
604 end
605
605
606 # Copies boards from +project+
606 # Copies boards from +project+
607 def copy_boards(project)
607 def copy_boards(project)
608 project.boards.each do |board|
608 project.boards.each do |board|
609 new_board = Board.new
609 new_board = Board.new
610 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
610 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
611 new_board.project = self
611 new_board.project = self
612 self.boards << new_board
612 self.boards << new_board
613 end
613 end
614 end
614 end
615
615
616 def allowed_permissions
616 def allowed_permissions
617 @allowed_permissions ||= begin
617 @allowed_permissions ||= begin
618 module_names = enabled_modules.collect {|m| m.name}
618 module_names = enabled_modules.collect {|m| m.name}
619 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
619 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
620 end
620 end
621 end
621 end
622
622
623 def allowed_actions
623 def allowed_actions
624 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
624 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
625 end
625 end
626
626
627 # Returns all the active Systemwide and project specific activities
627 # Returns all the active Systemwide and project specific activities
628 def active_activities
628 def active_activities
629 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
629 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
630
630
631 if overridden_activity_ids.empty?
631 if overridden_activity_ids.empty?
632 return TimeEntryActivity.shared.active
632 return TimeEntryActivity.shared.active
633 else
633 else
634 return system_activities_and_project_overrides
634 return system_activities_and_project_overrides
635 end
635 end
636 end
636 end
637
637
638 # Returns all the Systemwide and project specific activities
638 # Returns all the Systemwide and project specific activities
639 # (inactive and active)
639 # (inactive and active)
640 def all_activities
640 def all_activities
641 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
641 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
642
642
643 if overridden_activity_ids.empty?
643 if overridden_activity_ids.empty?
644 return TimeEntryActivity.shared
644 return TimeEntryActivity.shared
645 else
645 else
646 return system_activities_and_project_overrides(true)
646 return system_activities_and_project_overrides(true)
647 end
647 end
648 end
648 end
649
649
650 # Returns the systemwide active activities merged with the project specific overrides
650 # Returns the systemwide active activities merged with the project specific overrides
651 def system_activities_and_project_overrides(include_inactive=false)
651 def system_activities_and_project_overrides(include_inactive=false)
652 if include_inactive
652 if include_inactive
653 return TimeEntryActivity.shared.
653 return TimeEntryActivity.shared.
654 find(:all,
654 find(:all,
655 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
655 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
656 self.time_entry_activities
656 self.time_entry_activities
657 else
657 else
658 return TimeEntryActivity.shared.active.
658 return TimeEntryActivity.shared.active.
659 find(:all,
659 find(:all,
660 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
660 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
661 self.time_entry_activities.active
661 self.time_entry_activities.active
662 end
662 end
663 end
663 end
664
664
665 # Archives subprojects recursively
665 # Archives subprojects recursively
666 def archive!
666 def archive!
667 children.each do |subproject|
667 children.each do |subproject|
668 subproject.send :archive!
668 subproject.send :archive!
669 end
669 end
670 update_attribute :status, STATUS_ARCHIVED
670 update_attribute :status, STATUS_ARCHIVED
671 end
671 end
672 end
672 end
@@ -1,730 +1,741
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :all
21 fixtures :all
22
22
23 def setup
23 def setup
24 @ecookbook = Project.find(1)
24 @ecookbook = Project.find(1)
25 @ecookbook_sub1 = Project.find(3)
25 @ecookbook_sub1 = Project.find(3)
26 User.current = nil
26 User.current = nil
27 end
27 end
28
28
29 should_validate_presence_of :name
29 should_validate_presence_of :name
30 should_validate_presence_of :identifier
30 should_validate_presence_of :identifier
31
31
32 should_validate_uniqueness_of :name
32 should_validate_uniqueness_of :name
33 should_validate_uniqueness_of :identifier
33 should_validate_uniqueness_of :identifier
34
34
35 context "associations" do
35 context "associations" do
36 should_have_many :members
36 should_have_many :members
37 should_have_many :users, :through => :members
37 should_have_many :users, :through => :members
38 should_have_many :member_principals
38 should_have_many :member_principals
39 should_have_many :principals, :through => :member_principals
39 should_have_many :principals, :through => :member_principals
40 should_have_many :enabled_modules
40 should_have_many :enabled_modules
41 should_have_many :issues
41 should_have_many :issues
42 should_have_many :issue_changes, :through => :issues
42 should_have_many :issue_changes, :through => :issues
43 should_have_many :versions
43 should_have_many :versions
44 should_have_many :time_entries
44 should_have_many :time_entries
45 should_have_many :queries
45 should_have_many :queries
46 should_have_many :documents
46 should_have_many :documents
47 should_have_many :news
47 should_have_many :news
48 should_have_many :issue_categories
48 should_have_many :issue_categories
49 should_have_many :boards
49 should_have_many :boards
50 should_have_many :changesets, :through => :repository
50 should_have_many :changesets, :through => :repository
51
51
52 should_have_one :repository
52 should_have_one :repository
53 should_have_one :wiki
53 should_have_one :wiki
54
54
55 should_have_and_belong_to_many :trackers
55 should_have_and_belong_to_many :trackers
56 should_have_and_belong_to_many :issue_custom_fields
56 should_have_and_belong_to_many :issue_custom_fields
57 end
57 end
58
58
59 def test_truth
59 def test_truth
60 assert_kind_of Project, @ecookbook
60 assert_kind_of Project, @ecookbook
61 assert_equal "eCookbook", @ecookbook.name
61 assert_equal "eCookbook", @ecookbook.name
62 end
62 end
63
63
64 def test_update
64 def test_update
65 assert_equal "eCookbook", @ecookbook.name
65 assert_equal "eCookbook", @ecookbook.name
66 @ecookbook.name = "eCook"
66 @ecookbook.name = "eCook"
67 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
67 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
68 @ecookbook.reload
68 @ecookbook.reload
69 assert_equal "eCook", @ecookbook.name
69 assert_equal "eCook", @ecookbook.name
70 end
70 end
71
71
72 def test_validate_identifier
72 def test_validate_identifier
73 to_test = {"abc" => true,
73 to_test = {"abc" => true,
74 "ab12" => true,
74 "ab12" => true,
75 "ab-12" => true,
75 "ab-12" => true,
76 "12" => false,
76 "12" => false,
77 "new" => false}
77 "new" => false}
78
78
79 to_test.each do |identifier, valid|
79 to_test.each do |identifier, valid|
80 p = Project.new
80 p = Project.new
81 p.identifier = identifier
81 p.identifier = identifier
82 p.valid?
82 p.valid?
83 assert_equal valid, p.errors.on('identifier').nil?
83 assert_equal valid, p.errors.on('identifier').nil?
84 end
84 end
85 end
85 end
86
86
87 def test_members_should_be_active_users
87 def test_members_should_be_active_users
88 Project.all.each do |project|
88 Project.all.each do |project|
89 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
89 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
90 end
90 end
91 end
91 end
92
92
93 def test_users_should_be_active_users
93 def test_users_should_be_active_users
94 Project.all.each do |project|
94 Project.all.each do |project|
95 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
95 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
96 end
96 end
97 end
97 end
98
98
99 def test_archive
99 def test_archive
100 user = @ecookbook.members.first.user
100 user = @ecookbook.members.first.user
101 @ecookbook.archive
101 @ecookbook.archive
102 @ecookbook.reload
102 @ecookbook.reload
103
103
104 assert !@ecookbook.active?
104 assert !@ecookbook.active?
105 assert !user.projects.include?(@ecookbook)
105 assert !user.projects.include?(@ecookbook)
106 # Subproject are also archived
106 # Subproject are also archived
107 assert !@ecookbook.children.empty?
107 assert !@ecookbook.children.empty?
108 assert @ecookbook.descendants.active.empty?
108 assert @ecookbook.descendants.active.empty?
109 end
109 end
110
110
111 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
111 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
112 # Assign an issue of a project to a version of a child project
112 # Assign an issue of a project to a version of a child project
113 Issue.find(4).update_attribute :fixed_version_id, 4
113 Issue.find(4).update_attribute :fixed_version_id, 4
114
114
115 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
115 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
116 assert_equal false, @ecookbook.archive
116 assert_equal false, @ecookbook.archive
117 end
117 end
118 @ecookbook.reload
118 @ecookbook.reload
119 assert @ecookbook.active?
119 assert @ecookbook.active?
120 end
120 end
121
121
122 def test_unarchive
122 def test_unarchive
123 user = @ecookbook.members.first.user
123 user = @ecookbook.members.first.user
124 @ecookbook.archive
124 @ecookbook.archive
125 # A subproject of an archived project can not be unarchived
125 # A subproject of an archived project can not be unarchived
126 assert !@ecookbook_sub1.unarchive
126 assert !@ecookbook_sub1.unarchive
127
127
128 # Unarchive project
128 # Unarchive project
129 assert @ecookbook.unarchive
129 assert @ecookbook.unarchive
130 @ecookbook.reload
130 @ecookbook.reload
131 assert @ecookbook.active?
131 assert @ecookbook.active?
132 assert user.projects.include?(@ecookbook)
132 assert user.projects.include?(@ecookbook)
133 # Subproject can now be unarchived
133 # Subproject can now be unarchived
134 @ecookbook_sub1.reload
134 @ecookbook_sub1.reload
135 assert @ecookbook_sub1.unarchive
135 assert @ecookbook_sub1.unarchive
136 end
136 end
137
137
138 def test_destroy
138 def test_destroy
139 # 2 active members
139 # 2 active members
140 assert_equal 2, @ecookbook.members.size
140 assert_equal 2, @ecookbook.members.size
141 # and 1 is locked
141 # and 1 is locked
142 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
142 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
143 # some boards
143 # some boards
144 assert @ecookbook.boards.any?
144 assert @ecookbook.boards.any?
145
145
146 @ecookbook.destroy
146 @ecookbook.destroy
147 # make sure that the project non longer exists
147 # make sure that the project non longer exists
148 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
148 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
149 # make sure related data was removed
149 # make sure related data was removed
150 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
150 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
151 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
151 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
152 end
152 end
153
153
154 def test_move_an_orphan_project_to_a_root_project
154 def test_move_an_orphan_project_to_a_root_project
155 sub = Project.find(2)
155 sub = Project.find(2)
156 sub.set_parent! @ecookbook
156 sub.set_parent! @ecookbook
157 assert_equal @ecookbook.id, sub.parent.id
157 assert_equal @ecookbook.id, sub.parent.id
158 @ecookbook.reload
158 @ecookbook.reload
159 assert_equal 4, @ecookbook.children.size
159 assert_equal 4, @ecookbook.children.size
160 end
160 end
161
161
162 def test_move_an_orphan_project_to_a_subproject
162 def test_move_an_orphan_project_to_a_subproject
163 sub = Project.find(2)
163 sub = Project.find(2)
164 assert sub.set_parent!(@ecookbook_sub1)
164 assert sub.set_parent!(@ecookbook_sub1)
165 end
165 end
166
166
167 def test_move_a_root_project_to_a_project
167 def test_move_a_root_project_to_a_project
168 sub = @ecookbook
168 sub = @ecookbook
169 assert sub.set_parent!(Project.find(2))
169 assert sub.set_parent!(Project.find(2))
170 end
170 end
171
171
172 def test_should_not_move_a_project_to_its_children
172 def test_should_not_move_a_project_to_its_children
173 sub = @ecookbook
173 sub = @ecookbook
174 assert !(sub.set_parent!(Project.find(3)))
174 assert !(sub.set_parent!(Project.find(3)))
175 end
175 end
176
176
177 def test_set_parent_should_add_roots_in_alphabetical_order
177 def test_set_parent_should_add_roots_in_alphabetical_order
178 ProjectCustomField.delete_all
178 ProjectCustomField.delete_all
179 Project.delete_all
179 Project.delete_all
180 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
180 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
181 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
181 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
182 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
182 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
183 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
183 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
184
184
185 assert_equal 4, Project.count
185 assert_equal 4, Project.count
186 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
186 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
187 end
187 end
188
188
189 def test_set_parent_should_add_children_in_alphabetical_order
189 def test_set_parent_should_add_children_in_alphabetical_order
190 ProjectCustomField.delete_all
190 ProjectCustomField.delete_all
191 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
191 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
192 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
192 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
193 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
193 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
194 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
194 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
195 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
195 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
196
196
197 parent.reload
197 parent.reload
198 assert_equal 4, parent.children.size
198 assert_equal 4, parent.children.size
199 assert_equal parent.children.sort_by(&:name), parent.children
199 assert_equal parent.children.sort_by(&:name), parent.children
200 end
200 end
201
201
202 def test_rebuild_should_sort_children_alphabetically
202 def test_rebuild_should_sort_children_alphabetically
203 ProjectCustomField.delete_all
203 ProjectCustomField.delete_all
204 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
204 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
205 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
205 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
206 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
206 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
207 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
207 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
208 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
208 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
209
209
210 Project.update_all("lft = NULL, rgt = NULL")
210 Project.update_all("lft = NULL, rgt = NULL")
211 Project.rebuild!
211 Project.rebuild!
212
212
213 parent.reload
213 parent.reload
214 assert_equal 4, parent.children.size
214 assert_equal 4, parent.children.size
215 assert_equal parent.children.sort_by(&:name), parent.children
215 assert_equal parent.children.sort_by(&:name), parent.children
216 end
216 end
217
217
218
218
219 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
219 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
220 # Parent issue with a hierarchy project's fixed version
220 # Parent issue with a hierarchy project's fixed version
221 parent_issue = Issue.find(1)
221 parent_issue = Issue.find(1)
222 parent_issue.update_attribute(:fixed_version_id, 4)
222 parent_issue.update_attribute(:fixed_version_id, 4)
223 parent_issue.reload
223 parent_issue.reload
224 assert_equal 4, parent_issue.fixed_version_id
224 assert_equal 4, parent_issue.fixed_version_id
225
225
226 # Should keep fixed versions for the issues
226 # Should keep fixed versions for the issues
227 issue_with_local_fixed_version = Issue.find(5)
227 issue_with_local_fixed_version = Issue.find(5)
228 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
228 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
229 issue_with_local_fixed_version.reload
229 issue_with_local_fixed_version.reload
230 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
230 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
231
231
232 # Local issue with hierarchy fixed_version
232 # Local issue with hierarchy fixed_version
233 issue_with_hierarchy_fixed_version = Issue.find(13)
233 issue_with_hierarchy_fixed_version = Issue.find(13)
234 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
234 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
235 issue_with_hierarchy_fixed_version.reload
235 issue_with_hierarchy_fixed_version.reload
236 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
236 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
237
237
238 # Move project out of the issue's hierarchy
238 # Move project out of the issue's hierarchy
239 moved_project = Project.find(3)
239 moved_project = Project.find(3)
240 moved_project.set_parent!(Project.find(2))
240 moved_project.set_parent!(Project.find(2))
241 parent_issue.reload
241 parent_issue.reload
242 issue_with_local_fixed_version.reload
242 issue_with_local_fixed_version.reload
243 issue_with_hierarchy_fixed_version.reload
243 issue_with_hierarchy_fixed_version.reload
244
244
245 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
245 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
246 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
246 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
247 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
247 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
248 end
248 end
249
249
250 def test_parent
250 def test_parent
251 p = Project.find(6).parent
251 p = Project.find(6).parent
252 assert p.is_a?(Project)
252 assert p.is_a?(Project)
253 assert_equal 5, p.id
253 assert_equal 5, p.id
254 end
254 end
255
255
256 def test_ancestors
256 def test_ancestors
257 a = Project.find(6).ancestors
257 a = Project.find(6).ancestors
258 assert a.first.is_a?(Project)
258 assert a.first.is_a?(Project)
259 assert_equal [1, 5], a.collect(&:id)
259 assert_equal [1, 5], a.collect(&:id)
260 end
260 end
261
261
262 def test_root
262 def test_root
263 r = Project.find(6).root
263 r = Project.find(6).root
264 assert r.is_a?(Project)
264 assert r.is_a?(Project)
265 assert_equal 1, r.id
265 assert_equal 1, r.id
266 end
266 end
267
267
268 def test_children
268 def test_children
269 c = Project.find(1).children
269 c = Project.find(1).children
270 assert c.first.is_a?(Project)
270 assert c.first.is_a?(Project)
271 assert_equal [5, 3, 4], c.collect(&:id)
271 assert_equal [5, 3, 4], c.collect(&:id)
272 end
272 end
273
273
274 def test_descendants
274 def test_descendants
275 d = Project.find(1).descendants
275 d = Project.find(1).descendants
276 assert d.first.is_a?(Project)
276 assert d.first.is_a?(Project)
277 assert_equal [5, 6, 3, 4], d.collect(&:id)
277 assert_equal [5, 6, 3, 4], d.collect(&:id)
278 end
278 end
279
279
280 def test_allowed_parents_should_be_empty_for_non_member_user
280 def test_allowed_parents_should_be_empty_for_non_member_user
281 Role.non_member.add_permission!(:add_project)
281 Role.non_member.add_permission!(:add_project)
282 user = User.find(9)
282 user = User.find(9)
283 assert user.memberships.empty?
283 assert user.memberships.empty?
284 User.current = user
284 User.current = user
285 assert Project.new.allowed_parents.compact.empty?
285 assert Project.new.allowed_parents.compact.empty?
286 end
286 end
287
287
288 def test_users_by_role
288 def test_users_by_role
289 users_by_role = Project.find(1).users_by_role
289 users_by_role = Project.find(1).users_by_role
290 assert_kind_of Hash, users_by_role
290 assert_kind_of Hash, users_by_role
291 role = Role.find(1)
291 role = Role.find(1)
292 assert_kind_of Array, users_by_role[role]
292 assert_kind_of Array, users_by_role[role]
293 assert users_by_role[role].include?(User.find(2))
293 assert users_by_role[role].include?(User.find(2))
294 end
294 end
295
295
296 def test_rolled_up_trackers
296 def test_rolled_up_trackers
297 parent = Project.find(1)
297 parent = Project.find(1)
298 parent.trackers = Tracker.find([1,2])
298 parent.trackers = Tracker.find([1,2])
299 child = parent.children.find(3)
299 child = parent.children.find(3)
300
300
301 assert_equal [1, 2], parent.tracker_ids
301 assert_equal [1, 2], parent.tracker_ids
302 assert_equal [2, 3], child.trackers.collect(&:id)
302 assert_equal [2, 3], child.trackers.collect(&:id)
303
303
304 assert_kind_of Tracker, parent.rolled_up_trackers.first
304 assert_kind_of Tracker, parent.rolled_up_trackers.first
305 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
305 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
306
306
307 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
307 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
308 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
308 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
309 end
309 end
310
310
311 def test_rolled_up_trackers_should_ignore_archived_subprojects
311 def test_rolled_up_trackers_should_ignore_archived_subprojects
312 parent = Project.find(1)
312 parent = Project.find(1)
313 parent.trackers = Tracker.find([1,2])
313 parent.trackers = Tracker.find([1,2])
314 child = parent.children.find(3)
314 child = parent.children.find(3)
315 child.trackers = Tracker.find([1,3])
315 child.trackers = Tracker.find([1,3])
316 parent.children.each(&:archive)
316 parent.children.each(&:archive)
317
317
318 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
318 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
319 end
319 end
320
320
321 def test_shared_versions_none_sharing
321 def test_shared_versions_none_sharing
322 p = Project.find(5)
322 p = Project.find(5)
323 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
323 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
324 assert p.shared_versions.include?(v)
324 assert p.shared_versions.include?(v)
325 assert !p.children.first.shared_versions.include?(v)
325 assert !p.children.first.shared_versions.include?(v)
326 assert !p.root.shared_versions.include?(v)
326 assert !p.root.shared_versions.include?(v)
327 assert !p.siblings.first.shared_versions.include?(v)
327 assert !p.siblings.first.shared_versions.include?(v)
328 assert !p.root.siblings.first.shared_versions.include?(v)
328 assert !p.root.siblings.first.shared_versions.include?(v)
329 end
329 end
330
330
331 def test_shared_versions_descendants_sharing
331 def test_shared_versions_descendants_sharing
332 p = Project.find(5)
332 p = Project.find(5)
333 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
333 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
334 assert p.shared_versions.include?(v)
334 assert p.shared_versions.include?(v)
335 assert p.children.first.shared_versions.include?(v)
335 assert p.children.first.shared_versions.include?(v)
336 assert !p.root.shared_versions.include?(v)
336 assert !p.root.shared_versions.include?(v)
337 assert !p.siblings.first.shared_versions.include?(v)
337 assert !p.siblings.first.shared_versions.include?(v)
338 assert !p.root.siblings.first.shared_versions.include?(v)
338 assert !p.root.siblings.first.shared_versions.include?(v)
339 end
339 end
340
340
341 def test_shared_versions_hierarchy_sharing
341 def test_shared_versions_hierarchy_sharing
342 p = Project.find(5)
342 p = Project.find(5)
343 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
343 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
344 assert p.shared_versions.include?(v)
344 assert p.shared_versions.include?(v)
345 assert p.children.first.shared_versions.include?(v)
345 assert p.children.first.shared_versions.include?(v)
346 assert p.root.shared_versions.include?(v)
346 assert p.root.shared_versions.include?(v)
347 assert !p.siblings.first.shared_versions.include?(v)
347 assert !p.siblings.first.shared_versions.include?(v)
348 assert !p.root.siblings.first.shared_versions.include?(v)
348 assert !p.root.siblings.first.shared_versions.include?(v)
349 end
349 end
350
350
351 def test_shared_versions_tree_sharing
351 def test_shared_versions_tree_sharing
352 p = Project.find(5)
352 p = Project.find(5)
353 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
353 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
354 assert p.shared_versions.include?(v)
354 assert p.shared_versions.include?(v)
355 assert p.children.first.shared_versions.include?(v)
355 assert p.children.first.shared_versions.include?(v)
356 assert p.root.shared_versions.include?(v)
356 assert p.root.shared_versions.include?(v)
357 assert p.siblings.first.shared_versions.include?(v)
357 assert p.siblings.first.shared_versions.include?(v)
358 assert !p.root.siblings.first.shared_versions.include?(v)
358 assert !p.root.siblings.first.shared_versions.include?(v)
359 end
359 end
360
360
361 def test_shared_versions_system_sharing
361 def test_shared_versions_system_sharing
362 p = Project.find(5)
362 p = Project.find(5)
363 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
363 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
364 assert p.shared_versions.include?(v)
364 assert p.shared_versions.include?(v)
365 assert p.children.first.shared_versions.include?(v)
365 assert p.children.first.shared_versions.include?(v)
366 assert p.root.shared_versions.include?(v)
366 assert p.root.shared_versions.include?(v)
367 assert p.siblings.first.shared_versions.include?(v)
367 assert p.siblings.first.shared_versions.include?(v)
368 assert p.root.siblings.first.shared_versions.include?(v)
368 assert p.root.siblings.first.shared_versions.include?(v)
369 end
369 end
370
370
371 def test_shared_versions
371 def test_shared_versions
372 parent = Project.find(1)
372 parent = Project.find(1)
373 child = parent.children.find(3)
373 child = parent.children.find(3)
374 private_child = parent.children.find(5)
374 private_child = parent.children.find(5)
375
375
376 assert_equal [1,2,3], parent.version_ids.sort
376 assert_equal [1,2,3], parent.version_ids.sort
377 assert_equal [4], child.version_ids
377 assert_equal [4], child.version_ids
378 assert_equal [6], private_child.version_ids
378 assert_equal [6], private_child.version_ids
379 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
379 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
380
380
381 assert_equal 6, parent.shared_versions.size
381 assert_equal 6, parent.shared_versions.size
382 parent.shared_versions.each do |version|
382 parent.shared_versions.each do |version|
383 assert_kind_of Version, version
383 assert_kind_of Version, version
384 end
384 end
385
385
386 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
386 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
387 end
387 end
388
388
389 def test_shared_versions_should_ignore_archived_subprojects
389 def test_shared_versions_should_ignore_archived_subprojects
390 parent = Project.find(1)
390 parent = Project.find(1)
391 child = parent.children.find(3)
391 child = parent.children.find(3)
392 child.archive
392 child.archive
393 parent.reload
393 parent.reload
394
394
395 assert_equal [1,2,3], parent.version_ids.sort
395 assert_equal [1,2,3], parent.version_ids.sort
396 assert_equal [4], child.version_ids
396 assert_equal [4], child.version_ids
397 assert !parent.shared_versions.collect(&:id).include?(4)
397 assert !parent.shared_versions.collect(&:id).include?(4)
398 end
398 end
399
399
400 def test_shared_versions_visible_to_user
400 def test_shared_versions_visible_to_user
401 user = User.find(3)
401 user = User.find(3)
402 parent = Project.find(1)
402 parent = Project.find(1)
403 child = parent.children.find(5)
403 child = parent.children.find(5)
404
404
405 assert_equal [1,2,3], parent.version_ids.sort
405 assert_equal [1,2,3], parent.version_ids.sort
406 assert_equal [6], child.version_ids
406 assert_equal [6], child.version_ids
407
407
408 versions = parent.shared_versions.visible(user)
408 versions = parent.shared_versions.visible(user)
409
409
410 assert_equal 4, versions.size
410 assert_equal 4, versions.size
411 versions.each do |version|
411 versions.each do |version|
412 assert_kind_of Version, version
412 assert_kind_of Version, version
413 end
413 end
414
414
415 assert !versions.collect(&:id).include?(6)
415 assert !versions.collect(&:id).include?(6)
416 end
416 end
417
417
418
418
419 def test_next_identifier
419 def test_next_identifier
420 ProjectCustomField.delete_all
420 ProjectCustomField.delete_all
421 Project.create!(:name => 'last', :identifier => 'p2008040')
421 Project.create!(:name => 'last', :identifier => 'p2008040')
422 assert_equal 'p2008041', Project.next_identifier
422 assert_equal 'p2008041', Project.next_identifier
423 end
423 end
424
424
425 def test_next_identifier_first_project
425 def test_next_identifier_first_project
426 Project.delete_all
426 Project.delete_all
427 assert_nil Project.next_identifier
427 assert_nil Project.next_identifier
428 end
428 end
429
429
430
430
431 def test_enabled_module_names_should_not_recreate_enabled_modules
431 def test_enabled_module_names_should_not_recreate_enabled_modules
432 project = Project.find(1)
432 project = Project.find(1)
433 # Remove one module
433 # Remove one module
434 modules = project.enabled_modules.slice(0..-2)
434 modules = project.enabled_modules.slice(0..-2)
435 assert modules.any?
435 assert modules.any?
436 assert_difference 'EnabledModule.count', -1 do
436 assert_difference 'EnabledModule.count', -1 do
437 project.enabled_module_names = modules.collect(&:name)
437 project.enabled_module_names = modules.collect(&:name)
438 end
438 end
439 project.reload
439 project.reload
440 # Ids should be preserved
440 # Ids should be preserved
441 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
441 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
442 end
442 end
443
443
444 def test_copy_from_existing_project
444 def test_copy_from_existing_project
445 source_project = Project.find(1)
445 source_project = Project.find(1)
446 copied_project = Project.copy_from(1)
446 copied_project = Project.copy_from(1)
447
447
448 assert copied_project
448 assert copied_project
449 # Cleared attributes
449 # Cleared attributes
450 assert copied_project.id.blank?
450 assert copied_project.id.blank?
451 assert copied_project.name.blank?
451 assert copied_project.name.blank?
452 assert copied_project.identifier.blank?
452 assert copied_project.identifier.blank?
453
453
454 # Duplicated attributes
454 # Duplicated attributes
455 assert_equal source_project.description, copied_project.description
455 assert_equal source_project.description, copied_project.description
456 assert_equal source_project.enabled_modules, copied_project.enabled_modules
456 assert_equal source_project.enabled_modules, copied_project.enabled_modules
457 assert_equal source_project.trackers, copied_project.trackers
457 assert_equal source_project.trackers, copied_project.trackers
458
458
459 # Default attributes
459 # Default attributes
460 assert_equal 1, copied_project.status
460 assert_equal 1, copied_project.status
461 end
461 end
462
462
463 def test_activities_should_use_the_system_activities
463 def test_activities_should_use_the_system_activities
464 project = Project.find(1)
464 project = Project.find(1)
465 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
465 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
466 end
466 end
467
467
468
468
469 def test_activities_should_use_the_project_specific_activities
469 def test_activities_should_use_the_project_specific_activities
470 project = Project.find(1)
470 project = Project.find(1)
471 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
471 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
472 assert overridden_activity.save!
472 assert overridden_activity.save!
473
473
474 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
474 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
475 end
475 end
476
476
477 def test_activities_should_not_include_the_inactive_project_specific_activities
477 def test_activities_should_not_include_the_inactive_project_specific_activities
478 project = Project.find(1)
478 project = Project.find(1)
479 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
479 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
480 assert overridden_activity.save!
480 assert overridden_activity.save!
481
481
482 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
482 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
483 end
483 end
484
484
485 def test_activities_should_not_include_project_specific_activities_from_other_projects
485 def test_activities_should_not_include_project_specific_activities_from_other_projects
486 project = Project.find(1)
486 project = Project.find(1)
487 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
487 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
488 assert overridden_activity.save!
488 assert overridden_activity.save!
489
489
490 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
490 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
491 end
491 end
492
492
493 def test_activities_should_handle_nils
493 def test_activities_should_handle_nils
494 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
494 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
495 TimeEntryActivity.delete_all
495 TimeEntryActivity.delete_all
496
496
497 # No activities
497 # No activities
498 project = Project.find(1)
498 project = Project.find(1)
499 assert project.activities.empty?
499 assert project.activities.empty?
500
500
501 # No system, one overridden
501 # No system, one overridden
502 assert overridden_activity.save!
502 assert overridden_activity.save!
503 project.reload
503 project.reload
504 assert_equal [overridden_activity], project.activities
504 assert_equal [overridden_activity], project.activities
505 end
505 end
506
506
507 def test_activities_should_override_system_activities_with_project_activities
507 def test_activities_should_override_system_activities_with_project_activities
508 project = Project.find(1)
508 project = Project.find(1)
509 parent_activity = TimeEntryActivity.find(:first)
509 parent_activity = TimeEntryActivity.find(:first)
510 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
510 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
511 assert overridden_activity.save!
511 assert overridden_activity.save!
512
512
513 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
513 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
514 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
514 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
515 end
515 end
516
516
517 def test_activities_should_include_inactive_activities_if_specified
517 def test_activities_should_include_inactive_activities_if_specified
518 project = Project.find(1)
518 project = Project.find(1)
519 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
519 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
520 assert overridden_activity.save!
520 assert overridden_activity.save!
521
521
522 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
522 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
523 end
523 end
524
524
525 test 'activities should not include active System activities if the project has an override that is inactive' do
526 project = Project.find(1)
527 system_activity = TimeEntryActivity.find_by_name('Design')
528 assert system_activity.active?
529 overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
530 assert overridden_activity.save!
531
532 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
533 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
534 end
535
525 def test_close_completed_versions
536 def test_close_completed_versions
526 Version.update_all("status = 'open'")
537 Version.update_all("status = 'open'")
527 project = Project.find(1)
538 project = Project.find(1)
528 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
539 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
529 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
540 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
530 project.close_completed_versions
541 project.close_completed_versions
531 project.reload
542 project.reload
532 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
543 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
533 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
544 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
534 end
545 end
535
546
536 context "Project#copy" do
547 context "Project#copy" do
537 setup do
548 setup do
538 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
549 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
539 Project.destroy_all :identifier => "copy-test"
550 Project.destroy_all :identifier => "copy-test"
540 @source_project = Project.find(2)
551 @source_project = Project.find(2)
541 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
552 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
542 @project.trackers = @source_project.trackers
553 @project.trackers = @source_project.trackers
543 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
554 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
544 end
555 end
545
556
546 should "copy issues" do
557 should "copy issues" do
547 @source_project.issues << Issue.generate!(:status_id => 5,
558 @source_project.issues << Issue.generate!(:status_id => 5,
548 :subject => "copy issue status",
559 :subject => "copy issue status",
549 :tracker_id => 1,
560 :tracker_id => 1,
550 :assigned_to_id => 2,
561 :assigned_to_id => 2,
551 :project_id => @source_project.id)
562 :project_id => @source_project.id)
552 assert @project.valid?
563 assert @project.valid?
553 assert @project.issues.empty?
564 assert @project.issues.empty?
554 assert @project.copy(@source_project)
565 assert @project.copy(@source_project)
555
566
556 assert_equal @source_project.issues.size, @project.issues.size
567 assert_equal @source_project.issues.size, @project.issues.size
557 @project.issues.each do |issue|
568 @project.issues.each do |issue|
558 assert issue.valid?
569 assert issue.valid?
559 assert ! issue.assigned_to.blank?
570 assert ! issue.assigned_to.blank?
560 assert_equal @project, issue.project
571 assert_equal @project, issue.project
561 end
572 end
562
573
563 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
574 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
564 assert copied_issue
575 assert copied_issue
565 assert copied_issue.status
576 assert copied_issue.status
566 assert_equal "Closed", copied_issue.status.name
577 assert_equal "Closed", copied_issue.status.name
567 end
578 end
568
579
569 should "change the new issues to use the copied version" do
580 should "change the new issues to use the copied version" do
570 User.current = User.find(1)
581 User.current = User.find(1)
571 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
582 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
572 @source_project.versions << assigned_version
583 @source_project.versions << assigned_version
573 assert_equal 3, @source_project.versions.size
584 assert_equal 3, @source_project.versions.size
574 Issue.generate_for_project!(@source_project,
585 Issue.generate_for_project!(@source_project,
575 :fixed_version_id => assigned_version.id,
586 :fixed_version_id => assigned_version.id,
576 :subject => "change the new issues to use the copied version",
587 :subject => "change the new issues to use the copied version",
577 :tracker_id => 1,
588 :tracker_id => 1,
578 :project_id => @source_project.id)
589 :project_id => @source_project.id)
579
590
580 assert @project.copy(@source_project)
591 assert @project.copy(@source_project)
581 @project.reload
592 @project.reload
582 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
593 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
583
594
584 assert copied_issue
595 assert copied_issue
585 assert copied_issue.fixed_version
596 assert copied_issue.fixed_version
586 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
597 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
587 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
598 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
588 end
599 end
589
600
590 should "copy issue relations" do
601 should "copy issue relations" do
591 Setting.cross_project_issue_relations = '1'
602 Setting.cross_project_issue_relations = '1'
592
603
593 second_issue = Issue.generate!(:status_id => 5,
604 second_issue = Issue.generate!(:status_id => 5,
594 :subject => "copy issue relation",
605 :subject => "copy issue relation",
595 :tracker_id => 1,
606 :tracker_id => 1,
596 :assigned_to_id => 2,
607 :assigned_to_id => 2,
597 :project_id => @source_project.id)
608 :project_id => @source_project.id)
598 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
609 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
599 :issue_to => second_issue,
610 :issue_to => second_issue,
600 :relation_type => "relates")
611 :relation_type => "relates")
601 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
612 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
602 :issue_to => second_issue,
613 :issue_to => second_issue,
603 :relation_type => "duplicates")
614 :relation_type => "duplicates")
604
615
605 assert @project.copy(@source_project)
616 assert @project.copy(@source_project)
606 assert_equal @source_project.issues.count, @project.issues.count
617 assert_equal @source_project.issues.count, @project.issues.count
607 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
618 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
608 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
619 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
609
620
610 # First issue with a relation on project
621 # First issue with a relation on project
611 assert_equal 1, copied_issue.relations.size, "Relation not copied"
622 assert_equal 1, copied_issue.relations.size, "Relation not copied"
612 copied_relation = copied_issue.relations.first
623 copied_relation = copied_issue.relations.first
613 assert_equal "relates", copied_relation.relation_type
624 assert_equal "relates", copied_relation.relation_type
614 assert_equal copied_second_issue.id, copied_relation.issue_to_id
625 assert_equal copied_second_issue.id, copied_relation.issue_to_id
615 assert_not_equal source_relation.id, copied_relation.id
626 assert_not_equal source_relation.id, copied_relation.id
616
627
617 # Second issue with a cross project relation
628 # Second issue with a cross project relation
618 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
629 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
619 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
630 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
620 assert_equal "duplicates", copied_relation.relation_type
631 assert_equal "duplicates", copied_relation.relation_type
621 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
632 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
622 assert_not_equal source_relation_cross_project.id, copied_relation.id
633 assert_not_equal source_relation_cross_project.id, copied_relation.id
623 end
634 end
624
635
625 should "copy members" do
636 should "copy members" do
626 assert @project.valid?
637 assert @project.valid?
627 assert @project.members.empty?
638 assert @project.members.empty?
628 assert @project.copy(@source_project)
639 assert @project.copy(@source_project)
629
640
630 assert_equal @source_project.members.size, @project.members.size
641 assert_equal @source_project.members.size, @project.members.size
631 @project.members.each do |member|
642 @project.members.each do |member|
632 assert member
643 assert member
633 assert_equal @project, member.project
644 assert_equal @project, member.project
634 end
645 end
635 end
646 end
636
647
637 should "copy project specific queries" do
648 should "copy project specific queries" do
638 assert @project.valid?
649 assert @project.valid?
639 assert @project.queries.empty?
650 assert @project.queries.empty?
640 assert @project.copy(@source_project)
651 assert @project.copy(@source_project)
641
652
642 assert_equal @source_project.queries.size, @project.queries.size
653 assert_equal @source_project.queries.size, @project.queries.size
643 @project.queries.each do |query|
654 @project.queries.each do |query|
644 assert query
655 assert query
645 assert_equal @project, query.project
656 assert_equal @project, query.project
646 end
657 end
647 end
658 end
648
659
649 should "copy versions" do
660 should "copy versions" do
650 @source_project.versions << Version.generate!
661 @source_project.versions << Version.generate!
651 @source_project.versions << Version.generate!
662 @source_project.versions << Version.generate!
652
663
653 assert @project.versions.empty?
664 assert @project.versions.empty?
654 assert @project.copy(@source_project)
665 assert @project.copy(@source_project)
655
666
656 assert_equal @source_project.versions.size, @project.versions.size
667 assert_equal @source_project.versions.size, @project.versions.size
657 @project.versions.each do |version|
668 @project.versions.each do |version|
658 assert version
669 assert version
659 assert_equal @project, version.project
670 assert_equal @project, version.project
660 end
671 end
661 end
672 end
662
673
663 should "copy wiki" do
674 should "copy wiki" do
664 assert_difference 'Wiki.count' do
675 assert_difference 'Wiki.count' do
665 assert @project.copy(@source_project)
676 assert @project.copy(@source_project)
666 end
677 end
667
678
668 assert @project.wiki
679 assert @project.wiki
669 assert_not_equal @source_project.wiki, @project.wiki
680 assert_not_equal @source_project.wiki, @project.wiki
670 assert_equal "Start page", @project.wiki.start_page
681 assert_equal "Start page", @project.wiki.start_page
671 end
682 end
672
683
673 should "copy wiki pages and content" do
684 should "copy wiki pages and content" do
674 assert @project.copy(@source_project)
685 assert @project.copy(@source_project)
675
686
676 assert @project.wiki
687 assert @project.wiki
677 assert_equal 1, @project.wiki.pages.length
688 assert_equal 1, @project.wiki.pages.length
678
689
679 @project.wiki.pages.each do |wiki_page|
690 @project.wiki.pages.each do |wiki_page|
680 assert wiki_page.content
691 assert wiki_page.content
681 assert !@source_project.wiki.pages.include?(wiki_page)
692 assert !@source_project.wiki.pages.include?(wiki_page)
682 end
693 end
683 end
694 end
684
695
685 should "copy issue categories" do
696 should "copy issue categories" do
686 assert @project.copy(@source_project)
697 assert @project.copy(@source_project)
687
698
688 assert_equal 2, @project.issue_categories.size
699 assert_equal 2, @project.issue_categories.size
689 @project.issue_categories.each do |issue_category|
700 @project.issue_categories.each do |issue_category|
690 assert !@source_project.issue_categories.include?(issue_category)
701 assert !@source_project.issue_categories.include?(issue_category)
691 end
702 end
692 end
703 end
693
704
694 should "copy boards" do
705 should "copy boards" do
695 assert @project.copy(@source_project)
706 assert @project.copy(@source_project)
696
707
697 assert_equal 1, @project.boards.size
708 assert_equal 1, @project.boards.size
698 @project.boards.each do |board|
709 @project.boards.each do |board|
699 assert !@source_project.boards.include?(board)
710 assert !@source_project.boards.include?(board)
700 end
711 end
701 end
712 end
702
713
703 should "change the new issues to use the copied issue categories" do
714 should "change the new issues to use the copied issue categories" do
704 issue = Issue.find(4)
715 issue = Issue.find(4)
705 issue.update_attribute(:category_id, 3)
716 issue.update_attribute(:category_id, 3)
706
717
707 assert @project.copy(@source_project)
718 assert @project.copy(@source_project)
708
719
709 @project.issues.each do |issue|
720 @project.issues.each do |issue|
710 assert issue.category
721 assert issue.category
711 assert_equal "Stock management", issue.category.name # Same name
722 assert_equal "Stock management", issue.category.name # Same name
712 assert_not_equal IssueCategory.find(3), issue.category # Different record
723 assert_not_equal IssueCategory.find(3), issue.category # Different record
713 end
724 end
714 end
725 end
715
726
716 should "limit copy with :only option" do
727 should "limit copy with :only option" do
717 assert @project.members.empty?
728 assert @project.members.empty?
718 assert @project.issue_categories.empty?
729 assert @project.issue_categories.empty?
719 assert @source_project.issues.any?
730 assert @source_project.issues.any?
720
731
721 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
732 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
722
733
723 assert @project.members.any?
734 assert @project.members.any?
724 assert @project.issue_categories.any?
735 assert @project.issue_categories.any?
725 assert @project.issues.empty?
736 assert @project.issues.empty?
726 end
737 end
727
738
728 end
739 end
729
740
730 end
741 end
General Comments 0
You need to be logged in to leave comments. Login now