From f0d50a280e09eee431872cebd1a1258cc3b8b858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Sun, 17 Mar 2024 07:30:49 +0100 Subject: [PATCH] Adapt weekly repeating algorithm to consider subtasks --- .../repeatinginfo/TaskRepeatWeekDayInfo.java | 9 ++ .../java/core/entities/timemanager/Task.java | 75 +++++++++-- .../core/entities/timemanager/TaskSerie.java | 5 + .../entities/timemanager/TaskSerieItem.java | 6 + .../timemanager/TaskRepository.java | 9 ++ .../java/core/services/TaskSeriesService.java | 117 ++++++++++-------- .../main/java/core/services/TaskService.java | 1 + backend/src/main/java/util/Tripel.java | 15 +++ backend/src/main/java/util/Tupel.java | 20 +++ 9 files changed, 192 insertions(+), 65 deletions(-) create mode 100644 backend/src/main/java/util/Tripel.java create mode 100644 backend/src/main/java/util/Tupel.java diff --git a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java index 4330b2b..ab43ff6 100644 --- a/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/tasks/repeatinginfo/TaskRepeatWeekDayInfo.java @@ -8,6 +8,7 @@ import java.time.DayOfWeek; public class TaskRepeatWeekDayInfo { private int offset; private long taskID; + private DayOfWeek dayOfWeek; public int getOffset() { return offset; @@ -24,4 +25,12 @@ public class TaskRepeatWeekDayInfo { public void setTaskID(long taskID) { this.taskID = taskID; } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } } diff --git a/backend/src/main/java/core/entities/timemanager/Task.java b/backend/src/main/java/core/entities/timemanager/Task.java index c1a8d0f..8d05038 100644 --- a/backend/src/main/java/core/entities/timemanager/Task.java +++ b/backend/src/main/java/core/entities/timemanager/Task.java @@ -1,14 +1,15 @@ package core.entities.timemanager; import core.api.models.timemanager.tasks.TaskFieldInfo; +import core.api.models.timemanager.tasks.repeatinginfo.DeadlineStrategy; +import util.Tripel; +import util.Tupel; import javax.persistence.*; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.time.temporal.ChronoUnit; +import java.util.*; @Entity @Table(name = "tasks") @@ -54,14 +55,59 @@ public class Task { this.finishable = taskFieldInfo.isFinishable(); } - public static Task cloneTask(Task task) { + public Tripel, Collection> cloneTask() { + Collection clonedTasks = new ArrayList<>(); + Collection clonedSchedules = new ArrayList<>(); + Task clonedTask = new Task(); - clonedTask.setTaskgroup(task.getTaskgroup()); - clonedTask.setTaskName(task.taskName); - clonedTask.setEta(task.eta); - clonedTask.setFinished(false); - clonedTask.setFinishable(task.finishable); - return clonedTask; + clonedTasks.add(clonedTask); + + clonedTask.setTaskgroup(this.getTaskgroup()); + clonedTask.setTaskName(this.taskName); + clonedTask.setEta(this.eta); + clonedTask.setFinished(this.finished); + clonedTask.setFinishable(this.finishable); + clonedTask.setStartDate(this.startDate); + clonedTask.setDeadline(this.deadline); + + for(AbstractSchedule abstractSchedule : this.basicTaskSchedules) { + AbstractSchedule clonedSchedule = abstractSchedule.cloneSchedule(); + clonedSchedules.add(clonedSchedule); + } + + Set clonedSubtasks = new HashSet<>(); + for(Task task : this.subtasks) { + Tripel, Collection> clonedSubtask = task.cloneTask(); + clonedSubtask.getValue00().setParent(clonedTask); + clonedSubtasks.add(clonedSubtask.getValue00()); + + clonedTasks.addAll(clonedSubtask.getValue01()); + clonedSchedules.addAll(clonedSubtask.getValue02()); + } + clonedTask.setSubtasks(clonedSubtasks); + return new Tripel<>(clonedTask, clonedTasks, clonedSchedules); + } + + public void shiftTask(long startingDayDifference, long endingDayDifference) { + this.setStartDate(this.getStartDate().plusDays(startingDayDifference)); + this.setDeadline(this.getDeadline().plusDays(endingDayDifference)); + + for(AbstractSchedule abstractSchedule : this.basicTaskSchedules) { + abstractSchedule.shiftSchedule(startingDayDifference); + } + + for(Task subtask: this.subtasks) { + subtask.shiftTask(startingDayDifference, endingDayDifference); + } + } + + public void shiftTask(long startingDayDifference) { + this.setStartDate(this.getStartDate().plusDays(startingDayDifference)); + this.setDeadline(this.getStartDate()); + + for(Task subtask: this.subtasks) { + subtask.shiftTask(startingDayDifference); + } } @@ -253,4 +299,11 @@ public class Task { public void increaseWorkTime(long minutes) { this.workTime += (int) minutes; } + + public void shiftStartingTask(long dayDifference) { + this.setStartDate(this.getStartDate().plusDays(dayDifference)); + for (Task subtask : this.subtasks) { + subtask.shiftTask(dayDifference); + } + } } diff --git a/backend/src/main/java/core/entities/timemanager/TaskSerie.java b/backend/src/main/java/core/entities/timemanager/TaskSerie.java index 50be1c7..87df8cd 100644 --- a/backend/src/main/java/core/entities/timemanager/TaskSerie.java +++ b/backend/src/main/java/core/entities/timemanager/TaskSerie.java @@ -39,4 +39,9 @@ public class TaskSerie { this.tasks.add(taskSerieItem); return taskSerieItem; } + + public void addItem(TaskSerieItem taskSerieItem) { + this.tasks.add(taskSerieItem); + taskSerieItem.setTaskSerie(this); + } } diff --git a/backend/src/main/java/core/entities/timemanager/TaskSerieItem.java b/backend/src/main/java/core/entities/timemanager/TaskSerieItem.java index 533a931..3cf7ee8 100644 --- a/backend/src/main/java/core/entities/timemanager/TaskSerieItem.java +++ b/backend/src/main/java/core/entities/timemanager/TaskSerieItem.java @@ -31,6 +31,12 @@ public class TaskSerieItem { public TaskSerieItem() { } + public TaskSerieItem(Task task, int itemIndex) { + this.taskSerie = null; + this.seriesIndex = itemIndex; + this.task = task; + } + public long getItemID() { return itemID; } diff --git a/backend/src/main/java/core/repositories/timemanager/TaskRepository.java b/backend/src/main/java/core/repositories/timemanager/TaskRepository.java index 208dbe6..df0bdda 100644 --- a/backend/src/main/java/core/repositories/timemanager/TaskRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/TaskRepository.java @@ -25,6 +25,7 @@ public interface TaskRepository extends CrudRepository { @Transactional @Modifying + @Query(value = "DELETE FROM Task t WHERE t.taskID = ?1") void deleteByTaskID(long taskID); @Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.deadline is NOT NULL AND t.deadline < ?2 AND t.finished = FALSE") @@ -35,4 +36,12 @@ public interface TaskRepository extends CrudRepository { @Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND (t.startDate IS NULL OR t.startDate <= ?2) AND t.finished = FALSE") List findAllActive(String username, LocalDate now); + + @Query(value = "SELECT t FROM Task t WHERE t.taskgroup = ?1") + List findAllByTaskgroup(Taskgroup taskgroup); + + @Modifying + @Transactional + @Query(value = "UPDATE Task t SET t.parent = null WHERE t.taskgroup = ?1") + void deleteTaskHierarchyWhereTaskgroup(Taskgroup taskgroup); } diff --git a/backend/src/main/java/core/services/TaskSeriesService.java b/backend/src/main/java/core/services/TaskSeriesService.java index 7f1eb85..93827fd 100644 --- a/backend/src/main/java/core/services/TaskSeriesService.java +++ b/backend/src/main/java/core/services/TaskSeriesService.java @@ -11,14 +11,13 @@ import core.repositories.timemanager.TaskSerieItemRepository; import core.repositories.timemanager.TaskSeriesRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import util.Tupel; +import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalDate; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; +import java.util.*; @Service public class TaskSeriesService { @@ -30,59 +29,79 @@ public class TaskSeriesService { public ServiceExitCode createTaskSeries(TaskRepeatWeekInfo taskRepeatInfo) { - List createdTasks = new ArrayList<>(); + Map> offsetMap = calcOffsetMap(taskRepeatInfo); + TaskSerie taskSerie = new TaskSerie(); - List abstractSchedules = new ArrayList<>(); + List clonedTasks = new ArrayList<>(); + List clonedSchedules = new ArrayList<>(); + int weekDayIndex = 0; + for(TaskRepeatWeekDayInfo weekDayInfo : taskRepeatInfo.getWeekDayInfos()) { + Optional requestedTask = taskRepository.findById(weekDayInfo.getTaskID()); + if(requestedTask.isEmpty()) return ServiceExitCode.MISSING_ENTITY; + Task rootTask = requestedTask.get(); - for(TaskRepeatWeekDayInfo taskRepeatDayInfo : taskRepeatInfo.getWeekDayInfos()) { - Optional task = taskRepository.findById(taskRepeatDayInfo.getTaskID()); - if(task.isEmpty()) return ServiceExitCode.MISSING_ENTITY; + LocalDate currentDate = rootTask.getStartDate().plusDays(weekDayInfo.getOffset()); - TaskSerieItem rootItem = taskSerie.addTask(task.get()); - task.get().setTaskSerieItem(rootItem); + int itemIndex = weekDayIndex; + while(currentDate.isBefore(taskRepeatInfo.getEndDate())) { + var cloneResult = rootTask.cloneTask(); + Task clonedRootTask = cloneResult.getValue00(); + clonedTasks.addAll(cloneResult.getValue01()); + clonedSchedules.addAll(cloneResult.getValue02()); - LocalDate currentTaskDate = task.get().getStartDate().plusDays(taskRepeatDayInfo.getOffset()); - while(currentTaskDate.isBefore(taskRepeatInfo.getEndDate())) { - Task clonedTask = Task.cloneTask(task.get()); - clonedTask.setStartDate(currentTaskDate); - - TaskSerieItem taskSerieItem = taskSerie.addTask(clonedTask); - clonedTask.setTaskSerieItem(taskSerieItem); - createdTasks.add(clonedTask); - - abstractSchedules.addAll(cloneSchedules(task.get(), clonedTask)); - currentTaskDate = currentTaskDate.plusDays(taskRepeatDayInfo.getOffset()); - } - } - - taskSerie.getTasks().sort(Comparator.comparing(o -> o.getTask().getStartDate())); - for(int i=0; i offsetEntry = offsetMap.get(weekDayInfo); + clonedRootTask.shiftTask(offsetEntry.getValue00(), offsetEntry.getValue01()); + + currentDate = currentDate.plusDays(weekDayInfo.getOffset()); + itemIndex += taskRepeatInfo.getWeekDayInfos().size(); } + + + weekDayIndex++; } + + taskSeriesRepository.save(taskSerie); - taskRepository.saveAll(createdTasks); + taskRepository.saveAll(clonedTasks); taskSerieItemRepository.saveAll(taskSerie.getTasks()); - scheduleRepository.saveAll(abstractSchedules); + scheduleRepository.saveAll(clonedSchedules); return ServiceExitCode.OK; } + private HashMap> calcOffsetMap(TaskRepeatWeekInfo weekInfo) { + HashMap> offsetMap = new HashMap<>(); + weekInfo.getWeekDayInfos().sort(Comparator.comparing(TaskRepeatWeekDayInfo::getDayOfWeek)); + for(int i=0; i(offset, offset)); + } else { + int startingOffset = weekInfo.getWeekDayInfos().get(i).getOffset() -1; + + DayOfWeek nextWeekday = null; + if(i == weekInfo.getWeekDayInfos().size()-1) { + nextWeekday = weekInfo.getWeekDayInfos().get(0).getDayOfWeek(); + } else { + nextWeekday = weekInfo.getWeekDayInfos().get(i+1).getDayOfWeek(); + } + + DayOfWeek currentWeekDay = weekInfo.getWeekDayInfos().get(i).getDayOfWeek(); + + int endingOffset = startingOffset + Math.abs(nextWeekday.getValue() - currentWeekDay.getValue()); + + offsetMap.put(weekInfo.getWeekDayInfos().get(i), new Tupel<>(startingOffset, endingOffset)); + } + } + return offsetMap; + } + public ServiceExitCode createTaskSeries(Task rootTask, TaskRepeatDayInfo taskRepeatInfo) { if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.FIX_DEADLINE) { return ServiceExitCode.INVALID_PARAMETER; @@ -96,18 +115,8 @@ public class TaskSeriesService { LocalDate currentTaskDate = rootTask.getStartDate().plusDays(taskRepeatInfo.getOffset()); while(currentTaskDate.isBefore(taskRepeatInfo.getEndingDate())) { - Task task = Task.cloneTask(rootTask); - task.setStartDate(currentTaskDate); - if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_EQUAL_START) { - task.setDeadline(currentTaskDate); - } else if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_FIT_START) { - task.setDeadline(currentTaskDate.plusDays(taskRepeatInfo.getOffset()-1)); - } - TaskSerieItem taskSerieItem = taskSerie.addTask(task); - taskList.add(task); - task.setTaskSerieItem(taskSerieItem); + Tupel> clonedTasks = rootTask.cloneTask(); - abstractSchedules.addAll(cloneSchedules(rootTask, task)); currentTaskDate = currentTaskDate.plusDays(taskRepeatInfo.getOffset()); } diff --git a/backend/src/main/java/core/services/TaskService.java b/backend/src/main/java/core/services/TaskService.java index 168cdda..ae89548 100644 --- a/backend/src/main/java/core/services/TaskService.java +++ b/backend/src/main/java/core/services/TaskService.java @@ -114,6 +114,7 @@ public class TaskService { public void clearTasks(Taskgroup taskgroup) { taskSeriesService.deleteTaskSerieByTaskgroup(taskgroup); taskScheduleService.deleteSchedulesByTaskgroup(taskgroup); + taskRepository.deleteTaskHierarchyWhereTaskgroup(taskgroup); taskRepository.deleteAllByTaskgroup(taskgroup); } diff --git a/backend/src/main/java/util/Tripel.java b/backend/src/main/java/util/Tripel.java new file mode 100644 index 0000000..71ab146 --- /dev/null +++ b/backend/src/main/java/util/Tripel.java @@ -0,0 +1,15 @@ +package util; + +public class Tripel extends Tupel { + + private final C value02; + + public Tripel(A value00, B value01, C value02) { + super(value00, value01); + this.value02 = value02; + } + + public C getValue02() { + return value02; + } +} diff --git a/backend/src/main/java/util/Tupel.java b/backend/src/main/java/util/Tupel.java new file mode 100644 index 0000000..b7749e7 --- /dev/null +++ b/backend/src/main/java/util/Tupel.java @@ -0,0 +1,20 @@ +package util; + +public class Tupel { + + private final A value00; + private final B value01; + + public Tupel(A value00, B value01) { + this.value00 = value00; + this.value01 = value01; + } + + public A getValue00() { + return value00; + } + + public B getValue01() { + return value01; + } +}