kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
8.4 KiB
190 lines
8.4 KiB
#-- encoding: UTF-8
|
|
#-- copyright
|
|
# OpenProject is a project management system.
|
|
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# See doc/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
class TimeEntries::ReportsController < ApplicationController
|
|
menu_item :issues
|
|
before_filter :find_optional_project
|
|
before_filter :load_available_criterias
|
|
|
|
include SortHelper
|
|
include TimelogHelper
|
|
include CustomFieldsHelper
|
|
|
|
menu_item :time_entries
|
|
|
|
def show
|
|
@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.map { |criteria| @available_criterias[criteria][:sql] + ' AS ' + criteria }.join(', ')
|
|
sql_group_by = @criterias.map { |criteria| @available_criterias[criteria][:sql] }.join(', ')
|
|
|
|
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' % context_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 do render layout: !request.xhr? end
|
|
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), type: 'text/csv; header=present', filename: 'timelog.csv') }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def load_available_criterias
|
|
@available_criterias = { 'project' => { sql: "#{TimeEntry.table_name}.project_id",
|
|
klass: Project,
|
|
label: Project.model_name.human },
|
|
'version' => { sql: "#{WorkPackage.table_name}.fixed_version_id",
|
|
klass: Version,
|
|
label: Version.model_name.human },
|
|
'category' => { sql: "#{WorkPackage.table_name}.category_id",
|
|
klass: Category,
|
|
label: Category.model_name.human },
|
|
'member' => { sql: "#{TimeEntry.table_name}.user_id",
|
|
klass: User,
|
|
label: Member.model_name.human },
|
|
'type' => { sql: "#{WorkPackage.table_name}.type_id",
|
|
klass: ::Type,
|
|
label: ::Type.model_name.human },
|
|
'activity' => { sql: "#{TimeEntry.table_name}.activity_id",
|
|
klass: TimeEntryActivity,
|
|
label: :label_activity },
|
|
'work_package' => { sql: "#{TimeEntry.table_name}.work_package_id",
|
|
klass: WorkPackage,
|
|
label: WorkPackage.model_name.human }
|
|
}
|
|
|
|
# Add list and boolean custom fields as available criterias
|
|
custom_fields = (@project.nil? ? WorkPackageCustomField.for_all : @project.all_work_package_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 = 'WorkPackage' AND c.customized_id = #{WorkPackage.table_name}.id)",
|
|
format: cf.field_format,
|
|
label: cf.name }
|
|
end if @project
|
|
|
|
# Add list and boolean time entry custom fields
|
|
TimeEntryCustomField.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.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 #{WorkPackage.table_name} ON #{TimeEntry.table_name}.work_package_id = #{WorkPackage.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
|
|
|
|
def default_breadcrumb
|
|
I18n.t(:label_spent_time)
|
|
end
|
|
|
|
def context_sql_condition
|
|
if @project.nil?
|
|
project_context_sql_condition
|
|
elsif @issue.nil?
|
|
@project.project_condition(Setting.display_subprojects_work_packages?)
|
|
else
|
|
"#{WorkPackage.table_name}.root_id = #{@issue.root_id}" +
|
|
" AND #{WorkPackage.table_name}.lft >= #{@issue.lft}" +
|
|
" AND #{WorkPackage.table_name}.rgt <= #{@issue.rgt}"
|
|
end
|
|
end
|
|
|
|
def project_context_sql_condition
|
|
time_entry_table = TimeEntry.arel_table
|
|
allowed_project_ids = Project.allowed_to(User.current, :view_time_entries).select(:id).arel
|
|
time_entry_table[:project_id].in(allowed_project_ids).to_sql
|
|
end
|
|
end
|
|
|