diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 1e553ff..727aae9 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -265,6 +265,18 @@ module IssuesHelper
     end
   end
 
+  def issue_remaining_hours_details(issue)
+    if issue.total_remaining_hours.present?
+      if issue.total_remaining_hours == issue.remaining_hours
+        l_hours_short(issue.remaining_hours)
+      else
+        s = issue.remaining_hours.present? ? l_hours_short(issue.remaining_hours) : ""
+        s << " (#{l(:label_total)}: #{l_hours_short(issue.total_remaining_hours)})"
+        s.html_safe
+      end
+    end
+  end
+
   def issue_spent_hours_details(issue)
     if issue.total_spent_hours > 0
       path = project_time_entries_path(issue.project, :issue_id => "~#{issue.id}")
@@ -540,7 +552,7 @@ module IssuesHelper
         value = find_name_by_reflection(field, detail.value)
         old_value = find_name_by_reflection(field, detail.old_value)
 
-      when 'estimated_hours'
+      when 'estimated_hours', 'remaining_hours'
         value = l_hours_short(detail.value.to_f) unless detail.value.blank?
         old_value = l_hours_short(detail.old_value.to_f) unless detail.old_value.blank?
 
diff --git a/app/models/issue.rb b/app/models/issue.rb
index dddb9df..deadcd4 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -70,6 +70,7 @@ class Issue < ActiveRecord::Base
   validates_length_of :subject, :maximum => 255
   validates_inclusion_of :done_ratio, :in => 0..100
   validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
+  validates :remaining_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
   validates :start_date, :date => true
   validates :due_date, :date => true
   validate :validate_issue, :validate_required_fields, :validate_permissions
@@ -109,8 +110,9 @@ class Issue < ActiveRecord::Base
 
   before_validation :default_assign, on: :create
   before_validation :clear_disabled_fields
+  before_validation :update_remaining_hours_from_estimated_hours
   before_save :close_duplicates, :update_done_ratio_from_issue_status,
-              :force_updated_on_change, :update_closed_on
+              :force_updated_on_change, :update_closed_on, :update_remaining_hours
   after_save do |issue|
     if !issue.saved_change_to_id? && issue.saved_change_to_project_id?
       issue.send :after_project_change
@@ -270,6 +272,7 @@ class Issue < ActiveRecord::Base
     @spent_hours = nil
     @total_spent_hours = nil
     @total_estimated_hours = nil
+    @total_remaining_hours = nil
     @last_updated_by = nil
     @last_notes = nil
     base_reload(*args)
@@ -486,6 +489,13 @@ class Issue < ActiveRecord::Base
     write_attribute :estimated_hours, (h.is_a?(String) ? (h.to_hours || h) : h)
   end
 
