Skip to content

Commit

Permalink
Merge pull request #16649 from opf/implementation/57529-options-for-t…
Browse files Browse the repository at this point in the history
…otal-calculation-sum-mode

[#57529] Add options for sum totals calculation mode in hierarchies
  • Loading branch information
cbliard authored Sep 26, 2024
2 parents a0fd7e4 + de86221 commit d86dfdc
Show file tree
Hide file tree
Showing 19 changed files with 1,351 additions and 33 deletions.
1 change: 1 addition & 0 deletions app/models/journal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Journal < ApplicationRecord
progress_mode_changed_to_status_based
status_changed
system_update
total_percent_complete_mode_changed_to_work_weighted_average
work_package_children_changed_times
work_package_parent_changed_times
work_package_predecessor_changed_times
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class Journal::CausedByTotalPercentCompleteModeChangedToSimpleAverage < CauseOfChange::Base
def initialize
super("total_percent_complete_mode_changed_to_simple_average")
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class Journal::CausedByTotalPercentCompleteModeChangedToWorkWeightedAverage < CauseOfChange::Base
def initialize
super("total_percent_complete_mode_changed_to_work_weighted_average")
end
end
1 change: 1 addition & 0 deletions app/models/work_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class WorkPackage < ApplicationRecord
include OpenProject::Journal::AttachmentHelper

DONE_RATIO_OPTIONS = %w[field status].freeze
TOTAL_PERCENT_COMPLETE_MODE_OPTIONS = %w[work_weighted_average simple_average].freeze

belongs_to :project
belongs_to :type
Expand Down
22 changes: 20 additions & 2 deletions app/services/settings/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,29 @@ def set_setting_value(name, value)
old_value = Setting[name]
new_value = derive_value(value)
Setting[name] = new_value
if name == :work_package_done_ratio && old_value != "status" && new_value == "status"
WorkPackages::Progress::ApplyStatusesChangeJob.perform_later(cause_type: "progress_mode_changed_to_status_based")

if name == :work_package_done_ratio
trigger_update_job_for_progress_mode_change(old_value, new_value)
elsif name == :total_percent_complete_mode
trigger_update_job_for_total_percent_complete_mode_change(old_value, new_value)
end
end

def trigger_update_job_for_progress_mode_change(old_value, new_value)
return if old_value == new_value
return if new_value != "status" # only trigger if changing to status-based

WorkPackages::Progress::ApplyStatusesChangeJob.perform_later(cause_type: "progress_mode_changed_to_status_based")
end

def trigger_update_job_for_total_percent_complete_mode_change(old_value, new_value)
return if old_value == new_value

WorkPackages::Progress::ApplyTotalPercentCompleteModeChangeJob
.perform_later(mode: new_value,
cause_type: "total_percent_complete_mode_changed_to_#{new_value}")
end

def derive_value(value)
case value
when Array, Hash
Expand Down
2 changes: 2 additions & 0 deletions app/services/work_packages/update_ancestors/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class WorkPackages::UpdateAncestors::Loader
parent_id: "parent_id",
estimated_hours: "estimated_hours",
remaining_hours: "remaining_hours",
done_ratio: "done_ratio",
derived_done_ratio: "derived_done_ratio",
status_excluded_from_totals: "statuses.excluded_from_totals",
schedule_manually: "schedule_manually",
ignore_non_working_days: "ignore_non_working_days"
Expand Down
32 changes: 31 additions & 1 deletion app/services/work_packages/update_ancestors_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,45 @@ def derive_done_ratio(ancestor, loader)
end

def compute_derived_done_ratio(work_package, loader)
return if no_children?(work_package, loader)

case Setting.total_percent_complete_mode
when "work_weighted_average"
calculate_work_weighted_average_percent_complete(work_package)
when "simple_average"
calculate_simple_average_percent_complete(work_package, loader)
end
end

def calculate_work_weighted_average_percent_complete(work_package)
return if work_package.derived_estimated_hours.nil? || work_package.derived_remaining_hours.nil?
return if work_package.derived_estimated_hours.zero?
return if no_children?(work_package, loader)

work_done = (work_package.derived_estimated_hours - work_package.derived_remaining_hours)
progress = (work_done.to_f / work_package.derived_estimated_hours) * 100
progress.round
end

def calculate_simple_average_percent_complete(work_package, loader)
all_done_ratios = children_done_ratio_values(work_package, loader)

if work_package.done_ratio.present? && !work_package.status.excluded_from_totals
all_done_ratios << work_package.done_ratio
end

return if all_done_ratios.empty?

progress = all_done_ratios.sum.to_f / all_done_ratios.count
progress.round
end

def children_done_ratio_values(work_package, loader)
loader
.children_of(work_package)
.filter(&:included_in_totals_calculation?)
.map { |child| child.derived_done_ratio || child.done_ratio || 0 }
end

# Sets the ignore_non_working_days to true if any descendant has its value set to true.
# If there is no value returned from the descendants, that means that the work package in
# question no longer has a descendant. But since we are in the service going up the ancestor chain,
Expand Down
7 changes: 4 additions & 3 deletions app/views/admin/settings/progress_tracking/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>

<% html_title t(:label_administration), t(:label_work_package_plural), t(:label_progress_tracking) -%>

<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_progress_tracking) }
Expand All @@ -37,7 +35,6 @@ See COPYRIGHT and LICENSE files for more details.
t(:label_progress_tracking)])
end
%>

