From: Eric Davis Date: Tue, 5 Oct 2010 16:07:17 +0000 (+0000) Subject: Refactor: extract TimelogController#report to a new controller class X-Git-Tag: 1.1.0~303 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e13790c62c800a5b6d9528b5e2db2aad2c5e3710;p=redmine.git Refactor: extract TimelogController#report to a new controller class git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4232 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- diff --git a/app/controllers/time_entry_reports_controller.rb b/app/controllers/time_entry_reports_controller.rb new file mode 100644 index 000000000..dd02ff8ff --- /dev/null +++ b/app/controllers/time_entry_reports_controller.rb @@ -0,0 +1,209 @@ +class TimeEntryReportsController < ApplicationController + menu_item :issues + before_filter :find_optional_project + before_filter :load_available_criterias + + helper :sort + include SortHelper + helper :issues + helper :timelog + include TimelogHelper + helper :custom_fields + include CustomFieldsHelper + + def report + @criterias = params[:criterias] || [] + @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria} + @criterias.uniq! + @criterias = @criterias[0,3] + + @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month' + + retrieve_date_range + + unless @criterias.empty? + sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ') + sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ') + sql_condition = '' + + if @project.nil? + sql_condition = Project.allowed_to_condition(User.current, :view_time_entries) + elsif @issue.nil? + sql_condition = @project.project_condition(Setting.display_subprojects_issues?) + else + sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}" + end + + sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours" + sql << " FROM #{TimeEntry.table_name}" + sql << time_report_joins + sql << " WHERE" + sql << " (%s) AND" % sql_condition + sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)] + sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" + + @hours = ActiveRecord::Base.connection.select_all(sql) + + @hours.each do |row| + case @columns + when 'year' + row['year'] = row['tyear'] + when 'month' + row['month'] = "#{row['tyear']}-#{row['tmonth']}" + when 'week' + row['week'] = "#{row['tyear']}-#{row['tweek']}" + when 'day' + row['day'] = "#{row['spent_on']}" + end + end + + @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} + + @periods = [] + # Date#at_beginning_of_ not supported in Rails 1.2.x + date_from = @from.to_time + # 100 columns max + while date_from <= @to.to_time && @periods.length < 100 + case @columns + when 'year' + @periods << "#{date_from.year}" + date_from = (date_from + 1.year).at_beginning_of_year + when 'month' + @periods << "#{date_from.year}-#{date_from.month}" + date_from = (date_from + 1.month).at_beginning_of_month + when 'week' + @periods << "#{date_from.year}-#{date_from.to_date.cweek}" + date_from = (date_from + 7.day).at_beginning_of_week + when 'day' + @periods << "#{date_from.to_date}" + date_from = date_from + 1.day + end + end + end + + respond_to do |format| + format.html { render :layout => !request.xhr? } + format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') } + end + end + + private + + # TODO: duplicated in TimelogController + def find_optional_project + if !params[:issue_id].blank? + @issue = Issue.find(params[:issue_id]) + @project = @issue.project + elsif !params[:project_id].blank? + @project = Project.find(params[:project_id]) + end + deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true) + end + + # Retrieves the date range based on predefined ranges or specific from/to param dates + # TODO: duplicated in TimelogController + def retrieve_date_range + @free_period = false + @from, @to = nil, nil + + if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) + case params[:period].to_s + when 'today' + @from = @to = Date.today + when 'yesterday' + @from = @to = Date.today - 1 + when 'current_week' + @from = Date.today - (Date.today.cwday - 1)%7 + @to = @from + 6 + when 'last_week' + @from = Date.today - 7 - (Date.today.cwday - 1)%7 + @to = @from + 6 + when '7_days' + @from = Date.today - 7 + @to = Date.today + when 'current_month' + @from = Date.civil(Date.today.year, Date.today.month, 1) + @to = (@from >> 1) - 1 + when 'last_month' + @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 + @to = (@from >> 1) - 1 + when '30_days' + @from = Date.today - 30 + @to = Date.today + when 'current_year' + @from = Date.civil(Date.today.year, 1, 1) + @to = Date.civil(Date.today.year, 12, 31) + end + elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) + begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end + begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end + @free_period = true + else + # default + end + + @from, @to = @to, @from if @from && @to && @from > @to + @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today) + @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) + end + + def load_available_criterias + @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", + :klass => Project, + :label => :label_project}, + 'version' => {:sql => "#{Issue.table_name}.fixed_version_id", + :klass => Version, + :label => :label_version}, + 'category' => {:sql => "#{Issue.table_name}.category_id", + :klass => IssueCategory, + :label => :field_category}, + 'member' => {:sql => "#{TimeEntry.table_name}.user_id", + :klass => User, + :label => :label_member}, + 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", + :klass => Tracker, + :label => :label_tracker}, + 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", + :klass => TimeEntryActivity, + :label => :label_activity}, + 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", + :klass => Issue, + :label => :label_issue} + } + + # Add list and boolean custom fields as available criterias + custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) + custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)", + :format => cf.field_format, + :label => cf.name} + end if @project + + # Add list and boolean time entry custom fields + TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)", + :format => cf.field_format, + :label => cf.name} + end + + # Add list and boolean time entry activity custom fields + TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)", + :format => cf.field_format, + :label => cf.name} + end + + call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project }) + @available_criterias + end + + def time_report_joins + sql = '' + sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" + sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" + # TODO: rename hook + call_hook(:controller_timelog_time_report_joins, {:sql => sql} ) + sql + end + +end diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index 45f41ae5e..c03ea6891 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -18,8 +18,7 @@ class TimelogController < ApplicationController menu_item :issues before_filter :find_project, :authorize, :only => [:edit, :destroy] - before_filter :find_optional_project, :only => [:report, :details] - before_filter :load_available_criterias, :only => [:report] + before_filter :find_optional_project, :only => [:details] verify :method => :post, :only => :destroy, :redirect_to => { :action => :details } @@ -30,82 +29,6 @@ class TimelogController < ApplicationController helper :custom_fields include CustomFieldsHelper - def report - @criterias = params[:criterias] || [] - @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria} - @criterias.uniq! - @criterias = @criterias[0,3] - - @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month' - - retrieve_date_range - - unless @criterias.empty? - sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ') - sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ') - sql_condition = '' - - if @project.nil? - sql_condition = Project.allowed_to_condition(User.current, :view_time_entries) - elsif @issue.nil? - sql_condition = @project.project_condition(Setting.display_subprojects_issues?) - else - sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}" - end - - sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours" - sql << " FROM #{TimeEntry.table_name}" - sql << time_report_joins - sql << " WHERE" - sql << " (%s) AND" % sql_condition - sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)] - sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" - - @hours = ActiveRecord::Base.connection.select_all(sql) - - @hours.each do |row| - case @columns - when 'year' - row['year'] = row['tyear'] - when 'month' - row['month'] = "#{row['tyear']}-#{row['tmonth']}" - when 'week' - row['week'] = "#{row['tyear']}-#{row['tweek']}" - when 'day' - row['day'] = "#{row['spent_on']}" - end - end - - @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} - - @periods = [] - # Date#at_beginning_of_ not supported in Rails 1.2.x - date_from = @from.to_time - # 100 columns max - while date_from <= @to.to_time && @periods.length < 100 - case @columns - when 'year' - @periods << "#{date_from.year}" - date_from = (date_from + 1.year).at_beginning_of_year - when 'month' - @periods << "#{date_from.year}-#{date_from.month}" - date_from = (date_from + 1.month).at_beginning_of_month - when 'week' - @periods << "#{date_from.year}-#{date_from.to_date.cweek}" - date_from = (date_from + 7.day).at_beginning_of_week - when 'day' - @periods << "#{date_from.to_date}" - date_from = date_from + 1.day - end - end - end - - respond_to do |format| - format.html { render :layout => !request.xhr? } - format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') } - end - end - def details sort_init 'spent_on', 'desc' sort_update 'spent_on' => 'spent_on', @@ -264,61 +187,4 @@ private @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) end - def load_available_criterias - @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", - :klass => Project, - :label => :label_project}, - 'version' => {:sql => "#{Issue.table_name}.fixed_version_id", - :klass => Version, - :label => :label_version}, - 'category' => {:sql => "#{Issue.table_name}.category_id", - :klass => IssueCategory, - :label => :field_category}, - 'member' => {:sql => "#{TimeEntry.table_name}.user_id", - :klass => User, - :label => :label_member}, - 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", - :klass => Tracker, - :label => :label_tracker}, - 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", - :klass => TimeEntryActivity, - :label => :label_activity}, - 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", - :klass => Issue, - :label => :label_issue} - } - - # Add list and boolean custom fields as available criterias - custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) - custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)", - :format => cf.field_format, - :label => cf.name} - end if @project - - # Add list and boolean time entry custom fields - TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)", - :format => cf.field_format, - :label => cf.name} - end - - # Add list and boolean time entry activity custom fields - TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)", - :format => cf.field_format, - :label => cf.name} - end - - call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project }) - @available_criterias - end - - def time_report_joins - sql = '' - sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" - sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" - call_hook(:controller_timelog_time_report_joins, {:sql => sql} ) - sql - end end diff --git a/app/views/projects/show.rhtml b/app/views/projects/show.rhtml index cf3814b77..80e45adf0 100644 --- a/app/views/projects/show.rhtml +++ b/app/views/projects/show.rhtml @@ -68,7 +68,7 @@

