commit 270b2cb741922fbf1d81e0bc4c9d9617937df323 Author: Marius BALTEANU Date: Sun Jul 9 23:13:19 2017 +0000 add remaining time form to timelog form diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb old mode 100644 new mode 100755 index 1e0f5bc..f5008ea --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -25,6 +25,7 @@ class TimeEntry < ActiveRecord::Base belongs_to :activity, :class_name => 'TimeEntryActivity' attr_protected :user_id, :tyear, :tmonth, :tweek + attr_accessor :remaining_time_action, :remaining_time_hours acts_as_customizable acts_as_event :title => Proc.new { |o| @@ -45,11 +46,16 @@ class TimeEntry < ActiveRecord::Base validates_presence_of :issue_id, :if => lambda { Setting.timelog_required_fields.include?('issue_id') } validates_presence_of :comments, :if => lambda { Setting.timelog_required_fields.include?('comments') } validates_numericality_of :hours, :allow_nil => true, :message => :invalid + validates :remaining_time_action, :inclusion => { :in => ['auto', 'set', 'nothing'] }, :allow_nil => true + validates :remaining_time_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_blank => true, :message => :invalid} + validates_length_of :comments, :maximum => 1024, :allow_nil => true validates :spent_on, :date => true before_validation :set_project_if_nil validate :validate_time_entry + after_save :update_issue_remaining_hours + scope :visible, lambda {|*args| joins(:project). where(TimeEntry.visible_condition(args.shift || User.current, *args)) @@ -62,7 +68,7 @@ class TimeEntry < ActiveRecord::Base where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") } - safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields' + safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields', 'remaining_time_action', 'remaining_time_hours' # Returns a SQL conditions string used to find all time entries visible by the specified user def self.visible_condition(user, options={}) @@ -126,6 +132,7 @@ class TimeEntry < ActiveRecord::Base errors.add :project_id, :invalid if project.nil? errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) || @invalid_issue_id errors.add :activity_id, :inclusion if activity_id_changed? && project && !project.activities.include?(activity) + errors.add :remaining_time_hours, :invalid if (remaining_time_action == 'set' && remaining_time_hours.blank?) end def hours=(h) @@ -166,4 +173,32 @@ class TimeEntry < ActiveRecord::Base def editable_custom_fields(user=nil) editable_custom_field_values(user).map(&:custom_field).uniq end + + def remaining_time_action + @remaining_time_action + end + + def remaining_time_hours + @remaining_time_hours + end + + def update_issue_remaining_hours + issue = self.issue + return unless issue + + h = (remaining_time_hours.is_a?(String)) ? remaining_time_hours.to_hours : remaining_time_hours + + case remaining_time_action + when "auto" + new_remaining_hours = issue.remaining_hours - self.hours unless issue.remaining_hours.nil? + when "set" + new_remaining_hours = h + when "nothing" + return + end + + issue.init_journal(User.current) + issue.remaining_hours = new_remaining_hours + issue.save + end end diff --git a/app/views/issues/_edit.html.erb b/app/views/issues/_edit.html.erb index fe2119a..982c11d 100755 --- a/app/views/issues/_edit.html.erb +++ b/app/views/issues/_edit.html.erb @@ -24,6 +24,9 @@ <% @time_entry.custom_field_values.each do |value| %>

<%= custom_field_tag_with_label :time_entry, value %>

<% end %> +

+ <%= render :partial => 'timelog/remaining_time', :locals => { :issue => @issue } %> +

<% end %> <% end %> diff --git a/app/views/timelog/_form.html.erb b/app/views/timelog/_form.html.erb old mode 100644 new mode 100755 index aa62ffc..f1755d0 --- a/app/views/timelog/_form.html.erb +++ b/app/views/timelog/_form.html.erb @@ -23,6 +23,9 @@ <% @time_entry.custom_field_values.each do |value| %>

<%= custom_field_tag_with_label :time_entry, value %>

<% end %> +

+ <%= render :partial => 'remaining_time', :locals => { :issue => @time_entry.issue } %> +

<%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> diff --git a/app/views/timelog/_remaining_time.html.erb b/app/views/timelog/_remaining_time.html.erb new file mode 100644 index 0000000..a20ff14 --- /dev/null +++ b/app/views/timelog/_remaining_time.html.erb @@ -0,0 +1,43 @@ +<% return if (!issue) || (!issue.safe_attribute? 'remaining_hours') %> + + + + <% if @time_entry.new_record? %> + + <% end %> + + <%= text_field 'time_entry', "remaining_time_hours", :size => "3", :disabled => true %> <%= l(:field_hours) %> + + + +<%= javascript_tag do %> +$(document).ready(function(){ + var block = $("p#issue_remaining_time"); + + if (block.find('#time_entry_remaining_time_action_set').is(':checked')) { + block.find('input#time_entry_remaining_time_hours').prop("disabled", false) + } + + block.find("input[type=radio]").change(function(e){ + block.find('input#time_entry_remaining_time_hours').prop("disabled", true) + if ($(e.target).val() === 'set') { + block.find('input#time_entry_remaining_time_hours').prop("disabled", false).focus() + } + }) +}); + +<% unless params[:time_entry].present? %> + <% if @time_entry.new_record? %> + $('#time_entry_remaining_time_action_auto').prop('checked', true); + <% else %> + $('#time_entry_remaining_time_action_nothing').prop('checked', true); + <% end %> +<% end %> +<% end %> + diff --git a/app/views/timelog/new.js.erb b/app/views/timelog/new.js.erb index 4cba8cf..f99a614 100644 --- a/app/views/timelog/new.js.erb +++ b/app/views/timelog/new.js.erb @@ -1,2 +1,3 @@ $('#time_entry_activity_id').html('<%= escape_javascript options_for_select(activity_collection_for_select_options(@time_entry), @time_entry.activity_id) %>'); +$('#new_time_entry p#issue_remaining_time').html('<%= escape_javascript render :partial => "remaining_time", :locals => { :issue => @time_entry.issue } %>'); $('#time_entry_issue').html('<%= escape_javascript link_to_issue(@time_entry.issue) if @time_entry.issue.try(:visible?) %>'); diff --git a/config/locales/en.yml b/config/locales/en.yml index cd8edef..747dcbe 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1021,6 +1021,10 @@ en: label_font_monospace: Monospaced font label_font_proportional: Proportional font label_last_notes: Last notes + label_issue_remaining_hours: Issue remaining time + label_remaining_time_action_auto: Adjust automatically + label_remaining_time_action_set: Set to + label_remaining_time_action_nothing: Do not update remaining time button_login: Login button_submit: Submit diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index da7d3ef..84d5302 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -699,6 +699,8 @@ label.no-css { width:auto; } input#time_entry_comments { width: 90%;} +p#issue_remaining_time label.hours { display: inline-block; } + #preview fieldset {margin-top: 1em; background: url(../images/draft.png)} diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb old mode 100644 new mode 100755 index 1f8ad93..33194b4 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -61,6 +61,8 @@ class TimelogControllerTest < Redmine::ControllerTest assert_select 'input[name=?][type=hidden]', 'issue_id' assert_select 'a[href=?]', '/issues/2', :text => /Feature request #2/ assert_select 'select[name=?]', 'time_entry[project_id]', 0 + assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 3 + assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 1 end def test_new_without_project_should_prefill_the_form @@ -97,6 +99,19 @@ class TimelogControllerTest < Redmine::ControllerTest assert_select 'option', :text => 'Inactive Activity', :count => 0 end + def test_new_on_issue_with_remaining_time_disabled_should_not_show_the_update_issue_remaining_time_section + tracker = Tracker.find(2) + tracker.core_fields = tracker.core_fields - %w(remaining_hours) + tracker.save! + + @request.session[:user_id] = 3 + get :new, :issue_id => 2 + assert_response :success + assert_template 'new' + assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 0 + assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 0 + end + def test_post_new_as_js_should_update_activity_options @request.session[:user_id] = 3 post :new, :params => {:time_entry => {:project_id => 1}, :format => 'js'} @@ -112,6 +127,16 @@ class TimelogControllerTest < Redmine::ControllerTest assert_select 'form[action=?]', '/time_entries/2' end + def test_get_should_not_show_the_adjust_automatically_option_in_issue_remaining_time_section + @request.session[:user_id] = 2 + get :edit, :id => 2, :project_id => nil + assert_response :success + assert_template 'edit' + assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 2 + assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 1 + assert_select 'input[type=radio][id=?]', 'time_entry_remaining_time_action_auto]', 0 + end + def test_get_edit_with_an_existing_time_entry_with_inactive_activity te = TimeEntry.find(1) te.activity = TimeEntryActivity.find_by_name("Inactive Activity") diff --git a/test/integration/api_test/issues_test.rb b/test/integration/api_test/issues_test.rb index 6e06d1f..458b395 100755 --- a/test/integration/api_test/issues_test.rb +++ b/test/integration/api_test/issues_test.rb @@ -387,7 +387,7 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base parent.update_columns :estimated_hours => 2.0 child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.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) + :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id, :remaining_time_action => 'nothing') get '/issues/3.xml' assert_equal 'application/xml', response.content_type @@ -443,7 +443,7 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base parent.update_columns :estimated_hours => 2.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) + :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id, :remaining_time_action => 'nothing') get '/issues/3.json' assert_equal 'application/json', response.content_type diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb old mode 100644 new mode 100755 index 6476dc5..5d80fd1 --- a/test/unit/time_entry_test.rb +++ b/test/unit/time_entry_test.rb @@ -184,4 +184,58 @@ class TimeEntryTest < ActiveSupport::TestCase assert_equal ["Comment cannot be blank", "Issue cannot be blank"], entry.errors.full_messages.sort end end + + def test_time_entry_decrease_issue_remaining_time_with_logged_time + issue = Issue.find(1) + issue.update_attribute(:remaining_hours, 3) + + te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue, + :user => User.find(1), :activity => TimeEntryActivity.first, + :remaining_time_action => "auto") + + assert te.save + assert_equal 2, issue.remaining_hours + end + + def test_time_entry_set_issue_remaining_time + issue = Issue.find(1) + issue.update_attribute(:remaining_hours, 3) + + te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue, + :user => User.find(1), :activity => TimeEntryActivity.first, + :remaining_time_action => "set", :remaining_time_hours => 4) + + assert te.save + assert_equal 4, issue.remaining_hours + end + + def test_time_entry_do_not_update_issue_remaining_time + issue = Issue.find(1) + issue.update_attribute(:remaining_hours, 3) + + te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue, + :user => User.find(1), :activity => TimeEntryActivity.first, + :remaining_time_action => "nothing", :remaining_time_hours => 4) + assert te.save + assert_equal 3, issue.remaining_hours + end + + def test_time_entry_do_not_decrease_issue_remaining_time_when_issue_remaining_time_is_nil + issue = Issue.find(1) + issue.update_attribute(:remaining_hours, '') + + te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue, + :user => User.find(1), :activity => TimeEntryActivity.first, + :remaining_time_action => "auto", :remaining_time_hours => 4) + assert te.save + assert_nil issue.remaining_hours + end + + def test_validate_time_entry_remaining_time_action + te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => Issue.find(1), + :user => User.find(1), :activity => TimeEntryActivity.first, + :remaining_time_action => "test", :remaining_time_hours => 'one') + assert !te.valid? + assert_equal 2, te.errors.count + end end