02_add_remaining_time_to_log_time_15945.patch

Marius BALTEANU, 2016-11-07 01:22

Download (14.9 KB)

View differences:

app/models/time_entry.rb
25 25
  belongs_to :activity, :class_name => 'TimeEntryActivity'
26 26

  
27 27
  attr_protected :user_id, :tyear, :tmonth, :tweek
28
  attr_accessor :remaining_time_action, :remaining_time_hours
28 29

  
29 30
  acts_as_customizable
30 31
  acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
......
39 40

  
40 41
  validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
41 42
  validates_numericality_of :hours, :allow_nil => true, :message => :invalid
43
  validates :remaining_time_action,  :inclusion => { :in => ['auto', 'set', 'nothing'] }, :allow_nil => true
44
  validates :remaining_time_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_blank => true, :message => :invalid}
45

  
42 46
  validates_length_of :comments, :maximum => 1024, :allow_nil => true
43 47
  validates :spent_on, :date => true
44 48
  before_validation :set_project_if_nil
45 49
  validate :validate_time_entry
46 50

  
51
  after_save :update_issue_remaining_hours
52

  
47 53
  scope :visible, lambda {|*args|
48 54
    joins(:project).
49 55
    where(TimeEntry.visible_condition(args.shift || User.current, *args))
......
53 59
    where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
54 60
  }
55 61

  
56
  safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
62
  safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields', 'remaining_time_action', 'remaining_time_hours'
57 63

  
58 64
  # Returns a SQL conditions string used to find all time entries visible by the specified user
59 65
  def self.visible_condition(user, options={})
......
117 123
    errors.add :project_id, :invalid if project.nil?
118 124
    errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) || @invalid_issue_id
119 125
    errors.add :activity_id, :inclusion if activity_id_changed? && project && !project.activities.include?(activity)
126
    errors.add :remaining_time_hours, :invalid if (remaining_time_action == 'set' && remaining_time_hours.blank?)
120 127
  end
121 128

  
122 129
  def hours=(h)
......
157 164
  def editable_custom_fields(user=nil)
158 165
    editable_custom_field_values(user).map(&:custom_field).uniq
159 166
  end
167

  
168
  def remaining_time_action
169
    @remaining_time_action
170
  end
171

  
172
  def remaining_time_hours
173
    @remaining_time_hours
174
  end
175

  
176
  def update_issue_remaining_hours
177
    issue = self.issue
178
    return unless issue
179

  
180
    h = (remaining_time_hours.is_a?(String)) ? remaining_time_hours.to_hours : remaining_time_hours
181

  
182
    case remaining_time_action
183
    when "auto"
184
      new_remaining_hours = issue.remaining_hours - self.hours unless issue.remaining_hours.nil?
185
    when "set"
186
      new_remaining_hours = h
187
    when "nothing"
188
      return
189
    end
190

  
191
    issue.init_journal(User.current)
192
    issue.remaining_hours = new_remaining_hours
193
    issue.save
194
  end
160 195
end
app/views/issues/_edit.html.erb
24 24
        <% @time_entry.custom_field_values.each do |value| %>
25 25
          <p><%= custom_field_tag_with_label :time_entry, value %></p>
26 26
        <% end %>
27
        <p id="issue_remaining_time">
28
          <%= render :partial => 'timelog/remaining_time', :locals => { :issue => @issue } %>
29
        </p>
27 30
        <% end %>
28 31
    </fieldset>
29 32
    <% end %>
app/views/timelog/_form.html.erb
24 24
  <% @time_entry.custom_field_values.each do |value| %>
25 25
    <p><%= custom_field_tag_with_label :time_entry, value %></p>
26 26
  <% end %>
27
  <p id="issue_remaining_time">
28
    <%= render :partial => 'remaining_time', :locals => { :issue => @time_entry.issue } %>
29
  </p>
27 30
  <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
28 31
</div>
29 32

  
app/views/timelog/_remaining_time.html.erb
1
<% return if (!issue) || (!issue.safe_attribute? 'remaining_hours') %>
2

  
3
<label for=""><%= l(:label_issue_remaining_hours) %></label>
4
<span class="check_box_group">
5
  <% if @time_entry.new_record? %>
6
    <label>
