issue-106 #107
| @ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*; | ||||
| import javax.validation.Valid; | ||||
| import java.time.LocalDate; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
| @CrossOrigin(origins = "*", maxAge = 3600) | ||||
| @ -72,7 +73,11 @@ public class TaskController { | ||||
|             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}") | ||||
| @ -164,4 +169,42 @@ public class TaskController { | ||||
|         taskService.finishTask(taskPermissionResult.getResult()); | ||||
|         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,10 +31,13 @@ public class RecursiveTaskgroupInfo { | ||||
|         } | ||||
| 
 | ||||
|         for(Task task : taskgroup.getActiveTasks()) { | ||||
|             this.activeTasks.add(new TaskOverviewInfo(task)); | ||||
|             if(task.getDeadline() != null && task.getDeadline().isBefore(LocalDate.now())) { | ||||
|                 this.hasOverdueTask = true; | ||||
|             if(task.getParent() == null) { | ||||
|                 this.activeTasks.add(new TaskOverviewInfo(task)); | ||||
|                 if(task.getDeadline() != null && task.getDeadline().isBefore(LocalDate.now())) { | ||||
|                     this.hasOverdueTask = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|         this.amountActiveTasks = taskgroup.getAmountOfActiveTasks(); | ||||
|     } | ||||
|  | ||||
| @ -29,6 +29,8 @@ public class TaskEntityInfo { | ||||
| 
 | ||||
|     private boolean hasPlannedSchedules; | ||||
|     private boolean hasTaskSerie; | ||||
|     private boolean hasSubtasks; | ||||
|     private boolean hasParent; | ||||
| 
 | ||||
|     public TaskEntityInfo(Task task) { | ||||
|         this.taskID = task.getTaskID(); | ||||
| @ -49,6 +51,8 @@ public class TaskEntityInfo { | ||||
|         this.hasActiveSchedules = task.hasActiveSchedule(); | ||||
|         this.hasPlannedSchedules = task.hasPlannedSchedules(); | ||||
|         this.hasTaskSerie = task.getTaskSerieItem() != null; | ||||
|         this.hasSubtasks = task.getSubtasks() != null && !task.getSubtasks().isEmpty(); | ||||
|         this.hasParent = task.getParent() != null; | ||||
|     } | ||||
| 
 | ||||
|     public long getTaskID() { | ||||
| @ -146,4 +150,20 @@ public class TaskEntityInfo { | ||||
|     public void setHasTaskSerie(boolean 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 finishable; | ||||
|     private boolean hasSubtasks; | ||||
| 
 | ||||
|     public TaskOverviewInfo(Task task) { | ||||
|         this.taskID = task.getTaskID(); | ||||
| @ -33,6 +34,7 @@ public class TaskOverviewInfo { | ||||
|             this.overdue = LocalDate.now().isAfter(task.getDeadline()); | ||||
|         } | ||||
|         this.finishable = task.isFinishable(); | ||||
|         this.hasSubtasks = !task.getSubtasks().isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     public long getTaskID() { | ||||
| @ -90,4 +92,12 @@ public class TaskOverviewInfo { | ||||
|     public void setFinishable(boolean 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 { | ||||
| 
 | ||||
|     private int offset; | ||||
|     private DeadlineStrategy deadlineStrategy; | ||||
| 
 | ||||
|     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") | ||||
|     private LocalDate endingDate; | ||||
| @ -20,14 +19,6 @@ public class TaskRepeatDayInfo { | ||||
|         this.offset = offset; | ||||
|     } | ||||
| 
 | ||||
|     public DeadlineStrategy getDeadlineStrategy() { | ||||
|         return deadlineStrategy; | ||||
|     } | ||||
| 
 | ||||
|     public void setDeadlineStrategy(DeadlineStrategy deadlineStrategy) { | ||||
|         this.deadlineStrategy = deadlineStrategy; | ||||
|     } | ||||
| 
 | ||||
|     public LocalDate getEndingDate() { | ||||
|         return endingDate; | ||||
|     } | ||||
|  | ||||
| @ -8,6 +8,7 @@ import java.time.DayOfWeek; | ||||
| public class TaskRepeatWeekDayInfo { | ||||
|     private int offset; | ||||
|     private long taskID; | ||||
|     private DayOfWeek dayOfWeek; | ||||
| 
 | ||||
|     public int getOffset() { | ||||
|         return offset; | ||||
| @ -24,4 +25,12 @@ public class TaskRepeatWeekDayInfo { | ||||
|     public void setTaskID(long taskID) { | ||||
|         this.taskID = taskID; | ||||
|     } | ||||
| 
 | ||||
|     public DayOfWeek getDayOfWeek() { | ||||
|         return dayOfWeek; | ||||
|     } | ||||
| 
 | ||||
|     public void setDayOfWeek(DayOfWeek dayOfWeek) { | ||||
|         this.dayOfWeek = dayOfWeek; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,16 +2,19 @@ package core.api.models.timemanager.tasks.repeatinginfo; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
| import util.Tupel; | ||||
| 
 | ||||
| import javax.validation.constraints.Size; | ||||
| import java.time.DayOfWeek; | ||||
| import java.time.LocalDate; | ||||
| import java.util.Comparator; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class TaskRepeatWeekInfo { | ||||
| 
 | ||||
|     @Size(min = 1, max = 7) | ||||
|     private List<TaskRepeatWeekDayInfo> weekDayInfos; | ||||
|     private DeadlineStrategy deadlineStrategy; | ||||
|     @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") | ||||
|     private LocalDate endDate; | ||||
| 
 | ||||
| @ -22,15 +25,6 @@ public class TaskRepeatWeekInfo { | ||||
|     public void setWeekDayInfos(List<TaskRepeatWeekDayInfo> weekDayInfos) { | ||||
|         this.weekDayInfos = weekDayInfos; | ||||
|     } | ||||
| 
 | ||||
|     public DeadlineStrategy getDeadlineStrategy() { | ||||
|         return deadlineStrategy; | ||||
|     } | ||||
| 
 | ||||
|     public void setDeadlineStrategy(DeadlineStrategy deadlineStrategy) { | ||||
|         this.deadlineStrategy = deadlineStrategy; | ||||
|     } | ||||
| 
 | ||||
|     public LocalDate getEndDate() { | ||||
|         return endDate; | ||||
|     } | ||||
|  | ||||
| @ -1,14 +1,15 @@ | ||||
| package core.entities.timemanager; | ||||
| 
 | ||||
| import core.api.models.timemanager.tasks.TaskFieldInfo; | ||||
| import core.api.models.timemanager.tasks.repeatinginfo.DeadlineStrategy; | ||||
| import util.Tripel; | ||||
| import util.Tupel; | ||||
| 
 | ||||
| import javax.persistence.*; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
| import java.time.temporal.ChronoUnit; | ||||
| import java.util.*; | ||||
| 
 | ||||
| @Entity | ||||
| @Table(name = "tasks") | ||||
| @ -33,6 +34,13 @@ public class Task { | ||||
|     @OneToOne(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true) | ||||
|     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() { | ||||
|         this.basicTaskSchedules = new ArrayList<>(); | ||||
|     } | ||||
| @ -47,14 +55,52 @@ public class Task { | ||||
|         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(); | ||||
|        clonedTask.setTaskgroup(task.getTaskgroup()); | ||||
|        clonedTask.setTaskName(task.taskName); | ||||
|        clonedTask.setEta(task.eta); | ||||
|        clonedTask.setFinished(false); | ||||
|        clonedTask.setFinishable(task.finishable); | ||||
|        return clonedTask; | ||||
|        clonedTasks.add(clonedTask); | ||||
| 
 | ||||
|        clonedTask.setTaskgroup(this.getTaskgroup()); | ||||
|        clonedTask.setTaskName(this.taskName); | ||||
|        clonedTask.setEta(this.eta); | ||||
|        clonedTask.setFinished(this.finished); | ||||
|        clonedTask.setFinishable(this.finishable); | ||||
|        clonedTask.setStartDate(this.startDate); | ||||
|        clonedTask.setDeadline(this.deadline); | ||||
| 
 | ||||
|        for(AbstractSchedule abstractSchedule : this.basicTaskSchedules) { | ||||
|            AbstractSchedule clonedSchedule = abstractSchedule.cloneSchedule(); | ||||
|            clonedSchedules.add(clonedSchedule); | ||||
|            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; | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|     public boolean equals(Object o) { | ||||
|         if (this == o) return true; | ||||
|  | ||||
| @ -39,4 +39,9 @@ public class TaskSerie { | ||||
|         this.tasks.add(taskSerieItem); | ||||
|         return taskSerieItem; | ||||
|     } | ||||
| 
 | ||||
|     public void addItem(TaskSerieItem taskSerieItem) { | ||||
|         this.tasks.add(taskSerieItem); | ||||
|         taskSerieItem.setTaskSerie(this); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -31,6 +31,12 @@ public class TaskSerieItem { | ||||
|     public TaskSerieItem() { | ||||
|     } | ||||
| 
 | ||||
|     public TaskSerieItem(Task task, int itemIndex) { | ||||
|         this.taskSerie = null; | ||||
|         this.seriesIndex = itemIndex; | ||||
|         this.task = task; | ||||
|     } | ||||
| 
 | ||||
|     public long getItemID() { | ||||
|         return itemID; | ||||
|     } | ||||
|  | ||||
| @ -2,10 +2,12 @@ package core.repositories.timemanager; | ||||
| 
 | ||||
| import core.entities.timemanager.AbstractSchedule; | ||||
| import core.entities.timemanager.Taskgroup; | ||||
| import org.springframework.data.jpa.repository.Modifying; | ||||
| import org.springframework.data.jpa.repository.Query; | ||||
| import org.springframework.data.repository.CrudRepository; | ||||
| import org.springframework.stereotype.Repository; | ||||
| 
 | ||||
| import javax.transaction.Transactional; | ||||
| import java.time.LocalDate; | ||||
| import java.util.Collection; | ||||
| 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") | ||||
|     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 | ||||
|     @Modifying | ||||
|     @Query(value = "DELETE FROM Task t WHERE t.taskID = ?1") | ||||
|     void deleteByTaskID(long taskID); | ||||
| 
 | ||||
|     @Query(value = "SELECT t FROM Task t WHERE t.taskgroup.user.username = ?1 AND t.deadline is NOT NULL AND t.deadline < ?2 AND t.finished = FALSE") | ||||
| @ -35,4 +36,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") | ||||
|     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.ForgottenScheduleInfo; | ||||
| import core.api.models.timemanager.taskSchedule.scheduleInfos.ScheduleInfo; | ||||
| import core.entities.timemanager.AbstractSchedule; | ||||
| import core.entities.timemanager.AdvancedTaskSchedule; | ||||
| import core.entities.timemanager.BasicTaskSchedule; | ||||
| import core.entities.timemanager.Task; | ||||
| import core.entities.timemanager.*; | ||||
| import core.repositories.UserRepository; | ||||
| import core.repositories.timemanager.AdvancedScheduleRepository; | ||||
| import core.repositories.timemanager.ScheduleRepository; | ||||
| @ -259,4 +256,8 @@ public class TaskScheduleService { | ||||
|         schedule.setStopTime(schedule.getStartTime().plusMinutes(manualScheduleStopInfo.getDuration())); | ||||
|         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 org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
| import util.Tupel; | ||||
| 
 | ||||
| import java.time.DayOfWeek; | ||||
| import java.time.Duration; | ||||
| import java.time.LocalDate; | ||||
| import java.time.temporal.ChronoUnit; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| import java.util.*; | ||||
| 
 | ||||
| @Service | ||||
| public class TaskSeriesService { | ||||
| @ -30,106 +29,103 @@ public class TaskSeriesService { | ||||
| 
 | ||||
| 
 | ||||
|     public ServiceExitCode createTaskSeries(TaskRepeatWeekInfo taskRepeatInfo) { | ||||
|         List<Task> createdTasks = new ArrayList<>(); | ||||
|         HashMap<Task, Integer> offsetMap = calcWeeklyOffsetMap(taskRepeatInfo); | ||||
| 
 | ||||
|         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()) { | ||||
|             Optional<Task> task = taskRepository.findById(taskRepeatDayInfo.getTaskID()); | ||||
|             if(task.isEmpty()) return ServiceExitCode.MISSING_ENTITY; | ||||
|             int itemIndex = weekDayIndex +1; | ||||
|             Tupel<Collection<Task>, Collection<AbstractSchedule>> repeatingResult = repeatTask(rootTask, taskRepeatInfo.getEndDate(), offsetMap, taskSerie, itemIndex); | ||||
| 
 | ||||
|             TaskSerieItem rootItem = taskSerie.addTask(task.get()); | ||||
|             task.get().setTaskSerieItem(rootItem); | ||||
|             clonedTasks.addAll(repeatingResult.getValue00()); | ||||
|             clonedSchedules.addAll(repeatingResult.getValue01()); | ||||
| 
 | ||||
|             LocalDate currentTaskDate = task.get().getStartDate().plusDays(taskRepeatDayInfo.getOffset()); | ||||
|             while(currentTaskDate.isBefore(taskRepeatInfo.getEndDate())) { | ||||
|                 Task clonedTask = Task.cloneTask(task.get()); | ||||
|                 clonedTask.setStartDate(currentTaskDate); | ||||
| 
 | ||||
|                 TaskSerieItem taskSerieItem = taskSerie.addTask(clonedTask); | ||||
|                 clonedTask.setTaskSerieItem(taskSerieItem); | ||||
|                 createdTasks.add(clonedTask); | ||||
| 
 | ||||
|                 abstractSchedules.addAll(cloneSchedules(task.get(), clonedTask)); | ||||
|                 currentTaskDate = currentTaskDate.plusDays(taskRepeatDayInfo.getOffset()); | ||||
|             } | ||||
|             weekDayIndex++; | ||||
|         } | ||||
| 
 | ||||
|         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); | ||||
|         taskRepository.saveAll(createdTasks); | ||||
|         taskRepository.saveAll(clonedTasks); | ||||
|         taskSerieItemRepository.saveAll(taskSerie.getTasks()); | ||||
|         scheduleRepository.saveAll(abstractSchedules); | ||||
|         scheduleRepository.saveAll(clonedSchedules); | ||||
| 
 | ||||
|         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) { | ||||
|         if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.FIX_DEADLINE) { | ||||
|             return ServiceExitCode.INVALID_PARAMETER; | ||||
|         } | ||||
| 
 | ||||
|         List<Task> taskList = new ArrayList<>(); | ||||
|         List<AbstractSchedule> abstractSchedules = new ArrayList<>(); | ||||
|         TaskSerie taskSerie = new TaskSerie(); | ||||
|         TaskSerieItem rootItem = taskSerie.addTask(rootTask); | ||||
|         rootTask.setTaskSerieItem(rootItem); | ||||
|         addRootSubTasksToTaskSerie(taskSerie, rootTask, 0); | ||||
| 
 | ||||
|         LocalDate currentTaskDate = rootTask.getStartDate().plusDays(taskRepeatInfo.getOffset()); | ||||
|         while(currentTaskDate.isBefore(taskRepeatInfo.getEndingDate())) { | ||||
|             Task task = Task.cloneTask(rootTask); | ||||
|             task.setStartDate(currentTaskDate); | ||||
|             if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_EQUAL_START) { | ||||
|                 task.setDeadline(currentTaskDate); | ||||
|             } else if(taskRepeatInfo.getDeadlineStrategy() == DeadlineStrategy.DEADLINE_FIT_START) { | ||||
|                 task.setDeadline(currentTaskDate.plusDays(taskRepeatInfo.getOffset()-1)); | ||||
|             } | ||||
|             TaskSerieItem taskSerieItem = taskSerie.addTask(task); | ||||
|             taskList.add(task); | ||||
|             task.setTaskSerieItem(taskSerieItem); | ||||
|         HashMap<Task, Integer> offsetMap = new HashMap<>(); | ||||
|         offsetMap.put(rootTask, taskRepeatInfo.getOffset()); | ||||
| 
 | ||||
|         var repeatingResult = repeatTask(rootTask, taskRepeatInfo.getEndingDate(), offsetMap, taskSerie, 0); | ||||
|         List<Task> clonedTasks = new ArrayList<>(repeatingResult.getValue00()); | ||||
|         List<AbstractSchedule> clonedSchedules = new ArrayList<>(repeatingResult.getValue01()); | ||||
| 
 | ||||
|             abstractSchedules.addAll(cloneSchedules(rootTask, task)); | ||||
|             currentTaskDate = currentTaskDate.plusDays(taskRepeatInfo.getOffset()); | ||||
|         } | ||||
| 
 | ||||
|         taskSeriesRepository.save(taskSerie); | ||||
|         taskRepository.saveAll(taskList); | ||||
|         taskRepository.saveAll(clonedTasks); | ||||
|         taskSerieItemRepository.saveAll(taskSerie.getTasks()); | ||||
|         scheduleRepository.saveAll(abstractSchedules); | ||||
|         scheduleRepository.saveAll(clonedSchedules); | ||||
| 
 | ||||
|         return ServiceExitCode.OK; | ||||
|     } | ||||
| 
 | ||||
|     public List<AbstractSchedule> cloneSchedules(Task previousTask, Task nextTask) { | ||||
|         long numberDays = ChronoUnit.DAYS.between(previousTask.getStartDate(), nextTask.getStartDate()); | ||||
|     private void addRootSubTasksToTaskSerie(TaskSerie taskSerie, Task rootTask, int index) { | ||||
|         Queue<Task> taskQueue = new LinkedList<>(Collections.singletonList(rootTask)); | ||||
|         while(!taskQueue.isEmpty()) { | ||||
|             Task currentTask = taskQueue.poll(); | ||||
| 
 | ||||
|         List<AbstractSchedule> clonedSchedules = new ArrayList<>(); | ||||
|         for(AbstractSchedule abstractSchedule : previousTask.getBasicTaskSchedules()) { | ||||
|             AbstractSchedule clonedSchedule = abstractSchedule.cloneSchedule(); | ||||
|             clonedSchedule.shiftSchedule(numberDays); | ||||
| 
 | ||||
|             clonedSchedules.add(clonedSchedule); | ||||
|             TaskSerieItem taskSerieItem = new TaskSerieItem(currentTask, index); | ||||
|             taskSerie.addItem(taskSerieItem); | ||||
|         } | ||||
|         return clonedSchedules; | ||||
|     } | ||||
| 
 | ||||
|     public void deleteTaskSeriesItem(Task task) { | ||||
| @ -144,22 +140,20 @@ public class TaskSeriesService { | ||||
|             } | ||||
|             taskSerie.getTasks().clear(); | ||||
|             taskSeriesRepository.delete(taskSerie); | ||||
|         } else { | ||||
|             repearIndexing(taskSerie); | ||||
|         } else if(task.getParent() == null){ | ||||
|             repearIndexing(taskSerie, item.getSeriesIndex()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void repearIndexing(TaskSerie taskSerie) { | ||||
|     private void repearIndexing(TaskSerie taskSerie, int deletedIndex) { | ||||
|         taskSerie.getTasks().sort(Comparator.comparingInt(TaskSerieItem::getSeriesIndex)); | ||||
|         List<TaskSerieItem> updatedItems = new ArrayList<>(); | ||||
|         int currentIndex = 1; | ||||
| 
 | ||||
|         for(TaskSerieItem taskSerieItem : taskSerie.getTasks()) { | ||||
|             if(taskSerieItem.getSeriesIndex() != currentIndex) { | ||||
|                 taskSerieItem.setSeriesIndex(currentIndex); | ||||
|             if(taskSerieItem.getSeriesIndex() > deletedIndex) { | ||||
|                 taskSerieItem.setSeriesIndex(taskSerieItem.getSeriesIndex() -1); | ||||
|                 updatedItems.add(taskSerieItem); | ||||
|             } | ||||
| 
 | ||||
|             currentIndex++; | ||||
|         } | ||||
| 
 | ||||
|         taskSerieItemRepository.saveAll(updatedItems); | ||||
|  | ||||
| @ -49,6 +49,21 @@ public class TaskService { | ||||
|         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) { | ||||
|         for(Task task : tasks) { | ||||
|             if(task.getTaskName().equals(name)) { | ||||
| @ -94,11 +109,12 @@ public class TaskService { | ||||
|             taskSeriesService.deleteTaskSeriesItem(task); | ||||
|         } | ||||
|         taskRepository.delete(task); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public void clearTasks(Taskgroup taskgroup) { | ||||
|         taskSeriesService.deleteTaskSerieByTaskgroup(taskgroup); | ||||
|         taskScheduleService.deleteSchedulesByTaskgroup(taskgroup); | ||||
|         taskRepository.deleteTaskHierarchyWhereTaskgroup(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 UserRepository userRepository; | ||||
|     private final TaskService taskService; | ||||
|     public TaskgroupService(@Autowired  TaskgroupRepository taskgroupRepository, | ||||
|                             @Autowired UserRepository userRepository) { | ||||
|                             @Autowired UserRepository userRepository, @Autowired TaskService taskService) { | ||||
|         this.taskgroupRepository = taskgroupRepository; | ||||
|         this.userRepository = userRepository; | ||||
|         this.taskService = taskService; | ||||
|     } | ||||
| 
 | ||||
|     public PermissionResult<Taskgroup> getTaskgroupByIDAndUsername(long taskgroupID, String username) { | ||||
| @ -97,6 +99,7 @@ public class TaskgroupService { | ||||
|     } | ||||
| 
 | ||||
|     public void deleteTaskgroup(Taskgroup taskgroup) { | ||||
|         taskService.clearTasks(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 tasks | ||||
|  | ||||
| @ -60,5 +60,13 @@ export interface TaskEntityInfo { | ||||
|      * determines whether the task is associated with a taskserie | ||||
|      */ | ||||
|     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 | ||||
|      */ | ||||
|     finishable: boolean; | ||||
|     /** | ||||
|      * determines whether the task has subtasks | ||||
|      */ | ||||
|     hasSubtasks: boolean; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -16,18 +16,9 @@ export interface TaskRepeatDayInfo { | ||||
|      * number repeating days | ||||
|      */ | ||||
|     offset: number; | ||||
|     deadlineStrategy: TaskRepeatDayInfo.DeadlineStrategyEnum; | ||||
|     /** | ||||
|      * Date until the tasks repeat | ||||
|      */ | ||||
|     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 | ||||
|      */ | ||||
|     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 {  | ||||
|     deadlineStrategy: TaskRepeatWeekInfo.DeadlineStrategyEnum; | ||||
|     /** | ||||
|      * Date until the tasks repeat | ||||
|      */ | ||||
|     endDate: string; | ||||
|     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) { | ||||
|     const taskEditorInfo: TaskEditorData = { | ||||
|       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"}) | ||||
|   } | ||||
|  | ||||
| @ -31,3 +31,13 @@ | ||||
|   text-decoration: none; | ||||
|   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> | ||||
|   <mat-card *ngFor="let task of tasks"> | ||||
|     <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> | ||||
|       <p class="task-info"><i>ETA: </i>{{task.activeTime}} / {{task.eta}}</p> | ||||
|       <p class="task-info"><i>Limit: </i>{{task.limit}}</p> | ||||
|  | ||||
| @ -75,7 +75,8 @@ export class TaskOverviewComponent { | ||||
|   openTaskCreation() { | ||||
|     const editorData: TaskEditorData = { | ||||
|       task: undefined, | ||||
|       taskgroupID: this.taskgroupID! | ||||
|       taskgroupID: this.taskgroupID!, | ||||
|       parentTask: undefined | ||||
|     } | ||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) | ||||
|     dialogRef.afterClosed().subscribe(res => { | ||||
| @ -88,6 +89,7 @@ export class TaskOverviewComponent { | ||||
|           activeTime: 0, | ||||
|           overdue: res.overdue, | ||||
|           taskgroupPath: [], | ||||
|           hasSubtasks: false, | ||||
|           finishable: res.finishable | ||||
|         } | ||||
|         this.creationEmitter.emit({ | ||||
|  | ||||
| @ -104,7 +104,8 @@ export class TaskgroupDashboardComponent implements OnInit { | ||||
|   openTaskCreation() { | ||||
|     const editorData: TaskEditorData = { | ||||
|       task: undefined, | ||||
|       taskgroupID: this.taskgroupID | ||||
|       taskgroupID: this.taskgroupID, | ||||
|       parentTask: undefined | ||||
|     } | ||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: editorData, width: "600px"}) | ||||
|     dialogRef.afterClosed().subscribe(res => { | ||||
|  | ||||
| @ -1,12 +1,14 @@ | ||||
| import {Component, Inject, OnInit} from '@angular/core'; | ||||
| 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 {MatSnackBar} from "@angular/material/snack-bar"; | ||||
| 
 | ||||
| export interface ClearTaskDialogData { | ||||
|   taskgroupID: number, | ||||
|   tasks: TaskEntityInfo[] | ||||
|   taskgroupID: number | undefined, | ||||
|   parentTaskID: number | undefined | ||||
|   tasks: TaskEntityInfo[], | ||||
|   subtasks: boolean | ||||
| } | ||||
| @Component({ | ||||
|   selector: 'app-clear-task-dialog', | ||||
| @ -23,7 +25,8 @@ export class ClearTaskDialogComponent implements OnInit{ | ||||
|   constructor(@Inject(MAT_DIALOG_DATA) public editorData: ClearTaskDialogData, | ||||
|               private taskgroupService: TaskgroupService, | ||||
|               private dialogRef: MatDialogRef<ClearTaskDialogComponent>, | ||||
|               private snackbar: MatSnackBar) { | ||||
|               private snackbar: MatSnackBar, | ||||
|               private taskService: TaskService) { | ||||
|   } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
| @ -48,22 +51,43 @@ export class ClearTaskDialogComponent implements OnInit{ | ||||
|   } | ||||
| 
 | ||||
|   clearTasks() { | ||||
|     this.taskgroupService.taskgroupsTaskgroupIDClearDelete(this.editorData.taskgroupID).subscribe({ | ||||
|       next: resp => { | ||||
|         if(resp.status == "success") { | ||||
|           const deletedTasks = this.editorData.tasks; | ||||
|           this.dialogRef.close(deletedTasks); | ||||
|     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}); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       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 => { | ||||
|           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}); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -63,7 +63,7 @@ | ||||
|     </ng-container> | ||||
|     <ng-container matColumnDef="delete"> | ||||
|       <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> | ||||
|       <td mat-cell *matCellDef="let task"> | ||||
|         <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 {MatPaginator} from "@angular/material/paginator"; | ||||
| import {MatSort} from "@angular/material/sort"; | ||||
| @ -20,12 +20,20 @@ import {TaskWeeklySeriesCreatorComponent} from "../task-weekly-series-creator/ta | ||||
| }) | ||||
| export class TaskDashboardComponent implements OnChanges{ | ||||
|   ngOnChanges(): void { | ||||
|     if(this.taskgroupID != undefined) { | ||||
|     if(this.subTasks.length == 0 && this.taskgroupID != undefined) { | ||||
|       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("subTasks") subTasks: TaskEntityInfo[] = [] | ||||
|   @Input("parentTaskID") parentTaskID: number | undefined | ||||
|   @ViewChild(MatPaginator) paginator: MatPaginator | undefined | ||||
|   @ViewChild(MatSort) sort: MatSort | undefined | ||||
| 
 | ||||
| @ -97,15 +105,18 @@ export class TaskDashboardComponent implements OnChanges{ | ||||
|   editTask(task: TaskEntityInfo) { | ||||
|     const taskEditorInfo: TaskEditorData = { | ||||
|       task: task, | ||||
|       taskgroupID: this.taskgroupID! | ||||
|       taskgroupID: this.taskgroupID!, | ||||
|       parentTask: undefined | ||||
|     }; | ||||
|     const dialogRef = this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, minWidth: "400px"}) | ||||
|   } | ||||
| 
 | ||||
|   clearTasks() { | ||||
|     const clearTaskData: ClearTaskDialogData = { | ||||
|     let clearTaskData: ClearTaskDialogData = { | ||||
|       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"}); | ||||
|     dialogRef.afterClosed().subscribe(res => { | ||||
| @ -146,5 +157,6 @@ export class TaskDashboardComponent implements OnChanges{ | ||||
|         resp.forEach(task => console.log(task)) | ||||
|       } | ||||
|     }) | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|           <div class="left"> | ||||
|             <div>{{taskStatus + " " + task!.taskName}}</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> | ||||
|       </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="yellowBtn" (click)="startTaskNow()">Start now</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> | ||||
|         </div> | ||||
| 
 | ||||
| @ -42,6 +42,13 @@ | ||||
|     </mat-card-actions> | ||||
|   </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-header>Schedules</mat-expansion-panel-header> | ||||
|  | ||||
| @ -50,6 +50,8 @@ export class TaskDetailOverviewComponent implements OnInit { | ||||
|   currentProgress: string = "0"; | ||||
|   futureProgress: string = "0"; | ||||
| 
 | ||||
|   subTasks: TaskEntityInfo[] = []; | ||||
| 
 | ||||
|   constructor(private activatedRoute: ActivatedRoute, | ||||
|               private taskgroupService: TaskgroupService, | ||||
|               private taskService: TaskService, | ||||
| @ -100,6 +102,12 @@ export class TaskDetailOverviewComponent implements OnInit { | ||||
|             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) { | ||||
|       const taskEditorInfo: TaskEditorData = { | ||||
|         task: this.task!, | ||||
|         taskgroupID: this.taskgroupID! | ||||
|         taskgroupID: this.taskgroupID!, | ||||
|         parentTask: undefined | ||||
|       }; | ||||
|       this.dialog.open(TaskEditorComponent, {data: taskEditorInfo, width: "600px"}) | ||||
|     } | ||||
| @ -166,4 +175,16 @@ export class TaskDetailOverviewComponent implements OnInit { | ||||
|       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 { | ||||
|   taskgroupID: number; | ||||
|   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">Create New Task</h1> | ||||
| <h1 mat-dialog-title *ngIf="editorData.task == undefined">Create New {{editorData.parentTask != undefined? 'Sub-':''}}Task</h1> | ||||
| <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-label>Name</mat-label> | ||||
|     <input matInput [formControl]="nameCtrl"> | ||||
|  | ||||
| @ -61,7 +61,36 @@ export class TaskEditorComponent implements OnInit { | ||||
|       startDate_formatted = moment(this.startDate.value).format('YYYY-MM-DDTHH:mm:ss.SSSZ'); | ||||
|     } | ||||
| 
 | ||||
|     this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { | ||||
|     if(this.editorData.parentTask != undefined) { | ||||
|       this.createSubTask(startDate_formatted, endDate_formatted); | ||||
|     } else { | ||||
|       this.taskService.tasksTaskgroupIDPut(this.editorData.taskgroupID, { | ||||
|         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  { | ||||
|             this.snackbar.open("Unexpected error", "", {duration: 3000}); | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   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, | ||||
| @ -78,7 +107,9 @@ export class TaskEditorComponent implements OnInit { | ||||
|           this.snackbar.open("Taskgroup not found", "", {duration: 2000}); | ||||
|         } else if(err.status == 409) { | ||||
|           this.snackbar.open("Task already exists", "", {duration: 2000}); | ||||
|         } else  { | ||||
|         } else if(err.status == 400) { | ||||
|           this.snackbar.open("Invalid Dates", "", {duration: 2000}) | ||||
|         } else { | ||||
|           this.snackbar.open("Unexpected error", "", {duration: 3000}); | ||||
|         } | ||||
|       } | ||||
| @ -123,4 +154,6 @@ export class TaskEditorComponent implements OnInit { | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   protected readonly parent = parent; | ||||
| } | ||||
|  | ||||
| @ -7,13 +7,6 @@ | ||||
|         <mat-label>Offset</mat-label> | ||||
|         <input matInput formControlName="offsetCtrl" type="number" (keypress)="($event.charCode >= 48 && $event.charCode < 58)"> | ||||
|       </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> | ||||
|         <button mat-raised-button color="primary" matStepperNext>Next</button> | ||||
|       </div> | ||||
|  | ||||
| @ -2,7 +2,6 @@ import {Component, Inject} from '@angular/core'; | ||||
| import {FormBuilder, Validators} from "@angular/forms"; | ||||
| import {TaskEntityInfo, TaskRepeatDayInfo, TaskseriesService} from "../../../api"; | ||||
| import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | ||||
| import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; | ||||
| import * as moment from "moment"; | ||||
| 
 | ||||
| 
 | ||||
| @ -15,7 +14,6 @@ export class TaskSeriesCreatorComponent { | ||||
| 
 | ||||
|   dailyFormGroup = this._formBuilder.group({ | ||||
|     offsetCtrl: ['', Validators.required], | ||||
|     deadlineStrategyCtrl: ['', Validators.required] | ||||
|   }) | ||||
| 
 | ||||
|   endDateFormGroup = this._formBuilder.group({ | ||||
| @ -32,7 +30,6 @@ export class TaskSeriesCreatorComponent { | ||||
|   save() { | ||||
|     this.taskSeriesService.tasksTaskIDTaskseriesDailyPost(this.task.taskID,{ | ||||
|       offset: Number(this.dailyFormGroup.get('offsetCtrl')!.value!), | ||||
|       deadlineStrategy: this.convertDeadlineStrategyCtrlToDeadlineEnum(), | ||||
|       endingDate: moment( this.endDateFormGroup.get('endDateCtrl')!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ') | ||||
|     }).subscribe({ | ||||
|       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> | ||||
|     </mat-accordion> | ||||
|   </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-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 * as moment from "moment"; | ||||
| import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; | ||||
| import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; | ||||
| import DayOfWeekEnum = TaskRepeatWeekDayInfo.DayOfWeekEnum; | ||||
| 
 | ||||
| 
 | ||||
| @Component({ | ||||
| @ -14,7 +14,6 @@ import DeadlineStrategyEnum = TaskRepeatDayInfo.DeadlineStrategyEnum; | ||||
| export class TaskWeeklySeriesCreatorComponent implements OnInit{ | ||||
| 
 | ||||
|   repeatingOffsetForm: FormGroup | undefined | ||||
|   availableDeadlineStrategys: DeadlineStrategyEnum[] = ["DEADLINE_EQUAL_START", "DEADLINE_FIT_START"] | ||||
| 
 | ||||
|   constructor(private dialogRef: MatDialogRef<TaskWeeklySeriesCreatorComponent>, | ||||
|               private taskRepeatingService: TaskseriesService, | ||||
| @ -27,7 +26,6 @@ export class TaskWeeklySeriesCreatorComponent implements OnInit{ | ||||
|       offsets: this.formbuilder.array(this.tasks.map(task => this.formbuilder.group({ | ||||
|         offsetCtrl: ['', [Validators.required, Validators.min(1)]] | ||||
|       }))), | ||||
|       deadlineStrategyCtrl: ['', Validators.required], | ||||
|       endingDateCtrl: ['', Validators.required] | ||||
| 
 | ||||
|     }) | ||||
| @ -62,12 +60,12 @@ export class TaskWeeklySeriesCreatorComponent implements OnInit{ | ||||
|     for(let i=0; i<formArrayValues.length; i++) { | ||||
|       weekDayInfos.push({ | ||||
|         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({ | ||||
|       deadlineStrategy: this.repeatingOffsetForm!.controls['deadlineStrategyCtrl']!.value!, | ||||
|       endDate: moment(this.repeatingOffsetForm!.controls['endingDateCtrl']!.value!).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), | ||||
|       weekDayInfos: weekDayInfos | ||||
|     }).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() { | ||||
|     this.dialogRef.close(false); | ||||
|   } | ||||
|  | ||||
| @ -59,7 +59,8 @@ export class UpcomingTaskOverviewComponent implements OnInit{ | ||||
|   editTask(editedTask: TaskTaskgroupInfo) { | ||||
|     const taskEditorInfo: TaskEditorData = { | ||||
|       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"}) | ||||
|   } | ||||
|  | ||||
							
								
								
									
										152
									
								
								openapi.yaml
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								openapi.yaml
									
									
									
									
									
								
							| @ -1269,6 +1269,120 @@ paths: | ||||
|               schema: | ||||
|                 type: object | ||||
|                 $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: | ||||
|     get: | ||||
|       security: | ||||
| @ -2415,6 +2529,8 @@ components: | ||||
|         - hasActiveSchedules | ||||
|         - hasPlannedSchedules | ||||
|         - hasTaskSerie | ||||
|         - hasSubtasks | ||||
|         - hasParent | ||||
|       additionalProperties: false | ||||
|       properties: | ||||
|         taskID: | ||||
| @ -2463,6 +2579,14 @@ components: | ||||
|           type: boolean | ||||
|           description: determines whether the task is associated with a taskserie | ||||
|           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: | ||||
|       required: | ||||
|         - taskID | ||||
| @ -2714,6 +2838,7 @@ components: | ||||
|         - overdue | ||||
|         - taskgroupPath | ||||
|         - finishable | ||||
|         - hasSubtasks | ||||
|       additionalProperties: false | ||||
|       properties: | ||||
|         taskID: | ||||
| @ -2748,6 +2873,9 @@ components: | ||||
|         finishable: | ||||
|           type: boolean | ||||
|           description: determines whether the task can be finished | ||||
|         hasSubtasks: | ||||
|           type: boolean | ||||
|           description: determines whether the task has subtasks | ||||
|     ScheduleStatus: | ||||
|       required: | ||||
|         - activeMinutes | ||||
| @ -2866,7 +2994,6 @@ components: | ||||
|     TaskRepeatDayInfo: | ||||
|       required: | ||||
|         - offset | ||||
|         - deadlineStrategy | ||||
|         - endingDate | ||||
|       additionalProperties: false | ||||
|       properties: | ||||
| @ -2875,11 +3002,6 @@ components: | ||||
|           description: number repeating days | ||||
|           example: 7 | ||||
|           minimum: 1 | ||||
|         deadlineStrategy: | ||||
|           type: string | ||||
|           enum: | ||||
|             - DEADLINE_EQUAL_START | ||||
|             - DEADLINE_FIT_START | ||||
|         endingDate: | ||||
|           type: string | ||||
|           format: date | ||||
| @ -2888,6 +3010,7 @@ components: | ||||
|       required: | ||||
|         - offset | ||||
|         - taskID | ||||
|         - dayOfWeek | ||||
|       additionalProperties: false | ||||
|       properties: | ||||
|         offset: | ||||
| @ -2899,18 +3022,23 @@ components: | ||||
|           type: number | ||||
|           description: internal identifier of task | ||||
|           example: 1 | ||||
|         dayOfWeek: | ||||
|           type: string | ||||
|           description: day of week | ||||
|           enum: | ||||
|             - MONDAY | ||||
|             - TUESDAY | ||||
|             - WEDNESDAY | ||||
|             - THURSDAY | ||||
|             - FRIDAY | ||||
|             - SATURDAY | ||||
|             - SUNDAY | ||||
|     TaskRepeatWeekInfo: | ||||
|       required: | ||||
|         - weekDayInfos | ||||
|         - deadlineStrategy | ||||
|         - endDate | ||||
|       additionalProperties: false | ||||
|       properties: | ||||
|         deadlineStrategy: | ||||
|           type: string | ||||
|           enum: | ||||
|             - DEADLINE_EQUAL_START | ||||
|             - DEADLINE_FIT_START | ||||
|         endDate: | ||||
|           type: string | ||||
|           format: date | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user