Project

General

Profile

Feature #24277 » 01_add_remaining_hours_field_3.4.1.patch

Marius BĂLTEANU, 2017-07-10 19:50

View differences:

app/helpers/issues_helper.rb
150 150
    end
151 151
  end
152 152

  
153
  def issue_remaining_hours_details(issue)
154
    if issue.total_remaining_hours.present?
155
      if issue.total_remaining_hours == issue.remaining_hours
156
        l_hours_short(issue.remaining_hours)
157
      else
158
        s = issue.remaining_hours.present? ? l_hours_short(issue.remaining_hours) : ""
159
        s << " (#{l(:label_total)}: #{l_hours_short(issue.total_remaining_hours)})"
160
        s.html_safe
161
      end
162
    end
163
  end
164

  
153 165
  def issue_spent_hours_details(issue)
154 166
    if issue.total_spent_hours > 0
155 167
      path = project_time_entries_path(issue.project, :issue_id => "~#{issue.id}")
......
408 420
        value = find_name_by_reflection(field, detail.value)
409 421
        old_value = find_name_by_reflection(field, detail.old_value)
410 422

  
411
      when 'estimated_hours'
423
      when 'estimated_hours', 'remaining_hours'
412 424
        value = l_hours_short(detail.value.to_f) unless detail.value.blank?
413 425
        old_value = l_hours_short(detail.old_value.to_f) unless detail.old_value.blank?
414 426

  
app/models/issue.rb
66 66
  validates_length_of :subject, :maximum => 255
67 67
  validates_inclusion_of :done_ratio, :in => 0..100
68 68
  validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
69
  validates :remaining_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
69 70
  validates :start_date, :date => true
70 71
  validates :due_date, :date => true
71 72
  validate :validate_issue, :validate_required_fields, :validate_permissions
......
107 108

  
108 109
  before_validation :default_assign, on: :create
109 110
  before_validation :clear_disabled_fields
111
  before_validation :update_remaining_hours_from_estimated_hours
110 112
  before_save :close_duplicates, :update_done_ratio_from_issue_status,
111
              :force_updated_on_change, :update_closed_on, :set_assigned_to_was
113
              :force_updated_on_change, :update_closed_on, :set_assigned_to_was, :update_remaining_hours
112 114
  after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
113 115
  after_save :reschedule_following_issues, :update_nested_set_attributes,
114 116
             :update_parent_attributes, :delete_selected_attachments, :create_journal
......
245 247
    @spent_hours = nil
246 248
    @total_spent_hours = nil
247 249
    @total_estimated_hours = nil
250
    @total_remaining_hours = nil
248 251
    @last_updated_by = nil
249 252
    @last_notes = nil
250 253
    base_reload(*args)
......
446 449
    write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
447 450
  end
448 451

  
452
  def remaining_hours=(h)
453
    h = h.is_a?(String) ? h.to_hours : h
454
    # remaining time cannot be less than zero
455
    h = 0 if !h.nil? && h < 0
456
    write_attribute :remaining_hours, h
457
  end
458

  
449 459
  safe_attributes 'project_id',
450 460
    'tracker_id',
451 461
    'status_id',
......
459 469
    'due_date',
460 470
    'done_ratio',
461 471
    'estimated_hours',
472
    'remaining_hours',
462 473
    'custom_field_values',
463 474
    'custom_fields',
464 475
    'lock_version',
......
1092 1103
    end
1093 1104
  end
1094 1105

  
1106
  def total_remaining_hours
1107
    if leaf?
1108
      remaining_hours
1109
    else
1110
      @total_remaining_hours ||= self_and_descendants.sum(:remaining_hours)
1111
    end
1112
  end
1113

  
1095 1114
  def relations
1096 1115
    @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
1097 1116
  end
......
1834 1853
    end
1835 1854
  end
1836 1855

  
1856
  # Callback for setting remaining time to zero when the issue is closed.
1857
  def update_remaining_hours
1858
    if closing? && safe_attribute?('remaining_hours') && self.remaining_hours.to_f > 0
1859
      self.remaining_hours = 0
1860
    end
1861
  end
1862

  
1837 1863
  # Saves the changes in a Journal
1838 1864
  # Called after_save
1839 1865
  def create_journal
......
1868 1894
      self.done_ratio ||= 0
1869 1895
    end
1870 1896
  end
1897

  
1898
  def update_remaining_hours_from_estimated_hours
1899
    if self.remaining_hours.blank? && self.estimated_hours
1900
      self.remaining_hours = self.estimated_hours
1901
    end
1902
  end
1871 1903
end
app/models/issue_import.rb
162 162
    if estimated_hours = row_value(row, 'estimated_hours')
163 163
      attributes['estimated_hours'] = estimated_hours
164 164
    end
165
    if remaining_hours = row_value(row, 'remaining_hours')
166
      attributes['remaining_hours'] = remaining_hours
167
    end
165 168
    if done_ratio = row_value(row, 'done_ratio')
166 169
      attributes['done_ratio'] = done_ratio
167 170
    end
app/models/issue_query.rb
36 36
    QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
37 37
    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
38 38
    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
39
    QueryColumn.new(:remaining_hours, :sortable => "#{Issue.table_name}.remaining_hours", :totalable => true),
39 40
    QueryColumn.new(:total_estimated_hours,
40 41
      :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
41 42
        " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
42 43
      :default_order => 'desc'),
44
    QueryColumn.new(:total_remaining_hours,
45
      :sortable => "COALESCE((SELECT SUM(remaining_hours) FROM #{Issue.table_name} subtasks" +
46
        " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
47
      :default_order => 'desc'),
43 48
    QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
44 49
    QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
45 50
    QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
......
134 139
    add_available_filter "start_date", :type => :date
135 140
    add_available_filter "due_date", :type => :date
136 141
    add_available_filter "estimated_hours", :type => :float
142
    add_available_filter "remaining_hours", :type => :float
137 143
    add_available_filter "done_ratio", :type => :integer
