Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

time_entry_query.rb 9.9KB

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