From 4c1f8086d3ef56369d7eb6f7ce3f47295ce2f52d Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 16:58:36 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[feat/#114]=20=EC=8A=B5=EA=B4=80=20?= =?UTF-8?q?=EC=9E=AC=EB=8F=84=EC=A0=84=20=EC=8B=9C=20=EC=88=98=ED=96=89=20?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=20=EB=B0=8F=20=EB=B3=B4=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=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 --- .../domain/habit/controller/HabitController.java | 6 ++++-- .../server/domain/habit/dto/HabitRetryRequest.java | 7 +++++++ .../com/swyp/server/domain/habit/entity/Habit.java | 5 ++++- .../domain/habit/repository/HabitRepository.java | 2 +- .../server/domain/habit/service/HabitService.java | 13 +++++++++---- 5 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/swyp/server/domain/habit/dto/HabitRetryRequest.java diff --git a/src/main/java/com/swyp/server/domain/habit/controller/HabitController.java b/src/main/java/com/swyp/server/domain/habit/controller/HabitController.java index a09bc9f..bd482e2 100644 --- a/src/main/java/com/swyp/server/domain/habit/controller/HabitController.java +++ b/src/main/java/com/swyp/server/domain/habit/controller/HabitController.java @@ -76,8 +76,10 @@ public ResponseEntity> updateHabit( @Operation(summary = "습관 재도전") @PatchMapping("/failed/{habitId}/status/in-progress") public ResponseEntity> retryFailedHabits( - @AuthenticationPrincipal Long userId, @PathVariable Long habitId) { - habitService.retryFailedHabit(userId, habitId); + @AuthenticationPrincipal Long userId, + @PathVariable Long habitId, + @Valid @RequestBody HabitRetryRequest request) { + habitService.retryFailedHabit(userId, habitId, request); return ResponseEntity.ok(ApiResponse.success(null)); } diff --git a/src/main/java/com/swyp/server/domain/habit/dto/HabitRetryRequest.java b/src/main/java/com/swyp/server/domain/habit/dto/HabitRetryRequest.java new file mode 100644 index 0000000..229f691 --- /dev/null +++ b/src/main/java/com/swyp/server/domain/habit/dto/HabitRetryRequest.java @@ -0,0 +1,7 @@ +package com.swyp.server.domain.habit.dto; + +import com.swyp.server.domain.habit.entity.HabitDuration; +import jakarta.validation.constraints.NotNull; + +public record HabitRetryRequest( + @NotNull(message = "HABIT_DURATION_REQUIRED") HabitDuration duration, String reward) {} diff --git a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java index 36afc39..1652826 100644 --- a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java +++ b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java @@ -1,5 +1,6 @@ package com.swyp.server.domain.habit.entity; +import com.swyp.server.domain.habit.dto.HabitRetryRequest; import com.swyp.server.domain.user.entity.User; import com.swyp.server.domain.user.entity.UserType; import com.swyp.server.global.SoftDeletableEntity; @@ -89,7 +90,9 @@ public void updateRewardStatus(RewardStatus rewardStatus) { this.status = rewardStatus; } - public void retry(User user) { + public void retry(User user, HabitRetryRequest request) { + this.duration = request.duration(); + this.reward = request.reward(); this.status = user.getUserType() == UserType.CHILD ? RewardStatus.REWARD_CHECKING diff --git a/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java b/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java index dd5f45d..ad67e29 100644 --- a/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java +++ b/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java @@ -72,7 +72,7 @@ List findAllByUserIdsAndStatusOptional( @Modifying(clearAutomatically = true) @Query( - "UPDATE Habit h SET h.status = 'FAIL' " + "UPDATE Habit h SET h.status = 'FAIL', h.isCompleted = false " + " WHERE h.failCount > 1 " + " AND h.status IN ('REWARD_CHECKING', 'IN_PROGRESS')" + " AND h.duration NOT IN ('THREE_DAYS', 'SEVEN_DAYS')") diff --git a/src/main/java/com/swyp/server/domain/habit/service/HabitService.java b/src/main/java/com/swyp/server/domain/habit/service/HabitService.java index 95480f7..d4e41b6 100644 --- a/src/main/java/com/swyp/server/domain/habit/service/HabitService.java +++ b/src/main/java/com/swyp/server/domain/habit/service/HabitService.java @@ -141,7 +141,8 @@ public HabitRewardListResponse getHabitRewards(Long userId, RewardStatus status) List searchStatuses = determineSearchStatuses(user.getUserType(), status); - if(targetUserIds.isEmpty() || searchStatuses.isEmpty()) return HabitRewardListResponse.empty(); + if (targetUserIds.isEmpty() || searchStatuses.isEmpty()) + return HabitRewardListResponse.empty(); List habits = habitRepository.findAllByUserIdsAndStatusOptional(targetUserIds, searchStatuses); @@ -227,7 +228,7 @@ public void updateHabit(Long userId, Long habitId, HabitUpdateRequest request) { } @Transactional - public void retryFailedHabit(Long userId, Long habitId) { + public void retryFailedHabit(Long userId, Long habitId, HabitRetryRequest request) { Habit habit = habitRepository .findByIdAndUserIdAndStatus(habitId, userId, RewardStatus.FAIL) @@ -238,7 +239,7 @@ public void retryFailedHabit(Long userId, Long habitId) { .findById(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - habit.retry(user); + habit.retry(user, request); } @Transactional @@ -345,7 +346,11 @@ private List determineSearchStatuses(UserType userType, RewardStat } if (status == RewardStatus.ALL) { - return List.of(RewardStatus.REWARD_CHECKING, RewardStatus.IN_PROGRESS, RewardStatus.REWARD_WAITING, RewardStatus.COMPLETE); + return List.of( + RewardStatus.REWARD_CHECKING, + RewardStatus.IN_PROGRESS, + RewardStatus.REWARD_WAITING, + RewardStatus.COMPLETE); } if (userType == UserType.CHILD && status == RewardStatus.IN_PROGRESS) { From c53358c97546fdc293b5d8dc049155194f7b5e86 Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 16:59:17 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[test/#114]=20=EC=8A=B5=EA=B4=80=20?= =?UTF-8?q?=EC=9E=AC=EB=8F=84=EC=A0=84=20=EC=8B=9C=20=EC=88=98=ED=96=89=20?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=20=EB=B0=8F=20=EB=B3=B4=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=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 --- .../domain/habit/HabitRepositoryTest.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java b/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java index 5d4f7fc..e47953b 100644 --- a/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java +++ b/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java @@ -10,6 +10,10 @@ import com.swyp.server.domain.user.entity.User; import com.swyp.server.domain.user.entity.UserType; import com.swyp.server.domain.user.repository.UserRepository; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,11 +24,6 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.List; - @SpringBootTest @Transactional @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @@ -40,7 +39,7 @@ public class HabitRepositoryTest { @Test @DisplayName("status가 'FAIL'인 습관은 보상 조회 시 조회되지 않아야 한다.") - void getHabitRewards(){ + void getHabitRewards() { User user = User.builder() @@ -95,17 +94,22 @@ void getHabitRewards(){ habitFailed.updateRewardStatus(RewardStatus.FAIL); + habitRepository.saveAll( + List.of(habitInProgress, habitRewardWaiting, habitCompleted, habitFailed)); - habitRepository.saveAll(List.of(habitInProgress, habitRewardWaiting, habitCompleted, habitFailed)); - - HabitRewardListResponse habitRewardsStatusAll = habitService.getHabitRewards(user.getId(), RewardStatus.ALL); - HabitRewardListResponse habitRewardsFail = habitService.getHabitRewards(user.getId(), RewardStatus.FAIL); + HabitRewardListResponse habitRewardsStatusAll = + habitService.getHabitRewards(user.getId(), RewardStatus.ALL); + HabitRewardListResponse habitRewardsFail = + habitService.getHabitRewards(user.getId(), RewardStatus.FAIL); Assertions.assertThat(habitRewardsStatusAll.habitRewards()) .hasSize(3) .extracting("status") .doesNotContain(RewardStatus.FAIL) - .containsExactlyInAnyOrder(RewardStatus.IN_PROGRESS, RewardStatus.REWARD_WAITING, RewardStatus.COMPLETE); + .containsExactlyInAnyOrder( + RewardStatus.IN_PROGRESS, + RewardStatus.REWARD_WAITING, + RewardStatus.COMPLETE); Assertions.assertThat(habitRewardsFail.habitRewards()).isEmpty(); } @@ -233,7 +237,8 @@ void updateImmediateFailureHabits() { } @Test - @DisplayName("매일 자정 보상 확인중, 진행중 상태이며 수행 기간이 '3일', '7일' 외 습관들은 실패 횟수가 2회 이상이면 실패 상태가 되어야 한다.") + @DisplayName( + "매일 자정 보상 확인중, 진행중 상태이며 수행 기간이 '3일', '7일' 외 습관들은 실패 횟수가 2회 이상이면 실패 상태가 되어야 하며 일일 성공 여부가 false가 되어야 한다.") void updateCumulativeFailureHabits() { User user = User.builder() @@ -281,8 +286,10 @@ void updateCumulativeFailureHabits() { Assertions.assertThat(fourteenDaysOneFailedHabitInDB.getStatus()) .isNotEqualTo(RewardStatus.FAIL); + Assertions.assertThat(fourteenDaysTwoFailedHabitInDB.getStatus()) .isEqualTo(RewardStatus.FAIL); + Assertions.assertThat(fourteenDaysTwoFailedHabitInDB.isCompleted()).isEqualTo(false); } @Test From 1681441ff143e0fe10035e0e642f9c402f6f2b96 Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 17:18:12 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[fix/#114]=20=EC=8A=B5=EA=B4=80=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swyp/server/domain/habit/repository/HabitRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java b/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java index ad67e29..4a0b52b 100644 --- a/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java +++ b/src/main/java/com/swyp/server/domain/habit/repository/HabitRepository.java @@ -64,7 +64,7 @@ List findAllByUserIdsAndStatusOptional( @Modifying(clearAutomatically = true) @Query( - "UPDATE Habit h SET h.status = 'FAIL' " + "UPDATE Habit h SET h.status = 'FAIL', h.isCompleted = false " + " WHERE h.isCompleted = false " + " AND h.status IN ('REWARD_CHECKING', 'IN_PROGRESS')" + " AND h.duration IN ('THREE_DAYS', 'SEVEN_DAYS')") From e6da33bae2b396e428252dca85b4d3a14703c1cd Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 17:18:55 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[test/#114]=20=EC=88=98=EC=A0=95=EB=90=9C?= =?UTF-8?q?=20=EC=8A=B5=EA=B4=80=20=EC=8B=A4=ED=8C=A8=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=BF=BC=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/swyp/server/domain/habit/HabitRepositoryTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java b/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java index e47953b..dfe97be 100644 --- a/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java +++ b/src/test/java/com/swyp/server/domain/habit/HabitRepositoryTest.java @@ -186,7 +186,8 @@ void updateHabitFailCount() { } @Test - @DisplayName("매일 자정 보상 확인중, 진행중 상태이며 수행 기간이 '3일', '7일'인 습관들은 당일 미실행 시 실패 상태가 되어야 한다.") + @DisplayName( + "매일 자정 보상 확인중, 진행중 상태이며 수행 기간이 '3일', '7일'인 습관들은 당일 미실행 시 실패 상태가 되어야 하며 일일 성공 여부가 false가 되어야 한다.") void updateImmediateFailureHabits() { User user = User.builder() @@ -232,8 +233,11 @@ void updateImmediateFailureHabits() { Assertions.assertThat(threeDaysNotCompletedHabitInDB.getStatus()) .isEqualTo(RewardStatus.FAIL); + Assertions.assertThat(threeDaysNotCompletedHabitInDB.isCompleted()).isEqualTo(false); + Assertions.assertThat(sevenDaysNotCompletedHabitInDB.getStatus()) .isEqualTo(RewardStatus.FAIL); + Assertions.assertThat(sevenDaysNotCompletedHabitInDB.isCompleted()).isEqualTo(false); } @Test From f419b3aa9cc5b50fd9522d5700a83baa9de42d6e Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 17:26:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[fix/#114]=20=EC=8A=B5=EA=B4=80=20=EC=9E=AC?= =?UTF-8?q?=EB=8F=84=EC=A0=84=20=EC=8B=9C=20=EB=B3=B4=EC=83=81=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/swyp/server/domain/habit/entity/Habit.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java index 1652826..2ea52e9 100644 --- a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java +++ b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java @@ -4,6 +4,8 @@ import com.swyp.server.domain.user.entity.User; import com.swyp.server.domain.user.entity.UserType; import com.swyp.server.global.SoftDeletableEntity; +import com.swyp.server.global.exception.CustomException; +import com.swyp.server.global.exception.ErrorCode; import jakarta.persistence.*; import java.time.LocalDateTime; import java.time.ZoneId; @@ -91,6 +93,11 @@ public void updateRewardStatus(RewardStatus rewardStatus) { } public void retry(User user, HabitRetryRequest request) { + if (user.getUserType() == UserType.CHILD) { + if (request.reward() == null || request.reward().isBlank()) + throw new CustomException(ErrorCode.HABIT_REWARD_REQUIRED); + } + this.duration = request.duration(); this.reward = request.reward(); this.status = From 43f3fe278cfceeda36b62a38ee2d5b4024e1fc29 Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 17:55:06 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[fix/#114]=20=EC=8A=B5=EA=B4=80=20=EC=9E=AC?= =?UTF-8?q?=EB=8F=84=EC=A0=84=20=EC=8B=9C=20=EB=B3=B4=EC=83=81=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/swyp/server/domain/habit/entity/Habit.java | 8 ++++---- .../swyp/server/domain/habit/service/HabitService.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java index 2ea52e9..0063862 100644 --- a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java +++ b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java @@ -92,14 +92,14 @@ public void updateRewardStatus(RewardStatus rewardStatus) { this.status = rewardStatus; } - public void retry(User user, HabitRetryRequest request) { + public void retry(User user, HabitDuration duration, String reward) { if (user.getUserType() == UserType.CHILD) { - if (request.reward() == null || request.reward().isBlank()) + if (reward == null || reward.isBlank()) throw new CustomException(ErrorCode.HABIT_REWARD_REQUIRED); } - this.duration = request.duration(); - this.reward = request.reward(); + this.duration = duration; + this.reward = reward; this.status = user.getUserType() == UserType.CHILD ? RewardStatus.REWARD_CHECKING diff --git a/src/main/java/com/swyp/server/domain/habit/service/HabitService.java b/src/main/java/com/swyp/server/domain/habit/service/HabitService.java index d4e41b6..d399cd1 100644 --- a/src/main/java/com/swyp/server/domain/habit/service/HabitService.java +++ b/src/main/java/com/swyp/server/domain/habit/service/HabitService.java @@ -239,7 +239,7 @@ public void retryFailedHabit(Long userId, Long habitId, HabitRetryRequest reques .findById(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - habit.retry(user, request); + habit.retry(user, request.duration(), request.reward()); } @Transactional From 3da2f25963ffd856f77a0ddea4426ca3e8452aff Mon Sep 17 00:00:00 2001 From: guzzang <991012kim@gmail.com> Date: Sun, 19 Apr 2026 18:03:15 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[fix/#114]=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/swyp/server/domain/habit/entity/Habit.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java index 0063862..616f67b 100644 --- a/src/main/java/com/swyp/server/domain/habit/entity/Habit.java +++ b/src/main/java/com/swyp/server/domain/habit/entity/Habit.java @@ -1,6 +1,5 @@ package com.swyp.server.domain.habit.entity; -import com.swyp.server.domain.habit.dto.HabitRetryRequest; import com.swyp.server.domain.user.entity.User; import com.swyp.server.domain.user.entity.UserType; import com.swyp.server.global.SoftDeletableEntity;