From: redacted Date: Wed, 20 Oct 2021 12:42:09 +0200 Subject: =?UTF-8?q?Add=20partial=5Fderived=20option=20for=20issue=20dates?= =?UTF-8?q?=0AThis=20implements=20the=20suggestions=20from=20feature=20#34?= =?UTF-8?q?609.=0AWith=20this=20parent=20issues=20can=20have=20start=20dat?= =?UTF-8?q?es=20earlier=20than=20child=20issue=0Aand=20due=20dates=20later?= =?UTF-8?q?=20than=20child=20issues=20but=20not=20the=20other=20way=20arou?= =?UTF-8?q?nd.?= --- app/helpers/settings_helper.rb | 1 + app/models/issue.rb | 21 +++++++++++++++++++++ config/locales/de.yml | 3 +++ config/locales/en-GB.yml | 3 +++ config/locales/en.yml | 3 +++ 5 files changed, 31 insertions(+) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index c3b8e7a25..abf4ae0fc 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -185,6 +185,7 @@ module SettingsHelper def parent_issue_dates_options options = [ [:label_parent_task_attributes_derived, 'derived'], + [:label_parent_task_attributes_partially_derived, 'partially_derived'], [:label_parent_task_attributes_independent, 'independent'] ] diff --git a/app/models/issue.rb b/app/models/issue.rb index 55962f0fe..733158efb 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -745,6 +745,14 @@ class Issue < ActiveRecord::Base errors.add :due_date, :greater_than_start_date end + if dates_partially_derived? && start_date && start_date_changed? && start_date > children.minimum(:start_date) + errors.add :start_date, :later_than_minimum_start_date, :date => format_date(children.minimum(:start_date)) + end + + if dates_partially_derived? && due_date && due_date_changed? && due_date < children.maximum(:due_date) + errors.add :due_date, :earlier_than_maximum_due_date, :date => format_date(children.maximum(:due_date)) + end + if start_date && start_date_changed? && soonest_start && start_date < soonest_start errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start) end @@ -1412,6 +1420,10 @@ class Issue < ActiveRecord::Base !leaf? && Setting.parent_issue_dates == 'derived' end + def dates_partially_derived? + !leaf? && Setting.parent_issue_dates == 'partially_derived' + end + def priority_derived? !leaf? && Setting.parent_issue_priority == 'derived' end @@ -1829,6 +1841,15 @@ class Issue < ActiveRecord::Base end end + if p.dates_partially_derived? + # start/due dates = lowest/highest dates of children or own date + p.start_date = [p.children.minimum(:start_date), p.start_date].compact.min + p.due_date = [p.children.maximum(:due_date),p.due_date].compact.max + if p.start_date && p.due_date && p.due_date < p.start_date + p.start_date, p.due_date = p.due_date, p.start_date + end + end + if p.done_ratio_derived? # done ratio = average ratio of children weighted with their total estimated hours unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio diff --git a/config/locales/de.yml b/config/locales/de.yml index 1bb53c4d0..da933c255 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -146,6 +146,8 @@ de: circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einem seiner untergeordneten Tickets verlinkt werden" earlier_than_minimum_start_date: "kann wegen eines Vorgängertickets nicht vor %{date} liegen" + later_than_minimum_start_date: "kann wegen eines Untertickets nicht nach %{date} liegen" + earlier_than_maximum_due_date: "kann wegen eines Untertickets nicht vor %{date} liegen" not_a_regexp: "ist kein gültiger regulärer Ausdruck" open_issue_with_closed_parent: "Ein offenes Ticket kann nicht an ein geschlossenes übergeordnetes Ticket angehängt werden" must_contain_uppercase: "muss Großbuchstaben (A-Z) enthalten" @@ -1173,6 +1175,7 @@ de: field_remote_ip: IP-Adresse label_parent_task_attributes: Eigenschaften übergeordneter Tickets label_parent_task_attributes_derived: Abgeleitet von untergeordneten Tickets + label_parent_task_attributes_partially_derived: Teilweise abgeleitet von untergeordneten Tickets label_parent_task_attributes_independent: Unabhängig von untergeordneten Tickets label_time_entries_visibility_all: Alle Zeitaufwände label_time_entries_visibility_own: Nur eigene Aufwände diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 453e86fa7..e170c8cdb 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -134,6 +134,8 @@ en-GB: circular_dependency: "This relation would create a circular dependency" cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + later_than_minimum_start_date: "cannot be later than %{date} because of child issues" + earlier_than_maximum_due_date: "cannot be earlier than %{date} because of child issues" not_a_regexp: "is not a valid regular expression" open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" must_contain_uppercase: "must contain uppercase letters (A-Z)" @@ -1132,6 +1134,7 @@ en-GB: field_time_entries_visibility: Time logs visibility setting_password_max_age: Require password change after label_parent_task_attributes: Parent tasks attributes + label_parent_task_attributes_partially_derived: Partially calculated from subtasks label_parent_task_attributes_derived: Calculated from subtasks label_parent_task_attributes_independent: Independent of subtasks label_time_entries_visibility_all: All time entries diff --git a/config/locales/en.yml b/config/locales/en.yml index 8fd84ff13..61feec4aa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -130,6 +130,8 @@ en: circular_dependency: "This relation would create a circular dependency" cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + earlier_than_maximum_due_date: "cannot be earlier than %{date} because of child issues" + later_than_minimum_start_date: "cannot be later than %{date} because of child issues" not_a_regexp: "is not a valid regular expression" open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" must_contain_uppercase: "must contain uppercase letters (A-Z)" @@ -1064,6 +1066,7 @@ en: label_blank_value: blank label_parent_task_attributes: Parent tasks attributes label_parent_task_attributes_derived: Calculated from subtasks + label_parent_task_attributes_partially_derived: Partially calculated from subtasks label_parent_task_attributes_independent: Independent of subtasks label_time_entries_visibility_all: All time entries label_time_entries_visibility_own: Time entries created by the user -- 2.30.2 From bf96dfa31a9ccea6b194bd48c1ef79548ce3e957 Mon Sep 17 00:00:00 2001 From: redacted Date: Thu, 21 Oct 2021 15:45:34 +0200 Subject: =?UTF-8?q?Add=20partial=5Fderived=20option=20for=20issue=20done?= =?UTF-8?q?=5Fratio=20and=20priority=0AThis=20implements=20the=20second=20?= =?UTF-8?q?suggestion=20from=20feature=20#34609.=0AWith=20this=20parent=20?= =?UTF-8?q?issues=20can=20have=20priorities=20higher=20than=20child=20issu?= =?UTF-8?q?es=0Aand=20done=5Fratios=20lower=20than=20child=20issues=20but?= =?UTF-8?q?=20not=20the=20other=20way=20around.?= --- app/helpers/settings_helper.rb | 2 + app/models/issue.rb | 70 +++++++++++++++++++++++++++++++--- config/locales/de.yml | 2 + config/locales/en-GB.yml | 2 + config/locales/en.yml | 2 + 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index abf4ae0fc..04e357840 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -195,6 +195,7 @@ module SettingsHelper def parent_issue_priority_options options = [ [:label_parent_task_attributes_derived, 'derived'], + [:label_parent_task_attributes_partially_derived, 'partially_derived'], [:label_parent_task_attributes_independent, 'independent'] ] @@ -204,6 +205,7 @@ module SettingsHelper def parent_issue_done_ratio_options options = [ [:label_parent_task_attributes_derived, 'derived'], + [:label_parent_task_attributes_partially_derived, 'partially_derived'], [:label_parent_task_attributes_independent, 'independent'] ] diff --git a/app/models/issue.rb b/app/models/issue.rb index 733158efb..de1cdb011 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -765,6 +765,44 @@ class Issue < ActiveRecord::Base end end + if priority_partially_derived? && priority_id && priority_id_changed? + #Get derived priority from children and reject if own priority is lower + children_priority_position = children.open.joins(:priority).maximum("#{IssuePriority.table_name}.position") + own_priority_position = IssuePriority.find_by_id(priority_id).position + if children_priority_position && children_priority_position > own_priority_position + own_priority_name = IssuePriority.find_by_position(own_priority_position).name + children_priority_name = IssuePriority.find_by_position(children_priority_position) + errors.add :priority, :priority_lower_than_children, :own_priority => own_priority_name, :priority => children_priority_name + end + end + + if done_ratio_partially_derived? && done_ratio && done_ratio_changed? + #get derived done_ratio from children and reject if own done_ratio is higher + children_done_ratio = 100 #set comparison to max in case we can't determine a child done ratio below. + chld=children.to_a + if chld.any? + chld_with_total_estimated_hours = chld.select {|c| c.total_estimated_hours.to_f > 0.0} + if chld_with_total_estimated_hours.any? + average= chld_with_total_estimated_hours.sum(&:total_estimated_hours).to_d / chld_with_total_estimated_hours.count + else + average = 1.0.to_d + end + done = children.sum do |c| + estimated = (c.total_estimated_hours || 0.0).to_d + estimated = average unless estimated > 0.0 + ratio = c.closed? ? 100 : (c.done_ratio || 0) + estimated * ratio + end + progress = done / (average * chld.count) + children_done_ratio = progress.floor + end + + own_done_ratio = done_ratio + if children_done_ratio && children_done_ratio < own_done_ratio + errors.add :done_ratio, :done_ratio_higher_than_children, :done_ratio => children_done_ratio + end + end + # Checks that the issue can not be added/moved to a disabled tracker if project && (tracker_id_changed? || project_id_changed?) if tracker && !project.trackers.include?(tracker) @@ -1428,10 +1466,18 @@ class Issue < ActiveRecord::Base !leaf? && Setting.parent_issue_priority == 'derived' end + def priority_partially_derived? + !leaf? && Setting.parent_issue_priority == 'partially_derived' + end + def done_ratio_derived? !leaf? && Setting.parent_issue_done_ratio == 'derived' end + def done_ratio_partially_derived? + !leaf? && Setting.parent_issue_done_ratio == 'partially_derived' + end + def <=>(issue) if issue.nil? -1 @@ -1821,12 +1867,18 @@ class Issue < ActiveRecord::Base def recalculate_attributes_for(issue_id) if issue_id && p = Issue.find_by_id(issue_id) - if p.priority_derived? - # priority = highest priority of open children + if p.priority_derived? || p.priority_partially_derived? + # priority = highest priority of open children (or higher if partially derived) # priority is left unchanged if all children are closed and there's no default priority defined if priority_position = p.children.open.joins(:priority).maximum("#{IssuePriority.table_name}.position") - p.priority = IssuePriority.find_by_position(priority_position) + if p.priority_partially_derived? + #like priority_derived, but priority can be higher than highest childs priority + #TODO: use priority derived or set whichever is higher + p.priority = IssuePriority.find_by_position(priority_position) + else + p.priority = IssuePriority.find_by_position(priority_position) + end elsif default_priority = IssuePriority.default p.priority = default_priority end @@ -1850,8 +1902,8 @@ class Issue < ActiveRecord::Base end end - if p.done_ratio_derived? - # done ratio = average ratio of children weighted with their total estimated hours + if p.done_ratio_derived? || p.done_ratio_partially_derived? + # done ratio = average ratio of children weighted with their total estimated hours (or lower if partially derived) unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio children = p.children.to_a if children.any? @@ -1870,7 +1922,13 @@ class Issue < ActiveRecord::Base estimated * ratio end progress = done / (average * children.count) - p.done_ratio = progress.floor + if done_ratio_partially_derived? + #done ratio like done_ratio_derived, but parent done ratio can be lower than calculated done ratio + #TODO: use done_ratio derived or set, whichever is lower + p.done_ratio = [progress.floor, p.done_ratio].compact.min + else + p.done_ratio = progress.floor + end end end end diff --git a/config/locales/de.yml b/config/locales/de.yml index 9b488045d..9b86231d5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -148,6 +148,8 @@ de: earlier_than_minimum_start_date: "kann wegen eines Vorgängertickets nicht vor %{date} liegen" later_than_minimum_start_date: "kann wegen eines Untertickets nicht nach %{date} liegen" earlier_than_maximum_due_date: "kann wegen eines Untertickets nicht vor %{date} liegen" + priority_lower_than_children: "(%{own_priority}) kann nicht niedriger sein als die höchste Priorität eines Untertickets (%{priority})" + done_ratio_higher_than_children: "kann nicht höher sein, als der Ticketfortschritt der Untertickets (%{done_ratio})" not_a_regexp: "ist kein gültiger regulärer Ausdruck" open_issue_with_closed_parent: "Ein offenes Ticket kann nicht an ein geschlossenes übergeordnetes Ticket angehängt werden" must_contain_uppercase: "muss Großbuchstaben (A-Z) enthalten" diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index bb211fc8e..5bdb2ae16 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -136,6 +136,8 @@ en-GB: earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" later_than_minimum_start_date: "cannot be later than %{date} because of child issues" earlier_than_maximum_due_date: "cannot be earlier than %{date} because of child issues" + priority_lower_than_children: "(%{own_priority}) cannot be lower than maximum priority derived from children (%{priority})" + done_ratio_higher_than_children: "cannot be higher than done ratio derived from children (%{done_ratio})" not_a_regexp: "is not a valid regular expression" open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" must_contain_uppercase: "must contain uppercase letters (A-Z)" diff --git a/config/locales/en.yml b/config/locales/en.yml index 0e0c9255d..4c2ed945a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -132,6 +132,8 @@ en: earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" earlier_than_maximum_due_date: "cannot be earlier than %{date} because of child issues" later_than_minimum_start_date: "cannot be later than %{date} because of child issues" + priority_lower_than_children: "(%{own_priority}) cannot be lower than maximum priority derived from children (%{priority})" + done_ratio_higher_than_children: "cannot be higher than done ratio derived from children (%{done_ratio})" not_a_regexp: "is not a valid regular expression" open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task" must_contain_uppercase: "must contain uppercase letters (A-Z)" -- 2.30.2 From: redacted Date: Thu, 21 Oct 2021 15:54:48 +0200 Subject: Fix priority update for partially derived case --- app/models/issue.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/issue.rb b/app/models/issue.rb index de1cdb011..5b1fc9622 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1875,7 +1875,7 @@ class Issue < ActiveRecord::Base if p.priority_partially_derived? #like priority_derived, but priority can be higher than highest childs priority #TODO: use priority derived or set whichever is higher - p.priority = IssuePriority.find_by_position(priority_position) + p.priority = IssuePriority.find_by_position([p.priority.position, priority_position].compact.max) else p.priority = IssuePriority.find_by_position(priority_position) end -- 2.30.2