| 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 IssuesController < ApplicationController
|
| 19 |
19 |
menu_item :new_issue, :only => :new
|
| 20 |
|
|
|
20 |
|
| 21 |
21 |
before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
|
| 22 |
22 |
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
|
| 23 |
23 |
before_filter :find_project, :only => [:new, :update_form, :preview, :gantt, :calendar]
|
| ... | ... | |
| 27 |
27 |
|
| 28 |
28 |
helper :journals
|
| 29 |
29 |
helper :projects
|
| 30 |
|
include ProjectsHelper
|
|
30 |
include ProjectsHelper
|
| 31 |
31 |
helper :custom_fields
|
| 32 |
32 |
include CustomFieldsHelper
|
| 33 |
33 |
helper :ifpdf
|
| ... | ... | |
| 76 |
76 |
rescue ActiveRecord::RecordNotFound
|
| 77 |
77 |
render_404
|
| 78 |
78 |
end
|
| 79 |
|
|
|
79 |
|
| 80 |
80 |
def changes
|
| 81 |
81 |
sort_init "#{Issue.table_name}.id", "desc"
|
| 82 |
82 |
sort_update
|
| ... | ... | |
| 92 |
92 |
rescue ActiveRecord::RecordNotFound
|
| 93 |
93 |
render_404
|
| 94 |
94 |
end
|
| 95 |
|
|
|
95 |
|
| 96 |
96 |
def show
|
| 97 |
97 |
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
|
| 98 |
98 |
@journals.each_with_index {|j,i| j.indice = i+1}
|
| ... | ... | |
| 123 |
123 |
end
|
| 124 |
124 |
@issue.attributes = params[:issue]
|
| 125 |
125 |
@issue.author = User.current
|
| 126 |
|
|
|
126 |
|
| 127 |
127 |
default_status = IssueStatus.default
|
| 128 |
128 |
unless default_status
|
| 129 |
129 |
flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
|
| 130 |
130 |
render :nothing => true, :layout => true
|
| 131 |
131 |
return
|
| 132 |
|
end
|
|
132 |
end
|
| 133 |
133 |
@issue.status = default_status
|
| 134 |
134 |
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
|
| 135 |
|
|
|
135 |
|
| 136 |
136 |
if request.get? || request.xhr?
|
| 137 |
137 |
@issue.start_date ||= Date.today
|
| 138 |
138 |
else
|
| ... | ... | |
| 145 |
145 |
Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
|
| 146 |
146 |
redirect_to :controller => 'issues', :action => 'show', :id => @issue
|
| 147 |
147 |
return
|
| 148 |
|
end
|
| 149 |
|
end
|
|
148 |
end
|
|
149 |
end
|
| 150 |
150 |
@priorities = Enumeration::get_values('IPRI')
|
| 151 |
151 |
render :layout => !request.xhr?
|
| 152 |
152 |
end
|
| 153 |
|
|
|
153 |
|
| 154 |
154 |
# Attributes that can be updated on workflow transition (without :edit permission)
|
| 155 |
155 |
# TODO: make it configurable (at least per role)
|
| 156 |
156 |
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
|
| 157 |
|
|
|
157 |
|
| 158 |
158 |
def edit
|
| 159 |
159 |
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
| 160 |
160 |
@priorities = Enumeration::get_values('IPRI')
|
| 161 |
161 |
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
| 162 |
162 |
@time_entry = TimeEntry.new
|
| 163 |
|
|
|
163 |
|
| 164 |
164 |
@notes = params[:notes]
|
| 165 |
165 |
journal = @issue.init_journal(User.current, @notes)
|
| 166 |
166 |
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
| ... | ... | |
| 174 |
174 |
if request.post?
|
| 175 |
175 |
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
|
| 176 |
176 |
@time_entry.attributes = params[:time_entry]
|
|
177 |
@time_entry.comments = @time_entry.comments.strip unless @time_entry.comments.nil?
|
| 177 |
178 |
attachments = attach_files(@issue, params[:attachments])
|
| 178 |
179 |
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
|
| 179 |
180 |
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
|
| 180 |
181 |
# Log spend time
|
| 181 |
182 |
if current_role.allowed_to?(:log_time)
|
| 182 |
|
@time_entry.save
|
|
183 |
not_save = (@time_entry.hours.nil? or @time_entry.hours.zero?)
|
|
184 |
not_save = (not_save and (@time_entry.comments.nil? or @time_entry.comments.empty?))
|
|
185 |
@time_entry.save unless not_save
|
| 183 |
186 |
end
|
| 184 |
187 |
if !journal.new_record?
|
| 185 |
188 |
# Only send notification if something was actually changed
|
| ... | ... | |
| 213 |
216 |
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
|
| 214 |
217 |
}
|
| 215 |
218 |
end
|
| 216 |
|
|
|
219 |
|
| 217 |
220 |
# Bulk edit a set of issues
|
| 218 |
221 |
def bulk_edit
|
| 219 |
222 |
if request.post?
|
| ... | ... | |
| 222 |
225 |
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
|
| 223 |
226 |
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
| 224 |
227 |
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
|
| 225 |
|
|
| 226 |
|
unsaved_issue_ids = []
|
|
228 |
|
|
229 |
unsaved_issue_ids = []
|
| 227 |
230 |
@issues.each do |issue|
|
| 228 |
231 |
journal = issue.init_journal(User.current, params[:notes])
|
| 229 |
232 |
issue.priority = priority if priority
|
| ... | ... | |
| 266 |
269 |
User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
|
| 267 |
270 |
end
|
| 268 |
271 |
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
|
| 269 |
|
@target_project ||= @project
|
|
272 |
@target_project ||= @project
|
| 270 |
273 |
@trackers = @target_project.trackers
|
| 271 |
274 |
if request.post?
|
| 272 |
275 |
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
|
| ... | ... | |
| 285 |
288 |
end
|
| 286 |
289 |
render :layout => false if request.xhr?
|
| 287 |
290 |
end
|
| 288 |
|
|
|
291 |
|
| 289 |
292 |
def destroy
|
| 290 |
293 |
@hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
|
| 291 |
294 |
if @hours > 0
|
| ... | ... | |
| 321 |
324 |
journal.save
|
| 322 |
325 |
redirect_to :action => 'show', :id => @issue
|
| 323 |
326 |
end
|
| 324 |
|
|
|
327 |
|
| 325 |
328 |
def gantt
|
| 326 |
329 |
@gantt = Redmine::Helpers::Gantt.new(params)
|
| 327 |
330 |
retrieve_query
|
| 328 |
331 |
if @query.valid?
|
| 329 |
332 |
events = []
|
| 330 |
333 |
# Issues that have start and due dates
|
| 331 |
|
events += Issue.find(:all,
|
|
334 |
events += Issue.find(:all,
|
| 332 |
335 |
:order => "start_date, due_date",
|
| 333 |
|
:include => [:tracker, :status, :assigned_to, :priority, :project],
|
|
336 |
:include => [:tracker, :status, :assigned_to, :priority, :project],
|
| 334 |
337 |
:conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
| 335 |
338 |
)
|
| 336 |
339 |
# Issues that don't have a due date but that are assigned to a version with a date
|
| 337 |
|
events += Issue.find(:all,
|
|
340 |
events += Issue.find(:all,
|
| 338 |
341 |
:order => "start_date, effective_date",
|
| 339 |
|
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
|
|
342 |
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
|
| 340 |
343 |
:conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
| 341 |
344 |
)
|
| 342 |
345 |
# Versions
|
| 343 |
346 |
events += Version.find(:all, :include => :project,
|
| 344 |
347 |
:conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
|
| 345 |
|
|
|
348 |
|
| 346 |
349 |
@gantt.events = events
|
| 347 |
350 |
end
|
| 348 |
|
|
|
351 |
|
| 349 |
352 |
respond_to do |format|
|
| 350 |
353 |
format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
|
| 351 |
354 |
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
|
| 352 |
355 |
format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-gantt.pdf") }
|
| 353 |
356 |
end
|
| 354 |
357 |
end
|
| 355 |
|
|
|
358 |
|
| 356 |
359 |
def calendar
|
| 357 |
360 |
if params[:year] and params[:year].to_i > 1900
|
| 358 |
361 |
@year = params[:year].to_i
|
| 359 |
362 |
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
|
| 360 |
363 |
@month = params[:month].to_i
|
| 361 |
|
end
|
|
364 |
end
|
| 362 |
365 |
end
|
| 363 |
366 |
@year ||= Date.today.year
|
| 364 |
367 |
@month ||= Date.today.month
|
| 365 |
|
|
|
368 |
|
| 366 |
369 |
@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
|
| 367 |
370 |
retrieve_query
|
| 368 |
371 |
if @query.valid?
|
| 369 |
372 |
events = []
|
| 370 |
|
events += Issue.find(:all,
|
| 371 |
|
:include => [:tracker, :status, :assigned_to, :priority, :project],
|
|
373 |
events += Issue.find(:all,
|
|
374 |
:include => [:tracker, :status, :assigned_to, :priority, :project],
|
| 372 |
375 |
:conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
|
| 373 |
376 |
)
|
| 374 |
377 |
events += Version.find(:all, :include => :project,
|
| 375 |
378 |
:conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
|
| 376 |
|
|
|
379 |
|
| 377 |
380 |
@calendar.events = events
|
| 378 |
381 |
end
|
| 379 |
|
|
|
382 |
|
| 380 |
383 |
render :layout => false if request.xhr?
|
| 381 |
384 |
end
|
| 382 |
|
|
|
385 |
|
| 383 |
386 |
def context_menu
|
| 384 |
387 |
@issues = Issue.find_all_by_id(params[:ids], :include => :project)
|
| 385 |
388 |
if (@issues.size == 1)
|
| ... | ... | |
| 400 |
403 |
@assignables = @project.assignable_users
|
| 401 |
404 |
@assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
|
| 402 |
405 |
end
|
| 403 |
|
|
|
406 |
|
| 404 |
407 |
@priorities = Enumeration.get_values('IPRI').reverse
|
| 405 |
408 |
@statuses = IssueStatus.find(:all, :order => 'position')
|
| 406 |
409 |
@back = request.env['HTTP_REFERER']
|
| 407 |
|
|
|
410 |
|
| 408 |
411 |
render :layout => false
|
| 409 |
412 |
end
|
| 410 |
413 |
|
| ... | ... | |
| 412 |
415 |
@issue = Issue.new(params[:issue])
|
| 413 |
416 |
render :action => :new, :layout => false
|
| 414 |
417 |
end
|
| 415 |
|
|
|
418 |
|
| 416 |
419 |
def preview
|
| 417 |
420 |
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
|
| 418 |
421 |
@attachements = @issue.attachments if @issue
|
| 419 |
422 |
@text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
|
| 420 |
423 |
render :partial => 'common/preview'
|
| 421 |
424 |
end
|
| 422 |
|
|
|
425 |
|
| 423 |
426 |
private
|
| 424 |
427 |
def find_issue
|
| 425 |
428 |
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
|
| ... | ... | |
| 427 |
430 |
rescue ActiveRecord::RecordNotFound
|
| 428 |
431 |
render_404
|
| 429 |
432 |
end
|
| 430 |
|
|
|
433 |
|
| 431 |
434 |
# Filter for bulk operations
|
| 432 |
435 |
def find_issues
|
| 433 |
436 |
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
| ... | ... | |
| 442 |
445 |
rescue ActiveRecord::RecordNotFound
|
| 443 |
446 |
render_404
|
| 444 |
447 |
end
|
| 445 |
|
|
|
448 |
|
| 446 |
449 |
def find_project
|
| 447 |
450 |
@project = Project.find(params[:project_id])
|
| 448 |
451 |
rescue ActiveRecord::RecordNotFound
|
| 449 |
452 |
render_404
|
| 450 |
453 |
end
|
| 451 |
|
|
|
454 |
|
| 452 |
455 |
def find_optional_project
|
| 453 |
456 |
return true unless params[:project_id]
|
| 454 |
457 |
@project = Project.find(params[:project_id])
|
| ... | ... | |
| 456 |
459 |
rescue ActiveRecord::RecordNotFound
|
| 457 |
460 |
render_404
|
| 458 |
461 |
end
|
| 459 |
|
|
|
462 |
|
| 460 |
463 |
# Retrieve query from session or build a new query
|
| 461 |
464 |
def retrieve_query
|
| 462 |
465 |
if !params[:query_id].blank?
|