##// END OF EJS Templates
Removed unnecessary calculations in time entries index....
Jean-Philippe Lang -
r7965:10509933486a
parent child
Show More
@@ -1,333 +1,331
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 before_filter :find_project, :only => [:new, :create]
20 before_filter :find_project, :only => [:new, :create]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :except => [:index, :report]
23 before_filter :authorize, :except => [:index, :report]
24 before_filter :find_optional_project, :only => [:index, :report]
24 before_filter :find_optional_project, :only => [:index, :report]
25 accept_rss_auth :index
25 accept_rss_auth :index
26 accept_api_auth :index, :show, :create, :update, :destroy
26 accept_api_auth :index, :show, :create, :update, :destroy
27
27
28 helper :sort
28 helper :sort
29 include SortHelper
29 include SortHelper
30 helper :issues
30 helper :issues
31 include TimelogHelper
31 include TimelogHelper
32 helper :custom_fields
32 helper :custom_fields
33 include CustomFieldsHelper
33 include CustomFieldsHelper
34
34
35 def index
35 def index
36 sort_init 'spent_on', 'desc'
36 sort_init 'spent_on', 'desc'
37 sort_update 'spent_on' => 'spent_on',
37 sort_update 'spent_on' => 'spent_on',
38 'user' => 'user_id',
38 'user' => 'user_id',
39 'activity' => 'activity_id',
39 'activity' => 'activity_id',
40 'project' => "#{Project.table_name}.name",
40 'project' => "#{Project.table_name}.name",
41 'issue' => 'issue_id',
41 'issue' => 'issue_id',
42 'hours' => 'hours'
42 'hours' => 'hours'
43
43
44 retrieve_date_range
44 retrieve_date_range
45
45
46 scope = TimeEntry.visible.spent_between(@from, @to)
46 scope = TimeEntry.visible.spent_between(@from, @to)
47 if @issue
47 if @issue
48 scope = scope.on_issue(@issue)
48 scope = scope.on_issue(@issue)
49 elsif @project
49 elsif @project
50 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
50 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
51 end
51 end
52
52
53 respond_to do |format|
53 respond_to do |format|
54 format.html {
54 format.html {
55 # Paginate results
55 # Paginate results
56 @entry_count = scope.count
56 @entry_count = scope.count
57 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
57 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
58 @entries = scope.all(
58 @entries = scope.all(
59 :include => [:project, :activity, :user, {:issue => :tracker}],
59 :include => [:project, :activity, :user, {:issue => :tracker}],
60 :order => sort_clause,
60 :order => sort_clause,
61 :limit => @entry_pages.items_per_page,
61 :limit => @entry_pages.items_per_page,
62 :offset => @entry_pages.current.offset
62 :offset => @entry_pages.current.offset
63 )
63 )
64 @total_hours = scope.sum(:hours).to_f
64 @total_hours = scope.sum(:hours).to_f
65
65
66 render :layout => !request.xhr?
66 render :layout => !request.xhr?
67 }
67 }
68 format.api {
68 format.api {
69 @entry_count = scope.count
69 @entry_count = scope.count
70 @offset, @limit = api_offset_and_limit
70 @offset, @limit = api_offset_and_limit
71 @entries = scope.all(
71 @entries = scope.all(
72 :include => [:project, :activity, :user, {:issue => :tracker}],
72 :include => [:project, :activity, :user, {:issue => :tracker}],
73 :order => sort_clause,
73 :order => sort_clause,
74 :limit => @limit,
74 :limit => @limit,
75 :offset => @offset
75 :offset => @offset
76 )
76 )
77 }
77 }
78 format.atom {
78 format.atom {
79 entries = scope.all(
79 entries = scope.all(
80 :include => [:project, :activity, :user, {:issue => :tracker}],
80 :include => [:project, :activity, :user, {:issue => :tracker}],
81 :order => "#{TimeEntry.table_name}.created_on DESC",
81 :order => "#{TimeEntry.table_name}.created_on DESC",
82 :limit => Setting.feeds_limit.to_i
82 :limit => Setting.feeds_limit.to_i
83 )
83 )
84 render_feed(entries, :title => l(:label_spent_time))
84 render_feed(entries, :title => l(:label_spent_time))
85 }
85 }
86 format.csv {
86 format.csv {
87 # Export all entries
87 # Export all entries
88 @entries = scope.all(
88 @entries = scope.all(
89 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
89 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
90 :order => sort_clause
90 :order => sort_clause
91 )
91 )
92 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
92 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
93 }
93 }
94 end
94 end
95 end
95 end
96
96
97 def report
97 def report
98 retrieve_date_range
98 retrieve_date_range
99 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
99 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
100
100
101 respond_to do |format|
101 respond_to do |format|
102 format.html { render :layout => !request.xhr? }
102 format.html { render :layout => !request.xhr? }
103 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
103 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
104 end
104 end
105 end
105 end
106
106
107 def show
107 def show
108 respond_to do |format|
108 respond_to do |format|
109 # TODO: Implement html response
109 # TODO: Implement html response
110 format.html { render :nothing => true, :status => 406 }
110 format.html { render :nothing => true, :status => 406 }
111 format.api
111 format.api
112 end
112 end
113 end
113 end
114
114
115 def new
115 def new
116 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
116 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
117 @time_entry.attributes = params[:time_entry]
117 @time_entry.attributes = params[:time_entry]
118
118
119 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
119 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
120 render :action => 'edit'
120 render :action => 'edit'
121 end
121 end
122
122
123 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
123 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
124 def create
124 def create
125 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
125 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
126 @time_entry.attributes = params[:time_entry]
126 @time_entry.attributes = params[:time_entry]
127
127
128 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
128 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
129
129
130 if @time_entry.save
130 if @time_entry.save
131 respond_to do |format|
131 respond_to do |format|
132 format.html {
132 format.html {
133 flash[:notice] = l(:notice_successful_update)
133 flash[:notice] = l(:notice_successful_update)
134 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
134 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
135 }
135 }
136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
137 end
137 end
138 else
138 else
139 respond_to do |format|
139 respond_to do |format|
140 format.html { render :action => 'edit' }
140 format.html { render :action => 'edit' }
141 format.api { render_validation_errors(@time_entry) }
141 format.api { render_validation_errors(@time_entry) }
142 end
142 end
143 end
143 end
144 end
144 end
145
145
146 def edit
146 def edit
147 @time_entry.attributes = params[:time_entry]
147 @time_entry.attributes = params[:time_entry]
148
148
149 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
149 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
150 end
150 end
151
151
152 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
152 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
153 def update
153 def update
154 @time_entry.attributes = params[:time_entry]
154 @time_entry.attributes = params[:time_entry]
155
155
156 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
156 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
157
157
158 if @time_entry.save
158 if @time_entry.save
159 respond_to do |format|
159 respond_to do |format|
160 format.html {
160 format.html {
161 flash[:notice] = l(:notice_successful_update)
161 flash[:notice] = l(:notice_successful_update)
162 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
162 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
163 }
163 }
164 format.api { head :ok }
164 format.api { head :ok }
165 end
165 end
166 else
166 else
167 respond_to do |format|
167 respond_to do |format|
168 format.html { render :action => 'edit' }
168 format.html { render :action => 'edit' }
169 format.api { render_validation_errors(@time_entry) }
169 format.api { render_validation_errors(@time_entry) }
170 end
170 end
171 end
171 end
172 end
172 end
173
173
174 def bulk_edit
174 def bulk_edit
175 @available_activities = TimeEntryActivity.shared.active
175 @available_activities = TimeEntryActivity.shared.active
176 @custom_fields = TimeEntry.first.available_custom_fields
176 @custom_fields = TimeEntry.first.available_custom_fields
177 end
177 end
178
178
179 def bulk_update
179 def bulk_update
180 attributes = parse_params_for_bulk_time_entry_attributes(params)
180 attributes = parse_params_for_bulk_time_entry_attributes(params)
181
181
182 unsaved_time_entry_ids = []
182 unsaved_time_entry_ids = []
183 @time_entries.each do |time_entry|
183 @time_entries.each do |time_entry|
184 time_entry.reload
184 time_entry.reload
185 time_entry.attributes = attributes
185 time_entry.attributes = attributes
186 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
186 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
187 unless time_entry.save
187 unless time_entry.save
188 # Keep unsaved time_entry ids to display them in flash error
188 # Keep unsaved time_entry ids to display them in flash error
189 unsaved_time_entry_ids << time_entry.id
189 unsaved_time_entry_ids << time_entry.id
190 end
190 end
191 end
191 end
192 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
192 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
193 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
193 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
194 end
194 end
195
195
196 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
196 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
197 def destroy
197 def destroy
198 @time_entries.each do |t|
198 @time_entries.each do |t|
199 begin
199 begin
200 unless t.destroy && t.destroyed?
200 unless t.destroy && t.destroyed?
201 respond_to do |format|
201 respond_to do |format|
202 format.html {
202 format.html {
203 flash[:error] = l(:notice_unable_delete_time_entry)
203 flash[:error] = l(:notice_unable_delete_time_entry)
204 redirect_to :back
204 redirect_to :back
205 }
205 }
206 format.api { render_validation_errors(t) }
206 format.api { render_validation_errors(t) }
207 end
207 end
208 return
208 return
209 end
209 end
210 rescue ::ActionController::RedirectBackError
210 rescue ::ActionController::RedirectBackError
211 redirect_to :action => 'index', :project_id => @projects.first
211 redirect_to :action => 'index', :project_id => @projects.first
212 return
212 return
213 end
213 end
214 end
214 end
215
215
216 respond_to do |format|
216 respond_to do |format|
217 format.html {
217 format.html {
218 flash[:notice] = l(:notice_successful_delete)
218 flash[:notice] = l(:notice_successful_delete)
219 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
219 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
220 }
220 }
221 format.api { head :ok }
221 format.api { head :ok }
222 end
222 end
223 end
223 end
224
224
225 private
225 private
226 def find_time_entry
226 def find_time_entry
227 @time_entry = TimeEntry.find(params[:id])
227 @time_entry = TimeEntry.find(params[:id])
228 unless @time_entry.editable_by?(User.current)
228 unless @time_entry.editable_by?(User.current)
229 render_403
229 render_403
230 return false
230 return false
231 end
231 end
232 @project = @time_entry.project
232 @project = @time_entry.project
233 rescue ActiveRecord::RecordNotFound
233 rescue ActiveRecord::RecordNotFound
234 render_404
234 render_404
235 end
235 end
236
236
237 def find_time_entries
237 def find_time_entries
238 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
238 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
239 raise ActiveRecord::RecordNotFound if @time_entries.empty?
239 raise ActiveRecord::RecordNotFound if @time_entries.empty?
240 @projects = @time_entries.collect(&:project).compact.uniq
240 @projects = @time_entries.collect(&:project).compact.uniq
241 @project = @projects.first if @projects.size == 1
241 @project = @projects.first if @projects.size == 1
242 rescue ActiveRecord::RecordNotFound
242 rescue ActiveRecord::RecordNotFound
243 render_404
243 render_404
244 end
244 end
245
245
246 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
246 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
247 if unsaved_time_entry_ids.empty?
247 if unsaved_time_entry_ids.empty?
248 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
248 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
249 else
249 else
250 flash[:error] = l(:notice_failed_to_save_time_entries,
250 flash[:error] = l(:notice_failed_to_save_time_entries,
251 :count => unsaved_time_entry_ids.size,
251 :count => unsaved_time_entry_ids.size,
252 :total => time_entries.size,
252 :total => time_entries.size,
253 :ids => '#' + unsaved_time_entry_ids.join(', #'))
253 :ids => '#' + unsaved_time_entry_ids.join(', #'))
254 end
254 end
255 end
255 end
256
256
257 def find_project
257 def find_project
258 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
258 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
259 @issue = Issue.find(issue_id)
259 @issue = Issue.find(issue_id)
260 @project = @issue.project
260 @project = @issue.project
261 elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
261 elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
262 @project = Project.find(project_id)
262 @project = Project.find(project_id)
263 else
263 else
264 render_404
264 render_404
265 return false
265 return false
266 end
266 end
267 rescue ActiveRecord::RecordNotFound
267 rescue ActiveRecord::RecordNotFound
268 render_404
268 render_404
269 end
269 end
270
270
271 def find_optional_project
271 def find_optional_project
272 if !params[:issue_id].blank?
272 if !params[:issue_id].blank?
273 @issue = Issue.find(params[:issue_id])
273 @issue = Issue.find(params[:issue_id])
274 @project = @issue.project
274 @project = @issue.project
275 elsif !params[:project_id].blank?
275 elsif !params[:project_id].blank?
276 @project = Project.find(params[:project_id])
276 @project = Project.find(params[:project_id])
277 end
277 end
278 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
278 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
279 end
279 end
280
280
281 # Retrieves the date range based on predefined ranges or specific from/to param dates
281 # Retrieves the date range based on predefined ranges or specific from/to param dates
282 def retrieve_date_range
282 def retrieve_date_range
283 @free_period = false
283 @free_period = false
284 @from, @to = nil, nil
284 @from, @to = nil, nil
285
285
286 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
286 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
287 case params[:period].to_s
287 case params[:period].to_s
288 when 'today'
288 when 'today'
289 @from = @to = Date.today
289 @from = @to = Date.today
290 when 'yesterday'
290 when 'yesterday'
291 @from = @to = Date.today - 1
291 @from = @to = Date.today - 1
292 when 'current_week'
292 when 'current_week'
293 @from = Date.today - (Date.today.cwday - 1)%7
293 @from = Date.today - (Date.today.cwday - 1)%7
294 @to = @from + 6
294 @to = @from + 6
295 when 'last_week'
295 when 'last_week'
296 @from = Date.today - 7 - (Date.today.cwday - 1)%7
296 @from = Date.today - 7 - (Date.today.cwday - 1)%7
297 @to = @from + 6
297 @to = @from + 6
298 when '7_days'
298 when '7_days'
299 @from = Date.today - 7
299 @from = Date.today - 7
300 @to = Date.today
300 @to = Date.today
301 when 'current_month'
301 when 'current_month'
302 @from = Date.civil(Date.today.year, Date.today.month, 1)
302 @from = Date.civil(Date.today.year, Date.today.month, 1)
303 @to = (@from >> 1) - 1
303 @to = (@from >> 1) - 1
304 when 'last_month'
304 when 'last_month'
305 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
305 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
306 @to = (@from >> 1) - 1
306 @to = (@from >> 1) - 1
307 when '30_days'
307 when '30_days'
308 @from = Date.today - 30
308 @from = Date.today - 30
309 @to = Date.today
309 @to = Date.today
310 when 'current_year'
310 when 'current_year'
311 @from = Date.civil(Date.today.year, 1, 1)
311 @from = Date.civil(Date.today.year, 1, 1)
312 @to = Date.civil(Date.today.year, 12, 31)
312 @to = Date.civil(Date.today.year, 12, 31)
313 end
313 end
314 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
314 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
315 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
315 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
316 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
316 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
317 @free_period = true
317 @free_period = true
318 else
318 else
319 # default
319 # default
320 end
320 end
321
321
322 @from, @to = @to, @from if @from && @to && @from > @to
322 @from, @to = @to, @from if @from && @to && @from > @to
323 @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
324 @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
325 end
323 end
326
324
327 def parse_params_for_bulk_time_entry_attributes(params)
325 def parse_params_for_bulk_time_entry_attributes(params)
328 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
326 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
329 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
327 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
330 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
328 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
331 attributes
329 attributes
332 end
330 end
333 end
331 end
@@ -1,115 +1,107
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 TimeEntry < ActiveRecord::Base
18 class TimeEntry < ActiveRecord::Base
19 # could have used polymorphic association
19 # could have used polymorphic association
20 # project association here allows easy loading of time entries at project level with one database trip
20 # project association here allows easy loading of time entries at project level with one database trip
21 belongs_to :project
21 belongs_to :project
22 belongs_to :issue
22 belongs_to :issue
23 belongs_to :user
23 belongs_to :user
24 belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
24 belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
25
25
26 attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
26 attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
27
27
28 acts_as_customizable
28 acts_as_customizable
29 acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
29 acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
30 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
30 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
31 :author => :user,
31 :author => :user,
32 :description => :comments
32 :description => :comments
33
33
34 acts_as_activity_provider :timestamp => "#{table_name}.created_on",
34 acts_as_activity_provider :timestamp => "#{table_name}.created_on",
35 :author_key => :user_id,
35 :author_key => :user_id,
36 :find_options => {:include => :project}
36 :find_options => {:include => :project}
37
37
38 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
38 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
39 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
39 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
40 validates_length_of :comments, :maximum => 255, :allow_nil => true
40 validates_length_of :comments, :maximum => 255, :allow_nil => true
41 before_validation :set_project_if_nil
41 before_validation :set_project_if_nil
42 validate :validate_time_entry
42 validate :validate_time_entry
43
43
44 named_scope :visible, lambda {|*args| {
44 named_scope :visible, lambda {|*args| {
45 :include => :project,
45 :include => :project,
46 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
46 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
47 }}
47 }}
48 named_scope :on_issue, lambda {|issue| {
48 named_scope :on_issue, lambda {|issue| {
49 :include => :issue,
49 :include => :issue,
50 :conditions => "#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}"
50 :conditions => "#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}"
51 }}
51 }}
52 named_scope :on_project, lambda {|project, include_subprojects| {
52 named_scope :on_project, lambda {|project, include_subprojects| {
53 :include => :project,
53 :include => :project,
54 :conditions => project.project_condition(include_subprojects)
54 :conditions => project.project_condition(include_subprojects)
55 }}
55 }}
56 named_scope :spent_between, lambda {|from, to| {
56 named_scope :spent_between, lambda {|from, to|
57 :conditions => ["#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to]
57 if from && to
58 }}
58 {:conditions => ["#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to]}
59
59 elsif from
60 {:conditions => ["#{TimeEntry.table_name}.spent_on >= ?", from]}
61 elsif to
62 {:conditions => ["#{TimeEntry.table_name}.spent_on <= ?", to]}
63 else
64 {}
65 end
66 }
67
60 def after_initialize
68 def after_initialize
61 if new_record? && self.activity.nil?
69 if new_record? && self.activity.nil?
62 if default_activity = TimeEntryActivity.default
70 if default_activity = TimeEntryActivity.default
63 self.activity_id = default_activity.id
71 self.activity_id = default_activity.id
64 end
72 end
65 self.hours = nil if hours == 0
73 self.hours = nil if hours == 0
66 end
74 end
67 end
75 end
68
76
69 def set_project_if_nil
77 def set_project_if_nil
70 self.project = issue.project if issue && project.nil?
78 self.project = issue.project if issue && project.nil?
71 end
79 end
72
80
73 def validate_time_entry
81 def validate_time_entry
74 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
82 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
75 errors.add :project_id, :invalid if project.nil?
83 errors.add :project_id, :invalid if project.nil?
76 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
84 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
77 end
85 end
78
86
79 def hours=(h)
87 def hours=(h)
80 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
88 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
81 end
89 end
82
90
83 # tyear, tmonth, tweek assigned where setting spent_on attributes
91 # tyear, tmonth, tweek assigned where setting spent_on attributes
84 # these attributes make time aggregations easier
92 # these attributes make time aggregations easier
85 def spent_on=(date)
93 def spent_on=(date)
86 super
94 super
87 if spent_on.is_a?(Time)
95 if spent_on.is_a?(Time)
88 self.spent_on = spent_on.to_date
96 self.spent_on = spent_on.to_date
89 end
97 end
90 self.tyear = spent_on ? spent_on.year : nil
98 self.tyear = spent_on ? spent_on.year : nil
91 self.tmonth = spent_on ? spent_on.month : nil
99 self.tmonth = spent_on ? spent_on.month : nil
92 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
100 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
93 end
101 end
94
102
95 # Returns true if the time entry can be edited by usr, otherwise false
103 # Returns true if the time entry can be edited by usr, otherwise false
96 def editable_by?(usr)
104 def editable_by?(usr)
97 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
105 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
98 end
106 end
99
100 def self.earilest_date_for_project(project=nil)
101 finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
102 if project
103 finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
104 end
105 TimeEntry.minimum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
106 end
107
108 def self.latest_date_for_project(project=nil)
109 finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
110 if project
111 finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
112 end
113 TimeEntry.maximum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
114 end
115 end
107 end
@@ -1,510 +1,510
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Redmine - project management software
2 # Redmine - project management software
3 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require File.expand_path('../../test_helper', __FILE__)
19 require File.expand_path('../../test_helper', __FILE__)
20 require 'timelog_controller'
20 require 'timelog_controller'
21
21
22 # Re-raise errors caught by the controller.
22 # Re-raise errors caught by the controller.
23 class TimelogController; def rescue_action(e) raise e end; end
23 class TimelogController; def rescue_action(e) raise e end; end
24
24
25 class TimelogControllerTest < ActionController::TestCase
25 class TimelogControllerTest < ActionController::TestCase
26 fixtures :projects, :enabled_modules, :roles, :members,
26 fixtures :projects, :enabled_modules, :roles, :members,
27 :member_roles, :issues, :time_entries, :users,
27 :member_roles, :issues, :time_entries, :users,
28 :trackers, :enumerations, :issue_statuses,
28 :trackers, :enumerations, :issue_statuses,
29 :custom_fields, :custom_values
29 :custom_fields, :custom_values
30
30
31 include Redmine::I18n
31 include Redmine::I18n
32
32
33 def setup
33 def setup
34 @controller = TimelogController.new
34 @controller = TimelogController.new
35 @request = ActionController::TestRequest.new
35 @request = ActionController::TestRequest.new
36 @response = ActionController::TestResponse.new
36 @response = ActionController::TestResponse.new
37 end
37 end
38
38
39 def test_get_new
39 def test_get_new
40 @request.session[:user_id] = 3
40 @request.session[:user_id] = 3
41 get :new, :project_id => 1
41 get :new, :project_id => 1
42 assert_response :success
42 assert_response :success
43 assert_template 'edit'
43 assert_template 'edit'
44 # Default activity selected
44 # Default activity selected
45 assert_tag :tag => 'option', :attributes => { :selected => 'selected' },
45 assert_tag :tag => 'option', :attributes => { :selected => 'selected' },
46 :content => 'Development'
46 :content => 'Development'
47 end
47 end
48
48
49 def test_get_new_should_only_show_active_time_entry_activities
49 def test_get_new_should_only_show_active_time_entry_activities
50 @request.session[:user_id] = 3
50 @request.session[:user_id] = 3
51 get :new, :project_id => 1
51 get :new, :project_id => 1
52 assert_response :success
52 assert_response :success
53 assert_template 'edit'
53 assert_template 'edit'
54 assert_no_tag :tag => 'option', :content => 'Inactive Activity'
54 assert_no_tag :tag => 'option', :content => 'Inactive Activity'
55 end
55 end
56
56
57 def test_get_edit_existing_time
57 def test_get_edit_existing_time
58 @request.session[:user_id] = 2
58 @request.session[:user_id] = 2
59 get :edit, :id => 2, :project_id => nil
59 get :edit, :id => 2, :project_id => nil
60 assert_response :success
60 assert_response :success
61 assert_template 'edit'
61 assert_template 'edit'
62 # Default activity selected
62 # Default activity selected
63 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
63 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
64 end
64 end
65
65
66 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
66 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
67 te = TimeEntry.find(1)
67 te = TimeEntry.find(1)
68 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
68 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
69 te.save!
69 te.save!
70
70
71 @request.session[:user_id] = 1
71 @request.session[:user_id] = 1
72 get :edit, :project_id => 1, :id => 1
72 get :edit, :project_id => 1, :id => 1
73 assert_response :success
73 assert_response :success
74 assert_template 'edit'
74 assert_template 'edit'
75 # Blank option since nothing is pre-selected
75 # Blank option since nothing is pre-selected
76 assert_tag :tag => 'option', :content => '--- Please select ---'
76 assert_tag :tag => 'option', :content => '--- Please select ---'
77 end
77 end
78
78
79 def test_post_create
79 def test_post_create
80 # TODO: should POST to issues’ time log instead of project. change form
80 # TODO: should POST to issues’ time log instead of project. change form
81 # and routing
81 # and routing
82 @request.session[:user_id] = 3
82 @request.session[:user_id] = 3
83 post :create, :project_id => 1,
83 post :create, :project_id => 1,
84 :time_entry => {:comments => 'Some work on TimelogControllerTest',
84 :time_entry => {:comments => 'Some work on TimelogControllerTest',
85 # Not the default activity
85 # Not the default activity
86 :activity_id => '11',
86 :activity_id => '11',
87 :spent_on => '2008-03-14',
87 :spent_on => '2008-03-14',
88 :issue_id => '1',
88 :issue_id => '1',
89 :hours => '7.3'}
89 :hours => '7.3'}
90 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
90 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
91
91
92 i = Issue.find(1)
92 i = Issue.find(1)
93 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
93 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
94 assert_not_nil t
94 assert_not_nil t
95 assert_equal 11, t.activity_id
95 assert_equal 11, t.activity_id
96 assert_equal 7.3, t.hours
96 assert_equal 7.3, t.hours
97 assert_equal 3, t.user_id
97 assert_equal 3, t.user_id
98 assert_equal i, t.issue
98 assert_equal i, t.issue
99 assert_equal i.project, t.project
99 assert_equal i.project, t.project
100 end
100 end
101
101
102 def test_post_create_with_blank_issue
102 def test_post_create_with_blank_issue
103 # TODO: should POST to issues’ time log instead of project. change form
103 # TODO: should POST to issues’ time log instead of project. change form
104 # and routing
104 # and routing
105 @request.session[:user_id] = 3
105 @request.session[:user_id] = 3
106 post :create, :project_id => 1,
106 post :create, :project_id => 1,
107 :time_entry => {:comments => 'Some work on TimelogControllerTest',
107 :time_entry => {:comments => 'Some work on TimelogControllerTest',
108 # Not the default activity
108 # Not the default activity
109 :activity_id => '11',
109 :activity_id => '11',
110 :issue_id => '',
110 :issue_id => '',
111 :spent_on => '2008-03-14',
111 :spent_on => '2008-03-14',
112 :hours => '7.3'}
112 :hours => '7.3'}
113 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
113 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
114
114
115 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
115 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
116 assert_not_nil t
116 assert_not_nil t
117 assert_equal 11, t.activity_id
117 assert_equal 11, t.activity_id
118 assert_equal 7.3, t.hours
118 assert_equal 7.3, t.hours
119 assert_equal 3, t.user_id
119 assert_equal 3, t.user_id
120 end
120 end
121
121
122 def test_create_without_log_time_permission_should_be_denied
122 def test_create_without_log_time_permission_should_be_denied
123 @request.session[:user_id] = 2
123 @request.session[:user_id] = 2
124 Role.find_by_name('Manager').remove_permission! :log_time
124 Role.find_by_name('Manager').remove_permission! :log_time
125 post :create, :project_id => 1,
125 post :create, :project_id => 1,
126 :time_entry => {:activity_id => '11',
126 :time_entry => {:activity_id => '11',
127 :issue_id => '',
127 :issue_id => '',
128 :spent_on => '2008-03-14',
128 :spent_on => '2008-03-14',
129 :hours => '7.3'}
129 :hours => '7.3'}
130
130
131 assert_response 403
131 assert_response 403
132 end
132 end
133
133
134 def test_update
134 def test_update
135 entry = TimeEntry.find(1)
135 entry = TimeEntry.find(1)
136 assert_equal 1, entry.issue_id
136 assert_equal 1, entry.issue_id
137 assert_equal 2, entry.user_id
137 assert_equal 2, entry.user_id
138
138
139 @request.session[:user_id] = 1
139 @request.session[:user_id] = 1
140 put :update, :id => 1,
140 put :update, :id => 1,
141 :time_entry => {:issue_id => '2',
141 :time_entry => {:issue_id => '2',
142 :hours => '8'}
142 :hours => '8'}
143 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
143 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
144 entry.reload
144 entry.reload
145
145
146 assert_equal 8, entry.hours
146 assert_equal 8, entry.hours
147 assert_equal 2, entry.issue_id
147 assert_equal 2, entry.issue_id
148 assert_equal 2, entry.user_id
148 assert_equal 2, entry.user_id
149 end
149 end
150
150
151 def test_get_bulk_edit
151 def test_get_bulk_edit
152 @request.session[:user_id] = 2
152 @request.session[:user_id] = 2
153 get :bulk_edit, :ids => [1, 2]
153 get :bulk_edit, :ids => [1, 2]
154 assert_response :success
154 assert_response :success
155 assert_template 'bulk_edit'
155 assert_template 'bulk_edit'
156
156
157 # System wide custom field
157 # System wide custom field
158 assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'}
158 assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'}
159 end
159 end
160
160
161 def test_get_bulk_edit_on_different_projects
161 def test_get_bulk_edit_on_different_projects
162 @request.session[:user_id] = 2
162 @request.session[:user_id] = 2
163 get :bulk_edit, :ids => [1, 2, 6]
163 get :bulk_edit, :ids => [1, 2, 6]
164 assert_response :success
164 assert_response :success
165 assert_template 'bulk_edit'
165 assert_template 'bulk_edit'
166 end
166 end
167
167
168 def test_bulk_update
168 def test_bulk_update
169 @request.session[:user_id] = 2
169 @request.session[:user_id] = 2
170 # update time entry activity
170 # update time entry activity
171 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
171 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
172
172
173 assert_response 302
173 assert_response 302
174 # check that the issues were updated
174 # check that the issues were updated
175 assert_equal [9, 9], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.activity_id}
175 assert_equal [9, 9], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.activity_id}
176 end
176 end
177
177
178 def test_bulk_update_on_different_projects
178 def test_bulk_update_on_different_projects
179 @request.session[:user_id] = 2
179 @request.session[:user_id] = 2
180 # makes user a manager on the other project
180 # makes user a manager on the other project
181 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
181 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
182
182
183 # update time entry activity
183 # update time entry activity
184 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
184 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
185
185
186 assert_response 302
186 assert_response 302
187 # check that the issues were updated
187 # check that the issues were updated
188 assert_equal [9, 9, 9], TimeEntry.find_all_by_id([1, 2, 4]).collect {|i| i.activity_id}
188 assert_equal [9, 9, 9], TimeEntry.find_all_by_id([1, 2, 4]).collect {|i| i.activity_id}
189 end
189 end
190
190
191 def test_bulk_update_on_different_projects_without_rights
191 def test_bulk_update_on_different_projects_without_rights
192 @request.session[:user_id] = 3
192 @request.session[:user_id] = 3
193 user = User.find(3)
193 user = User.find(3)
194 action = { :controller => "timelog", :action => "bulk_update" }
194 action = { :controller => "timelog", :action => "bulk_update" }
195 assert user.allowed_to?(action, TimeEntry.find(1).project)
195 assert user.allowed_to?(action, TimeEntry.find(1).project)
196 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
196 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
197 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
197 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
198 assert_response 403
198 assert_response 403
199 end
199 end
200
200
201 def test_bulk_update_custom_field
201 def test_bulk_update_custom_field
202 @request.session[:user_id] = 2
202 @request.session[:user_id] = 2
203 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
203 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
204
204
205 assert_response 302
205 assert_response 302
206 assert_equal ["0", "0"], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.custom_value_for(10).value}
206 assert_equal ["0", "0"], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.custom_value_for(10).value}
207 end
207 end
208
208
209 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
209 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
210 @request.session[:user_id] = 2
210 @request.session[:user_id] = 2
211 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
211 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
212
212
213 assert_response :redirect
213 assert_response :redirect
214 assert_redirected_to '/time_entries'
214 assert_redirected_to '/time_entries'
215 end
215 end
216
216
217 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
217 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
218 @request.session[:user_id] = 2
218 @request.session[:user_id] = 2
219 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
219 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
220
220
221 assert_response :redirect
221 assert_response :redirect
222 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
222 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
223 end
223 end
224
224
225 def test_post_bulk_update_without_edit_permission_should_be_denied
225 def test_post_bulk_update_without_edit_permission_should_be_denied
226 @request.session[:user_id] = 2
226 @request.session[:user_id] = 2
227 Role.find_by_name('Manager').remove_permission! :edit_time_entries
227 Role.find_by_name('Manager').remove_permission! :edit_time_entries
228 post :bulk_update, :ids => [1,2]
228 post :bulk_update, :ids => [1,2]
229
229
230 assert_response 403
230 assert_response 403
231 end
231 end
232
232
233 def test_destroy
233 def test_destroy
234 @request.session[:user_id] = 2
234 @request.session[:user_id] = 2
235 delete :destroy, :id => 1
235 delete :destroy, :id => 1
236 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
236 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
237 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
237 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
238 assert_nil TimeEntry.find_by_id(1)
238 assert_nil TimeEntry.find_by_id(1)
239 end
239 end
240
240
241 def test_destroy_should_fail
241 def test_destroy_should_fail
242 # simulate that this fails (e.g. due to a plugin), see #5700
242 # simulate that this fails (e.g. due to a plugin), see #5700
243 TimeEntry.any_instance.expects(:destroy).returns(false)
243 TimeEntry.any_instance.expects(:destroy).returns(false)
244
244
245 @request.session[:user_id] = 2
245 @request.session[:user_id] = 2
246 delete :destroy, :id => 1
246 delete :destroy, :id => 1
247 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
247 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
248 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
248 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
249 assert_not_nil TimeEntry.find_by_id(1)
249 assert_not_nil TimeEntry.find_by_id(1)
250 end
250 end
251
251
252 def test_index_all_projects
252 def test_index_all_projects
253 get :index
253 get :index
254 assert_response :success
254 assert_response :success
255 assert_template 'index'
255 assert_template 'index'
256 assert_not_nil assigns(:total_hours)
256 assert_not_nil assigns(:total_hours)
257 assert_equal "162.90", "%.2f" % assigns(:total_hours)
257 assert_equal "162.90", "%.2f" % assigns(:total_hours)
258 assert_tag :form,
258 assert_tag :form,
259 :attributes => {:action => "/time_entries", :id => 'query_form'}
259 :attributes => {:action => "/time_entries", :id => 'query_form'}
260 end
260 end
261
261
262 def test_index_at_project_level
262 def test_index_at_project_level
263 get :index, :project_id => 'ecookbook'
263 get :index, :project_id => 'ecookbook'
264 assert_response :success
264 assert_response :success
265 assert_template 'index'
265 assert_template 'index'
266 assert_not_nil assigns(:entries)
266 assert_not_nil assigns(:entries)
267 assert_equal 4, assigns(:entries).size
267 assert_equal 4, assigns(:entries).size
268 # project and subproject
268 # project and subproject
269 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
269 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
270 assert_not_nil assigns(:total_hours)
270 assert_not_nil assigns(:total_hours)
271 assert_equal "162.90", "%.2f" % assigns(:total_hours)
271 assert_equal "162.90", "%.2f" % assigns(:total_hours)
272 # display all time by default
272 # display all time by default
273 assert_equal '2007-03-12'.to_date, assigns(:from)
273 assert_nil assigns(:from)
274 assert_equal '2007-04-22'.to_date, assigns(:to)
274 assert_nil assigns(:to)
275 assert_tag :form,
275 assert_tag :form,
276 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
276 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
277 end
277 end
278
278
279 def test_index_at_project_level_with_date_range
279 def test_index_at_project_level_with_date_range
280 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
280 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
281 assert_response :success
281 assert_response :success
282 assert_template 'index'
282 assert_template 'index'
283 assert_not_nil assigns(:entries)
283 assert_not_nil assigns(:entries)
284 assert_equal 3, assigns(:entries).size
284 assert_equal 3, assigns(:entries).size
285 assert_not_nil assigns(:total_hours)
285 assert_not_nil assigns(:total_hours)
286 assert_equal "12.90", "%.2f" % assigns(:total_hours)
286 assert_equal "12.90", "%.2f" % assigns(:total_hours)
287 assert_equal '2007-03-20'.to_date, assigns(:from)
287 assert_equal '2007-03-20'.to_date, assigns(:from)
288 assert_equal '2007-04-30'.to_date, assigns(:to)
288 assert_equal '2007-04-30'.to_date, assigns(:to)
289 assert_tag :form,
289 assert_tag :form,
290 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
290 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
291 end
291 end
292
292
293 def test_index_at_project_level_with_period
293 def test_index_at_project_level_with_period
294 get :index, :project_id => 'ecookbook', :period => '7_days'
294 get :index, :project_id => 'ecookbook', :period => '7_days'
295 assert_response :success
295 assert_response :success
296 assert_template 'index'
296 assert_template 'index'
297 assert_not_nil assigns(:entries)
297 assert_not_nil assigns(:entries)
298 assert_not_nil assigns(:total_hours)
298 assert_not_nil assigns(:total_hours)
299 assert_equal Date.today - 7, assigns(:from)
299 assert_equal Date.today - 7, assigns(:from)
300 assert_equal Date.today, assigns(:to)
300 assert_equal Date.today, assigns(:to)
301 assert_tag :form,
301 assert_tag :form,
302 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
302 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
303 end
303 end
304
304
305 def test_index_one_day
305 def test_index_one_day
306 get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23"
306 get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23"
307 assert_response :success
307 assert_response :success
308 assert_template 'index'
308 assert_template 'index'
309 assert_not_nil assigns(:total_hours)
309 assert_not_nil assigns(:total_hours)
310 assert_equal "4.25", "%.2f" % assigns(:total_hours)
310 assert_equal "4.25", "%.2f" % assigns(:total_hours)
311 assert_tag :form,
311 assert_tag :form,
312 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
312 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
313 end
313 end
314
314
315 def test_index_at_issue_level
315 def test_index_at_issue_level
316 get :index, :issue_id => 1
316 get :index, :issue_id => 1
317 assert_response :success
317 assert_response :success
318 assert_template 'index'
318 assert_template 'index'
319 assert_not_nil assigns(:entries)
319 assert_not_nil assigns(:entries)
320 assert_equal 2, assigns(:entries).size
320 assert_equal 2, assigns(:entries).size
321 assert_not_nil assigns(:total_hours)
321 assert_not_nil assigns(:total_hours)
322 assert_equal 154.25, assigns(:total_hours)
322 assert_equal 154.25, assigns(:total_hours)
323 # display all time based on what's been logged
323 # display all time
324 assert_equal '2007-03-12'.to_date, assigns(:from)
324 assert_nil assigns(:from)
325 assert_equal '2007-04-22'.to_date, assigns(:to)
325 assert_nil assigns(:to)
326 # TODO: remove /projects/:project_id/issues/:issue_id/time_entries routes
326 # TODO: remove /projects/:project_id/issues/:issue_id/time_entries routes
327 # to use /issues/:issue_id/time_entries
327 # to use /issues/:issue_id/time_entries
328 assert_tag :form,
328 assert_tag :form,
329 :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'}
329 :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'}
330 end
330 end
331
331
332 def test_index_atom_feed
332 def test_index_atom_feed
333 get :index, :project_id => 1, :format => 'atom'
333 get :index, :project_id => 1, :format => 'atom'
334 assert_response :success
334 assert_response :success
335 assert_equal 'application/atom+xml', @response.content_type
335 assert_equal 'application/atom+xml', @response.content_type
336 assert_not_nil assigns(:items)
336 assert_not_nil assigns(:items)
337 assert assigns(:items).first.is_a?(TimeEntry)
337 assert assigns(:items).first.is_a?(TimeEntry)
338 end
338 end
339
339
340 def test_index_all_projects_csv_export
340 def test_index_all_projects_csv_export
341 Setting.date_format = '%m/%d/%Y'
341 Setting.date_format = '%m/%d/%Y'
342 get :index, :format => 'csv'
342 get :index, :format => 'csv'
343 assert_response :success
343 assert_response :success
344 assert_equal 'text/csv', @response.content_type
344 assert_equal 'text/csv', @response.content_type
345 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
345 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
346 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
346 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
347 end
347 end
348
348
349 def test_index_csv_export
349 def test_index_csv_export
350 Setting.date_format = '%m/%d/%Y'
350 Setting.date_format = '%m/%d/%Y'
351 get :index, :project_id => 1, :format => 'csv'
351 get :index, :project_id => 1, :format => 'csv'
352 assert_response :success
352 assert_response :success
353 assert_equal 'text/csv', @response.content_type
353 assert_equal 'text/csv', @response.content_type
354 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
354 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
355 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
355 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
356 end
356 end
357
357
358 def test_csv_big_5
358 def test_csv_big_5
359 user = User.find_by_id(3)
359 user = User.find_by_id(3)
360 user.language = "zh-TW"
360 user.language = "zh-TW"
361 assert user.save
361 assert user.save
362 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
362 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
363 str_big5 = "\xa4@\xa4\xeb"
363 str_big5 = "\xa4@\xa4\xeb"
364 if str_utf8.respond_to?(:force_encoding)
364 if str_utf8.respond_to?(:force_encoding)
365 str_utf8.force_encoding('UTF-8')
365 str_utf8.force_encoding('UTF-8')
366 str_big5.force_encoding('Big5')
366 str_big5.force_encoding('Big5')
367 end
367 end
368 @request.session[:user_id] = 3
368 @request.session[:user_id] = 3
369 post :create, :project_id => 1,
369 post :create, :project_id => 1,
370 :time_entry => {:comments => str_utf8,
370 :time_entry => {:comments => str_utf8,
371 # Not the default activity
371 # Not the default activity
372 :activity_id => '11',
372 :activity_id => '11',
373 :issue_id => '',
373 :issue_id => '',
374 :spent_on => '2011-11-10',
374 :spent_on => '2011-11-10',
375 :hours => '7.3'}
375 :hours => '7.3'}
376 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
376 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
377
377
378 t = TimeEntry.find_by_comments(str_utf8)
378 t = TimeEntry.find_by_comments(str_utf8)
379 assert_not_nil t
379 assert_not_nil t
380 assert_equal 11, t.activity_id
380 assert_equal 11, t.activity_id
381 assert_equal 7.3, t.hours
381 assert_equal 7.3, t.hours
382 assert_equal 3, t.user_id
382 assert_equal 3, t.user_id
383
383
384 get :index, :project_id => 1, :format => 'csv',
384 get :index, :project_id => 1, :format => 'csv',
385 :from => '2011-11-10', :to => '2011-11-10'
385 :from => '2011-11-10', :to => '2011-11-10'
386 assert_response :success
386 assert_response :success
387 assert_equal 'text/csv', @response.content_type
387 assert_equal 'text/csv', @response.content_type
388 ar = @response.body.chomp.split("\n")
388 ar = @response.body.chomp.split("\n")
389 s1 = "\xa4\xe9\xb4\xc1"
389 s1 = "\xa4\xe9\xb4\xc1"
390 if str_utf8.respond_to?(:force_encoding)
390 if str_utf8.respond_to?(:force_encoding)
391 s1.force_encoding('Big5')
391 s1.force_encoding('Big5')
392 end
392 end
393 assert ar[0].include?(s1)
393 assert ar[0].include?(s1)
394 assert ar[1].include?(str_big5)
394 assert ar[1].include?(str_big5)
395 end
395 end
396
396
397 def test_csv_cannot_convert_should_be_replaced_big_5
397 def test_csv_cannot_convert_should_be_replaced_big_5
398 user = User.find_by_id(3)
398 user = User.find_by_id(3)
399 user.language = "zh-TW"
399 user.language = "zh-TW"
400 assert user.save
400 assert user.save
401 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
401 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
402 if str_utf8.respond_to?(:force_encoding)
402 if str_utf8.respond_to?(:force_encoding)
403 str_utf8.force_encoding('UTF-8')
403 str_utf8.force_encoding('UTF-8')
404 end
404 end
405 @request.session[:user_id] = 3
405 @request.session[:user_id] = 3
406 post :create, :project_id => 1,
406 post :create, :project_id => 1,
407 :time_entry => {:comments => str_utf8,
407 :time_entry => {:comments => str_utf8,
408 # Not the default activity
408 # Not the default activity
409 :activity_id => '11',
409 :activity_id => '11',
410 :issue_id => '',
410 :issue_id => '',
411 :spent_on => '2011-11-10',
411 :spent_on => '2011-11-10',
412 :hours => '7.3'}
412 :hours => '7.3'}
413 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
413 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
414
414
415 t = TimeEntry.find_by_comments(str_utf8)
415 t = TimeEntry.find_by_comments(str_utf8)
416 assert_not_nil t
416 assert_not_nil t
417 assert_equal 11, t.activity_id
417 assert_equal 11, t.activity_id
418 assert_equal 7.3, t.hours
418 assert_equal 7.3, t.hours
419 assert_equal 3, t.user_id
419 assert_equal 3, t.user_id
420
420
421 get :index, :project_id => 1, :format => 'csv',
421 get :index, :project_id => 1, :format => 'csv',
422 :from => '2011-11-10', :to => '2011-11-10'
422 :from => '2011-11-10', :to => '2011-11-10'
423 assert_response :success
423 assert_response :success
424 assert_equal 'text/csv', @response.content_type
424 assert_equal 'text/csv', @response.content_type
425 ar = @response.body.chomp.split("\n")
425 ar = @response.body.chomp.split("\n")
426 s1 = "\xa4\xe9\xb4\xc1"
426 s1 = "\xa4\xe9\xb4\xc1"
427 if str_utf8.respond_to?(:force_encoding)
427 if str_utf8.respond_to?(:force_encoding)
428 s1.force_encoding('Big5')
428 s1.force_encoding('Big5')
429 end
429 end
430 assert ar[0].include?(s1)
430 assert ar[0].include?(s1)
431 s2 = ar[1].split(",")[8]
431 s2 = ar[1].split(",")[8]
432 if s2.respond_to?(:force_encoding)
432 if s2.respond_to?(:force_encoding)
433 s3 = "\xa5H?"
433 s3 = "\xa5H?"
434 s3.force_encoding('Big5')
434 s3.force_encoding('Big5')
435 assert_equal s3, s2
435 assert_equal s3, s2
436 elsif RUBY_PLATFORM == 'java'
436 elsif RUBY_PLATFORM == 'java'
437 assert_equal "??", s2
437 assert_equal "??", s2
438 else
438 else
439 assert_equal "\xa5H???", s2
439 assert_equal "\xa5H???", s2
440 end
440 end
441 end
441 end
442
442
443 def test_csv_tw
443 def test_csv_tw
444 with_settings :default_language => "zh-TW" do
444 with_settings :default_language => "zh-TW" do
445 str1 = "test_csv_tw"
445 str1 = "test_csv_tw"
446 user = User.find_by_id(3)
446 user = User.find_by_id(3)
447 te1 = TimeEntry.create(:spent_on => '2011-11-10',
447 te1 = TimeEntry.create(:spent_on => '2011-11-10',
448 :hours => 999.9,
448 :hours => 999.9,
449 :project => Project.find(1),
449 :project => Project.find(1),
450 :user => user,
450 :user => user,
451 :activity => TimeEntryActivity.find_by_name('Design'),
451 :activity => TimeEntryActivity.find_by_name('Design'),
452 :comments => str1)
452 :comments => str1)
453 te2 = TimeEntry.find_by_comments(str1)
453 te2 = TimeEntry.find_by_comments(str1)
454 assert_not_nil te2
454 assert_not_nil te2
455 assert_equal 999.9, te2.hours
455 assert_equal 999.9, te2.hours
456 assert_equal 3, te2.user_id
456 assert_equal 3, te2.user_id
457
457
458 get :index, :project_id => 1, :format => 'csv',
458 get :index, :project_id => 1, :format => 'csv',
459 :from => '2011-11-10', :to => '2011-11-10'
459 :from => '2011-11-10', :to => '2011-11-10'
460 assert_response :success
460 assert_response :success
461 assert_equal 'text/csv', @response.content_type
461 assert_equal 'text/csv', @response.content_type
462
462
463 ar = @response.body.chomp.split("\n")
463 ar = @response.body.chomp.split("\n")
464 s2 = ar[1].split(",")[7]
464 s2 = ar[1].split(",")[7]
465 assert_equal '999.9', s2
465 assert_equal '999.9', s2
466
466
467 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
467 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
468 if str_tw.respond_to?(:force_encoding)
468 if str_tw.respond_to?(:force_encoding)
469 str_tw.force_encoding('UTF-8')
469 str_tw.force_encoding('UTF-8')
470 end
470 end
471 assert_equal str_tw, l(:general_lang_name)
471 assert_equal str_tw, l(:general_lang_name)
472 assert_equal ',', l(:general_csv_separator)
472 assert_equal ',', l(:general_csv_separator)
473 assert_equal '.', l(:general_csv_decimal_separator)
473 assert_equal '.', l(:general_csv_decimal_separator)
474 end
474 end
475 end
475 end
476
476
477 def test_csv_fr
477 def test_csv_fr
478 with_settings :default_language => "fr" do
478 with_settings :default_language => "fr" do
479 str1 = "test_csv_fr"
479 str1 = "test_csv_fr"
480 user = User.find_by_id(3)
480 user = User.find_by_id(3)
481 te1 = TimeEntry.create(:spent_on => '2011-11-10',
481 te1 = TimeEntry.create(:spent_on => '2011-11-10',
482 :hours => 999.9,
482 :hours => 999.9,
483 :project => Project.find(1),
483 :project => Project.find(1),
484 :user => user,
484 :user => user,
485 :activity => TimeEntryActivity.find_by_name('Design'),
485 :activity => TimeEntryActivity.find_by_name('Design'),
486 :comments => str1)
486 :comments => str1)
487 te2 = TimeEntry.find_by_comments(str1)
487 te2 = TimeEntry.find_by_comments(str1)
488 assert_not_nil te2
488 assert_not_nil te2
489 assert_equal 999.9, te2.hours
489 assert_equal 999.9, te2.hours
490 assert_equal 3, te2.user_id
490 assert_equal 3, te2.user_id
491
491
492 get :index, :project_id => 1, :format => 'csv',
492 get :index, :project_id => 1, :format => 'csv',
493 :from => '2011-11-10', :to => '2011-11-10'
493 :from => '2011-11-10', :to => '2011-11-10'
494 assert_response :success
494 assert_response :success
495 assert_equal 'text/csv', @response.content_type
495 assert_equal 'text/csv', @response.content_type
496
496
497 ar = @response.body.chomp.split("\n")
497 ar = @response.body.chomp.split("\n")
498 s2 = ar[1].split(";")[7]
498 s2 = ar[1].split(";")[7]
499 assert_equal '999,9', s2
499 assert_equal '999,9', s2
500
500
501 str_fr = "Fran\xc3\xa7ais"
501 str_fr = "Fran\xc3\xa7ais"
502 if str_fr.respond_to?(:force_encoding)
502 if str_fr.respond_to?(:force_encoding)
503 str_fr.force_encoding('UTF-8')
503 str_fr.force_encoding('UTF-8')
504 end
504 end
505 assert_equal str_fr, l(:general_lang_name)
505 assert_equal str_fr, l(:general_lang_name)
506 assert_equal ';', l(:general_csv_separator)
506 assert_equal ';', l(:general_csv_separator)
507 assert_equal ',', l(:general_csv_decimal_separator)
507 assert_equal ',', l(:general_csv_decimal_separator)
508 end
508 end
509 end
509 end
510 end
510 end
@@ -1,176 +1,128
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class TimeEntryTest < ActiveSupport::TestCase
20 class TimeEntryTest < ActiveSupport::TestCase
21 fixtures :issues, :projects, :users, :time_entries,
21 fixtures :issues, :projects, :users, :time_entries,
22 :members, :roles, :member_roles, :auth_sources,
22 :members, :roles, :member_roles, :auth_sources,
23 :trackers, :issue_statuses,
23 :trackers, :issue_statuses,
24 :projects_trackers,
24 :projects_trackers,
25 :journals, :journal_details,
25 :journals, :journal_details,
26 :issue_categories, :enumerations,
26 :issue_categories, :enumerations,
27 :groups_users,
27 :groups_users,
28 :enabled_modules,
28 :enabled_modules,
29 :workflows
29 :workflows
30
30
31 def test_hours_format
31 def test_hours_format
32 assertions = { "2" => 2.0,
32 assertions = { "2" => 2.0,
33 "21.1" => 21.1,
33 "21.1" => 21.1,
34 "2,1" => 2.1,
34 "2,1" => 2.1,
35 "1,5h" => 1.5,
35 "1,5h" => 1.5,
36 "7:12" => 7.2,
36 "7:12" => 7.2,
37 "10h" => 10.0,
37 "10h" => 10.0,
38 "10 h" => 10.0,
38 "10 h" => 10.0,
39 "45m" => 0.75,
39 "45m" => 0.75,
40 "45 m" => 0.75,
40 "45 m" => 0.75,
41 "3h15" => 3.25,
41 "3h15" => 3.25,
42 "3h 15" => 3.25,
42 "3h 15" => 3.25,
43 "3 h 15" => 3.25,
43 "3 h 15" => 3.25,
44 "3 h 15m" => 3.25,
44 "3 h 15m" => 3.25,
45 "3 h 15 m" => 3.25,
45 "3 h 15 m" => 3.25,
46 "3 hours" => 3.0,
46 "3 hours" => 3.0,
47 "12min" => 0.2,
47 "12min" => 0.2,
48 }
48 }
49
49
50 assertions.each do |k, v|
50 assertions.each do |k, v|
51 t = TimeEntry.new(:hours => k)
51 t = TimeEntry.new(:hours => k)
52 assert_equal v, t.hours, "Converting #{k} failed:"
52 assert_equal v, t.hours, "Converting #{k} failed:"
53 end
53 end
54 end
54 end
55
55
56 def test_hours_should_default_to_nil
56 def test_hours_should_default_to_nil
57 assert_nil TimeEntry.new.hours
57 assert_nil TimeEntry.new.hours
58 end
58 end
59
59
60 def test_spent_on_with_blank
60 def test_spent_on_with_blank
61 c = TimeEntry.new
61 c = TimeEntry.new
62 c.spent_on = ''
62 c.spent_on = ''
63 assert_nil c.spent_on
63 assert_nil c.spent_on
64 end
64 end
65
65
66 def test_spent_on_with_nil
66 def test_spent_on_with_nil
67 c = TimeEntry.new
67 c = TimeEntry.new
68 c.spent_on = nil
68 c.spent_on = nil
69 assert_nil c.spent_on
69 assert_nil c.spent_on
70 end
70 end
71
71
72 def test_spent_on_with_string
72 def test_spent_on_with_string
73 c = TimeEntry.new
73 c = TimeEntry.new
74 c.spent_on = "2011-01-14"
74 c.spent_on = "2011-01-14"
75 assert_equal Date.parse("2011-01-14"), c.spent_on
75 assert_equal Date.parse("2011-01-14"), c.spent_on
76 end
76 end
77
77
78 def test_spent_on_with_invalid_string
78 def test_spent_on_with_invalid_string
79 c = TimeEntry.new
79 c = TimeEntry.new
80 c.spent_on = "foo"
80 c.spent_on = "foo"
81 assert_nil c.spent_on
81 assert_nil c.spent_on
82 end
82 end
83
83
84 def test_spent_on_with_date
84 def test_spent_on_with_date
85 c = TimeEntry.new
85 c = TimeEntry.new
86 c.spent_on = Date.today
86 c.spent_on = Date.today
87 assert_equal Date.today, c.spent_on
87 assert_equal Date.today, c.spent_on
88 end
88 end
89
89
90 def test_spent_on_with_time
90 def test_spent_on_with_time
91 c = TimeEntry.new
91 c = TimeEntry.new
92 c.spent_on = Time.now
92 c.spent_on = Time.now
93 assert_equal Date.today, c.spent_on
93 assert_equal Date.today, c.spent_on
94 end
94 end
95
95
96 def test_validate_time_entry
96 def test_validate_time_entry
97 anon = User.anonymous
97 anon = User.anonymous
98 project = Project.find(1)
98 project = Project.find(1)
99 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1,
99 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1,
100 :priority => IssuePriority.all.first, :subject => 'test_create',
100 :priority => IssuePriority.all.first, :subject => 'test_create',
101 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
101 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
102 assert issue.save
102 assert issue.save
103 activity = TimeEntryActivity.find_by_name('Design')
103 activity = TimeEntryActivity.find_by_name('Design')
104 te = TimeEntry.create(:spent_on => '2010-01-01',
104 te = TimeEntry.create(:spent_on => '2010-01-01',
105 :hours => 100000,
105 :hours => 100000,
106 :issue => issue,
106 :issue => issue,
107 :project => project,
107 :project => project,
108 :user => anon,
108 :user => anon,
109 :activity => activity)
109 :activity => activity)
110 assert_equal 1, te.errors.count
110 assert_equal 1, te.errors.count
111 end
111 end
112
112
113 def test_set_project_if_nil
113 def test_set_project_if_nil
114 anon = User.anonymous
114 anon = User.anonymous
115 project = Project.find(1)
115 project = Project.find(1)
116 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1,
116 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1,
117 :priority => IssuePriority.all.first, :subject => 'test_create',
117 :priority => IssuePriority.all.first, :subject => 'test_create',
118 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
118 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
119 assert issue.save
119 assert issue.save
120 activity = TimeEntryActivity.find_by_name('Design')
120 activity = TimeEntryActivity.find_by_name('Design')
121 te = TimeEntry.create(:spent_on => '2010-01-01',
121 te = TimeEntry.create(:spent_on => '2010-01-01',
122 :hours => 10,
122 :hours => 10,
123 :issue => issue,
123 :issue => issue,
124 :user => anon,
124 :user => anon,
125 :activity => activity)
125 :activity => activity)
126 assert_equal project.id, te.project.id
126 assert_equal project.id, te.project.id
127 end
127 end
128
129 context "#earilest_date_for_project" do
130 setup do
131 User.current = nil
132 @public_project = Project.generate!(:is_public => true)
133 @issue = Issue.generate_for_project!(@public_project)
134 TimeEntry.generate!(:spent_on => '2010-01-01',
135 :issue => @issue,
136 :project => @public_project)
137 end
138
139 context "without a project" do
140 should "return the lowest spent_on value that is visible to the current user" do
141 assert_equal "2007-03-12", TimeEntry.earilest_date_for_project.to_s
142 end
143 end
144
145 context "with a project" do
146 should "return the lowest spent_on value that is visible to the current user for that project and it's subprojects only" do
147 assert_equal "2010-01-01", TimeEntry.earilest_date_for_project(@public_project).to_s
148 end
149 end
150
151 end
152
153 context "#latest_date_for_project" do
154 setup do
155 User.current = nil
156 @public_project = Project.generate!(:is_public => true)
157 @issue = Issue.generate_for_project!(@public_project)
158 TimeEntry.generate!(:spent_on => '2010-01-01',
159 :issue => @issue,
160 :project => @public_project)
161 end
162
163 context "without a project" do
164 should "return the highest spent_on value that is visible to the current user" do
165 assert_equal "2010-01-01", TimeEntry.latest_date_for_project.to_s
166 end
167 end
168
169 context "with a project" do
170 should "return the highest spent_on value that is visible to the current user for that project and it's subprojects only" do
171 project = Project.find(1)
172 assert_equal "2007-04-22", TimeEntry.latest_date_for_project(project).to_s
173 end
174 end
175 end
176 end
128 end
General Comments 0
You need to be logged in to leave comments. Login now