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.

time_entry_query.rb 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # Redmine - project management software
  2. # Copyright (C) 2006-2017 Jean-Philippe Lang
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  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
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. class TimeEntryQuery < Query
  18. self.queried_class = TimeEntry
  19. self.view_permission = :view_time_entries
  20. self.available_columns = [
  21. QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
  22. QueryColumn.new(:spent_on, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :default_order => 'desc', :groupable => true),
  23. QueryColumn.new(:tweek, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :caption => l(:label_week)),
  24. QueryColumn.new(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
  25. QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true),
  26. QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"),
  27. QueryAssociationColumn.new(:issue, :tracker, :caption => :field_tracker, :sortable => "#{Tracker.table_name}.position"),
  28. QueryAssociationColumn.new(:issue, :status, :caption => :field_status, :sortable => "#{IssueStatus.table_name}.position"),
  29. QueryColumn.new(:comments),
  30. QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true),
  31. ]
  32. def initialize(attributes=nil, *args)
  33. super attributes
  34. self.filters ||= {}
  35. add_filter('spent_on', '*') unless filters.present?
  36. end
  37. def initialize_available_filters
  38. add_available_filter "spent_on", :type => :date_past
  39. add_available_filter("project_id",
  40. :type => :list, :values => lambda { project_values }
  41. ) if project.nil?
  42. if project && !project.leaf?
  43. add_available_filter "subproject_id",
  44. :type => :list_subprojects,
  45. :values => lambda { subproject_values }
  46. end
  47. add_available_filter("issue_id", :type => :tree, :label => :label_issue)
  48. add_available_filter("issue.tracker_id",
  49. :type => :list,
  50. :name => l("label_attribute_of_issue", :name => l(:field_tracker)),
  51. :values => lambda { trackers.map {|t| [t.name, t.id.to_s]} })
  52. add_available_filter("issue.status_id",
  53. :type => :list,
  54. :name => l("label_attribute_of_issue", :name => l(:field_status)),
  55. :values => lambda { issue_statuses_values })
  56. add_available_filter("issue.fixed_version_id",
  57. :type => :list,
  58. :name => l("label_attribute_of_issue", :name => l(:field_fixed_version)),
  59. :values => lambda { fixed_version_values }) if project
  60. add_available_filter("user_id",
  61. :type => :list_optional, :values => lambda { author_values }
  62. )
  63. activities = (project ? project.activities : TimeEntryActivity.shared)
  64. add_available_filter("activity_id",
  65. :type => :list, :values => activities.map {|a| [a.name, a.id.to_s]}
  66. )
  67. add_available_filter "comments", :type => :text
  68. add_available_filter "hours", :type => :float
  69. add_custom_fields_filters(TimeEntryCustomField)
  70. add_associations_custom_fields_filters :project
  71. add_custom_fields_filters(issue_custom_fields, :issue)
  72. add_associations_custom_fields_filters :user
  73. end
  74. def available_columns
  75. return @available_columns if @available_columns
  76. @available_columns = self.class.available_columns.dup
  77. @available_columns += TimeEntryCustomField.visible.
  78. map {|cf| QueryCustomFieldColumn.new(cf) }
  79. @available_columns += issue_custom_fields.visible.
  80. map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf, :totalable => false) }
  81. @available_columns += ProjectCustomField.visible.
  82. map {|cf| QueryAssociationCustomFieldColumn.new(:project, cf) }
  83. @available_columns
  84. end
  85. def default_columns_names
  86. @default_columns_names ||= begin
  87. default_columns = [:spent_on, :user, :activity, :issue, :comments, :hours]
  88. project.present? ? default_columns : [:project] | default_columns
  89. end
  90. end
  91. def default_totalable_names
  92. [:hours]
  93. end
  94. def default_sort_criteria
  95. [['spent_on', 'desc']]
  96. end
  97. def base_scope
  98. TimeEntry.visible.
  99. joins(:project, :user).
  100. includes(:activity).
  101. references(:activity).
  102. left_join_issue.
  103. where(statement)
  104. end
  105. def results_scope(options={})
  106. order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?)
  107. base_scope.
  108. order(order_option).
  109. joins(joins_for_order_statement(order_option.join(',')))
  110. end
  111. # Returns sum of all the spent hours
  112. def total_for_hours(scope)
  113. map_total(scope.sum(:hours)) {|t| t.to_f.round(2)}
  114. end
  115. def sql_for_issue_id_field(field, operator, value)
  116. case operator
  117. when "="
  118. "#{TimeEntry.table_name}.issue_id = #{value.first.to_i}"
  119. when "~"
  120. issue = Issue.where(:id => value.first.to_i).first
  121. if issue && (issue_ids = issue.self_and_descendants.pluck(:id)).any?
  122. "#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})"
  123. else
  124. "1=0"
  125. end
  126. when "!*"
  127. "#{TimeEntry.table_name}.issue_id IS NULL"
  128. when "*"
  129. "#{TimeEntry.table_name}.issue_id IS NOT NULL"
  130. end
  131. end
  132. def sql_for_issue_fixed_version_id_field(field, operator, value)
  133. issue_ids = Issue.where(:fixed_version_id => value.first.to_i).pluck(:id)
  134. case operator
  135. when "="
  136. if issue_ids.any?
  137. "#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})"
  138. else
  139. "1=0"
  140. end
  141. when "!"
  142. if issue_ids.any?
  143. "#{TimeEntry.table_name}.issue_id NOT IN (#{issue_ids.join(',')})"
  144. else
  145. "1=1"
  146. end
  147. end
  148. end
  149. def sql_for_activity_id_field(field, operator, value)
  150. condition_on_id = sql_for_field(field, operator, value, Enumeration.table_name, 'id')
  151. condition_on_parent_id = sql_for_field(field, operator, value, Enumeration.table_name, 'parent_id')
  152. ids = value.map(&:to_i).join(',')
  153. table_name = Enumeration.table_name
  154. if operator == '='
  155. "(#{table_name}.id IN (#{ids}) OR #{table_name}.parent_id IN (#{ids}))"
  156. else
  157. "(#{table_name}.id NOT IN (#{ids}) AND (#{table_name}.parent_id IS NULL OR #{table_name}.parent_id NOT IN (#{ids})))"
  158. end
  159. end
  160. def sql_for_issue_tracker_id_field(field, operator, value)
  161. sql_for_field("tracker_id", operator, value, Issue.table_name, "tracker_id")
  162. end
  163. def sql_for_issue_status_id_field(field, operator, value)
  164. sql_for_field("status_id", operator, value, Issue.table_name, "status_id")
  165. end
  166. # Accepts :from/:to params as shortcut filters
  167. def build_from_params(params, defaults={})
  168. super
  169. if params[:from].present? && params[:to].present?
  170. add_filter('spent_on', '><', [params[:from], params[:to]])
  171. elsif params[:from].present?
  172. add_filter('spent_on', '>=', [params[:from]])
  173. elsif params[:to].present?
  174. add_filter('spent_on', '<=', [params[:to]])
  175. end
  176. self
  177. end
  178. def joins_for_order_statement(order_options)
  179. joins = [super]
  180. if order_options
  181. if order_options.include?('issue_statuses')
  182. joins << "LEFT OUTER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id"
  183. end
  184. if order_options.include?('trackers')
  185. joins << "LEFT OUTER JOIN #{Tracker.table_name} ON #{Tracker.table_name}.id = #{Issue.table_name}.tracker_id"
  186. end
  187. end
  188. joins.compact!
  189. joins.any? ? joins.join(' ') : nil
  190. end
  191. end