138 144

  
139 145
    if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
......
249 255
    map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
250 256
  end
251 257

  
258
  # Returns sum of all the issue's remaining_hours
259
  def total_for_remaining_hours(scope)
260
    map_total(scope.sum(:remaining_hours)) {|t| t.to_f.round(2)}
261
  end
262

  
252 263
  # Returns sum of all the issue's time entries hours
253 264
  def total_for_spent_hours(scope)
254 265
    total = if group_by_column.try(:name) == :project
app/models/mail_handler.rb
424 424
      'start_date' => get_keyword(:start_date, :format => '\d{4}-\d{2}-\d{2}'),
425 425
      'due_date' => get_keyword(:due_date, :format => '\d{4}-\d{2}-\d{2}'),
426 426
      'estimated_hours' => get_keyword(:estimated_hours),
427
      'remaining_hours' => get_keyword(:remaining_hours),
427 428
      'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0')
428 429
    }.delete_if {|k, v| v.blank? }
429 430

  
app/models/tracker.rb
21 21
  CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject priority_id is_private).freeze
22 22
  # Fields that can be disabled
23 23
  # Other (future) fields should be appended, not inserted!
24
  CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio description).freeze
24
  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).freeze
25 25
  CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze
26 26

  
27 27
  before_destroy :check_integrity
app/views/imports/_fields_mapping.html.erb
82 82
  <%= mapping_select_tag @import, 'estimated_hours' %>
83 83
</p>
84 84
<p>
85
  <label><%= l(:field_remaining_hours) %></label>
86
  <%= mapping_select_tag @import, 'remaining_hours' %>
87
</p>
88
<p>
85 89
  <label><%= l(:field_done_ratio) %></label>
86 90
  <%= mapping_select_tag @import, 'done_ratio' %>
87 91
</p>
app/views/issues/_attributes.html.erb
64 64
</p>
65 65
<% end %>
66 66

  
67
<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
68
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
69
<% end %>
70

  
67 71
<% if @issue.safe_attribute? 'estimated_hours' %>
68 72
<p><%= f.hours_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p>
69 73
<% end %>
70 74

  
71
<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
72
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
75
<% if @issue.safe_attribute? 'remaining_hours' %>
76
<p><%= f.text_field :remaining_hours, :size => 3, :required => @issue.required_attribute?('remaining_hours') %> <%= l(:field_hours) %></p>
73 77
<% end %>
74 78
</div>
75 79
</div>
app/views/issues/bulk_edit.html.erb
181 181
</p>
182 182
<% end %>
183 183

  
184
<% if @safe_attributes.include?('remaining_hours') %>
185
<p>
186
  <label for='issue_remaining_hours'><%= l(:field_remaining_hours) %></label>
187
  <%= text_field_tag 'issue[remaining_hours]', '', :value => @issue_params[:remaining_hours], :size => 10 %>
188
  <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>
189
</p>
190
<% end %>
191

  
184 192
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
185 193
<p>
186 194
  <label for='issue_done_ratio'><%= l(:field_done_ratio) %></label>
app/views/issues/index.api.rsb
19 19
      api.done_ratio  issue.done_ratio
20 20
      api.is_private  issue.is_private
21 21
      api.estimated_hours issue.estimated_hours
22
      api.remaining_hours issue.remaining_hours
22 23

  
23 24
      render_api_custom_values issue.visible_custom_field_values, api
24 25

  
app/views/issues/show.api.rsb
17 17
  api.done_ratio @issue.done_ratio
18 18
  api.is_private @issue.is_private
19 19
  api.estimated_hours @issue.estimated_hours
20
  api.remaining_hours @issue.remaining_hours
20 21
  api.total_estimated_hours @issue.total_estimated_hours
22
  api.total_remaining_hours @issue.total_remaining_hours
21 23
  if User.current.allowed_to?(:view_time_entries, @project)
22 24
    api.spent_hours(@issue.spent_hours)
23 25
    api.total_spent_hours(@issue.total_spent_hours)
app/views/issues/show.html.erb
67 67
  unless @issue.disabled_core_fields.include?('estimated_hours')
68 68
    rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
69 69
  end
70
  unless @issue.disabled_core_fields.include?('remaining_hours')
71
    rows.right l(:field_remaining_hours), issue_remaining_hours_details(@issue), :class => 'remaining-hours'
72
  end
70 73
  if User.current.allowed_to?(:view_time_entries, @project) && @issue.total_spent_hours > 0
71 74
    rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
72 75
  end
config/locales/en.yml
375 375
  field_full_width_layout: Full width layout
376 376
  field_digest: Checksum
377 377
  field_default_assigned_to: Default assignee
378
  field_remaining_hours: Remaining time
379
  field_total_remaining_hours: Total remaining time
378 380

  
379 381
  setting_app_title: Application title
380 382
  setting_app_subtitle: Application subtitle
db/migrate/20160920184857_add_remaining_hours_to_issues.rb
1
class AddRemainingHoursToIssues < ActiveRecord::Migration
2
  def self.up
3
    add_column :issues, :remaining_hours, :float
4
    Issue.where("estimated_hours > ?", 0).update_all("remaining_hours = estimated_hours")
5
  end
6

  
7
  def self.down
8
    remove_column :issues, :remaining_hours
9
   end
10
end
test/fixtures/mail_handler/ticket_on_given_project.eml
18 18
X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 19
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20 20

  
21
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet 
22
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus 
23
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti 
24
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In 
25
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras 
26
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum 
27
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus 
28
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique 
29
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et 
30
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse 
21
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
22
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
23
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
24
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
25
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
26
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
27
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
28
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
29
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
30
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
31 31
platea dictumst.
32 32

  
33 33
Project: onlinestore
......
37 37
Assigned to: John Smith
38 38
fixed version: alpha
39 39
estimated hours: 2.5
40
remaining hours: 1
40 41
done ratio: 30
41 42

  
42 43
--- This line starts with a delimiter and should not be stripped
......
51 52

  
52 53
This paragraph is after the delimiter so it shouldn't appear.
53 54

  
54
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque 
55
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. 
56
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, 
57
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, 
58
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo 
55
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
56
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
57
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
58
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
59
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
59 60
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
60 61

  
test/functional/issues_controller_test.rb
152 152
        :f => ['tracker_id'],
