##// END OF EJS Templates
Fixed: locked users should not receive email notifications....
Jean-Philippe Lang -
r1061:3e031b4243b3
parent child
Show More
@@ -1,228 +1,228
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :tracker
21 21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27
28 28 has_many :journals, :as => :journalized, :dependent => :destroy
29 29 has_many :attachments, :as => :container, :dependent => :destroy
30 30 has_many :time_entries, :dependent => :nullify
31 31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 32 has_many :custom_fields, :through => :custom_values
33 33 has_and_belongs_to_many :changesets, :order => "revision ASC"
34 34
35 35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37 37
38 38 acts_as_watchable
39 39 acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
40 40 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
41 41 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
42 42
43 43 validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
44 44 validates_length_of :subject, :maximum => 255
45 45 validates_inclusion_of :done_ratio, :in => 0..100
46 46 validates_numericality_of :estimated_hours, :allow_nil => true
47 47 validates_associated :custom_values, :on => :update
48 48
49 49 def after_initialize
50 50 if new_record?
51 51 # set default values for new records only
52 52 self.status ||= IssueStatus.default
53 53 self.priority ||= Enumeration.default('IPRI')
54 54 end
55 55 end
56 56
57 57 def copy_from(arg)
58 58 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
59 59 self.attributes = issue.attributes.dup
60 60 self.custom_values = issue.custom_values.collect {|v| v.clone}
61 61 self
62 62 end
63 63
64 64 # Move an issue to a new project and tracker
65 65 def move_to(new_project, new_tracker = nil)
66 66 transaction do
67 67 if new_project && project_id != new_project.id
68 68 # delete issue relations
69 69 self.relations_from.clear
70 70 self.relations_to.clear
71 71 # issue is moved to another project
72 72 self.category = nil
73 73 self.fixed_version = nil
74 74 self.project = new_project
75 75 end
76 76 if new_tracker
77 77 self.tracker = new_tracker
78 78 end
79 79 if save
80 80 # Manually update project_id on related time entries
81 81 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
82 82 else
83 83 rollback_db_transaction
84 84 return false
85 85 end
86 86 end
87 87 return true
88 88 end
89 89
90 90 def priority_id=(pid)
91 91 self.priority = nil
92 92 write_attribute(:priority_id, pid)
93 93 end
94 94
95 95 def validate
96 96 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
97 97 errors.add :due_date, :activerecord_error_not_a_date
98 98 end
99 99
100 100 if self.due_date and self.start_date and self.due_date < self.start_date
101 101 errors.add :due_date, :activerecord_error_greater_than_start_date
102 102 end
103 103
104 104 if start_date && soonest_start && start_date < soonest_start
105 105 errors.add :start_date, :activerecord_error_invalid
106 106 end
107 107 end
108 108
109 109 def validate_on_create
110 110 errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
111 111 end
112 112
113 113 def before_create
114 114 # default assignment based on category
115 115 if assigned_to.nil? && category && category.assigned_to
116 116 self.assigned_to = category.assigned_to
117 117 end
118 118 end
119 119
120 120 def before_save
121 121 if @current_journal
122 122 # attributes changes
123 123 (Issue.column_names - %w(id description)).each {|c|
124 124 @current_journal.details << JournalDetail.new(:property => 'attr',
125 125 :prop_key => c,
126 126 :old_value => @issue_before_change.send(c),
127 127 :value => send(c)) unless send(c)==@issue_before_change.send(c)
128 128 }
129 129 # custom fields changes
130 130 custom_values.each {|c|
131 131 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
132 132 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
133 133 @current_journal.details << JournalDetail.new(:property => 'cf',
134 134 :prop_key => c.custom_field_id,
135 135 :old_value => @custom_values_before_change[c.custom_field_id],
136 136 :value => c.value)
137 137 }
138 138 @current_journal.save
139 139 end
140 140 # Save the issue even if the journal is not saved (because empty)
141 141 true
142 142 end
143 143
144 144 def after_save
145 145 # Update start/due dates of following issues
146 146 relations_from.each(&:set_issue_to_dates)
147 147
148 148 # Close duplicates if the issue was closed
149 149 if @issue_before_change && !@issue_before_change.closed? && self.closed?
150 150 duplicates.each do |duplicate|
151 151 # Don't re-close it if it's already closed
152 152 next if duplicate.closed?
153 153 # Same user and notes
154 154 duplicate.init_journal(@current_journal.user, @current_journal.notes)
155 155 duplicate.update_attribute :status, self.status
156 156 end
157 157 end
158 158 end
159 159
160 160 def custom_value_for(custom_field)
161 161 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
162 162 return nil
163 163 end
164 164
165 165 def init_journal(user, notes = "")
166 166 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
167 167 @issue_before_change = self.clone
168 168 @custom_values_before_change = {}
169 169 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
170 170 @current_journal
171 171 end
172 172
173 173 # Return true if the issue is closed, otherwise false
174 174 def closed?
175 175 self.status.is_closed?
176 176 end
177 177
178 178 # Users the issue can be assigned to
179 179 def assignable_users
180 180 project.assignable_users
181 181 end
182 182
183 183 # Returns an array of status that user is able to apply
184 184 def new_statuses_allowed_to(user)
185 185 statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
186 186 statuses << status unless statuses.empty?
187 187 statuses.uniq.sort
188 188 end
189 189
190 190 # Returns the mail adresses of users that should be notified for the issue
191 191 def recipients
192 192 recipients = project.recipients
193 # Author and assignee are always notified
194 recipients << author.mail if author
195 recipients << assigned_to.mail if assigned_to
193 # Author and assignee are always notified unless they have been locked
194 recipients << author.mail if author && author.active?
195 recipients << assigned_to.mail if assigned_to && assigned_to.active?
196 196 recipients.compact.uniq
197 197 end
198 198
199 199 def spent_hours
200 200 @spent_hours ||= time_entries.sum(:hours) || 0
201 201 end
202 202
203 203 def relations
204 204 (relations_from + relations_to).sort
205 205 end
206 206
207 207 def all_dependent_issues
208 208 dependencies = []
209 209 relations_from.each do |relation|
210 210 dependencies << relation.issue_to
211 211 dependencies += relation.issue_to.all_dependent_issues
212 212 end
213 213 dependencies
214 214 end
215 215
216 216 # Returns an array of the duplicate issues
217 217 def duplicates
218 218 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
219 219 end
220 220
221 221 def duration
222 222 (start_date && due_date) ? due_date - start_date : 0
223 223 end
224 224
225 225 def soonest_start
226 226 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
227 227 end
228 228 end
@@ -1,27 +1,27
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MessageObserver < ActiveRecord::Observer
19 19 def after_create(message)
20 20 # send notification to the authors of the thread
21 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
21 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author && m.author.active?}
22 22 # send notification to the board watchers
23 23 recipients += message.board.watcher_recipients
24 24 recipients = recipients.compact.uniq
25 25 Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted')
26 26 end
27 27 end
@@ -1,53 +1,53
1 1 # ActsAsWatchable
2 2 module Redmine
3 3 module Acts
4 4 module Watchable
5 5 def self.included(base)
6 6 base.extend ClassMethods
7 7 end
8 8
9 9 module ClassMethods
10 10 def acts_as_watchable(options = {})
11 11 return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods)
12 12 send :include, Redmine::Acts::Watchable::InstanceMethods
13 13
14 14 class_eval do
15 15 has_many :watchers, :as => :watchable, :dependent => :delete_all
16 16 end
17 17 end
18 18 end
19 19
20 20 module InstanceMethods
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 def add_watcher(user)
26 26 self.watchers << Watcher.new(:user => user)
27 27 end
28 28
29 29 def remove_watcher(user)
30 30 return nil unless user && user.is_a?(User)
31 31 Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}"
32 32 end
33 33
34 34 def watched_by?(user)
35 35 !self.watchers.find(:first,
36 36 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil?
37 37 end
38 38
39 39 def watcher_recipients
40 self.watchers.collect { |w| w.user.mail }
40 self.watchers.collect { |w| w.user.mail if w.user.active? }.compact
41 41 end
42 42
43 43 module ClassMethods
44 44 def watched_by(user)
45 45 find(:all,
46 46 :include => :watchers,
47 47 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id])
48 48 end
49 49 end
50 50 end
51 51 end
52 52 end
53 53 end No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now