<%= l(:label_spent_time) %>

<%= l_hours(@total_hours) %>

<%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> | - <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %>

+ <%= link_to(l(:label_report), {:controller => 'time_entry_reports', :action => 'report', :project_id => @project}) %>

<% end %> <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> <% end %> diff --git a/app/views/time_entry_reports/_report_criteria.rhtml b/app/views/time_entry_reports/_report_criteria.rhtml new file mode 100644 index 000000000..c9a1cfb45 --- /dev/null +++ b/app/views/time_entry_reports/_report_criteria.rhtml @@ -0,0 +1,19 @@ +<% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %> +<% hours_for_value = select_hours(hours, criterias[level], value) -%> +<% next if hours_for_value.empty? -%> + +<%= '' * level %> +<%= h(format_criteria_value(criterias[level], value)) %> +<%= '' * (criterias.length - level - 1) -%> + <% total = 0 -%> + <% @periods.each do |period| -%> + <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%> + <%= html_hours("%.2f" % sum) if sum > 0 %> + <% end -%> + <%= html_hours("%.2f" % total) if total > 0 %> + +<% if criterias.length > level+1 -%> + <%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %> +<% end -%> + +<% end %> diff --git a/app/views/time_entry_reports/report.rhtml b/app/views/time_entry_reports/report.rhtml new file mode 100644 index 000000000..0d28d5dc7 --- /dev/null +++ b/app/views/time_entry_reports/report.rhtml @@ -0,0 +1,76 @@ +
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %> +
+ +<%= render_timelog_breadcrumb %> + +

