Class: Issue

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/issue.rb

Overview

redMine - project management software Copyright (C) 2006-2007 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.

Instance Method Summary collapse

Instance Method Details

#after_initializeObject



62
63
64
65
66
67
68
# File 'app/models/issue.rb', line 62

def after_initialize
  if new_record?
    # set default values for new records only
    self.status ||= IssueStatus.default
    self.priority ||= Enumeration.priorities.default
  end
end

#after_saveObject



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'app/models/issue.rb', line 179

def after_save
  # Reload is needed in order to get the right status
  reload
  
  # Update start/due dates of following issues
  relations_from.each(&:set_issue_to_dates)
  
  # Close duplicates if the issue was closed
  if @issue_before_change && !@issue_before_change.closed? && self.closed?
    duplicates.each do |duplicate|
      # Reload is need in case the duplicate was updated by a previous duplicate
      duplicate.reload
      # Don't re-close it if it's already closed
      next if duplicate.closed?
      # Same user and notes
      duplicate.init_journal(@current_journal.user, @current_journal.notes)
      duplicate.update_attribute :status, self.status
    end
  end
end

#all_dependent_issuesObject



250
251
252
253
254
255
256
257
# File 'app/models/issue.rb', line 250

def all_dependent_issues
  dependencies = []
  relations_from.each do |relation|
    dependencies << relation.issue_to
    dependencies += relation.issue_to.all_dependent_issues
  end
  dependencies
end

#assignable_usersObject

Users the issue can be assigned to



222
223
224
# File 'app/models/issue.rb', line 222

def assignable_users
  project.assignable_users
end

#available_custom_fieldsObject

Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields



71
72
73
# File 'app/models/issue.rb', line 71

def available_custom_fields
  (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
end

#before_createObject



148
149
150
151
152
153
# File 'app/models/issue.rb', line 148

def before_create
  # default assignment based on category
  if assigned_to.nil? && category && category.assigned_to
    self.assigned_to = category.assigned_to
  end
end

#before_saveObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/models/issue.rb', line 155

def before_save  
  if @current_journal
    # attributes changes
    (Issue.column_names - %w(id description)).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
  end
  # Save the issue even if the journal is not saved (because empty)
  true
end

#closed?Boolean

Return true if the issue is closed, otherwise false



212
213
214
# File 'app/models/issue.rb', line 212

def closed?
  self.status.is_closed?
end

#copy_from(arg) ⇒ Object



75
76
77
78
79
80
# File 'app/models/issue.rb', line 75

def copy_from(arg)
  issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
  self.attributes = issue.attributes.dup
  self.custom_values = issue.custom_values.collect {|v| v.clone}
  self
end

#due_beforeObject

Returns the due date or the target due date if any Used on gantt chart



266
267
268
# File 'app/models/issue.rb', line 266

def due_before
  due_date || (fixed_version ? fixed_version.effective_date : nil)
end

#duplicatesObject

Returns an array of issues that duplicate this one



260
261
262
# File 'app/models/issue.rb', line 260

def duplicates
  relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
end

#durationObject



270
271
272
# File 'app/models/issue.rb', line 270

def duration
  (start_date && due_date) ? due_date - start_date : 0
end

#estimated_hours=(h) ⇒ Object



126
127
128
# File 'app/models/issue.rb', line 126

def estimated_hours=(h)
  write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
end

#init_journal(user, notes = "") ⇒ Object



200
201
202
203
204
205
206
207
208
209
# File 'app/models/issue.rb', line 200

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

#move_to(new_project, new_tracker = nil, options = {}) ⇒ Object

Moves/copies an issue to a new project and tracker Returns the moved/copied issue on success, false on failure



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/models/issue.rb', line 84

def move_to(new_project, new_tracker = nil, options = {})
  options ||= {}
  issue = options[:copy] ? self.clone : self
  transaction do
    if new_project && issue.project_id != new_project.id
      # delete issue relations
      unless Setting.cross_project_issue_relations?
        issue.relations_from.clear
        issue.relations_to.clear
      end
      # issue is moved to another project
      # reassign to the category with same name if any
      new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
      issue.category = new_category
      issue.fixed_version = nil
      issue.project = new_project
    end
    if new_tracker
      issue.tracker = new_tracker
    end
    if options[:copy]
      issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
      issue.status = self.status
    end
    if issue.save
      unless options[:copy]
        # Manually update project_id on related time entries
        TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
      end
    else
      Issue.connection.rollback_db_transaction
      return false
    end
  end
  return issue
end

#new_statuses_allowed_to(user) ⇒ Object

Returns an array of status that user is able to apply



227
228
229
230
231
# File 'app/models/issue.rb', line 227

def new_statuses_allowed_to(user)
  statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
  statuses << status unless statuses.empty?
  statuses.uniq.sort
end

#overdue?Boolean

Returns true if the issue is overdue



217
218
219
# File 'app/models/issue.rb', line 217

def overdue?
  !due_date.nil? && (due_date < Date.today) && !status.is_closed?
end

#priority_id=(pid) ⇒ Object



121
122
123
124
# File 'app/models/issue.rb', line 121

def priority_id=(pid)
  self.priority = nil
  write_attribute(:priority_id, pid)
end

#recipientsObject

Returns the mail adresses of users that should be notified for the issue



234
235
236
237
238
239
240
# File 'app/models/issue.rb', line 234

def recipients
  recipients = project.recipients
  # Author and assignee are always notified unless they have been locked
  recipients << author.mail if author && author.active?
  recipients << assigned_to.mail if assigned_to && assigned_to.active?
  recipients.compact.uniq
end

#relationsObject



246
247
248
# File 'app/models/issue.rb', line 246

def relations
  (relations_from + relations_to).sort
end

#soonest_startObject



274
275
276
# File 'app/models/issue.rb', line 274

def soonest_start
  @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
end

#spent_hoursObject



242
243
244
# File 'app/models/issue.rb', line 242

def spent_hours
  @spent_hours ||= time_entries.sum(:hours) || 0
end

#to_sObject



278
279
280
# File 'app/models/issue.rb', line 278

def to_s
  "#{tracker} ##{id}: #{subject}"
end

#validateObject



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'app/models/issue.rb', line 130

def validate
  if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
    errors.add :due_date, :not_a_date
  end
  
  if self.due_date and self.start_date and self.due_date < self.start_date
    errors.add :due_date, :greater_than_start_date
  end
  
  if start_date && soonest_start && start_date < soonest_start
    errors.add :start_date, :invalid
  end
end

#validate_on_createObject



144
145
146
# File 'app/models/issue.rb', line 144

def validate_on_create
  errors.add :tracker_id, :invalid unless project.trackers.include?(tracker)
end

#visible?(usr = nil) ⇒ Boolean

Returns true if usr or current user is allowed to view the issue



58
59
60
# File 'app/models/issue.rb', line 58

def visible?(usr=nil)
  (usr || User.current).allowed_to?(:view_issues, self.project)
end