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?
|