153 153
        :op => {
154 154
          'tracker_id' => '='
155
        },  
155
        },
156 156
        :v => {
157 157
          'tracker_id' => ['1']
158 158
        }
......
253 253
        :f => [filter_name],
254 254
        :op => {
255 255
          filter_name => '='
256
        },  
256
        },
257 257
        :v => {
258 258
          filter_name => ['Foo']
259
        },  
259
        },
260 260
        :c => ['project']
261 261
      }
262 262
    assert_response :success
......
970 970
    assert_equal hours.sort.reverse, hours
971 971
  end
972 972

  
973
  def test_index_sort_by_total_remaining_hours
974
    get :index, :sort => 'total_remaining_hours:desc'
975
    assert_response :success
976
    hours = assigns(:issues).collect(&:total_remaining_hours)
977
    assert_equal hours.sort.reverse, hours
978
  end
979

  
973 980
  def test_index_sort_by_user_custom_field
974 981
    cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
975 982
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
......
1146 1153
    assert_select 'table.issues td.total_estimated_hours'
1147 1154
  end
1148 1155

  
1156
  def test_index_with_total_remaining_hours_column
1157
    get :index, :set_filter => 1, :c => %w(subject total_remaining_hours)
1158
    assert_select 'table.issues td.total_remaining_hours'
1159
  end
1160

  
1149 1161
  def test_index_should_not_show_spent_hours_column_without_permission
1150 1162
    Role.anonymous.remove_permission! :view_time_entries
1151 1163
    get :index, :params => {
......
1352 1364
    assert_select 'input[type=checkbox][name=?][value=estimated_hours][checked=checked]', 't[]'
1353 1365
  end
1354 1366

  
1367
  def test_index_with_remaining_hours_total
1368
    Issue.delete_all
1369
    Issue.generate!(:remaining_hours => 5.4)
1370
    Issue.generate!(:remaining_hours => 1.1)
1371

  
1372
    get :index, :t => %w(remaining_hours)
1373
    assert_response :success
1374
    assert_select '.query-totals'
1375
    assert_select '.total-for-remaining-hours span.value', :text => '6.50'
1376
    assert_select 'input[type=checkbox][name=?][value=remaining_hours][checked=checked]', 't[]'
1377
  end
1378

  
1355 1379
  def test_index_with_grouped_query_and_estimated_hours_total
1356 1380
    Issue.delete_all
1357 1381
    Issue.generate!(:estimated_hours => 5.5, :category_id => 1)
......
1415 1439
        :f => ['start_date'],
1416 1440
        :op => {
1417 1441
          :start_date => '='
1418
        },  
1442
        },
1419 1443
        :format => 'csv'
1420 1444
      }
1421 1445
    assert_equal 'text/csv', @response.content_type
......
2590 2614
          :tracker_id => 3,
2591 2615
          :description => 'Prefilled',
2592 2616
          :custom_field_values => {
2593
          '2' => 'Custom field value'}    
2617
          '2' => 'Custom field value'}
2594 2618
        }
2595 2619
      }
2596 2620

  
......
2714 2738
    assert !t.disabled_core_fields.include?('parent_issue_id')
2715 2739

  
2716 2740
    get :new, :params => {
2717
        :project_id => 1, issue: { parent_issue_id: 1 
2741
        :project_id => 1, issue: { parent_issue_id: 1
2718 2742
      }
2719 2743
      }
2720 2744
    assert_response :success
......
2724 2748
    t.save!
2725 2749
    assert t.disabled_core_fields.include?('parent_issue_id')
2726 2750
    get :new, :params => {
2727
        :project_id => 1, issue: { parent_issue_id: 1 
2751
        :project_id => 1, issue: { parent_issue_id: 1
2728 2752
      }
2729 2753
      }
2730 2754
    assert_response :success
......
2782 2806
        :issue => {
2783 2807
          :tracker_id => 2,
2784 2808
          :status_id => 1
2785
        },  
2809
        },
2786 2810
        :was_default_status => 1
2787 2811
      }
2788 2812
    assert_response :success
......
2801 2825
        :issue => {
2802 2826
          :project_id => 1,
2803 2827
          :fixed_version_id => ''
2804
        },  
2828
        },
2805 2829
        :form_update_triggered_by => 'issue_project_id'
2806 2830
      }
2807 2831
    assert_response :success
......
2828 2852
              :priority_id => 5,
2829 2853
              :start_date => '2010-11-07',
2830 2854
              :estimated_hours => '',
2855
              :remaining_hours => '',
2831 2856
              :custom_field_values => {
2832
              '2' => 'Value for field 2'}    
2857
              '2' => 'Value for field 2'}
2833 2858
            }
2834 2859
          }
2835 2860
      end
......
2843 2868
    assert_equal 2, issue.status_id
2844 2869
    assert_equal Date.parse('2010-11-07'), issue.start_date
2845 2870
    assert_nil issue.estimated_hours
2871
    assert_nil issue.remaining_hours
2846 2872
    v = issue.custom_values.where(:custom_field_id => 2).first
2847 2873
    assert_not_nil v
2848 2874
    assert_equal 'Value for field 2', v.value
......
2888 2914
              :priority_id => 5,
2889 2915
              :estimated_hours => '',
2890 2916
              :custom_field_values => {
2891
              '2' => 'Value for field 2'}    
2917
              '2' => 'Value for field 2'}
2892 2918
            }
2893 2919
          }
2894 2920
      end
......
2914 2940
              :priority_id => 5,
2915 2941
              :estimated_hours => '',
2916 2942
              :custom_field_values => {
2917
              '2' => 'Value for field 2'}    
