[63475] Preserve unset dates when moving work packages in calendar

https://community.openproject.org/wp/63475

If it's resized, both start and due dates are set.

If it's dragged and dropped and it's a milestone, it's the same as
resizing: both dates are set.

If it's dragged and dropped and it's not a milestone, if it has a
duration, set duration and start date to deal with non-working days, and
if it has no duration, set start date if it was already set, or set the
due date.
This commit is contained in:
Christophe Bliard
2025-05-16 17:21:57 +02:00
parent f2319838b2
commit fface454ae
3 changed files with 87 additions and 32 deletions
@@ -432,16 +432,40 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin {
updateDates(resizeInfo:EventResizeDoneArg|EventDropArg|EventReceiveArg, dragged?:boolean):ResourceChangeset<WorkPackageResource> {
const workPackage = resizeInfo.event.extendedProps.workPackage as WorkPackageResource;
const changeset = this.halEditing.edit(workPackage);
if (!workPackage.ignoreNonWorkingDays && workPackage.duration && dragged) {
changeset.setValue('duration', workPackage.duration);
} else {
const due = moment(resizeInfo.event.endStr)
.subtract(1, 'day')
.format('YYYY-MM-DD');
changeset.setValue('dueDate', due);
const startDate = resizeInfo.event.startStr;
const endDate = moment(resizeInfo.event.endStr).subtract(1, 'day').format('YYYY-MM-DD');
if (dragged && !this.isMilestone(workPackage)) {
return this.moveToDates(workPackage, startDate, endDate);
}
changeset.setValue('startDate', resizeInfo.event.startStr);
return this.changeToDates(workPackage, startDate, endDate);
}
private moveToDates(workPackage:WorkPackageResource, startDate:string, endDate:string):ResourceChangeset<WorkPackageResource> {
const changeset = this.halEditing.edit(workPackage);
// Due to non-working days, we can't directly set start and due date when
// drag-n-dropping work packages. Instead set duration (if present) and
// start date to get due date recomputed.
if (workPackage.duration) {
changeset.setValue('duration', workPackage.duration);
}
// Keep dates unset if they are not set
if (workPackage.startDate) {
changeset.setValue('startDate', startDate);
} else {
changeset.setValue('dueDate', endDate);
}
return changeset;
}
private changeToDates(workPackage:WorkPackageResource, startDate:string, endDate:string):ResourceChangeset<WorkPackageResource> {
const changeset = this.halEditing.edit(workPackage);
changeset.setValue('startDate', startDate);
changeset.setValue('dueDate', endDate);
return changeset;
}
}
@@ -34,17 +34,16 @@ RSpec.describe "Calendar drag&dop and resizing",
:selenium do
include_context "with calendar full access"
let!(:other_user) do
create(:user,
firstname: "Bernd",
member_with_permissions: { project => %w[view_work_packages view_calendar] })
end
let!(:work_package) do
shared_let(:monday) { Time.zone.today.beginning_of_week }
shared_let(:tuesday) { monday + 1.day }
shared_let(:wednesday) { monday + 2.days }
shared_let(:thursday) { monday + 3.days }
shared_let(:friday) { monday + 4.days }
shared_let(:work_package) do
create(:work_package,
project:,
start_date: Time.zone.today.beginning_of_week.next_occurring(:tuesday),
due_date: Time.zone.today.beginning_of_week.next_occurring(:thursday))
start_date: tuesday,
due_date: thursday)
end
before do
@@ -55,11 +54,10 @@ RSpec.describe "Calendar drag&dop and resizing",
context "with full permissions" do
it "allows to resize to change the dates of a wp" do
target = work_package.due_date + 1.day
target = friday
current_start = work_package.start_date
retry_block do
calendar.resize_date(work_package, target)
end
calendar.resize_end_date(work_package, target)
calendar.expect_and_dismiss_toaster(message: I18n.t("js.notice_successful_update"))
@@ -69,12 +67,10 @@ RSpec.describe "Calendar drag&dop and resizing",
end
it "allows to resize from the start" do
target = work_package.start_date - 1.day
target = monday
current_end = work_package.due_date
retry_block do
calendar.resize_date(work_package, target, end_date: false)
end
calendar.resize_start_date(work_package, target)
calendar.expect_and_dismiss_toaster(message: I18n.t("js.notice_successful_update"))
@@ -84,13 +80,9 @@ RSpec.describe "Calendar drag&dop and resizing",
end
it "allows to drag the work package to another date" do
target = Time.zone.today.beginning_of_week
target = monday
retry_block do
calendar.drag_event(work_package, target)
end
calendar.expect_and_dismiss_toaster(message: I18n.t("js.notice_successful_update"))
calendar.drag_event(work_package, target)
work_package.reload
@@ -98,9 +90,39 @@ RSpec.describe "Calendar drag&dop and resizing",
expect(work_package.start_date).to eq(target - 1.day)
expect(work_package.due_date).to eq(target + 1.day)
end
context "with work packages having only start or due date" do
shared_let(:start_only_wp) do
create(:work_package,
subject: "Start only",
project:,
start_date: tuesday)
end
shared_let(:due_only_wp) do
create(:work_package,
subject: "Due only",
project:,
due_date: thursday)
end
it "keeps one date set and the other unset when dragging them to another date (Bug #63475)" do
# move start_only_wp to wednesday
calendar.drag_event(start_only_wp, wednesday)
expect(start_only_wp.reload).to have_attributes(start_date: wednesday, due_date: nil)
# move due_only_wp to wednesday
calendar.drag_event(due_only_wp, wednesday)
expect(due_only_wp.reload).to have_attributes(start_date: nil, due_date: wednesday)
end
end
end
context "without permission to edit" do
let(:other_user) do
create(:user,
firstname: "Bernd",
member_with_permissions: { project => %w[view_work_packages view_calendar] })
end
let(:current_user) { other_user }
it "allows neither dragging nor resizing any wp" do
@@ -57,6 +57,14 @@ module Pages
::Pages::SplitWorkPackageCreate.new project:
end
def resize_start_date(work_package, date)
resize_date(work_package, date, end_date: false)
end
def resize_end_date(work_package, date)
resize_date(work_package, date, end_date: true)
end
def resize_date(work_package, date, end_date: true)
retry_block do
wp_strip = event(work_package)
@@ -81,6 +89,7 @@ module Pages
end_container = date_container target
drag_n_drop_element(from: start_container, to: end_container)
expect_and_dismiss_toaster(message: I18n.t("js.notice_successful_update"))
end
def date_container(date)