Project

General

Profile

Patch #37862 » 0001-estimated-remaining-hours-issue-query-column.patch

Jens Krämer, 2022-10-30 03:51

View differences:

app/helpers/queries_helper.rb
186 186
  def total_tag(column, value)
187 187
    label = content_tag('span', "#{column.caption}:")
188 188
    value =
189
      if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours].include? column.name
189
      if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours, :estimated_remaining_hours].include? column.name
190 190
        format_hours(value)
191 191
      else
192 192
        format_object(value)
......
265 265
          'span',
266 266
          value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
267 267
          :class => value.css_classes_for(item))
268
      when :hours, :estimated_hours, :total_estimated_hours
268
      when :hours, :estimated_hours, :total_estimated_hours, :estimated_remaining_hours
269 269
        format_hours(value)
270 270
      when :spent_hours
271 271
        link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
app/models/issue_query.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
class IssueQuery < Query
21
  class EstimatedRemainingHoursColumn < QueryColumn
22
    COLUMN_SQL = Arel.sql("COALESCE(#{Issue.table_name}.estimated_hours, 0) * (100 - COALESCE(#{Issue.table_name}.done_ratio, 0)) / 100")
23

  
24
    def initialize
25
      super :estimated_remaining_hours, totalable: true, sortable: COLUMN_SQL
26
    end
27

  
28
    def value(object)
29
      (object.estimated_hours || 0) * (100 - (object.done_ratio || 0)) / 100
30
    end
31

  
32
    def value_object(object)
33
      value(object)
34
    end
35
  end
36

  
21 37
  self.queried_class = Issue
22 38
  self.view_permission = :view_issues
23 39

  
......
49 65
    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true),
50 66
    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours",
51 67
                    :totalable => true),
68
    EstimatedRemainingHoursColumn.new,
52 69
    QueryColumn.new(
53 70
      :total_estimated_hours,
54 71
      :sortable =>
......
324 341
    end
325 342

  
326 343
    disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
327
    disabled_fields << "total_estimated_hours" if disabled_fields.include?("estimated_hours")
344
    if disabled_fields.include?("estimated_hours")
345
      disabled_fields += %w[total_estimated_hours estimated_remaining_hours]
346
    end
328 347
    @available_columns.reject! do |column|
329 348
      disabled_fields.include?(column.name.to_s)
330 349
    end
......
364 383
    map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
365 384
  end
366 385

  
386
  def total_for_estimated_remaining_hours(scope)
387
    map_total(scope.sum(EstimatedRemainingHoursColumn::COLUMN_SQL)) {|t| t.to_f.round(2)}
388
  end
389

  
367 390
  # Returns sum of all the issue's time entries hours
368 391
  def total_for_spent_hours(scope)
369 392
    total = scope.joins(:time_entries).
config/locales/de.yml
398 398
  field_watcher: Beobachter
399 399
  field_default_assigned_to: Standardbearbeiter
400 400
  field_unique_id: Eindeutige ID
401
  field_estimated_remaining_hours: Geschätzter verbleibender Aufwand
401 402

  
402 403
  general_csv_decimal_separator: ','
403 404
  general_csv_encoding: ISO-8859-1
config/locales/en.yml
418 418
  field_default_issue_query: Default issue query
419 419
  field_default_project_query: Default project query
420 420
  field_default_time_entry_activity: Default spent time activity
421
  field_estimated_remaining_hours: Estimated remaining time
421 422

  
422 423
  setting_app_title: Application title
423 424
  setting_welcome_text: Welcome text
test/unit/query_test.rb
2029 2029
    assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
2030 2030
  end
2031 2031

  
2032
  def test_available_totalable_columns_should_include_estimated_remaining_hours
2033
    q = IssueQuery.new
2034
    assert_include :estimated_remaining_hours, q.available_totalable_columns.map(&:name)
2035
  end
2036

  
2032 2037
  def test_available_totalable_columns_should_include_spent_hours
2033 2038
    User.current = User.find(1)
2034 2039

  
......
2102 2107
    )
2103 2108
  end
2104 2109

  
2110
  def test_total_for_estimated_remaining_hours
2111
    Issue.delete_all
2112
    Issue.generate!(:estimated_hours => 5.5, :done_ratio => 50)
2113
    Issue.generate!(:estimated_hours => 1.1, :done_ratio => 100)
2114
    Issue.generate!
2115

  
2116
    q = IssueQuery.new
2117
    assert_equal 2.75, q.total_for(:estimated_remaining_hours)
2118
  end
2119

  
2120
  def test_total_by_group_for_estimated_remaining_hours
2121
    Issue.delete_all
2122
    Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2, :done_ratio => 50)
2123
    Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3, :done_ratio => 100)
2124
    Issue.generate!(:estimated_hours => 3.5, :done_ratio => 0)
2125

  
2126
    q = IssueQuery.new(:group_by => 'assigned_to')
2127
    assert_equal(
2128
      {nil => 3.5, User.find(2) => 2.75, User.find(3) => 0},
2129
      q.total_by_group_for(:estimated_remaining_hours)
2130
    )
2131
  end
2132

  
2105 2133
  def test_total_for_spent_hours
2106 2134
    TimeEntry.delete_all
2107 2135
    TimeEntry.generate!(:hours => 5.5)
    (1-1/1)