##// END OF EJS Templates
Preload some associations....
Jean-Philippe Lang -
r11814:ef8dd0f64b70
parent child
Show More
@@ -1,311 +1,308
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20
20
21 before_filter :find_project_for_new_time_entry, :only => [:create]
21 before_filter :find_project_for_new_time_entry, :only => [:create]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :except => [:new, :index, :report]
24 before_filter :authorize, :except => [:new, :index, :report]
25
25
26 before_filter :find_optional_project, :only => [:index, :report]
26 before_filter :find_optional_project, :only => [:index, :report]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
28 before_filter :authorize_global, :only => [:new, :index, :report]
28 before_filter :authorize_global, :only => [:new, :index, :report]
29
29
30 accept_rss_auth :index
30 accept_rss_auth :index
31 accept_api_auth :index, :show, :create, :update, :destroy
31 accept_api_auth :index, :show, :create, :update, :destroy
32
32
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
34
34
35 helper :sort
35 helper :sort
36 include SortHelper
36 include SortHelper
37 helper :issues
37 helper :issues
38 include TimelogHelper
38 include TimelogHelper
39 helper :custom_fields
39 helper :custom_fields
40 include CustomFieldsHelper
40 include CustomFieldsHelper
41 helper :queries
41 helper :queries
42 include QueriesHelper
42 include QueriesHelper
43
43
44 def index
44 def index
45 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
45 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
46
46
47 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
47 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
48 sort_update(@query.sortable_columns)
48 sort_update(@query.sortable_columns)
49 scope = time_entry_scope(:order => sort_clause)
49 scope = time_entry_scope(:order => sort_clause).
50 includes(:project, :activity, :user, :issue).
51 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
50
52
51 respond_to do |format|
53 respond_to do |format|
52 format.html {
54 format.html {
53 # Paginate results
55 # Paginate results
54 @entry_count = scope.count
56 @entry_count = scope.count
55 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
57 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
56 @entries = scope.all(
58 @entries = scope.all(
57 :include => [:project, :activity, :user, {:issue => :tracker}],
58 :limit => @entry_pages.per_page,
59 :limit => @entry_pages.per_page,
59 :offset => @entry_pages.offset
60 :offset => @entry_pages.offset
60 )
61 )
61 @total_hours = scope.sum(:hours).to_f
62 @total_hours = scope.sum(:hours).to_f
62
63
63 render :layout => !request.xhr?
64 render :layout => !request.xhr?
64 }
65 }
65 format.api {
66 format.api {
66 @entry_count = scope.count
67 @entry_count = scope.count
67 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
68 @entries = scope.all(
69 @entries = scope.preload(:custom_values => :custom_field).all(
69 :include => [:project, :activity, :user, {:issue => :tracker}],
70 :limit => @limit,
70 :limit => @limit,
71 :offset => @offset
71 :offset => @offset
72 )
72 )
73 }
73 }
74 format.atom {
74 format.atom {
75 entries = scope.reorder("#{TimeEntry.table_name}.created_on DESC").all(
75 entries = scope.reorder("#{TimeEntry.table_name}.created_on DESC").all(
76 :include => [:project, :activity, :user, {:issue => :tracker}],
77 :limit => Setting.feeds_limit.to_i
76 :limit => Setting.feeds_limit.to_i
78 )
77 )
79 render_feed(entries, :title => l(:label_spent_time))
78 render_feed(entries, :title => l(:label_spent_time))
80 }
79 }
81 format.csv {
80 format.csv {
82 # Export all entries
81 # Export all entries
83 @entries = scope.all(
82 @entries = scope.all
84 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}]
85 )
86 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
83 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
87 }
84 }
88 end
85 end
89 end
86 end
90
87
91 def report
88 def report
92 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
89 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
93 scope = time_entry_scope
90 scope = time_entry_scope
94
91
95 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
92 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
96
93
97 respond_to do |format|
94 respond_to do |format|
98 format.html { render :layout => !request.xhr? }
95 format.html { render :layout => !request.xhr? }
99 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
96 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
100 end
97 end
101 end
98 end
102
99
103 def show
100 def show
104 respond_to do |format|
101 respond_to do |format|
105 # TODO: Implement html response
102 # TODO: Implement html response
106 format.html { render :nothing => true, :status => 406 }
103 format.html { render :nothing => true, :status => 406 }
107 format.api
104 format.api
108 end
105 end
109 end
106 end
110
107
111 def new
108 def new
112 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
109 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
113 @time_entry.safe_attributes = params[:time_entry]
110 @time_entry.safe_attributes = params[:time_entry]
114 end
111 end
115
112
116 def create
113 def create
117 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
114 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
118 @time_entry.safe_attributes = params[:time_entry]
115 @time_entry.safe_attributes = params[:time_entry]
119
116
120 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
117 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
121
118
122 if @time_entry.save
119 if @time_entry.save
123 respond_to do |format|
120 respond_to do |format|
124 format.html {
121 format.html {
125 flash[:notice] = l(:notice_successful_create)
122 flash[:notice] = l(:notice_successful_create)
126 if params[:continue]
123 if params[:continue]
127 if params[:project_id]
124 if params[:project_id]
128 options = {
125 options = {
129 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
126 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
130 :back_url => params[:back_url]
127 :back_url => params[:back_url]
131 }
128 }
132 if @time_entry.issue
129 if @time_entry.issue
133 redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
130 redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
134 else
131 else
135 redirect_to new_project_time_entry_path(@time_entry.project, options)
132 redirect_to new_project_time_entry_path(@time_entry.project, options)
136 end
133 end
137 else
134 else
138 options = {
135 options = {
139 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
136 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
140 :back_url => params[:back_url]
137 :back_url => params[:back_url]
141 }
138 }
142 redirect_to new_time_entry_path(options)
139 redirect_to new_time_entry_path(options)
143 end
140 end
144 else
141 else
145 redirect_back_or_default project_time_entries_path(@time_entry.project)
142 redirect_back_or_default project_time_entries_path(@time_entry.project)
146 end
143 end
147 }
144 }
148 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
145 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
149 end
146 end
150 else
147 else
151 respond_to do |format|
148 respond_to do |format|
152 format.html { render :action => 'new' }
149 format.html { render :action => 'new' }
153 format.api { render_validation_errors(@time_entry) }
150 format.api { render_validation_errors(@time_entry) }
154 end
151 end
155 end
152 end
156 end
153 end
157
154
158 def edit
155 def edit
159 @time_entry.safe_attributes = params[:time_entry]
156 @time_entry.safe_attributes = params[:time_entry]
160 end
157 end
161
158
162 def update
159 def update
163 @time_entry.safe_attributes = params[:time_entry]
160 @time_entry.safe_attributes = params[:time_entry]
164
161
165 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
162 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
166
163
167 if @time_entry.save
164 if @time_entry.save
168 respond_to do |format|
165 respond_to do |format|
169 format.html {
166 format.html {
170 flash[:notice] = l(:notice_successful_update)
167 flash[:notice] = l(:notice_successful_update)
171 redirect_back_or_default project_time_entries_path(@time_entry.project)
168 redirect_back_or_default project_time_entries_path(@time_entry.project)
172 }
169 }
173 format.api { render_api_ok }
170 format.api { render_api_ok }
174 end
171 end
175 else
172 else
176 respond_to do |format|
173 respond_to do |format|
177 format.html { render :action => 'edit' }
174 format.html { render :action => 'edit' }
178 format.api { render_validation_errors(@time_entry) }
175 format.api { render_validation_errors(@time_entry) }
179 end
176 end
180 end
177 end
181 end
178 end
182
179
183 def bulk_edit
180 def bulk_edit
184 @available_activities = TimeEntryActivity.shared.active
181 @available_activities = TimeEntryActivity.shared.active
185 @custom_fields = TimeEntry.first.available_custom_fields
182 @custom_fields = TimeEntry.first.available_custom_fields
186 end
183 end
187
184
188 def bulk_update
185 def bulk_update
189 attributes = parse_params_for_bulk_time_entry_attributes(params)
186 attributes = parse_params_for_bulk_time_entry_attributes(params)
190
187
191 unsaved_time_entry_ids = []
188 unsaved_time_entry_ids = []
192 @time_entries.each do |time_entry|
189 @time_entries.each do |time_entry|
193 time_entry.reload
190 time_entry.reload
194 time_entry.safe_attributes = attributes
191 time_entry.safe_attributes = attributes
195 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
192 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
196 unless time_entry.save
193 unless time_entry.save
197 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
194 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
198 # Keep unsaved time_entry ids to display them in flash error
195 # Keep unsaved time_entry ids to display them in flash error
199 unsaved_time_entry_ids << time_entry.id
196 unsaved_time_entry_ids << time_entry.id
200 end
197 end
201 end
198 end
202 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
199 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
203 redirect_back_or_default project_time_entries_path(@projects.first)
200 redirect_back_or_default project_time_entries_path(@projects.first)
204 end
201 end
205
202
206 def destroy
203 def destroy
207 destroyed = TimeEntry.transaction do
204 destroyed = TimeEntry.transaction do
208 @time_entries.each do |t|
205 @time_entries.each do |t|
209 unless t.destroy && t.destroyed?
206 unless t.destroy && t.destroyed?
210 raise ActiveRecord::Rollback
207 raise ActiveRecord::Rollback
211 end
208 end
212 end
209 end
213 end
210 end
214
211
215 respond_to do |format|
212 respond_to do |format|
216 format.html {
213 format.html {
217 if destroyed
214 if destroyed
218 flash[:notice] = l(:notice_successful_delete)
215 flash[:notice] = l(:notice_successful_delete)
219 else
216 else
220 flash[:error] = l(:notice_unable_delete_time_entry)
217 flash[:error] = l(:notice_unable_delete_time_entry)
221 end
218 end
222 redirect_back_or_default project_time_entries_path(@projects.first)
219 redirect_back_or_default project_time_entries_path(@projects.first)
223 }
220 }
224 format.api {
221 format.api {
225 if destroyed
222 if destroyed
226 render_api_ok
223 render_api_ok
227 else
224 else
228 render_validation_errors(@time_entries)
225 render_validation_errors(@time_entries)
229 end
226 end
230 }
227 }
231 end
228 end
232 end
229 end
233
230
234 private
231 private
235 def find_time_entry
232 def find_time_entry
236 @time_entry = TimeEntry.find(params[:id])
233 @time_entry = TimeEntry.find(params[:id])
237 unless @time_entry.editable_by?(User.current)
234 unless @time_entry.editable_by?(User.current)
238 render_403
235 render_403
239 return false
236 return false
240 end
237 end
241 @project = @time_entry.project
238 @project = @time_entry.project
242 rescue ActiveRecord::RecordNotFound
239 rescue ActiveRecord::RecordNotFound
243 render_404
240 render_404
244 end
241 end
245
242
246 def find_time_entries
243 def find_time_entries
247 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
244 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
248 raise ActiveRecord::RecordNotFound if @time_entries.empty?
245 raise ActiveRecord::RecordNotFound if @time_entries.empty?
249 @projects = @time_entries.collect(&:project).compact.uniq
246 @projects = @time_entries.collect(&:project).compact.uniq
250 @project = @projects.first if @projects.size == 1
247 @project = @projects.first if @projects.size == 1
251 rescue ActiveRecord::RecordNotFound
248 rescue ActiveRecord::RecordNotFound
252 render_404
249 render_404
253 end
250 end
254
251
255 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
252 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
256 if unsaved_time_entry_ids.empty?
253 if unsaved_time_entry_ids.empty?
257 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
254 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
258 else
255 else
259 flash[:error] = l(:notice_failed_to_save_time_entries,
256 flash[:error] = l(:notice_failed_to_save_time_entries,
260 :count => unsaved_time_entry_ids.size,
257 :count => unsaved_time_entry_ids.size,
261 :total => time_entries.size,
258 :total => time_entries.size,
262 :ids => '#' + unsaved_time_entry_ids.join(', #'))
259 :ids => '#' + unsaved_time_entry_ids.join(', #'))
263 end
260 end
264 end
261 end
265
262
266 def find_optional_project_for_new_time_entry
263 def find_optional_project_for_new_time_entry
267 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
264 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
268 @project = Project.find(project_id)
265 @project = Project.find(project_id)
269 end
266 end
270 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
267 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
271 @issue = Issue.find(issue_id)
268 @issue = Issue.find(issue_id)
272 @project ||= @issue.project
269 @project ||= @issue.project
273 end
270 end
274 rescue ActiveRecord::RecordNotFound
271 rescue ActiveRecord::RecordNotFound
275 render_404
272 render_404
276 end
273 end
277
274
278 def find_project_for_new_time_entry
275 def find_project_for_new_time_entry
279 find_optional_project_for_new_time_entry
276 find_optional_project_for_new_time_entry
280 if @project.nil?
277 if @project.nil?
281 render_404
278 render_404
282 end
279 end
283 end
280 end
284
281
285 def find_optional_project
282 def find_optional_project
286 if !params[:issue_id].blank?
283 if !params[:issue_id].blank?
287 @issue = Issue.find(params[:issue_id])
284 @issue = Issue.find(params[:issue_id])
288 @project = @issue.project
285 @project = @issue.project
289 elsif !params[:project_id].blank?
286 elsif !params[:project_id].blank?
290 @project = Project.find(params[:project_id])
287 @project = Project.find(params[:project_id])
291 end
288 end
292 end
289 end
293
290
294 # Returns the TimeEntry scope for index and report actions
291 # Returns the TimeEntry scope for index and report actions
295 def time_entry_scope(options={})
292 def time_entry_scope(options={})
296 scope = @query.results_scope(options)
293 scope = @query.results_scope(options)
297 if @issue
294 if @issue
298 scope = scope.on_issue(@issue)
295 scope = scope.on_issue(@issue)
299 elsif @project
296 elsif @project
300 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
297 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
301 end
298 end
302 scope
299 scope
303 end
300 end
304
301
305 def parse_params_for_bulk_time_entry_attributes(params)
302 def parse_params_for_bulk_time_entry_attributes(params)
306 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
303 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
307 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
304 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
308 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
305 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
309 attributes
306 attributes
310 end
307 end
311 end
308 end
General Comments 0
You need to be logged in to leave comments. Login now