issue-106 #107
| @ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*; | |||||||
| import javax.validation.Valid; | import javax.validation.Valid; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @CrossOrigin(origins = "*", maxAge = 3600) | @CrossOrigin(origins = "*", maxAge = 3600) | ||||||
| @ -72,7 +73,11 @@ public class TaskController { | |||||||
|             return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); |             return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return ResponseEntity.ok(taskgroupPermissionResult.getResult().getTasks().stream().map(TaskEntityInfo::new)); |         List<Task> tasks = new ArrayList<>(); | ||||||
|  |         for(Task task : taskgroupPermissionResult.getResult().getTasks()) { | ||||||
|  |             if(task.getParent() == null) tasks.add(task); | ||||||
|  |         } | ||||||
|  |         return ResponseEntity.ok(tasks.stream().map(TaskEntityInfo::new)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @PutMapping("/tasks/{taskgroupID}") |     @PutMapping("/tasks/{taskgroupID}") | ||||||
| @ -164,4 +169,42 @@ public class TaskController { | |||||||
|         taskService.finishTask(taskPermissionResult.getResult()); |         taskService.finishTask(taskPermissionResult.getResult()); | ||||||
|         return ResponseEntity.ok(new SimpleStatusResponse("success")); |         return ResponseEntity.ok(new SimpleStatusResponse("success")); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @PutMapping("/tasks/{taskID}/subtasks") | ||||||
|  |     public ResponseEntity<?> onCreateSubTask(@PathVariable long taskID, @Valid @RequestBody TaskFieldInfo taskFieldInfo) { | ||||||
|  |         var taskPermissionResult = taskService.getTaskPermissions(taskID, SecurityContextHolder.getContext().getAuthentication().getName()); | ||||||
|  |         if(taskPermissionResult.hasIssue()) { | ||||||
|  |             return taskPermissionResult.mapToResponseEntity(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var serviceResult = taskService.createSubTask(taskPermissionResult.getResult(), taskFieldInfo); | ||||||
|  |         if(serviceResult.hasIssue()) { | ||||||
|  |             return serviceResult.mapToResponseEntity(); | ||||||
|  |         } else { | ||||||
|  |             return ResponseEntity.ok(new TaskEntityInfo(serviceResult.getResult())); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @GetMapping("/tasks/{taskID}/subtasks") | ||||||
|  |     public ResponseEntity<?> onListSubtasks(@PathVariable long taskID) { | ||||||
|  |         var taskPermissionResult = taskService.getTaskPermissions(taskID, SecurityContextHolder.getContext().getAuthentication().getName()); | ||||||
|  |         if(taskPermissionResult.hasIssue()) { | ||||||
|  |             return taskPermissionResult.mapToResponseEntity(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Collection<Task> subtasks = taskPermissionResult.getResult().getSubtasks(); | ||||||
|  |         return ResponseEntity.ok(subtasks.stream().map(TaskEntityInfo::new).toList()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @DeleteMapping("/tasks/{taskID}/subtasks") | ||||||
|  |     public ResponseEntity<?> onClearSubtasks(@PathVariable long taskID) { | ||||||
|  |         var taskPermissionResult = taskService.getTaskPermissions(taskID, SecurityContextHolder.getContext().getAuthentication().getName()); | ||||||
|  |         if(taskPermissionResult.hasIssue()) { | ||||||
|  |             return taskPermissionResult.mapToResponseEntity(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         ServiceExitCode result = taskService.clearSubTasks(taskPermissionResult.getResult()); | ||||||
|  |         return result.mapToResponseEntity(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -31,11 +31,14 @@ public class RecursiveTaskgroupInfo { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for(Task task : taskgroup.getActiveTasks()) { |         for(Task task : taskgroup.getActiveTasks()) { | ||||||
|  |             if(task.getParent() == null) { | ||||||
|                 this.activeTasks.add(new TaskOverviewInfo(task)); |                 this.activeTasks.add(new TaskOverviewInfo(task)); | ||||||
|                 if(task.getDeadline() != null && task.getDeadline().isBefore(LocalDate.now())) { |                 if(task.getDeadline() != null && task.getDeadline().isBefore(LocalDate.now())) { | ||||||
|                     this.hasOverdueTask = true; |                     this.hasOverdueTask = true; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|         this.amountActiveTasks = taskgroup.getAmountOfActiveTasks(); |         this.amountActiveTasks = taskgroup.getAmountOfActiveTasks(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -29,6 +29,8 @@ public class TaskEntityInfo { | |||||||
| 
 | 
 | ||||||
|     private boolean hasPlannedSchedules; |     private boolean hasPlannedSchedules; | ||||||
|     private boolean hasTaskSerie; |     private boolean hasTaskSerie; | ||||||
|  |     private boolean hasSubtasks; | ||||||
|  |     private boolean hasParent; | ||||||
| 
 | 
 | ||||||
|     public TaskEntityInfo(Task task) { |     public TaskEntityInfo(Task task) { | ||||||
|         this.taskID = task.getTaskID(); |         this.taskID = task.getTaskID(); | ||||||
| @ -49,6 +51,8 @@ public class TaskEntityInfo { | |||||||
|         this.hasActiveSchedules = task.hasActiveSchedule(); |         this.hasActiveSchedules = task.hasActiveSchedule(); | ||||||
|         this.hasPlannedSchedules = task.hasPlannedSchedules(); |         this.hasPlannedSchedules = task.hasPlannedSchedules(); | ||||||
|         this.hasTaskSerie = task.getTaskSerieItem() != null; |         this.hasTaskSerie = task.getTaskSerieItem() != null; | ||||||
|  |         this.hasSubtasks = task.getSubtasks() != null && !task.getSubtasks().isEmpty(); | ||||||
|  |         this.hasParent = task.getParent() != null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getTaskID() { |     public long getTaskID() { | ||||||
| @ -146,4 +150,20 @@ public class TaskEntityInfo { | |||||||
|     public void setHasTaskSerie(boolean hasTaskSerie) { |     public void setHasTaskSerie(boolean hasTaskSerie) { | ||||||
|         this.hasTaskSerie = hasTaskSerie; |         this.hasTaskSerie = hasTaskSerie; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isHasSubtasks() { | ||||||
|  |         return hasSubtasks; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setHasSubtasks(boolean hasSubtasks) { | ||||||
|  |         this.hasSubtasks = hasSubtasks; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isHasParent() { | ||||||
|  |         return hasParent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setHasParent(boolean hasParent) { | ||||||
|  |         this.hasParent = hasParent; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ public class TaskOverviewInfo { | |||||||
|     private boolean overdue; |     private boolean overdue; | ||||||
| 
 | 
 | ||||||
|     private boolean finishable; |     private boolean finishable; | ||||||
|  |     private boolean hasSubtasks; | ||||||
| 
 | 
 | ||||||
|     public TaskOverviewInfo(Task task) { |     public TaskOverviewInfo(Task task) { | ||||||
|         this.taskID = task.getTaskID(); |         this.taskID = task.getTaskID(); | ||||||
| @ -33,6 +34,7 @@ public class TaskOverviewInfo { | |||||||
|             this.overdue = LocalDate.now().isAfter(task.getDeadline()); |             this.overdue = LocalDate.now().isAfter(task.getDeadline()); | ||||||
|         } |         } | ||||||
|         this.finishable = task.isFinishable(); |         this.finishable = task.isFinishable(); | ||||||
|  |         this.hasSubtasks = !task.getSubtasks().isEmpty(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getTaskID() { |     public long getTaskID() { | ||||||
| @ -90,4 +92,12 @@ public class TaskOverviewInfo { | |||||||
|     public void setFinishable(boolean finishable) { |     public void setFinishable(boolean finishable) { | ||||||
|         this.finishable = finishable; |         this.finishable = finishable; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isHasSubtasks() { | ||||||
|  |         return hasSubtasks; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setHasSubtasks(boolean hasSubtasks) { | ||||||
|  |         this.hasSubtasks = hasSubtasks; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ import java.time.LocalDate; | |||||||
| public class TaskRepeatDayInfo { | public class TaskRepeatDayInfo { | ||||||
| 
 | 
 | ||||||
|     private int offset; |     private int offset; | ||||||
|     private DeadlineStrategy deadlineStrategy; |  | ||||||
| 
 | 
 | ||||||
|     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") |     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") | ||||||
|     private LocalDate endingDate; |     private LocalDate endingDate; | ||||||
| @ -20,14 +19,6 @@ public class TaskRepeatDayInfo { | |||||||
|         this.offset = offset; |         this.offset = offset; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public DeadlineStrategy getDeadlineStrategy() { |  | ||||||
|         return deadlineStrategy; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setDeadlineStrategy(DeadlineStrategy deadlineStrategy) { |  | ||||||
|         this.deadlineStrategy = deadlineStrategy; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public LocalDate getEndingDate() { |     public LocalDate getEndingDate() { | ||||||
|         return endingDate; |         return endingDate; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import java.time.DayOfWeek; | |||||||
| public class TaskRepeatWeekDayInfo { | public class TaskRepeatWeekDayInfo { | ||||||
|     private int offset; |     private int offset; | ||||||
|     private long taskID; |     private long taskID; | ||||||
|  |     private DayOfWeek dayOfWeek; | ||||||
| 
 | 
 | ||||||
|     public int getOffset() { |     public int getOffset() { | ||||||
|         return offset; |         return offset; | ||||||
| @ -24,4 +25,12 @@ public class TaskRepeatWeekDayInfo { | |||||||
|     public void setTaskID(long taskID) { |     public void setTaskID(long taskID) { | ||||||
|         this.taskID = taskID; |         this.taskID = taskID; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public DayOfWeek getDayOfWeek() { | ||||||
|  |         return dayOfWeek; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setDayOfWeek(DayOfWeek dayOfWeek) { | ||||||
|  |         this.dayOfWeek = dayOfWeek; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,16 +2,19 @@ package core.api.models.timemanager.tasks.repeatinginfo; | |||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.annotation.JsonFormat; | import com.fasterxml.jackson.annotation.JsonFormat; | ||||||
| import org.hibernate.validator.constraints.Length; | import org.hibernate.validator.constraints.Length; | ||||||
|  | import util.Tupel; | ||||||
| 
 | 
 | ||||||
| import javax.validation.constraints.Size; | import javax.validation.constraints.Size; | ||||||
|  | import java.time.DayOfWeek; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
|  | import java.util.Comparator; | ||||||
|  | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| public class TaskRepeatWeekInfo { | public class TaskRepeatWeekInfo { | ||||||
| 
 | 
 | ||||||
|     @Size(min = 1, max = 7) |     @Size(min = 1, max = 7) | ||||||
|     private List<TaskRepeatWeekDayInfo> weekDayInfos; |     private List<TaskRepeatWeekDayInfo> weekDayInfos; | ||||||
|     private DeadlineStrategy deadlineStrategy; |  | ||||||
|     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") |     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") | ||||||
|     private LocalDate endDate; |     private LocalDate endDate; | ||||||
| 
 | 
 | ||||||
| @ -22,15 +25,6 @@ public class TaskRepeatWeekInfo { | |||||||
|     public void setWeekDayInfos(List<TaskRepeatWeekDayInfo> weekDayInfos) { |     public void setWeekDayInfos(List<TaskRepeatWeekDayInfo> weekDayInfos) { | ||||||
|         this.weekDayInfos = weekDayInfos; |         this.weekDayInfos = weekDayInfos; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public DeadlineStrategy getDeadlineStrategy() { |  | ||||||
|         return deadlineStrategy; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setDeadlineStrategy(DeadlineStrategy deadlineStrategy) { |  | ||||||
|         this.deadlineStrategy = deadlineStrategy; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public LocalDate getEndDate() { |     public LocalDate getEndDate() { | ||||||
|         return endDate; |         return endDate; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,14 +1,15 @@ | |||||||
| package core.entities.timemanager; | package core.entities.timemanager; | ||||||
| 
 | 
 | ||||||
| import core.api.models.timemanager.tasks.TaskFieldInfo; | 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 javax.persistence.*; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| import java.util.ArrayList; | import java.time.temporal.ChronoUnit; | ||||||
| import java.util.List; | import java.util.*; | ||||||
| import java.util.Objects; |  | ||||||
| import java.util.Set; |  | ||||||
| 
 | 
 | ||||||
| @Entity | @Entity | ||||||
| @Table(name = "tasks") | @Table(name = "tasks") | ||||||
| @ -33,6 +34,13 @@ public class Task { | |||||||
|     @OneToOne(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true) |     @OneToOne(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true) | ||||||
|     private TaskSerieItem taskSerieItem; |     private TaskSerieItem taskSerieItem; | ||||||
| 
 | 
 | ||||||
|  |     @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) | ||||||
|  |     private Set<Task> subtasks; | ||||||
|  | 
 | ||||||
|  |     @ManyToOne | ||||||
|  |     @JoinColumn(name = "parent") | ||||||
|  |     private Task parent; | ||||||
|  | 
 | ||||||
|     public Task() { |     public Task() { | ||||||
|         this.basicTaskSchedules = new ArrayList<>(); |         this.basicTaskSchedules = new ArrayList<>(); | ||||||
|     } |     } | ||||||
| @ -47,14 +55,52 @@ public class Task { | |||||||
|         this.finishable = taskFieldInfo.isFinishable(); |         this.finishable = taskFieldInfo.isFinishable(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static Task cloneTask(Task task) { |     public Tripel<Task, Collection<Task>, Collection<AbstractSchedule>> cloneTask() { | ||||||
|  |        Collection<Task> clonedTasks = new ArrayList<>(); | ||||||
|  |        Collection<AbstractSchedule> clonedSchedules = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|        Task clonedTask = new Task(); |        Task clonedTask = new Task(); | ||||||
|        clonedTask.setTaskgroup(task.getTaskgroup()); |        clonedTasks.add(clonedTask); | ||||||
|        clonedTask.setTaskName(task.taskName); | 
 | ||||||
|        clonedTask.setEta(task.eta); |        clonedTask.setTaskgroup(this.getTaskgroup()); | ||||||
|        clonedTask.setFinished(false); |        clonedTask.setTaskName(this.taskName); | ||||||
|        clonedTask.setFinishable(task.finishable); |        clonedTask.setEta(this.eta); | ||||||
|        return clonedTask; |        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); | ||||||
|  |            clonedTask.getBasicTaskSchedules().clear(); | ||||||
|  |            clonedTask.getBasicTaskSchedules().add(clonedSchedule); | ||||||
|  |        } | ||||||
|  | 
 | ||||||
|  |        Set<Task> clonedSubtasks = new HashSet<>(); | ||||||
|  |        for(Task task : this.subtasks) { | ||||||
|  |            Tripel<Task, Collection<Task>, Collection<AbstractSchedule>> 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 offset) { | ||||||
|  |        this.setStartDate(this.getStartDate().plusDays(offset)); | ||||||
|  |        this.setDeadline(this.getDeadline().plusDays(offset)); | ||||||
|  | 
 | ||||||
|  |        for(AbstractSchedule abstractSchedule : this.basicTaskSchedules) { | ||||||
|  |            abstractSchedule.shiftSchedule(offset); | ||||||
|  |        } | ||||||
|  | 
 | ||||||
|  |        for(Task subtask: this.subtasks) { | ||||||
|  |            subtask.shiftTask(offset); | ||||||
|  |        } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -149,6 +195,27 @@ public class Task { | |||||||
|         this.basicTaskSchedules = basicTaskSchedules; |         this.basicTaskSchedules = basicTaskSchedules; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void addSubtask(Task subtask) { | ||||||
|  |         subtask.setParent(this); | ||||||
|  |         this.subtasks.add(subtask); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Set<Task> getSubtasks() { | ||||||
|  |         return subtasks; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setSubtasks(Set<Task> subtasks) { | ||||||
|  |         this.subtasks = subtasks; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Task getParent() { | ||||||
|  |         return parent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setParent(Task parent) { | ||||||
|  |         this.parent = parent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean equals(Object o) { |     public boolean equals(Object o) { | ||||||
|         if (this == o) return true; |         if (this == o) return true; | ||||||
|  | |||||||
| @ -39,4 +39,9 @@ public class TaskSerie { | |||||||
|         this.tasks.add(taskSerieItem); |         this.tasks.add(taskSerieItem); | ||||||
|         return taskSerieItem; |         return taskSerieItem; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public void addItem(TaskSerieItem taskSerieItem) { | ||||||
|  |         this.tasks.add(taskSerieItem); | ||||||
|  |         taskSerieItem.setTaskSerie(this); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -31,6 +31,12 @@ public class TaskSerieItem { | |||||||
|     public TaskSerieItem() { |     public TaskSerieItem() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public TaskSerieItem(Task task, int itemIndex) { | ||||||
|  |         this.taskSerie = null; | ||||||
|  |         this.seriesIndex = itemIndex; | ||||||
|  |         this.task = task; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public long getItemID() { |     public long getItemID() { | ||||||
|         return itemID; |         return itemID; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,10 +2,12 @@ package core.repositories.timemanager; | |||||||
| 
 | 
 | ||||||
| import core.entities.timemanager.AbstractSchedule; | import core.entities.timemanager.AbstractSchedule; | ||||||
| import core.entities.timemanager.Taskgroup; | import core.entities.timemanager.Taskgroup; | ||||||
|  | import org.springframework.data.jpa.repository.Modifying; | ||||||
| import org.springframework.data.jpa.repository.Query; | import org.springframework.data.jpa.repository.Query; | ||||||
| import org.springframework.data.repository.CrudRepository; | import org.springframework.data.repository.CrudRepository; | ||||||
| import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||||
| 
 | 
 | ||||||
|  | import javax.transaction.Transactional; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -20,4 +22,9 @@ public interface ScheduleRepository extends CrudRepository<AbstractSchedule, Lon | |||||||
|     @Query(value = "SELECT s FROM AbstractSchedule s WHERE s.task.taskgroup.user.username = ?1 AND s.startTime is NOT NULL and s.stopTime is NULL") |     @Query(value = "SELECT s FROM AbstractSchedule s WHERE s.task.taskgroup.user.username = ?1 AND s.startTime is NOT NULL and s.stopTime is NULL") | ||||||
|     Optional<AbstractSchedule> getActiveScheduleOfUser(String username); |     Optional<AbstractSchedule> getActiveScheduleOfUser(String username); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     @Modifying | ||||||
|  |     @Transactional | ||||||
|  |     @Query(value = "DELETE FROM AbstractSchedule a WHERE a.task IN (SELECT t FROM Task t WHERE t.taskgroup = ?1)") | ||||||
|  |     void deleteByTaskgroup(Taskgroup taskgroup); | ||||||
| } | } | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ public interface TaskRepository extends CrudRepository<Task, Long> { | |||||||
| 
 | 
 | ||||||
|     @Transactional |     @Transactional | ||||||
|     @Modifying |     @Modifying | ||||||
|  |     @Query(value = "DELETE FROM Task t WHERE t.taskID = ?1") | ||||||
|     void deleteByTaskID(long taskID); |     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") |     @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,17 @@ public interface TaskRepository extends CrudRepository<Task, Long> { | |||||||
| 
 | 
 | ||||||
|     @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") |     @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<Task> findAllActive(String username, LocalDate now); |     List<Task> findAllActive(String username, LocalDate now); | ||||||
|  | 
 | ||||||
|  |     @Query(value = "SELECT t FROM Task t WHERE t.taskgroup = ?1") | ||||||
|  |     List<Task> findAllByTaskgroup(Taskgroup taskgroup); | ||||||
|  | 
 | ||||||
|  |     @Modifying | ||||||
|  |     @Transactional | ||||||
|  |     @Query(value = "UPDATE Task t SET t.parent = null WHERE t.taskgroup = ?1") | ||||||
|  |     void deleteTaskHierarchyWhereTaskgroup(Taskgroup taskgroup); | ||||||
|  | 
 | ||||||
|  |     @Modifying | ||||||
|  |     @Transactional | ||||||
|  |     @Query(value = "DELETE Task t WHERE t.parent = ?1") | ||||||
|  |     void deleteTasksByParent(Task parentTask); | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,10 +7,7 @@ import core.api.models.timemanager.taskSchedule.scheduleInfos.AdvancedScheduleIn | |||||||
| import core.api.models.timemanager.taskSchedule.scheduleInfos.BasicScheduleFieldInfo; | import core.api.models.timemanager.taskSchedule.scheduleInfos.BasicScheduleFieldInfo; | ||||||
| import core.api.models.timemanager.taskSchedule.ForgottenScheduleInfo; | import core.api.models.timemanager.taskSchedule.ForgottenScheduleInfo; | ||||||
| import core.api.models.timemanager.taskSchedule.scheduleInfos.ScheduleInfo; | import core.api.models.timemanager.taskSchedule.scheduleInfos.ScheduleInfo; | ||||||
| import core.entities.timemanager.AbstractSchedule; | import core.entities.timemanager.*; | ||||||
| import core.entities.timemanager.AdvancedTaskSchedule; |  | ||||||
| import core.entities.timemanager.BasicTaskSchedule; |  | ||||||
| import core.entities.timemanager.Task; |  | ||||||
| import core.repositories.UserRepository; | import core.repositories.UserRepository; | ||||||
| import core.repositories.timemanager.AdvancedScheduleRepository; | import core.repositories.timemanager.AdvancedScheduleRepository; | ||||||
| import core.repositories.timemanager.ScheduleRepository; | import core.repositories.timemanager.ScheduleRepository; | ||||||
| @ -259,4 +256,8 @@ public class TaskScheduleService { | |||||||
|         schedule.setStopTime(schedule.getStartTime().plusMinutes(manualScheduleStopInfo.getDuration())); |         schedule.setStopTime(schedule.getStartTime().plusMinutes(manualScheduleStopInfo.getDuration())); | ||||||
|         scheduleRepository.save(schedule); |         scheduleRepository.save(schedule); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public void deleteSchedulesByTaskgroup(Taskgroup taskgroup) { | ||||||
|  |         scheduleRepository.deleteByTaskgroup(taskgroup); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,14 +11,13 @@ import core.repositories.timemanager.TaskSerieItemRepository; | |||||||
| import core.repositories.timemanager.TaskSeriesRepository; | import core.repositories.timemanager.TaskSeriesRepository; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
|  | import util.Tupel; | ||||||
| 
 | 
 | ||||||
|  | import java.time.DayOfWeek; | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| import java.time.temporal.ChronoUnit; | import java.time.temporal.ChronoUnit; | ||||||
| import java.util.ArrayList; | import java.util.*; | ||||||
| import java.util.Comparator; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Optional; |  | ||||||
| 
 | 
 | ||||||
| @Service | @Service | ||||||
| public class TaskSeriesService { | public class TaskSeriesService { | ||||||
| @ -30,106 +29,103 @@ public class TaskSeriesService { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public ServiceExitCode createTaskSeries(TaskRepeatWeekInfo taskRepeatInfo) { |     public ServiceExitCode createTaskSeries(TaskRepeatWeekInfo taskRepeatInfo) { | ||||||
|         List<Task> createdTasks = new ArrayList<>(); |         HashMap<Task, Integer> offsetMap = calcWeeklyOffsetMap(taskRepeatInfo); | ||||||
|  | 
 | ||||||
|         TaskSerie taskSerie = new TaskSerie(); |         TaskSerie taskSerie = new TaskSerie(); | ||||||
|         List<AbstractSchedule> abstractSchedules = new ArrayList<>(); |         List<Task> clonedTasks = new ArrayList<>(); | ||||||
|  |         List<AbstractSchedule> clonedSchedules = new ArrayList<>(); | ||||||
|  |         int weekDayIndex = 0; | ||||||
|  |         for(Map.Entry<Task, Integer> repeatingTaskInfo: offsetMap.entrySet()) { | ||||||
|  |             Task rootTask = repeatingTaskInfo.getKey(); | ||||||
|  |             addRootSubTasksToTaskSerie(taskSerie, rootTask, weekDayIndex); | ||||||
| 
 | 
 | ||||||
|         for(TaskRepeatWeekDayInfo taskRepeatDayInfo : taskRepeatInfo.getWeekDayInfos()) { |             int itemIndex = weekDayIndex +1; | ||||||
|             Optional<Task> task = taskRepository.findById(taskRepeatDayInfo.getTaskID()); |             Tupel<Collection<Task>, Collection<AbstractSchedule>> repeatingResult = repeatTask(rootTask, taskRepeatInfo.getEndDate(), offsetMap, taskSerie, itemIndex); | ||||||
|             if(task.isEmpty()) return ServiceExitCode.MISSING_ENTITY; |  | ||||||
| 
 | 
 | ||||||
|             TaskSerieItem rootItem = taskSerie.addTask(task.get()); |             clonedTasks.addAll(repeatingResult.getValue00()); | ||||||
|             task.get().setTaskSerieItem(rootItem); |             clonedSchedules.addAll(repeatingResult.getValue01()); | ||||||
| 
 | 
 | ||||||
|             LocalDate currentTaskDate = task.get().getStartDate().plusDays(taskRepeatDayInfo.getOffset()); |             weekDayIndex++; | ||||||
|             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<taskSerie.getTasks().size(); i++) { |  | ||||||
|             taskSerie.getTasks().get(i).setSeriesIndex(i+1); |  | ||||||
|             if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_EQUAL_START) { |  | ||||||
|                 taskSerie.getTasks().get(i).getTask().setDeadline(taskSerie.getTasks().get(i).getTask().getStartDate()); |  | ||||||
|             } else { |  | ||||||
|                 if(i + 1 == taskSerie.getTasks().size()) { |  | ||||||
|                     int firstWeekDayIndex = i % taskRepeatInfo.getWeekDayInfos().size(); |  | ||||||
|                     LocalDate reference_start = taskSerie.getTasks().get(firstWeekDayIndex).getTask().getStartDate(); |  | ||||||
|                     LocalDate reference_deadline = taskSerie.getTasks().get(firstWeekDayIndex).getTask().getDeadline(); |  | ||||||
| 
 |  | ||||||
|                     Duration duration = Duration.between(reference_start, reference_deadline); |  | ||||||
|                     long days = duration.toDays(); |  | ||||||
|                     taskSerie.getTasks().get(i).getTask().setDeadline(taskSerie.getTasks().get(i).getTask().getStartDate().plusDays(days)); |  | ||||||
|                 } else { |  | ||||||
|                     taskSerie.getTasks().get(i).getTask().setDeadline(taskSerie.getTasks().get(i+1).getTask().getStartDate().minusDays(1)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         taskSeriesRepository.save(taskSerie); |         taskSeriesRepository.save(taskSerie); | ||||||
|         taskRepository.saveAll(createdTasks); |         taskRepository.saveAll(clonedTasks); | ||||||
|         taskSerieItemRepository.saveAll(taskSerie.getTasks()); |         taskSerieItemRepository.saveAll(taskSerie.getTasks()); | ||||||
|         scheduleRepository.saveAll(abstractSchedules); |         scheduleRepository.saveAll(clonedSchedules); | ||||||
| 
 | 
 | ||||||
|         return ServiceExitCode.OK; |         return ServiceExitCode.OK; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Tupel<Collection<Task>, Collection<AbstractSchedule>> repeatTask(Task rootTask, LocalDate endingDate, HashMap<Task, Integer> offsetMap,TaskSerie taskSerie, int itemIndex) { | ||||||
|  |         List<Task> clonedTasks = new ArrayList<>(); | ||||||
|  |         List<AbstractSchedule> clonedSchedules = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |         LocalDate currentDate = rootTask.getStartDate().plusDays(offsetMap.get(rootTask)); | ||||||
|  |         while(currentDate.isBefore(endingDate)) { | ||||||
|  |             var cloneResult = rootTask.cloneTask(); | ||||||
|  |             Task clonedRootTask = cloneResult.getValue00(); | ||||||
|  |             clonedTasks.addAll(cloneResult.getValue01()); | ||||||
|  |             clonedSchedules.addAll(cloneResult.getValue02()); | ||||||
|  | 
 | ||||||
|  |             for(Task clonedTask : cloneResult.getValue01()) { | ||||||
|  |                 TaskSerieItem item = new TaskSerieItem(clonedTask, itemIndex); | ||||||
|  |                 taskSerie.addItem(item); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             clonedRootTask.shiftTask(offsetMap.get(rootTask)); | ||||||
|  | 
 | ||||||
|  |             currentDate = currentDate.plusDays(offsetMap.get(rootTask)); | ||||||
|  |             itemIndex += offsetMap.size(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new Tupel<>(clonedTasks, clonedSchedules); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private HashMap<Task, Integer> calcWeeklyOffsetMap(TaskRepeatWeekInfo weekInfo) throws NoSuchElementException { | ||||||
|  |         HashMap<Task,Integer> offsetMap = new HashMap<>(); | ||||||
|  |         weekInfo.getWeekDayInfos().sort(Comparator.comparing(TaskRepeatWeekDayInfo::getDayOfWeek)); | ||||||
|  |         for(int i=0; i<weekInfo.getWeekDayInfos().size(); i++) { | ||||||
|  |             Optional<Task> requestedTask = taskRepository.findById(weekInfo.getWeekDayInfos().get(i).getTaskID()); | ||||||
|  |             if(requestedTask.isEmpty()) { | ||||||
|  |                 throw new NoSuchElementException(); | ||||||
|  |             } else { | ||||||
|  |                 int offset = weekInfo.getWeekDayInfos().get(i).getOffset()-1; | ||||||
|  |                 offsetMap.put(requestedTask.get(), offset); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         return offsetMap; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public ServiceExitCode createTaskSeries(Task rootTask, TaskRepeatDayInfo taskRepeatInfo) { |     public ServiceExitCode createTaskSeries(Task rootTask, TaskRepeatDayInfo taskRepeatInfo) { | ||||||
|         if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.FIX_DEADLINE) { |  | ||||||
|             return ServiceExitCode.INVALID_PARAMETER; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         List<Task> taskList = new ArrayList<>(); |  | ||||||
|         List<AbstractSchedule> abstractSchedules = new ArrayList<>(); |  | ||||||
|         TaskSerie taskSerie = new TaskSerie(); |         TaskSerie taskSerie = new TaskSerie(); | ||||||
|         TaskSerieItem rootItem = taskSerie.addTask(rootTask); |         addRootSubTasksToTaskSerie(taskSerie, rootTask, 0); | ||||||
|         rootTask.setTaskSerieItem(rootItem); |  | ||||||
| 
 | 
 | ||||||
|         LocalDate currentTaskDate = rootTask.getStartDate().plusDays(taskRepeatInfo.getOffset()); |         HashMap<Task, Integer> offsetMap = new HashMap<>(); | ||||||
|         while(currentTaskDate.isBefore(taskRepeatInfo.getEndingDate())) { |         offsetMap.put(rootTask, taskRepeatInfo.getOffset()); | ||||||
|             Task task = Task.cloneTask(rootTask); | 
 | ||||||
|             task.setStartDate(currentTaskDate); |         var repeatingResult = repeatTask(rootTask, taskRepeatInfo.getEndingDate(), offsetMap, taskSerie, 0); | ||||||
|             if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_EQUAL_START) { |         List<Task> clonedTasks = new ArrayList<>(repeatingResult.getValue00()); | ||||||
|                 task.setDeadline(currentTaskDate); |         List<AbstractSchedule> clonedSchedules = new ArrayList<>(repeatingResult.getValue01()); | ||||||
|             } 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); |  | ||||||
| 
 | 
 | ||||||
|             abstractSchedules.addAll(cloneSchedules(rootTask, task)); |  | ||||||
|             currentTaskDate = currentTaskDate.plusDays(taskRepeatInfo.getOffset()); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         taskSeriesRepository.save(taskSerie); |         taskSeriesRepository.save(taskSerie); | ||||||
|         taskRepository.saveAll(taskList); |         taskRepository.saveAll(clonedTasks); | ||||||
|         taskSerieItemRepository.saveAll(taskSerie.getTasks()); |         taskSerieItemRepository.saveAll(taskSerie.getTasks()); | ||||||
|         scheduleRepository.saveAll(abstractSchedules); |         scheduleRepository.saveAll(clonedSchedules); | ||||||
| 
 | 
 | ||||||
|         return ServiceExitCode.OK; |         return ServiceExitCode.OK; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public List<AbstractSchedule> cloneSchedules(Task previousTask, Task nextTask) { |     private void addRootSubTasksToTaskSerie(TaskSerie taskSerie, Task rootTask, int index) { | ||||||
|         long numberDays = ChronoUnit.DAYS.between(previousTask.getStartDate(), nextTask.getStartDate()); |         Queue<Task> taskQueue = new LinkedList<>(Collections.singletonList(rootTask)); | ||||||
|  |         while(!taskQueue.isEmpty()) { | ||||||
|  |             Task currentTask = taskQueue.poll(); | ||||||
| 
 | 
 | ||||||
|         List<AbstractSchedule> clonedSchedules = new ArrayList<>(); |             TaskSerieItem taskSerieItem = new TaskSerieItem(currentTask, index); | ||||||
|         for(AbstractSchedule abstractSchedule : previousTask.getBasicTaskSchedules()) { |             taskSerie.addItem(taskSerieItem); | ||||||
|             AbstractSchedule clonedSchedule = abstractSchedule.cloneSchedule(); |  | ||||||
|             clonedSchedule.shiftSchedule(numberDays); |  | ||||||
| 
 |  | ||||||
|             clonedSchedules.add(clonedSchedule); |  | ||||||
|         } |         } | ||||||
|         return clonedSchedules; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void deleteTaskSeriesItem(Task task) { |     public void deleteTaskSeriesItem(Task task) { | ||||||
| @ -144,22 +140,20 @@ public class TaskSeriesService { | |||||||
|             } |             } | ||||||
|             taskSerie.getTasks().clear(); |             taskSerie.getTasks().clear(); | ||||||
|             taskSeriesRepository.delete(taskSerie); |             taskSeriesRepository.delete(taskSerie); | ||||||
|         } else { |         } else if(task.getParent() == null){ | ||||||
|             repearIndexing(taskSerie); |             repearIndexing(taskSerie, item.getSeriesIndex()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void repearIndexing(TaskSerie taskSerie) { |     private void repearIndexing(TaskSerie taskSerie, int deletedIndex) { | ||||||
|         taskSerie.getTasks().sort(Comparator.comparingInt(TaskSerieItem::getSeriesIndex)); |         taskSerie.getTasks().sort(Comparator.comparingInt(TaskSerieItem::getSeriesIndex)); | ||||||
|         List<TaskSerieItem> updatedItems = new ArrayList<>(); |         List<TaskSerieItem> updatedItems = new ArrayList<>(); | ||||||
|         int currentIndex = 1; | 
 | ||||||
|         for(TaskSerieItem taskSerieItem : taskSerie.getTasks()) { |         for(TaskSerieItem taskSerieItem : taskSerie.getTasks()) { | ||||||
|             if(taskSerieItem.getSeriesIndex() != currentIndex) { |             if(taskSerieItem.getSeriesIndex() > deletedIndex) { | ||||||
|                 taskSerieItem.setSeriesIndex(currentIndex); |                 taskSerieItem.setSeriesIndex(taskSerieItem.getSeriesIndex() -1); | ||||||
|                 updatedItems.add(taskSerieItem); |                 updatedItems.add(taskSerieItem); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             currentIndex++; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         taskSerieItemRepository.saveAll(updatedItems); |         taskSerieItemRepository.saveAll(updatedItems); | ||||||
|  | |||||||
| @ -49,6 +49,21 @@ public class TaskService { | |||||||
|         return new ServiceResult<>(task); |         return new ServiceResult<>(task); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public ServiceResult<Task> createSubTask(Task parentTask, TaskFieldInfo taskFieldInfo) { | ||||||
|  |         if(taskFieldInfo.getStartDate().isBefore(parentTask.getStartDate()) || | ||||||
|  |                 taskFieldInfo.getDeadline().isAfter(parentTask.getDeadline()) || | ||||||
|  |                 taskFieldInfo.getDeadline().isBefore(taskFieldInfo.getStartDate())) { | ||||||
|  |             return new ServiceResult<>(ServiceExitCode.INVALID_PARAMETER); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Task task = new Task(parentTask.getTaskgroup(), taskFieldInfo); | ||||||
|  |         parentTask.getTaskgroup().getTasks().add(task); | ||||||
|  |         parentTask.addSubtask(task); | ||||||
|  | 
 | ||||||
|  |         taskRepository.save(task); | ||||||
|  |         return new ServiceResult<>(task); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private boolean existTaskByName(Collection<Task> tasks, String name) { |     private boolean existTaskByName(Collection<Task> tasks, String name) { | ||||||
|         for(Task task : tasks) { |         for(Task task : tasks) { | ||||||
|             if(task.getTaskName().equals(name)) { |             if(task.getTaskName().equals(name)) { | ||||||
| @ -94,11 +109,12 @@ public class TaskService { | |||||||
|             taskSeriesService.deleteTaskSeriesItem(task); |             taskSeriesService.deleteTaskSeriesItem(task); | ||||||
|         } |         } | ||||||
|         taskRepository.delete(task); |         taskRepository.delete(task); | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void clearTasks(Taskgroup taskgroup) { |     public void clearTasks(Taskgroup taskgroup) { | ||||||
|         taskSeriesService.deleteTaskSerieByTaskgroup(taskgroup); |         taskSeriesService.deleteTaskSerieByTaskgroup(taskgroup); | ||||||
|  |         taskScheduleService.deleteSchedulesByTaskgroup(taskgroup); | ||||||
|  |         taskRepository.deleteTaskHierarchyWhereTaskgroup(taskgroup); | ||||||
|         taskRepository.deleteAllByTaskgroup(taskgroup); |         taskRepository.deleteAllByTaskgroup(taskgroup); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -143,4 +159,9 @@ public class TaskService { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public ServiceExitCode clearSubTasks(Task parentTask) { | ||||||
|  |         taskRepository.deleteTasksByParent(parentTask); | ||||||
|  |         return ServiceExitCode.OK; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,10 +21,12 @@ public class TaskgroupService { | |||||||
| 
 | 
 | ||||||
|     private final TaskgroupRepository taskgroupRepository; |     private final TaskgroupRepository taskgroupRepository; | ||||||
|     private final UserRepository userRepository; |     private final UserRepository userRepository; | ||||||
|  |     private final TaskService taskService; | ||||||
|     public TaskgroupService(@Autowired  TaskgroupRepository taskgroupRepository, |     public TaskgroupService(@Autowired  TaskgroupRepository taskgroupRepository, | ||||||
|                             @Autowired UserRepository userRepository) { |                             @Autowired UserRepository userRepository, @Autowired TaskService taskService) { | ||||||
|         this.taskgroupRepository = taskgroupRepository; |         this.taskgroupRepository = taskgroupRepository; | ||||||
|         this.userRepository = userRepository; |         this.userRepository = userRepository; | ||||||
|  |         this.taskService = taskService; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public PermissionResult<Taskgroup> getTaskgroupByIDAndUsername(long taskgroupID, String username) { |     public PermissionResult<Taskgroup> getTaskgroupByIDAndUsername(long taskgroupID, String username) { | ||||||
| @ -97,6 +99,7 @@ public class TaskgroupService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void deleteTaskgroup(Taskgroup taskgroup) { |     public void deleteTaskgroup(Taskgroup taskgroup) { | ||||||
|  |         taskService.clearTasks(taskgroup); | ||||||
|         taskgroupRepository.delete(taskgroup); |         taskgroupRepository.delete(taskgroup); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								backend/src/main/java/util/Tripel.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								backend/src/main/java/util/Tripel.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | package util; | ||||||
|  | 
 | ||||||
|  | public class Tripel<A,B,C> extends Tupel<A, B> { | ||||||
|  | 
 | ||||||
|  |     private final C value02; | ||||||
|  | 
 | ||||||
|  |     public Tripel(A value00, B value01, C value02) { | ||||||
|  |         super(value00, value01); | ||||||
|  |         this.value02 = value02; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public C getValue02() { | ||||||
|  |         return value02; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								backend/src/main/java/util/Tupel.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								backend/src/main/java/util/Tupel.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | package util; | ||||||
|  | 
 | ||||||
|  | public class Tupel <A, B>{ | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -401,6 +401,190 @@ export class TaskService { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @param taskID internal id of task | ||||||
|  |      * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | ||||||
|  |      * @param reportProgress flag to report request and response progress. | ||||||
|  |      */ | ||||||
|  |     public tasksTaskIDSubtasksDelete(taskID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<SimpleStatusResponse>; | ||||||
|  |     public tasksTaskIDSubtasksDelete(taskID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<SimpleStatusResponse>>; | ||||||
|  |     public tasksTaskIDSubtasksDelete(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<SimpleStatusResponse>>; | ||||||
|  |     public tasksTaskIDSubtasksDelete(taskID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { | ||||||
|  |         if (taskID === null || taskID === undefined) { | ||||||
|  |             throw new Error('Required parameter taskID was null or undefined when calling tasksTaskIDSubtasksDelete.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHeaders = this.defaultHeaders; | ||||||
|  | 
 | ||||||
|  |         let localVarCredential: string | undefined; | ||||||
|  |         // authentication (API_TOKEN) required
 | ||||||
|  |         localVarCredential = this.configuration.lookupCredential('API_TOKEN'); | ||||||
|  |         if (localVarCredential) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | ||||||
|  |         if (localVarHttpHeaderAcceptSelected === undefined) { | ||||||
|  |             // to determine the Accept header
 | ||||||
|  |             const httpHeaderAccepts: string[] = [ | ||||||
|  |                 'application/json' | ||||||
|  |             ]; | ||||||
|  |             localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | ||||||
|  |         } | ||||||
|  |         if (localVarHttpHeaderAcceptSelected !== undefined) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpContext: HttpContext | undefined = options && options.context; | ||||||
|  |         if (localVarHttpContext === undefined) { | ||||||
|  |             localVarHttpContext = new HttpContext(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         let responseType_: 'text' | 'json' = 'json'; | ||||||
|  |         if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { | ||||||
|  |             responseType_ = 'text'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this.httpClient.delete<SimpleStatusResponse>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}/subtasks`, | ||||||
|  |             { | ||||||
|  |                 context: localVarHttpContext, | ||||||
|  |                 responseType: <any>responseType_, | ||||||
|  |                 withCredentials: this.configuration.withCredentials, | ||||||
|  |                 headers: localVarHeaders, | ||||||
|  |                 observe: observe, | ||||||
|  |                 reportProgress: reportProgress | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param taskID internal id of task | ||||||
|  |      * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | ||||||
|  |      * @param reportProgress flag to report request and response progress. | ||||||
|  |      */ | ||||||
|  |     public tasksTaskIDSubtasksGet(taskID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskEntityInfo>>; | ||||||
|  |     public tasksTaskIDSubtasksGet(taskID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskEntityInfo>>>; | ||||||
|  |     public tasksTaskIDSubtasksGet(taskID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskEntityInfo>>>; | ||||||
|  |     public tasksTaskIDSubtasksGet(taskID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { | ||||||
|  |         if (taskID === null || taskID === undefined) { | ||||||
|  |             throw new Error('Required parameter taskID was null or undefined when calling tasksTaskIDSubtasksGet.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHeaders = this.defaultHeaders; | ||||||
|  | 
 | ||||||
|  |         let localVarCredential: string | undefined; | ||||||
|  |         // authentication (API_TOKEN) required
 | ||||||
|  |         localVarCredential = this.configuration.lookupCredential('API_TOKEN'); | ||||||
|  |         if (localVarCredential) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | ||||||
|  |         if (localVarHttpHeaderAcceptSelected === undefined) { | ||||||
|  |             // to determine the Accept header
 | ||||||
|  |             const httpHeaderAccepts: string[] = [ | ||||||
|  |                 'application/json' | ||||||
|  |             ]; | ||||||
|  |             localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | ||||||
|  |         } | ||||||
|  |         if (localVarHttpHeaderAcceptSelected !== undefined) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpContext: HttpContext | undefined = options && options.context; | ||||||
|  |         if (localVarHttpContext === undefined) { | ||||||
|  |             localVarHttpContext = new HttpContext(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         let responseType_: 'text' | 'json' = 'json'; | ||||||
|  |         if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { | ||||||
|  |             responseType_ = 'text'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this.httpClient.get<Array<TaskEntityInfo>>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}/subtasks`, | ||||||
|  |             { | ||||||
|  |                 context: localVarHttpContext, | ||||||
|  |                 responseType: <any>responseType_, | ||||||
|  |                 withCredentials: this.configuration.withCredentials, | ||||||
|  |                 headers: localVarHeaders, | ||||||
|  |                 observe: observe, | ||||||
|  |                 reportProgress: reportProgress | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates Subtask | ||||||
|  |      * Create Subtask | ||||||
|  |      * @param taskID internal id of task | ||||||
|  |      * @param taskFieldInfo  | ||||||
|  |      * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | ||||||
|  |      * @param reportProgress flag to report request and response progress. | ||||||
|  |      */ | ||||||
|  |     public tasksTaskIDSubtasksPut(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<TaskEntityInfo>; | ||||||
|  |     public tasksTaskIDSubtasksPut(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<TaskEntityInfo>>; | ||||||
|  |     public tasksTaskIDSubtasksPut(taskID: number, taskFieldInfo?: TaskFieldInfo, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<TaskEntityInfo>>; | ||||||
|  |     public tasksTaskIDSubtasksPut(taskID: number, taskFieldInfo?: TaskFieldInfo, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { | ||||||
|  |         if (taskID === null || taskID === undefined) { | ||||||
|  |             throw new Error('Required parameter taskID was null or undefined when calling tasksTaskIDSubtasksPut.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHeaders = this.defaultHeaders; | ||||||
|  | 
 | ||||||
|  |         let localVarCredential: string | undefined; | ||||||
|  |         // authentication (API_TOKEN) required
 | ||||||
|  |         localVarCredential = this.configuration.lookupCredential('API_TOKEN'); | ||||||
|  |         if (localVarCredential) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | ||||||
|  |         if (localVarHttpHeaderAcceptSelected === undefined) { | ||||||
|  |             // to determine the Accept header
 | ||||||
|  |             const httpHeaderAccepts: string[] = [ | ||||||
|  |                 'application/json' | ||||||
|  |             ]; | ||||||
|  |             localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | ||||||
|  |         } | ||||||
|  |         if (localVarHttpHeaderAcceptSelected !== undefined) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let localVarHttpContext: HttpContext | undefined = options && options.context; | ||||||
|  |         if (localVarHttpContext === undefined) { | ||||||
|  |             localVarHttpContext = new HttpContext(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         // to determine the Content-Type header
 | ||||||
|  |         const consumes: string[] = [ | ||||||
|  |             'application/json' | ||||||
|  |         ]; | ||||||
|  |         const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); | ||||||
|  |         if (httpContentTypeSelected !== undefined) { | ||||||
|  |             localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let responseType_: 'text' | 'json' = 'json'; | ||||||
|  |         if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { | ||||||
|  |             responseType_ = 'text'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this.httpClient.put<TaskEntityInfo>(`${this.configuration.basePath}/tasks/${encodeURIComponent(String(taskID))}/subtasks`, | ||||||
|  |             taskFieldInfo, | ||||||
|  |             { | ||||||
|  |                 context: localVarHttpContext, | ||||||
|  |                 responseType: <any>responseType_, | ||||||
|  |                 withCredentials: this.configuration.withCredentials, | ||||||
|  |                 headers: localVarHeaders, | ||||||
|  |                 observe: observe, | ||||||
|  |                 reportProgress: reportProgress | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * creates a new task |      * creates a new task | ||||||
|      * creates tasks |      * creates tasks | ||||||
|  | |||||||
| @ -60,5 +60,13 @@ export interface TaskEntityInfo { | |||||||
|      * determines whether the task is associated with a taskserie |      * determines whether the task is associated with a taskserie | ||||||
|      */ |      */ | ||||||
|     hasTaskSerie: boolean; |     hasTaskSerie: boolean; | ||||||
|  |     /** | ||||||
|  |      * determines whether a task has subtasks | ||||||
|  |      */ | ||||||
|  |     hasSubtasks: boolean; | ||||||
|  |     /** | ||||||
|  |      * determines whether a task is a top task or Not | ||||||
|  |      */ | ||||||
|  |     hasParent: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -42,5 +42,9 @@ export interface TaskOverviewInfo { | |||||||
|      * determines whether the task can be finished |      * determines whether the task can be finished | ||||||
|      */ |      */ | ||||||
|     finishable: boolean; |     finishable: boolean; | ||||||
|  |     /** | ||||||
|  |      * determines whether the task has subtasks | ||||||
|  |      */ | ||||||
|  |     hasSubtasks: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,18 +16,9 @@ export interface TaskRepeatDayInfo { | |||||||
|      * number repeating days |      * number repeating days | ||||||
|      */ |      */ | ||||||
|     offset: number; |     offset: number; | ||||||
|     deadlineStrategy: TaskRepeatDayInfo.DeadlineStrategyEnum; |  | ||||||
|     /** |     /** | ||||||
|      * Date until the tasks repeat |      * Date until the tasks repeat | ||||||
|      */ |      */ | ||||||
|     endingDate: string; |     endingDate: string; | ||||||
| } | } | ||||||
| export namespace TaskRepeatDayInfo { |  | ||||||
|     export type DeadlineStrategyEnum = 'DEADLINE_EQUAL_START' | 'DEADLINE_FIT_START'; |  | ||||||
|     export const DeadlineStrategyEnum = { |  | ||||||
|         EqualStart: 'DEADLINE_EQUAL_START' as DeadlineStrategyEnum, |  | ||||||
|         FitStart: 'DEADLINE_FIT_START' as DeadlineStrategyEnum |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,5 +20,22 @@ export interface TaskRepeatWeekDayInfo { | |||||||
|      * internal identifier of task |      * internal identifier of task | ||||||
|      */ |      */ | ||||||
|     taskID: number; |     taskID: number; | ||||||
|  |     /** | ||||||
|  |      * day of week | ||||||
|  |      */ | ||||||
|  |     dayOfWeek: TaskRepeatWeekDayInfo.DayOfWeekEnum; | ||||||
|  | } | ||||||
|  | export namespace TaskRepeatWeekDayInfo { | ||||||
|  |     export type DayOfWeekEnum = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY'; | ||||||
|  |     export const DayOfWeekEnum = { | ||||||
|  |         Monday: 'MONDAY' as DayOfWeekEnum, | ||||||
|  |         Tuesday: 'TUESDAY' as DayOfWeekEnum, | ||||||
|  |         Wednesday: 'WEDNESDAY' as DayOfWeekEnum, | ||||||
|  |         Thursday: 'THURSDAY' as DayOfWeekEnum, | ||||||
|  |         Friday: 'FRIDAY' as DayOfWeekEnum, | ||||||
|  |         Saturday: 'SATURDAY' as DayOfWeekEnum, | ||||||
|  |         Sunday: 'SUNDAY' as DayOfWeekEnum | ||||||
|  |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -13,19 +13,10 @@ import { TaskRepeatWeekDayInfo } from './taskRepeatWeekDayInfo'; | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export interface TaskRepeatWeekInfo {  | export interface TaskRepeatWeekInfo {  | ||||||
|     deadlineStrategy: TaskRepeatWeekInfo.DeadlineStrategyEnum; |  | ||||||
|     /** |     /** | ||||||
|      * Date until the tasks repeat |      * Date until the tasks repeat | ||||||
|      */ |      */ | ||||||
|     endDate: string; |     endDate: string; | ||||||
|     weekDayInfos: Array<TaskRepeatWeekDayInfo>; |     weekDayInfos: Array<TaskRepeatWeekDayInfo>; | ||||||
| } | } | ||||||
| export namespace TaskRepeatWeekInfo { |  | ||||||
|     export type DeadlineStrategyEnum = 'DEADLINE_EQUAL_START' | 'DEADLINE_FIT_START'; |  | ||||||
|     export const DeadlineStrategyEnum = { |  | ||||||
|         EqualStart: 'DEADLINE_EQUAL_START' as DeadlineStrategyEnum, |  | ||||||
|         FitStart: 'DEADLINE_FIT_START' as DeadlineStrategyEnum |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -93,7 +93,8 @@ export class ActiveTaskOverviewComponent implements OnInit{ | |||||||
|   editTask(editedTask: TaskTaskgroupInfo) { |   editTask(editedTask: TaskTaskgroupInfo) { | ||||||
|     const taskEditorInfo: TaskEditorData = { |     const taskEditorInfo: TaskEditorData = { | ||||||
|       task: editedTask, |       task: editedTask, | ||||||
|       taskgroupID: editedTask.taskgroups[editedTask.taskgroups.length-1].taskgroupID |       taskgroupID: editedTask.taskgroups[editedTask.taskgroups.length-1].taskgroupID, | ||||||
|  |       parentTask: undefined | ||||||
|     }; |     }; | ||||||
|     this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) |     this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -31,3 +31,13 @@ | |||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
|   color: black; |   color: black; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .task-title { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .subtask-link { | ||||||
|  |   color: #00bc8c; | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,7 +2,10 @@ | |||||||
|   <button mat-raised-button class="greenBtn long-btn"(click)="openTaskCreation()">Add</button> |   <button mat-raised-button class="greenBtn long-btn"(click)="openTaskCreation()">Add</button> | ||||||
|   <mat-card *ngFor="let task of tasks"> |   <mat-card *ngFor="let task of tasks"> | ||||||
|     <mat-card-content> |     <mat-card-content> | ||||||
|       <h3><a class="task-link" [routerLink]="['/taskgroups', taskgroupID!, 'tasks', task.taskID]">{{task.taskName}}</a></h3> |       <h3 class="task-title"> | ||||||
|  |         <a class="task-link" [routerLink]="['/taskgroups', taskgroupID!, 'tasks', task.taskID]">{{task.taskName}}</a> | ||||||
|  |         <button mat-button class="subtask-link" *ngIf="task.hasSubtasks" [routerLink]="['/taskgroups', taskgroupID!, 'tasks', task.taskID]">has Subtasks</button> | ||||||
|  |       </h3> | ||||||
|       <mat-progress-bar mode="determinate" value="{{task.activeTime}}" class="progress"></mat-progress-bar> |       <mat-progress-bar mode="determinate" value="{{task.activeTime}}" class="progress"></mat-progress-bar> | ||||||
|       <p class="task-info"><i>ETA: </i>{{task.activeTime}} / {{task.eta}}</p> |       <p class="task-info"><i>ETA: </i>{{task.activeTime}} / {{task.eta}}</p> | ||||||
|       <p class="task-info"><i>Limit: </i>{{task.limit}}</p> |       <p class="task-info"><i>Limit: </i>{{task.limit}}</p> | ||||||
|  | |||||||
| @ -75,7 +75,8 @@ export class TaskOverviewComponent { | |||||||
|   openTaskCreation() { |   openTaskCreation() { | ||||||
|     const editorData: TaskEditorData = { |     const editorData: TaskEditorData = { | ||||||
|       task: undefined, |       task: undefined, | ||||||
|       taskgroupID: this.taskgroupID! |       taskgroupID: this.taskgroupID!, | ||||||
|  |       parentTask: undefined | ||||||
|     } |     } | ||||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) |     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) | ||||||
|     dialogRef.afterClosed().subscribe(res => { |     dialogRef.afterClosed().subscribe(res => { | ||||||
| @ -88,6 +89,7 @@ export class TaskOverviewComponent { | |||||||
|           activeTime: 0, |           activeTime: 0, | ||||||
|           overdue: res.overdue, |           overdue: res.overdue, | ||||||
|           taskgroupPath: [], |           taskgroupPath: [], | ||||||
|  |           hasSubtasks: false, | ||||||
|           finishable: res.finishable |           finishable: res.finishable | ||||||
|         } |         } | ||||||
|         this.creationEmitter.emit({ |         this.creationEmitter.emit({ | ||||||
|  | |||||||
| @ -104,7 +104,8 @@ export class TaskgroupDashboardComponent implements OnInit { | |||||||
|   openTaskCreation() { |   openTaskCreation() { | ||||||
|     const editorData: TaskEditorData = { |     const editorData: TaskEditorData = { | ||||||
|       task: undefined, |       task: undefined, | ||||||
|       taskgroupID: this.taskgroupID |       taskgroupID: this.taskgroupID, | ||||||
|  |       parentTask: undefined | ||||||
|     } |     } | ||||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) |     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) | ||||||
|     dialogRef.afterClosed().subscribe(res => { |     dialogRef.afterClosed().subscribe(res => { | ||||||
|  | |||||||
| @ -1,12 +1,14 @@ | |||||||
| import {Component, Inject, OnInit} from '@angular/core'; | import {Component, Inject, OnInit} from '@angular/core'; | ||||||
| import {TaskEditorData} from "../task-editor/TaskEditorData"; | import {TaskEditorData} from "../task-editor/TaskEditorData"; | ||||||
| import {TaskEntityInfo, TaskgroupService} from "../../../api"; | import {TaskEntityInfo, TaskgroupService, TaskService} from "../../../api"; | ||||||
| import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | ||||||
| import {MatSnackBar} from "@angular/material/snack-bar"; | import {MatSnackBar} from "@angular/material/snack-bar"; | ||||||
| 
 | 
 | ||||||
| export interface ClearTaskDialogData { | export interface ClearTaskDialogData { | ||||||
|   taskgroupID: number, |   taskgroupID: number | undefined, | ||||||
|   tasks: TaskEntityInfo[] |   parentTaskID: number | undefined | ||||||
|  |   tasks: TaskEntityInfo[], | ||||||
|  |   subtasks: boolean | ||||||
| } | } | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-clear-task-dialog', |   selector: 'app-clear-task-dialog', | ||||||
| @ -23,7 +25,8 @@ export class ClearTaskDialogComponent implements OnInit{ | |||||||
|   constructor(@Inject(MAT_DIALOG_DATA) public editorData: ClearTaskDialogData, |   constructor(@Inject(MAT_DIALOG_DATA) public editorData: ClearTaskDialogData, | ||||||
|               private taskgroupService: TaskgroupService, |               private taskgroupService: TaskgroupService, | ||||||
|               private dialogRef: MatDialogRef<ClearTaskDialogComponent>, |               private dialogRef: MatDialogRef<ClearTaskDialogComponent>, | ||||||
|               private snackbar: MatSnackBar) { |               private snackbar: MatSnackBar, | ||||||
|  |               private taskService: TaskService) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
| @ -48,7 +51,26 @@ export class ClearTaskDialogComponent implements OnInit{ | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   clearTasks() { |   clearTasks() { | ||||||
|     this.taskgroupService.taskgroupsTaskgroupIDClearDelete(this.editorData.taskgroupID).subscribe({ |     if(this.editorData.subtasks) { | ||||||
|  |       this.taskService.tasksTaskIDSubtasksDelete(this.editorData.parentTaskID!).subscribe({ | ||||||
|  |         next: resp => { | ||||||
|  |           if(resp.status == "success") { | ||||||
|  |             const deletedTasks = this.editorData.tasks; | ||||||
|  |             this.dialogRef.close(deletedTasks); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         error: err => { | ||||||
|  |           if(err.status == 403) { | ||||||
|  |             this.snackbar.open("No permission", "", {duration: 2000}); | ||||||
|  |           } else if(err.status == 404) { | ||||||
|  |             this.snackbar.open("Not found", "", {duration: 2000}); | ||||||
|  |           } else { | ||||||
|  |             this.snackbar.open("Unexpected error", "", {duration: 2000}); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     } else { | ||||||
|  |       this.taskgroupService.taskgroupsTaskgroupIDClearDelete(this.editorData.taskgroupID!).subscribe({ | ||||||
|         next: resp => { |         next: resp => { | ||||||
|           if(resp.status == "success") { |           if(resp.status == "success") { | ||||||
|             const deletedTasks = this.editorData.tasks; |             const deletedTasks = this.editorData.tasks; | ||||||
| @ -66,4 +88,6 @@ export class ClearTaskDialogComponent implements OnInit{ | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ | |||||||
|     </ng-container> |     </ng-container> | ||||||
|     <ng-container matColumnDef="delete"> |     <ng-container matColumnDef="delete"> | ||||||
|       <th mat-header-cell *matHeaderCellDef mat-sort-header> |       <th mat-header-cell *matHeaderCellDef mat-sort-header> | ||||||
|         <button mat-icon-button color="primary" (click)="repeatSelectedTasks()" [disabled]="selection.isEmpty()"><mat-icon>event_repeat</mat-icon></button> |         <button *ngIf="subTasks.length == 0" mat-icon-button color="primary" (click)="repeatSelectedTasks()" [disabled]="selection.isEmpty()"><mat-icon>event_repeat</mat-icon></button> | ||||||
|       </th> |       </th> | ||||||
|       <td mat-cell *matCellDef="let task"> |       <td mat-cell *matCellDef="let task"> | ||||||
|         <button mat-icon-button color="warn" (click)="deleteTask(task)"><mat-icon>delete</mat-icon></button> |         <button mat-icon-button color="warn" (click)="deleteTask(task)"><mat-icon>delete</mat-icon></button> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import {Component, Input, OnChanges, ViewChild} from '@angular/core'; | import {Component, Input, OnChanges, OnInit, ViewChild} from '@angular/core'; | ||||||
| import {TaskEntityInfo, TaskService} from "../../../api"; | import {TaskEntityInfo, TaskService} from "../../../api"; | ||||||
| import {MatPaginator} from "@angular/material/paginator"; | import {MatPaginator} from "@angular/material/paginator"; | ||||||
| import {MatSort} from "@angular/material/sort"; | import {MatSort} from "@angular/material/sort"; | ||||||
| @ -20,12 +20,20 @@ import {TaskWeeklySeriesCreatorComponent} from "../task-weekly-series-creator/ta | |||||||
| }) | }) | ||||||
| export class TaskDashboardComponent implements OnChanges{ | export class TaskDashboardComponent implements OnChanges{ | ||||||
|   ngOnChanges(): void { |   ngOnChanges(): void { | ||||||
|     if(this.taskgroupID != undefined) { |     if(this.subTasks.length == 0 && this.taskgroupID != undefined) { | ||||||
|       this.fetchTasks() |       this.fetchTasks() | ||||||
|  |     } else if(this.subTasks.length > 0) { | ||||||
|  |       this.datasource.data = this.subTasks; | ||||||
|  |       this.datasource.paginator = this.paginator!; | ||||||
|  |       this.datasource.sort = this.sort!; | ||||||
|  | 
 | ||||||
|  |       this.displayedColumns = this.displayedColumns.filter(col => col !== 'select') | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @Input("taskgroupID") taskgroupID: number | undefined |   @Input("taskgroupID") taskgroupID: number | undefined | ||||||
|  |   @Input("subTasks") subTasks: TaskEntityInfo[] = [] | ||||||
|  |   @Input("parentTaskID") parentTaskID: number | undefined | ||||||
|   @ViewChild(MatPaginator) paginator: MatPaginator | undefined |   @ViewChild(MatPaginator) paginator: MatPaginator | undefined | ||||||
|   @ViewChild(MatSort) sort: MatSort | undefined |   @ViewChild(MatSort) sort: MatSort | undefined | ||||||
| 
 | 
 | ||||||
| @ -97,15 +105,18 @@ export class TaskDashboardComponent implements OnChanges{ | |||||||
|   editTask(task: TaskEntityInfo) { |   editTask(task: TaskEntityInfo) { | ||||||
|     const taskEditorInfo: TaskEditorData = { |     const taskEditorInfo: TaskEditorData = { | ||||||
|       task: task, |       task: task, | ||||||
|       taskgroupID: this.taskgroupID! |       taskgroupID: this.taskgroupID!, | ||||||
|  |       parentTask: undefined | ||||||
|     }; |     }; | ||||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, minWidth: "400px"}) |     const dialogRef = this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, minWidth: "400px"}) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   clearTasks() { |   clearTasks() { | ||||||
|     const clearTaskData: ClearTaskDialogData = { |     let clearTaskData: ClearTaskDialogData = { | ||||||
|       tasks: this.datasource.data, |       tasks: this.datasource.data, | ||||||
|       taskgroupID: this.taskgroupID! |       taskgroupID: this.taskgroupID, | ||||||
|  |       parentTaskID: this.parentTaskID, | ||||||
|  |       subtasks: this.subTasks.length > 0 | ||||||
|     } |     } | ||||||
|     const dialogRef = this.dialog.open(ClearTaskDialogComponent, {data: clearTaskData, width: "600px"}); |     const dialogRef = this.dialog.open(ClearTaskDialogComponent, {data: clearTaskData, width: "600px"}); | ||||||
|     dialogRef.afterClosed().subscribe(res => { |     dialogRef.afterClosed().subscribe(res => { | ||||||
| @ -146,5 +157,6 @@ export class TaskDashboardComponent implements OnChanges{ | |||||||
|         resp.forEach(task => console.log(task)) |         resp.forEach(task => console.log(task)) | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|  | 
 | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ | |||||||
|           <div class="left"> |           <div class="left"> | ||||||
|             <div>{{taskStatus + " " + task!.taskName}}</div> |             <div>{{taskStatus + " " + task!.taskName}}</div> | ||||||
|           </div> |           </div> | ||||||
|           <button class="right lightBlueBtn" mat-raised-button style="margin-left: auto">Add Subtask</button> |           <button class="right lightBlueBtn" mat-raised-button style="margin-left: auto" (click)="addSubtask()">Add Subtask</button> | ||||||
|         </div> |         </div> | ||||||
|       </mat-card-title> |       </mat-card-title> | ||||||
| 
 | 
 | ||||||
| @ -29,7 +29,7 @@ | |||||||
|           <button mat-flat-button class="borderless-btn" color="primary" [routerLink]="['/taskgroups', taskgroup!.taskgroupID, 'tasks', task!.taskID, 'schedule']">Schedule</button> |           <button mat-flat-button class="borderless-btn" color="primary" [routerLink]="['/taskgroups', taskgroup!.taskgroupID, 'tasks', task!.taskID, 'schedule']">Schedule</button> | ||||||
|           <button mat-flat-button class="yellowBtn" (click)="startTaskNow()">Start now</button> |           <button mat-flat-button class="yellowBtn" (click)="startTaskNow()">Start now</button> | ||||||
|           <button mat-flat-button class="grayBtn" (click)="openTaskEditor()">Edit</button> |           <button mat-flat-button class="grayBtn" (click)="openTaskEditor()">Edit</button> | ||||||
|           <button mat-flat-button class="lightBlueBtn" *ngIf="!task.hasTaskSerie" (click)="openRepeatingTaskEditor()">Copy</button> |           <button mat-flat-button class="lightBlueBtn" *ngIf="!task.hasTaskSerie && !task.hasParent" (click)="openRepeatingTaskEditor()">Copy</button> | ||||||
|           <button mat-flat-button class="greenBtn"  *ngIf="task!.finishable" (click)="finishTask()">{{task!.finished ? 'Reopen':'Finish'}}</button> |           <button mat-flat-button class="greenBtn"  *ngIf="task!.finishable" (click)="finishTask()">{{task!.finished ? 'Reopen':'Finish'}}</button> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
| @ -42,6 +42,13 @@ | |||||||
|     </mat-card-actions> |     </mat-card-actions> | ||||||
|   </mat-card> |   </mat-card> | ||||||
| 
 | 
 | ||||||
|  |   <mat-expansion-panel *ngIf="subTasks.length > 0" style="margin-top: 20px" expanded> | ||||||
|  |     <mat-expansion-panel-header>Subtasks</mat-expansion-panel-header> | ||||||
|  |     <div *ngIf="task != undefined"> | ||||||
|  |       <app-task-dashboard [subTasks]="subTasks" [taskgroupID]="taskgroupID" [parentTaskID]="task!.taskID"></app-task-dashboard> | ||||||
|  |     </div> | ||||||
|  |   </mat-expansion-panel> | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|   <mat-expansion-panel *ngIf="taskgroup != undefined && task != undefined" style="margin-top: 20px" expanded> |   <mat-expansion-panel *ngIf="taskgroup != undefined && task != undefined" style="margin-top: 20px" expanded> | ||||||
|     <mat-expansion-panel-header>Schedules</mat-expansion-panel-header> |     <mat-expansion-panel-header>Schedules</mat-expansion-panel-header> | ||||||
|  | |||||||
| @ -50,6 +50,8 @@ export class TaskDetailOverviewComponent implements OnInit { | |||||||
|   currentProgress: string = "0"; |   currentProgress: string = "0"; | ||||||
|   futureProgress: string = "0"; |   futureProgress: string = "0"; | ||||||
| 
 | 
 | ||||||
|  |   subTasks: TaskEntityInfo[] = []; | ||||||
|  | 
 | ||||||
|   constructor(private activatedRoute: ActivatedRoute, |   constructor(private activatedRoute: ActivatedRoute, | ||||||
|               private taskgroupService: TaskgroupService, |               private taskgroupService: TaskgroupService, | ||||||
|               private taskService: TaskService, |               private taskService: TaskService, | ||||||
| @ -100,6 +102,12 @@ export class TaskDetailOverviewComponent implements OnInit { | |||||||
|             this.calcProgress(); |             this.calcProgress(); | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|  | 
 | ||||||
|  |         this.taskService.tasksTaskIDSubtasksGet(Number(params.get('taskID'))).subscribe({ | ||||||
|  |           next: resp => { | ||||||
|  |             this.subTasks = resp; | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @ -135,7 +143,8 @@ export class TaskDetailOverviewComponent implements OnInit { | |||||||
|     if(this.task != undefined) { |     if(this.task != undefined) { | ||||||
|       const taskEditorInfo: TaskEditorData = { |       const taskEditorInfo: TaskEditorData = { | ||||||
|         task: this.task!, |         task: this.task!, | ||||||
|         taskgroupID: this.taskgroupID! |         taskgroupID: this.taskgroupID!, | ||||||
|  |         parentTask: undefined | ||||||
|       }; |       }; | ||||||
|       this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) |       this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) | ||||||
|     } |     } | ||||||
| @ -166,4 +175,16 @@ export class TaskDetailOverviewComponent implements OnInit { | |||||||
|       minWidth: "400px" |       minWidth: "400px" | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   addSubtask() { | ||||||
|  |     const editorData: TaskEditorData = { | ||||||
|  |       task: undefined, | ||||||
|  |       taskgroupID: this.taskgroupID, | ||||||
|  |       parentTask: this.task | ||||||
|  |     } | ||||||
|  |     const dialogRef = this.dialog.open(TaskEditorComponent, { | ||||||
|  |       data: editorData, | ||||||
|  |       minWidth: "400px" | ||||||
|  |     }) | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,4 +3,5 @@ import {TaskEntityInfo, TaskTaskgroupInfo} from "../../../api"; | |||||||
| export interface TaskEditorData { | export interface TaskEditorData { | ||||||
|   taskgroupID: number; |   taskgroupID: number; | ||||||
|   task: TaskTaskgroupInfo | TaskEntityInfo | undefined |   task: TaskTaskgroupInfo | TaskEntityInfo | undefined | ||||||
|  |   parentTask: TaskEntityInfo | undefined | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| <h1 mat-dialog-title *ngIf="editorData.task != undefined">Edit Task ({{editorData.task!.taskName}})</h1> | <h1 mat-dialog-title *ngIf="editorData.task != undefined">Edit Task ({{editorData.task!.taskName}})</h1> | ||||||
| <h1 mat-dialog-title *ngIf="editorData.task == undefined">Create New Task</h1> | <h1 mat-dialog-title *ngIf="editorData.task == undefined">Create New {{editorData.parentTask != undefined? 'Sub-':''}}Task</h1> | ||||||
| <div mat-dialog-content> | <div mat-dialog-content> | ||||||
|  |   <p *ngIf="editorData.parentTask != undefined">Create a new Subtask for the Task <i>{{editorData.parentTask!.taskName}}</i></p> | ||||||
|   <mat-form-field appearance="outline" class="long-form"> |   <mat-form-field appearance="outline" class="long-form"> | ||||||
|     <mat-label>Name</mat-label> |     <mat-label>Name</mat-label> | ||||||
|     <input matInput [formControl]="nameCtrl"> |     <input matInput [formControl]="nameCtrl"> | ||||||
|  | |||||||
| @ -61,6 +61,9 @@ export class TaskEditorComponent implements OnInit { | |||||||
|       startDate_formatted = moment(this.startDate.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ'); |       startDate_formatted = moment(this.startDate.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(this.editorData.parentTask != undefined) { | ||||||
|  |       this.createSubTask(startDate_formatted, endDate_formatted); | ||||||
|  |     } else { | ||||||
|       this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { |       this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { | ||||||
|         taskName: this.nameCtrl.value, |         taskName: this.nameCtrl.value, | ||||||
|         eta: this.etaCtrl.value, |         eta: this.etaCtrl.value, | ||||||
| @ -84,6 +87,34 @@ export class TaskEditorComponent implements OnInit { | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createSubTask(startDate_formatted: string|undefined, endDate_formatted: string|undefined) { | ||||||
|  |     this.taskService.tasksTaskIDSubtasksPut(this.editorData.parentTask!.taskID, { | ||||||
|  |       taskName: this.nameCtrl.value, | ||||||
|  |       eta: this.etaCtrl.value, | ||||||
|  |       startDate: startDate_formatted, | ||||||
|  |       deadline: endDate_formatted, | ||||||
|  |       finishable: this.finishable, | ||||||
|  |     }).subscribe({ | ||||||
|  |       next: resp => { | ||||||
|  |         this.dialog.close(resp); | ||||||
|  |       }, | ||||||
|  |       error: err => { | ||||||
|  |         if(err.status == 403) { | ||||||
|  |           this.snackbar.open("No permission", "", {duration: 2000}); | ||||||
|  |         } else if(err.status == 404) { | ||||||
|  |           this.snackbar.open("Taskgroup not found", "", {duration: 2000}); | ||||||
|  |         } else if(err.status == 409) { | ||||||
|  |           this.snackbar.open("Task already exists", "", {duration: 2000}); | ||||||
|  |         } else if(err.status == 400) { | ||||||
|  |           this.snackbar.open("Invalid Dates", "", {duration: 2000}) | ||||||
|  |         } else { | ||||||
|  |           this.snackbar.open("Unexpected error", "", {duration: 3000}); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   editTask() { |   editTask() { | ||||||
|     let endDate_formatted: string|undefined = undefined; |     let endDate_formatted: string|undefined = undefined; | ||||||
| @ -123,4 +154,6 @@ export class TaskEditorComponent implements OnInit { | |||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   protected readonly parent = parent; | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,13 +7,6 @@ | |||||||
|         <mat-label>Offset</mat-label> |         <mat-label>Offset</mat-label> | ||||||
|         <input matInput formControlName="offsetCtrl" type="number" (keypress)="($event.charCode >= 48 && $event.charCode < 58)"> |         <input matInput formControlName="offsetCtrl" type="number" (keypress)="($event.charCode >= 48 && $event.charCode < 58)"> | ||||||
|       </mat-form-field> |       </mat-form-field> | ||||||
|       <mat-form-field appearance="outline" class="long-form"> |  | ||||||
|         <mat-label>Deadline Strategy</mat-label> |  | ||||||
|         <mat-select formControlName="deadlineStrategyCtrl"> |  | ||||||
|           <mat-option value="DEADLINE_FIT_START">Fit Next Start</mat-option> |  | ||||||
|           <mat-option value="DEADLINE_EQUAL_START">Equal Same Start</mat-option> |  | ||||||
|         </mat-select> |  | ||||||
|       </mat-form-field> |  | ||||||
|       <div> |       <div> | ||||||
|         <button mat-raised-button color="primary" matStepperNext>Next</button> |         <button mat-raised-button color="primary" matStepperNext>Next</button> | ||||||
|       </div> |       </div> | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ import {Component, Inject} from '@angular/core'; | |||||||
| import {FormBuilder, Validators} from "@angular/forms"; | import {FormBuilder, Validators} from "@angular/forms"; | ||||||
| import {TaskEntityInfo, TaskRepeatDayInfo, TaskseriesService} from "../../../api"; | import {TaskEntityInfo, TaskRepeatDayInfo, TaskseriesService} from "../../../api"; | ||||||
| import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | ||||||
| import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; |  | ||||||
| import * as moment from "moment"; | import * as moment from "moment"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -15,7 +14,6 @@ export class TaskSeriesCreatorComponent { | |||||||
| 
 | 
 | ||||||
|   dailyFormGroup = this._formBuilder.group({ |   dailyFormGroup = this._formBuilder.group({ | ||||||
|     offsetCtrl: ['', Validators.required], |     offsetCtrl: ['', Validators.required], | ||||||
|     deadlineStrategyCtrl: ['', Validators.required] |  | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   endDateFormGroup = this._formBuilder.group({ |   endDateFormGroup = this._formBuilder.group({ | ||||||
| @ -32,7 +30,6 @@ export class TaskSeriesCreatorComponent { | |||||||
|   save() { |   save() { | ||||||
|     this.taskSeriesService.tasksTaskIDTaskseriesDailyPost(this.task.taskID,{ |     this.taskSeriesService.tasksTaskIDTaskseriesDailyPost(this.task.taskID,{ | ||||||
|       offset: Number(this.dailyFormGroup.get('offsetCtrl')!.value!), |       offset: Number(this.dailyFormGroup.get('offsetCtrl')!.value!), | ||||||
|       deadlineStrategy: this.convertDeadlineStrategyCtrlToDeadlineEnum(), |  | ||||||
|       endingDate: moment( this.endDateFormGroup.get('endDateCtrl')!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ') |       endingDate: moment( this.endDateFormGroup.get('endDateCtrl')!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ') | ||||||
|     }).subscribe({ |     }).subscribe({ | ||||||
|       next: resp => { |       next: resp => { | ||||||
| @ -40,13 +37,4 @@ export class TaskSeriesCreatorComponent { | |||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   convertDeadlineStrategyCtrlToDeadlineEnum() { |  | ||||||
|     const deadlineStrategy = this.dailyFormGroup.get('deadlineStrategyCtrl')!.value; |  | ||||||
|     if(deadlineStrategy === DeadlineStrategyEnum.EqualStart) { |  | ||||||
|       return DeadlineStrategyEnum.EqualStart; |  | ||||||
|     } else { |  | ||||||
|       return DeadlineStrategyEnum.FitStart; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -24,14 +24,6 @@ | |||||||
|       </div> |       </div> | ||||||
|     </mat-accordion> |     </mat-accordion> | ||||||
|   </div> |   </div> | ||||||
|   <mat-form-field appearance="outline" class="long-form" id="deadline-strategy-form"> |  | ||||||
|     <mat-label>Deadline-Strategy</mat-label> |  | ||||||
|     <mat-select formControlName="deadlineStrategyCtrl"> |  | ||||||
|       <mat-option *ngFor="let deadlineStrategy of availableDeadlineStrategys" [value]="deadlineStrategy"> |  | ||||||
|         {{deadlineStrategy}} |  | ||||||
|       </mat-option> |  | ||||||
|     </mat-select> |  | ||||||
|   </mat-form-field> |  | ||||||
| 
 | 
 | ||||||
|   <mat-form-field appearance="outline" class="long-form" id="endDate-form"> |   <mat-form-field appearance="outline" class="long-form" id="endDate-form"> | ||||||
|     <mat-label>Ending Date</mat-label> |     <mat-label>Ending Date</mat-label> | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | |||||||
| import {TaskEntityInfo, TaskRepeatDayInfo, TaskRepeatWeekDayInfo, TaskseriesService} from "../../../api"; | import {TaskEntityInfo, TaskRepeatDayInfo, TaskRepeatWeekDayInfo, TaskseriesService} from "../../../api"; | ||||||
| import * as moment from "moment"; | import * as moment from "moment"; | ||||||
| import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; | import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; | ||||||
| import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; | import DayOfWeekEnum = TaskRepeatWeekDayInfo.DayOfWeekEnum; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -14,7 +14,6 @@ import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; | |||||||
| export class TaskWeeklySeriesCreatorComponent implements OnInit{ | export class TaskWeeklySeriesCreatorComponent implements OnInit{ | ||||||
| 
 | 
 | ||||||
|   repeatingOffsetForm: FormGroup | undefined |   repeatingOffsetForm: FormGroup | undefined | ||||||
|   availableDeadlineStrategys: DeadlineStrategyEnum[] = ["DEADLINE_EQUAL_START", "DEADLINE_FIT_START"] |  | ||||||
| 
 | 
 | ||||||
|   constructor(private dialogRef: MatDialogRef<TaskWeeklySeriesCreatorComponent>, |   constructor(private dialogRef: MatDialogRef<TaskWeeklySeriesCreatorComponent>, | ||||||
|               private taskRepeatingService: TaskseriesService, |               private taskRepeatingService: TaskseriesService, | ||||||
| @ -27,7 +26,6 @@ export class TaskWeeklySeriesCreatorComponent implements OnInit{ | |||||||
|       offsets: this.formbuilder.array(this.tasks.map(task => this.formbuilder.group({ |       offsets: this.formbuilder.array(this.tasks.map(task => this.formbuilder.group({ | ||||||
|         offsetCtrl: ['', [Validators.required, Validators.min(1)]] |         offsetCtrl: ['', [Validators.required, Validators.min(1)]] | ||||||
|       }))), |       }))), | ||||||
|       deadlineStrategyCtrl: ['', Validators.required], |  | ||||||
|       endingDateCtrl: ['', Validators.required] |       endingDateCtrl: ['', Validators.required] | ||||||
| 
 | 
 | ||||||
|     }) |     }) | ||||||
| @ -62,12 +60,12 @@ export class TaskWeeklySeriesCreatorComponent implements OnInit{ | |||||||
|     for(let i=0; i<formArrayValues.length; i++) { |     for(let i=0; i<formArrayValues.length; i++) { | ||||||
|       weekDayInfos.push({ |       weekDayInfos.push({ | ||||||
|         taskID: this.tasks[i].taskID, |         taskID: this.tasks[i].taskID, | ||||||
|         offset: Number(formArrayValues[i].offsetCtrl) * 7 |         offset: Number(formArrayValues[i].offsetCtrl) * 7, | ||||||
|  |         dayOfWeek: this.getDayOfWeekFromTask(this.tasks[i])! | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.taskRepeatingService.tasksTaskseriesWeeklyPost({ |     this.taskRepeatingService.tasksTaskseriesWeeklyPost({ | ||||||
|       deadlineStrategy: this.repeatingOffsetForm!.controls['deadlineStrategyCtrl']!.value!, |  | ||||||
|       endDate: moment(this.repeatingOffsetForm!.controls['endingDateCtrl']!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), |       endDate: moment(this.repeatingOffsetForm!.controls['endingDateCtrl']!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), | ||||||
|       weekDayInfos: weekDayInfos |       weekDayInfos: weekDayInfos | ||||||
|     }).subscribe({ |     }).subscribe({ | ||||||
| @ -77,6 +75,21 @@ export class TaskWeeklySeriesCreatorComponent implements OnInit{ | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private getDayOfWeekFromTask(task: TaskEntityInfo): DayOfWeekEnum | undefined { | ||||||
|  |     const weekday = moment(task.startDate).isoWeekday(); | ||||||
|  | 
 | ||||||
|  |     switch (weekday) { | ||||||
|  |       case 1: return DayOfWeekEnum.Monday; | ||||||
|  |       case 2: return DayOfWeekEnum.Tuesday; | ||||||
|  |       case 3: return DayOfWeekEnum.Wednesday; | ||||||
|  |       case 4: return DayOfWeekEnum.Thursday; | ||||||
|  |       case 5: return DayOfWeekEnum.Friday; | ||||||
|  |       case 6: return DayOfWeekEnum.Saturday; | ||||||
|  |       case 7: return DayOfWeekEnum.Sunday; | ||||||
|  |       default: return undefined; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   cancel() { |   cancel() { | ||||||
|     this.dialogRef.close(false); |     this.dialogRef.close(false); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -59,7 +59,8 @@ export class UpcomingTaskOverviewComponent implements OnInit{ | |||||||
|   editTask(editedTask: TaskTaskgroupInfo) { |   editTask(editedTask: TaskTaskgroupInfo) { | ||||||
|     const taskEditorInfo: TaskEditorData = { |     const taskEditorInfo: TaskEditorData = { | ||||||
|       task: editedTask, |       task: editedTask, | ||||||
|       taskgroupID: editedTask.taskgroups[editedTask.taskgroups.length-1].taskgroupID |       taskgroupID: editedTask.taskgroups[editedTask.taskgroups.length-1].taskgroupID, | ||||||
|  |       parentTask: undefined | ||||||
|     }; |     }; | ||||||
|     this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) |     this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										152
									
								
								openapi.yaml
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								openapi.yaml
									
									
									
									
									
								
							| @ -1269,6 +1269,120 @@ paths: | |||||||
|               schema: |               schema: | ||||||
|                 type: object |                 type: object | ||||||
|                 $ref: "#/components/schemas/SimpleStatusResponse" |                 $ref: "#/components/schemas/SimpleStatusResponse" | ||||||
|  |   /tasks/{taskID}/subtasks: | ||||||
|  |     put: | ||||||
|  |       security: | ||||||
|  |         - API_TOKEN: [] | ||||||
|  |       tags: | ||||||
|  |         - task | ||||||
|  |       description: Create Subtask | ||||||
|  |       summary: Creates Subtask | ||||||
|  |       parameters: | ||||||
|  |         - name: taskID | ||||||
|  |           in: path | ||||||
|  |           description: internal id of task | ||||||
|  |           required: true | ||||||
|  |           schema: | ||||||
|  |             type: number | ||||||
|  |             example: 1 | ||||||
|  |       requestBody: | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/TaskFieldInfo' | ||||||
|  |       responses: | ||||||
|  |         200: | ||||||
|  |           description: Operation successfull | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/TaskEntityInfo' | ||||||
|  |         403: | ||||||
|  |           description: No permission | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |         404: | ||||||
|  |           description: Task not found | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |         400: | ||||||
|  |           description: Invalid start/end date | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |     get: | ||||||
|  |       security: | ||||||
|  |         - API_TOKEN: [] | ||||||
|  |       tags: | ||||||
|  |         - task | ||||||
|  |       parameters: | ||||||
|  |         - name: taskID | ||||||
|  |           in: path | ||||||
|  |           description: internal id of task | ||||||
|  |           required: true | ||||||
|  |           schema: | ||||||
|  |             type: number | ||||||
|  |             example: 1 | ||||||
|  |       responses: | ||||||
|  |         200: | ||||||
|  |           description: Operation successfull | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 type: array | ||||||
|  |                 items: | ||||||
|  |                   $ref: '#/components/schemas/TaskEntityInfo' | ||||||
|  |         403: | ||||||
|  |           description: No permission | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |         404: | ||||||
|  |           description: Task not found | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |     delete: | ||||||
|  |       security: | ||||||
|  |         - API_TOKEN: [] | ||||||
|  |       tags: | ||||||
|  |         - task | ||||||
|  |       parameters: | ||||||
|  |         - name: taskID | ||||||
|  |           in: path | ||||||
|  |           description: internal id of task | ||||||
|  |           required: true | ||||||
|  |           schema: | ||||||
|  |             type: number | ||||||
|  |             example: 1 | ||||||
|  |       responses: | ||||||
|  |         200: | ||||||
|  |           description: Operation successfull | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |         403: | ||||||
|  |           description: No permission | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  |         404: | ||||||
|  |           description: Task not found | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/SimpleStatusResponse' | ||||||
|  | 
 | ||||||
|  |        | ||||||
|   /schedules: |   /schedules: | ||||||
|     get: |     get: | ||||||
|       security: |       security: | ||||||
| @ -2415,6 +2529,8 @@ components: | |||||||
|         - hasActiveSchedules |         - hasActiveSchedules | ||||||
|         - hasPlannedSchedules |         - hasPlannedSchedules | ||||||
|         - hasTaskSerie |         - hasTaskSerie | ||||||
|  |         - hasSubtasks | ||||||
|  |         - hasParent | ||||||
|       additionalProperties: false |       additionalProperties: false | ||||||
|       properties: |       properties: | ||||||
|         taskID: |         taskID: | ||||||
| @ -2463,6 +2579,14 @@ components: | |||||||
|           type: boolean |           type: boolean | ||||||
|           description: determines whether the task is associated with a taskserie |           description: determines whether the task is associated with a taskserie | ||||||
|           example: false |           example: false | ||||||
|  |         hasSubtasks: | ||||||
|  |           type: boolean | ||||||
|  |           description: determines whether a task has subtasks | ||||||
|  |           example: true | ||||||
|  |         hasParent: | ||||||
|  |           type: boolean | ||||||
|  |           description: determines whether a task is a top task or Not | ||||||
|  |           example: false | ||||||
|     TaskTaskgroupInfo: |     TaskTaskgroupInfo: | ||||||
|       required: |       required: | ||||||
|         - taskID |         - taskID | ||||||
| @ -2714,6 +2838,7 @@ components: | |||||||
|         - overdue |         - overdue | ||||||
|         - taskgroupPath |         - taskgroupPath | ||||||
|         - finishable |         - finishable | ||||||
|  |         - hasSubtasks | ||||||
|       additionalProperties: false |       additionalProperties: false | ||||||
|       properties: |       properties: | ||||||
|         taskID: |         taskID: | ||||||
| @ -2748,6 +2873,9 @@ components: | |||||||
|         finishable: |         finishable: | ||||||
|           type: boolean |           type: boolean | ||||||
|           description: determines whether the task can be finished |           description: determines whether the task can be finished | ||||||
|  |         hasSubtasks: | ||||||
|  |           type: boolean | ||||||
|  |           description: determines whether the task has subtasks | ||||||
|     ScheduleStatus: |     ScheduleStatus: | ||||||
|       required: |       required: | ||||||
|         - activeMinutes |         - activeMinutes | ||||||
| @ -2866,7 +2994,6 @@ components: | |||||||
|     TaskRepeatDayInfo: |     TaskRepeatDayInfo: | ||||||
|       required: |       required: | ||||||
|         - offset |         - offset | ||||||
|         - deadlineStrategy |  | ||||||
|         - endingDate |         - endingDate | ||||||
|       additionalProperties: false |       additionalProperties: false | ||||||
|       properties: |       properties: | ||||||
| @ -2875,11 +3002,6 @@ components: | |||||||
|           description: number repeating days |           description: number repeating days | ||||||
|           example: 7 |           example: 7 | ||||||
|           minimum: 1 |           minimum: 1 | ||||||
|         deadlineStrategy: |  | ||||||
|           type: string |  | ||||||
|           enum: |  | ||||||
|             - DEADLINE_EQUAL_START |  | ||||||
|             - DEADLINE_FIT_START |  | ||||||
|         endingDate: |         endingDate: | ||||||
|           type: string |           type: string | ||||||
|           format: date |           format: date | ||||||
| @ -2888,6 +3010,7 @@ components: | |||||||
|       required: |       required: | ||||||
|         - offset |         - offset | ||||||
|         - taskID |         - taskID | ||||||
|  |         - dayOfWeek | ||||||
|       additionalProperties: false |       additionalProperties: false | ||||||
|       properties: |       properties: | ||||||
|         offset: |         offset: | ||||||
| @ -2899,18 +3022,23 @@ components: | |||||||
|           type: number |           type: number | ||||||
|           description: internal identifier of task |           description: internal identifier of task | ||||||
|           example: 1 |           example: 1 | ||||||
|  |         dayOfWeek: | ||||||
|  |           type: string | ||||||
|  |           description: day of week | ||||||
|  |           enum: | ||||||
|  |             - MONDAY | ||||||
|  |             - TUESDAY | ||||||
|  |             - WEDNESDAY | ||||||
|  |             - THURSDAY | ||||||
|  |             - FRIDAY | ||||||
|  |             - SATURDAY | ||||||
|  |             - SUNDAY | ||||||
|     TaskRepeatWeekInfo: |     TaskRepeatWeekInfo: | ||||||
|       required: |       required: | ||||||
|         - weekDayInfos |         - weekDayInfos | ||||||
|         - deadlineStrategy |  | ||||||
|         - endDate |         - endDate | ||||||
|       additionalProperties: false |       additionalProperties: false | ||||||
|       properties: |       properties: | ||||||
|         deadlineStrategy: |  | ||||||
|           type: string |  | ||||||
|           enum: |  | ||||||
|             - DEADLINE_EQUAL_START |  | ||||||
|             - DEADLINE_FIT_START |  | ||||||
|         endDate: |         endDate: | ||||||
|           type: string |           type: string | ||||||
|           format: date |           format: date | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user