2943
              '2' => 'Value for field 2'}
2918 2944
            }
2919 2945
          }
2920 2946
      end
......
2935 2961
            :tracker_id => 3,
2936 2962
            :subject => 'This is first issue',
2937 2963
            :priority_id => 5
2938
          },  
2964
          },
2939 2965
          :continue => ''
2940 2966
        }
2941 2967
    end
......
2977 3003
            :description => 'This is the description',
2978 3004
            :priority_id => 5,
2979 3005
            :custom_field_values => {
2980
            '1' => ['', 'MySQL', 'Oracle']}    
3006
            '1' => ['', 'MySQL', 'Oracle']}
2981 3007
          }
2982 3008
        }
2983 3009
    end
......
3000 3026
            :description => 'This is the description',
3001 3027
            :priority_id => 5,
3002 3028
            :custom_field_values => {
3003
            '1' => ['']}    
3029
            '1' => ['']}
3004 3030
          }
3005 3031
        }
3006 3032
    end
......
3023 3049
            :description => 'This is the description',
3024 3050
            :priority_id => 5,
3025 3051
            :custom_field_values => {
3026
            field.id.to_s => ['', '2', '3']}    
3052
            field.id.to_s => ['', '2', '3']}
3027 3053
          }
3028 3054
        }
3029 3055
    end
......
3071 3097
            :due_date => '',
3072 3098
            :custom_field_values => {
3073 3099
              cf1.id.to_s => '', cf2.id.to_s => ''
3074
            }    
3075
            
3100
            }
3101

  
3076 3102
          }
3077 3103
        }
3078 3104
      assert_response :success
......
3101 3127
            :due_date => '',
3102 3128
            :custom_field_values => {
3103 3129
              cf1.id.to_s => '', cf2.id.to_s => ['']
3104
            }    
3105
            
3130
            }
3131

  
3106 3132
          }
3107 3133
        }
3108 3134
      assert_response :success
......
3131 3157
            :due_date => '2012-07-16',
3132 3158
            :custom_field_values => {
3133 3159
              cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
3134
            }    
3135
            
3160
            }
3161

  
3136 3162
          }
3137 3163
        }
3138 3164
      assert_response 302
......
3158 3184
            :tracker_id => 1,
3159 3185
            :status_id => 1,
3160 3186
            :subject => 'Test'
3161
            
3187

  
3162 3188
          }
3163 3189
        }
3164 3190
      assert_response 302
......
3336 3362
            :project_id => 3,
3337 3363
            :tracker_id => 2,
3338 3364
            :subject => 'Foo'
3339
          },  
3365
          },
3340 3366
          :continue => '1'
3341 3367
        }
3342 3368
      assert_redirected_to '/issues/new?issue%5Bproject_id%5D=3&issue%5Btracker_id%5D=2'
......
3389 3415
              :priority_id => 5,
3390 3416
              :estimated_hours => '',
3391 3417
              :custom_field_values => {
3392
              '2' => 'Value for field 2'}    
3418
              '2' => 'Value for field 2'}
3393 3419
            }
3394 3420
          }
3395 3421
      end
......
3448 3474
      post :create, :params => {
3449 3475
          :project_id => 1,
3450 3476
          :issue => {
3451
            :tracker => "A param can not be a Tracker" 
3477
            :tracker => "A param can not be a Tracker"
3452 3478
          }
3453 3479
        }
3454 3480
    end
......
3465 3491
              :project_id => 1,
3466 3492
              :issue => {
3467 3493
                :tracker_id => '1',
3468
                :subject => 'With attachment' 
3469
              },  
3494
                :subject => 'With attachment'
3495
              },
3470 3496
              :attachments => {
3471 3497
                '1' => {
3472
                'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}    
3498
                'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}
3473 3499
              }
3474 3500
            }
3475 3501
        end
......
3500 3526
            :project_id => 1,
3501 3527
            :issue => {
3502 3528
              :tracker_id => '1',
3503
              :subject => 'With attachment' 
3504
            },  
3529
              :subject => 'With attachment'
3530
            },
3505 3531
            :attachments => {
3506 3532
              '1' => {
3507
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}    
3533
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}
3508 3534
            }
3509 3535
          }
3510 3536
      end
......
3526 3552
            :project_id => 1,
3527 3553
            :issue => {
3528 3554
              :tracker_id => '1',
3529
              :subject => '' 
3530
            },  
3555
              :subject => ''
3556
            },
3531 3557
            :attachments => {
3532 3558
              '1' => {
3533
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}    
3559
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}
3534 3560
            }
3535 3561
          }
3536 3562
        assert_response :success
......
3557 3583
            :project_id => 1,
3558 3584
            :issue => {
3559 3585
              :tracker_id => '1',
3560
              :subject => '' 
3561
            },  
3586
              :subject => ''
3587
            },
3562 3588
            :attachments => {
3563 3589
              'p0' => {
3564
              'token' => attachment.token}    
3590
              'token' => attachment.token}
3565 3591
            }
3566 3592
          }
3567 3593
        assert_response :success
......
3583 3609
            :project_id => 1,
3584 3610
            :issue => {
3585 3611
              :tracker_id => '1',
3586
              :subject => 'Saved attachments' 
3587
            },  
3612
              :subject => 'Saved attachments'
3613
            },
3588 3614
            :attachments => {
3589 3615
              'p0' => {
3590
              'token' => attachment.token}    
3616
              'token' => attachment.token}
3591 3617
            }
3592 3618
          }
3593 3619
        assert_response 302
......
3885 3911
            :project_id => '1',
3886 3912
            :tracker_id => '1',
3887 3913
            :status_id => '1'
3888
          },  
3914
          },
3889 3915
          :was_default_status => '1'
3890 3916
        }
3891 3917
    end
......
3929 3955
              :tracker_id => '3',
3930 3956
              :status_id => '1',
3931 3957
              :subject => 'Copy with attachments'
3932
            },  
