]> source.dussan.org Git - redmine.git/commitdiff
Ability to assign issues to groups (#2964).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 23 Jul 2011 18:18:13 +0000 (18:18 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 23 Jul 2011 18:18:13 +0000 (18:18 +0000)
Option is disabled by default. It can be turned on in application settings.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@6306 e93f8b46-1217-0410-a6f0-8f06a7374b81

61 files changed:
app/controllers/reports_controller.rb
app/models/group.rb
app/models/issue.rb
app/models/issue_category.rb
app/models/mail_handler.rb
app/models/principal.rb
app/models/project.rb
app/models/query.rb
app/models/user.rb
app/views/issue_categories/_form.rhtml
app/views/settings/_issues.rhtml
config/locales/bg.yml
config/locales/bs.yml
config/locales/ca.yml
config/locales/cs.yml
config/locales/da.yml
config/locales/de.yml
config/locales/el.yml
config/locales/en-GB.yml
config/locales/en.yml
config/locales/es.yml
config/locales/eu.yml
config/locales/fa.yml
config/locales/fi.yml
config/locales/fr.yml
config/locales/gl.yml
config/locales/he.yml
config/locales/hr.yml
config/locales/hu.yml
config/locales/id.yml
config/locales/it.yml
config/locales/ja.yml
config/locales/ko.yml
config/locales/lt.yml
config/locales/lv.yml
config/locales/mk.yml
config/locales/mn.yml
config/locales/nl.yml
config/locales/no.yml
config/locales/pl.yml
config/locales/pt-BR.yml
config/locales/pt.yml
config/locales/ro.yml
config/locales/ru.yml
config/locales/sk.yml
config/locales/sl.yml
config/locales/sr-YU.yml
config/locales/sr.yml
config/locales/sv.yml
config/locales/th.yml
config/locales/tr.yml
config/locales/uk.yml
config/locales/vi.yml
config/locales/zh-TW.yml
config/locales/zh.yml
config/settings.yml
test/functional/issues_controller_test.rb
test/unit/group_test.rb
test/unit/issue_category_test.rb
test/unit/issue_test.rb
test/unit/mail_handler_test.rb

index 0d471507893d4a406197ffdb50d142ee167ddca8..1b5d2ccbcb4e392b7a4cc6a9135a0dbd29f71f40 100644 (file)
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006  Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -24,8 +24,8 @@ class ReportsController < ApplicationController
     @versions = @project.shared_versions.sort
     @priorities = IssuePriority.all
     @categories = @project.issue_categories
-    @assignees = @project.members.collect { |m| m.user }.sort
-    @authors = @project.members.collect { |m| m.user }.sort
+    @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
+    @authors = @project.users.sort
     @subprojects = @project.descendants.visible
 
     @issues_by_tracker = Issue.by_tracker(@project)
@@ -63,12 +63,12 @@ class ReportsController < ApplicationController
       @report_title = l(:field_category)
     when "assigned_to"
       @field = "assigned_to_id"
-      @rows = @project.members.collect { |m| m.user }.sort
+      @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
       @data = Issue.by_assigned_to(@project)
       @report_title = l(:field_assigned_to)
     when "author"
       @field = "author_id"
-      @rows = @project.members.collect { |m| m.user }.sort
+      @rows = @project.users.sort
       @data = Issue.by_author(@project)
       @report_title = l(:field_author)
     when "subproject"
index 1b55c25661198b8a9e621cce6ca5a30818d7babd..f3721f7fdfbd6c16e3813ae2303aa4643032dac2 100644 (file)
@@ -1,5 +1,5 @@
 # Redmine - project management software
-# Copyright (C) 2006-2009  Jean-Philippe Lang
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -24,11 +24,15 @@ class Group < Principal
   validates_presence_of :lastname
   validates_uniqueness_of :lastname, :case_sensitive => false
   validates_length_of :lastname, :maximum => 30
-    
+  
+  before_destroy :remove_references_before_destroy
+  
   def to_s
     lastname.to_s
   end
   
+  alias :name :to_s
+  
   def user_added(user)
     members.each do |member|
       next if member.project.nil?
@@ -46,4 +50,13 @@ class Group < Principal
                             :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
     end
   end
+  
+  private
+  
+  # Removes references that are not handled by associations
+  def remove_references_before_destroy
+    return if self.id.nil?
+    
+    Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
+  end
 end
index 5ac166270365a8636884f35544caa49551fe6008..d7a3e6ecb20d2981b7773676768dd5cdd4ced4c8 100644 (file)
@@ -22,7 +22,7 @@ class Issue < ActiveRecord::Base
   belongs_to :tracker
   belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
   belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
-  belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+  belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
   belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
   belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
   belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
@@ -93,9 +93,11 @@ class Issue < ActiveRecord::Base
       when 'all'
         nil
       when 'default'
-        "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
+        user_ids = [user.id] + user.groups.map(&:id)
+        "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids}))"
       when 'own'
-        "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
+        user_ids = [user.id] + user.groups.map(&:id)
+        "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids}))"
       else
         '1=0'
       end
@@ -109,9 +111,9 @@ class Issue < ActiveRecord::Base
       when 'all'
         true
       when 'default'
-        !self.is_private? || self.author == user || self.assigned_to == user
+        !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
       when 'own'
-        self.author == user || self.assigned_to == user
+        self.author == user || user.is_or_belongs_to?(assigned_to)
       else
         false
       end
@@ -482,7 +484,13 @@ class Issue < ActiveRecord::Base
     # Author and assignee are always notified unless they have been
     # locked or don't want to be notified
     notified << author if author && author.active? && author.notify_about?(self)
-    notified << assigned_to if assigned_to && assigned_to.active? && assigned_to.notify_about?(self)
+    if assigned_to
+      if assigned_to.is_a?(Group)
+        notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
+      else
+        notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
+      end
+    end
     notified.uniq!
     # Remove users that can not view the issue
     notified.reject! {|user| !visible?(user)}
index 96b8eeee12120d4c47e7baa646b80ac45398ac5c..19b2d906b1a29c88fd9a19375040bc204fc26de6 100644 (file)
@@ -17,7 +17,7 @@
 
 class IssueCategory < ActiveRecord::Base
   belongs_to :project
-  belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+  belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
   has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
   
   validates_presence_of :name
index dbbd4f5f364336e1b692e81c556303f6fdeef85e..0ba8d3fd2b8961b50582c2beebd6d7bda3d535cf 100644 (file)
@@ -261,8 +261,7 @@ class MailHandler < ActionMailer::Base
 
   # Returns a Hash of issue attributes extracted from keywords in the email body
   def issue_attributes_from_keywords(issue)
-    assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_user_from_keyword(k)
-    assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to)
+    assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
 
     attrs = {
       'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
@@ -353,14 +352,19 @@ class MailHandler < ActionMailer::Base
     end
     body.strip
   end
-
-  def find_user_from_keyword(keyword)
-    user ||= User.find_by_mail(keyword)
-    user ||= User.find_by_login(keyword)
-    if user.nil? && keyword.match(/ /)
+  
+  def find_assignee_from_keyword(keyword, issue)
+    keyword = keyword.to_s.downcase
+    assignable = issue.assignable_users
+    assignee = nil
+    assignee ||= assignable.detect {|a| a.mail.to_s.downcase == keyword || a.login.to_s.downcase == keyword}
+    if assignee.nil? && keyword.match(/ /)
       firstname, lastname = *(keyword.split) # "First Last Throwaway"
-      user ||= User.find_by_firstname_and_lastname(firstname, lastname)
+      assignee ||= assignable.detect {|a| a.is_a?(User) && a.firstname.to_s.downcase == firstname && a.lastname.to_s.downcase == lastname}
+    end
+    if assignee.nil?
+      assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword}
     end
