diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index f867760..c270455 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -150,6 +150,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}") @@ -408,7 +420,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 old mode 100644 new mode 100755 index f984e03..60da138 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -66,6 +66,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 @@ -107,8 +108,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, :set_assigned_to_was + :force_updated_on_change, :update_closed_on, :set_assigned_to_was, :update_remaining_hours after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :delete_selected_attachments, :create_journal @@ -245,6 +247,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) @@ -446,6 +449,13 @@ class Issue < ActiveRecord::Base write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : 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', 'status_id', @@ -459,6 +469,7 @@ class Issue < ActiveRecord::Base 'due_date', 'done_ratio', 'estimated_hours', + 'remaining_hours', 'custom_field_values', 'custom_fields', 'lock_version', @@ -1092,6 +1103,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 @@ -1834,6 +1853,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 @@ -1868,4 +1894,10 @@ class Issue < ActiveRecord::Base self.done_ratio ||= 0 end 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 ad04c0b..5084c80 100644 --- a/app/models/issue_import.rb +++ b/app/models/issue_import.rb @@ -162,6 +162,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 dedbe8d..46ea43c 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -36,10 +36,15 @@ class IssueQuery < Query QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true), + QueryColumn.new(:remaining_hours, :sortable => "#{Issue.table_name}.remaining_hours", :totalable => true), QueryColumn.new(:total_estimated_hours, :sortable => "COALESCE((SELECT SUM(estimated_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(: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), QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), @@ -134,6 +139,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 add_available_filter "done_ratio", :type => :integer if User.current.allowed_to?(:set_issues_private, nil, :global => true) || @@ -249,6 +255,11 @@ class IssueQuery < Query map_total(scope.sum(:estimated_hours)) {|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 = if group_by_column.try(:name) == :project diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 9bcc1b1..7d53e3c 100755 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -424,6 +424,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') }.delete_if {|k, v| v.blank? } diff --git a/app/models/tracker.rb b/app/models/tracker.rb old mode 100644 new mode 100755 index 5926321..4a47990 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -21,7 +21,7 @@ class Tracker < ActiveRecord::Base CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject priority_id 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).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).freeze CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze before_destroy :check_integrity diff --git a/app/views/imports/_fields_mapping.html.erb b/app/views/imports/_fields_mapping.html.erb index 0e1d455..76826e9 100644 --- a/app/views/imports/_fields_mapping.html.erb +++ b/app/views/imports/_fields_mapping.html.erb @@ -82,6 +82,10 @@ <%= mapping_select_tag @import, 'estimated_hours' %>

+ + <%= mapping_select_tag @import, 'remaining_hours' %> +

+

<%= mapping_select_tag @import, 'done_ratio' %>

diff --git a/app/views/issues/_attributes.html.erb b/app/views/issues/_attributes.html.erb old mode 100644 new mode 100755 index 640a00e..a7b243d --- a/app/views/issues/_attributes.html.erb +++ b/app/views/issues/_attributes.html.erb @@ -64,12 +64,16 @@

<% end %> +<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %> +

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %>

+<% end %> + <% if @issue.safe_attribute? 'estimated_hours' %>

<%= f.hours_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %>

<% end %> -<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %> -

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %>

+<% if @issue.safe_attribute? 'remaining_hours' %> +

<%= f.text_field :remaining_hours, :size => 3, :required => @issue.required_attribute?('remaining_hours') %> <%= l(:field_hours) %>

<% end %> diff --git a/app/views/issues/_edit.html.erb b/app/views/issues/_edit.html.erb old mode 100644 new mode 100755 diff --git a/app/views/issues/bulk_edit.html.erb b/app/views/issues/bulk_edit.html.erb index 7e10d03..0a95c2c 100644 --- a/app/views/issues/bulk_edit.html.erb +++ b/app/views/issues/bulk_edit.html.erb @@ -181,6 +181,14 @@

<% end %> +<% if @safe_attributes.include?('remaining_hours') %> +

+ + <%= text_field_tag 'issue[remaining_hours]', '', :value => @issue_params[:remaining_hours], :size => 10 %> + +

+<% end %> + <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>