+  def remaining_hours=(h)
+    h = h.is_a?(String) ? h.to_hours : h
+    # remaining time cannot be less than zero
+    h = 0 if !h.nil? && h < 0
+    write_attribute :remaining_hours, h
+  end
+
   safe_attributes(
     'project_id',
     'tracker_id',
@@ -500,6 +510,7 @@ class Issue < ActiveRecord::Base
     'due_date',
     'done_ratio',
     'estimated_hours',
+    'remaining_hours',
     'custom_field_values',
     'custom_fields',
     'lock_version',
@@ -1165,6 +1176,14 @@ class Issue < ActiveRecord::Base
     end
   end
 
+  def total_remaining_hours
+    if leaf?
+      remaining_hours
+    else
+      @total_remaining_hours ||= self_and_descendants.sum(:remaining_hours)
+    end
+  end
+
   def relations
     @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
   end
@@ -2006,6 +2025,13 @@ class Issue < ActiveRecord::Base
     end
   end
 
+  # Callback for setting remaining time to zero when the issue is closed.
+  def update_remaining_hours
+    if closing? && safe_attribute?('remaining_hours') && self.remaining_hours.to_f > 0
+      self.remaining_hours = 0
+    end
+  end
+
   # Saves the changes in a Journal
   # Called after_save
   def create_journal
@@ -2095,4 +2121,10 @@ class Issue < ActiveRecord::Base
     roles = user.admin ? Role.all.to_a : user.roles_for_project(project)
     roles.select(&:consider_workflow?)
   end
+
+  def update_remaining_hours_from_estimated_hours
+    if self.remaining_hours.blank? && self.estimated_hours
+      self.remaining_hours = self.estimated_hours
+    end
+  end
 end
diff --git a/app/models/issue_import.rb b/app/models/issue_import.rb
index d7e0919..3ca2a16 100644
--- a/app/models/issue_import.rb
+++ b/app/models/issue_import.rb
@@ -213,6 +213,9 @@ class IssueImport < Import
     if estimated_hours = row_value(row, 'estimated_hours')
       attributes['estimated_hours'] = estimated_hours
     end
+    if remaining_hours = row_value(row, 'remaining_hours')
+      attributes['remaining_hours'] = remaining_hours
+    end
     if done_ratio = row_value(row, 'done_ratio')
       attributes['done_ratio'] = done_ratio
     end
diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb
index 49483fd..737ea7a 100644
--- a/app/models/issue_query.rb
+++ b/app/models/issue_query.rb
@@ -65,6 +65,8 @@ 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),
+    QueryColumn.new(:remaining_hours, :sortable => "#{Issue.table_name}.remaining_hours",
+                    :totalable => true),
     EstimatedRemainingHoursColumn.new,
     QueryColumn.new(
       :total_estimated_hours,
@@ -77,6 +79,10 @@ class IssueQuery < Query
           " AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)"
         end,
       :default_order => 'desc'),
+    QueryColumn.new(:total_remaining_hours,
+      :sortable => "COALESCE((SELECT SUM(remaining_hours) FROM #{Issue.table_name} subtasks" +
+        " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
+      :default_order => 'desc'),
     QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
     TimestampQueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on",
                              :default_order => 'desc', :groupable => true),
@@ -223,6 +229,7 @@ class IssueQuery < Query
     add_available_filter "start_date", :type => :date
     add_available_filter "due_date", :type => :date
     add_available_filter "estimated_hours", :type => :float
+    add_available_filter "remaining_hours", :type => :float
 
     if User.current.allowed_to?(:view_time_entries, project, :global => true)
       add_available_filter "spent_time", :type => :float, :label => :label_spent_time
@@ -389,6 +396,11 @@ class IssueQuery < Query
     map_total(scope.sum(EstimatedRemainingHoursColumn::COLUMN_SQL)) {|t| t.to_f.round(2)}
   end
 
+  # Returns sum of all the issue's remaining_hours
+  def total_for_remaining_hours(scope)
+    map_total(scope.sum(:remaining_hours)) {|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/app/models/mail_handler.rb b/app/models/mail_handler.rb
index a55c1b9..0e142f3 100644
--- a/app/models/mail_handler.rb
+++ b/app/models/mail_handler.rb
@@ -484,6 +484,7 @@ class MailHandler < ActionMailer::Base
       'start_date' => get_keyword(:start_date, :format => '\d{4}-\d{2}-\d{2}'),
       'due_date' => get_keyword(:due_date, :format => '\d{4}-\d{2}-\d{2}'),
       'estimated_hours' => get_keyword(:estimated_hours),
+      'remaining_hours' => get_keyword(:remaining_hours),
       'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0'),
       'is_private' => get_keyword_bool(:is_private),
       'parent_issue_id' => get_keyword(:parent_issue)
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
index dc6828d..72ce649 100644
--- a/app/models/tracker.rb
+++ b/app/models/tracker.rb
@@ -23,9 +23,7 @@ class Tracker < ActiveRecord::Base
   CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject is_private).freeze
   # Fields that can be disabled
   # Other (future) fields should be appended, not inserted!