-    user
+    assignee
   end
 end
index b3e07dda5bffdca9edb6e34fb28c87939f8f8b7f..a157cd22f55d181c3912b0371c023027d146de95 100644 (file)
@@ -1,5 +1,5 @@
 # Redmine - project management software
-# Copyright (C) 2006-2009  Jean-Philippe Lang
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -21,6 +21,7 @@ class Principal < ActiveRecord::Base
   has_many :members, :foreign_key => 'user_id', :dependent => :destroy
   has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
   has_many :projects, :through => :memberships
+  has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
 
   # Groups and active users
   named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
index c3b55308320f2cb8c0800660221984efea0c23ac..b4872a3b456d7c6774b5bb6bb2049b47103ca335 100644 (file)
@@ -426,9 +426,10 @@ class Project < ActiveRecord::Base
     Member.delete_all(['project_id = ?', id])
   end
   
-  # Users issues can be assigned to
+  # Users/groups issues can be assigned to
   def assignable_users
-    members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
+    assignable = Setting.issue_group_assignment? ? member_principals : members
+    assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
   end
   
   # Returns the mail adresses of users that should be always notified on project events
index 3cb4c45070e59893d95beb800e2e7958f0dd08be..28e1a6d18621e6437e11353839258fa87ea827fa 100644 (file)
@@ -223,15 +223,14 @@ class Query < ActiveRecord::Base
                            "estimated_hours" => { :type => :float, :order => 13 },
                            "done_ratio" =>  { :type => :integer, :order => 14 }}
 