<%=
primer_form_with(
scope: :settings, action: :update, method: :patch,
Expand All @@ -64,6 +61,10 @@ primer_form_with(
end
end
end
form.radio_button_group(
name: "total_percent_complete_mode",
values: WorkPackage::TOTAL_PERCENT_COMPLETE_MODE_OPTIONS
)
form.radio_button_group(
name: "percent_complete_on_status_closed",
disabled: WorkPackage.status_based_mode?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class WorkPackages::Progress::ApplyTotalPercentCompleteModeChangeJob < WorkPackages::Progress::Job
VALID_CAUSE_TYPES = %w[
total_percent_complete_mode_changed_to_work_weighted_average
total_percent_complete_mode_changed_to_simple_average
].freeze

attr_reader :cause_type, :mode

# Updates the total % complete of all work packages after the total
# percent complete mode has been changed.
#
# It creates a journal entry with the System user describing the changes.
#
#
# Updates the total % complete of all work packages after the total
# percent complete mode has been changed.
#
# It creates a journal entry with the System user describing the changes.
#
# @param [String] cause_type The cause type of the change
# @param [String] mode The new total percent complete mode
# @return [void]
def perform(cause_type:, mode:)
@cause_type = cause_type
@mode = mode

with_temporary_total_percent_complete_table do
update_total_percent_complete
copy_total_percent_complete_values_to_work_packages_and_update_journals(journal_cause)
end
end

private

def update_total_percent_complete
case mode
when "work_weighted_average"
update_total_percent_complete_in_work_weighted_average_mode
when "simple_average"
update_total_percent_complete_in_simple_average_mode
else
raise ArgumentError, "Invalid total percent complete mode: #{mode}"
end
end

def journal_cause
assert_valid_cause_type!

@journal_cause ||=
case cause_type
when "total_percent_complete_mode_changed_to_work_weighted_average"
Journal::CausedByTotalPercentCompleteModeChangedToWorkWeightedAverage.new
when "total_percent_complete_mode_changed_to_simple_average"
Journal::CausedByTotalPercentCompleteModeChangedToSimpleAverage.new
else
raise "Unable to handle cause type #{cause_type.inspect}"
end
end

def assert_valid_cause_type!
unless VALID_CAUSE_TYPES.include?(cause_type)
raise ArgumentError, "Invalid cause type #{cause_type.inspect}. " \
"Valid values are #{VALID_CAUSE_TYPES.inspect}"
end
end
end
Loading

0 comments on commit d86dfdc

Please sign in to comment.