diff --git a/app/models/issue.rb b/app/models/issue.rb
index dddb9df6b..893f7ec38 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1848,26 +1848,29 @@ class Issue < ActiveRecord::Base
 
       if p.done_ratio_derived?
         # done ratio = average ratio of children weighted with their total estimated hours
-        unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
-          children = p.children.to_a
-          if children.any?
-            child_with_total_estimated_hours = children.select {|c| c.total_estimated_hours.to_f > 0.0}
-            if child_with_total_estimated_hours.any?
-              average = Rational(
-                child_with_total_estimated_hours.sum(&:total_estimated_hours).to_s,
-                child_with_total_estimated_hours.count
-              )
-            else
-              average = Rational(1)
-            end
-            done = children.sum do |c|
-              estimated = Rational(c.total_estimated_hours.to_f.to_s)
-              estimated = average unless estimated > 0.0
-              ratio = c.closed? ? 100 : (c.done_ratio || 0)
-              estimated * ratio
-            end
-            progress = Rational(done, average * children.count)
-            p.done_ratio = progress.floor
+        children = p.children.to_a
+        if children.any?
+          child_with_total_estimated_hours = children.select {|c| c.total_estimated_hours.to_f > 0.0}
+          if child_with_total_estimated_hours.any?
+            average = Rational(
+              child_with_total_estimated_hours.sum(&:total_estimated_hours).to_s,
+              child_with_total_estimated_hours.count
+            )
+          else
+            average = Rational(1)
+          end
+          done = children.sum do |c|
+            estimated = Rational(c.total_estimated_hours.to_f.to_s)
+            estimated = average unless estimated > 0.0
+            ratio = c.closed? ? 100 : (c.done_ratio || 0)
+            estimated * ratio
+          end
+          progress = Rational(done, average * children.count)
+          p.done_ratio = progress.floor
+
+          if Issue.use_status_for_done_ratio?
+            calculate = Redmine::ParentIssueDone.new(p, progress.floor)
+            p.status = calculate.by_status
           end
         end
       end
diff --git a/lib/redmine/parent_issue_done.rb b/lib/redmine/parent_issue_done.rb
new file mode 100644
index 000000000..fc73e5113
--- /dev/null
+++ b/lib/redmine/parent_issue_done.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006-  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.
+
+module Redmine
+  class ParentIssueDone
+    def initialize(parent_issue, average_done_ratio)
+      self.parent = parent_issue
+      self.average = average_done_ratio
+    end
+
+    def by_status
+      return exact_status if exact_status.presence
+
+      parent.status = closest_status
+    end
+
+    private
+
+    attr_accessor :parent, :average
+
+    def exact_status
+      exact = parent_statuses.select { |status| status.default_done_ratio == average }
+      exact.uniq.first
+    end
+
+    def closest_status
+      # Compare the closest status candidates
+      candidates = []
+      candidates << parent_statuses[closest_index - 1] if closest_index > 0
+      if closest_index < parent_statuses.length
+        candidates << parent_statuses[closest_index]
+        candidates << parent_statuses[closest_index + 1]
+      end
+      # Find the closest status
+      candidates.min_by { |status|  (status.default_done_ratio - average).abs }
+    end
+
+    def closest_index
+      @closest_index ||= closest_index_search
+    end
+
+    def closest_index_search
+      return if parent_statuses.empty?
+
+      low = 0
+      high = parent_statuses.length - 1
+
+      # Binary search to find the closest index
+      while low < high
+        mid = (low + high) / 2
+
+        ratio = parent_statuses[mid].default_done_ratio
+        if ratio < average
+          low = mid + 1
+        else
+          high = mid
+        end
+      end
+      # Now low is the index of the first element greater than or equal to average
+      low
+    end
+
+    def parent_statuses
+      @parent_statuses ||= trackers_issue_statuses
+    end
+
+    delegate :tracker, to: :parent
+
+    def trackers_issue_statuses
+      return unless tracker
+
+      # Make sure nil values of default_done_ratio are ignored
+      tracker
+        .issue_statuses
+        .select(&:default_done_ratio)
+        .sort_by(&:default_done_ratio)
+    end
+  end
+end
diff --git a/test/unit/issue_subtasking_test.rb b/test/unit/issue_subtasking_test.rb
index 86e9e7708..fe5059e86 100644
--- a/test/unit/issue_subtasking_test.rb
+++ b/test/unit/issue_subtasking_test.rb
@@ -272,7 +272,7 @@ class IssueSubtaskingTest < ActiveSupport::TestCase
     end
   end
 
-  def test_done_ratio_of_parent_should_reflect_children
+def test_done_ratio_of_parent_should_reflect_children
     root = Issue.generate!
     child1 = root.generate_child!
     child2 = child1.generate_child!
@@ -282,14 +282,57 @@ class IssueSubtaskingTest < ActiveSupport::TestCase
     assert_equal 0, child2.done_ratio
 
     with_settings :issue_done_ratio => 'issue_status' do