7
      <%= radio_button 'time_entry', 'remaining_time_action', 'auto' %> <%= l(:label_remaining_time_action_auto) %>
8
    </label>
9
  <% end %>
10
  <label class="hours">
11
    <%= radio_button 'time_entry', 'remaining_time_action', "set" %> <%= l(:label_remaining_time_action_set) %>
12
  </label>
13
  <%= text_field 'time_entry', "remaining_time_hours", :size => "3", :disabled => true %> <%= l(:field_hours) %>
14
  <label>
15
  <%= radio_button 'time_entry', 'remaining_time_action', 'nothing' %> <%= l(:label_remaining_time_action_nothing) %>
16
  </label>
17
</span>
18

  
19
<%= javascript_tag do %>
20
$(document).ready(function(){
21
  var block = $("p#issue_remaining_time");
22

  
23
  if (block.find('#time_entry_remaining_time_action_set').is(':checked')) {
24
    block.find('input#time_entry_remaining_time_hours').prop("disabled", false)
25
  }
26

  
27
  block.find("input[type=radio]").change(function(e){
28
    block.find('input#time_entry_remaining_time_hours').prop("disabled", true)
29
    if ($(e.target).val() === 'set') {
30
      block.find('input#time_entry_remaining_time_hours').prop("disabled", false).focus()
31
    }
32
  })
33
});
34

  
35
<% unless params[:time_entry].present? %>
36
  <% if @time_entry.new_record? %>
37
    $('#time_entry_remaining_time_action_auto').prop('checked', true);
38
  <% else %>
39
    $('#time_entry_remaining_time_action_nothing').prop('checked', true);
40
  <% end %>
41
<% end %>
42
<% end %>
43

  
app/views/timelog/new.js.erb
1 1
$('#time_entry_activity_id').html('<%= escape_javascript options_for_select(activity_collection_for_select_options(@time_entry), @time_entry.activity_id) %>');
2
$('#new_time_entry p#issue_remaining_time').html('<%= escape_javascript render :partial => "remaining_time", :locals => { :issue => @time_entry.issue } %>');
config/locales/en.yml
1004 1004
  label_font_default: Default font
1005 1005
  label_font_monospace: Monospaced font
1006 1006
  label_font_proportional: Proportional font
1007
  label_issue_remaining_hours: Issue remaining time
1008
  label_remaining_time_action_auto: Adjust automatically
1009
  label_remaining_time_action_set: Set to
1010
  label_remaining_time_action_nothing: Do not update remaining time
1007 1011

  
1008 1012
  button_login: Login
1009 1013
  button_submit: Submit
public/stylesheets/application.css
658 658
  width:auto;
659 659
}
660 660
input#time_entry_comments { width: 90%;}
661
p#issue_remaining_time label.hours { display: inline-block; }
662

  
661 663

  
662 664
#preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
663 665

  
test/functional/timelog_controller_test.rb
59 59
    assert_select 'input[name=?][type=hidden]', 'project_id', 0
60 60
    assert_select 'input[name=?][type=hidden]', 'issue_id'
61 61
    assert_select 'select[name=?]', 'time_entry[project_id]', 0
62
    assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 3
63
    assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 1
62 64
  end
63 65

  
64 66
  def test_new_without_project_should_prefill_the_form
......
95 97
    assert_select 'option', :text => 'Inactive Activity', :count => 0
96 98
  end
97 99

  
100
  def test_new_on_issue_with_remaining_time_disabled_should_not_show_the_update_issue_remaining_time_section
101
    tracker = Tracker.find(2)
102
    tracker.core_fields = tracker.core_fields - %w(remaining_hours)
103
    tracker.save!
104

  
105
    @request.session[:user_id] = 3
106
    get :new, :issue_id => 2
107
    assert_response :success
108
    assert_template 'new'
109
    assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 0
110
    assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 0
111
  end
112

  
98 113
  def test_post_new_as_js_should_update_activity_options
99 114
    @request.session[:user_id] = 3
100 115
    post :new, :params => {:time_entry => {:project_id => 1}, :format => 'js'}
......
110 125
    assert_select 'form[action=?]', '/time_entries/2'
111 126
  end
112 127

  
128
  def test_get_should_not_show_the_adjust_automatically_option_in_issue_remaining_time_section
