time_entry_query.rb
223 lines
| 8.0 KiB
| text/x-ruby
|
RubyLexer
|
r10740 | # Redmine - project management software | ||
|
r14856 | # Copyright (C) 2006-2016 Jean-Philippe Lang | ||
|
r10740 | # | ||
# 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. | ||||
class TimeEntryQuery < Query | ||||
self.queried_class = TimeEntry | ||||
|
r15254 | self.view_permission = :view_time_entries | ||
|
r10740 | |||
self.available_columns = [ | ||||
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), | ||||
|
r10745 | QueryColumn.new(:spent_on, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :default_order => 'desc', :groupable => true), | ||
|
r14341 | QueryColumn.new(:tweek, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :caption => l(:label_week)), | ||
|
r10740 | QueryColumn.new(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), | ||
QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true), | ||||
QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"), | ||||
|
r15356 | QueryAssociationColumn.new(:issue, :tracker, :caption => :field_tracker, :sortable => "#{Tracker.table_name}.position"), | ||
QueryAssociationColumn.new(:issue, :status, :caption => :field_status, :sortable => "#{IssueStatus.table_name}.position"), | ||||
|
r10740 | QueryColumn.new(:comments), | ||
|
r15267 | QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true), | ||
|
r10740 | ] | ||
def initialize(attributes=nil, *args) | ||||
super attributes | ||||
self.filters ||= {} | ||||
add_filter('spent_on', '*') unless filters.present? | ||||
end | ||||
|
r11142 | def initialize_available_filters | ||
add_available_filter "spent_on", :type => :date_past | ||||
|
r10743 | |||
|
r15788 | add_available_filter("project_id", | ||
:type => :list, :values => lambda { project_values } | ||||
) if project.nil? | ||||
if project && !project.leaf? | ||||
add_available_filter "subproject_id", | ||||
:type => :list_subprojects, | ||||
:values => lambda { subproject_values } | ||||
|
r10743 | end | ||
|
r15262 | |||
add_available_filter("issue_id", :type => :tree, :label => :label_issue) | ||||
|
r15356 | add_available_filter("issue.tracker_id", | ||
:type => :list, | ||||
:name => l("label_attribute_of_issue", :name => l(:field_tracker)), | ||||
|
r15788 | :values => lambda { Tracker.sorted.map {|t| [t.name, t.id.to_s]} }) | ||
|
r15356 | add_available_filter("issue.status_id", | ||
:type => :list, | ||||
:name => l("label_attribute_of_issue", :name => l(:field_status)), | ||||
|
r15788 | :values => lambda { IssueStatus.sorted.map {|s| [s.name, s.id.to_s]} }) | ||
|
r15264 | add_available_filter("issue.fixed_version_id", | ||
:type => :list, | ||||
:name => l("label_attribute_of_issue", :name => l(:field_fixed_version)), | ||||
|
r15788 | :values => lambda { fixed_version_values }) if project | ||
|
r10743 | |||
|
r11142 | add_available_filter("user_id", | ||
|
r15788 | :type => :list_optional, :values => lambda { author_values } | ||
) | ||||
|
r10743 | |||
|
r14244 | activities = (project ? project.activities : TimeEntryActivity.shared) | ||
|
r11142 | add_available_filter("activity_id", | ||
:type => :list, :values => activities.map {|a| [a.name, a.id.to_s]} | ||||
|
r15788 | ) | ||
|
r11142 | |||
add_available_filter "comments", :type => :text | ||||
add_available_filter "hours", :type => :float | ||||
|
r10743 | |||
|
r11687 | add_custom_fields_filters(TimeEntryCustomField) | ||
|
r15508 | add_associations_custom_fields_filters :project | ||
add_custom_fields_filters(issue_custom_fields, :issue) | ||||
add_associations_custom_fields_filters :user | ||||
|
r10740 | end | ||
|
r10745 | def available_columns | ||
return @available_columns if @available_columns | ||||
@available_columns = self.class.available_columns.dup | ||||
|
r12457 | @available_columns += TimeEntryCustomField.visible. | ||
map {|cf| QueryCustomFieldColumn.new(cf) } | ||||
|
r15508 | @available_columns += issue_custom_fields.visible. | ||
|
r15267 | map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf, :totalable => false) } | ||
|
r10745 | @available_columns | ||
end | ||||
|
r10740 | def default_columns_names | ||
|
r15660 | @default_columns_names ||= begin | ||
default_columns = [:spent_on, :user, :activity, :issue, :comments, :hours] | ||||
project.present? ? default_columns : [:project] | default_columns | ||||
end | ||||
|
r10740 | end | ||
|
r15267 | def default_totalable_names | ||
[:hours] | ||||
end | ||||
def base_scope | ||||
|
r15356 | TimeEntry.visible. | ||
joins(:project, :user). | ||||
joins("LEFT OUTER JOIN issues ON issues.id = time_entries.issue_id"). | ||||
where(statement) | ||||
|
r15267 | end | ||
|
r11812 | def results_scope(options={}) | ||
order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) | ||||
|
r15356 | base_scope. | ||
|
r11812 | order(order_option). | ||
|
r12139 | joins(joins_for_order_statement(order_option.join(','))). | ||
|
r13100 | includes(:activity). | ||
references(:activity) | ||||
|
r12139 | end | ||
|
r15267 | |||
# Returns sum of all the spent hours | ||||
def total_for_hours(scope) | ||||
map_total(scope.sum(:hours)) {|t| t.to_f.round(2)} | ||||
end | ||||
|
r15316 | |||
|
r15262 | def sql_for_issue_id_field(field, operator, value) | ||
case operator | ||||
when "=" | ||||
"#{TimeEntry.table_name}.issue_id = #{value.first.to_i}" | ||||
when "~" | ||||
issue = Issue.where(:id => value.first.to_i).first | ||||
if issue && (issue_ids = issue.self_and_descendants.pluck(:id)).any? | ||||
"#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})" | ||||
else | ||||
"1=0" | ||||
end | ||||
when "!*" | ||||
"#{TimeEntry.table_name}.issue_id IS NULL" | ||||
when "*" | ||||
"#{TimeEntry.table_name}.issue_id IS NOT NULL" | ||||
end | ||||
end | ||||
|
r12139 | |||
|
r15264 | def sql_for_issue_fixed_version_id_field(field, operator, value) | ||
issue_ids = Issue.where(:fixed_version_id => value.first.to_i).pluck(:id) | ||||
case operator | ||||
when "=" | ||||
if issue_ids.any? | ||||
"#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})" | ||||
else | ||||
"1=0" | ||||
end | ||||
when "!" | ||||
if issue_ids.any? | ||||
"#{TimeEntry.table_name}.issue_id NOT IN (#{issue_ids.join(',')})" | ||||
else | ||||
"1=1" | ||||
end | ||||
end | ||||
end | ||||
|
r12139 | def sql_for_activity_id_field(field, operator, value) | ||
condition_on_id = sql_for_field(field, operator, value, Enumeration.table_name, 'id') | ||||
condition_on_parent_id = sql_for_field(field, operator, value, Enumeration.table_name, 'parent_id') | ||||
ids = value.map(&:to_i).join(',') | ||||
table_name = Enumeration.table_name | ||||
if operator == '=' | ||||
"(#{table_name}.id IN (#{ids}) OR #{table_name}.parent_id IN (#{ids}))" | ||||
else | ||||
"(#{table_name}.id NOT IN (#{ids}) AND (#{table_name}.parent_id IS NULL OR #{table_name}.parent_id NOT IN (#{ids})))" | ||||
end | ||||
|
r11812 | end | ||
|
r15356 | def sql_for_issue_tracker_id_field(field, operator, value) | ||
sql_for_field("tracker_id", operator, value, Issue.table_name, "tracker_id") | ||||
end | ||||
def sql_for_issue_status_id_field(field, operator, value) | ||||
sql_for_field("status_id", operator, value, Issue.table_name, "status_id") | ||||
end | ||||
|
r10740 | # Accepts :from/:to params as shortcut filters | ||
def build_from_params(params) | ||||
super | ||||
if params[:from].present? && params[:to].present? | ||||
add_filter('spent_on', '><', [params[:from], params[:to]]) | ||||
elsif params[:from].present? | ||||
add_filter('spent_on', '>=', [params[:from]]) | ||||
elsif params[:to].present? | ||||
add_filter('spent_on', '<=', [params[:to]]) | ||||
end | ||||
self | ||||
end | ||||
|
r15356 | |||
def joins_for_order_statement(order_options) | ||||
joins = [super] | ||||
if order_options | ||||
if order_options.include?('issue_statuses') | ||||
joins << "LEFT OUTER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id" | ||||
end | ||||
if order_options.include?('trackers') | ||||
joins << "LEFT OUTER JOIN #{Tracker.table_name} ON #{Tracker.table_name}.id = #{Issue.table_name}.tracker_id" | ||||
end | ||||
end | ||||
joins.compact! | ||||
joins.any? ? joins.join(' ') : nil | ||||
end | ||||
|
r15508 | |||
def issue_custom_fields | ||||
if project | ||||
project.all_issue_custom_fields | ||||
else | ||||
IssueCustomField.where(:is_for_all => true) | ||||
end | ||||
end | ||||
|
r10740 | end | ||