<%= l(:label_spent_time) %>

+ +<% form_remote_tag(:url => {}, :html => {:method => :get, :id => 'query_form'}, :method => :get, :update => 'content') do %> + <% @criterias.each do |criteria| %> + <%= hidden_field_tag 'criterias[]', criteria, :id => nil %> + <% end %> + <%# TODO: get rid of the project_id field, that should already be in the URL %> + <%= hidden_field_tag('project_id', params[:project_id]) if @project %> + <%= hidden_field_tag('issue_id', params[:issue_id]) if @issue %> + <%= render :partial => 'timelog/date_range' %> + +

<%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], + [l(:label_month), 'month'], + [l(:label_week), 'week'], + [l(:label_day_plural).titleize, 'day']], @columns), + :onchange => "this.form.onsubmit();" %> + + <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l_or_humanize(@available_criterias[k][:label]), k]}), + :onchange => "this.form.onsubmit();", + :style => 'width: 200px', + :id => nil, + :disabled => (@criterias.length >= 3)) %> + <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, + :method => :get, + :update => 'content' + }, :class => 'icon icon-reload' %>

+<% end %> + +<% unless @criterias.empty? %> +
+

<%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %>

+
+ +<% unless @hours.empty? %> + + + +<% @criterias.each do |criteria| %> + +<% end %> +<% columns_width = (40 / (@periods.length+1)).to_i %> +<% @periods.each do |period| %> + +<% end %> + + + + +<%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %> + + + <%= '' * (@criterias.size - 1) %> + <% total = 0 -%> + <% @periods.each do |period| -%> + <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%> + + <% end -%> + + + +
<%= l_or_humanize(@available_criterias[criteria][:label]) %><%= period %><%= l(:label_total) %>
<%= l(:label_total) %><%= html_hours("%.2f" % sum) if sum > 0 %><%= html_hours("%.2f" % total) if total > 0 %>
+ +<% other_formats_links do |f| %> + <%= f.link_to 'CSV', :url => params %> +<% end %> +<% end %> +<% end %> + +<% html_title l(:label_spent_time), l(:label_report) %> + diff --git a/app/views/timelog/_date_range.rhtml b/app/views/timelog/_date_range.rhtml index 2482a9fc3..041e5587f 100644 --- a/app/views/timelog/_date_range.rhtml +++ b/app/views/timelog/_date_range.rhtml @@ -30,7 +30,7 @@ diff --git a/app/views/timelog/_report_criteria.rhtml b/app/views/timelog/_report_criteria.rhtml deleted file mode 100644 index c9a1cfb45..000000000 --- a/app/views/timelog/_report_criteria.rhtml +++ /dev/null @@ -1,19 +0,0 @@ -<% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %> -<% hours_for_value = select_hours(hours, criterias[level], value) -%> -<% next if hours_for_value.empty? -%> - -<%= '' * level %> -<%= h(format_criteria_value(criterias[level], value)) %> -<%= '' * (criterias.length - level - 1) -%> - <% total = 0 -%> - <% @periods.each do |period| -%> - <% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%> - <%= html_hours("%.2f" % sum) if sum > 0 %> - <% end -%> - <%= html_hours("%.2f" % total) if total > 0 %> - -<% if criterias.length > level+1 -%> - <%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %> -<% end -%> - -<% end %> diff --git a/app/views/timelog/report.rhtml b/app/views/timelog/report.rhtml deleted file mode 100644 index 533467ef2..000000000 --- a/app/views/timelog/report.rhtml +++ /dev/null @@ -1,76 +0,0 @@ -
-<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %> -
- -<%= render_timelog_breadcrumb %> - -