129
    @request.session[:user_id] = 2
130
    get :edit, :id => 2, :project_id => nil
131
    assert_response :success
132
    assert_template 'edit'
133
    assert_select 'input[type=radio][name=?]', 'time_entry[remaining_time_action]', 2
134
    assert_select 'input[type=text][name=?]', 'time_entry[remaining_time_hours]', 1
135
    assert_select 'input[type=radio][id=?]', 'time_entry_remaining_time_action_auto]', 0
136
  end
137

  
113 138
  def test_get_edit_with_an_existing_time_entry_with_inactive_activity
114 139
    te = TimeEntry.find(1)
115 140
    te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
......
501 526
    assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
502 527
      # System wide custom field
503 528
      assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
504
  
529

  
505 530
      # Activities
506 531
      assert_select 'select[name=?]', 'time_entry[activity_id]' do
507 532
        assert_select 'option[value=""]', :text => '(No change)'
......
549 574
    @request.session[:user_id] = 2
550 575
    # makes user a manager on the other project
551 576
    Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
552
    
577

  
553 578
    # update time entry activity
554 579
    post :bulk_update, :params => {:ids => [1, 2, 4], :time_entry => { :activity_id => 9 }}
555 580

  
test/integration/api_test/issues_test.rb
358 358
    parent = Issue.find(3)
359 359
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
360 360
    TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
361
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
361
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id, :remaining_time_action => 'nothing')
362 362
    get '/issues/3.xml'
363 363

  
364 364
    assert_equal 'application/xml', response.content_type
......
395 395
    parent = Issue.find(3)
396 396
    child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
397 397
    TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
398
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
398
                      :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id, :remaining_time_action => 'nothing')
399 399
    get '/issues/3.json'
400 400

  
401 401
    assert_equal 'application/json', response.content_type
test/unit/time_entry_test.rb
172 172
                          :activity => activity)
173 173
    assert_equal project.id, te.project.id
174 174
  end
175

  
176
  def test_time_entry_decrease_issue_remaining_time_with_logged_time
177
    issue = Issue.find(1)
178
    issue.update_attribute(:remaining_hours, 3)
179

  
180
    te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue,
181
                        :user => User.find(1), :activity => TimeEntryActivity.first,
182
                        :remaining_time_action => "auto")
183

  
184
    assert te.save
185
    assert_equal 2, issue.remaining_hours
186
  end
187

  
188
  def test_time_entry_set_issue_remaining_time
189
    issue = Issue.find(1)
190
    issue.update_attribute(:remaining_hours, 3)
191

  
192
    te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue,
193
                        :user => User.find(1), :activity => TimeEntryActivity.first,
194
                        :remaining_time_action => "set", :remaining_time_hours => 4)
195

  
196
    assert te.save
197
    assert_equal 4, issue.remaining_hours
198
  end
199

  
200
  def test_time_entry_do_not_update_issue_remaining_time
201
    issue = Issue.find(1)
202
    issue.update_attribute(:remaining_hours, 3)
203

  
204
    te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue,
205
                        :user => User.find(1), :activity => TimeEntryActivity.first,
206
                        :remaining_time_action => "nothing", :remaining_time_hours => 4)
207
    assert te.save
208
    assert_equal 3, issue.remaining_hours
209
  end
210

  
211
  def test_time_entry_do_not_decrease_issue_remaining_time_when_issue_remaining_time_is_nil
212
    issue = Issue.find(1)
213
    issue.update_attribute(:remaining_hours, '')
214

  
215
    te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => issue,
216
                        :user => User.find(1), :activity => TimeEntryActivity.first,
217
                        :remaining_time_action => "auto", :remaining_time_hours => 4)
218
    assert te.save
219
    assert_nil issue.remaining_hours
220
  end
221

  
222
  def test_validate_time_entry_remaining_time_action
223
    te = TimeEntry.new(:spent_on => '2010-01-01', :hours => 1, :issue => Issue.find(1),
224
                        :user => User.find(1), :activity => TimeEntryActivity.first,
225
                        :remaining_time_action => "test", :remaining_time_hours => 'one')
226
    assert !te.valid?
227
    assert_equal 2, te.errors.count
228
  end
175 229
end