+      ratio = 0
+
+      IssueStatus.all.each do |status|
+        status.update_attribute :default_done_ratio, ratio
+        ratio += 10
+      end
+
       status = IssueStatus.find(4)
-      status.update_attribute :default_done_ratio, 50
       child1.reload
       child1.update_attribute :status, status
 
-      assert_equal 50, child1.done_ratio
+      assert_equal 30, child1.done_ratio
+      root.reload
+      assert_equal 30, root.done_ratio
+      assert_equal status.name, root.status.name
+      assert_equal 0, child2.done_ratio
+    end
+  end
+
+  def test_done_ratio_of_parent_should_map_closest_done_ratio_to_children_average
+    root = Issue.generate!
+    child1 = root.generate_child!
+    child2 = root.generate_child!
+    child3 = root.generate_child!
+
+    assert_equal 0, root.done_ratio
+    assert_equal 0, child1.done_ratio
+    assert_equal 0, child2.done_ratio
+
+    with_settings :issue_done_ratio => 'issue_status' do
+      ratio = 0
+
+      IssueStatus.all.each do |status|
+        status.update_attribute :default_done_ratio, ratio
+        ratio += 10
+      end
+
+      id = 1
+
+      root.children.each do |child|
+        status = IssueStatus.find(id)
+        child.reload
+        child.update_attribute :status, status
+        assert_equal status.default_done_ratio, child.done_ratio
+        id += 1
+      end
+
       root.reload
-      assert_equal 50, root.done_ratio
+      expected_root_status = IssueStatus.find(2)
+      assert_equal expected_root_status.default_done_ratio, root.done_ratio
+      assert_equal expected_root_status.name, root.status.name
     end
   end
 
diff --git a/test/unit/lib/redmine/parent_issue_done_test.rb b/test/unit/lib/redmine/parent_issue_done_test.rb
new file mode 100644
index 000000000..a1da5198e
--- /dev/null
+++ b/test/unit/lib/redmine/parent_issue_done_test.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006-  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.
+
+require_relative '../../../test_helper'
+
+class Redmine::ParentIssueDoneTest < ActiveSupport::TestCase
+  fixtures :projects, :users, :roles, :members, :member_roles,
+          :trackers, :projects_trackers,
+          :issue_statuses, :issue_categories, :enumerations,
+          :issues,
+          :enabled_modules,
+          :workflows
+
+  def setup
+    User.current = nil
+  end
+
+  def test_should_ignore_nil_default_done_ratio_for_tracker_issue_statuses
+    issue = Issue.generate!
+    average_done_ratio = 0
+    status = IssueStatus.find(4)
+    ratio = 60
+    status.update_attribute :default_done_ratio, ratio
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    assert_equal 1, parent_issue_done.send(:trackers_issue_statuses).count
+    assert_equal ratio, parent_issue_done.send(:trackers_issue_statuses).first.default_done_ratio
+  end
+
+  def test_should_sort_tracker_issue_status_by_ascending_default_done_ratio
+    issue = Issue.generate!
+    average_done_ratio = 0
+
+    ratio = 0
+
+    IssueStatus.all.each do |status|
+      status.update_attribute :default_done_ratio, ratio
+      ratio += 10
+    end
+
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    sorted_ratios = parent_issue_done.send(:trackers_issue_statuses).map(&:default_done_ratio)
+    assert_equal [0, 10, 20, 30, 40, 50], sorted_ratios
+  end
+
+  def test_should_early_exit_closest_index_if_parent_statuses_are_empty
+    issue = Issue.generate!
+    average_done_ratio = 0
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    assert_nil parent_issue_done.send(:closest_index_search)
+  end
+
+  def test_should_find_closest_index
+    issue = Issue.generate!
+    average_done_ratio = 25.34
+
+    ratio = 0
+
+    IssueStatus.all.each do |status|
+      status.update_attribute :default_done_ratio, ratio
+      ratio += 10
+    end
+
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    index = parent_issue_done.send(:closest_index_search)
+    assert_equal 3, index
+  end
+
+  def test_should_find_closest_status
+    issue = Issue.generate!
+    average_done_ratio = 25.34
+
+    ratio = 0
+
+    IssueStatus.all.each do |status|
+      status.update_attribute :default_done_ratio, ratio
+      ratio += 10
+    end
+
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    closest_status = parent_issue_done.send(:closest_status)
+    status = IssueStatus.find(4)
+    assert_equal status, closest_status
+  end
+
+  def test_should_find_exact_status_if_any
+    issue = Issue.generate!
+    average_done_ratio = 20
+
+    ratio = 0
+
+    IssueStatus.all.each do |status|
+      status.update_attribute :default_done_ratio, ratio
+      ratio += 10
+    end
+
+    parent_issue_done = Redmine::ParentIssueDone.new(issue, average_done_ratio)
+    exact_status = parent_issue_done.send(:exact_status)
+    status = IssueStatus.find(3)
+    assert_equal status, exact_status
+  end
+end