-  CORE_FIELDS =
-    %w(assigned_to_id category_id fixed_version_id parent_issue_id
-       start_date due_date estimated_hours done_ratio description priority_id).freeze
+  CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours remaining_hours done_ratio description priority_id).freeze
   CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze
 
   before_destroy :check_integrity
diff --git a/app/views/imports/_issues_fields_mapping.html.erb b/app/views/imports/_issues_fields_mapping.html.erb
index ed1f6fa..69853f2 100644
--- a/app/views/imports/_issues_fields_mapping.html.erb
+++ b/app/views/imports/_issues_fields_mapping.html.erb
@@ -71,6 +71,10 @@
   <label for="import_mapping_estimated_hours"><%= l(:field_estimated_hours) %></label>
   <%= mapping_select_tag @import, 'estimated_hours' %>
 </p>
+<p>
+  <label><%= l(:field_remaining_hours) %></label>
+  <%= mapping_select_tag @import, 'remaining_hours' %>
+</p>
 <p>
   <label for="import_mapping_done_ratio"><%= l(:field_done_ratio) %></label>
   <%= mapping_select_tag @import, 'done_ratio' %>
diff --git a/app/views/issues/_attributes.html.erb b/app/views/issues/_attributes.html.erb
index 0487b5e..1e4f941 100644
--- a/app/views/issues/_attributes.html.erb
+++ b/app/views/issues/_attributes.html.erb
@@ -83,12 +83,16 @@
 </p>
 <% end %>
 
+<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
+<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
+<% end %>
+
 <% if @issue.safe_attribute? 'estimated_hours' %>
 <p><%= f.hours_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p>
 <% end %>
 
-<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
-<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
+<% if @issue.safe_attribute? 'remaining_hours' %>
+<p><%= f.text_field :remaining_hours, :size => 3, :required => @issue.required_attribute?('remaining_hours') %> <%= l(:field_hours) %></p>
 <% end %>
 </div>
 </div>
diff --git a/app/views/issues/bulk_edit.html.erb b/app/views/issues/bulk_edit.html.erb
index 4fe457b..fb90858 100644
--- a/app/views/issues/bulk_edit.html.erb
+++ b/app/views/issues/bulk_edit.html.erb
@@ -175,6 +175,14 @@
 </p>
 <% end %>
 
+<% if @safe_attributes.include?('remaining_hours') %>
+<p>
+  <label for='issue_remaining_hours'><%= l(:field_remaining_hours) %></label>
+  <%= text_field_tag 'issue[remaining_hours]', '', :value => @issue_params[:remaining_hours], :size => 10 %>
+  <label class="inline"><%= check_box_tag 'issue[remaining_hours]', 'none', (@issue_params[:remaining_hours] == 'none'), :id => nil, :data => {:disables => '#issue_remaining_hours'} %><%= l(:button_clear) %></label>
+</p>
+<% end %>
+
 <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
 <p>
   <label for='issue_done_ratio'><%= l(:field_done_ratio) %></label>
