summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarius Balteanu <marius.balteanu@zitec.com>2024-05-01 07:49:29 +0000
committerMarius Balteanu <marius.balteanu@zitec.com>2024-05-01 07:49:29 +0000
commit6fa12a95abaff2e5422588a69b8abf4f2ba28d74 (patch)
treef71618b87f919b961b0857a23851accd0c609eb1
parent61c7d539dccc3bec3da4d365443fb780bffd8ef0 (diff)
downloadredmine-6fa12a95abaff2e5422588a69b8abf4f2ba28d74.tar.gz
redmine-6fa12a95abaff2e5422588a69b8abf4f2ba28d74.zip
Adds estimated remaining hours issue query column calculated based on estimated time and done ratio (#37862).
Patch by Jens Krämer (@jkraemer). git-svn-id: https://svn.redmine.org/redmine/trunk@22800 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/helpers/queries_helper.rb4
-rw-r--r--app/models/issue_query.rb25
-rw-r--r--config/locales/de.yml1
-rw-r--r--config/locales/en.yml1
-rw-r--r--test/unit/query_test.rb28
5 files changed, 56 insertions, 3 deletions
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index b9234bd1f..160473ae8 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -190,7 +190,7 @@ module QueriesHelper
def total_tag(column, value)
label = content_tag('span', "#{column.caption}:")
value =
- if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours].include? column.name
+ if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours, :estimated_remaining_hours].include? column.name
format_hours(value)
else
format_object(value)
@@ -269,7 +269,7 @@ module QueriesHelper
'span',
value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
:class => value.css_classes_for(item))
- when :hours, :estimated_hours, :total_estimated_hours
+ when :hours, :estimated_hours, :total_estimated_hours, :estimated_remaining_hours
format_hours(value)
when :spent_hours
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb
index 91678874e..6a2771343 100644
--- a/app/models/issue_query.rb
+++ b/app/models/issue_query.rb
@@ -18,6 +18,22 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueQuery < Query
+ class EstimatedRemainingHoursColumn < QueryColumn
+ COLUMN_SQL = Arel.sql("COALESCE(#{Issue.table_name}.estimated_hours, 0) * (100 - COALESCE(#{Issue.table_name}.done_ratio, 0)) / 100")
+
+ def initialize
+ super :estimated_remaining_hours, totalable: true, sortable: COLUMN_SQL
+ end
+
+ def value(object)
+ (object.estimated_hours || 0) * (100 - (object.done_ratio || 0)) / 100
+ end
+
+ def value_object(object)
+ value(object)
+ end
+ end
+
self.queried_class = Issue
self.view_permission = :view_issues
@@ -50,6 +66,7 @@ class IssueQuery < Query
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true),
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours",
:totalable => true),
+ EstimatedRemainingHoursColumn.new,
QueryColumn.new(
:total_estimated_hours,
:sortable =>
@@ -330,7 +347,9 @@ class IssueQuery < Query
end
disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.delete_suffix('_id')}
- disabled_fields << "total_estimated_hours" if disabled_fields.include?("estimated_hours")
+ if disabled_fields.include?("estimated_hours")
+ disabled_fields += %w[total_estimated_hours estimated_remaining_hours]
+ end
@available_columns.reject! do |column|
disabled_fields.include?(column.name.to_s)
end
@@ -370,6 +389,10 @@ class IssueQuery < Query
map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
end
+ def total_for_estimated_remaining_hours(scope)
+ map_total(scope.sum(EstimatedRemainingHoursColumn::COLUMN_SQL)) {|t| t.to_f.round(2)}
+ end
+
# Returns sum of all the issue's time entries hours
def total_for_spent_hours(scope)
total = scope.joins(:time_entries).
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 086c2979c..f85345122 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -399,6 +399,7 @@ de:
field_watcher: Beobachter
field_default_assigned_to: Standardbearbeiter
field_unique_id: Eindeutige ID
+ field_estimated_remaining_hours: Geschätzter verbleibender Aufwand
general_csv_decimal_separator: ','
general_csv_encoding: ISO-8859-1
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ecda509ec..3ae06f745 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -418,6 +418,7 @@ en:
field_default_project_query: Default project query
field_default_time_entry_activity: Default spent time activity
field_any_searchable: Any searchable text
+ field_estimated_remaining_hours: Estimated remaining time
setting_app_title: Application title
setting_welcome_text: Welcome text
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb
index 78205013a..8ea079764 100644
--- a/test/unit/query_test.rb
+++ b/test/unit/query_test.rb
@@ -2388,6 +2388,11 @@ class QueryTest < ActiveSupport::TestCase
assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
end
+ def test_available_totalable_columns_should_include_estimated_remaining_hours
+ q = IssueQuery.new
+ assert_include :estimated_remaining_hours, q.available_totalable_columns.map(&:name)
+ end
+
def test_available_totalable_columns_should_include_spent_hours
User.current = User.find(1)
@@ -2461,6 +2466,29 @@ class QueryTest < ActiveSupport::TestCase
)
end
+ def test_total_for_estimated_remaining_hours
+ Issue.delete_all
+ Issue.generate!(:estimated_hours => 5.5, :done_ratio => 50)
+ Issue.generate!(:estimated_hours => 1.1, :done_ratio => 100)
+ Issue.generate!
+
+ q = IssueQuery.new
+ assert_equal 2.75, q.total_for(:estimated_remaining_hours)
+ end
+
+ def test_total_by_group_for_estimated_remaining_hours
+ Issue.delete_all
+ Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2, :done_ratio => 50)
+ Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3, :done_ratio => 100)
+ Issue.generate!(:estimated_hours => 3.5, :done_ratio => 0)
+
+ q = IssueQuery.new(:group_by => 'assigned_to')
+ assert_equal(
+ {nil => 3.5, User.find(2) => 2.75, User.find(3) => 0},
+ q.total_by_group_for(:estimated_remaining_hours)
+ )
+ end
+
def test_total_for_spent_hours
TimeEntry.delete_all
TimeEntry.generate!(:hours => 5.5)