|
|
|
@ -38,17 +38,16 @@ class Issue < ActiveRecord::Base |
|
|
|
|
|
|
|
|
|
acts_as_journalized :event_title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, |
|
|
|
|
:event_type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }, |
|
|
|
|
:activity_find_options => {:include => [:project, :author, :tracker]} |
|
|
|
|
:activity_find_options => {:include => [:project, :author, :tracker]}, |
|
|
|
|
:activity_author_key => :author_id |
|
|
|
|
|
|
|
|
|
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], |
|
|
|
|
:include => [:project, :changes], |
|
|
|
|
:include => [:project, :journals], |
|
|
|
|
# sort by id so that limited eager loading doesn't break with postgresql |
|
|
|
|
:order_column => "#{table_name}.id" |
|
|
|
|
|
|
|
|
|
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
|
|
|
|
|
|
|
|
|
attr_reader :current_journal |
|
|
|
|
|
|
|
|
|
validates_presence_of :subject, :priority, :project, :tracker, :author, :status |
|
|
|
|
|
|
|
|
|
validates_length_of :subject, :maximum => 255 |
|
|
|
@ -67,7 +66,7 @@ class Issue < ActiveRecord::Base |
|
|
|
|
|
|
|
|
|
before_create :default_assign |
|
|
|
|
before_save :reschedule_following_issues, :close_duplicates, :update_done_ratio_from_issue_status |
|
|
|
|
after_save :update_nested_set_attributes, :update_parent_attributes, :create_journal |
|
|
|
|
after_save :update_nested_set_attributes, :update_parent_attributes |
|
|
|
|
after_destroy :destroy_children |
|
|
|
|
after_destroy :update_parent_attributes |
|
|
|
|
|
|
|
|
@ -310,18 +309,7 @@ class Issue < ActiveRecord::Base |
|
|
|
|
self.done_ratio = status.default_done_ratio |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def init_journal(user, notes = "") |
|
|
|
|
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) |
|
|
|
|
@issue_before_change = self.clone |
|
|
|
|
@issue_before_change.status = self.status |
|
|
|
|
@custom_values_before_change = {} |
|
|
|
|
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } |
|
|
|
|
# Make sure updated_on is updated when adding a note. |
|
|
|
|
updated_on_will_change! |
|
|
|
|
@current_journal |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Return true if the issue is closed, otherwise false |
|
|
|
|
def closed? |
|
|
|
|
self.status.is_closed? |
|
|
|
@ -496,13 +484,12 @@ class Issue < ActiveRecord::Base |
|
|
|
|
if valid? |
|
|
|
|
attachments = Attachment.attach_files(self, params[:attachments]) |
|
|
|
|
|
|
|
|
|
attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} |
|
|
|
|
# TODO: Rename hook |
|
|
|
|
Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) |
|
|
|
|
Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => current_journal}) |
|
|
|
|
begin |
|
|
|
|
if save |
|
|
|
|
# TODO: Rename hook |
|
|
|
|
Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) |
|
|
|
|
Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => current_journal}) |
|
|
|
|
else |
|
|
|
|
raise ActiveRecord::Rollback |
|
|
|
|
end |
|
|
|
@ -718,22 +705,12 @@ class Issue < ActiveRecord::Base |
|
|
|
|
).each do |issue| |
|
|
|
|
next if issue.project.nil? || issue.fixed_version.nil? |
|
|
|
|
unless issue.project.shared_versions.include?(issue.fixed_version) |
|
|
|
|
issue.init_journal(User.current) |
|
|
|
|
issue.fixed_version = nil |
|
|
|
|
issue.save |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
# Callback on attachment deletion |
|
|
|
|
def attachment_removed(obj) |
|
|
|
|
journal = init_journal(User.current) |
|
|
|
|
journal.details << JournalDetail.new(:property => 'attachment', |
|
|
|
|
:prop_key => obj.id, |
|
|
|
|
:old_value => obj.filename) |
|
|
|
|
journal.save |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
# Default assignment based on category |
|
|
|
|
def default_assign |
|
|
|
|
if assigned_to.nil? && category && category.assigned_to |
|
|
|
@ -758,40 +735,14 @@ class Issue < ActiveRecord::Base |
|
|
|
|
duplicate.reload |
|
|
|
|
# Don't re-close it if it's already closed |
|
|
|
|
next if duplicate.closed? |
|
|
|
|
# Same user and notes |
|
|
|
|
if @current_journal |
|
|
|
|
duplicate.init_journal(@current_journal.user, @current_journal.notes) |
|
|
|
|
end |
|
|
|
|
# Implicitely creates a new journal |
|
|
|
|
duplicate.update_attribute :status, self.status |
|
|
|
|
# Same user and notes |
|
|
|
|
duplicate.versions.last.user = current_journal.user |
|
|
|
|
duplicate.versions.last.notes = current_journal.notes |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
# Saves the changes in a Journal |
|
|
|
|
# Called after_save |
|
|
|
|
def create_journal |
|
|
|
|
if @current_journal |
|
|
|
|
# attributes changes |
|
|
|
|
(Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c| |
|
|
|
|
@current_journal.details << JournalDetail.new(:property => 'attr', |
|
|
|
|
:prop_key => c, |
|
|
|
|
:old_value => @issue_before_change.send(c), |
|
|
|
|
:value => send(c)) unless send(c)==@issue_before_change.send(c) |
|
|
|
|
} |
|
|
|
|
# custom fields changes |
|
|
|
|
custom_values.each {|c| |
|
|
|
|
next if (@custom_values_before_change[c.custom_field_id]==c.value || |
|
|
|
|
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?)) |
|
|
|
|
@current_journal.details << JournalDetail.new(:property => 'cf', |
|
|
|
|
:prop_key => c.custom_field_id, |
|
|
|
|
:old_value => @custom_values_before_change[c.custom_field_id], |
|
|
|
|
:value => c.value) |
|
|
|
|
} |
|
|
|
|
@current_journal.save |
|
|
|
|
# reset current journal |
|
|
|
|
init_journal @current_journal.user, @current_journal.notes |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
# Query generator for selecting groups of issue counts for a project |
|
|
|
|
# based on specific criteria |
|
|
|
|