-    user_values = []
-    user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    principals = []
     if project
-      user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
+      principals += project.principals.sort
     else
       all_projects = Project.visible.all
       if all_projects.any?
         # members of visible projects
-        user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
+        principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort
 
         # project filter
         project_values = []
@@ -242,8 +241,17 @@ class Query < ActiveRecord::Base
         @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
       end
     end
-    @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
-    @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
+    users = principals.select {|p| p.is_a?(User)}
+    
+    assigned_to_values = []
+    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
+    @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
+    
+    author_values = []
+    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    author_values += users.collect{|s| [s.name, s.id.to_s] }
+    @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
 
     group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
     @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
index 1b7680182c14457f662aba45686ab58e202366d2..656110f731213c26cac964ea5f8fd727cff9d715 100644 (file)
@@ -45,7 +45,6 @@ class User < Principal
 
   has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
                                    :after_remove => Proc.new {|user, group| group.user_removed(user)}
-  has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
   has_many :changesets, :dependent => :nullify
   has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
   has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
@@ -388,6 +387,17 @@ class User < Principal
     @projects_by_role
   end
   
+  # Returns true if user is arg or belongs to arg
+  def is_or_belongs_to?(arg)
+    if arg.is_a?(User)
+      self == arg
+    elsif arg.is_a?(Group)
+      arg.users.include?(self)
+    else
+      false
+    end
+  end
+  
   # Return true if the user is allowed to do the specified action on a specific context
   # Action can be:
   # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
@@ -469,7 +479,7 @@ class User < Principal
       true
     when 'selected'
       # user receives notifications for created/assigned issues on unselected projects
-      if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
+      if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
         true
       else
         false
@@ -477,13 +487,13 @@ class User < Principal
     when 'none'
       false
     when 'only_my_events'
-      if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
+      if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
         true
       else
         false
       end
     when 'only_assigned'
-      if object.is_a?(Issue) && object.assigned_to == self
+      if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to)
         true
       else
         false
index 232808800b0f982751f0df5bab5bf66bda134717..fc5f158d33227eb382a8f8a6ddbb0946f80d9230 100644 (file)
@@ -2,5 +2,5 @@
 
 <div class="box">
 <p><%= f.text_field :name, :size => 30, :required => true %></p>
-<p><%= f.select :assigned_to_id, @project.users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
+<p><%= f.select :assigned_to_id, @project.assignable_users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
 </div>
index 273d4b581bcad5d5148378aef583f35cad532f32..cfe843e0cee109e2f197d9fa4b073a5121f9f8e0 100644 (file)
@@ -3,6 +3,8 @@
 <div class="box tabular settings">
 <p><%= setting_check_box :cross_project_issue_relations %></p>
 
+<p><%= setting_check_box :issue_group_assignment %></p>
+
 <p><%= setting_check_box :display_subprojects_issues %></p>
 
 <p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
index aa91c96c56eddb843f29c29c3cfa288c151a4c4c..1ed6ce6dfd4b2bbc23962e858645c66ffb1cd76a 100644 (file)
@@ -977,3 +977,4 @@ bg:
   enumeration_doc_categories: Категории документи
   enumeration_activities: Дейности (time tracking)
   enumeration_system_activity: Системна активност