<%= l(:label_spent_time) %>

- -<% form_remote_tag(:url => {}, :html => {:method => :get, :id => 'query_form'}, :method => :get, :update => 'content') do %> - <% @criterias.each do |criteria| %> - <%= hidden_field_tag 'criterias[]', criteria, :id => nil %> - <% end %> - <%# TODO: get rid of the project_id field, that should already be in the URL %> - <%= hidden_field_tag('project_id', params[:project_id]) if @project %> - <%= hidden_field_tag('issue_id', params[:issue_id]) if @issue %> - <%= render :partial => 'date_range' %> - -

<%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], - [l(:label_month), 'month'], - [l(:label_week), 'week'], - [l(:label_day_plural).titleize, 'day']], @columns), - :onchange => "this.form.onsubmit();" %> - - <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l_or_humanize(@available_criterias[k][:label]), k]}), - :onchange => "this.form.onsubmit();", - :style => 'width: 200px', - :id => nil, - :disabled => (@criterias.length >= 3)) %> - <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, - :method => :get, - :update => 'content' - }, :class => 'icon icon-reload' %>

-<% end %> - -<% unless @criterias.empty? %> -
-

<%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %>

-
- -<% unless @hours.empty? %> - - - -<% @criterias.each do |criteria| %> - -<% end %> -<% columns_width = (40 / (@periods.length+1)).to_i %> -<% @periods.each do |period| %> - -<% end %> - - - - -<%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %> - - - <%= '' * (@criterias.size - 1) %> - <% total = 0 -%> - <% @periods.each do |period| -%> - <% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%> - - <% end -%> - - - -
<%= l_or_humanize(@available_criterias[criteria][:label]) %><%= period %><%= l(:label_total) %>
<%= l(:label_total) %><%= html_hours("%.2f" % sum) if sum > 0 %><%= html_hours("%.2f" % total) if total > 0 %>
- -<% other_formats_links do |f| %> - <%= f.link_to 'CSV', :url => params %> -<% end %> -<% end %> -<% end %> - -<% html_title l(:label_spent_time), l(:label_report) %> - diff --git a/config/routes.rb b/config/routes.rb index 7902fee32..32ab0ca37 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,8 +30,8 @@ ActionController::Routing::Routes.draw do |map| time_details.connect 'projects/:project_id/issues/:issue_id/time_entries' time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format' end - timelog.connect 'projects/:project_id/time_entries/report', :action => 'report' - timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report| + timelog.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report' + timelog.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report| time_report.connect 'time_entries/report' time_report.connect 'time_entries/report.:format' time_report.connect 'projects/:project_id/time_entries/report.:format' diff --git a/lib/redmine.rb b/lib/redmine.rb index 9d9b0e408..9bff8e19a 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -85,7 +85,7 @@ Redmine::AccessControl.map do |map| map.project_module :time_tracking do |map| map.permission :log_time, {:timelog => :edit}, :require => :loggedin - map.permission :view_time_entries, :timelog => [:details, :report] + map.permission :view_time_entries, :timelog => [:details], :time_entry_reports => [:report] map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member diff --git a/test/functional/time_entry_reports_controller_test.rb b/test/functional/time_entry_reports_controller_test.rb new file mode 100644 index 000000000..0465c88ec --- /dev/null +++ b/test/functional/time_entry_reports_controller_test.rb @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +require File.dirname(__FILE__) + '/../test_helper' + +class TimeEntryReportsControllerTest < ActionController::TestCase + fixtures :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses, :custom_fields, :custom_values + + def test_report_no_criteria + get :report, :project_id => 1 + assert_response :success + assert_template 'report' + end + + def test_report_all_projects + get :report + assert_response :success + assert_template 'report' + end + + def test_report_all_projects_denied + r = Role.anonymous + r.permissions.delete(:view_time_entries) + r.permissions_will_change! + r.save + get :report + assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport' + end + + def test_report_all_projects_one_criteria + get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "8.65", "%.2f" % assigns(:total_hours) + end + + def test_report_all_time + get :report, :project_id => 1, :criterias => ['project', 'issue'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + end + + def test_report_all_time_by_day + get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day' + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + assert_tag :tag => 'th', :content => '2007-03-12' + end + + def test_report_one_criteria + get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "8.65", "%.2f" % assigns(:total_hours) + end + + def test_report_two_criterias + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + end + + def test_report_one_day + get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criterias => ["member", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "4.25", "%.2f" % assigns(:total_hours) + end + + def test_report_at_issue_level + get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "154.25", "%.2f" % assigns(:total_hours) + end + + def test_report_custom_field_criteria + get :report, :project_id => 1, :criterias => ['project', 'cf_1', 'cf_7'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_not_nil assigns(:criterias) + assert_equal 3, assigns(:criterias).size + assert_equal "162.90", "%.2f" % assigns(:total_hours) + # Custom field column + assert_tag :tag => 'th', :content => 'Database' + # Custom field row + assert_tag :tag => 'td', :content => 'MySQL', + :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, + :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, + :content => '1' }} + # Second custom field column + assert_tag :tag => 'th', :content => 'Billable' + end + + def test_report_one_criteria_no_result + get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project'] + assert_response :success + assert_template 'report' + assert_not_nil assigns(:total_hours) + assert_equal "0.00", "%.2f" % assigns(:total_hours) + end + + def test_report_all_projects_csv_export + get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" + assert_response :success + assert_equal 'text/csv', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first + # Total row + assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + end + + def test_report_csv_export + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" + assert_response :success + assert_equal 'text/csv', @response.content_type + lines = @response.body.chomp.split("\n") + # Headers + assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first + # Total row + assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + end + +end diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb index 0559a95ca..00178dcb9 100644 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -137,133 +137,6 @@ class TimelogControllerTest < ActionController::TestCase TimeEntry.before_destroy.reject! {|callback| callback.method == :stop_callback_chain } end - def test_report_no_criteria - get :report, :project_id => 1 - assert_response :success - assert_template 'report' - end - - def test_report_all_projects - get :report - assert_response :success - assert_template 'report' - end - - def test_report_all_projects_denied - r = Role.anonymous - r.permissions.delete(:view_time_entries) - r.permissions_will_change! - r.save - get :report - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport' - end - - def test_report_all_projects_one_criteria - get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "8.65", "%.2f" % assigns(:total_hours) - end - - def test_report_all_time - get :report, :project_id => 1, :criterias => ['project', 'issue'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - end - - def test_report_all_time_by_day - get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day' - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - assert_tag :tag => 'th', :content => '2007-03-12' - end - - def test_report_one_criteria - get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "8.65", "%.2f" % assigns(:total_hours) - end - - def test_report_two_criterias - get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - end - - def test_report_one_day - get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "4.25", "%.2f" % assigns(:total_hours) - end - - def test_report_at_issue_level - get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "154.25", "%.2f" % assigns(:total_hours) - end - - def test_report_custom_field_criteria - get :report, :project_id => 1, :criterias => ['project', 'cf_1', 'cf_7'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_not_nil assigns(:criterias) - assert_equal 3, assigns(:criterias).size - assert_equal "162.90", "%.2f" % assigns(:total_hours) - # Custom field column - assert_tag :tag => 'th', :content => 'Database' - # Custom field row - assert_tag :tag => 'td', :content => 'MySQL', - :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, - :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, - :content => '1' }} - # Second custom field column - assert_tag :tag => 'th', :content => 'Billable' - end - - def test_report_one_criteria_no_result - get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "0.00", "%.2f" % assigns(:total_hours) - end - - def test_report_all_projects_csv_export - get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first - # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last - end - - def test_report_csv_export - get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first - # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last - end - def test_details_all_projects get :details assert_response :success diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index b42468ffb..d1a2aa390 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -226,9 +226,15 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/ecookbook/time_entries/new", :controller => 'timelog', :action => 'edit', :project_id => 'ecookbook' should_route :get, "/projects/ecookbook/issues/567/time_entries/new", :controller => 'timelog', :action => 'edit', :project_id => 'ecookbook', :issue_id => '567' should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22' - should_route :get, "/time_entries/report", :controller => 'timelog', :action => 'report' - should_route :get, "/projects/567/time_entries/report", :controller => 'timelog', :action => 'report', :project_id => '567' - should_route :get, "/projects/567/time_entries/report.csv", :controller => 'timelog', :action => 'report', :project_id => '567', :format => 'csv' + + should_route :post, "/time_entries/55/destroy", :controller => 'timelog', :action => 'destroy', :id => '55' + end + + context "time_entry_reports" do + should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report' + should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567' + should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv' + should_route :get, "/time_entries", :controller => 'timelog', :action => 'details' should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'details', :format => 'csv' should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'details', :format => 'atom' @@ -240,7 +246,6 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'details', :issue_id => '234', :format => 'atom' should_route :get, "/projects/ecookbook/issues/123/time_entries", :controller => 'timelog', :action => 'details', :project_id => 'ecookbook', :issue_id => '123' - should_route :post, "/time_entries/55/destroy", :controller => 'timelog', :action => 'destroy', :id => '55' end context "users" do