From b0a9e7eee55a78edeba4851c5d153779df642f7b Mon Sep 17 00:00:00 2001 From: aneykrap Date: Mon, 12 Oct 2026 16:17:54 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat(timer):=20=EA=B8=B0=EC=A4=80=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B4=ED=9B=84=20=ED=83=80=EC=9D=B4=EB=A8=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=ED=99=95=EC=9D=B8=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TimerRecordRepository.java | 20 +++++++++++++++++++ .../repository/TimerSessionRepository.java | 12 +++++++++++ .../domain/timer/service/TimerService.java | 10 ++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java index dcb13d4f..f3068d12 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java +++ b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java @@ -26,6 +26,26 @@ boolean existsByTodo_IdAndTargetDateAndStatusIn( @Query("delete from TimerRecord r where r.todo.id = :todoId") void deleteByTodoId(@Param("todoId") Long todoId); + @Query(""" + select count(r) > 0 + from TimerRecord r + where r.todo.id = :todoId + and r.status in :statuses + and (r.targetDate is null or r.targetDate >= :fromDate) + """) + boolean existsByTodoIdAndTargetDateFromAndStatusIn( + @Param("todoId") Long todoId, + @Param("fromDate") LocalDate fromDate, + @Param("statuses") List statuses + ); + + @Modifying(clearAutomatically = true) + @Query("delete from TimerRecord r where r.todo.id = :todoId and r.targetDate >= :fromDate") + void deleteByTodoIdAndTargetDateFrom( + @Param("todoId") Long todoId, + @Param("fromDate") LocalDate fromDate + ); + @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select t from TimerRecord t where t.id = :id") Optional findByIdForUpdate(@Param("id") Long id); diff --git a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerSessionRepository.java b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerSessionRepository.java index a00c18ec..159f8e5d 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerSessionRepository.java +++ b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerSessionRepository.java @@ -1,6 +1,7 @@ package com.Timo.Timo.domain.timer.repository; import com.Timo.Timo.domain.timer.entity.TimerSession; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -19,6 +20,17 @@ public interface TimerSessionRepository extends JpaRepository= :fromDate + """) + void deleteByTodoIdAndTargetDateFrom( + @Param("todoId") Long todoId, + @Param("fromDate") LocalDate fromDate + ); + @Query(""" select ts from TimerSession ts diff --git a/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java b/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java index 81570041..03c36227 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java +++ b/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java @@ -200,6 +200,16 @@ public void deleteTimersByTodo(Long todoId) { timerRecordRepository.deleteByTodoId(todoId); } + public boolean hasActiveTimerFrom(Long todoId, LocalDate fromDate) { + return timerRecordRepository.existsByTodoIdAndTargetDateFromAndStatusIn(todoId, fromDate, ACTIVE_STATUS); + } + + @Transactional + public void deleteTimersByTodoFrom(Long todoId, LocalDate fromDate) { + timerSessionRepository.deleteByTodoIdAndTargetDateFrom(todoId, fromDate); + timerRecordRepository.deleteByTodoIdAndTargetDateFrom(todoId, fromDate); + } + @Transactional(propagation = Propagation.NOT_SUPPORTED) public TimerFinishResponse completeTimer(Long userId, Long timerId) { return finishTimer(userId, timerId, TimerStatus.COMPLETED); From 67c2d01e20c36eb029cf0abe0c0d768d915029c3 Mon Sep 17 00:00:00 2001 From: aneykrap Date: Mon, 12 Oct 2026 16:17:54 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat(todo):=20=EA=B8=B0=EC=A4=80=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B4=ED=9B=84=20=ED=88=AC=EB=91=90=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=EC=99=80=20=EC=84=9C=EB=B8=8C=ED=83=9C?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=20=EC=99=84=EB=A3=8C=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/todo/repository/SubtaskCompletionRepository.java | 6 ++++++ .../Timo/domain/todo/repository/TodoInstanceRepository.java | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/com/Timo/Timo/domain/todo/repository/SubtaskCompletionRepository.java b/src/main/java/com/Timo/Timo/domain/todo/repository/SubtaskCompletionRepository.java index 615c672f..1cbe1be8 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/repository/SubtaskCompletionRepository.java +++ b/src/main/java/com/Timo/Timo/domain/todo/repository/SubtaskCompletionRepository.java @@ -1,5 +1,6 @@ package com.Timo.Timo.domain.todo.repository; +import java.time.LocalDate; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -22,6 +23,11 @@ public interface SubtaskCompletionRepository extends JpaRepository= :fromDate)") + void deleteByTodoIdAndDateFrom(@Param("todoId") Long todoId, @Param("fromDate") LocalDate fromDate); + @Modifying(clearAutomatically = true) @Query("delete from SubtaskCompletion sc where sc.subtask.id in :subtaskIds") void deleteBySubtaskIdIn(@Param("subtaskIds") Collection subtaskIds); diff --git a/src/main/java/com/Timo/Timo/domain/todo/repository/TodoInstanceRepository.java b/src/main/java/com/Timo/Timo/domain/todo/repository/TodoInstanceRepository.java index 2d360a1f..509eab12 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/repository/TodoInstanceRepository.java +++ b/src/main/java/com/Timo/Timo/domain/todo/repository/TodoInstanceRepository.java @@ -19,6 +19,10 @@ public interface TodoInstanceRepository extends JpaRepository= :fromDate") + void deleteByTodoIdAndDateFrom(@Param("todoId") Long todoId, @Param("fromDate") LocalDate fromDate); + @Query(""" select ti from TodoInstance ti From b56ae55aee4400fd824319454ed47ad8420d1172 Mon Sep 17 00:00:00 2001 From: aneykrap Date: Mon, 12 Oct 2026 16:17:54 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(todo):=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=ED=88=AC=EB=91=90=20=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9D=BC=20=EC=A0=84=EB=82=A0=EB=A1=9C=20?= =?UTF-8?q?=EC=A4=84=EC=9D=B4=EB=8A=94=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/domain/todo/entity/Todo.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java b/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java index ba7a67fe..f78b61b6 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java +++ b/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java @@ -218,6 +218,20 @@ public void changeSchedule( this.repeatDayOfMonth = repeatType == RepeatType.MONTHLY ? repeatDayOfMonth : null; } + public boolean isRepeating() { + return repeatType != RepeatType.NONE; + } + + public boolean startsBefore(LocalDate date) { + return startDate.isBefore(date); + } + + public void endRepeatBefore(LocalDate date) { + if (!date.isAfter(endDate)) { + this.endDate = date.minusDays(1); + } + } + public void replaceSubtasks(List edits) { Map existingById = subtasks.stream() .filter(subtask -> subtask.getId() != null) From 9dc6dece211f2bb4e7e994f073d303a81c5a5702 Mon Sep 17 00:00:00 2001 From: aneykrap Date: Mon, 12 Oct 2026 16:17:58 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat(todo):=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=ED=88=AC=EB=91=90=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=EC=9D=BC=EC=9D=84=20=ED=8F=AC=ED=95=A8=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EC=9D=BC=EC=A0=95=EB=A7=8C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/controller/TodoController.java | 5 ++-- .../domain/todo/docs/TodoControllerDocs.java | 16 +++++++++- .../Timo/domain/todo/service/TodoService.java | 30 +++++++++++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/todo/controller/TodoController.java b/src/main/java/com/Timo/Timo/domain/todo/controller/TodoController.java index 3e902086..1d40ed99 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/controller/TodoController.java +++ b/src/main/java/com/Timo/Timo/domain/todo/controller/TodoController.java @@ -150,9 +150,10 @@ public ResponseEntity> updateTodo( @DeleteMapping("/{todoId}") public ResponseEntity> deleteTodo( @AuthenticationPrincipal CustomUserDetails userDetails, - @PathVariable Long todoId + @PathVariable Long todoId, + @RequestParam(required = false) String date ) { - todoService.deleteTodo(userDetails.getUserId(), todoId); + todoService.deleteTodo(userDetails.getUserId(), todoId, date); return ResponseEntity .status(TodoSuccessCode.DELETED.getHttpStatus()) diff --git a/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java b/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java index 9602984a..cf0d4578 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java +++ b/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java @@ -360,6 +360,11 @@ ResponseEntity> updateTodo( TODO와 연결된 하위 태스크, 반복 규칙을 함께 삭제합니다. 해당 TODO에 실행 중이거나 일시정지된 타이머가 있으면 삭제할 수 없습니다. + date에는 삭제를 누른 카드의 날짜를 반복 여부와 상관없이 항상 전달합니다. + 반복 TODO는 date를 포함한 이후 반복 일정만 삭제하고, date 이전 일정과 완료·타이머 기록은 유지합니다. + 이때 반복 종료일은 date 전날로 변경되며, date 이후 일정에 실행 중이거나 일시정지된 타이머가 있으면 삭제할 수 없습니다. + 반복이 아닌 TODO는 해당 TODO 전체를 삭제합니다. + Swagger UI 오른쪽 위의 Authorize 버튼을 눌러 유효한 Access Token을 입력해야 합니다. """ ) @@ -369,6 +374,14 @@ ResponseEntity> updateTodo( description = "TODO 삭제 성공", useReturnTypeSchema = true ), + @ApiResponse( + responseCode = "400", + description = "date가 누락되었거나 날짜 형식(yyyy-MM-dd)이 올바르지 않은 경우", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorDto.class) + ) + ), @ApiResponse( responseCode = "401", description = "Access Token이 없거나 만료되었거나 유효하지 않은 경우", @@ -404,7 +417,8 @@ ResponseEntity> updateTodo( }) ResponseEntity> deleteTodo( @Parameter(hidden = true) CustomUserDetails userDetails, - @Parameter(description = "삭제할 TODO ID", example = "205") Long todoId + @Parameter(description = "삭제할 TODO ID", example = "205") Long todoId, + @Parameter(description = "삭제를 누른 카드의 날짜 (yyyy-MM-dd)", example = "2026-10-20", required = true) String date ); @Operation( diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java index 24baac95..37a0d471 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java @@ -255,10 +255,36 @@ public void updateTodo(Long userId, Long todoId, TodoUpdateRequest request) { } @Transactional - public void deleteTodo(Long userId, Long todoId) { + public void deleteTodo(Long userId, Long todoId, String dateValue) { + LocalDate date = parseDate(dateValue); Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId) .orElseThrow(() -> new CustomException(TodoErrorCode.TODO_NOT_FOUND)); + if (todo.isRepeating() && todo.startsBefore(date)) { + removeTodoFrom(todo, date); + } else { + removeTodo(todo); + } + + bumpAiHistoryVersionAfterCommit(userId); + } + + private void removeTodoFrom(Todo todo, LocalDate date) { + Long todoId = todo.getId(); + if (timerService.hasActiveTimerFrom(todoId, date)) { + throw new CustomException(TodoErrorCode.TIMER_RUNNING); + } + + todo.endRepeatBefore(date); + todoRepository.flush(); + + timerService.deleteTimersByTodoFrom(todoId, date); + subtaskCompletionRepository.deleteByTodoIdAndDateFrom(todoId, date); + todoInstanceRepository.deleteByTodoIdAndDateFrom(todoId, date); + } + + private void removeTodo(Todo todo) { + Long todoId = todo.getId(); if (timerService.hasActiveTimer(todoId)) { throw new CustomException(TodoErrorCode.TIMER_RUNNING); } @@ -268,8 +294,6 @@ public void deleteTodo(Long userId, Long todoId) { subtaskCompletionRepository.deleteByTodoId(todoId); todoInstanceRepository.deleteByTodoId(todoId); todoRepository.delete(todo); - - bumpAiHistoryVersionAfterCommit(userId); } private boolean isHistoryKeyChanged(Todo todo, TodoUpdateRequest request) { From abe0425faae36b22cd377a7e01b983191b04bf6c Mon Sep 17 00:00:00 2001 From: aneykrap Date: Tue, 13 Oct 2026 12:58:30 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix(todo):=20=EB=B0=98=EB=B3=B5=20=ED=88=AC?= =?UTF-8?q?=EB=91=90=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=EC=9D=BC=20=EB=8C=80=EC=8B=A0=20=EC=8B=A4=EC=A0=9C=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=20=EC=82=AD=EC=A0=9C=20=EC=97=AC=EB=B6=80=20=ED=8C=90?= =?UTF-8?q?=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Timo/Timo/domain/todo/entity/Todo.java | 4 ---- .../Timo/domain/todo/service/TodoDateCalculator.java | 10 ++++++++++ .../com/Timo/Timo/domain/todo/service/TodoService.java | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java b/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java index f78b61b6..1e1bf316 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java +++ b/src/main/java/com/Timo/Timo/domain/todo/entity/Todo.java @@ -222,10 +222,6 @@ public boolean isRepeating() { return repeatType != RepeatType.NONE; } - public boolean startsBefore(LocalDate date) { - return startDate.isBefore(date); - } - public void endRepeatBefore(LocalDate date) { if (!date.isAfter(endDate)) { this.endDate = date.minusDays(1); diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoDateCalculator.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoDateCalculator.java index 746e36b0..069f4171 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoDateCalculator.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoDateCalculator.java @@ -79,6 +79,16 @@ public boolean occursOn(Todo rule, LocalDate date) { }; } + public boolean occursBefore(Todo rule, LocalDate date) { + LocalDate end = date.isAfter(rule.getEndDate()) ? rule.getEndDate().plusDays(1) : date; + if (!end.isAfter(rule.getStartDate())) { + return false; + } + + return rule.getStartDate().datesUntil(end) + .anyMatch(candidate -> occursOn(rule, candidate)); + } + private boolean matchesWeekly(LocalDate date, List weekdays) { Set targets = weekdays.stream() .map(DAY_OF_WEEKS::get) diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java index 159c9c21..fa694915 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java @@ -259,7 +259,7 @@ public void deleteTodo(Long userId, Long todoId, String dateValue) { Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId) .orElseThrow(() -> new CustomException(TodoErrorCode.TODO_NOT_FOUND)); - if (todo.isRepeating() && todo.startsBefore(date)) { + if (todo.isRepeating() && todoDateCalculator.occursBefore(todo, date)) { removeTodoFrom(todo, date); } else { removeTodo(todo); From f935809f4efbc03cefaa5451ed9ed977aeaeeb0d Mon Sep 17 00:00:00 2001 From: aneykrap Date: Tue, 13 Oct 2026 13:09:04 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix(timer):=20=EA=B8=B0=EC=A4=80=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B4=ED=9B=84=20=EC=8B=A4=ED=96=89=20=EC=A4=91=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=EB=A8=B8=20=ED=99=95=EC=9D=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20targetDate=20null=20=EC=A1=B0=EA=B1=B4=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/domain/timer/repository/TimerRecordRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java index f3068d12..025d10fa 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java +++ b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java @@ -31,7 +31,7 @@ select count(r) > 0 from TimerRecord r where r.todo.id = :todoId and r.status in :statuses - and (r.targetDate is null or r.targetDate >= :fromDate) + and r.targetDate >= :fromDate """) boolean existsByTodoIdAndTargetDateFromAndStatusIn( @Param("todoId") Long todoId, From feb4cfcd6690d9db9f74a54b571b88a9dfd40350 Mon Sep 17 00:00:00 2001 From: aneykrap Date: Tue, 13 Oct 2026 13:09:04 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix(todo):=20=EB=B0=98=EB=B3=B5=20=ED=88=AC?= =?UTF-8?q?=EB=91=90=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9E=A0=EA=B8=88=EC=9C=BC=EB=A1=9C=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=A8=B8=20=EC=8B=9C=EC=9E=91=EA=B3=BC=20=EC=A7=81?= =?UTF-8?q?=EB=A0=AC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Timo/Timo/domain/todo/service/TodoService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java index fa694915..57aec3fe 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java @@ -256,6 +256,8 @@ public void updateTodo(Long userId, Long todoId, TodoUpdateRequest request) { @Transactional public void deleteTodo(Long userId, Long todoId, String dateValue) { LocalDate date = parseDate(dateValue); + userRepository.findByIdForUpdate(userId) + .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId) .orElseThrow(() -> new CustomException(TodoErrorCode.TODO_NOT_FOUND)); From aa026b1a8875c12f63d12a06bf418704b5406c4a Mon Sep 17 00:00:00 2001 From: aneykrap Date: Thu, 15 Oct 2026 11:57:04 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix(todo):=20=ED=88=AC=EB=91=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=20date=EA=B0=80=20=EC=97=86=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EA=B8=B0=EC=A1=B4=EC=B2=98=EB=9F=BC=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Timo/Timo/domain/todo/service/TodoService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java index e00f8c23..34837712 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java @@ -257,7 +257,7 @@ public void updateTodo(Long userId, Long todoId, TodoUpdateRequest request) { @Transactional public void deleteTodo(Long userId, Long todoId, String dateValue) { - LocalDate date = parseDate(dateValue); + LocalDate date = dateValue == null ? null : parseDate(dateValue); userRepository.findByIdForUpdate(userId) .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId) @@ -267,7 +267,7 @@ public void deleteTodo(Long userId, Long todoId, String dateValue) { throw new CustomException(TodoErrorCode.GOOGLE_EVENT_NOT_DELETABLE); } - if (todo.isRepeating() && todoDateCalculator.occursBefore(todo, date)) { + if (date != null && todo.isRepeating() && todoDateCalculator.occursBefore(todo, date)) { removeTodoFrom(todo, date); } else { removeTodo(todo); From 57dc6c51b7daa66fd1b40fe6b9dccef101fb96b4 Mon Sep 17 00:00:00 2001 From: aneykrap Date: Thu, 15 Oct 2026 11:57:04 +0900 Subject: [PATCH 9/9] =?UTF-8?q?docs(todo):=20=ED=88=AC=EB=91=90=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20API=EC=97=90=20date=20=EB=AF=B8=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=20=EC=8B=9C=20=EB=8F=99=EC=9E=91=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java b/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java index 41445e79..e0bee3bb 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java +++ b/src/main/java/com/Timo/Timo/domain/todo/docs/TodoControllerDocs.java @@ -367,6 +367,7 @@ ResponseEntity> updateTodo( 반복 TODO는 date를 포함한 이후 반복 일정만 삭제하고, date 이전 일정과 완료·타이머 기록은 유지합니다. 이때 반복 종료일은 date 전날로 변경되며, date 이후 일정에 실행 중이거나 일시정지된 타이머가 있으면 삭제할 수 없습니다. 반복이 아닌 TODO는 해당 TODO 전체를 삭제합니다. + 기존 클라이언트 호환을 위해 당분간 date 없이 요청하면 반복 여부와 상관없이 TODO 전체를 삭제합니다. 구글 캘린더에서 가져온 일정은 삭제할 수 없습니다. 단, 구글에서 삭제되었지만 타이머 기록이 있어 남아 있는 일정(isDeletedFromGoogle=true)과 @@ -383,7 +384,7 @@ ResponseEntity> updateTodo( ), @ApiResponse( responseCode = "400", - description = "date가 누락되었거나 날짜 형식(yyyy-MM-dd)이 올바르지 않은 경우", + description = "date의 날짜 형식(yyyy-MM-dd)이 올바르지 않은 경우", content = @Content( mediaType = "application/json", schema = @Schema(implementation = ErrorDto.class)