+  setting_issue_group_assignment: Allow issue assignment to groups
index 962e08d586aad3ca42a4c5bfacdf2d580a65c346..8f635418f7bdcb10846a355bea967b1f23ce500a 100644 (file)
@@ -992,3 +992,4 @@ bs:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 41ea899eeedacf79a5ac3f1c0c292f039dc0b65d..62b3acfbefd68e96abc90da089626f418de3e392 100644 (file)
@@ -981,3 +981,4 @@ ca:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 3dfc149649d9882c83347961f8efc4a47a20a335..88c0af57a4ce04b580590bf03a7ef49e2a17352b 100644 (file)
@@ -982,3 +982,4 @@ cs:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 88849ed663956ae2370fbe1693af61dd73bf5f64..e0c69c64f4c239b45351b3c404f5b5907436f48d 100644 (file)
@@ -995,3 +995,4 @@ da:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 535e3aec104570547dede2703c39c69587f1052d..68b3cba3d27274e973993f8c442eac8267ae72b7 100644 (file)
@@ -997,3 +997,4 @@ de:
 
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index c5566fadd61dbc0cb9f70ea4d23d35bec9fce142..9fcceeebcddb4fb12f8985089ad8145ff06de60a 100644 (file)
@@ -978,3 +978,4 @@ el:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 0a3d793f889c8d3f3c88a460aaf46f00f67d348a..8efa96d1481c9ed836779d3c61e8c7a656a04cfa 100644 (file)
@@ -981,3 +981,4 @@ en-GB:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 66792632b5ada7e1243a050d197e388f047def4f..8dd3c8f7c12bcaf2ca4677097a0402a6c80a4275 100644 (file)
@@ -374,6 +374,7 @@ en:
   setting_commit_logtime_enabled: Enable time logging
   setting_commit_logtime_activity_id: Activity for logged time
   setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
+  setting_issue_group_assignment: Allow issue assignment to groups
   
   permission_add_project: Create project
   permission_add_subprojects: Create subprojects
index b2f4a5bd9827e8cecc23510b0cc7fd437011fe91..7cd8d0d076c5e4175bfb7e435cd4c32c7fcb60c5 100644 (file)
@@ -1015,3 +1015,4 @@ es:
   text_scm_command_not_available: La orden para el Scm no está disponible. Por favor, compruebe la configuración en el panel de administración.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index fb0269332a20d8a3893bd900ccc78d0cfb5faa90..5fb1ec11c8ac953e0726bcc3077f7d51b64a5b20 100644 (file)
@@ -982,3 +982,4 @@ eu:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 81fef9187a8ff525f60749dc278abc891f1b174d..98291cc57e6d011c806310bc69b17011efa95e9a 100644 (file)
@@ -981,3 +981,4 @@ fa:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 134823e59fe94279be8f305732b8f2be34a2bdd9..22005c2c0a9d6a81a89ae631084c2fd972f413da 100644 (file)
@@ -999,3 +999,4 @@ fi:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index cf23259bdc5ecba26e292c7b0afaeba48b899006..b6a85d5f06b00e28fc6a3daadae906b3552d1fc7 100644 (file)
@@ -371,6 +371,7 @@ fr:
   setting_commit_logtime_enabled: Permettre la saisie de temps
   setting_commit_logtime_activity_id: Activité pour le temps saisi
   setting_gantt_items_limit: Nombre maximum d'éléments affichés sur le gantt
+  setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
   
   permission_add_project: Créer un projet
   permission_add_subprojects: Créer des sous-projets
