summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2012-07-15 14:12:17 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2012-07-15 14:12:17 +0000
commitd7b669e50b1c863b748231dc8fb66a692a33cdd1 (patch)
tree59001bb1ae7cb03a9c8ce92e8ffb9b874c56f4af
parent54d55a360a21569b4a76070b52177e778d5521c7 (diff)
downloadredmine-d7b669e50b1c863b748231dc8fb66a692a33cdd1.tar.gz
redmine-d7b669e50b1c863b748231dc8fb66a692a33cdd1.zip
Workflow enhancement: editable and required fields configurable by role, tracker and status (#703, #3521).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@9977 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/controllers/issues_controller.rb6
-rw-r--r--app/controllers/roles_controller.rb2
-rw-r--r--app/controllers/trackers_controller.rb2
-rw-r--r--app/controllers/workflows_controller.rb42
-rw-r--r--app/helpers/custom_fields_helper.rb12
-rw-r--r--app/helpers/workflows_helper.rb6
-rw-r--r--app/models/issue.rb126
-rw-r--r--app/models/issue_status.rb8
-rw-r--r--app/models/role.rb4
-rw-r--r--app/models/tracker.rb15
-rw-r--r--app/models/workflow_permission.rb29
-rw-r--r--app/models/workflow_rule.rb (renamed from app/models/workflow.rb)32
-rw-r--r--app/models/workflow_transition.rb39
-rw-r--r--app/views/issues/_attributes.html.erb19
-rw-r--r--app/views/issues/_form.html.erb4
-rw-r--r--app/views/issues/_form_custom_fields.html.erb4
-rw-r--r--app/views/trackers/index.html.erb2
-rw-r--r--app/views/workflows/edit.html.erb13
-rw-r--r--app/views/workflows/permissions.html.erb91
-rw-r--r--config/routes.rb1
-rw-r--r--db/migrate/20120714122000_add_workflows_type.rb9
-rw-r--r--db/migrate/20120714122100_update_workflows_to_sti.rb9
-rw-r--r--db/migrate/20120714122200_add_workflows_rule_fields.rb11
-rw-r--r--lib/redmine/default_data/loader.rb8
-rw-r--r--public/stylesheets/application.css5
-rw-r--r--test/fixtures/workflows.yml807
-rw-r--r--test/functional/issues_controller_test.rb143
-rw-r--r--test/functional/roles_controller_test.rb2
-rw-r--r--test/functional/trackers_controller_test.rb4
-rw-r--r--test/functional/workflows_controller_test.rb122
-rw-r--r--test/unit/issue_status_test.rb14
-rw-r--r--test/unit/issue_test.rb179
-rw-r--r--test/unit/role_test.rb6
-rw-r--r--test/unit/tracker_test.rb14
-rw-r--r--test/unit/workflow_test.rb46
35 files changed, 1405 insertions, 431 deletions
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index a6365531b..246140f45 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -129,11 +129,7 @@ class IssuesController < ApplicationController
format.html { render :action => 'new', :layout => !request.xhr? }
format.js {
render(:update) { |page|
- if params[:project_change]
- page.replace_html 'all_attributes', :partial => 'form'
- else
- page.replace_html 'attributes', :partial => 'attributes'
- end
+ page.replace_html 'all_attributes', :partial => 'form'
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
page << "if ($('log_time')) {Element.#{m}('log_time');}"
}
diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb
index b947cf946..790eb28d4 100644
--- a/app/controllers/roles_controller.rb
+++ b/app/controllers/roles_controller.rb
@@ -46,7 +46,7 @@ class RolesController < ApplicationController
if request.post? && @role.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
- @role.workflows.copy(copy_from)
+ @role.workflow_rules.copy(copy_from)
end
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index'
diff --git a/app/controllers/trackers_controller.rb b/app/controllers/trackers_controller.rb
index a67583c16..5d4bfcaf1 100644
--- a/app/controllers/trackers_controller.rb
+++ b/app/controllers/trackers_controller.rb
@@ -45,7 +45,7 @@ class TrackersController < ApplicationController
if request.post? and @tracker.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
- @tracker.workflows.copy(copy_from)
+ @tracker.workflow_rules.copy(copy_from)
end
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index'
diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb
index c381666a3..3c49de8d5 100644
--- a/app/controllers/workflows_controller.rb
+++ b/app/controllers/workflows_controller.rb
@@ -23,7 +23,7 @@ class WorkflowsController < ApplicationController
before_filter :find_trackers
def index
- @workflow_counts = Workflow.count_by_tracker_and_role
+ @workflow_counts = WorkflowTransition.count_by_tracker_and_role
end
def edit
@@ -31,16 +31,15 @@ class WorkflowsController < ApplicationController
@tracker = Tracker.find_by_id(params[:tracker_id])
if request.post?
- Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
+ WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions|
transitions.each { |new_status_id, options|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
- @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
+ WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
}
}
if @role.save
- flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
return
end
@@ -53,7 +52,7 @@ class WorkflowsController < ApplicationController
@statuses ||= IssueStatus.find(:all, :order => 'position')
if @tracker && @role && @statuses.any?
- workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
+ workflows = WorkflowTransition.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
@workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author}
@@ -61,6 +60,37 @@ class WorkflowsController < ApplicationController
end
end
+ def permissions
+ @role = Role.find_by_id(params[:role_id])
+ @tracker = Tracker.find_by_id(params[:tracker_id])
+
+ if @role && @tracker
+ if request.post?
+ WorkflowPermission.destroy_all({:role_id => @role.id, :tracker_id => @tracker.id})
+ (params[:permissions] || {}).each { |field, rule_by_status_id|
+ rule_by_status_id.each { |status_id, rule|
+ if rule.present?
+ WorkflowPermission.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
+ end
+ }
+ }
+ redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker
+ return
+ end
+
+ @statuses = @tracker.issue_statuses
+ @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
+ @custom_fields = @tracker.custom_fields
+
+ @permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w|
+ h[w.old_status_id] ||= {}
+ h[w.old_status_id][w.field_name] = w.rule
+ h
+ end
+ @statuses.each {|status| @permissions[status.id] ||= {}}
+ end
+ end
+
def copy
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@@ -83,7 +113,7 @@ class WorkflowsController < ApplicationController
elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target)
else
- Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
+ WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role
end
diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb
index ff953d857..c55943b32 100644
--- a/app/helpers/custom_fields_helper.rb
+++ b/app/helpers/custom_fields_helper.rb
@@ -73,15 +73,17 @@ module CustomFieldsHelper
end
# Return custom field label tag
- def custom_field_label_tag(name, custom_value)
+ def custom_field_label_tag(name, custom_value, options={})
+ required = options[:required] || custom_value.custom_field.is_required?
+
content_tag "label", h(custom_value.custom_field.name) +
- (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
- :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
+ (required ? " <span class=\"required\">*</span>".html_safe : ""),
+ :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
end
# Return custom field tag with its label tag
- def custom_field_tag_with_label(name, custom_value)
- custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
+ def custom_field_tag_with_label(name, custom_value, options={})
+ custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb
index 59b31d2ea..3dd514042 100644
--- a/app/helpers/workflows_helper.rb
+++ b/app/helpers/workflows_helper.rb
@@ -18,4 +18,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper
+ def field_permission_tag(permissions, status, field)
+ name = field.is_a?(CustomField) ? field.id.to_s : field
+ select_tag("permissions[#{name}][#{status.id}]",
+ options_for_select([["", ""], ["Read-only", "readonly"], ["Required", "required"]], permissions[status.id][name])
+ )
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 5fc0f1d61..152953708 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -58,7 +58,7 @@ class Issue < ActiveRecord::Base
validates_length_of :subject, :maximum => 255
validates_inclusion_of :done_ratio, :in => 0..100
validates_numericality_of :estimated_hours, :allow_nil => true
- validate :validate_issue
+ validate :validate_issue, :validate_required_fields
scope :visible,
lambda {|*args| { :include => :project,
@@ -146,6 +146,11 @@ class Issue < ActiveRecord::Base
super
end
+ def reload(*args)
+ @workflow_rule_by_attribute = nil
+ super
+ end
+
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
@@ -208,7 +213,9 @@ class Issue < ActiveRecord::Base
def status_id=(sid)
self.status = nil
- write_attribute(:status_id, sid)
+ result = write_attribute(:status_id, sid)
+ @workflow_rule_by_attribute = nil
+ result
end
def priority_id=(pid)
@@ -230,6 +237,7 @@ class Issue < ActiveRecord::Base
self.tracker = nil
result = write_attribute(:tracker_id, tid)
@custom_field_values = nil
+ @workflow_rule_by_attribute = nil
result
end
@@ -336,9 +344,10 @@ class Issue < ActiveRecord::Base
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
user.allowed_to?(:manage_subtasks, issue.project)}
- def safe_attribute_names(*args)
- names = super(*args)
+ def safe_attribute_names(user=nil)
+ names = super
names -= disabled_core_fields
+ names -= read_only_attribute_names(user)
names
end
@@ -362,15 +371,15 @@ class Issue < ActiveRecord::Base
self.tracker_id = t
end
- attrs = delete_unsafe_attributes(attrs, user)
- return if attrs.empty?
-
- if attrs['status_id']
- unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
- attrs.delete('status_id')
+ if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
+ if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
+ self.status_id = s
end
end
+ attrs = delete_unsafe_attributes(attrs, user)
+ return if attrs.empty?
+
unless leaf?
attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
end
@@ -379,6 +388,14 @@ class Issue < ActiveRecord::Base
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end
+ if attrs['custom_field_values'].present?
+ attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s}
+ end
+
+ if attrs['custom_fields'].present?
+ attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s}
+ end
+
# mass-assignment security bypass
assign_attributes attrs, :without_protection => true
end
@@ -387,6 +404,76 @@ class Issue < ActiveRecord::Base
tracker ? tracker.disabled_core_fields : []
end
+ # Returns the custom_field_values that can be edited by the given user
+ def editable_custom_field_values(user=nil)
+ custom_field_values.reject do |value|
+ read_only_attribute_names(user).include?(value.custom_field_id.to_s)
+ end
+ end
+
+ # Returns the names of attributes that are read-only for user or the current user
+ # For users with multiple roles, the read-only fields are the intersection of
+ # read-only fields of each role
+ # The result is an array of strings where sustom fields are represented with their ids
+ #
+ # Examples:
+ # issue.read_only_attribute_names # => ['due_date', '2']
+ # issue.read_only_attribute_names(user) # => []
+ def read_only_attribute_names(user=nil)
+ workflow_rule_by_attribute(user).select {|attr, rule| rule == 'readonly'}.keys
+ end
+
+ # Returns the names of required attributes for user or the current user
+ # For users with multiple roles, the required fields are the intersection of
+ # required fields of each role
+ # The result is an array of strings where sustom fields are represented with their ids
+ #
+ # Examples:
+ # issue.required_attribute_names # => ['due_date', '2']
+ # issue.required_attribute_names(user) # => []
+ def required_attribute_names(user=nil)
+ workflow_rule_by_attribute(user).select {|attr, rule| rule == 'required'}.keys
+ end
+
+ # Returns true if the attribute is required for user
+ def required_attribute?(name, user=nil)
+ required_attribute_names(user).include?(name.to_s)
+ end
+
+ # Returns a hash of the workflow rule by attribute for the given user
+ #
+ # Examples:
+ # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
+ def workflow_rule_by_attribute(user=nil)
+ return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
+
+ user_real = user || User.current
+ roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
+ return {} if roles.empty?
+
+ result = {}
+ workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
+ if workflow_permissions.any?
+ workflow_rules = workflow_permissions.inject({}) do |h, wp|
+ h[wp.field_name] ||= []
+ h[wp.field_name] << wp.rule
+ h
+ end
+ workflow_rules.each do |attr, rules|
+ next if rules.size < roles.size
+ uniq_rules = rules.uniq
+ if uniq_rules.size == 1
+ result[attr] = uniq_rules.first
+ else
+ result[attr] = 'required'
+ end
+ end
+ end
+ @workflow_rule_by_attribute = result if user.nil?
+ result
+ end
+ private :workflow_rule_by_attribute
+
def done_ratio
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
status.default_done_ratio
@@ -448,6 +535,25 @@ class Issue < ActiveRecord::Base
end
end
+ # Validates the issue against additional workflow requirements
+ def validate_required_fields
+ user = new_record? ? author : current_journal.try(:user)
+
+ required_attribute_names(user).each do |attribute|
+ if attribute =~ /^\d+$/
+ attribute = attribute.to_i
+ v = custom_field_values.detect {|v| v.custom_field_id == attribute }
+ if v && v.value.blank?
+ errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
+ end
+ else
+ if respond_to?(attribute) && send(attribute).blank?
+ errors.add attribute, :blank
+ end
+ end
+ end
+ end
+
# Set the done_ratio using the status if that setting is set. This will keep the done_ratios
# even if the user turns off the setting later
def update_done_ratio_from_issue_status
diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb
index 6973ca656..2d09d5e88 100644
--- a/app/models/issue_status.rb
+++ b/app/models/issue_status.rb
@@ -17,10 +17,10 @@
class IssueStatus < ActiveRecord::Base
before_destroy :check_integrity
- has_many :workflows, :foreign_key => "old_status_id"
+ has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
acts_as_list
- before_destroy :delete_workflows
+ before_destroy :delete_workflow_rules
after_save :update_default
validates_presence_of :name
@@ -98,7 +98,7 @@ private
end
# Deletes associated workflows
- def delete_workflows
- Workflow.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
+ def delete_workflow_rules
+ WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
end
end
diff --git a/app/models/role.rb b/app/models/role.rb
index 8e8737afb..412e5a63c 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -47,9 +47,9 @@ class Role < ActiveRecord::Base
}
before_destroy :check_deletable
- has_many :workflows, :dependent => :delete_all do
+ has_many :workflow_rules, :dependent => :delete_all do
def copy(source_role)
- Workflow.copy(nil, source_role, nil, proxy_association.owner)
+ WorkflowRule.copy(nil, source_role, nil, proxy_association.owner)
end
end
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
index 109e0f423..472b94b31 100644
--- a/app/models/tracker.rb
+++ b/app/models/tracker.rb
@@ -17,14 +17,17 @@
class Tracker < ActiveRecord::Base
- # Other fields should be appended, not inserted!
- CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio)
+ CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze
+ # Fields that can be disabled
+ # Other (future) fields should be appended, not inserted!
+ CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio).freeze
+ CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze
before_destroy :check_integrity
has_many :issues
- has_many :workflows, :dependent => :delete_all do
+ has_many :workflow_rules, :dependent => :delete_all do
def copy(source_tracker)
- Workflow.copy(source_tracker, nil, proxy_association.owner, nil)
+ WorkflowRule.copy(source_tracker, nil, proxy_association.owner, nil)
end
end
@@ -56,8 +59,8 @@ class Tracker < ActiveRecord::Base
return []
end
- ids = Workflow.
- connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{Workflow.table_name} WHERE tracker_id = #{id}").
+ ids = WorkflowTransition.
+ connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'").
flatten.
uniq
diff --git a/app/models/workflow_permission.rb b/app/models/workflow_permission.rb
new file mode 100644
index 000000000..72e95543f
--- /dev/null
+++ b/app/models/workflow_permission.rb
@@ -0,0 +1,29 @@
+# Redmine - project management software
+# Copyright (C) 2006-2012 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class WorkflowPermission < WorkflowRule
+ validates_inclusion_of :rule, :in => %w(readonly required)
+ validate :validate_field_name
+
+ protected
+
+ def validate_field_name
+ unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/)
+ errors.add :field_name, :invalid
+ end
+ end
+end
diff --git a/app/models/workflow.rb b/app/models/workflow_rule.rb
index 36b4c7df8..2fc020ba1 100644
--- a/app/models/workflow.rb
+++ b/app/models/workflow_rule.rb
@@ -15,31 +15,15 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-class Workflow < ActiveRecord::Base
+class WorkflowRule < ActiveRecord::Base
+ self.table_name = "#{table_name_prefix}workflows#{table_name_suffix}"
+
belongs_to :role
+ belongs_to :tracker
belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
- validates_presence_of :role, :old_status, :new_status
-
- # Returns workflow transitions count by tracker and role
- def self.count_by_tracker_and_role
- counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{Workflow.table_name} GROUP BY role_id, tracker_id")
- roles = Role.sorted.all
- trackers = Tracker.sorted.all
-
- result = []
- trackers.each do |tracker|
- t = []
- roles.each do |role|
- row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
- t << [role, (row.nil? ? 0 : row['c'].to_i)]
- end
- result << [tracker, t]
- end
-
- result
- end
+ validates_presence_of :role, :tracker, :old_status
# Copies workflows from source to targets
def self.copy(source_tracker, source_role, target_trackers, target_roles)
@@ -78,9 +62,9 @@ class Workflow < ActiveRecord::Base
else
transaction do
delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
- connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee)" +
- " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee" +
- " FROM #{Workflow.table_name}" +
+ connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, rule, type)" +
+ " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, rule, type" +
+ " FROM #{WorkflowRule.table_name}" +
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
end
true
diff --git a/app/models/workflow_transition.rb b/app/models/workflow_transition.rb
new file mode 100644
index 000000000..0c01edd04
--- /dev/null
+++ b/app/models/workflow_transition.rb
@@ -0,0 +1,39 @@
+# Redmine - project management software
+# Copyright (C) 2006-2012 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class WorkflowTransition < WorkflowRule
+ validates_presence_of :new_status
+
+ # Returns workflow transitions count by tracker and role
+ def self.count_by_tracker_and_role
+ counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
+ roles = Role.sorted.all
+ trackers = Tracker.sorted.all
+
+ result = []
+ trackers.each do |tracker|
+ t = []
+ roles.each do |role|
+ row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
+ t << [role, (row.nil? ? 0 : row['c'].to_i)]
+ end
+ result << [tracker, t]
+ end
+
+ result
+ end
+end
diff --git a/app/views/issues/_attributes.html.erb b/app/views/issues/_attributes.html.erb
index d2c13e82b..cc51cd435 100644
--- a/app/views/issues/_attributes.html.erb
+++ b/app/views/issues/_attributes.html.erb
@@ -4,6 +4,9 @@
<div class="splitcontentleft">
<% if @issue.safe_attribute? 'status_id' %>
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
+<%= observe_field :issue_status_id, :url => project_issue_form_path(@project, :id => @issue),
+ :with => "Form.serialize('issue-form')" %>
+
<% else %>
<p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
<% end %>
@@ -13,11 +16,11 @@
<% end %>
<% if @issue.safe_attribute? 'assigned_to_id' %>
-<p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
+<p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %></p>
<% end %>
<% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
-<p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
+<p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %>
<%= link_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
{:url => new_project_issue_category_path(@issue.project), :method => 'get'},
:title => l(:label_issue_category_new),
@@ -25,7 +28,7 @@
<% end %>
<% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
-<p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
+<p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %>
<%= link_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
{:url => new_project_version_path(@issue.project), :method => 'get'},
:title => l(:label_version_new),
@@ -36,25 +39,25 @@
<div class="splitcontentright">
<% if @issue.safe_attribute? 'parent_issue_id' %>
-<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
+<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %></p>
<div id="parent_issue_candidates" class="autocomplete"></div>
<%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
<% end %>
<% if @issue.safe_attribute? 'start_date' %>
-<p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
+<p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('start_date') %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
<% end %>
<% if @issue.safe_attribute? 'due_date' %>
-<p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
+<p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('due_date') %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
<% end %>
<% if @issue.safe_attribute? 'estimated_hours' %>
-<p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
+<p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p>
<% end %>
<% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
-<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
+<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
<% end %>
</div>
</div>
diff --git a/app/views/issues/_form.html.erb b/app/views/issues/_form.html.erb
index 9bc362377..4a13fa07a 100644
--- a/app/views/issues/_form.html.erb
+++ b/app/views/issues/_form.html.erb
@@ -9,7 +9,7 @@
<% if @issue.safe_attribute? 'project_id' %>
<p><%= f.select :project_id, project_tree_options_for_select(@issue.allowed_target_projects, :selected => @issue.project), :required => true %></p>
-<%= observe_field :issue_project_id, :url => project_issue_form_path(@project, :id => @issue, :project_change => '1'),
+<%= observe_field :issue_project_id, :url => project_issue_form_path(@project, :id => @issue),
:with => "Form.serialize('issue-form')" %>
<% end %>
@@ -25,7 +25,7 @@
<% if @issue.safe_attribute? 'description' %>
<p>
- <label><%= l(:field_description) %></label>
+ <%= f.label_for_field :description, :required => @issue.required_attribute?('description') %>
<%= link_to_function image_tag('edit.png'),
'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
diff --git a/app/views/issues/_form_custom_fields.html.erb b/app/views/issues/_form_custom_fields.html.erb
index 33b0b848b..4da98eb13 100644
--- a/app/views/issues/_form_custom_fields.html.erb
+++ b/app/views/issues/_form_custom_fields.html.erb
@@ -2,8 +2,8 @@
<div class="splitcontentleft">
<% i = 0 %>
<% split_on = (@issue.custom_field_values.size / 2.0).ceil - 1 %>
-<% @issue.custom_field_values.each do |value| %>
- <p><%= custom_field_tag_with_label :issue, value %></p>
+<% @issue.editable_custom_field_values.each do |value| %>
+ <p><%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %></p>
<% if i == split_on -%>
</div><div class="splitcontentright">
<% end -%>
diff --git a/app/views/trackers/index.html.erb b/app/views/trackers/index.html.erb
index 0515ae335..4d10d857e 100644
--- a/app/views/trackers/index.html.erb
+++ b/app/views/trackers/index.html.erb
@@ -15,7 +15,7 @@
<% for tracker in @trackers %>
<tr class="<%= cycle("odd", "even") %>">
<td><%= link_to h(tracker.name), edit_tracker_path(tracker) %></td>
- <td align="center"><% unless tracker.workflows.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)</span><% end %></td>
+ <td align="center"><% unless tracker.workflow_rules.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)</span><% end %></td>
<td align="center" style="width:15%;"><%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %></td>
<td class="buttons">
<%= delete_link tracker_path(tracker) %>
diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb
index 20eaff992..a634ee310 100644
--- a/app/views/workflows/edit.html.erb
+++ b/app/views/workflows/edit.html.erb
@@ -2,6 +2,13 @@
<h2><%=l(:label_workflow)%></h2>
+<div class="tabs">
+ <ul>
+ <li><%= link_to 'Status transitions', {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
+ <li><%= link_to 'Fields permissions', {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %></li>
+ </ul>
+</div>
+
<p><%=l(:text_workflow_edit)%>:</p>
<%= form_tag({}, :method => 'get') do %>
@@ -12,11 +19,11 @@
<label><%=l(:label_tracker)%>:
<%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
+ <%= submit_tag l(:button_edit), :name => nil %>
+
<%= hidden_field_tag 'used_statuses_only', '0' %>
<label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
-</p>
-<p>
-<%= submit_tag l(:button_edit), :name => nil %>
+
</p>
<% end %>
diff --git a/app/views/workflows/permissions.html.erb b/app/views/workflows/permissions.html.erb
new file mode 100644
index 000000000..73cf91656
--- /dev/null
+++ b/app/views/workflows/permissions.html.erb
@@ -0,0 +1,91 @@
+<%= render :partial => 'action_menu' %>
+
+<h2><%=l(:label_workflow)%></h2>
+
+<div class="tabs">
+ <ul>
+ <li><%= link_to 'Status transitions', {:action => 'edit', :role_id => @role, :tracker_id => @tracker} %></li>
+ <li><%= link_to 'Fields permissions', {:action => 'permissions', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
+ </ul>
+</div>
+
+<p><%=l(:text_workflow_edit)%>:</p>
+
+<%= form_tag({}, :method => 'get') do %>
+<p>
+ <label><%=l(:label_role)%>:
+ <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label>
+
+ <label><%=l(:label_tracker)%>:
+ <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
+
+ <%= submit_tag l(:button_edit), :name => nil %>
+</p>
+<% end %>
+
+<% if @tracker && @role && @statuses.any? %>
+ <%= form_tag({}, :id => 'workflow_form' ) do %>
+ <%= hidden_field_tag 'tracker_id', @tracker.id %>
+ <%= hidden_field_tag 'role_id', @role.id %>
+ <div class="autoscroll">
+ <table class="list fields_permissions">
+ <thead>
+ <tr>
+ <th align="left">
+ </th>
+ <th align="center" colspan="<%= @statuses.length %>"><%=l(:label_issue_status)%></th>
+ </tr>
+ <tr>
+ <td></td>
+ <% for status in @statuses %>
+ <td width="<%= 75 / @statuses.size %>%" align="center">
+ <%=h status.name %>
+ </td>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="group open">
+ <td colspan="<%= @statuses.size + 1 %>">
+ <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
+ <%= l(:field_core_fields) %>
+ </td>
+ </tr>
+ <% @fields.each do |field, name| %>
+ <tr class="<%= cycle("odd", "even") %>">
+ <td>
+ <%=h name %>
+ </td>
+ <% for status in @statuses -%>
+ <td align="center" class="<%= @permissions[status.id][field] %>">
+ <%= field_permission_tag(@permissions, status, field) %>
+ </td>
+ <% end -%>
+ </tr>
+ <% end %>
+ <% if @custom_fields.any? %>
+ <tr class="group open">
+ <td colspan="<%= @statuses.size + 1 %>">
+ <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
+ <%= l(:label_custom_field_plural) %>
+ </td>
+ </tr>
+ <% @custom_fields.each do |field| %>
+ <tr class="<%= cycle("odd", "even") %>">
+ <td>
+ <%=h field.name %>
+ </td>
+ <% for status in @statuses -%>
+ <td align="center" class="<%= @permissions[status.id][field.id.to_s] %>">
+ <%= field_permission_tag(@permissions, status, field) %>
+ </td>
+ <% end -%>
+ </tr>
+ <% end %>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+ <%= submit_tag l(:button_save) %>
+ <% end %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index e3ed8859a..0ee3908b1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -312,6 +312,7 @@ RedmineApp::Application.routes.draw do
match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
+ match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
match 'settings', :controller => 'settings', :action => 'index', :via => :get
match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
diff --git a/db/migrate/20120714122000_add_workflows_type.rb b/db/migrate/20120714122000_add_workflows_type.rb
new file mode 100644
index 000000000..f263f25df
--- /dev/null
+++ b/db/migrate/20120714122000_add_workflows_type.rb
@@ -0,0 +1,9 @@
+class AddWorkflowsType < ActiveRecord::Migration
+ def up
+ add_column :workflows, :type, :string, :limit => 30
+ end
+
+ def down
+ remove_column :workflows, :type
+ end
+end
diff --git a/db/migrate/20120714122100_update_workflows_to_sti.rb b/db/migrate/20120714122100_update_workflows_to_sti.rb
new file mode 100644
index 000000000..8ee5c6dc3
--- /dev/null
+++ b/db/migrate/20120714122100_update_workflows_to_sti.rb
@@ -0,0 +1,9 @@
+class UpdateWorkflowsToSti < ActiveRecord::Migration
+ def up
+ WorkflowRule.update_all "type = 'WorkflowTransition'"
+ end
+
+ def down
+ WorkflowRule.update_all "type = NULL"
+ end
+end
diff --git a/db/migrate/20120714122200_add_workflows_rule_fields.rb b/db/migrate/20120714122200_add_workflows_rule_fields.rb
new file mode 100644
index 000000000..6e9c7df70
--- /dev/null
+++ b/db/migrate/20120714122200_add_workflows_rule_fields.rb
@@ -0,0 +1,11 @@
+class AddWorkflowsRuleFields < ActiveRecord::Migration
+ def up
+ add_column :workflows, :field_name, :string, :limit => 30
+ add_column :workflows, :rule, :string, :limit => 30
+ end
+
+ def down
+ remove_column :workflows, :field_name
+ remove_column :workflows, :rule
+ end
+end
diff --git a/lib/redmine/default_data/loader.rb b/lib/redmine/default_data/loader.rb
index 2133ee668..910a6a319 100644
--- a/lib/redmine/default_data/loader.rb
+++ b/lib/redmine/default_data/loader.rb
@@ -140,7 +140,7 @@ module Redmine
Tracker.find(:all).each { |t|
IssueStatus.find(:all).each { |os|
IssueStatus.find(:all).each { |ns|
- Workflow.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
+ WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
}
}
}
@@ -148,7 +148,7 @@ module Redmine
Tracker.find(:all).each { |t|
[new, in_progress, resolved, feedback].each { |os|
[in_progress, resolved, feedback, closed].each { |ns|
- Workflow.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
+ WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
}
}
}
@@ -156,10 +156,10 @@ module Redmine
Tracker.find(:all).each { |t|
[new, in_progress, resolved, feedback].each { |os|
[closed].each { |ns|
- Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
+ WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
}
}
- Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id)
+ WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id)
}
# Enumerations
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 57f2f24b6..013c1b726 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -433,6 +433,9 @@ ul.properties li span {font-style:italic;}
#user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
#workflow_copy_form select { width: 200px; }
+table.fields_permissions select {font-size:90%}
+table.fields_permissions td.readonly {background:#ddd;}
+table.fields_permissions td.required {background:#d88;}
textarea#custom_field_possible_values {width: 99%}
input#content_comments {width: 99%}
@@ -504,7 +507,7 @@ input#time_entry_comments { width: 90%;}
fieldset.settings label { display: block; }
fieldset#notified_events .parent { padding-left: 20px; }
-.required {color: #bb0000;}
+span.required {color: #bb0000;}
.summary {font-style: italic;}
#attachments_fields input.description {margin-left: 8px; width:340px;}
diff --git a/test/fixtures/workflows.yml b/test/fixtures/workflows.yml
index 6126055ca..d544545b3 100644
--- a/test/fixtures/workflows.yml
+++ b/test/fixtures/workflows.yml
@@ -1,1615 +1,1884 @@
---
-workflows_189:
+WorkflowTransitions_189:
new_status_id: 5
role_id: 1
old_status_id: 2
id: 189
tracker_id: 3
-workflows_001:
+ type: WorkflowTransition
+WorkflowTransitions_001:
new_status_id: 2
role_id: 1
old_status_id: 1
id: 1
tracker_id: 1
-workflows_002:
+ type: WorkflowTransition
+WorkflowTransitions_002:
new_status_id: 3
role_id: 1
old_status_id: 1
id: 2
tracker_id: 1
-workflows_003:
+ type: WorkflowTransition
+WorkflowTransitions_003:
new_status_id: 4
role_id: 1
old_status_id: 1
id: 3
tracker_id: 1
-workflows_110:
+ type: WorkflowTransition
+WorkflowTransitions_110:
new_status_id: 6
role_id: 1
old_status_id: 4
id: 110
tracker_id: 2
-workflows_004:
+ type: WorkflowTransition
+WorkflowTransitions_004:
new_status_id: 5
role_id: 1
old_status_id: 1
id: 4
tracker_id: 1
-workflows_030:
+ type: WorkflowTransition
+WorkflowTransitions_030:
new_status_id: 5
role_id: 1
old_status_id: 6
id: 30
tracker_id: 1
-workflows_111:
+ type: WorkflowTransition
+WorkflowTransitions_111:
new_status_id: 1
role_id: 1
old_status_id: 5
id: 111
tracker_id: 2
-workflows_005:
+ type: WorkflowTransition
+WorkflowTransitions_005:
new_status_id: 6
role_id: 1
old_status_id: 1
id: 5
tracker_id: 1
-workflows_031:
+ type: WorkflowTransition
+WorkflowTransitions_031:
new_status_id: 2
role_id: 2
old_status_id: 1
id: 31
tracker_id: 1
-workflows_112:
+ type: WorkflowTransition
+WorkflowTransitions_112:
new_status_id: 2
role_id: 1
old_status_id: 5
id: 112
tracker_id: 2
-workflows_006:
+ type: WorkflowTransition
+WorkflowTransitions_006:
new_status_id: 1
role_id: 1
old_status_id: 2
id: 6
tracker_id: 1
-workflows_032:
+ type: WorkflowTransition
+WorkflowTransitions_032:
new_status_id: 3
role_id: 2
old_status_id: 1
id: 32
tracker_id: 1
-workflows_113:
+ type: WorkflowTransition
+WorkflowTransitions_113:
new_status_id: 3
role_id: 1
old_status_id: 5
id: 113
tracker_id: 2
-workflows_220:
+ type: WorkflowTransition
+WorkflowTransitions_220:
new_status_id: 6
role_id: 2
old_status_id: 2
id: 220
tracker_id: 3
-workflows_007:
+ type: WorkflowTransition
+WorkflowTransitions_007:
new_status_id: 3
role_id: 1
old_status_id: 2
id: 7
tracker_id: 1
-workflows_033:
+ type: WorkflowTransition
+WorkflowTransitions_033:
new_status_id: 4
role_id: 2
old_status_id: 1
id: 33
tracker_id: 1
-workflows_060:
+ type: WorkflowTransition
+WorkflowTransitions_060:
new_status_id: 5
role_id: 2
old_status_id: 6
id: 60
tracker_id: 1
-workflows_114:
+ type: WorkflowTransition
+WorkflowTransitions_114:
new_status_id: 4
role_id: 1
old_status_id: 5
id: 114
tracker_id: 2
-workflows_140:
+ type: WorkflowTransition
+WorkflowTransitions_140:
new_status_id: 6
role_id: 2
old_status_id: 4
id: 140
tracker_id: 2
-workflows_221:
+ type: WorkflowTransition
+WorkflowTransitions_221:
new_status_id: 1
role_id: 2
old_status_id: 3
id: 221
tracker_id: 3
-workflows_008:
+ type: WorkflowTransition
+WorkflowTransitions_008:
new_status_id: 4
role_id: 1
old_status_id: 2
id: 8
tracker_id: 1
-workflows_034:
+ type: WorkflowTransition
+WorkflowTransitions_034:
new_status_id: 5
role_id: 2
old_status_id: 1
id: 34
tracker_id: 1
-workflows_115:
+ type: WorkflowTransition
+WorkflowTransitions_115:
new_status_id: 6
role_id: 1
old_status_id: 5
id: 115
tracker_id: 2
-workflows_141:
+ type: WorkflowTransition
+WorkflowTransitions_141:
new_status_id: 1
role_id: 2
old_status_id: 5
id: 141
tracker_id: 2
-workflows_222:
+ type: WorkflowTransition
+WorkflowTransitions_222:
new_status_id: 2
role_id: 2
old_status_id: 3
id: 222
tracker_id: 3
-workflows_223:
+ type: WorkflowTransition
+WorkflowTransitions_223:
new_status_id: 4
role_id: 2
old_status_id: 3
id: 223
tracker_id: 3
-workflows_009:
+ type: WorkflowTransition
+WorkflowTransitions_009:
new_status_id: 5
role_id: 1
old_status_id: 2
id: 9
tracker_id: 1
-workflows_035:
+ type: WorkflowTransition
+WorkflowTransitions_035:
new_status_id: 6
role_id: 2
old_status_id: 1
id: 35
tracker_id: 1
-workflows_061:
+ type: WorkflowTransition
+WorkflowTransitions_061:
new_status_id: 2
role_id: 3
old_status_id: 1
id: 61
tracker_id: 1
-workflows_116:
+ type: WorkflowTransition
+WorkflowTransitions_116:
new_status_id: 1
role_id: 1
old_status_id: 6
id: 116
tracker_id: 2
-workflows_142:
+ type: WorkflowTransition
+WorkflowTransitions_142:
new_status_id: 2
role_id: 2
old_status_id: 5
id: 142
tracker_id: 2
-workflows_250:
+ type: WorkflowTransition
+WorkflowTransitions_250:
new_status_id: 6
role_id: 3
old_status_id: 2
id: 250
tracker_id: 3
-workflows_224:
+ type: WorkflowTransition
+WorkflowTransitions_224:
new_status_id: 5
role_id: 2
old_status_id: 3
id: 224
tracker_id: 3
-workflows_036:
+ type: WorkflowTransition
+WorkflowTransitions_036:
new_status_id: 1
role_id: 2
old_status_id: 2
id: 36
tracker_id: 1
-workflows_062:
+ type: WorkflowTransition
+WorkflowTransitions_062:
new_status_id: 3
role_id: 3
old_status_id: 1
id: 62
tracker_id: 1
-workflows_117:
+ type: WorkflowTransition
+WorkflowTransitions_117:
new_status_id: 2
role_id: 1
old_status_id: 6
id: 117
tracker_id: 2
-workflows_143:
+ type: WorkflowTransition
+WorkflowTransitions_143:
new_status_id: 3
role_id: 2
old_status_id: 5
id: 143
tracker_id: 2
-workflows_170:
+ type: WorkflowTransition
+WorkflowTransitions_170:
new_status_id: 6
role_id: 3
old_status_id: 4
id: 170
tracker_id: 2
-workflows_251:
+ type: WorkflowTransition
+WorkflowTransitions_251:
new_status_id: 1
role_id: 3
old_status_id: 3
id: 251
tracker_id: 3
-workflows_225:
+ type: WorkflowTransition
+WorkflowTransitions_225:
new_status_id: 6
role_id: 2
old_status_id: 3
id: 225
tracker_id: 3
-workflows_063:
+ type: WorkflowTransition
+WorkflowTransitions_063:
new_status_id: 4
role_id: 3
old_status_id: 1
id: 63
tracker_id: 1
-workflows_090:
+ type: WorkflowTransition
+WorkflowTransitions_090:
new_status_id: 5
role_id: 3
old_status_id: 6
id: 90
tracker_id: 1
-workflows_118:
+ type: WorkflowTransition
+WorkflowTransitions_118:
new_status_id: 3
role_id: 1
old_status_id: 6
id: 118
tracker_id: 2
-workflows_144:
+ type: WorkflowTransition
+WorkflowTransitions_144:
new_status_id: 4
role_id: 2
old_status_id: 5
id: 144
tracker_id: 2
-workflows_252:
+ type: WorkflowTransition
+WorkflowTransitions_252:
new_status_id: 2
role_id: 3
old_status_id: 3
id: 252
tracker_id: 3
-workflows_226:
+ type: WorkflowTransition
+WorkflowTransitions_226:
new_status_id: 1
role_id: 2
old_status_id: 4
id: 226
tracker_id: 3
-workflows_038:
+ type: WorkflowTransition
+WorkflowTransitions_038:
new_status_id: 4
role_id: 2
old_status_id: 2
id: 38
tracker_id: 1
-workflows_064:
+ type: WorkflowTransition
+WorkflowTransitions_064:
new_status_id: 5
role_id: 3
old_status_id: 1
id: 64
tracker_id: 1
-workflows_091:
+ type: WorkflowTransition
+WorkflowTransitions_091:
new_status_id: 2
role_id: 1
old_status_id: 1
id: 91
tracker_id: 2
-workflows_119:
+ type: WorkflowTransition
+WorkflowTransitions_119:
new_status_id: 4
role_id: 1
old_status_id: 6
id: 119
tracker_id: 2
-workflows_145:
+ type: WorkflowTransition
+WorkflowTransitions_145:
new_status_id: 6
role_id: 2
old_status_id: 5
id: 145
tracker_id: 2
-workflows_171:
+ type: WorkflowTransition
+WorkflowTransitions_171:
new_status_id: 1
role_id: 3
old_status_id: 5
id: 171
tracker_id: 2
-workflows_253:
+ type: WorkflowTransition
+WorkflowTransitions_253:
new_status_id: 4
role_id: 3
old_status_id: 3
id: 253
tracker_id: 3
-workflows_227:
+ type: WorkflowTransition
+WorkflowTransitions_227:
new_status_id: 2
role_id: 2
old_status_id: 4
id: 227
tracker_id: 3
-workflows_039:
+ type: WorkflowTransition
+WorkflowTransitions_039:
new_status_id: 5
role_id: 2
old_status_id: 2
id: 39
tracker_id: 1
-workflows_065:
+ type: WorkflowTransition
+WorkflowTransitions_065:
new_status_id: 6
role_id: 3
old_status_id: 1
id: 65
tracker_id: 1
-workflows_092:
+ type: WorkflowTransition
+WorkflowTransitions_092:
new_status_id: 3
role_id: 1
old_status_id: 1
id: 92
tracker_id: 2
-workflows_146:
+ type: WorkflowTransition
+WorkflowTransitions_146:
new_status_id: 1
role_id: 2
old_status_id: 6
id: 146
tracker_id: 2
-workflows_172:
+ type: WorkflowTransition
+WorkflowTransitions_172:
new_status_id: 2
role_id: 3
old_status_id: 5
id: 172
tracker_id: 2
-workflows_254:
+ type: WorkflowTransition
+WorkflowTransitions_254:
new_status_id: 5
role_id: 3
old_status_id: 3
id: 254
tracker_id: 3
-workflows_228:
+ type: WorkflowTransition
+WorkflowTransitions_228:
new_status_id: 3
role_id: 2
old_status_id: 4
id: 228
tracker_id: 3
-workflows_066:
+ type: WorkflowTransition
+WorkflowTransitions_066:
new_status_id: 1
role_id: 3
old_status_id: 2
id: 66
tracker_id: 1
-workflows_093:
+ type: WorkflowTransition
+WorkflowTransitions_093:
new_status_id: 4
role_id: 1
old_status_id: 1
id: 93
tracker_id: 2
-workflows_147:
+ type: WorkflowTransition
+WorkflowTransitions_147:
new_status_id: 2
role_id: 2
old_status_id: 6
id: 147
tracker_id: 2
-workflows_173:
+ type: WorkflowTransition
+WorkflowTransitions_173:
new_status_id: 3
role_id: 3
old_status_id: 5
id: 173
tracker_id: 2
-workflows_255:
+ type: WorkflowTransition
+WorkflowTransitions_255:
new_status_id: 6
role_id: 3
old_status_id: 3
id: 255
tracker_id: 3
-workflows_229:
+ type: WorkflowTransition
+WorkflowTransitions_229:
new_status_id: 5
role_id: 2
old_status_id: 4
id: 229
tracker_id: 3
-workflows_067:
+ type: WorkflowTransition
+WorkflowTransitions_067:
new_status_id: 3
role_id: 3
old_status_id: 2
id: 67
tracker_id: 1
-workflows_148:
+ type: WorkflowTransition
+WorkflowTransitions_148:
new_status_id: 3
role_id: 2
old_status_id: 6
id: 148
tracker_id: 2
-workflows_174:
+ type: WorkflowTransition
+WorkflowTransitions_174:
new_status_id: 4
role_id: 3
old_status_id: 5
id: 174
tracker_id: 2
-workflows_256:
+ type: WorkflowTransition
+WorkflowTransitions_256:
new_status_id: 1
role_id: 3
old_status_id: 4
id: 256
tracker_id: 3
-workflows_068:
+ type: WorkflowTransition
+WorkflowTransitions_068:
new_status_id: 4
role_id: 3
old_status_id: 2
id: 68
tracker_id: 1
-workflows_094:
+ type: WorkflowTransition
+WorkflowTransitions_094:
new_status_id: 5
role_id: 1
old_status_id: 1
id: 94
tracker_id: 2
-workflows_149:
+ type: WorkflowTransition
+WorkflowTransitions_149:
new_status_id: 4
role_id: 2
old_status_id: 6
id: 149
tracker_id: 2
-workflows_175:
+ type: WorkflowTransition
+WorkflowTransitions_175:
new_status_id: 6
role_id: 3
old_status_id: 5
id: 175
tracker_id: 2
-workflows_257:
+ type: WorkflowTransition
+WorkflowTransitions_257:
new_status_id: 2
role_id: 3
old_status_id: 4
id: 257
tracker_id: 3
-workflows_069:
+ type: WorkflowTransition
+WorkflowTransitions_069:
new_status_id: 5
role_id: 3
old_status_id: 2
id: 69
tracker_id: 1
-workflows_095:
+ type: WorkflowTransition
+WorkflowTransitions_095:
new_status_id: 6
role_id: 1
old_status_id: 1
id: 95
tracker_id: 2
-workflows_176:
+ type: WorkflowTransition
+WorkflowTransitions_176:
new_status_id: 1
role_id: 3
old_status_id: 6
id: 176
tracker_id: 2
-workflows_258:
+ type: WorkflowTransition
+WorkflowTransitions_258:
new_status_id: 3
role_id: 3
old_status_id: 4
id: 258
tracker_id: 3
-workflows_096:
+ type: WorkflowTransition
+WorkflowTransitions_096:
new_status_id: 1
role_id: 1
old_status_id: 2
id: 96
tracker_id: 2
-workflows_177:
+ type: WorkflowTransition
+WorkflowTransitions_177:
new_status_id: 2
role_id: 3
old_status_id: 6
id: 177
tracker_id: 2
-workflows_259:
+ type: WorkflowTransition
+WorkflowTransitions_259:
new_status_id: 5
role_id: 3
old_status_id: 4
id: 259
tracker_id: 3
-workflows_097:
+ type: WorkflowTransition
+WorkflowTransitions_097:
new_status_id: 3
role_id: 1
old_status_id: 2
id: 97
tracker_id: 2
-workflows_178:
+ type: WorkflowTransition
+WorkflowTransitions_178:
new_status_id: 3
role_id: 3
old_status_id: 6
id: 178
tracker_id: 2
-workflows_098:
+ type: WorkflowTransition
+WorkflowTransitions_098:
new_status_id: 4
role_id: 1
old_status_id: 2
id: 98
tracker_id: 2
-workflows_179:
+ type: WorkflowTransition
+WorkflowTransitions_179:
new_status_id: 4
role_id: 3
old_status_id: 6
id: 179
tracker_id: 2
-workflows_099:
+ type: WorkflowTransition
+WorkflowTransitions_099:
new_status_id: 5
role_id: 1
old_status_id: 2
id: 99
tracker_id: 2
-workflows_100:
+ type: WorkflowTransition
+WorkflowTransitions_100:
new_status_id: 6
role_id: 1
old_status_id: 2
id: 100
tracker_id: 2
-workflows_020:
+ type: WorkflowTransition
+WorkflowTransitions_020:
new_status_id: 6
role_id: 1
old_status_id: 4
id: 20
tracker_id: 1
-workflows_101:
+ type: WorkflowTransition
+WorkflowTransitions_101:
new_status_id: 1
role_id: 1
old_status_id: 3
id: 101
tracker_id: 2
-workflows_021:
+ type: WorkflowTransition
+WorkflowTransitions_021:
new_status_id: 1
role_id: 1
old_status_id: 5
id: 21
tracker_id: 1
-workflows_102:
+ type: WorkflowTransition
+WorkflowTransitions_102:
new_status_id: 2
role_id: 1
old_status_id: 3
id: 102
tracker_id: 2
-workflows_210:
+ type: WorkflowTransition
+WorkflowTransitions_210:
new_status_id: 5
role_id: 1
old_status_id: 6
id: 210
tracker_id: 3
-workflows_022:
+ type: WorkflowTransition
+WorkflowTransitions_022:
new_status_id: 2
role_id: 1
old_status_id: 5
id: 22
tracker_id: 1
-workflows_103:
+ type: WorkflowTransition
+WorkflowTransitions_103:
new_status_id: 4
role_id: 1
old_status_id: 3
id: 103
tracker_id: 2
-workflows_023:
+ type: WorkflowTransition
+WorkflowTransitions_023:
new_status_id: 3
role_id: 1
old_status_id: 5
id: 23
tracker_id: 1
-workflows_104:
+ type: WorkflowTransition
+WorkflowTransitions_104:
new_status_id: 5
role_id: 1
old_status_id: 3
id: 104
tracker_id: 2
-workflows_130:
+ type: WorkflowTransition
+WorkflowTransitions_130:
new_status_id: 6
role_id: 2
old_status_id: 2
id: 130
tracker_id: 2
-workflows_211:
+ type: WorkflowTransition
+WorkflowTransitions_211:
new_status_id: 2
role_id: 2
old_status_id: 1
id: 211
tracker_id: 3
-workflows_024:
+ type: WorkflowTransition
+WorkflowTransitions_024:
new_status_id: 4
role_id: 1
old_status_id: 5
id: 24
tracker_id: 1
-workflows_050:
+ type: WorkflowTransition
+WorkflowTransitions_050:
new_status_id: 6
role_id: 2
old_status_id: 4
id: 50
tracker_id: 1
-workflows_105:
+ type: WorkflowTransition
+WorkflowTransitions_105:
new_status_id: 6
role_id: 1
old_status_id: 3
id: 105
tracker_id: 2
-workflows_131:
+ type: WorkflowTransition
+WorkflowTransitions_131:
new_status_id: 1
role_id: 2
old_status_id: 3
id: 131
tracker_id: 2
-workflows_212:
+ type: WorkflowTransition
+WorkflowTransitions_212:
new_status_id: 3
role_id: 2
old_status_id: 1
id: 212
tracker_id: 3
-workflows_025:
+ type: WorkflowTransition
+WorkflowTransitions_025:
new_status_id: 6
role_id: 1
old_status_id: 5
id: 25
tracker_id: 1
-workflows_051:
+ type: WorkflowTransition
+WorkflowTransitions_051:
new_status_id: 1
role_id: 2
old_status_id: 5
id: 51
tracker_id: 1
-workflows_106:
+ type: WorkflowTransition
+WorkflowTransitions_106:
new_status_id: 1
role_id: 1
old_status_id: 4
id: 106
tracker_id: 2
-workflows_132:
+ type: WorkflowTransition
+WorkflowTransitions_132:
new_status_id: 2
role_id: 2
old_status_id: 3
id: 132
tracker_id: 2
-workflows_213:
+ type: WorkflowTransition
+WorkflowTransitions_213:
new_status_id: 4
role_id: 2
old_status_id: 1
id: 213
tracker_id: 3
-workflows_240:
+ type: WorkflowTransition
+WorkflowTransitions_240:
new_status_id: 5
role_id: 2
old_status_id: 6
id: 240
tracker_id: 3
-workflows_026:
+ type: WorkflowTransition
+WorkflowTransitions_026:
new_status_id: 1
role_id: 1
old_status_id: 6
id: 26
tracker_id: 1
-workflows_052:
+ type: WorkflowTransition
+WorkflowTransitions_052:
new_status_id: 2
role_id: 2
old_status_id: 5
id: 52
tracker_id: 1
-workflows_107:
+ type: WorkflowTransition
+WorkflowTransitions_107:
new_status_id: 2
role_id: 1
old_status_id: 4
id: 107
tracker_id: 2
-workflows_133:
+ type: WorkflowTransition
+WorkflowTransitions_133:
new_status_id: 4
role_id: 2
old_status_id: 3
id: 133
tracker_id: 2
-workflows_214:
+ type: WorkflowTransition
+WorkflowTransitions_214:
new_status_id: 5
role_id: 2
old_status_id: 1
id: 214
tracker_id: 3
-workflows_241:
+ type: WorkflowTransition
+WorkflowTransitions_241:
new_status_id: 2
role_id: 3
old_status_id: 1
id: 241
tracker_id: 3
-workflows_027:
+ type: WorkflowTransition
+WorkflowTransitions_027:
new_status_id: 2
role_id: 1
old_status_id: 6
id: 27
tracker_id: 1
-workflows_053:
+ type: WorkflowTransition
+WorkflowTransitions_053:
new_status_id: 3
role_id: 2
old_status_id: 5
id: 53
tracker_id: 1
-workflows_080:
+ type: WorkflowTransition
+WorkflowTransitions_080:
new_status_id: 6
role_id: 3
old_status_id: 4
id: 80
tracker_id: 1
-workflows_108:
+ type: WorkflowTransition
+WorkflowTransitions_108:
new_status_id: 3
role_id: 1
old_status_id: 4
id: 108
tracker_id: 2
-workflows_134:
+ type: WorkflowTransition
+WorkflowTransitions_134:
new_status_id: 5
role_id: 2
old_status_id: 3
id: 134
tracker_id: 2
-workflows_160:
+ type: WorkflowTransition
+WorkflowTransitions_160:
new_status_id: 6
role_id: 3
old_status_id: 2
id: 160
tracker_id: 2
-workflows_215:
+ type: WorkflowTransition
+WorkflowTransitions_215:
new_status_id: 6
role_id: 2
old_status_id: 1
id: 215
tracker_id: 3
-workflows_242:
+ type: WorkflowTransition
+WorkflowTransitions_242:
new_status_id: 3
role_id: 3
old_status_id: 1
id: 242
tracker_id: 3
-workflows_028:
+ type: WorkflowTransition
+WorkflowTransitions_028:
new_status_id: 3
role_id: 1
old_status_id: 6
id: 28
tracker_id: 1
-workflows_054:
+ type: WorkflowTransition
+WorkflowTransitions_054:
new_status_id: 4
role_id: 2
old_status_id: 5
id: 54
tracker_id: 1
-workflows_081:
+ type: WorkflowTransition
+WorkflowTransitions_081:
new_status_id: 1
role_id: 3
old_status_id: 5
id: 81
tracker_id: 1
-workflows_109:
+ type: WorkflowTransition
+WorkflowTransitions_109:
new_status_id: 5
role_id: 1
old_status_id: 4
id: 109
tracker_id: 2
-workflows_135:
+ type: WorkflowTransition
+WorkflowTransitions_135:
new_status_id: 6
role_id: 2
old_status_id: 3
id: 135
tracker_id: 2
-workflows_161:
+ type: WorkflowTransition
+WorkflowTransitions_161:
new_status_id: 1
role_id: 3
old_status_id: 3
id: 161
tracker_id: 2
-workflows_216:
+ type: WorkflowTransition
+WorkflowTransitions_216:
new_status_id: 1
role_id: 2
old_status_id: 2
id: 216
tracker_id: 3
-workflows_243:
+ type: WorkflowTransition
+WorkflowTransitions_243:
new_status_id: 4
role_id: 3
old_status_id: 1
id: 243
tracker_id: 3
-workflows_029:
+ type: WorkflowTransition
+WorkflowTransitions_029:
new_status_id: 4
role_id: 1
old_status_id: 6
id: 29
tracker_id: 1
-workflows_055:
+ type: WorkflowTransition
+WorkflowTransitions_055:
new_status_id: 6
role_id: 2
old_status_id: 5
id: 55
tracker_id: 1
-workflows_082:
+ type: WorkflowTransition
+WorkflowTransitions_082:
new_status_id: 2
role_id: 3
old_status_id: 5
id: 82
tracker_id: 1
-workflows_136:
+ type: WorkflowTransition
+WorkflowTransitions_136:
new_status_id: 1
role_id: 2
old_status_id: 4
id: 136
tracker_id: 2
-workflows_162:
+ type: WorkflowTransition
+WorkflowTransitions_162:
new_status_id: 2
role_id: 3
old_status_id: 3
id: 162
tracker_id: 2
-workflows_217:
+ type: WorkflowTransition
+WorkflowTransitions_217:
new_status_id: 3
role_id: 2
old_status_id: 2
id: 217
tracker_id: 3
-workflows_270:
+ type: WorkflowTransition
+WorkflowTransitions_270:
new_status_id: 5
role_id: 3
old_status_id: 6
id: 270
tracker_id: 3
-workflows_244:
+ type: WorkflowTransition
+WorkflowTransitions_244:
new_status_id: 5
role_id: 3
old_status_id: 1
id: 244
tracker_id: 3
-workflows_056:
+ type: WorkflowTransition
+WorkflowTransitions_056:
new_status_id: 1
role_id: 2
old_status_id: 6
id: 56
tracker_id: 1
-workflows_137:
+ type: WorkflowTransition
+WorkflowTransitions_137:
new_status_id: 2
role_id: 2
old_status_id: 4
id: 137
tracker_id: 2
-workflows_163:
+ type: WorkflowTransition
+WorkflowTransitions_163:
new_status_id: 4
role_id: 3
old_status_id: 3
id: 163
tracker_id: 2
-workflows_190:
+ type: WorkflowTransition
+WorkflowTransitions_190:
new_status_id: 6
role_id: 1
old_status_id: 2
id: 190
tracker_id: 3
-workflows_218:
+ type: WorkflowTransition
+WorkflowTransitions_218:
new_status_id: 4
role_id: 2
old_status_id: 2
id: 218
tracker_id: 3
-workflows_245:
+ type: WorkflowTransition
+WorkflowTransitions_245:
new_status_id: 6
role_id: 3
old_status_id: 1
id: 245
tracker_id: 3
-workflows_057:
+ type: WorkflowTransition
+WorkflowTransitions_057:
new_status_id: 2
role_id: 2
old_status_id: 6
id: 57
tracker_id: 1
-workflows_083:
+ type: WorkflowTransition
+WorkflowTransitions_083:
new_status_id: 3
role_id: 3
old_status_id: 5
id: 83
tracker_id: 1
-workflows_138:
+ type: WorkflowTransition
+WorkflowTransitions_138:
new_status_id: 3
role_id: 2
old_status_id: 4
id: 138
tracker_id: 2
-workflows_164:
+ type: WorkflowTransition
+WorkflowTransitions_164:
new_status_id: 5
role_id: 3
old_status_id: 3
id: 164
tracker_id: 2
-workflows_191:
+ type: WorkflowTransition
+WorkflowTransitions_191:
new_status_id: 1
role_id: 1
old_status_id: 3
id: 191
tracker_id: 3
-workflows_219:
+ type: WorkflowTransition
+WorkflowTransitions_219:
new_status_id: 5
role_id: 2
old_status_id: 2
id: 219
tracker_id: 3
-workflows_246:
+ type: WorkflowTransition
+WorkflowTransitions_246:
new_status_id: 1
role_id: 3
old_status_id: 2
id: 246
tracker_id: 3
-workflows_058:
+ type: WorkflowTransition
+WorkflowTransitions_058:
new_status_id: 3
role_id: 2
old_status_id: 6
id: 58
tracker_id: 1
-workflows_084:
+ type: WorkflowTransition
+WorkflowTransitions_084:
new_status_id: 4
role_id: 3
old_status_id: 5
id: 84
tracker_id: 1
-workflows_139:
+ type: WorkflowTransition
+WorkflowTransitions_139:
new_status_id: 5
role_id: 2
old_status_id: 4
id: 139
tracker_id: 2
-workflows_165:
+ type: WorkflowTransition
+WorkflowTransitions_165:
new_status_id: 6
role_id: 3
old_status_id: 3
id: 165
tracker_id: 2
-workflows_192:
+ type: WorkflowTransition
+WorkflowTransitions_192:
new_status_id: 2
role_id: 1
old_status_id: 3
id: 192
tracker_id: 3
-workflows_247:
+ type: WorkflowTransition
+WorkflowTransitions_247:
new_status_id: 3
role_id: 3
old_status_id: 2
id: 247
tracker_id: 3
-workflows_059:
+ type: WorkflowTransition
+WorkflowTransitions_059:
new_status_id: 4
role_id: 2
old_status_id: 6
id: 59
tracker_id: 1
-workflows_085:
+ type: WorkflowTransition
+WorkflowTransitions_085:
new_status_id: 6
role_id: 3
old_status_id: 5
id: 85
tracker_id: 1
-workflows_166:
+ type: WorkflowTransition
+WorkflowTransitions_166:
new_status_id: 1
role_id: 3
old_status_id: 4
id: 166
tracker_id: 2
-workflows_248:
+ type: WorkflowTransition
+WorkflowTransitions_248:
new_status_id: 4
role_id: 3
old_status_id: 2
id: 248
tracker_id: 3
-workflows_086:
+ type: WorkflowTransition
+WorkflowTransitions_086:
new_status_id: 1
role_id: 3
old_status_id: 6
id: 86
tracker_id: 1
-workflows_167:
+ type: WorkflowTransition
+WorkflowTransitions_167:
new_status_id: 2
role_id: 3
old_status_id: 4
id: 167
tracker_id: 2
-workflows_193:
+ type: WorkflowTransition
+WorkflowTransitions_193:
new_status_id: 4
role_id: 1
old_status_id: 3
id: 193
tracker_id: 3
-workflows_249:
+ type: WorkflowTransition
+WorkflowTransitions_249:
new_status_id: 5
role_id: 3
old_status_id: 2
id: 249
tracker_id: 3
-workflows_087:
+ type: WorkflowTransition
+WorkflowTransitions_087:
new_status_id: 2
role_id: 3
old_status_id: 6
id: 87
tracker_id: 1
-workflows_168:
+ type: WorkflowTransition
+WorkflowTransitions_168:
new_status_id: 3
role_id: 3
old_status_id: 4
id: 168
tracker_id: 2
-workflows_194:
+ type: WorkflowTransition
+WorkflowTransitions_194:
new_status_id: 5
role_id: 1
old_status_id: 3
id: 194
tracker_id: 3
-workflows_088:
+ type: WorkflowTransition
+WorkflowTransitions_088:
new_status_id: 3
role_id: 3
old_status_id: 6
id: 88
tracker_id: 1
-workflows_169:
+ type: WorkflowTransition
+WorkflowTransitions_169:
new_status_id: 5
role_id: 3
old_status_id: 4
id: 169
tracker_id: 2
-workflows_195:
+ type: WorkflowTransition
+WorkflowTransitions_195:
new_status_id: 6
role_id: 1
old_status_id: 3
id: 195
tracker_id: 3
-workflows_089:
+ type: WorkflowTransition
+WorkflowTransitions_089:
new_status_id: 4
role_id: 3
old_status_id: 6
id: 89
tracker_id: 1
-workflows_196:
+ type: WorkflowTransition
+WorkflowTransitions_196:
new_status_id: 1
role_id: 1
old_status_id: 4
id: 196
tracker_id: 3
-workflows_197:
+ type: WorkflowTransition
+WorkflowTransitions_197:
new_status_id: 2
role_id: 1
old_status_id: 4
id: 197
tracker_id: 3
-workflows_198:
+ type: WorkflowTransition
+WorkflowTransitions_198:
new_status_id: 3
role_id: 1
old_status_id: 4
id: 198
tracker_id: 3
-workflows_199:
+ type: WorkflowTransition
+WorkflowTransitions_199:
new_status_id: 5
role_id: 1
old_status_id: 4
id: 199
tracker_id: 3
-workflows_010:
+ type: WorkflowTransition
+WorkflowTransitions_010:
new_status_id: 6
role_id: 1
old_status_id: 2
id: 10
tracker_id: 1
-workflows_011:
+ type: WorkflowTransition
+WorkflowTransitions_011:
new_status_id: 1
role_id: 1
old_status_id: 3
id: 11
tracker_id: 1
-workflows_012:
+ type: WorkflowTransition
+WorkflowTransitions_012:
new_status_id: 2
role_id: 1
old_status_id: 3
id: 12
tracker_id: 1
-workflows_200:
+ type: WorkflowTransition
+WorkflowTransitions_200:
new_status_id: 6
role_id: 1
old_status_id: 4
id: 200
tracker_id: 3
-workflows_013:
+ type: WorkflowTransition
+WorkflowTransitions_013:
new_status_id: 4
role_id: 1
old_status_id: 3
id: 13
tracker_id: 1
-workflows_120:
+ type: WorkflowTransition
+WorkflowTransitions_120:
new_status_id: 5
role_id: 1
old_status_id: 6
id: 120
tracker_id: 2
-workflows_201:
+ type: WorkflowTransition
+WorkflowTransitions_201:
new_status_id: 1
role_id: 1
old_status_id: 5
id: 201
tracker_id: 3
-workflows_040:
+ type: WorkflowTransition
+WorkflowTransitions_040:
new_status_id: 6
role_id: 2
old_status_id: 2
id: 40
tracker_id: 1
-workflows_121:
+ type: WorkflowTransition
+WorkflowTransitions_121:
new_status_id: 2
role_id: 2
old_status_id: 1
id: 121
tracker_id: 2
-workflows_202:
+ type: WorkflowTransition
+WorkflowTransitions_202:
new_status_id: 2
role_id: 1
old_status_id: 5
id: 202
tracker_id: 3
-workflows_014:
+ type: WorkflowTransition
+WorkflowTransitions_014:
new_status_id: 5
role_id: 1
old_status_id: 3
id: 14
tracker_id: 1
-workflows_041:
+ type: WorkflowTransition
+WorkflowTransitions_041:
new_status_id: 1
role_id: 2
old_status_id: 3
id: 41
tracker_id: 1
-workflows_122:
+ type: WorkflowTransition
+WorkflowTransitions_122:
new_status_id: 3
role_id: 2
old_status_id: 1
id: 122
tracker_id: 2
-workflows_203:
+ type: WorkflowTransition
+WorkflowTransitions_203:
new_status_id: 3
role_id: 1
old_status_id: 5
id: 203
tracker_id: 3
-workflows_015:
+ type: WorkflowTransition
+WorkflowTransitions_015:
new_status_id: 6
role_id: 1
old_status_id: 3
id: 15
tracker_id: 1
-workflows_230:
+ type: WorkflowTransition
+WorkflowTransitions_230:
new_status_id: 6
role_id: 2
old_status_id: 4
id: 230
tracker_id: 3
-workflows_123:
+ type: WorkflowTransition
+WorkflowTransitions_123:
new_status_id: 4
role_id: 2
old_status_id: 1
id: 123
tracker_id: 2
-workflows_204:
+ type: WorkflowTransition
+WorkflowTransitions_204:
new_status_id: 4
role_id: 1
old_status_id: 5
id: 204
tracker_id: 3
-workflows_016:
+ type: WorkflowTransition
+WorkflowTransitions_016:
new_status_id: 1
role_id: 1
old_status_id: 4
id: 16
tracker_id: 1
-workflows_042:
+ type: WorkflowTransition
+WorkflowTransitions_042:
new_status_id: 2
role_id: 2
old_status_id: 3
id: 42
tracker_id: 1
-workflows_231:
+ type: WorkflowTransition
+WorkflowTransitions_231:
new_status_id: 1
role_id: 2
old_status_id: 5
id: 231
tracker_id: 3
-workflows_070:
+ type: WorkflowTransition
+WorkflowTransitions_070:
new_status_id: 6
role_id: 3
old_status_id: 2
id: 70
tracker_id: 1
-workflows_124:
+ type: WorkflowTransition
+WorkflowTransitions_124:
new_status_id: 5
role_id: 2
old_status_id: 1
id: 124
tracker_id: 2
-workflows_150:
+ type: WorkflowTransition
+WorkflowTransitions_150:
new_status_id: 5
role_id: 2
old_status_id: 6
id: 150
tracker_id: 2
-workflows_205:
+ type: WorkflowTransition
+WorkflowTransitions_205:
new_status_id: 6
role_id: 1
old_status_id: 5
id: 205
tracker_id: 3
-workflows_017:
+ type: WorkflowTransition
+WorkflowTransitions_017:
new_status_id: 2
role_id: 1
old_status_id: 4
id: 17
tracker_id: 1
-workflows_043:
+ type: WorkflowTransition
+WorkflowTransitions_043:
new_status_id: 4
role_id: 2
old_status_id: 3
id: 43
tracker_id: 1
-workflows_232:
+ type: WorkflowTransition
+WorkflowTransitions_232:
new_status_id: 2
role_id: 2
old_status_id: 5
id: 232
tracker_id: 3
-workflows_125:
+ type: WorkflowTransition
+WorkflowTransitions_125:
new_status_id: 6
role_id: 2
old_status_id: 1
id: 125
tracker_id: 2
-workflows_151:
+ type: WorkflowTransition
+WorkflowTransitions_151:
new_status_id: 2
role_id: 3
old_status_id: 1
id: 151
tracker_id: 2
-workflows_206:
+ type: WorkflowTransition
+WorkflowTransitions_206:
new_status_id: 1
role_id: 1
old_status_id: 6
id: 206
tracker_id: 3
-workflows_018:
+ type: WorkflowTransition
+WorkflowTransitions_018:
new_status_id: 3
role_id: 1
old_status_id: 4
id: 18
tracker_id: 1
-workflows_044:
+ type: WorkflowTransition
+WorkflowTransitions_044:
new_status_id: 5
role_id: 2
old_status_id: 3
id: 44
tracker_id: 1
-workflows_071:
+ type: WorkflowTransition
+WorkflowTransitions_071:
new_status_id: 1
role_id: 3
old_status_id: 3
id: 71
tracker_id: 1
-workflows_233:
+ type: WorkflowTransition
+WorkflowTransitions_233:
new_status_id: 3
role_id: 2
old_status_id: 5
id: 233
tracker_id: 3
-workflows_126:
+ type: WorkflowTransition
+WorkflowTransitions_126:
new_status_id: 1
role_id: 2
old_status_id: 2
id: 126
tracker_id: 2
-workflows_152:
+ type: WorkflowTransition
+WorkflowTransitions_152:
new_status_id: 3
role_id: 3
old_status_id: 1
id: 152
tracker_id: 2
-workflows_207:
+ type: WorkflowTransition
+WorkflowTransitions_207:
new_status_id: 2
role_id: 1
old_status_id: 6
id: 207
tracker_id: 3
-workflows_019:
+ type: WorkflowTransition
+WorkflowTransitions_019:
new_status_id: 5
role_id: 1
old_status_id: 4
id: 19
tracker_id: 1
-workflows_045:
+ type: WorkflowTransition
+WorkflowTransitions_045:
new_status_id: 6
role_id: 2
old_status_id: 3
id: 45
tracker_id: 1
-workflows_260:
+ type: WorkflowTransition
+WorkflowTransitions_260:
new_status_id: 6
role_id: 3
old_status_id: 4
id: 260
tracker_id: 3
-workflows_234:
+ type: WorkflowTransition
+WorkflowTransitions_234:
new_status_id: 4
role_id: 2
old_status_id: 5
id: 234
tracker_id: 3
-workflows_127:
+ type: WorkflowTransition
+WorkflowTransitions_127:
new_status_id: 3
role_id: 2
old_status_id: 2
id: 127
tracker_id: 2
-workflows_153:
+ type: WorkflowTransition
+WorkflowTransitions_153:
new_status_id: 4
role_id: 3
old_status_id: 1
id: 153
tracker_id: 2
-workflows_180:
+ type: WorkflowTransition
+WorkflowTransitions_180:
new_status_id: 5
role_id: 3
old_status_id: 6
id: 180
tracker_id: 2
-workflows_208:
+ type: WorkflowTransition
+WorkflowTransitions_208:
new_status_id: 3
role_id: 1
old_status_id: 6
id: 208
tracker_id: 3
-workflows_046:
+ type: WorkflowTransition
+WorkflowTransitions_046:
new_status_id: 1
role_id: 2
old_status_id: 4
id: 46
tracker_id: 1
-workflows_072:
+ type: WorkflowTransition
+WorkflowTransitions_072:
new_status_id: 2
role_id: 3
old_status_id: 3
id: 72
tracker_id: 1
-workflows_261:
+ type: WorkflowTransition
+WorkflowTransitions_261:
new_status_id: 1
role_id: 3
old_status_id: 5
id: 261
tracker_id: 3
-workflows_235:
+ type: WorkflowTransition
+WorkflowTransitions_235:
new_status_id: 6
role_id: 2
old_status_id: 5
id: 235
tracker_id: 3
-workflows_154:
+ type: WorkflowTransition
+WorkflowTransitions_154:
new_status_id: 5
role_id: 3
old_status_id: 1
id: 154
tracker_id: 2
-workflows_181:
+ type: WorkflowTransition
+WorkflowTransitions_181:
new_status_id: 2
role_id: 1
old_status_id: 1
id: 181
tracker_id: 3
-workflows_209:
+ type: WorkflowTransition
+WorkflowTransitions_209:
new_status_id: 4
role_id: 1
old_status_id: 6
id: 209
tracker_id: 3
-workflows_047:
+ type: WorkflowTransition
+WorkflowTransitions_047:
new_status_id: 2
role_id: 2
old_status_id: 4
id: 47
tracker_id: 1
-workflows_073:
+ type: WorkflowTransition
+WorkflowTransitions_073:
new_status_id: 4
role_id: 3
old_status_id: 3
id: 73
tracker_id: 1
-workflows_128:
+ type: WorkflowTransition
+WorkflowTransitions_128:
new_status_id: 4
role_id: 2
old_status_id: 2
id: 128
tracker_id: 2
-workflows_262:
+ type: WorkflowTransition
+WorkflowTransitions_262:
new_status_id: 2
role_id: 3
old_status_id: 5
id: 262
tracker_id: 3
-workflows_236:
+ type: WorkflowTransition
+WorkflowTransitions_236:
new_status_id: 1
role_id: 2
old_status_id: 6
id: 236
tracker_id: 3
-workflows_155:
+ type: WorkflowTransition
+WorkflowTransitions_155:
new_status_id: 6
role_id: 3
old_status_id: 1
id: 155
tracker_id: 2
-workflows_048:
+ type: WorkflowTransition
+WorkflowTransitions_048:
new_status_id: 3
role_id: 2
old_status_id: 4
id: 48
tracker_id: 1
-workflows_074:
+ type: WorkflowTransition
+WorkflowTransitions_074:
new_status_id: 5
role_id: 3
old_status_id: 3
id: 74
tracker_id: 1
-workflows_129:
+ type: WorkflowTransition
+WorkflowTransitions_129:
new_status_id: 5
role_id: 2
old_status_id: 2
id: 129
tracker_id: 2
-workflows_263:
+ type: WorkflowTransition
+WorkflowTransitions_263:
new_status_id: 3
role_id: 3
old_status_id: 5
id: 263
tracker_id: 3
-workflows_237:
+ type: WorkflowTransition
+WorkflowTransitions_237:
new_status_id: 2
role_id: 2
old_status_id: 6
id: 237
tracker_id: 3
-workflows_182:
+ type: WorkflowTransition
+WorkflowTransitions_182:
new_status_id: 3
role_id: 1
old_status_id: 1
id: 182
tracker_id: 3
-workflows_049:
+ type: WorkflowTransition
+WorkflowTransitions_049:
new_status_id: 5
role_id: 2
old_status_id: 4
id: 49
tracker_id: 1
-workflows_075:
+ type: WorkflowTransition
+WorkflowTransitions_075:
new_status_id: 6
role_id: 3
old_status_id: 3
id: 75
tracker_id: 1
-workflows_156:
+ type: WorkflowTransition
+WorkflowTransitions_156:
new_status_id: 1
role_id: 3
old_status_id: 2
id: 156
tracker_id: 2
-workflows_264:
+ type: WorkflowTransition
+WorkflowTransitions_264:
new_status_id: 4
role_id: 3
old_status_id: 5
id: 264
tracker_id: 3
-workflows_238:
+ type: WorkflowTransition
+WorkflowTransitions_238:
new_status_id: 3
role_id: 2
old_status_id: 6
id: 238
tracker_id: 3
-workflows_183:
+ type: WorkflowTransition
+WorkflowTransitions_183:
new_status_id: 4
role_id: 1
old_status_id: 1
id: 183
tracker_id: 3
-workflows_076:
+ type: WorkflowTransition
+WorkflowTransitions_076:
new_status_id: 1
role_id: 3
old_status_id: 4
id: 76
tracker_id: 1
-workflows_157:
+ type: WorkflowTransition
+WorkflowTransitions_157:
new_status_id: 3
role_id: 3
old_status_id: 2
id: 157
tracker_id: 2
-workflows_265:
+ type: WorkflowTransition
+WorkflowTransitions_265:
new_status_id: 6
role_id: 3
old_status_id: 5
id: 265
tracker_id: 3
-workflows_239:
+ type: WorkflowTransition
+WorkflowTransitions_239:
new_status_id: 4
role_id: 2
old_status_id: 6
id: 239
tracker_id: 3
-workflows_077:
+ type: WorkflowTransition
+WorkflowTransitions_077:
new_status_id: 2
role_id: 3
old_status_id: 4
id: 77
tracker_id: 1
-workflows_158:
+ type: WorkflowTransition
+WorkflowTransitions_158:
new_status_id: 4
role_id: 3
old_status_id: 2
id: 158
tracker_id: 2
-workflows_184:
+ type: WorkflowTransition
+WorkflowTransitions_184:
new_status_id: 5
role_id: 1
old_status_id: 1
id: 184
tracker_id: 3
-workflows_266:
+ type: WorkflowTransition
+WorkflowTransitions_266:
new_status_id: 1
role_id: 3
old_status_id: 6
id: 266
tracker_id: 3
-workflows_078:
+ type: WorkflowTransition
+WorkflowTransitions_078:
new_status_id: 3
role_id: 3
old_status_id: 4
id: 78
tracker_id: 1
-workflows_159:
+ type: WorkflowTransition
+WorkflowTransitions_159:
new_status_id: 5
role_id: 3
old_status_id: 2
id: 159
tracker_id: 2
-workflows_185:
+ type: WorkflowTransition
+WorkflowTransitions_185:
new_status_id: 6
role_id: 1
old_status_id: 1
id: 185
tracker_id: 3
-workflows_267:
+ type: WorkflowTransition
+WorkflowTransitions_267:
new_status_id: 2
role_id: 3
old_status_id: 6
id: 267
tracker_id: 3
-workflows_079:
+ type: WorkflowTransition
+WorkflowTransitions_079:
new_status_id: 5
role_id: 3
old_status_id: 4
id: 79
tracker_id: 1
-workflows_186:
+ type: WorkflowTransition
+WorkflowTransitions_186:
new_status_id: 1
role_id: 1
old_status_id: 2
id: 186
tracker_id: 3
-workflows_268:
+ type: WorkflowTransition
+WorkflowTransitions_268:
new_status_id: 3
role_id: 3
old_status_id: 6
id: 268
tracker_id: 3
-workflows_187:
+ type: WorkflowTransition
+WorkflowTransitions_187:
new_status_id: 3
role_id: 1
old_status_id: 2
id: 187
tracker_id: 3
-workflows_269:
+ type: WorkflowTransition
+WorkflowTransitions_269:
new_status_id: 4
role_id: 3
old_status_id: 6
id: 269
tracker_id: 3
-workflows_188:
+ type: WorkflowTransition
+WorkflowTransitions_188:
new_status_id: 4
role_id: 1
old_status_id: 2
id: 188
tracker_id: 3
+ type: WorkflowTransition
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index bb80604c6..26b9dcb0b 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -823,7 +823,7 @@ class IssuesControllerTest < ActionController::TestCase
def test_show_should_display_update_form_with_minimal_permissions
Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
- Workflow.delete_all :role_id => 1
+ WorkflowTransition.delete_all :role_id => 1
@request.session[:user_id] = 2
get :show, :id => 1
@@ -1300,7 +1300,7 @@ class IssuesControllerTest < ActionController::TestCase
def test_get_new_with_minimal_permissions
Role.find(1).update_attribute :permissions, [:add_issues]
- Workflow.delete_all :role_id => 1
+ WorkflowTransition.delete_all :role_id => 1
@request.session[:user_id] = 2
get :new, :project_id => 1, :tracker_id => 1
@@ -1426,6 +1426,50 @@ class IssuesControllerTest < ActionController::TestCase
:attributes => {:name => 'issue[custom_field_values][2]', :value => 'Custom field value'}
end
+ def test_get_new_should_mark_required_fields
+ cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
+ @request.session[:user_id] = 2
+
+ get :new, :project_id => 1
+ assert_response :success
+ assert_template 'new'
+
+ assert_select 'label[for=issue_start_date]' do
+ assert_select 'span[class=required]', 0
+ end
+ assert_select 'label[for=issue_due_date]' do
+ assert_select 'span[class=required]'
+ end
+ assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
+ assert_select 'span[class=required]', 0
+ end
+ assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
+ assert_select 'span[class=required]'
+ end
+ end
+
+ def test_get_new_should_not_display_readonly_fields
+ cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
+ @request.session[:user_id] = 2
+
+ get :new, :project_id => 1
+ assert_response :success
+ assert_template 'new'
+
+ assert_select 'input[name=?]', 'issue[start_date]'
+ assert_select 'input[name=?]', 'issue[due_date]', 0
+ assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
+ assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
+ end
+
def test_get_new_without_tracker_id
@request.session[:user_id] = 2
get :new, :project_id => 1
@@ -1463,7 +1507,7 @@ class IssuesControllerTest < ActionController::TestCase
:description => 'This is the description',
:priority_id => 5}
assert_response :success
- assert_template 'attributes'
+ assert_template 'form'
issue = assigns(:issue)
assert_kind_of Issue, issue
@@ -1474,10 +1518,10 @@ class IssuesControllerTest < ActionController::TestCase
def test_update_new_form_should_propose_transitions_based_on_initial_status
@request.session[:user_id] = 2
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
xhr :post, :new, :project_id => 1,
:issue => {:tracker_id => 1,
@@ -1678,6 +1722,58 @@ class IssuesControllerTest < ActionController::TestCase
assert_error_tag :content => /Database can't be blank/
end
+ def test_create_should_validate_required_fields
+ cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
+ @request.session[:user_id] = 2
+
+ assert_no_difference 'Issue.count' do
+ post :create, :project_id => 1, :issue => {
+ :tracker_id => 2,
+ :status_id => 1,
+ :subject => 'Test',
+ :start_date => '',
+ :due_date => '',
+ :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
+ }
+ assert_response :success
+ assert_template 'new'
+ end
+
+ assert_error_tag :content => /Due date can't be blank/i
+ assert_error_tag :content => /Bar can't be blank/i
+ end
+
+ def test_create_should_ignore_readonly_fields
+ cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
+ @request.session[:user_id] = 2
+
+ assert_difference 'Issue.count' do
+ post :create, :project_id => 1, :issue => {
+ :tracker_id => 2,
+ :status_id => 1,
+ :subject => 'Test',
+ :start_date => '2012-07-14',
+ :due_date => '2012-07-16',
+ :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
+ }
+ assert_response 302
+ end
+
+ issue = Issue.first(:order => 'id DESC')
+ assert_equal Date.parse('2012-07-14'), issue.start_date
+ assert_nil issue.due_date
+ assert_equal 'value1', issue.custom_field_value(cf1)
+ assert_nil issue.custom_field_value(cf2)
+ end
+
def test_post_create_with_watchers
@request.session[:user_id] = 2
ActionMailer::Base.deliveries.clear
@@ -1917,7 +2013,7 @@ class IssuesControllerTest < ActionController::TestCase
context "without workflow privilege" do
setup do
- Workflow.delete_all(["role_id = ?", Role.anonymous.id])
+ WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
Role.anonymous.add_permission! :add_issues, :add_issue_notes
end
@@ -1976,9 +2072,9 @@ class IssuesControllerTest < ActionController::TestCase
context "with workflow privilege" do
setup do
- Workflow.delete_all(["role_id = ?", Role.anonymous.id])
- Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
- Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
+ WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
+ WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
+ WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
Role.anonymous.add_permission! :add_issues, :add_issue_notes
end
@@ -2286,7 +2382,7 @@ class IssuesControllerTest < ActionController::TestCase
:description => 'This is the description',
:priority_id => 5}
assert_response :success
- assert_template 'attributes'
+ assert_template 'form'
issue = assigns(:issue)
assert_kind_of Issue, issue
@@ -2298,10 +2394,10 @@ class IssuesControllerTest < ActionController::TestCase
def test_update_edit_form_should_propose_transitions_based_on_initial_status
@request.session[:user_id] = 2
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
xhr :put, :new, :project_id => 1,
:id => 2,
@@ -2317,7 +2413,6 @@ class IssuesControllerTest < ActionController::TestCase
@request.session[:user_id] = 2
xhr :put, :new, :project_id => 1,
:id => 1,
- :project_change => '1',
:issue => {:project_id => 2,
:tracker_id => 2,
:subject => 'This is the test_new issue',
@@ -2845,13 +2940,13 @@ class IssuesControllerTest < ActionController::TestCase
end
def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
@request.session[:user_id] = 2
get :bulk_edit, :ids => [1, 2]
diff --git a/test/functional/roles_controller_test.rb b/test/functional/roles_controller_test.rb
index 03eaf507d..327fad512 100644
--- a/test/functional/roles_controller_test.rb
+++ b/test/functional/roles_controller_test.rb
@@ -77,7 +77,7 @@ class RolesControllerTest < ActionController::TestCase
assert_redirected_to '/roles'
role = Role.find_by_name('RoleWithWorkflowCopy')
assert_not_nil role
- assert_equal Role.find(1).workflows.size, role.workflows.size
+ assert_equal Role.find(1).workflow_rules.size, role.workflow_rules.size
end
def test_edit
diff --git a/test/functional/trackers_controller_test.rb b/test/functional/trackers_controller_test.rb
index 7393088ae..4c4dd0d3a 100644
--- a/test/functional/trackers_controller_test.rb
+++ b/test/functional/trackers_controller_test.rb
@@ -66,7 +66,7 @@ class TrackersControllerTest < ActionController::TestCase
assert_equal [1], tracker.project_ids.sort
assert_equal Tracker::CORE_FIELDS, tracker.core_fields
assert_equal [1, 6], tracker.custom_field_ids.sort
- assert_equal 0, tracker.workflows.count
+ assert_equal 0, tracker.workflow_rules.count
end
def create_with_disabled_core_fields
@@ -86,7 +86,7 @@ class TrackersControllerTest < ActionController::TestCase
assert_redirected_to :action => 'index'
tracker = Tracker.find_by_name('New tracker')
assert_equal 0, tracker.projects.count
- assert_equal Tracker.find(1).workflows.count, tracker.workflows.count
+ assert_equal Tracker.find(1).workflow_rules.count, tracker.workflow_rules.count
end
def test_create_with_failure
diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb
index 5c37afc59..84250d346 100644
--- a/test/functional/workflows_controller_test.rb
+++ b/test/functional/workflows_controller_test.rb
@@ -37,7 +37,7 @@ class WorkflowsControllerTest < ActionController::TestCase
assert_response :success
assert_template 'index'
- count = Workflow.count(:all, :conditions => 'role_id = 1 AND tracker_id = 2')
+ count = WorkflowTransition.count(:all, :conditions => 'role_id = 1 AND tracker_id = 2')
assert_tag :tag => 'a', :content => count.to_s,
:attributes => { :href => '/workflows/edit?role_id=1&amp;tracker_id=2' }
end
@@ -51,9 +51,9 @@ class WorkflowsControllerTest < ActionController::TestCase
end
def test_get_edit_with_role_and_tracker
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
- Workflow.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
+ WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
get :edit, :role_id => 2, :tracker_id => 1
assert_response :success
@@ -79,7 +79,7 @@ class WorkflowsControllerTest < ActionController::TestCase
end
def test_get_edit_with_role_and_tracker_and_all_statuses
- Workflow.delete_all
+ WorkflowTransition.delete_all
get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0'
assert_response :success
@@ -102,9 +102,9 @@ class WorkflowsControllerTest < ActionController::TestCase
}
assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
- assert_equal 3, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
- assert_not_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
- assert_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4})
+ assert_equal 3, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ assert_not_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
+ assert_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4})
end
def test_post_edit_with_additional_transitions
@@ -115,27 +115,117 @@ class WorkflowsControllerTest < ActionController::TestCase
}
assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
- assert_equal 4, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ assert_equal 4, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2})
- w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5})
+ w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5})
assert ! w.author
assert ! w.assignee
- w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1})
+ w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1})
assert w.author
assert ! w.assignee
- w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
+ w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
assert ! w.author
assert w.assignee
- w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4})
+ w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4})
assert w.author
assert w.assignee
end
def test_clear_workflow
- assert Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0
+ assert WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0
post :edit, :role_id => 2, :tracker_id => 1
- assert_equal 0, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ assert_equal 0, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ end
+
+ def test_get_permissions
+ get :permissions
+
+ assert_response :success
+ assert_template 'permissions'
+ assert_not_nil assigns(:roles)
+ assert_not_nil assigns(:trackers)
+ end
+
+ def test_get_permissions_with_role_and_tracker
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
+ WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
+ WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
+
+ get :permissions, :role_id => 1, :tracker_id => 2
+ assert_response :success
+ assert_template 'permissions'
+
+ assert_select 'input[name=role_id][value=1]'
+ assert_select 'input[name=tracker_id][value=2]'
+
+ # Required field
+ assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do
+ assert_select 'option[value=]'
+ assert_select 'option[value=][selected=selected]', 0
+ assert_select 'option[value=readonly]', :text => 'Read-only'
+ assert_select 'option[value=readonly][selected=selected]', 0
+ assert_select 'option[value=required]', :text => 'Required'
+ assert_select 'option[value=required][selected=selected]'
+ end
+
+ # Read-only field
+ assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do
+ assert_select 'option[value=]'
+ assert_select 'option[value=][selected=selected]', 0
+ assert_select 'option[value=readonly]', :text => 'Read-only'
+ assert_select 'option[value=readonly][selected=selected]'
+ assert_select 'option[value=required]', :text => 'Required'
+ assert_select 'option[value=required][selected=selected]', 0
+ end
+
+ # Other field
+ assert_select 'select[name=?]', 'permissions[due_date][3]' do
+ assert_select 'option[value=]'
+ assert_select 'option[value=][selected=selected]', 0
+ assert_select 'option[value=readonly]', :text => 'Read-only'
+ assert_select 'option[value=readonly][selected=selected]', 0
+ assert_select 'option[value=required]', :text => 'Required'
+ assert_select 'option[value=required][selected=selected]', 0
+ end
+ end
+
+ def test_post_permissions
+ WorkflowPermission.delete_all
+
+ post :permissions, :role_id => 1, :tracker_id => 2, :permissions => {
+ 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''},
+ 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''},
+ 'due_date' => {'1' => '', '2' => '', '3' => ''},
+ }
+ assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
+
+ workflows = WorkflowPermission.all
+ assert_equal 3, workflows.size
+ workflows.each do |workflow|
+ assert_equal 1, workflow.role_id
+ assert_equal 2, workflow.tracker_id
+ end
+ assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'}
+ assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'}
+ assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'}
+ end
+
+ def test_post_permissions_should_clear_permissions
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
+ WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
+ wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
+ wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
+
+ post :permissions, :role_id => 1, :tracker_id => 2
+ assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
+
+ workflows = WorkflowPermission.all
+ assert_equal 2, workflows.size
+ assert wf1.reload
+ assert wf2.reload
end
def test_get_copy
@@ -192,7 +282,7 @@ class WorkflowsControllerTest < ActionController::TestCase
# Returns an array of status transitions that can be compared
def status_transitions(conditions)
- Workflow.find(:all, :conditions => conditions,
+ WorkflowTransition.find(:all, :conditions => conditions,
:order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]}
end
end
diff --git a/test/unit/issue_status_test.rb b/test/unit/issue_status_test.rb
index 6fce33675..f12fbdd3d 100644
--- a/test/unit/issue_status_test.rb
+++ b/test/unit/issue_status_test.rb
@@ -36,8 +36,8 @@ class IssueStatusTest < ActiveSupport::TestCase
assert_difference 'IssueStatus.count', -1 do
assert status.destroy
end
- assert_nil Workflow.first(:conditions => {:old_status_id => status.id})
- assert_nil Workflow.first(:conditions => {:new_status_id => status.id})
+ assert_nil WorkflowTransition.first(:conditions => {:old_status_id => status.id})
+ assert_nil WorkflowTransition.first(:conditions => {:new_status_id => status.id})
end
def test_destroy_status_in_use
@@ -70,12 +70,12 @@ class IssueStatusTest < ActiveSupport::TestCase
end
def test_new_statuses_allowed_to
- Workflow.delete_all
+ WorkflowTransition.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
status = IssueStatus.find(1)
role = Role.find(1)
tracker = Tracker.find(1)
diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb
index fe8bdf2ac..a89b2b315 100644
--- a/test/unit/issue_test.rb
+++ b/test/unit/issue_test.rb
@@ -31,6 +31,10 @@ class IssueTest < ActiveSupport::TestCase
include Redmine::I18n
+ def teardown
+ User.current = nil
+ end
+
def test_create
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
:status_id => 1, :priority => IssuePriority.all.first,
@@ -362,12 +366,12 @@ class IssueTest < ActiveSupport::TestCase
end
def test_new_statuses_allowed_to
- Workflow.delete_all
+ WorkflowTransition.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
status = IssueStatus.find(1)
role = Role.find(1)
tracker = Tracker.find(1)
@@ -390,7 +394,7 @@ class IssueTest < ActiveSupport::TestCase
admin = User.find(1)
issue = Issue.find(1)
assert !admin.member_of?(issue.project)
- expected_statuses = [issue.status] + Workflow.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
+ expected_statuses = [issue.status] + WorkflowTransition.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
end
@@ -403,7 +407,7 @@ class IssueTest < ActiveSupport::TestCase
assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
end
- def test_safe_attributes_should_not_include_disabled_field
+ def test_safe_attributes_names_should_not_include_disabled_field
tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
issue = Issue.new(:tracker => tracker)
@@ -435,7 +439,7 @@ class IssueTest < ActiveSupport::TestCase
assert_equal Date.parse('2012-07-14'), issue.due_date
end
- def test_safe_attributes_should_accept_target_tracker_fields
+ def test_safe_attributes_should_accept_target_tracker_enabled_fields
source = Tracker.find(1)
source.core_fields = []
source.save!
@@ -449,6 +453,165 @@ class IssueTest < ActiveSupport::TestCase
assert_equal Date.parse('2012-07-14'), issue.due_date
end
+ def test_safe_attributes_should_not_include_readonly_fields
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1)
+ assert_equal %w(due_date), issue.read_only_attribute_names(user)
+ assert_not_include 'due_date', issue.safe_attribute_names(user)
+
+ issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
+ assert_equal Date.parse('2012-07-14'), issue.start_date
+ assert_nil issue.due_date
+ end
+
+ def test_safe_attributes_should_not_include_readonly_custom_fields
+ cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1])
+ cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1])
+
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1)
+ assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
+ assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
+
+ issue.send :safe_attributes=, {'custom_field_values' => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}}, user
+ assert_equal 'value1', issue.custom_field_value(cf1)
+ assert_nil issue.custom_field_value(cf2)
+
+ issue.send :safe_attributes=, {'custom_fields' => [{'id' => cf1.id.to_s, 'value' => 'valuea'}, {'id' => cf2.id.to_s, 'value' => 'valueb'}]}, user
+ assert_equal 'valuea', issue.custom_field_value(cf1)
+ assert_nil issue.custom_field_value(cf2)
+ end
+
+ def test_editable_custom_field_values_should_return_non_readonly_custom_values
+ cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+ cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1)
+ values = issue.editable_custom_field_values(user)
+ assert values.detect {|value| value.custom_field == cf1}
+ assert_nil values.detect {|value| value.custom_field == cf2}
+
+ issue.tracker_id = 2
+ values = issue.editable_custom_field_values(user)
+ assert values.detect {|value| value.custom_field == cf1}
+ assert values.detect {|value| value.custom_field == cf2}
+ end
+
+ def test_safe_attributes_should_accept_target_tracker_writable_fields
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
+
+ issue.send :safe_attributes=, {'start_date' => '2012-07-12', 'due_date' => '2012-07-14'}, user
+ assert_equal Date.parse('2012-07-12'), issue.start_date
+ assert_nil issue.due_date
+
+ issue.send :safe_attributes=, {'start_date' => '2012-07-15', 'due_date' => '2012-07-16', 'tracker_id' => 2}, user
+ assert_equal Date.parse('2012-07-12'), issue.start_date
+ assert_equal Date.parse('2012-07-16'), issue.due_date
+ end
+
+ def test_safe_attributes_should_accept_target_status_writable_fields
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
+
+ issue.send :safe_attributes=, {'start_date' => '2012-07-12', 'due_date' => '2012-07-14'}, user
+ assert_equal Date.parse('2012-07-12'), issue.start_date
+ assert_nil issue.due_date
+
+ issue.send :safe_attributes=, {'start_date' => '2012-07-15', 'due_date' => '2012-07-16', 'status_id' => 2}, user
+ assert_equal Date.parse('2012-07-12'), issue.start_date
+ assert_equal Date.parse('2012-07-16'), issue.due_date
+ end
+
+ def test_required_attributes_should_be_validated
+ cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
+
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'category_id', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf.id.to_s, :rule => 'required')
+
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'start_date', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf.id.to_s, :rule => 'required')
+ user = User.find(2)
+
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Required fields', :author => user)
+ assert_equal [cf.id.to_s, "category_id", "due_date"], issue.required_attribute_names(user).sort
+ assert !issue.save, "Issue was saved"
+ assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"], issue.errors.full_messages.sort
+
+ issue.tracker_id = 2
+ assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
+ assert !issue.save, "Issue was saved"
+ assert_equal ["Foo can't be blank", "Start date can't be blank"], issue.errors.full_messages.sort
+
+ issue.start_date = Date.today
+ issue.custom_field_values = {cf.id.to_s => 'bar'}
+ assert issue.save
+ end
+
+ def test_required_attribute_names_for_multiple_roles_should_intersect_rules
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'required')
+ user = User.find(2)
+ member = Member.find(1)
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
+
+ assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
+
+ member.role_ids = [1, 2]
+ member.save!
+ assert_equal [], issue.required_attribute_names(user.reload)
+
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'required')
+ assert_equal %w(due_date), issue.required_attribute_names(user)
+
+ member.role_ids = [1, 2, 3]
+ member.save!
+ assert_equal [], issue.required_attribute_names(user.reload)
+
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'readonly')
+ # required + readonly => required
+ assert_equal %w(due_date), issue.required_attribute_names(user)
+ end
+
+ def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
+ WorkflowPermission.delete_all
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
+ user = User.find(2)
+ member = Member.find(1)
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
+
+ assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
+
+ member.role_ids = [1, 2]
+ member.save!
+ assert_equal [], issue.read_only_attribute_names(user.reload)
+
+ WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'readonly')
+ assert_equal %w(due_date), issue.read_only_attribute_names(user)
+ end
+
def test_copy
issue = Issue.new.copy_from(1)
assert issue.copy?
diff --git a/test/unit/role_test.rb b/test/unit/role_test.rb
index 5a1c96471..e61280fc9 100644
--- a/test/unit/role_test.rb
+++ b/test/unit/role_test.rb
@@ -35,13 +35,13 @@ class RoleTest < ActiveSupport::TestCase
def test_copy_workflows
source = Role.find(1)
- assert_equal 90, source.workflows.size
+ assert_equal 90, source.workflow_rules.size
target = Role.new(:name => 'Target')
assert target.save
- target.workflows.copy(source)
+ target.workflow_rules.copy(source)
target.reload
- assert_equal 90, target.workflows.size
+ assert_equal 90, target.workflow_rules.size
end
def test_permissions_should_be_unserialized_with_its_coder
diff --git a/test/unit/tracker_test.rb b/test/unit/tracker_test.rb
index 91b259827..e1b1663e8 100644
--- a/test/unit/tracker_test.rb
+++ b/test/unit/tracker_test.rb
@@ -30,20 +30,20 @@ class TrackerTest < ActiveSupport::TestCase
def test_copy_workflows
source = Tracker.find(1)
- assert_equal 89, source.workflows.size
+ assert_equal 89, source.workflow_rules.size
target = Tracker.new(:name => 'Target')
assert target.save
- target.workflows.copy(source)
+ target.workflow_rules.copy(source)
target.reload
- assert_equal 89, target.workflows.size
+ assert_equal 89, target.workflow_rules.size
end
def test_issue_statuses
tracker = Tracker.find(1)
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
- Workflow.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
+ WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
assert_kind_of Array, tracker.issue_statuses
assert_kind_of IssueStatus, tracker.issue_statuses.first
@@ -51,7 +51,7 @@ class TrackerTest < ActiveSupport::TestCase
end
def test_issue_statuses_empty
- Workflow.delete_all("tracker_id = 1")
+ WorkflowTransition.delete_all("tracker_id = 1")
assert_equal [], Tracker.find(1).issue_statuses
end
diff --git a/test/unit/workflow_test.rb b/test/unit/workflow_test.rb
index 7adf2c85f..0ea39410d 100644
--- a/test/unit/workflow_test.rb
+++ b/test/unit/workflow_test.rb
@@ -21,17 +21,45 @@ class WorkflowTest < ActiveSupport::TestCase
fixtures :roles, :trackers, :issue_statuses
def test_copy
- Workflow.delete_all
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 2)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3, :assignee => true)
- Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 4, :author => true)
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 2)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3, :assignee => true)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 4, :author => true)
- assert_difference 'Workflow.count', 3 do
- Workflow.copy(Tracker.find(2), Role.find(1), Tracker.find(3), Role.find(2))
+ assert_difference 'WorkflowTransition.count', 3 do
+ WorkflowTransition.copy(Tracker.find(2), Role.find(1), Tracker.find(3), Role.find(2))
end
- assert Workflow.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false})
- assert Workflow.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true})
- assert Workflow.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false})
+ assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false})
+ assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true})
+ assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false})
+ end
+
+ def test_workflow_permission_should_validate_rule
+ wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :field_name => 'due_date')
+ assert !wp.save
+
+ wp.rule = 'foo'
+ assert !wp.save
+
+ wp.rule = 'required'
+ assert wp.save
+
+ wp.rule = 'readonly'
+ assert wp.save
+ end
+
+ def test_workflow_permission_should_validate_field_name
+ wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :rule => 'required')
+ assert !wp.save
+
+ wp.field_name = 'foo'
+ assert !wp.save
+
+ wp.field_name = 'due_date'
+ assert wp.save
+
+ wp.field_name = '1'
+ assert wp.save
end
end