Skip to content

Commit

Permalink
[59539] Start as soon as possible in automatic scheduling mode
Browse files Browse the repository at this point in the history
The original start date of the work package is discarded. The soonest
start date is always used instead.
  • Loading branch information
cbliard committed Dec 11, 2024
1 parent 31f6865 commit e62c9e6
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 135 deletions.
2 changes: 1 addition & 1 deletion app/services/work_packages/set_schedule_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def reschedule_by_descendants(scheduled, dependency)
def reschedule_by_predecessors(scheduled, dependency)
return unless dependency.soonest_start_date

new_start_date = [scheduled.start_date, dependency.soonest_start_date].compact.max
new_start_date = dependency.soonest_start_date
new_due_date = determine_due_date(scheduled, new_start_date)
set_dates(scheduled, new_start_date, new_due_date)
assign_cause_for_journaling(scheduled, :predecessor)
Expand Down
131 changes: 87 additions & 44 deletions spec/services/work_packages/set_schedule_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def create_parent(child, start_date: child.start_date, due_date: child.due_date)

def create_child(parent, start_date, due_date, **attributes)
create(:work_package,
subject: "child of #{parent.subject}",
subject: "child #{parent.children.count + 1} of #{parent.subject}",
start_date:,
due_date:,
parent:,
Expand All @@ -127,16 +127,13 @@ def create_child(parent, start_date, due_date, **attributes)
subject { instance.call(attributes) }

shared_examples_for "reschedules" do
before do
subject
end
it "successfully updates the following work packages", :aggregate_failures do # rubocop:disable RSpec/ExampleLength
expect(subject).to be_success

it "is success" do
expect(subject)
.to be_success
end
# returns only the original and the changed work packages
expect(subject.all_results)
.to contain_exactly(work_package, *expected.keys)

it "updates the following work packages" do
expected.each do |wp, (start_date, due_date)|
expected_cause_type = "work_package_related_changed_times"
result = subject.all_results.find { |result_wp| result_wp.id == wp.id }
Expand Down Expand Up @@ -170,29 +167,15 @@ def create_child(parent, start_date, due_date, **attributes)
"to have duration #{duration.inspect}, got #{result.duration.inspect}"
end
end

it "returns only the original and the changed work packages" do
expect(subject.all_results)
.to match_array expected.keys + [work_package]
end
end

shared_examples_for "does not reschedule" do
before do
subject
end

it "is success" do
expect(subject)
.to be_success
end
it "is successful and does not change any other work packages nor assign any journal cause" do
expect(subject).to be_success

it "does not change any other work packages" do
expect(subject.all_results)
.to contain_exactly(work_package)
end

it "does not assign a journal cause" do
subject.all_results.each do |work_package|
expect(work_package.journal_cause).to be_blank
end
Expand Down Expand Up @@ -266,15 +249,19 @@ def create_child(parent, start_date, due_date, **attributes)
end
end

context "when moving forward with the follower having enough space left to not be moved at all" do
context "when moving forward with the follower having enough space left to start earlier" do
let(:follower1_start_date) { Time.zone.today + 10.days }
let(:follower1_due_date) { Time.zone.today + 12.days }

before do
work_package.due_date = Time.zone.today + 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today + 6.days, Time.zone.today + 8.days] }
end
end
end

context "when moving forward with the follower having some space left and a lag" do
Expand All @@ -301,6 +288,7 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today + 5.days
end

# no need to reschedule: the successor is already right after its predecessor
it_behaves_like "does not reschedule"
end

Expand All @@ -309,7 +297,11 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today - 4.days, Time.zone.today - 2.days] }
end
end
end

context "when moving backwards with space between" do
Expand All @@ -320,7 +312,11 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today - 4.days, Time.zone.today - 2.days] }
end
end
end

context 'when moving backwards with the follower having no start date (which should not happen) \
Expand Down Expand Up @@ -426,9 +422,9 @@ def create_child(parent, start_date, due_date, **attributes)
create_follower(follower1_start_date,
follower1_due_date,
{ work_package => follower1_lag,
another_successor => 0 })
another_predecessor => 0 })
end
let(:another_successor) do
let(:another_predecessor) do
create(:work_package,
start_date: nil,
due_date: nil)
Expand All @@ -451,7 +447,11 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today - 4.days, Time.zone.today - 2.days] }
end
end
end
end
end
Expand Down Expand Up @@ -591,7 +591,12 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today - 4.days, Time.zone.today - 2.days],
parent_following_work_package1 => [Time.zone.today - 4.days, Time.zone.today - 2.days] }
end
end
end
end

Expand Down Expand Up @@ -651,7 +656,7 @@ def create_child(parent, start_date, due_date, **attributes)
let(:follower1_due_date) { work_package_due_date + 10.days }
let(:child1_start_date) { follower1_start_date }
let(:child1_due_date) { follower1_start_date + 3.days }
let(:child2_start_date) { follower1_start_date + 8.days }
let(:child2_start_date) { follower1_due_date - 1.day }
let(:child2_due_date) { follower1_due_date }

