Skip to content

Commit

Permalink
[Feature/#23] 하위 목표 모두 성공시 보상 제공 및 팝업 기능 구현 (#25)
Browse files Browse the repository at this point in the history
* feat: 상위 목표 d-day 기능 구현, 테스트 작성 (#6)

- 상위 목표 종료 일자 필수 입력으로 확정 후, d-day 계산 로직 plan 엔티티내 생성 후 테스트
- planService에 상위 목표 단건 조회 로직 추가

* 간격 수정

* feat: 보상 제공 기능 구현 (#23)

* feat: 상위 목표 달성 시 팝업 기능 구현 (#23)

- 랜덤으로 보석 지급 기능 구현
- 상위 목표 리스트 조회 시 보석 enum도 조회 추가
- 보석 지급과 함께 complete 상태의 상위 목표 개수 조회

* test: 상위 목표 성공 시 기능 테스트 작성 (#23)

* refactor: 상위 목표 개수 반환값 및 주석 수정
  • Loading branch information
jemlog authored Aug 20, 2023
1 parent 55d72f7 commit 8de9f16
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
import com.backend.detailgoal.presentation.dto.request.DetailGoalUpdateRequest;
import com.backend.goal.domain.Goal;
import com.backend.goal.domain.GoalRepository;
import com.backend.goal.domain.GoalStatus;
import com.backend.goal.domain.RewardType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
Expand All @@ -21,8 +24,12 @@
public class DetailGoalService {

private final DetailGoalRepository detailGoalRepository;

private final GoalRepository goalRepository;

private final RewardService rewardService;



public List<DetailGoalListResponse> getDetailGoalList(Long goalId)
{
Expand Down Expand Up @@ -62,7 +69,19 @@ public GoalCompletedResponse removeDetailGoal(Long detailGoalId)
goal.decreaseCompletedDetailGoalCnt();
}

return new GoalCompletedResponse(goal.checkGoalCompleted());
boolean isCompleted = goal.checkGoalCompleted();

if(isCompleted)
{
RewardType reward = rewardService.provideReward();
goal.achieveReward(reward);
goal.complete();

int count = goalRepository.countByGoalStatusAndIsDeletedFalse(GoalStatus.COMPLETE);
return new GoalCompletedResponse(isCompleted, goal.getReward(), count);
}

return new GoalCompletedResponse(isCompleted, goal.getReward(), 0);
}

@Transactional
Expand All @@ -79,13 +98,25 @@ public DetailGoal updateDetailGoal(Long detailGoalId, DetailGoalUpdateRequest de
@Transactional
public GoalCompletedResponse completeDetailGoal(Long detailGoalId)
{
DetailGoal detailGoal = detailGoalRepository.getByIdAndIsDeletedFalse(detailGoalId);
detailGoal.complete();
DetailGoal detailGoal = detailGoalRepository.getByIdAndIsDeletedFalse(detailGoalId); // 1. 삭제되지 않은 하위 목표 가져온다
detailGoal.complete(); // 2. 하위 목표를 완료 상태로 변경한다.

Goal goal = goalRepository.getByIdAndIsDeletedFalse(detailGoal.getGoalId());
goal.increaseCompletedDetailGoalCnt(); // 성공한 개수 체크
Goal goal = goalRepository.getByIdAndIsDeletedFalse(detailGoal.getGoalId()); // 3. 전체 목표를 가져온다.
goal.increaseCompletedDetailGoalCnt(); // 4. 완료한 하위 목표 개수를 증가시킨다.

boolean isCompleted = goal.checkGoalCompleted(); // 5. 전체 하위 목표 개수와 완료한 하위 목표 개수가 같은지 체크한다.

if(isCompleted)
{
RewardType reward = rewardService.provideReward(); // 6. 리워드를 랜덤으로 지급한다.
goal.achieveReward(reward);
goal.complete();

int count = goalRepository.countByGoalStatusAndIsDeletedFalse(GoalStatus.COMPLETE);
return new GoalCompletedResponse(isCompleted, goal.getReward(), count);
}

return new GoalCompletedResponse(goal.checkGoalCompleted());
return new GoalCompletedResponse(isCompleted, goal.getReward(), 0);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.backend.detailgoal.application;

import com.backend.goal.domain.RewardType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Random;

@Service
@RequiredArgsConstructor
public class RewardService {

private Random random = new Random();
private static final int NUM_GEM_TYPES = RewardType.values().length;

public RewardType provideReward()
{
int randomIndex = random.nextInt(NUM_GEM_TYPES);
return RewardType.values()[randomIndex];
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
package com.backend.detailgoal.application.dto.response;

public record GoalCompletedResponse(Boolean isGoalCompleted) {
import com.backend.goal.domain.RewardType;

public record GoalCompletedResponse(
Boolean isGoalCompleted,
RewardType rewardType,
Integer completedGoalCount

) {
}
16 changes: 16 additions & 0 deletions src/main/java/com/backend/goal/domain/Goal.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,19 @@ public class Goal extends BaseEntity {
@Column(name = "is_deleted", nullable = false)
private Boolean isDeleted;

@Column(name = "reward")
private RewardType reward;

public void remove()
{
this.isDeleted = Boolean.TRUE;
}

public void writeRetrospect()
{
this.hasRetrospect = Boolean.TRUE;
}


@PrePersist
private void init()
Expand All @@ -73,6 +81,10 @@ private void init()
completedDetailGoalCnt = 0;
}

public void complete()
{
this.goalStatus = GoalStatus.COMPLETE;
}

public void increaseEntireDetailGoalCnt()
{
Expand Down Expand Up @@ -116,6 +128,10 @@ public boolean checkGoalCompleted()
return completedDetailGoalCnt == entireDetailGoalCnt;
}

public void achieveReward(RewardType reward)
{
this.reward = reward;
}

public void update(final String title, final LocalDate startDate, final LocalDate endDate, final Boolean reminderEnabled)
{
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/com/backend/goal/domain/GoalListResponseDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@
import java.time.LocalDate;

public record GoalListResponseDto(

Long goalId,

String title,

LocalDate startDate,

LocalDate endDate,

Integer entireDetailGoalCnt,

Integer completedDetailGoalCnt,

Long dDay,
Boolean hasRetrospect

Boolean hasRetrospect,

RewardType reward
) {

public static GoalListResponseDto from(Goal goal)
Expand All @@ -25,7 +35,8 @@ public static GoalListResponseDto from(Goal goal)
goal.getEntireDetailGoalCnt(),
goal.getCompletedDetailGoalCnt(),
goal.calculateDday(LocalDate.now()),
goal.getHasRetrospect()
goal.getHasRetrospect(),
goal.getReward()
)
;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/backend/goal/domain/GoalRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.backend.global.exception.BusinessException;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;


public interface GoalRepository extends JpaRepository<Goal, Long> {
Expand All @@ -14,4 +13,6 @@ default Goal getByIdAndIsDeletedFalse(Long id){
return findById(id).orElseThrow(() -> {throw new BusinessException(ErrorCode.GOAL_NOT_FOUND);
});
}

int countByGoalStatusAndIsDeletedFalse(GoalStatus goalStatus);
}
25 changes: 25 additions & 0 deletions src/main/java/com/backend/goal/domain/RewardType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.backend.goal.domain;

public enum RewardType {
BLUE_JEWEL_1,
BLUE_JEWEL_2,
BLUE_JEWEL_3,
BLUE_JEWEL_4,
BLUE_JEWEL_5,
PURPLE_JEWEL_1,
PURPLE_JEWEL_2,
PURPLE_JEWEL_3,
PURPLE_JEWEL_4,
PURPLE_JEWEL_5,
PINK_JEWEL_1,
PINK_JEWEL_2,
PINK_JEWEL_3,
PINK_JEWEL_4,
PINK_JEWEL_5,
GREEN_JEWEL_1,
GREEN_JEWEL_2,
GREEN_JEWEL_3,
GREEN_JEWEL_4,
GREEN_JEWEL_5;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.backend.goal.domain.GoalStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
Expand Down Expand Up @@ -208,26 +209,79 @@ void setUp() {
assertThat(goalCompletedResponse.isGoalCompleted()).isTrue();
}

@DisplayName("하위 목표를 삭제했을때 전체 하위 목표 개수와 달성한 하위 목표 개수가 같아지면 상위 목표가 성공한다")
@Test
void 하위목표를_삭제했을때_전체_하위목표_개수와_달성한_하위목표_개수가_같아지면_상위목표가_성공한다()
{
// given
Goal goal = new Goal(1L, "테스트 제목", LocalDate.of(2023, 8, 20), LocalDate.of(2023, 8, 24), true, GoalStatus.PROCESS);
Goal savedGoal = goalRepository.save(goal);
@Nested
@DisplayName("하위 목표를 삭제했을때 하위목표 개수와 달성한하위목표 개수가 같으면")
class 하위목표를_삭제했을때_하위목표개수와_달성한_하위목표_개수가_같으면{

DetailGoalSaveRequest detailGoalSaveRequest = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest);
@DisplayName("상위 목표가 성공한다")
@Test
void 상위목표가_성공한다()
{
// given
Goal goal = new Goal(1L, "테스트 제목", LocalDate.of(2023, 8, 20), LocalDate.of(2023, 8, 24), true, GoalStatus.PROCESS);
Goal savedGoal = goalRepository.save(goal);

DetailGoalSaveRequest detailGoalSaveRequest2 = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal1 = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest2);
DetailGoalSaveRequest detailGoalSaveRequest = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest);

// when
GoalCompletedResponse beforeRemoveResponse = detailGoalService.completeDetailGoal(detailGoal.getId());
GoalCompletedResponse afterRemovedResponse = detailGoalService.removeDetailGoal(detailGoal1.getId());
DetailGoalSaveRequest detailGoalSaveRequest2 = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal1 = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest2);

// then
assertThat(beforeRemoveResponse.isGoalCompleted()).isFalse();
assertThat(afterRemovedResponse.isGoalCompleted()).isTrue();
// when
GoalCompletedResponse beforeRemoveResponse = detailGoalService.completeDetailGoal(detailGoal.getId());
GoalCompletedResponse afterRemovedResponse = detailGoalService.removeDetailGoal(detailGoal1.getId());

// then
assertThat(beforeRemoveResponse.isGoalCompleted()).isFalse();
assertThat(afterRemovedResponse.isGoalCompleted()).isTrue();
}

@DisplayName("보석을 지급한다")
@Test
void 보석을_지급한다()
{
// given
Goal goal = new Goal(1L, "테스트 제목", LocalDate.of(2023, 8, 20), LocalDate.of(2023, 8, 24), true, GoalStatus.PROCESS);
Goal savedGoal = goalRepository.save(goal);

DetailGoalSaveRequest detailGoalSaveRequest = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest);

DetailGoalSaveRequest detailGoalSaveRequest2 = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal1 = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest2);

// when
detailGoalService.completeDetailGoal(detailGoal.getId());
GoalCompletedResponse afterRemovedResponse = detailGoalService.removeDetailGoal(detailGoal1.getId());

// then
assertThat(afterRemovedResponse.rewardType()).isNotNull();
}

@DisplayName("지금까지 성공한 상위목표 개수를 반환한다")
@Test
void 지금까지_성공한_상위목표_개수를_반환한다()
{
// given
Goal goal = new Goal(1L, "테스트 제목", LocalDate.of(2023, 8, 20), LocalDate.of(2023, 8, 24), true, GoalStatus.PROCESS);
Goal savedGoal = goalRepository.save(goal);

DetailGoalSaveRequest detailGoalSaveRequest = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest);

DetailGoalSaveRequest detailGoalSaveRequest2 = new DetailGoalSaveRequest("테스트 제목", true, LocalTime.of(10, 0), List.of("MONDAY", "TUESDAY"));
DetailGoal detailGoal1 = detailGoalService.saveDetailGoal(savedGoal.getId(), detailGoalSaveRequest2);

// when
detailGoalService.completeDetailGoal(detailGoal.getId());
GoalCompletedResponse afterRemovedResponse = detailGoalService.removeDetailGoal(detailGoal1.getId());

// then
assertThat(afterRemovedResponse.completedGoalCount()).isEqualTo(1);
}
}




}
36 changes: 34 additions & 2 deletions src/test/java/com/backend/goal/domain/GoalTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.backend.goal.domain;


import com.backend.global.common.code.ErrorCode;
import com.backend.global.exception.BusinessException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.DayOfWeek;
import java.time.LocalDate;

import static org.assertj.core.api.Assertions.*;
Expand Down Expand Up @@ -114,4 +114,36 @@ public class GoalTest {
// then
assertThat(dDay).isEqualTo(0L);
}

@DisplayName("현재 보관함에 있는 상태라면 상위 목표를 복구할 수 있다")
@Test
void 현재_보관함에_있는_상태라면_상위목표를_복구할수_있다()
{
// given
LocalDate startDate = LocalDate.of(2023,7,1);
LocalDate endDate = LocalDate.of(2023,8,10);
Goal goal = new Goal(1L, "테스트 제목", startDate, endDate, true, GoalStatus.STORE);

// when
goal.recover(LocalDate.of(2023,7,3), LocalDate.of(2023,8,10),false);

// then
assertThat(goal.getGoalStatus()).isEqualTo(GoalStatus.PROCESS);
assertThat(goal.getReminderEnabled()).isFalse();
}

@DisplayName("목표가 현재 보관함에 없다면 복구시 예외가 발생한다")
@Test
void 목표가_현재_보관함에_없다면_예외가_발생한다()
{
// given
LocalDate startDate = LocalDate.of(2023,7,1);
LocalDate endDate = LocalDate.of(2023,8,10);
Goal goal = new Goal(1L, "테스트 제목", startDate, endDate, true, GoalStatus.PROCESS);

// when & then
assertThatThrownBy(() -> {
goal.recover(LocalDate.of(2023,7,3), LocalDate.of(2023,8,10),false);}
).isInstanceOf(BusinessException.class).hasMessage(ErrorCode.RECOVER_GOAL_IMPOSSIBLE.getMessage());
}
}

0 comments on commit 8de9f16

Please sign in to comment.