diff --git a/app/views/issues/index.api.rsb b/app/views/issues/index.api.rsb
index f1627cd..67d23eb 100644
--- a/app/views/issues/index.api.rsb
+++ b/app/views/issues/index.api.rsb
@@ -19,6 +19,7 @@ api.array :issues, api_meta(:total_count => @issue_count, :offset => @offset, :l
       api.done_ratio  issue.done_ratio
       api.is_private  issue.is_private
       api.estimated_hours issue.estimated_hours
+      api.remaining_hours issue.remaining_hours
       api.total_estimated_hours issue.total_estimated_hours
       if User.current.allowed_to?(:view_time_entries, issue.project)
         api.spent_hours(issue.spent_hours)
diff --git a/app/views/issues/show.api.rsb b/app/views/issues/show.api.rsb
index 6f23ca4..ea8e80b 100644
--- a/app/views/issues/show.api.rsb
+++ b/app/views/issues/show.api.rsb
@@ -17,7 +17,9 @@ api.issue do
   api.done_ratio @issue.done_ratio
   api.is_private @issue.is_private
   api.estimated_hours @issue.estimated_hours
+  api.remaining_hours @issue.remaining_hours
   api.total_estimated_hours @issue.total_estimated_hours
+  api.total_remaining_hours @issue.total_remaining_hours
   if User.current.allowed_to?(:view_time_entries, @project)
     api.spent_hours(@issue.spent_hours)
     api.total_spent_hours(@issue.total_spent_hours)
diff --git a/app/views/issues/show.html.erb b/app/views/issues/show.html.erb
index 6f0a0a9..5452672 100644
--- a/app/views/issues/show.html.erb
+++ b/app/views/issues/show.html.erb
@@ -73,6 +73,9 @@
   unless @issue.disabled_core_fields.include?('estimated_hours')
     rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
   end
+  unless @issue.disabled_core_fields.include?('remaining_hours')
+    rows.right l(:field_remaining_hours), issue_remaining_hours_details(@issue), :class => 'remaining-hours'
+  end
   if User.current.allowed_to?(:view_time_entries, @project) && @issue.total_spent_hours > 0
     rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
   end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 092e10a..2b70319 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -409,6 +409,8 @@ en:
   field_full_width_layout: Full width layout
   field_digest: Checksum
   field_default_assigned_to: Default assignee
+  field_remaining_hours: Remaining time
+  field_total_remaining_hours: Total remaining time
   field_recently_used_projects: Number of recently used projects in jump box
   field_history_default_tab: Issue's history default tab
   field_unique_id: Unique ID
diff --git a/db/migrate/20160920184857_add_remaining_hours_to_issues.rb b/db/migrate/20160920184857_add_remaining_hours_to_issues.rb
new file mode 100644
index 0000000..5e37998
--- /dev/null
+++ b/db/migrate/20160920184857_add_remaining_hours_to_issues.rb
@@ -0,0 +1,10 @@
+class AddRemainingHoursToIssues < ActiveRecord::Migration[4.2]
+  def self.up
+    add_column :issues, :remaining_hours, :float
+    Issue.where("estimated_hours > ?", 0).update_all("remaining_hours = estimated_hours")
+  end
+
+  def self.down
+    remove_column :issues, :remaining_hours
+   end
+end
\ No newline at end of file
diff --git a/test/fixtures/mail_handler/ticket_on_given_project.eml b/test/fixtures/mail_handler/ticket_on_given_project.eml
old mode 100644
new mode 100755
index b39e701..9414520
--- a/test/fixtures/mail_handler/ticket_on_given_project.eml
+++ b/test/fixtures/mail_handler/ticket_on_given_project.eml
@@ -18,16 +18,16 @@ X-MSMail-Priority: Normal
 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
 
-Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet 
-turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus 
-blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti 
-sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In 
-in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras 
-sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum 
-id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus 
-eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique 
-sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et 
-malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse 
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
+turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
+blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
+sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
+in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
+sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
+id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
+eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
+sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
+malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
 platea dictumst.
 
 Project: onlinestore
@@ -37,6 +37,7 @@ Start Date:2010-01-01
 Assigned to: John Smith
 fixed version: alpha
 estimated hours: 2.5
+remaining hours: 1
 done ratio: 30
 parent issue: 4
 
@@ -52,10 +53,10 @@ This paragraph is between delimiters.
 
 This paragraph is after the delimiter so it shouldn't appear.
 
-Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque 
-sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. 
-Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, 
-dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, 
-massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo 
+Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
+sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
+Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
+dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
+massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
 
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index cce8ddc..60ea76a 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -1400,6 +1400,13 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_equal hours.sort.reverse, hours
   end
 
+  def test_index_sort_by_total_remaining_hours
+    get :index, :sort => 'total_remaining_hours:desc'
+    assert_response :success
+    hours = assigns(:issues).collect(&:total_remaining_hours)
+    assert_equal hours.sort.reverse, hours
+  end
+
   def test_index_sort_by_user_custom_field
     cf = IssueCustomField.
            create!(
@@ -1630,6 +1637,11 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_select 'table.issues td.total_estimated_hours'
   end
 
+  def test_index_with_total_remaining_hours_column
+    get :index, :set_filter => 1, :c => %w(subject total_remaining_hours)
+    assert_select 'table.issues td.total_remaining_hours'
+  end
+
   def test_index_should_not_show_spent_hours_column_without_permission
     Role.anonymous.remove_permission! :view_time_entries
     get(
@@ -1934,6 +1946,18 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_select 'input[type=checkbox][name=?][value=estimated_hours][checked=checked]', 't[]'
   end
 
+  def test_index_with_remaining_hours_total
+    Issue.delete_all
+    Issue.generate!(:remaining_hours => 5.4)
+    Issue.generate!(:remaining_hours => 1.1)
+
+    get :index, :t => %w(remaining_hours)
+    assert_response :success
+    assert_select '.query-totals'
+    assert_select '.total-for-remaining-hours span.value', :text => '6.50'
+    assert_select 'input[type=checkbox][name=?][value=remaining_hours][checked=checked]', 't[]'
+  end
+
   def test_index_with_grouped_query_and_estimated_hours_total
     Issue.delete_all
     Issue.generate!(:estimated_hours => '5:30', :category_id => 1)
@@ -4075,6 +4099,7 @@ class IssuesControllerTest < Redmine::ControllerTest
               :priority_id => 5,
               :start_date => '2010-11-07',
               :estimated_hours => '',
+              :remaining_hours => '',
               :custom_field_values => {
                 '2' => 'Value for field 2'
               }
@@ -4092,6 +4117,7 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_equal 2, issue.status_id
     assert_equal Date.parse('2010-11-07'), issue.start_date
     assert_nil issue.estimated_hours
+    assert_nil issue.remaining_hours
     v = issue.custom_values.where(:custom_field_id => 2).first
     assert_not_nil v
     assert_equal 'Value for field 2', v.value
@@ -7480,6 +7506,15 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_equal 4.25, Issue.find(2).estimated_hours
   end
 
+  def test_bulk_update_remaining_hours
+    @request.session[:user_id] = 2
+    post :bulk_update, :ids => [1, 2], :issue => {:remaining_hours => 4.15}
+
+    assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
+    assert_equal 4.15, Issue.find(1).remaining_hours
+    assert_equal 4.15, Issue.find(2).remaining_hours
+  end
+
   def test_bulk_update_custom_field
     @request.session[:user_id] = 2
     # update issues priority
diff --git a/test/helpers/issues_helper_test.rb b/test/helpers/issues_helper_test.rb
index c91c47c..16c08e2 100644
--- a/test/helpers/issues_helper_test.rb
+++ b/test/helpers/issues_helper_test.rb
@@ -208,6 +208,13 @@ class IssuesHelperTest < Redmine::HelperTest
     assert_match '6:18', show_detail(detail, true)
   end
 
+  test 'show_detail should show old and new values with a remaining hours attribute' do
+    detail = JournalDetail.new(:property => 'attr', :prop_key => 'remaining_hours',
+                               :old_value => '6.3', :value => '5')
+    assert_match '5.00', show_detail(detail, true)
+    assert_match '6.30', show_detail(detail, true)
+  end
+
   test 'show_detail should not show values with a description attribute' do
     detail = JournalDetail.new(:property => 'attr', :prop_key => 'description',
                                :old_value => 'Foo', :value => 'Bar')
diff --git a/test/integration/api_test/issues_test.rb b/test/integration/api_test/issues_test.rb
index 801baec..03d5c06 100644
--- a/test/integration/api_test/issues_test.rb
+++ b/test/integration/api_test/issues_test.rb
@@ -456,10 +456,10 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     end
   end
 
-  test "GET /issues/:id.xml should contains total_estimated_hours and total_spent_hours" do
+  test "GET /issues/:id.xml should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
     parent = Issue.find(3)
     parent.update_columns :estimated_hours => 2.0
-    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
+    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
     TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
                       :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
     get '/issues/3.xml'
@@ -468,15 +468,17 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     assert_select 'issue' do
       assert_select 'estimated_hours',       parent.estimated_hours.to_s
       assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
+      assert_select 'remaining_hours',       parent.remaining_hours.to_s
+      assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s
       assert_select 'spent_hours',           parent.spent_hours.to_s
       assert_select 'total_spent_hours',     (parent.spent_hours.to_f + 2.5).to_s
     end
   end
 
-  test "GET /issues/:id.xml should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
+  test "GET /issues/:id.xml should contains total_estimated_hours and total_remaining_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
     parent = Issue.find(3)
     parent.update_columns :estimated_hours => 2.0
-    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
+    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
     Role.anonymous.remove_permission! :view_time_entries
     get '/issues/3.xml'
 
@@ -484,6 +486,8 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     assert_select 'issue' do
       assert_select 'estimated_hours',       parent.estimated_hours.to_s
       assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
+      assert_select 'remaining_hours',       parent.remaining_hours.to_s
+      assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s
       assert_select 'spent_hours',           false
       assert_select 'total_spent_hours',     false
     end
@@ -506,10 +510,10 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     end
   end
 
-  test "GET /issues/:id.json should contains total_estimated_hours and total_spent_hours" do
+  test "GET /issues/:id.json should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
     parent = Issue.find(3)
     parent.update_columns :estimated_hours => 2.0
-    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
+    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
     TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
                       :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
     get '/issues/3.json'
@@ -518,14 +522,16 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     json = ActiveSupport::JSON.decode(response.body)
     assert_equal parent.estimated_hours, json['issue']['estimated_hours']
     assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
+    assert_equal parent.remaining_hours, json['issue']['remaining_hours']
+    assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours']
     assert_equal parent.spent_hours, json['issue']['spent_hours']
     assert_equal (parent.spent_hours.to_f + 2.5), json['issue']['total_spent_hours']
   end
 
-  test "GET /issues/:id.json should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
+  test "GET /issues/:id.json should contains total_estimated_hours and total_remaining_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
     parent = Issue.find(3)
     parent.update_columns :estimated_hours => 2.0
-    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
+    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
     Role.anonymous.remove_permission! :view_time_entries
     get '/issues/3.json'
 
@@ -533,6 +539,8 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
     json = ActiveSupport::JSON.decode(response.body)
     assert_equal parent.estimated_hours, json['issue']['estimated_hours']
     assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
+    assert_equal parent.remaining_hours, json['issue']['remaining_hours']
+    assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours']
     assert_nil json['issue']['spent_hours']
     assert_nil json['issue']['total_spent_hours']
   end
diff --git a/test/unit/issue_subtasking_test.rb b/test/unit/issue_subtasking_test.rb
index 86e9e77..ceb93ac 100644
--- a/test/unit/issue_subtasking_test.rb
+++ b/test/unit/issue_subtasking_test.rb
@@ -34,7 +34,7 @@ class IssueSubtaskingTest < ActiveSupport::TestCase
   def test_leaf_planning_fields_should_be_editable
     issue = Issue.generate!
     user = User.find(1)
-    %w(priority_id done_ratio start_date due_date estimated_hours).each do |attribute|
+    %w(priority_id done_ratio start_date due_date estimated_hours remaining_hours).each do |attribute|
       assert issue.safe_attribute?(attribute, user)
     end
   end
@@ -377,4 +377,14 @@ class IssueSubtaskingTest < ActiveSupport::TestCase
     assert !child.save
     assert_include I18n.t("activerecord.errors.messages.open_issue_with_closed_parent"), child.errors.full_messages
   end
+
+  def test_parent_total_remaining_hours_should_be_sum_of_descendants
+    parent = Issue.generate!
+    parent.generate_child!(:remaining_hours => nil)
+    assert_equal 0, parent.reload.total_remaining_hours
+    parent.generate_child!(:remaining_hours => 5)
+    assert_equal 5, parent.reload.total_remaining_hours
+    parent.generate_child!(:remaining_hours => 7)
+    assert_equal 12, parent.reload.total_remaining_hours
+  end
 end
diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb
index 18e4204..087875f 100644
--- a/test/unit/issue_test.rb
+++ b/test/unit/issue_test.rb
@@ -60,10 +60,11 @@ class IssueTest < ActiveSupport::TestCase
     issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
                       :status_id => 1, :priority => IssuePriority.first,
                       :subject => 'test_create',
-                      :description => 'IssueTest#test_create', :estimated_hours => '1:30')
+                      :description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1')
     assert issue.save
     issue.reload
     assert_equal 1.5, issue.estimated_hours
+    assert_equal 1, issue.remaining_hours
   end
 
   def test_create_minimal
@@ -72,6 +73,7 @@ class IssueTest < ActiveSupport::TestCase
     assert_equal issue.tracker.default_status, issue.status
     assert issue.description.nil?
     assert_nil issue.estimated_hours
+    assert_nil issue.remaining_hours
   end
 
   def test_create_with_all_fields_disabled
@@ -148,6 +150,23 @@ class IssueTest < ActiveSupport::TestCase
     end
   end
 
+  def test_remaining_hours_update_with_negative_value_should_set_to_zero
+    set_language_if_valid 'en'
+    ['-4'].each do |invalid|
+      issue = Issue.new(:remaining_hours => invalid)
+      assert_equal 0, issue.remaining_hours
+    end
+  end
+
+  def test_remaining_hours_should_be_set_from_estimated_hours_when_is_empty
+      issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
+                      :status_id => 1, :priority => IssuePriority.all.first,
+                      :subject => 'test_create',
+                      :description => 'IssueTest#test_create', :estimated_hours => '1:30')
+      assert issue.save
+      assert_equal 1.5, issue.remaining_hours
+  end
+
   def test_create_with_required_custom_field
     set_language_if_valid 'en'
     field = IssueCustomField.find_by_name('Database')
@@ -2727,6 +2746,7 @@ class IssueTest < ActiveSupport::TestCase
     user = User.find(3)
     user.members.update_all ["mail_notification = ?", false]
     user.update! :mail_notification => 'only_assigned'
+    puts user.inspect
 
     with_settings :notified_events => %w(issue_updated) do
       issue = Issue.find(2)
@@ -3284,6 +3304,17 @@ class IssueTest < ActiveSupport::TestCase
     assert_equal false, issue.closing?
   end
 
+  def test_closing_should_set_remaining_hours_to_zero
+    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
+                      :status_id => 1, :priority => IssuePriority.all.first,
+                      :subject => 'test_create',
+                      :description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1')
+    assert_equal 1, issue.remaining_hours
+    issue.status_id = 5
+    issue.save!
+    assert_equal 0, issue.remaining_hours
+  end
+
   def test_reopening_should_return_true_when_reopening_an_issue
     issue = Issue.find(8)
     issue.status = IssueStatus.find(6)
diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb
index ff77f36..36675cb 100644
--- a/test/unit/mail_handler_test.rb
+++ b/test/unit/mail_handler_test.rb
@@ -48,7 +48,7 @@ class MailHandlerTest < ActiveSupport::TestCase
         'ticket_on_given_project.eml',
         :allow_override =>
           ['status', 'start_date', 'due_date', 'assigned_to',
-           'fixed_version', 'estimated_hours', 'done_ratio',
+           'fixed_version', 'estimated_hours', 'remaining_hours', 'done_ratio',
            'parent_issue']
       )
     assert issue.is_a?(Issue)
