mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Prohibit scheduling automatically in API if no predecessors or children
Only when creating a work package. The only case where it's possible is when creating a work package with a parent explicitly set, and that parent is automatically scheduled and has at least one predecessor. When copying work packages, this restriction is removed as predecessors and children are added later in the process.
This commit is contained in:
@@ -36,5 +36,11 @@ module WorkPackages
|
||||
# a scenario where this field must be writable
|
||||
attribute :done_ratio,
|
||||
writable: true
|
||||
|
||||
# Do not validate predecessors or children presence when copying: when
|
||||
# copying, it's possible to create a work package in automatic scheduling
|
||||
# mode even if it has no predecessors or children yet. They will be added
|
||||
# later in the process.
|
||||
def validate_has_predecessors_or_children; end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,6 +42,10 @@ module WorkPackages
|
||||
# Note that nil would not override and [] would ignore the default permission, so we use the default here:
|
||||
permission: :add_work_packages
|
||||
|
||||
attribute :schedule_manually do
|
||||
validate_has_predecessors_or_children if model.schedule_automatically?
|
||||
end
|
||||
|
||||
default_attribute_permission :add_work_packages
|
||||
|
||||
validate :user_allowed_to_add
|
||||
@@ -66,5 +70,11 @@ module WorkPackages
|
||||
# lock version is initialized by AR itself
|
||||
super - ["lock_version"]
|
||||
end
|
||||
|
||||
def validate_has_predecessors_or_children
|
||||
if Relation.used_for_scheduling_of(model.parent).empty? && model.children.empty?
|
||||
errors.add(:schedule_manually, :cannot_be_automatically_scheduled)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,6 +43,8 @@ module Relations::Scopes
|
||||
# soon as a parent is manually scheduled, its predecessors and ancestors
|
||||
# are not involved in scheduling anymore.
|
||||
def used_for_scheduling_of(work_package)
|
||||
return [] if work_package.nil?
|
||||
|
||||
automatically_scheduled_ancestors =
|
||||
WorkPackageHierarchy.where(descendant_id: work_package.id)
|
||||
.where.not(ancestor_id: manually_scheduled_ancestors(work_package).select(:ancestor_id))
|
||||
|
||||
@@ -1498,6 +1498,8 @@ en:
|
||||
cannot_be_self_assigned: "cannot be assigned to itself."
|
||||
cannot_be_in_another_project: "cannot be in another project."
|
||||
not_a_valid_parent: "is invalid."
|
||||
schedule_manually:
|
||||
cannot_be_automatically_scheduled: "cannot be set to false (automatically scheduled) as it has no predecessors or children."
|
||||
start_date:
|
||||
violates_relationships: "can only be set to %{soonest_start} or later so as not to violate the work package's relationships."
|
||||
cannot_be_null: "can not be set to null as finish date and duration are known."
|
||||
|
||||
@@ -28,7 +28,10 @@ properties:
|
||||
- description: The work package description
|
||||
scheduleManually:
|
||||
type: boolean
|
||||
description: If false (default) schedule automatically.
|
||||
description: |-
|
||||
Uses manual scheduling mode when true (default). Uses automatic scheduling
|
||||
mode when false. Can be automatic only when predecessors or children are
|
||||
present.
|
||||
readonly:
|
||||
type: boolean
|
||||
description: If true, the work package is in a readonly status so with the exception of the status, no other property can be altered.
|
||||
|
||||
@@ -14,7 +14,10 @@ properties:
|
||||
- description: The work package description
|
||||
scheduleManually:
|
||||
type: boolean
|
||||
description: If false (default) schedule automatically.
|
||||
description: |-
|
||||
Uses manual scheduling mode when true (default). Uses automatic scheduling
|
||||
mode when false. Can be automatic only when predecessors or children are
|
||||
present.
|
||||
startDate:
|
||||
type:
|
||||
- 'string'
|
||||
@@ -41,9 +44,9 @@ properties:
|
||||
description: |-
|
||||
The amount of time in hours the work package needs to be completed. This value must be bigger or equal to `P1D`,
|
||||
and any the value will get floored to the nearest day.
|
||||
|
||||
|
||||
The duration has no effect, unless either a start date or a due date is set.
|
||||
|
||||
|
||||
Not available for milestone type of work packages.
|
||||
ignoreNonWorkingDays:
|
||||
type: boolean
|
||||
|
||||
@@ -53,7 +53,7 @@ description: |-
|
||||
| subject | Work package subject | String | not null; 1 <= length <= 255 | READ / WRITE | |
|
||||
| type | Name of the work package's type | String | not null | READ | |
|
||||
| description | The work package description | Formattable | | READ / WRITE | |
|
||||
| scheduleManually | If false (default) schedule automatically. | Boolean | | READ / WRITE | |
|
||||
| scheduleManually | Uses manual scheduling mode when true (default). Uses automatic scheduling mode when false. Can be automatic only when predecessors or children are present. | Boolean | | READ / WRITE | |
|
||||
| startDate | Scheduled beginning of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be equal or greater than the earliest possible start date; Exists only on work packages of a non milestone type | READ / WRITE | |
|
||||
| dueDate | Scheduled end of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be greater than or equal to the start date; Exists only on work packages of a non milestone type | READ / WRITE | |
|
||||
| date | Date on which a milestone is achieved | Date | Exists only on work packages of a milestone type | READ / WRITE | |
|
||||
@@ -83,9 +83,9 @@ description: |-
|
||||
|
||||
Properties that cannot be set directly on parent work packages are inferred from their children instead:
|
||||
|
||||
* `startDate` is the earliest start date from its children if manual scheduling is activated.
|
||||
* `startDate` is the earliest start date from its children if automatic scheduling is activated.
|
||||
|
||||
* `dueDate` is the latest finish date from its children if manual scheduling is activated.
|
||||
* `dueDate` is the latest finish date from its children if automatic scheduling is activated.
|
||||
|
||||
* `derivedEstimatedTime` is the sum of estimated times from its children and the work package's own estimated time.
|
||||
|
||||
|
||||
@@ -53,13 +53,13 @@ module API
|
||||
call.result
|
||||
end
|
||||
|
||||
post &::API::V3::Utilities::Endpoints::Create.new(model: WorkPackage,
|
||||
post(&::API::V3::Utilities::Endpoints::Create.new(model: WorkPackage,
|
||||
parse_service: WorkPackages::ParseParamsService,
|
||||
params_modifier: ->(attributes) {
|
||||
attributes[:send_notifications] = notify_according_to_params
|
||||
attributes
|
||||
})
|
||||
.mount
|
||||
.mount)
|
||||
|
||||
route_param :id, type: Integer, desc: "Work package ID" do
|
||||
helpers WorkPackagesSharedHelpers
|
||||
|
||||
@@ -253,11 +253,11 @@ RSpec.describe "API v3 Work package resource",
|
||||
end
|
||||
|
||||
context "when the work package has no direct or indirect predecessors and no children" do
|
||||
# TODO: should the API return an error here?
|
||||
it "does not set the scheduling mode to automatic as requested " \
|
||||
"and keeps manual scheduling mode (schedule_manually: true)" do
|
||||
expect(created_work_package.schedule_manually).to be true
|
||||
end
|
||||
it_behaves_like "error response",
|
||||
422,
|
||||
"PropertyConstraintViolation",
|
||||
I18n.t("activerecord.errors.models.work_package.attributes." \
|
||||
"schedule_manually.cannot_be_automatically_scheduled")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user