3958
            },
3933 3959
            :copy_attachments => '1'
3934 3960
          }
3935 3961
      end
......
3977 4003
              :tracker_id => '3',
3978 4004
              :status_id => '1',
3979 4005
              :subject => 'Copy with attachments'
3980
            },  
4006
            },
3981 4007
            :copy_attachments => '1',
3982 4008
            :attachments => {
3983 4009
              '1' => {
......
4076 4102
            :tracker_id => '3',
4077 4103
            :status_id => '1',
4078 4104
            :subject => 'Copy with subtasks'
4079
          },  
4105
          },
4080 4106
          :copy_subtasks => '1'
4081 4107
        }
4082 4108
    end
......
4100 4126
            :status_id => '1',
4101 4127
            :subject => 'Copy with subtasks',
4102 4128
            :custom_field_values => {
4103
            '2' => 'Foo'}    
4104
          },  
4129
            '2' => 'Foo'}
4130
          },
4105 4131
          :copy_subtasks => '1'
4106 4132
        }
4107 4133
    end
......
4242 4268
        :id => 1,
4243 4269
        :issue => {
4244 4270
          :status_id => 5,
4245
          :priority_id => 7 
4246
        },  
4271
          :priority_id => 7
4272
        },
4247 4273
        :time_entry => {
4248 4274
          :hours => '2.5',
4249 4275
          :comments => 'test_get_edit_with_params',
4250
          :activity_id => 10 
4276
          :activity_id => 10
4251 4277
        }
4252 4278
      }
4253 4279
    assert_response :success
......
4486 4512
                :project_id => '1',
4487 4513
                :tracker_id => '2',
4488 4514
                :priority_id => '6'
4489
                
4515

  
4490 4516
              }
4491 4517
            }
4492 4518
        end
......
4549 4575
            :issue => {
4550 4576
              :subject => 'Custom field change',
4551 4577
              :custom_field_values => {
4552
                '1' => ['', 'Oracle', 'PostgreSQL'] 
4553
              }    
4554
              
4578
                '1' => ['', 'Oracle', 'PostgreSQL']
4579
              }
4580

  
4555 4581
            }
4556 4582
          }
4557 4583
      end
......
4572 4598
            :issue => {
4573 4599
              :status_id => 2,
4574 4600
              :assigned_to_id => 3,
4575
              :notes => 'Assigned to dlopper' 
4576
            },  
4601
              :notes => 'Assigned to dlopper'
4602
            },
4577 4603
            :time_entry => {
4578 4604
              :hours => '',
4579 4605
              :comments => '',
4580
              :activity_id => TimeEntryActivity.first 
4606
              :activity_id => TimeEntryActivity.first
4581 4607
            }
4582 4608
          }
4583 4609
      end
......
4603 4629
      put :update, :params => {
4604 4630
          :id => 1,
4605 4631
          :issue => {
4606
            :notes => notes 
4632
            :notes => notes
4607 4633
          }
4608 4634
        }
4609 4635
    end
......
4671 4697
      put :update, :params => {
4672 4698
          :id => 1,
4673 4699
          :issue => {
4674
            :notes => '2.5 hours added' 
4675
          },  
4700
            :notes => '2.5 hours added'
4701
          },
4676 4702
          :time_entry => {
4677 4703
            :hours => '2.5',
4678 4704
            :comments => 'test_put_update_with_note_and_spent_time',
4679
            :activity_id => TimeEntryActivity.first.id 
4705
            :activity_id => TimeEntryActivity.first.id
4680 4706
          }
4681 4707
        }
4682 4708
    end
......
4730 4756
            :id => 1,
4731 4757
            :issue => {
4732 4758
              :notes => ''
4733
            },  
4759
            },
4734 4760
            :attachments => {
4735 4761
              '1' => {
4736
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}    
4762
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}
4737 4763
            }
4738 4764
          }
4739 4765
      end
......
4769 4795
        put :update, :params => {
4770 4796
            :id => 1,
4771 4797
            :issue => {
4772
              :subject => '' 
4773
            },  
4798
              :subject => ''
4799
            },
4774 4800
            :attachments => {
4775 4801
              '1' => {
4776
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}    
4802
              'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}
4777 4803
            }
4778 4804
          }
4779 4805
        assert_response :success
......
4799 4825
        put :update, :params => {
4800 4826
            :id => 1,
4801 4827
            :issue => {
4802
              :subject => '' 
4803
            },  
4828
              :subject => ''
4829
            },
4804 4830
            :attachments => {
4805 4831
              'p0' => {
4806
              'token' => attachment.token}    
4832
              'token' => attachment.token}
4807 4833
            }
4808 4834
          }
4809 4835
        assert_response :success
......
4826 4852
              :id => 1,
4827 4853
              :issue => {
4828 4854
                :notes => 'Attachment added'
4829
              },  
4855
              },
4830 4856
              :attachments => {
4831 4857
                'p0' => {
4832
                'token' => attachment.token}    
4858
                'token' => attachment.token}
4833 4859
              }
4834 4860
            }
4835 4861
          assert_redirected_to '/issues/1'
......
4854 4880
          :id => 1,
4855 4881
          :issue => {
4856 4882
            :notes => ''
4857
          },  
4883
          },
4858 4884
          :attachments => {
4859 4885
            '1' => {
4860
            'file' => uploaded_test_file('testfile.txt', 'text/plain')}    
4886
            'file' => uploaded_test_file('testfile.txt', 'text/plain')}
4861 4887
          }
4862 4888
        }
4863 4889
      assert_redirected_to :action => 'show', :id => '1'
......
4877 4903
            :issue => {
4878 4904
              :notes => 'Removing attachments',
4879 4905
              :deleted_attachment_ids => ['1', '5']
4880
              
4906

  
4881 4907
            }
4882 4908
          }
4883 4909
      end
......
4904 4930
              :subject => '',
4905 4931
              :notes => 'Removing attachments',
4906 4932
              :deleted_attachment_ids => ['1', '5']