index 7f40a42da74bbe087499d483b97cb307210dc2f4..d814a78fe449bfa51e382287aed5ecc5ac9f9a9d 100644 (file)
@@ -990,3 +990,4 @@ gl:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index ee37dba3976cce375eb73c642ae604813f4d241c..dc9495edb4a77c4cd1d534a6277f8d64339478bb 100644 (file)
@@ -983,3 +983,4 @@ he:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 9d0215d32579fb0f9b7a08972df7e088a3b05cb8..ec9f2abf8c39dce57597a727c26e25167153f634 100644 (file)
@@ -985,3 +985,4 @@ hr:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 1262ebab0376620b71f1492a2c3a668f50cb0ae9..475b7ac0d12a6574b83d96d2c0dd835220287830 100644 (file)
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 95b48db6659526d606ba66782a9aa9cce928d636..824f078108356444b0f0f7cfd7ea9b68e14bdd64 100644 (file)
@@ -986,3 +986,4 @@ id:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index da1f00d48524970698f975f323f7cf6255d76e57..44d8974a74a3dd3ad4f933eae9e8cefd93e747b0 100644 (file)
@@ -979,3 +979,4 @@ it:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index d026a75059b79da9980f9ddb1231f6a0f73ed533..c095a40b6fef30e2e35dfe483095a83e24be19c6 100644 (file)
@@ -1008,3 +1008,4 @@ ja:
   text_issues_destroy_descendants_confirmation: %{count}個の子チケットも削除されます。
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 9cc8bade30f7d40b926f099974f8e82919d650ad..2741f420e3360f451908571a267e871ede4d347c 100644 (file)
@@ -1030,3 +1030,4 @@ ko:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 7ee5dc017b7a8846e1e0fe21b30a0fb0d0df20e6..f33521bae367e03873ae7bdd885d8bbd8c1aa543 100644 (file)
@@ -1038,3 +1038,4 @@ lt:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index d436788f1ae31475ab1f7bbf80c39c3a31abed72..423693254e1ea28b750587c08e8e3d336bbeb62d 100644 (file)
@@ -973,3 +973,4 @@ lv:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 4832d6ac4e21289cb5f4b2887c8cb12c9384b1df..b3444ce776a4c2f409e5690559deaf1c241dbcc2 100644 (file)
@@ -978,3 +978,4 @@ mk:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index b14a2a296015a2d987eed411d9763f58b785e217..f6357b4ee6c6359d342914fcaa2f635eef09d25a 100644 (file)
@@ -979,3 +979,4 @@ mn:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 9e20dd2f2cbb929873b3480a76420477c48a30f2..e2a1b91a71985942bd511a4d837cc41e4539b480 100644 (file)
@@ -960,3 +960,4 @@ nl:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 2e560cb659603357bbb2eb4b5ff2e1fc18e5538c..795179eb9146c2ab74ae07f3a1f4b8566aeaa230 100644 (file)
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 1af6d26ff9be5ec72df3e3564a7a8c0586db51d4..16db7446b8002b79319f64fd277d09244b7bdfb4 100644 (file)
@@ -995,3 +995,4 @@ pl:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index edf33a75166a07c1d8953b584ab14bd1a22ead60..8e8d0f6a6c21081329924801806b07d38654d93c 100644 (file)
@@ -999,3 +999,4 @@ pt-BR:
   text_scm_command_not_available: Comando de versionamento não disponível. Por favor verifique as configurações no painel de administração.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 36b36586bdb6d6d4e57713dc0a4e818a6b31b964..a972090a0ad0456b4f05a42678a36d2a5fe1b25c 100644 (file)
@@ -983,3 +983,4 @@ pt:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index c850c90081f24dbe7896c9a1061c8a07947fee36..a2206a7039c79bf7699090a1d052ff7b59cffdac 100644 (file)
@@ -971,3 +971,4 @@ ro:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index b67d0ad7260e866cbf2b29a6c613ee702aeb4b09..af2b98d1681a507549e70f434470ba198b1ccdde 100644 (file)
@@ -1091,3 +1091,4 @@ ru:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 3fbe62d455d75f798121913191d23f0c3d010c99..c0bf3f78117735d25bcfdfbde9b539d06c2c0f8c 100644 (file)
@@ -973,3 +973,4 @@ sk:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index afb023b1ae0550c174797f60f2ebf62626b17830..6cf8e67228bc28c3659b9c681ba126c292abf9a0 100644 (file)
@@ -974,3 +974,4 @@ sl:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index a4c396bd2b26ee08ed4bab5149076eafe1f86a2f..c26b3ced2be987596b9bee6fb79f8e90f1610c13 100644 (file)
@@ -978,3 +978,4 @@ sr-YU:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index c908f944d7dddd491fcc2c108d040c3d61018a73..dbb1c9514f9549d460892a5f2a9eca522e392d92 100644 (file)
@@ -979,3 +979,4 @@ sr:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 8bdf04cb205f02868ce856333b5dec0c402a9314..712f7bf6fa2fb1f1ff351216a2a153f82217b1ae 100644 (file)
@@ -1019,3 +1019,4 @@ sv:
   enumeration_system_activity: Systemaktivitet
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index bf701f2a563ba5f488b66d87542e5fc22f9a06d6..1edeaac85850ac7ae30e1412008724e708b39bfc 100644 (file)
@@ -975,3 +975,4 @@ th:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 3b2f32927071af4742cc707044a3a4bb485ed7f6..84ff00f0e06f54ace8f2108db94579f62765fdde 100644 (file)
@@ -997,3 +997,4 @@ tr:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index ad23b9ce3dda830e0deb80dc4438ac60a5447576..c09574ac61168bc7e02c901a90ed5d0cda961ee3 100644 (file)
@@ -974,3 +974,4 @@ uk:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index e4ffc60932800833887a16faeb41ed1b609c57df..d144f89fcbc02002a7ed3ef6ffc1aa4e83f3394e 100644 (file)
@@ -1029,3 +1029,4 @@ vi:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index bd9f48eda73bdc6e82fb65db6d76d524b0662ca3..52e368b7549c9e4132f6a8380d522a7d662ad3c2 100644 (file)
   enumeration_doc_categories: 文件分類
   enumeration_activities: 活動 (時間追蹤)
   enumeration_system_activity: 系統活動