let(:child1_work_package) do
Expand All @@ -674,38 +679,62 @@ def create_child(parent, start_date, due_date, **attributes)
end

context "with unchanged dates (e.g. when creating a follows relation) and successor starting 1 day after scheduled" do
it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{
# child1 is not rescheduled as it is already starting right after the moved work package
# child2 is rescheduled right after the moved work package
child2_work_package => [work_package_due_date + 1.day, work_package_due_date + 2.days],
# following is rescheduled to match its 2 children's dates
following_work_package1 => [work_package_due_date + 1.day, work_package_due_date + 4.days]
}
end
end
end

context "with unchanged dates (e.g. when creating a follows relation) and successor starting 3 days after scheduled" do
let(:follower1_start_date) { work_package_due_date + 3.days }
let(:follower1_due_date) { follower1_start_date + 10.days }
let(:child1_start_date) { follower1_start_date }
let(:child1_due_date) { follower1_start_date + 6.days }
let(:child2_start_date) { follower1_start_date + 8.days }
let(:child2_start_date) { follower1_due_date - 1.day }
let(:child2_due_date) { follower1_due_date }

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{
# child1 and child2 rescheduled right after the moved work package
child1_work_package => [work_package_due_date + 1.day, work_package_due_date + 7.days],
child2_work_package => [work_package_due_date + 1.day, work_package_due_date + 2.days],
# following is rescheduled to match its 2 children's dates
following_work_package1 => [work_package_due_date + 1.day, work_package_due_date + 7.days]
}
end
end
end

context "with unchanged dates (e.g. when creating a follows relation) and successor's first child needs to be rescheduled" do
let(:follower1_start_date) { work_package_due_date - 3.days }
let(:follower1_due_date) { work_package_due_date + 10.days }
let(:child1_start_date) { follower1_start_date }
let(:child1_due_date) { follower1_start_date + 6.days }
let(:child2_start_date) { follower1_start_date + 8.days }
let(:child2_start_date) { follower1_due_date - 5.days }
let(:child2_due_date) { follower1_due_date }

# following parent is reduced in length as the children allow to be executed at the same time
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [work_package_due_date + 1.day, follower1_due_date],
child1_work_package => [work_package_due_date + 1.day, follower1_start_date + 10.days] }
{ # child1 and child2 rescheduled right after the moved work package
child1_work_package => [work_package_due_date + 1.day, work_package_due_date + 7.days],
child2_work_package => [work_package_due_date + 1.day, work_package_due_date + 6.days],
# following is rescheduled to match its 2 children's dates
following_work_package1 => [work_package_due_date + 1.day, work_package_due_date + 7.days]
}
end
end
end

context 'with unchanged dates (e.g. when creating a follows relation) and successor\s children need to be rescheduled' do
context "with unchanged dates (e.g. when creating a follows relation) and successor's children need to be rescheduled" do
let(:follower1_start_date) { work_package_due_date - 8.days }
let(:follower1_due_date) { work_package_due_date + 10.days }
let(:child1_start_date) { follower1_start_date }
Expand Down Expand Up @@ -767,7 +796,8 @@ def create_child(parent, start_date, due_date, **attributes)
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [Time.zone.today + 6.days, Time.zone.today + 8.days],
following_work_package2 => [Time.zone.today + 9.days, Time.zone.today + 12.days] }
following_work_package2 => [Time.zone.today + 9.days, Time.zone.today + 12.days],
following_work_package3 => [Time.zone.today + 13.days, Time.zone.today + 14.days] }
end
end
end
Expand All @@ -777,7 +807,13 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [work_package.due_date + 1.day, work_package.due_date + 3.days],
following_work_package2 => [work_package.due_date + 4.days, work_package.due_date + 8.days],
following_work_package3 => [work_package.due_date + 9.days, work_package.due_date + 10.days] }
end
end
end
end

Expand Down Expand Up @@ -826,7 +862,14 @@ def create_child(parent, start_date, due_date, **attributes)
work_package.due_date = Time.zone.today - 5.days
end

it_behaves_like "does not reschedule"
it_behaves_like "reschedules" do
let(:expected) do
{ following_work_package1 => [work_package.due_date + 1.day, work_package.due_date + 3.days],
following_work_package2 => [work_package.due_date + 4.days, work_package.due_date + 8.days],
following_work_package3 => [work_package.due_date + 1.day, work_package.due_date + 4.days],
following_work_package4 => [work_package.due_date + 9.days, work_package.due_date + 10.days] }
end
end
end
end

Expand Down
Loading

0 comments on commit e62c9e6

Please sign in to comment.