Project

General

Profile

Patch #13400 » 0001-Calculate-done_ratio-based-on-logged-time.patch

Gregor Schmidt, 2018-07-12 16:15

View differences:

app/models/issue.rb
52 52
  acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status),
53 53
                            :author_key => :author_id
54 54

  
55
  DONE_RATIO_OPTIONS = %w(issue_field issue_status)
55
  DONE_RATIO_OPTIONS = %w(issue_field issue_status logged_time)
56 56

  
57 57
  attr_accessor :deleted_attachment_ids
58 58
  attr_reader :current_journal
......
106 106

  
107 107
  before_validation :default_assign, on: :create
108 108
  before_validation :clear_disabled_fields
109
  before_save :close_duplicates, :update_done_ratio_from_issue_status,
109
  before_save :close_duplicates, :update_done_ratio,
110 110
              :force_updated_on_change, :update_closed_on
111 111
  after_save {|issue| issue.send :after_project_change if !issue.saved_change_to_id? && issue.saved_change_to_project_id?}
112 112
  after_save :reschedule_following_issues, :update_nested_set_attributes,
......
687 687
  private :workflow_rule_by_attribute
688 688

  
689 689
  def done_ratio
690
    if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
690
    if use_status_for_done_ratio?
691 691
      status.default_done_ratio
692

  
693
    elsif use_time_for_done_ratio?
694
      ratio =
695
        if done_ratio_derived? && total_estimated_hours.to_f > 0
696
          (total_spent_hours.to_f / total_estimated_hours.to_f)
697

  
698
        elsif !done_ratio_derived? && estimated_hours.to_f > 0
699
          (spent_hours.to_f / estimated_hours.to_f)
700

  
701
        else
702
          0.0
703
        end
704

  
705
        [ratio * 100, 100].min.to_i
692 706
    else
693 707
      read_attribute(:done_ratio)
694 708
    end
......
697 711
  def self.use_status_for_done_ratio?
698 712
    Setting.issue_done_ratio == 'issue_status'
699 713
  end
714
  def use_status_for_done_ratio?
715
    Issue.use_status_for_done_ratio? && status && status.default_done_ratio
716
  end
717

  
718
  def self.use_time_for_done_ratio?
719
    Setting.issue_done_ratio == 'logged_time'
720
  end
721
  def use_time_for_done_ratio?
722
    Issue.use_time_for_done_ratio?
723
  end
700 724

  
701 725
  def self.use_field_for_done_ratio?
702 726
    Setting.issue_done_ratio == 'issue_field'
703 727
  end
728
  def use_field_for_done_ratio?
729
    !(use_status_for_done_ratio? || use_time_for_done_ratio?)
730
  end
704 731

  
705 732
  def validate_issue
706 733
    if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date
......
799 826

  
800 827
  # Set the done_ratio using the status if that setting is set.  This will keep the done_ratios
801 828
  # even if the user turns off the setting later
802
  def update_done_ratio_from_issue_status
803
    if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
804
      self.done_ratio = status.default_done_ratio
829
  def update_done_ratio
830
    unless use_field_for_done_ratio?
831
      self.done_ratio = self.done_ratio
805 832
    end
806 833
  end
807 834

  
835
  def update_done_ratio!
836
    self.init_journal(User.current, "")
837
    self.update_done_ratio
838
    self.save
839
  end
840

  
808 841
  def init_journal(user, notes = "")
809 842
    @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
810 843
  end
......
1702 1735

  
1703 1736
      if p.done_ratio_derived?
1704 1737
        # done ratio = average ratio of children weighted with their total estimated hours
1705
        unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1738
        if p.use_field_for_done_ratio?
1706 1739
          children = p.children.to_a
1707 1740
          if children.any?
1708 1741
            child_with_total_estimated_hours = children.select {|c| c.total_estimated_hours.to_f > 0.0}
app/models/time_entry.rb
46 46
  validates_length_of :comments, :maximum => 1024, :allow_nil => true
47 47
  validates :spent_on, :date => true