+  setting_issue_group_assignment: Allow issue assignment to groups
index a9dbf29ca7f016856d2608f844780e15fbc0b99c..281d75330fc726cb6839ca03d084da96f0013f00 100644 (file)
@@ -981,3 +981,4 @@ zh:
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
   notice_issue_successful_create: Issue %{id} created.
   label_between: between
+  setting_issue_group_assignment: Allow issue assignment to groups
index 6a9676a9085715130fd3d425daa04ebb4dba47b4..c81ddc8531f01b59ab269cf8a5d31b380921e3c6 100644 (file)
@@ -121,6 +121,8 @@ user_format:
   format: symbol
 cross_project_issue_relations:
   default: 0
+issue_group_assignment:
+  default: 0
 notified_events:
   serialized: true
   default: 
index 53afe2a253b3e25a7f3c1b783c6a7d2f8068f453..3f539faa15a126e4daa768dd2d8d4ca0fc35fc2f 100644 (file)
@@ -535,6 +535,28 @@ class IssuesControllerTest < ActionController::TestCase
     assert_not_nil v
     assert_equal 'Value for field 2', v.value
   end
+  
+  def test_post_new_with_group_assignment
+    group = Group.find(11)
+    project = Project.find(1)
+    project.members << Member.new(:principal => group, :roles => [Role.first])
+
+    with_settings :issue_group_assignment => '1' do
+      @request.session[:user_id] = 2
+      assert_difference 'Issue.count' do
+        post :create, :project_id => project.id, 
+                      :issue => {:tracker_id => 3,
+                                 :status_id => 1,
+                                 :subject => 'This is the test_new_with_group_assignment issue',
+                                 :assigned_to_id => group.id}
+      end
+    end
+    assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
+    
+    issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
+    assert_not_nil issue
+    assert_equal group, issue.assigned_to
+  end
 
   def test_post_create_without_start_date
     @request.session[:user_id] = 2
@@ -1309,6 +1331,22 @@ class IssuesControllerTest < ActionController::TestCase
     assert_equal 1, journal.details.size
   end
 
+  def test_bulk_update_with_group_assignee
+    group = Group.find(11)
+    project = Project.find(1)
+    project.members << Member.new(:principal => group, :roles => [Role.first])
+    
+    @request.session[:user_id] = 2
+    # update issues assignee
+    post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
+                                     :issue => {:priority_id => '',
+                                                :assigned_to_id => group.id,
+                                                :custom_field_values => {'2' => ''}}
+
+    assert_response 302
+    assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
+  end
+
   def test_bulk_update_on_different_projects
     @request.session[:user_id] = 2
     # update issues priority
index a7670aab029b102fd0e443f7afe8703aeb35b4db..b86dbb0a8db26295bb377a182e55a43d60168412 100644 (file)
@@ -1,5 +1,5 @@
 # Redmine - project management software
-# Copyright (C) 2006-2009  Jean-Philippe Lang
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -74,4 +74,14 @@ class GroupTest < ActiveSupport::TestCase
     User.find(8).groups.clear
     assert !User.find(8).member_of?(Project.find(5))
   end