4907
              
4933

  
4908 4934
            }
4909 4935
          }
4910 4936
      end
......
4947 4973
            :subject => new_subject,
4948 4974
            :priority_id => '6',
4949 4975
            :category_id => '1' # no change
4950
            
4976

  
4951 4977
          }
4952 4978
        }
4953 4979
      assert_equal 1, ActionMailer::Base.deliveries.size
......
4963 4989
          :id => 1,
4964 4990
          :issue => {
4965 4991
            :notes => notes
4966
          },  
4992
          },
4967 4993
          :time_entry => {
4968 4994
            "comments"=>"", "activity_id"=>"", "hours"=>"2z"
4969 4995
          }
......
4985 5011
          :id => 1,
4986 5012
          :issue => {
4987 5013
            :notes => notes
4988
          },  
5014
          },
4989 5015
          :time_entry => {
4990 5016
            "comments"=>"this is my comment", "activity_id"=>"", "hours"=>""
4991 5017
          }
......
5007 5033
        :id => issue.id,
5008 5034
        :issue => {
5009 5035
          :fixed_version_id => 4
5010
          
5036

  
5011 5037
        }
5012 5038
      }
5013 5039

  
......
5025 5051
        :id => issue.id,
5026 5052
        :issue => {
5027 5053
          :fixed_version_id => 4
5028
          
5029
        },  
5054

  
5055
        },
5030 5056
        :back_url => '/issues'
5031 5057
      }
5032 5058

  
......
5042 5068
        :id => issue.id,
5043 5069
        :issue => {
5044 5070
          :fixed_version_id => 4
5045
          
5046
        },  
5071

  
5072
        },
5047 5073
        :back_url => 'http://google.com'
5048 5074
      }
5049 5075

  
......
5059 5085
        :issue => {
5060 5086
          :status_id => 6,
5061 5087
          :notes => 'Notes'
5062
        },  
5088
        },
5063 5089
        :prev_issue_id => 8,
5064 5090
        :next_issue_id => 12,
5065 5091
        :issue_position => 2,
......
5348 5374
          :priority_id => 7,
5349 5375
          :assigned_to_id => '',
5350 5376
          :custom_field_values => {
5351
          '2' => ''}    
5377
          '2' => ''}
5352 5378
        }
5353 5379
      }
5354 5380

  
......
5378 5404
            :priority_id => '',
5379 5405
            :assigned_to_id => group.id,
5380 5406
            :custom_field_values => {
5381
            '2' => ''}    
5407
            '2' => ''}
5382 5408
          }
5383 5409
        }
5384 5410

  
......
5397 5423
          :priority_id => 7,
5398 5424
          :assigned_to_id => '',
5399 5425
          :custom_field_values => {
5400
          '2' => ''}    
5426
          '2' => ''}
5401 5427
        }
5402 5428
      }
5403 5429

  
......
5425 5451
          :priority_id => 7,
5426 5452
          :assigned_to_id => '',
5427 5453
          :custom_field_values => {
5428
          '2' => ''}    
5454
          '2' => ''}
5429 5455
        }
5430 5456
      }
5431 5457
    assert_response 403
......
5473 5499
        :id => 1,
5474 5500
        :issue => {
5475 5501
          :project_id => '2'
5476
        },  
5502
        },
5477 5503
        :follow => '1'
5478 5504
      }
5479 5505
    assert_redirected_to '/issues/1'
......
5485 5511
        :id => [1, 2],
5486 5512
        :issue => {
5487 5513
          :project_id => '2'
5488
        },  
5514
        },
5489 5515
        :follow => '1'
5490 5516
      }
5491 5517
    assert_redirected_to '/projects/onlinestore/issues'
......
5582 5608
    assert_equal 4.25, Issue.find(2).estimated_hours
5583 5609
  end
5584 5610

  
5611
  def test_bulk_update_remaining_hours
5612
    @request.session[:user_id] = 2
5613
    post :bulk_update, :ids => [1, 2], :issue => {:remaining_hours => 4.15}
5614

  
5615
    assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
5616
    assert_equal 4.15, Issue.find(1).remaining_hours
5617
    assert_equal 4.15, Issue.find(2).remaining_hours
5618
  end
5619

  
5585 5620
  def test_bulk_update_custom_field
5586 5621
    @request.session[:user_id] = 2
5587 5622
    # update issues priority
......
5592 5627
          :priority_id => '',
5593 5628
          :assigned_to_id => '',
5594 5629
          :custom_field_values => {
5595
          '2' => '777'}    
5630
          '2' => '777'}
5596 5631
        }
5597 5632
      }
5598 5633

  
......
5615 5650
          :priority_id => '',
5616 5651
          :assigned_to_id => '',
5617 5652
          :custom_field_values => {
5618
          '1' => '__none__'}    
5653
          '1' => '__none__'}
5619 5654
        }
5620 5655
      }
5621 5656
    assert_response 302
......
5635 5670
          :priority_id => '',
5636 5671
          :assigned_to_id => '',
5637 5672
          :custom_field_values => {
5638
          '1' => ['MySQL', 'Oracle']}    
5673
          '1' => ['MySQL', 'Oracle']}
5639 5674
        }
5640 5675
      }
5641 5676

  
......
5659 5694
          :priority_id => '',
5660 5695
          :assigned_to_id => '',
5661 5696
          :custom_field_values => {
5662
          '1' => ['__none__']}    
5697
          '1' => ['__none__']}
5663 5698
        }
5664 5699
      }
5665 5700
    assert_response 302
......
5815 5850
            :ids => [1, 2],
5816 5851
            :issue => {
5817 5852
              :project_id => '2'
5818
            },  
5853
            },
5819 5854
            :copy => '1'
5820 5855
          }
5821 5856
      end
......
5837 5872
          :ids => [1, 2, 3],
5838 5873
          :issue => {
5839 5874
            :project_id => '2'
5840
          },  
5875
          },
5841 5876
          :copy => '1'