@@ -65,12 +65,14 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert_equal User.find_by_login('jsmith'), issue.assigned_to
     assert_equal Version.find_by_name('Alpha'), issue.fixed_version
     assert_equal 2.5, issue.estimated_hours
+    assert_equal 1, issue.remaining_hours
     assert_equal 30, issue.done_ratio
     assert_equal Issue.find(4), issue.parent
     # keywords should be removed from the email body
     assert !issue.description.match(/^Project:/i)
     assert !issue.description.match(/^Status:/i)
     assert !issue.description.match(/^Start Date:/i)
+    assert !issue.description.match(/^remaining hours:/i)
   end
 
   def test_add_issue_with_all_overrides
@@ -87,6 +89,7 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert_equal User.find_by_login('jsmith'), issue.assigned_to
     assert_equal Version.find_by_name('Alpha'), issue.fixed_version
     assert_equal 2.5, issue.estimated_hours
+    assert_equal 1, issue.remaining_hours
     assert_equal 30, issue.done_ratio
     assert_equal Issue.find(4), issue.parent
   end
@@ -109,6 +112,7 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert_nil issue.assigned_to
     assert_nil issue.fixed_version
     assert_nil issue.estimated_hours
+    assert_nil issue.remaining_hours
     assert_equal 0, issue.done_ratio
     assert_nil issue.parent
   end
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb
index bc35b18..c41da5b 100644
--- a/test/unit/query_test.rb
+++ b/test/unit/query_test.rb
@@ -2347,8 +2347,8 @@ class QueryTest < ActiveSupport::TestCase
 
   def test_set_totalable_names
     q = IssueQuery.new