48 48
  before_validation :set_project_if_nil
49
  after_save :update_done_ratio
50
  after_destroy :update_done_ratio
49 51
  validate :validate_time_entry
50 52

  
51 53
  scope :visible, lambda {|*args|
......
138 140
    errors.add :activity_id, :inclusion if activity_id_changed? && project && !project.activities.include?(activity)
139 141
  end
140 142

  
143
  def update_done_ratio
144
    if issue && Issue.use_time_for_done_ratio?
145
      # Only create a new journal for this update if we don't have any other
146
      # changes pending on the issue. In that case, we will save the issue
147
      # later anyway (which we thus expect here and don't save the issue
148
      # ourselfes)
149
      if issue.changed?
150
        issue.update_done_ratio
151
      else
152
        issue.update_done_ratio!
153
      end
154
    end
155
  end
156

  
141 157
  def hours=(h)
142 158
    write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
143 159
  end
config/locales/de.yml
1011 1011
  setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels
1012 1012
  setting_issue_done_ratio_issue_field: Ticket-Feld % erledigt
1013 1013
  setting_issue_done_ratio_issue_status: Ticket-Status
1014
  setting_issue_done_ratio_logged_time: geschätzter und gebuchter Zeit
1014 1015
  setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben
1015 1016
  setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung
1016 1017
  setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export
config/locales/en.yml
435 435
  setting_issue_done_ratio: Calculate the issue done ratio with
436 436
  setting_issue_done_ratio_issue_field: Use the issue field
437 437
  setting_issue_done_ratio_issue_status: Use the issue status
438
  setting_issue_done_ratio_logged_time: Use the logged and estimated time
438 439
  setting_start_of_week: Start calendars on
439 440
  setting_rest_api_enabled: Enable REST web service
440 441
  setting_cache_formatted_text: Cache formatted text
test/unit/issue_subtasking_test.rb
21 21
  fixtures :projects, :users, :roles, :members, :member_roles,
22 22
           :trackers, :projects_trackers,
23 23
           :issue_statuses, :issue_categories, :enumerations,
24
           :issues,
24
           :issues, :time_entries,
25 25
           :enabled_modules,
26 26
           :workflows
27 27

  
......
168 168
    end
169 169
  end
170 170

  
171
  def test_parent_done_ratio_via_logged_time_if_set_to_derived
172
    with_settings :parent_issue_done_ratio => 'derived', :issue_done_ratio => 'logged_time' do
173
      parent = Issue.generate!(:estimated_hours => 2)
174
      TimeEntry.generate!(:issue => parent, :hours => 2)
175

  
176
      child = parent.generate_child!(:estimated_hours => 8)
177
      TimeEntry.generate!(:issue => child, :hours => 2)
178

  
179
      # Estimated time: 10h, Spent time: 4h => 40 % done
180
      assert_equal 40, parent.reload.done_ratio
181
    end
182
  end
183

  
184
  def test_parent_done_ratio_via_logged_time_if_set_to_independent
185
    with_settings :parent_issue_done_ratio => 'independent', :issue_done_ratio => 'logged_time' do
186
      parent = Issue.generate!(:estimated_hours => 2)
187
      TimeEntry.generate!(:issue => parent, :hours => 2)
188

  
189
      child = parent.generate_child!(:estimated_hours => 8)
190
      TimeEntry.generate!(:issue => child, :hours => 2)
191

  
192
      # Estimated time: 2h, Spent time: 2h => 100 % done
193
      assert_equal 100, parent.reload.done_ratio
194
    end
195
  end
196

  
197
  def test_parent_done_ratio_via_logged_time_does_not_exceed_100
198
    with_settings :parent_issue_done_ratio => 'derived', :issue_done_ratio => 'logged_time' do
199
      parent = Issue.generate!(:estimated_hours => 2)
200
      TimeEntry.generate!(:issue => parent, :hours => 2)
201

  
202
      child = parent.generate_child!(:estimated_hours => 2)
203
      TimeEntry.generate!(:issue => child, :hours => 8)
204

  
205
      # Estimated time: 4h, Spent time: 10h => 250 % == 100 % done
206
      assert_equal 100, parent.reload.done_ratio
207
    end
208
  end
209

  
171 210
  def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any
172 211
    with_settings :parent_issue_done_ratio => 'derived' do
173 212
      parent = Issue.generate!
test/unit/issue_test.rb
2730 2730
    end
2731 2731
  end
2732 2732

  
2733
  test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2733
  test "#update_done_ratio should update done_ratio according to Setting.issue_done_ratio" do
2734 2734
    @issue = Issue.find(1)
2735
    @issue.update!(:estimated_hours => 308.5)
2735 2736
    @issue_status = IssueStatus.find(1)
2736 2737
    @issue_status.update!(:default_done_ratio => 50)
2738

  
2737 2739
    @issue2 = Issue.find(2)
2738 2740
    @issue_status2 = IssueStatus.find(2)
2739 2741
    @issue_status2.update!(:default_done_ratio => 0)
2740 2742

  
2741 2743
    with_settings :issue_done_ratio => 'issue_field' do
2742
      @issue.update_done_ratio_from_issue_status
2743
      @issue2.update_done_ratio_from_issue_status
2744
      @issue.update_done_ratio
2745
      @issue2.update_done_ratio
2744 2746

  
2745 2747
      assert_equal 0, @issue.read_attribute(:done_ratio)
2746 2748
      assert_equal 30, @issue2.read_attribute(:done_ratio)
2747 2749
    end
2748 2750

  
2749 2751
    with_settings :issue_done_ratio => 'issue_status' do
2750
      @issue.update_done_ratio_from_issue_status
2751
      @issue2.update_done_ratio_from_issue_status
2752
      @issue.update_done_ratio
2753
      @issue2.update_done_ratio
2752 2754

  
2753 2755
      assert_equal 50, @issue.read_attribute(:done_ratio)
2754 2756
      assert_equal 0, @issue2.read_attribute(:done_ratio)
2755 2757
    end
2758

  
2759
    with_settings :issue_done_ratio => 'logged_time' do
2760
      @issue.update_done_ratio
2761
      @issue2.update_done_ratio
2762

  
2763
      assert_equal 50, @issue.read_attribute(:done_ratio)
2764
      assert_equal 0, @issue2.read_attribute(:done_ratio)
2765
    end
2766

  
2756 2767
  end
2757 2768

  
2758 2769
  test "#by_tracker" do
test/unit/time_entry_test.rb
212 212
      assert_equal ["Comment cannot be blank", "Issue cannot be blank"], entry.errors.full_messages.sort
213 213
    end
214 214
  end
215

  
216
  def test_create_updates_issues_done_ratio
217
    with_settings :issue_done_ratio => 'logged_time' do
218
      issue = Issue.generate!(:estimated_hours => 10)
219
      assert_equal 0, issue.done_ratio
220

  
221
      TimeEntry.generate!(:issue => issue, :hours => 5)
222
      assert_equal 50, issue.reload.done_ratio
223
    end
224
  end
225

  
226
  def test_update_updates_issues_done_ratio
227
    with_settings :issue_done_ratio => 'logged_time' do
228
      issue = Issue.generate!(:estimated_hours => 10)
229

  
230
      te = TimeEntry.generate!(:issue => issue, :hours => 5)
231
      assert_equal 50, issue.reload.done_ratio
232

  
233
      te.update_attribute(:hours, 10)
234
      assert_equal 100, issue.reload.done_ratio
235
    end
236
  end
237

  
238
  def test_destroy_updates_issues_done_ratio
239
    with_settings :issue_done_ratio => 'logged_time' do
240
      issue = Issue.generate!(:estimated_hours => 10)
241

  
242
      te = TimeEntry.generate!(:issue => issue, :hours => 5)
243
      assert_equal 50, issue.reload.done_ratio
244

  
245
      te.destroy
246
      assert_equal 0, issue.reload.done_ratio
247
    end
248
  end
215 249
end
(4-4/6)