5842 5877
        }
5843 5878
      assert_response 302
......
5852 5887
        :ids => [1, 2, 3],
5853 5888
        :issue => {
5854 5889
          :project_id => ''
5855
        },  
5890
        },
5856 5891
        :copy => '1'
5857 5892
      }
5858 5893
    assert_response 403
......
5866 5901
        :ids => [1, 2, 3],
5867 5902
        :issue => {
5868 5903
          :project_id => '1'
5869
        },  
5904
        },
5870 5905
        :copy => '1'
5871 5906
      }
5872 5907
    assert_response 403
......
5893 5928
            :status_id => '',
5894 5929
            :start_date => '',
5895 5930
            :due_date => ''
5896
            
5931

  
5897 5932
          }
5898 5933
        }
5899 5934
    end
......
5933 5968
              :status_id => '1',
5934 5969
              :start_date => '2009-12-01',
5935 5970
              :due_date => '2009-12-31'
5936
              
5971

  
5937 5972
            }
5938 5973
          }
5939 5974
      end
......
5963 5998
            :status_id => '3',
5964 5999
            :start_date => '2009-12-01',
5965 6000
            :due_date => '2009-12-31'
5966
            
6001

  
5967 6002
          }
5968 6003
        }
5969 6004
    end
......
5986 6021
            :copy_attachments => '0',
5987 6022
            :issue => {
5988 6023
              :project_id => ''
5989
              
6024

  
5990 6025
            }
5991 6026
          }
5992 6027
      end
......
6006 6041
            :copy_attachments => '1',
6007 6042
            :issue => {
6008 6043
              :project_id => ''
6009
              
6044

  
6010 6045
            }
6011 6046
          }
6012 6047
      end
......
6024 6059
            :link_copy => '1',
6025 6060
            :issue => {
6026 6061
              :project_id => '1'
6027
              
6062

  
6028 6063
            }
6029 6064
          }
6030 6065
      end
......
6042 6077
          :copy_subtasks => '0',
6043 6078
          :issue => {
6044 6079
            :project_id => ''
6045
            
6080

  
6046 6081
          }
6047 6082
        }
6048 6083
    end
......
6060 6095
          :copy_subtasks => '1',
6061 6096
          :issue => {
6062 6097
            :project_id => ''
6063
            
6098

  
6064 6099
          }
6065 6100
        }
6066 6101
    end
......
6079 6114
          :copy_watchers => '1',
6080 6115
          :issue => {
6081 6116
            :project_id => ''
6082
            
6117

  
6083 6118
          }
6084 6119
        }
6085 6120
    end
......
6099 6134
          :copy_subtasks => '1',
6100 6135
          :issue => {
6101 6136
            :project_id => ''
6102
            
6137

  
6103 6138
          }
6104 6139
        }
6105 6140
    end
......
6114 6149
        :copy => '1',
6115 6150
        :issue => {
6116 6151
          :project_id => 2
6117
        },  
6152
        },
6118 6153
        :follow => '1'
6119 6154
      }
6120 6155
    issue = Issue.order('id DESC').first
test/integration/api_test/issues_test.rb
382 382
    end
383 383
  end
384 384

  
385
  test "GET /issues/:id.xml should contains total_estimated_hours and total_spent_hours" do
385
  test "GET /issues/:id.xml should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
386 386
    parent = Issue.find(3)
387 387
    parent.update_columns :estimated_hours => 2.0
388
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
388
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
389 389
    TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
390 390
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
391 391
    get '/issues/3.xml'
......
394 394
    assert_select 'issue' do
395 395
      assert_select 'estimated_hours',       parent.estimated_hours.to_s
396 396
      assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
397
      assert_select 'remaining_hours',       parent.remaining_hours.to_s
398
      assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s
397 399
      assert_select 'spent_hours',           parent.spent_hours.to_s
398 400
      assert_select 'total_spent_hours',     (parent.spent_hours.to_f + 2.5).to_s
399 401
    end
400 402
  end
401 403

  
402
  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
404
  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
403 405
    parent = Issue.find(3)
404 406
    parent.update_columns :estimated_hours => 2.0
405
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
407
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
406 408
    # remove permission!
407 409
    Role.anonymous.remove_permission! :view_time_entries
408 410
    #Role.all.each { |role| role.remove_permission! :view_time_entries }
......
412 414
    assert_select 'issue' do
413 415
      assert_select 'estimated_hours',       parent.estimated_hours.to_s
414 416
      assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
417
      assert_select 'remaining_hours',       parent.remaining_hours.to_s
418
      assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s
415 419
      assert_select 'spent_hours',           false
416 420
      assert_select 'total_spent_hours',     false
417 421
    end
......
434 438
    end
435 439
  end
436 440

  
437
  test "GET /issues/:id.json should contains total_estimated_hours and total_spent_hours" do
441
  test "GET /issues/:id.json should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
438 442
    parent = Issue.find(3)
439 443
    parent.update_columns :estimated_hours => 2.0
440
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
444
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
441 445
    TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
442 446
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
443 447
    get '/issues/3.json'
......
446 450
    json = ActiveSupport::JSON.decode(response.body)
447 451
    assert_equal parent.estimated_hours, json['issue']['estimated_hours']
448 452
    assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
453
    assert_equal parent.remaining_hours, json['issue']['remaining_hours']
454
    assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours']
449 455
    assert_equal parent.spent_hours, json['issue']['spent_hours']
450 456
    assert_equal (parent.spent_hours.to_f + 2.5), json['issue']['total_spent_hours']
451 457
  end
452 458

  
453
  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
459
  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
454 460
    parent = Issue.find(3)
455 461
    parent.update_columns :estimated_hours => 2.0
456
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
462
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
457 463
    # remove permission!
458 464
    Role.anonymous.remove_permission! :view_time_entries
459 465
    #Role.all.each { |role| role.remove_permission! :view_time_entries }
......
463 469
    json = ActiveSupport::JSON.decode(response.body)