-    q.totalable_names = ['estimated_hours', :spent_hours, '']
-    assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
+    q.totalable_names = ['estimated_hours', 'remaining_hours', :spent_hours, '']
+    assert_equal [:estimated_hours, :remaining_hours, :spent_hours], q.totalable_columns.map(&:name)
   end
 
   def test_totalable_columns_should_default_to_settings
@@ -2368,6 +2368,11 @@ class QueryTest < ActiveSupport::TestCase
     assert_include :estimated_remaining_hours, q.available_totalable_columns.map(&:name)
   end
 
+  def test_available_totalable_columns_should_include_remaining_hours
+    q = IssueQuery.new
+    assert_include :remaining_hours, q.available_totalable_columns.map(&:name)
+  end
+
   def test_available_totalable_columns_should_include_spent_hours
     User.current = User.find(1)
 
@@ -2464,6 +2469,29 @@ class QueryTest < ActiveSupport::TestCase
     )
   end
 
+  def test_total_for_remaining_hours
+    Issue.delete_all
+    Issue.generate!(:remaining_hours => 5.5)
+    Issue.generate!(:remaining_hours => 1.1)
+    Issue.generate!
+
+    q = IssueQuery.new
+    assert_equal 6.6, q.total_for(:remaining_hours)
+  end
+
+  def test_total_by_group_for_remaining_hours
+    Issue.delete_all
+    Issue.generate!(:remaining_hours => 5.5, :assigned_to_id => 2)
+    Issue.generate!(:remaining_hours => 1.1, :assigned_to_id => 3)
+    Issue.generate!(:remaining_hours => 3.5)
+
+    q = IssueQuery.new(:group_by => 'assigned_to')
+    assert_equal(
+      {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
+      q.total_by_group_for(:remaining_hours)
+    )
+  end
+
   def test_total_for_spent_hours
     TimeEntry.delete_all
     TimeEntry.generate!(:hours => 5.5)
