##// END OF EJS Templates
Fixed: custom field displayed as deleted in the issue history even if no value was set....
Jean-Philippe Lang -
r695:1b2e80545a20
parent child
Show More
@@ -1,166 +1,168
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :tracker
20 belongs_to :tracker
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27
27
28 has_many :journals, :as => :journalized, :dependent => :destroy
28 has_many :journals, :as => :journalized, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
30 has_many :time_entries, :dependent => :nullify
30 has_many :time_entries, :dependent => :nullify
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 has_many :custom_fields, :through => :custom_values
32 has_many :custom_fields, :through => :custom_values
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
34
34
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37
37
38 acts_as_watchable
38 acts_as_watchable
39 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
39 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
40 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
40 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
41
41
42 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
42 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
43 validates_length_of :subject, :maximum => 255
43 validates_length_of :subject, :maximum => 255
44 validates_inclusion_of :done_ratio, :in => 0..100
44 validates_inclusion_of :done_ratio, :in => 0..100
45 validates_associated :custom_values, :on => :update
45 validates_associated :custom_values, :on => :update
46
46
47 # set default status for new issues
47 # set default status for new issues
48 def before_validation
48 def before_validation
49 self.status = IssueStatus.default if status.nil?
49 self.status = IssueStatus.default if status.nil?
50 end
50 end
51
51
52 def validate
52 def validate
53 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
53 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
54 errors.add :due_date, :activerecord_error_not_a_date
54 errors.add :due_date, :activerecord_error_not_a_date
55 end
55 end
56
56
57 if self.due_date and self.start_date and self.due_date < self.start_date
57 if self.due_date and self.start_date and self.due_date < self.start_date
58 errors.add :due_date, :activerecord_error_greater_than_start_date
58 errors.add :due_date, :activerecord_error_greater_than_start_date
59 end
59 end
60
60
61 if start_date && soonest_start && start_date < soonest_start
61 if start_date && soonest_start && start_date < soonest_start
62 errors.add :start_date, :activerecord_error_invalid
62 errors.add :start_date, :activerecord_error_invalid
63 end
63 end
64
64
65 # validate assignment
65 # validate assignment
66 if assigned_to && !assignable_users.include?(assigned_to)
66 if assigned_to && !assignable_users.include?(assigned_to)
67 errors.add :assigned_to_id, :activerecord_error_invalid
67 errors.add :assigned_to_id, :activerecord_error_invalid
68 end
68 end
69 end
69 end
70
70
71 def before_create
71 def before_create
72 # default assignment based on category
72 # default assignment based on category
73 if assigned_to.nil? && category && category.assigned_to
73 if assigned_to.nil? && category && category.assigned_to
74 self.assigned_to = category.assigned_to
74 self.assigned_to = category.assigned_to
75 end
75 end
76 end
76 end
77
77
78 def before_save
78 def before_save
79 if @current_journal
79 if @current_journal
80 # attributes changes
80 # attributes changes
81 (Issue.column_names - %w(id description)).each {|c|
81 (Issue.column_names - %w(id description)).each {|c|
82 @current_journal.details << JournalDetail.new(:property => 'attr',
82 @current_journal.details << JournalDetail.new(:property => 'attr',
83 :prop_key => c,
83 :prop_key => c,
84 :old_value => @issue_before_change.send(c),
84 :old_value => @issue_before_change.send(c),
85 :value => send(c)) unless send(c)==@issue_before_change.send(c)
85 :value => send(c)) unless send(c)==@issue_before_change.send(c)
86 }
86 }
87 # custom fields changes
87 # custom fields changes
88 custom_values.each {|c|
88 custom_values.each {|c|
89 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
90 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
89 @current_journal.details << JournalDetail.new(:property => 'cf',
91 @current_journal.details << JournalDetail.new(:property => 'cf',
90 :prop_key => c.custom_field_id,
92 :prop_key => c.custom_field_id,
91 :old_value => @custom_values_before_change[c.custom_field_id],
93 :old_value => @custom_values_before_change[c.custom_field_id],
92 :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
94 :value => c.value)
93 }
95 }
94 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
96 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
95 end
97 end
96 end
98 end
97
99
98 def after_save
100 def after_save
99 # Update start/due dates of following issues
101 # Update start/due dates of following issues
100 relations_from.each(&:set_issue_to_dates)
102 relations_from.each(&:set_issue_to_dates)
101
103
102 # Close duplicates if the issue was closed
104 # Close duplicates if the issue was closed
103 if @issue_before_change && !@issue_before_change.closed? && self.closed?
105 if @issue_before_change && !@issue_before_change.closed? && self.closed?
104 duplicates.each do |duplicate|
106 duplicates.each do |duplicate|
105 # Don't re-close it if it's already closed
107 # Don't re-close it if it's already closed
106 next if duplicate.closed?
108 next if duplicate.closed?
107 # Same user and notes
109 # Same user and notes
108 duplicate.init_journal(@current_journal.user, @current_journal.notes)
110 duplicate.init_journal(@current_journal.user, @current_journal.notes)
109 duplicate.update_attribute :status, self.status
111 duplicate.update_attribute :status, self.status
110 end
112 end
111 end
113 end
112 end
114 end
113
115
114 def custom_value_for(custom_field)
116 def custom_value_for(custom_field)
115 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
117 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
116 return nil
118 return nil
117 end
119 end
118
120
119 def init_journal(user, notes = "")
121 def init_journal(user, notes = "")
120 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
122 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
121 @issue_before_change = self.clone
123 @issue_before_change = self.clone
122 @custom_values_before_change = {}
124 @custom_values_before_change = {}
123 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
125 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
124 @current_journal
126 @current_journal
125 end
127 end
126
128
127 # Return true if the issue is closed, otherwise false
129 # Return true if the issue is closed, otherwise false
128 def closed?
130 def closed?
129 self.status.is_closed?
131 self.status.is_closed?
130 end
132 end
131
133
132 # Users the issue can be assigned to
134 # Users the issue can be assigned to
133 def assignable_users
135 def assignable_users
134 project.members.select {|m| m.role.assignable?}.collect {|m| m.user}
136 project.members.select {|m| m.role.assignable?}.collect {|m| m.user}
135 end
137 end
136
138
137 def spent_hours
139 def spent_hours
138 @spent_hours ||= time_entries.sum(:hours) || 0
140 @spent_hours ||= time_entries.sum(:hours) || 0
139 end
141 end
140
142
141 def relations
143 def relations
142 (relations_from + relations_to).sort
144 (relations_from + relations_to).sort
143 end
145 end
144
146
145 def all_dependent_issues
147 def all_dependent_issues
146 dependencies = []
148 dependencies = []
147 relations_from.each do |relation|
149 relations_from.each do |relation|
148 dependencies << relation.issue_to
150 dependencies << relation.issue_to
149 dependencies += relation.issue_to.all_dependent_issues
151 dependencies += relation.issue_to.all_dependent_issues
150 end
152 end
151 dependencies
153 dependencies
152 end
154 end
153
155
154 # Returns an array of the duplicate issues
156 # Returns an array of the duplicate issues
155 def duplicates
157 def duplicates
156 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
158 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
157 end
159 end
158
160
159 def duration
161 def duration
160 (start_date && due_date) ? due_date - start_date : 0
162 (start_date && due_date) ? due_date - start_date : 0
161 end
163 end
162
164
163 def soonest_start
165 def soonest_start
164 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
166 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
165 end
167 end
166 end
168 end
General Comments 0
You need to be logged in to leave comments. Login now