+  
+  def test_destroy_should_unassign_issues
+    group = Group.first
+    Issue.update_all(["assigned_to_id = ?", group.id], 'id = 1')
+    
+    assert group.destroy
+    assert group.destroyed?
+    
+    assert_equal nil, Issue.find(1).assigned_to_id
+  end
 end
index 947f15f0ddad6f308810d19298de4455a82349e4..74c6539db136a7f84079c87a2f8163d92ae85f47 100644 (file)
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007  Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -24,6 +24,19 @@ class IssueCategoryTest < ActiveSupport::TestCase
     @category = IssueCategory.find(1)
   end
   
+  def test_create
+    assert IssueCategory.new(:project_id => 2, :name => 'New category').save
+    category = IssueCategory.first(:order => 'id DESC')
+    assert_equal 'New category', category.name
+  end
+  
+  def test_create_with_group_assignment
+    assert IssueCategory.new(:project_id => 2, :name => 'Group assignment', :assigned_to_id => 11).save
+    category = IssueCategory.first(:order => 'id DESC')
+    assert_kind_of Group, category.assigned_to
+    assert_equal Group.find(11), category.assigned_to
+  end
+  
   def test_destroy
     issue = @category.issues.first
     @category.destroy
index cf759b43da46705505eed85bd0fc5a8803b5e673..f36bf90491186904a1aa8cc5adb1c94f13e14494 100644 (file)
@@ -64,6 +64,15 @@ class IssueTest < ActiveSupport::TestCase
     issue.reload
     assert_equal 'PostgreSQL', issue.custom_value_for(field).value
   end
+  
+  def test_create_with_group_assignment
+    with_settings :issue_group_assignment => '1' do
+      assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, :subject => 'Group assignment', :assigned_to_id => 11).save
+      issue = Issue.first(:order => 'id DESC')
+      assert_kind_of Group, issue.assigned_to
+      assert_equal Group.find(11), issue.assigned_to
+    end
+  end
 
   def assert_visibility_match(user, issues)
     assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
@@ -579,6 +588,16 @@ class IssueTest < ActiveSupport::TestCase
     # author is not a member of project anymore
     assert !copy.recipients.include?(copy.author.mail)
   end
+  
+  def test_recipients_should_include_the_assigned_group_members
+    group_member = User.generate_with_protected!
+    group = Group.generate!
+    group.users << group_member
+    
+    issue = Issue.find(12)
+    issue.assigned_to = group
+    assert issue.recipients.include?(group_member.mail)
+  end
 
   def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
     user = User.find(3)
@@ -682,6 +701,28 @@ class IssueTest < ActiveSupport::TestCase
         assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
       end
     end
+    
+    context "with issue_group_assignment" do
+      should "include groups" do
+        issue = Issue.new(:project => Project.find(2))
+        
+        with_settings :issue_group_assignment => '1' do
+          assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
+          assert issue.assignable_users.include?(Group.find(11))
+        end
+      end
+    end
+    
+    context "without issue_group_assignment" do
+      should "not include groups" do
+        issue = Issue.new(:project => Project.find(2))
+        
+        with_settings :issue_group_assignment => '0' do
+          assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
+          assert !issue.assignable_users.include?(Group.find(11))
+        end
+      end
+    end
   end
 
   def test_create_should_send_email_notification
index 2ead0393503ed5f44a37548f74354c26a12b6037..d272e6090f0ddbb7b7275ac8cd5c4ebbac4a2b62 100644 (file)
@@ -109,6 +109,18 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert_equal 'Urgent', issue.priority.to_s
     assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
   end
+  
+  def test_add_issue_with_group_assignment
+    with_settings :issue_group_assignment => '1' do
+      issue = submit_email('ticket_on_given_project.eml') do |email|
+        email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
+      end
+      assert issue.is_a?(Issue)
+      assert !issue.new_record?
+      issue.reload
+      assert_equal Group.find(11), issue.assigned_to
+    end
+  end
 
   def test_add_issue_with_partial_attributes_override
     issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
@@ -446,6 +458,7 @@ class MailHandlerTest < ActiveSupport::TestCase
 
   def submit_email(filename, options={})
     raw = IO.read(File.join(FIXTURES_PATH, filename))
+    yield raw if block_given?
     MailHandler.receive(raw, options)
   end