464 470
    assert_equal parent.estimated_hours, json['issue']['estimated_hours']
465 471
    assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
472
    assert_equal parent.remaining_hours, json['issue']['remaining_hours']
473
    assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours']
466 474
    assert_nil json['issue']['spent_hours']
467 475
    assert_nil json['issue']['total_spent_hours']
468 476
  end
test/unit/helpers/issues_helper_test.rb
196 196
    assert_match '6.30', show_detail(detail, true)
197 197
  end
198 198

  
199
  test 'show_detail should show old and new values with a remaining hours attribute' do
200
    detail = JournalDetail.new(:property => 'attr', :prop_key => 'remaining_hours',
201
                               :old_value => '6.3', :value => '5')
202
    assert_match '5.00', show_detail(detail, true)
203
    assert_match '6.30', show_detail(detail, true)
204
  end
205

  
199 206
  test 'show_detail should not show values with a description attribute' do
200 207
    detail = JournalDetail.new(:property => 'attr', :prop_key => 'description',
201 208
                               :old_value => 'Foo', :value => 'Bar')
test/unit/issue_subtasking_test.rb
28 28
  def test_leaf_planning_fields_should_be_editable
29 29
    issue = Issue.generate!
30 30
    user = User.find(1)
31
    %w(priority_id done_ratio start_date due_date estimated_hours).each do |attribute|
31
    %w(priority_id done_ratio start_date due_date estimated_hours remaining_hours).each do |attribute|
32 32
      assert issue.safe_attribute?(attribute, user)
33 33
    end
34 34
  end
......
339 339
    assert !child.save
340 340
    assert_include I18n.t("activerecord.errors.messages.open_issue_with_closed_parent"), child.errors.full_messages
341 341
  end
342

  
343
  def test_parent_total_remaining_hours_should_be_sum_of_descendants
344
    parent = Issue.generate!
345
    parent.generate_child!(:remaining_hours => nil)
346
    assert_equal 0, parent.reload.total_remaining_hours
347
    parent.generate_child!(:remaining_hours => 5)
348
    assert_equal 5, parent.reload.total_remaining_hours
349
    parent.generate_child!(:remaining_hours => 7)
350
    assert_equal 12, parent.reload.total_remaining_hours
351
  end
342 352
end
test/unit/issue_test.rb
56 56
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
57 57
                      :status_id => 1, :priority => IssuePriority.all.first,
58 58
                      :subject => 'test_create',
59
                      :description => 'IssueTest#test_create', :estimated_hours => '1:30')
59
                      :description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1')
60 60
    assert issue.save
61 61
    issue.reload
62 62
    assert_equal 1.5, issue.estimated_hours
63
    assert_equal 1, issue.remaining_hours
63 64
  end
64 65

  
65 66
  def test_create_minimal
......
68 69
    assert_equal issue.tracker.default_status, issue.status
69 70
    assert issue.description.nil?
70 71
    assert_nil issue.estimated_hours
72
    assert_nil issue.remaining_hours
71 73
  end
72 74

  
73 75
  def test_create_with_all_fields_disabled
......
134 136
    end
135 137
  end
136 138

  
139
  def test_remaining_hours_update_with_negative_value_should_set_to_zero
140
    set_language_if_valid 'en'
141
    ['-4'].each do |invalid|
142
      issue = Issue.new(:remaining_hours => invalid)
143
      assert_equal 0, issue.remaining_hours
144
    end
145
  end
146

  
147
  def test_remaining_hours_should_be_set_from_estimated_hours_when_is_empty
148
      issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
149
                      :status_id => 1, :priority => IssuePriority.all.first,
150
                      :subject => 'test_create',
151
                      :description => 'IssueTest#test_create', :estimated_hours => '1:30')
152
      assert issue.save
153
      assert_equal 1.5, issue.remaining_hours
154
  end
155

  
137 156
  def test_create_with_required_custom_field
138 157
    set_language_if_valid 'en'
139 158
    field = IssueCustomField.find_by_name('Database')
......
2460 2479
    user = User.find(3)
2461 2480
    user.members.update_all ["mail_notification = ?", false]
2462 2481
    user.update! :mail_notification => 'only_assigned'
2482
    puts user.inspect
2463 2483

  
2464 2484
    with_settings :notified_events => %w(issue_updated) do
2465 2485
      issue = Issue.find(2)
......
2952 2972
    assert_equal false, issue.closing?
2953 2973
  end
2954 2974

  
2975
  def test_closing_should_set_remaining_hours_to_zero
2976
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
2977
                      :status_id => 1, :priority => IssuePriority.all.first,
2978
                      :subject => 'test_create',
2979
                      :description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1')
2980
    assert_equal 1, issue.remaining_hours
2981
    issue.status_id = 5
2982
    issue.save!
2983
    assert_equal 0, issue.remaining_hours
2984
  end
2985

  
2955 2986
  def test_reopening_should_return_true_when_reopening_an_issue
2956 2987
    issue = Issue.find(8)
2957 2988
    issue.status = IssueStatus.find(6)
test/unit/mail_handler_test.rb
43 43
  def test_add_issue_with_specific_overrides
44 44
    issue = submit_email('ticket_on_given_project.eml',
45 45
      :allow_override => ['status', 'start_date', 'due_date', 'assigned_to',
46
                          'fixed_version', 'estimated_hours', 'done_ratio']
46
                          'fixed_version', 'estimated_hours', 'remaining_hours', 'done_ratio']
47 47
    )
48 48
    assert issue.is_a?(Issue)
49 49
    assert !issue.new_record?
......
59 59
    assert_equal User.find_by_login('jsmith'), issue.assigned_to
60 60
    assert_equal Version.find_by_name('Alpha'), issue.fixed_version
61 61
    assert_equal 2.5, issue.estimated_hours
62
    assert_equal 1, issue.remaining_hours
... This diff was truncated because it exceeds the maximum size that can be displayed.
(9-9/10)