diff --git a/app/views/issues/index.api.rsb b/app/views/issues/index.api.rsb index 4bba325..06d837f 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 render_api_custom_values issue.visible_custom_field_values, api diff --git a/app/views/issues/show.api.rsb b/app/views/issues/show.api.rsb index f474ed9..7e7fe1d 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 old mode 100644 new mode 100755 index bac3dc5..e69dc42 --- a/app/views/issues/show.html.erb +++ b/app/views/issues/show.html.erb @@ -67,6 +67,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 old mode 100644 new mode 100755 index 50a003f..b8ad6aa --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -375,6 +375,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 setting_app_title: Application title setting_app_subtitle: Application subtitle 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..ecfbe11 --- /dev/null +++ b/db/migrate/20160920184857_add_remaining_hours_to_issues.rb @@ -0,0 +1,10 @@ +class AddRemainingHoursToIssues < ActiveRecord::Migration + 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 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 e311dfb..aee6478 --- 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 --- This line starts with a delimiter and should not be stripped @@ -51,10 +52,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 old mode 100644 new mode 100755 index 53c90bc..5082e3b --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -152,7 +152,7 @@ class IssuesControllerTest < Redmine::ControllerTest :f => ['tracker_id'], :op => { 'tracker_id' => '=' - }, + }, :v => { 'tracker_id' => ['1'] } @@ -253,10 +253,10 @@ class IssuesControllerTest < Redmine::ControllerTest :f => [filter_name], :op => { filter_name => '=' - }, + }, :v => { filter_name => ['Foo'] - }, + }, :c => ['project'] } assert_response :success @@ -970,6 +970,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!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user') CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2') @@ -1146,6 +1153,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 :index, :params => { @@ -1352,6 +1364,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.5, :category_id => 1) @@ -1415,7 +1439,7 @@ class IssuesControllerTest < Redmine::ControllerTest :f => ['start_date'], :op => { :start_date => '=' - }, + }, :format => 'csv' } assert_equal 'text/csv', @response.content_type @@ -2590,7 +2614,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => 3, :description => 'Prefilled', :custom_field_values => { - '2' => 'Custom field value'} + '2' => 'Custom field value'} } } @@ -2714,7 +2738,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert !t.disabled_core_fields.include?('parent_issue_id') get :new, :params => { - :project_id => 1, issue: { parent_issue_id: 1 + :project_id => 1, issue: { parent_issue_id: 1 } } assert_response :success @@ -2724,7 +2748,7 @@ class IssuesControllerTest < Redmine::ControllerTest t.save! assert t.disabled_core_fields.include?('parent_issue_id') get :new, :params => { - :project_id => 1, issue: { parent_issue_id: 1 + :project_id => 1, issue: { parent_issue_id: 1 } } assert_response :success @@ -2782,7 +2806,7 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :tracker_id => 2, :status_id => 1 - }, + }, :was_default_status => 1 } assert_response :success @@ -2801,7 +2825,7 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :project_id => 1, :fixed_version_id => '' - }, + }, :form_update_triggered_by => 'issue_project_id' } assert_response :success @@ -2828,8 +2852,9 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 5, :start_date => '2010-11-07', :estimated_hours => '', + :remaining_hours => '', :custom_field_values => { - '2' => 'Value for field 2'} + '2' => 'Value for field 2'} } } end @@ -2843,6 +2868,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 @@ -2888,7 +2914,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 5, :estimated_hours => '', :custom_field_values => { - '2' => 'Value for field 2'} + '2' => 'Value for field 2'} } } end @@ -2914,7 +2940,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 5, :estimated_hours => '', :custom_field_values => { - '2' => 'Value for field 2'} + '2' => 'Value for field 2'} } } end @@ -2935,7 +2961,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => 3, :subject => 'This is first issue', :priority_id => 5 - }, + }, :continue => '' } end @@ -2977,7 +3003,7 @@ class IssuesControllerTest < Redmine::ControllerTest :description => 'This is the description', :priority_id => 5, :custom_field_values => { - '1' => ['', 'MySQL', 'Oracle']} + '1' => ['', 'MySQL', 'Oracle']} } } end @@ -3000,7 +3026,7 @@ class IssuesControllerTest < Redmine::ControllerTest :description => 'This is the description', :priority_id => 5, :custom_field_values => { - '1' => ['']} + '1' => ['']} } } end @@ -3023,7 +3049,7 @@ class IssuesControllerTest < Redmine::ControllerTest :description => 'This is the description', :priority_id => 5, :custom_field_values => { - field.id.to_s => ['', '2', '3']} + field.id.to_s => ['', '2', '3']} } } end @@ -3071,8 +3097,8 @@ class IssuesControllerTest < Redmine::ControllerTest :due_date => '', :custom_field_values => { cf1.id.to_s => '', cf2.id.to_s => '' - } - + } + } } assert_response :success @@ -3101,8 +3127,8 @@ class IssuesControllerTest < Redmine::ControllerTest :due_date => '', :custom_field_values => { cf1.id.to_s => '', cf2.id.to_s => [''] - } - + } + } } assert_response :success @@ -3131,8 +3157,8 @@ class IssuesControllerTest < Redmine::ControllerTest :due_date => '2012-07-16', :custom_field_values => { cf1.id.to_s => 'value1', cf2.id.to_s => 'value2' - } - + } + } } assert_response 302 @@ -3158,7 +3184,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => 1, :status_id => 1, :subject => 'Test' - + } } assert_response 302 @@ -3336,7 +3362,7 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 3, :tracker_id => 2, :subject => 'Foo' - }, + }, :continue => '1' } assert_redirected_to '/issues/new?issue%5Bproject_id%5D=3&issue%5Btracker_id%5D=2' @@ -3389,7 +3415,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 5, :estimated_hours => '', :custom_field_values => { - '2' => 'Value for field 2'} + '2' => 'Value for field 2'} } } end @@ -3448,7 +3474,7 @@ class IssuesControllerTest < Redmine::ControllerTest post :create, :params => { :project_id => 1, :issue => { - :tracker => "A param can not be a Tracker" + :tracker => "A param can not be a Tracker" } } end @@ -3465,11 +3491,11 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 1, :issue => { :tracker_id => '1', - :subject => 'With attachment' - }, + :subject => 'With attachment' + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} } } end @@ -3500,11 +3526,11 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 1, :issue => { :tracker_id => '1', - :subject => 'With attachment' - }, + :subject => 'With attachment' + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} } } end @@ -3526,11 +3552,11 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 1, :issue => { :tracker_id => '1', - :subject => '' - }, + :subject => '' + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} } } assert_response :success @@ -3557,11 +3583,11 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 1, :issue => { :tracker_id => '1', - :subject => '' - }, + :subject => '' + }, :attachments => { 'p0' => { - 'token' => attachment.token} + 'token' => attachment.token} } } assert_response :success @@ -3583,11 +3609,11 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => 1, :issue => { :tracker_id => '1', - :subject => 'Saved attachments' - }, + :subject => 'Saved attachments' + }, :attachments => { 'p0' => { - 'token' => attachment.token} + 'token' => attachment.token} } } assert_response 302 @@ -3885,7 +3911,7 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => '1', :tracker_id => '1', :status_id => '1' - }, + }, :was_default_status => '1' } end @@ -3929,7 +3955,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments' - }, + }, :copy_attachments => '1' } end @@ -3977,7 +4003,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments' - }, + }, :copy_attachments => '1', :attachments => { '1' => { @@ -4076,7 +4102,7 @@ class IssuesControllerTest < Redmine::ControllerTest :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks' - }, + }, :copy_subtasks => '1' } end @@ -4100,8 +4126,8 @@ class IssuesControllerTest < Redmine::ControllerTest :status_id => '1', :subject => 'Copy with subtasks', :custom_field_values => { - '2' => 'Foo'} - }, + '2' => 'Foo'} + }, :copy_subtasks => '1' } end @@ -4242,12 +4268,12 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :status_id => 5, - :priority_id => 7 - }, + :priority_id => 7 + }, :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', - :activity_id => 10 + :activity_id => 10 } } assert_response :success @@ -4486,7 +4512,7 @@ class IssuesControllerTest < Redmine::ControllerTest :project_id => '1', :tracker_id => '2', :priority_id => '6' - + } } end @@ -4549,9 +4575,9 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :subject => 'Custom field change', :custom_field_values => { - '1' => ['', 'Oracle', 'PostgreSQL'] - } - + '1' => ['', 'Oracle', 'PostgreSQL'] + } + } } end @@ -4572,12 +4598,12 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :status_id => 2, :assigned_to_id => 3, - :notes => 'Assigned to dlopper' - }, + :notes => 'Assigned to dlopper' + }, :time_entry => { :hours => '', :comments => '', - :activity_id => TimeEntryActivity.first + :activity_id => TimeEntryActivity.first } } end @@ -4603,7 +4629,7 @@ class IssuesControllerTest < Redmine::ControllerTest put :update, :params => { :id => 1, :issue => { - :notes => notes + :notes => notes } } end @@ -4671,12 +4697,12 @@ class IssuesControllerTest < Redmine::ControllerTest put :update, :params => { :id => 1, :issue => { - :notes => '2.5 hours added' - }, + :notes => '2.5 hours added' + }, :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', - :activity_id => TimeEntryActivity.first.id + :activity_id => TimeEntryActivity.first.id } } end @@ -4730,10 +4756,10 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :notes => '' - }, + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} } } end @@ -4769,11 +4795,11 @@ class IssuesControllerTest < Redmine::ControllerTest put :update, :params => { :id => 1, :issue => { - :subject => '' - }, + :subject => '' + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'} } } assert_response :success @@ -4799,11 +4825,11 @@ class IssuesControllerTest < Redmine::ControllerTest put :update, :params => { :id => 1, :issue => { - :subject => '' - }, + :subject => '' + }, :attachments => { 'p0' => { - 'token' => attachment.token} + 'token' => attachment.token} } } assert_response :success @@ -4826,10 +4852,10 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :notes => 'Attachment added' - }, + }, :attachments => { 'p0' => { - 'token' => attachment.token} + 'token' => attachment.token} } } assert_redirected_to '/issues/1' @@ -4854,10 +4880,10 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :notes => '' - }, + }, :attachments => { '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain')} + 'file' => uploaded_test_file('testfile.txt', 'text/plain')} } } assert_redirected_to :action => 'show', :id => '1' @@ -4877,7 +4903,7 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :notes => 'Removing attachments', :deleted_attachment_ids => ['1', '5'] - + } } end @@ -4904,7 +4930,7 @@ class IssuesControllerTest < Redmine::ControllerTest :subject => '', :notes => 'Removing attachments', :deleted_attachment_ids => ['1', '5'] - + } } end @@ -4947,7 +4973,7 @@ class IssuesControllerTest < Redmine::ControllerTest :subject => new_subject, :priority_id => '6', :category_id => '1' # no change - + } } assert_equal 1, ActionMailer::Base.deliveries.size @@ -4963,7 +4989,7 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :notes => notes - }, + }, :time_entry => { "comments"=>"", "activity_id"=>"", "hours"=>"2z" } @@ -4985,7 +5011,7 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :notes => notes - }, + }, :time_entry => { "comments"=>"this is my comment", "activity_id"=>"", "hours"=>"" } @@ -5007,7 +5033,7 @@ class IssuesControllerTest < Redmine::ControllerTest :id => issue.id, :issue => { :fixed_version_id => 4 - + } } @@ -5025,8 +5051,8 @@ class IssuesControllerTest < Redmine::ControllerTest :id => issue.id, :issue => { :fixed_version_id => 4 - - }, + + }, :back_url => '/issues' } @@ -5042,8 +5068,8 @@ class IssuesControllerTest < Redmine::ControllerTest :id => issue.id, :issue => { :fixed_version_id => 4 - - }, + + }, :back_url => 'http://google.com' } @@ -5059,7 +5085,7 @@ class IssuesControllerTest < Redmine::ControllerTest :issue => { :status_id => 6, :notes => 'Notes' - }, + }, :prev_issue_id => 8, :next_issue_id => 12, :issue_position => 2, @@ -5348,7 +5374,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 7, :assigned_to_id => '', :custom_field_values => { - '2' => ''} + '2' => ''} } } @@ -5378,7 +5404,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => '', :assigned_to_id => group.id, :custom_field_values => { - '2' => ''} + '2' => ''} } } @@ -5397,7 +5423,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 7, :assigned_to_id => '', :custom_field_values => { - '2' => ''} + '2' => ''} } } @@ -5425,7 +5451,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => 7, :assigned_to_id => '', :custom_field_values => { - '2' => ''} + '2' => ''} } } assert_response 403 @@ -5473,7 +5499,7 @@ class IssuesControllerTest < Redmine::ControllerTest :id => 1, :issue => { :project_id => '2' - }, + }, :follow => '1' } assert_redirected_to '/issues/1' @@ -5485,7 +5511,7 @@ class IssuesControllerTest < Redmine::ControllerTest :id => [1, 2], :issue => { :project_id => '2' - }, + }, :follow => '1' } assert_redirected_to '/projects/onlinestore/issues' @@ -5582,6 +5608,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 @@ -5592,7 +5627,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => '', :assigned_to_id => '', :custom_field_values => { - '2' => '777'} + '2' => '777'} } } @@ -5615,7 +5650,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => '', :assigned_to_id => '', :custom_field_values => { - '1' => '__none__'} + '1' => '__none__'} } } assert_response 302 @@ -5635,7 +5670,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => '', :assigned_to_id => '', :custom_field_values => { - '1' => ['MySQL', 'Oracle']} + '1' => ['MySQL', 'Oracle']} } } @@ -5659,7 +5694,7 @@ class IssuesControllerTest < Redmine::ControllerTest :priority_id => '', :assigned_to_id => '', :custom_field_values => { - '1' => ['__none__']} + '1' => ['__none__']} } } assert_response 302 @@ -5815,7 +5850,7 @@ class IssuesControllerTest < Redmine::ControllerTest :ids => [1, 2], :issue => { :project_id => '2' - }, + }, :copy => '1' } end @@ -5837,7 +5872,7 @@ class IssuesControllerTest < Redmine::ControllerTest :ids => [1, 2, 3], :issue => { :project_id => '2' - }, + }, :copy => '1' } assert_response 302 @@ -5852,7 +5887,7 @@ class IssuesControllerTest < Redmine::ControllerTest :ids => [1, 2, 3], :issue => { :project_id => '' - }, + }, :copy => '1' } assert_response 403 @@ -5866,7 +5901,7 @@ class IssuesControllerTest < Redmine::ControllerTest :ids => [1, 2, 3], :issue => { :project_id => '1' - }, + }, :copy => '1' } assert_response 403 @@ -5893,7 +5928,7 @@ class IssuesControllerTest < Redmine::ControllerTest :status_id => '', :start_date => '', :due_date => '' - + } } end @@ -5933,7 +5968,7 @@ class IssuesControllerTest < Redmine::ControllerTest :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31' - + } } end @@ -5963,7 +5998,7 @@ class IssuesControllerTest < Redmine::ControllerTest :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31' - + } } end @@ -5986,7 +6021,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_attachments => '0', :issue => { :project_id => '' - + } } end @@ -6006,7 +6041,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_attachments => '1', :issue => { :project_id => '' - + } } end @@ -6024,7 +6059,7 @@ class IssuesControllerTest < Redmine::ControllerTest :link_copy => '1', :issue => { :project_id => '1' - + } } end @@ -6042,7 +6077,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_subtasks => '0', :issue => { :project_id => '' - + } } end @@ -6060,7 +6095,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_subtasks => '1', :issue => { :project_id => '' - + } } end @@ -6079,7 +6114,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_watchers => '1', :issue => { :project_id => '' - + } } end @@ -6099,7 +6134,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy_subtasks => '1', :issue => { :project_id => '' - + } } end @@ -6114,7 +6149,7 @@ class IssuesControllerTest < Redmine::ControllerTest :copy => '1', :issue => { :project_id => 2 - }, + }, :follow => '1' } issue = Issue.order('id DESC').first diff --git a/test/integration/api_test/issues_test.rb b/test/integration/api_test/issues_test.rb old mode 100644 new mode 100755 index 142b473..3044c68 --- a/test/integration/api_test/issues_test.rb +++ b/test/integration/api_test/issues_test.rb @@ -382,10 +382,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' @@ -394,15 +394,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) # remove permission! Role.anonymous.remove_permission! :view_time_entries #Role.all.each { |role| role.remove_permission! :view_time_entries } @@ -412,6 +414,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 @@ -434,10 +438,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' @@ -446,14 +450,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) # remove permission! Role.anonymous.remove_permission! :view_time_entries #Role.all.each { |role| role.remove_permission! :view_time_entries } @@ -463,6 +469,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/helpers/issues_helper_test.rb b/test/unit/helpers/issues_helper_test.rb old mode 100644 new mode 100755 index afac095..eb9757b --- a/test/unit/helpers/issues_helper_test.rb +++ b/test/unit/helpers/issues_helper_test.rb @@ -196,6 +196,13 @@ class IssuesHelperTest < Redmine::HelperTest assert_match '6.30', 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/unit/issue_subtasking_test.rb b/test/unit/issue_subtasking_test.rb old mode 100644 new mode 100755 index a50df02..c44c8ac --- a/test/unit/issue_subtasking_test.rb +++ b/test/unit/issue_subtasking_test.rb @@ -28,7 +28,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 @@ -339,4 +339,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 old mode 100644 new mode 100755 index df242a1..bdc8443 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -56,10 +56,11 @@ class IssueTest < ActiveSupport::TestCase 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') + :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 @@ -68,6 +69,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 @@ -134,6 +136,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') @@ -2460,6 +2479,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) @@ -2952,6 +2972,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 old mode 100644 new mode 100755 index f17161c..da88d1d --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -43,7 +43,7 @@ class MailHandlerTest < ActiveSupport::TestCase def test_add_issue_with_specific_overrides issue = submit_email('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'] ) assert issue.is_a?(Issue) assert !issue.new_record? @@ -59,11 +59,13 @@ 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 # 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 @@ -80,6 +82,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 end @@ -101,6 +104,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 end diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb old mode 100644 new mode 100755 index cdf008a..59aea02 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -1513,8 +1513,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 @@ -1529,6 +1529,11 @@ class QueryTest < ActiveSupport::TestCase assert_include :estimated_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) @@ -1571,6 +1576,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)