- # Redmine - project management software
- # Copyright (C) 2006-2017 Jean-Philippe Lang
- #
- # 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
- self.view_permission = :view_time_entries
-
- self.available_columns = [
- QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
- QueryColumn.new(:spent_on, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :default_order => 'desc', :groupable => true),
- QueryColumn.new(:created_on, :sortable => "#{TimeEntry.table_name}.created_on", :default_order => 'desc'),
- QueryColumn.new(:tweek, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :caption => :label_week),
- QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement}),
- 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"),
- QueryAssociationColumn.new(:issue, :tracker, :caption => :field_tracker, :sortable => "#{Tracker.table_name}.position"),
- QueryAssociationColumn.new(:issue, :status, :caption => :field_status, :sortable => "#{IssueStatus.table_name}.position"),
- QueryAssociationColumn.new(:issue, :category, :caption => :field_category, :sortable => "#{IssueCategory.table_name}.name"),
- QueryColumn.new(:comments),
- QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true),
- ]
-
- def initialize(attributes=nil, *args)
- super attributes
- self.filters ||= { 'spent_on' => {:operator => "*", :values => []} }
- end
-
- def initialize_available_filters
- add_available_filter "spent_on", :type => :date_past
-
- 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 }
- end
-
- add_available_filter("issue_id", :type => :tree, :label => :label_issue)
- add_available_filter("issue.tracker_id",
- :type => :list,
- :name => l("label_attribute_of_issue", :name => l(:field_tracker)),
- :values => lambda { trackers.map {|t| [t.name, t.id.to_s]} })
- add_available_filter("issue.status_id",
- :type => :list,
- :name => l("label_attribute_of_issue", :name => l(:field_status)),
- :values => lambda { issue_statuses_values })
- add_available_filter("issue.fixed_version_id",
- :type => :list,
- :name => l("label_attribute_of_issue", :name => l(:field_fixed_version)),
- :values => lambda { fixed_version_values })
- add_available_filter "issue.category_id",
- :type => :list_optional,
- :name => l("label_attribute_of_issue", :name => l(:field_category)),
- :values => lambda { project.issue_categories.collect{|s| [s.name, s.id.to_s] } } if project
-
- add_available_filter("user_id",
- :type => :list_optional, :values => lambda { author_values }
- )
-
- add_available_filter("author_id",
- :type => :list_optional, :values => lambda { author_values }
- )
-
- activities = (project ? project.activities : TimeEntryActivity.shared)
- add_available_filter("activity_id",
- :type => :list, :values => activities.map {|a| [a.name, a.id.to_s]}
- )
-
- add_available_filter("project.status",
- :type => :list,
- :name => l(:label_attribute_of_project, :name => l(:field_status)),
- :values => lambda { project_statuses_values }
- ) if project.nil? || !project.leaf?
-
- add_available_filter "comments", :type => :text
- add_available_filter "hours", :type => :float
-
- add_custom_fields_filters(TimeEntryCustomField)
- add_associations_custom_fields_filters :project
- add_custom_fields_filters(issue_custom_fields, :issue)
- add_associations_custom_fields_filters :user
- end
-
- def available_columns
- return @available_columns if @available_columns
- @available_columns = self.class.available_columns.dup
- @available_columns += TimeEntryCustomField.visible.
- map {|cf| QueryCustomFieldColumn.new(cf) }
- @available_columns += issue_custom_fields.visible.
- map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf, :totalable => false) }
- @available_columns += ProjectCustomField.visible.
- map {|cf| QueryAssociationCustomFieldColumn.new(:project, cf) }
- @available_columns
- end
-
- def default_columns_names
- @default_columns_names ||= begin
- default_columns = Setting.time_entry_list_defaults.symbolize_keys[:column_names].map(&:to_sym)
- project.present? ? default_columns : [:project] | default_columns
- end
- end
-
- def default_totalable_names
- Setting.time_entry_list_defaults.symbolize_keys[:totalable_names].map(&:to_sym)
- end
-
- def default_sort_criteria
- [['spent_on', 'desc']]
- end
-
- # If a filter against a single issue is set, returns its id, otherwise nil.
- def filtered_issue_id
- if value_for('issue_id').to_s =~ /\A(\d+)\z/
- $1
- end
- end
-
- def base_scope
- TimeEntry.visible.
- joins(:project, :user).
- includes(:activity).
- references(:activity).
- left_join_issue.
- where(statement)
- end
-
- def results_scope(options={})
- order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?)
-
- base_scope.
- order(order_option).
- joins(joins_for_order_statement(order_option.join(',')))
- end
-
- # Returns sum of all the spent hours
- def total_for_hours(scope)
- map_total(scope.sum(:hours)) {|t| t.to_f.round(2)}
- end
-
- 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
-
- def sql_for_issue_fixed_version_id_field(field, operator, value)
- issue_ids = Issue.where(:fixed_version_id => value.map(&: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
-
- 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
- end
-
- 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
-
- def sql_for_issue_category_id_field(field, operator, value)
- sql_for_field("category_id", operator, value, Issue.table_name, "category_id")
- end
-
- def sql_for_project_status_field(field, operator, value, options={})
- sql_for_field(field, operator, value, Project.table_name, "status")
- end
-
- # Accepts :from/:to params as shortcut filters
- def build_from_params(params, defaults={})
- 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
-
- 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
- if order_options.include?('issue_categories')
- joins << "LEFT OUTER JOIN #{IssueCategory.table_name} ON #{IssueCategory.table_name}.id = #{Issue.table_name}.category_id"
- end
- end
-
- joins.compact!
- joins.any? ? joins.join(' ') : nil
- end
- end
|