Skip to content

Commit

Permalink
[Feature/#21] 보관함 복구 기능, 회고 개수 조회 기능 구현 (#22)
Browse files Browse the repository at this point in the history
* feat: 상위 목표 d-day 기능 구현, 테스트 작성 (#6)

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

* 간격 수정

* feat: 상위 목표 복구 기능 구현 (#21)

* refactor: transactional 어노테이션 추가

* feat: LocalTime 요청 응답 값 오전/오후 반영하도록 변경 (#21)

* feat: 상위 목표 제약 조건 수정 (#21)

- 현재 보다 이전 날짜를 시작일로 선택 불가능하다는 조건이 없기에 조건 제거
- 보관함에서 복구 시 시작, 종료 날짜를 초기화 예정이기에 nullable을 true로 변경

* feat: 상위 목표 복구 기능 수정 (#21)

- 복구 시 새로운 데이터로 초기화해야 복구가 완료 되도록 설정

* feat: 회고 작성 가능한 완료 목표 개수 조회 (#21)

* feat: goal 생성시, goalstatus 생성자 주입으로 변경

- goalstatus 값을 변경해서 테스트 해야 할 경우들이 있다고 판단되어, 테스트 코드 편의성과 changeGoalStatus라는 setter 메서드를 제거하기 위해 작업을 진행함

* test: 테스트 작성 완료

* refactor: GoalController내 위치 변경

* application.yml 제거

* refactor: goalController 공통 URI 통일
  • Loading branch information
jemlog authored Aug 19, 2023
1 parent 765a0b1 commit 55d72f7
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class DetailGoalService {

public List<DetailGoalListResponse> getDetailGoalList(Long goalId)
{
List<DetailGoal> detailGoalList = detailGoalRepository.findDetailGoalsByGoalIdAndIsDeletedFalse(goalId);
List<DetailGoal> detailGoalList = detailGoalRepository.findAllByGoalIdAndIsDeletedFalse(goalId);
return detailGoalList.stream().map(DetailGoalListResponse::from).collect(Collectors.toList());
}

Expand All @@ -44,7 +44,7 @@ public DetailGoal saveDetailGoal(Long goalId, DetailGoalSaveRequest detailGoalSa
detailGoalRepository.save(detailGoal);

Goal goal = goalRepository.getByIdAndIsDeletedFalse(goalId);
goal.increaseEntireDetailGoalCnt();
goal.increaseEntireDetailGoalCnt(); // 전체 하위 목표 개수 증가
return detailGoal;
}

Expand All @@ -55,7 +55,12 @@ public GoalCompletedResponse removeDetailGoal(Long detailGoalId)
detailGoal.remove();

Goal goal = goalRepository.getByIdAndIsDeletedFalse(detailGoal.getGoalId());
goal.decreaseEntireDetailGoalCnt();
goal.decreaseEntireDetailGoalCnt(); // 전체 하위 목표 감소

if(detailGoal.getIsCompleted()) // 만약 이미 성취된 목표였다면, 성취된 목표 개수까지 함께 제거
{
goal.decreaseCompletedDetailGoalCnt();
}

return new GoalCompletedResponse(goal.checkGoalCompleted());
}
Expand All @@ -76,13 +81,14 @@ public GoalCompletedResponse completeDetailGoal(Long detailGoalId)
{
DetailGoal detailGoal = detailGoalRepository.getByIdAndIsDeletedFalse(detailGoalId);
detailGoal.complete();

Goal goal = goalRepository.getByIdAndIsDeletedFalse(detailGoal.getGoalId());
goal.increaseCompletedDetailGoalCnt();
goal.increaseCompletedDetailGoalCnt(); // 성공한 개수 체크

return new GoalCompletedResponse(goal.checkGoalCompleted());
}



@Transactional
public void inCompleteDetailGoal(Long detailGoalId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public record DetailGoalResponse(

String title,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm", timezone = "Asia/Seoul")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "a KK:mm", timezone = "Asia/Seoul")
LocalTime alarmTime,

List<DayOfWeek> alarmDays,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

public interface DetailGoalRepository extends JpaRepository<DetailGoal, Long> {

List<DetailGoal> findDetailGoalsByGoalIdAndIsDeletedFalse(Long goalId);
List<DetailGoal> findAllByGoalIdAndIsDeletedFalse(Long goalId);

default DetailGoal getByIdAndIsDeletedFalse(Long detailGoalId){

return findById(detailGoalId).orElseThrow(() -> new BusinessException(ErrorCode.DETAIL_GOAL_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.backend.detailgoal.presentation.dto.request.DetailGoalUpdateRequest;
import com.backend.global.common.response.CustomResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,52 +23,52 @@ public class DetailGoalController {

@Operation(summary = "하위 목표 리스트 조회", description = "하위 목표 리스트를 조회하는 API 입니다.")
@GetMapping("/goals/{id}/detail-goals")
public ResponseEntity<CustomResponse> getDetailGoalList(@PathVariable Long id)
public ResponseEntity<CustomResponse> getDetailGoalList(@Parameter(description = "상위 목표 ID") @PathVariable Long id)
{
return CustomResponse.success(SELECT_SUCCESS, detailGoalService.getDetailGoalList(id));
}

@Operation(summary = "하위 목표 상세 조회", description = "하위 목표를 상세 조회하는 API 입니다.")
@GetMapping("/detail-goals/{id}")
public ResponseEntity<CustomResponse> getDetailGoal(@PathVariable Long id)
public ResponseEntity<CustomResponse> getDetailGoal(@Parameter(description = "하위 목표 ID") @PathVariable Long id)
{
return CustomResponse.success(SELECT_SUCCESS, detailGoalService.getDetailGoal(id));
}

@Operation(summary = "하위 목표 리스트 생성", description = "하위 목표를 생성하는 API 입니다.")
@Operation(summary = "하위 목표 생성", description = "하위 목표를 생성하는 API 입니다.")
@PostMapping("/goals/{id}/detail-goals")
public ResponseEntity<CustomResponse> saveDetailGoal(@PathVariable Long id, @RequestBody @Valid DetailGoalSaveRequest detailGoalSaveRequest)
public ResponseEntity<CustomResponse> saveDetailGoal(@Parameter(description = "상위 목표 ID") @PathVariable Long id, @RequestBody @Valid DetailGoalSaveRequest detailGoalSaveRequest)
{
detailGoalService.saveDetailGoal(id, detailGoalSaveRequest);
return CustomResponse.success(INSERT_SUCCESS);
}

@Operation(summary = "하위 목표 수정", description = "하위 목표를 수정하는 API 입니다.")
@PatchMapping("/detail-goals/{id}")
public ResponseEntity<CustomResponse> updateDetailGoal(@PathVariable Long id, @RequestBody @Valid DetailGoalUpdateRequest detailGoalUpdateRequest)
public ResponseEntity<CustomResponse> updateDetailGoal(@Parameter(description = "하위 목표 ID") @PathVariable Long id, @RequestBody @Valid DetailGoalUpdateRequest detailGoalUpdateRequest)
{
detailGoalService.updateDetailGoal(id, detailGoalUpdateRequest);
return CustomResponse.success(UPDATE_SUCCESS);
}

@Operation(summary = "하위 목표 삭제", description = "하위 목표를 삭제하는 API 입니다.")
@DeleteMapping("/detail-goals/{id}")
public ResponseEntity<CustomResponse> removeDetailGoal(@PathVariable Long id)
public ResponseEntity<CustomResponse> removeDetailGoal(@Parameter(description = "하위 목표 ID") @PathVariable Long id)
{

return CustomResponse.success(DELETE_SUCCESS, detailGoalService.removeDetailGoal(id));
}

@Operation(summary = "하위 목표 달성", description = "하위 목표를 달성하는 API 입니다.")
@PatchMapping("/detail-goals/{id}/complete")
public ResponseEntity<CustomResponse> completeDetailGoal(@PathVariable Long id)
public ResponseEntity<CustomResponse> completeDetailGoal(@Parameter(description = "하위 목표 ID") @PathVariable Long id)
{
return CustomResponse.success(UPDATE_SUCCESS, detailGoalService.completeDetailGoal(id));
}

@Operation(summary = "하위 목표 달성 취소", description = "하위 목표 달성을 취소하는 API 입니다.")
@PatchMapping("/detail-goals/{id}/incomplete")
public ResponseEntity<CustomResponse> incompleteDetailGoal(@PathVariable Long id)
public ResponseEntity<CustomResponse> incompleteDetailGoal(@Parameter(description = "하위 목표 ID") @PathVariable Long id)
{
detailGoalService.inCompleteDetailGoal(id);
return CustomResponse.success(UPDATE_SUCCESS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.backend.detailgoal.domain.DetailGoal;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

Expand All @@ -13,14 +14,18 @@
public record DetailGoalSaveRequest(

@Size(max = 15, message = "상위 목표 제목은 15자를 초과할 수 없습니다.")
@Schema(description = "하위 목표 제목", example = "오픽 노잼 IH 시리즈 보기")
String title,

@NotNull(message = "알림 설정 여부는 빈값일 수 없습니다.")
@Schema(description = "알람 수행 여부")
Boolean alarmEnabled,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm", timezone = "Asia/Seoul")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "a KK:mm", timezone = "Asia/Seoul")
@Schema(description = "알람 받을 시각", example = "오후 11:30")
LocalTime alarmTime,

@Schema(description = "요일 정보", examples = {"MONDAY", "TUESDAY", "FRIDAY"})
List<String> alarmDays
) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.backend.detailgoal.presentation.dto.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

Expand All @@ -11,14 +12,18 @@ public record DetailGoalUpdateRequest(


@Size(max = 15, message = "상위 목표 제목은 15자를 초과할 수 없습니다.")
@Schema(description = "하위 목표 제목", example = "오픽 노잼 IH 영상 보기")
String title,

@NotNull(message = "알림 설정 여부는 빈값일 수 없습니다.")
@Schema(description = "알람 수행 여부")
Boolean alarmEnabled,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm", timezone = "Asia/Seoul")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "a KK:mm", timezone = "Asia/Seoul")
@Schema(description = "알람 받을 시각", example = "오후 11:30")
LocalTime alarmTime,

@Schema(description = "요일 정보", example = "MONDAY, TUESDAY, FRIDAY")
List<String> alarmDays
) {
}
2 changes: 2 additions & 0 deletions src/main/java/com/backend/global/common/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public enum ErrorCode {

COMPLETED_DETAIL_GOAL_CNT_INVALID(BAD_REQUEST.value(), "GOAL-003", "성공한 하위목표 개수가 0개 일때는 뺄 수 없습니다."),

RECOVER_GOAL_IMPOSSIBLE(BAD_REQUEST.value(), "GOAL-004", "상위 목표가 보관함에 있을때 채움함으로 복구할 수 있습니다."),

/* Detail Goal */
DETAIL_GOAL_NOT_FOUND(NOT_FOUND.value(), "DETAIL-GOAL-001", "하위 목표가 존재하지 않습니다."),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class GoalEventHandler {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void removeDetailGoalList(RemoveRelatedDetailGoalEvent event) {

List<DetailGoal> detailGoalList = detailGoalRepository.findDetailGoalsByGoalIdAndIsDeletedFalse(event.goalId());
List<DetailGoal> detailGoalList = detailGoalRepository.findAllByGoalIdAndIsDeletedFalse(event.goalId());
detailGoalList.forEach((DetailGoal::remove));
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/backend/goal/application/GoalService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.backend.goal.application;

import com.backend.detailgoal.application.dto.response.DetailGoalResponse;
import com.backend.detailgoal.domain.DetailGoal;
import com.backend.goal.application.dto.response.GoalCountResponse;
import com.backend.goal.application.dto.response.GoalListResponse;
import com.backend.goal.application.dto.response.RetrospectEnabledGoalCountResponse;
import com.backend.goal.domain.*;
import com.backend.goal.application.dto.response.GoalResponse;
import com.backend.goal.presentation.dto.GoalRecoverRequest;
import com.backend.goal.presentation.dto.GoalSaveRequest;
import com.backend.goal.presentation.dto.GoalUpdateRequest;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -47,6 +51,12 @@ public GoalCountResponse getGoalCounts()
return new GoalCountResponse(statusCounts);
}

public RetrospectEnabledGoalCountResponse getGoalCountRetrospectEnabled()
{
Long count = goalQueryRepository.getGoalCountRetrospectEnabled();
return new RetrospectEnabledGoalCountResponse(count);
}


@Transactional
public Long saveGoal(final Long memberId, final GoalSaveRequest goalSaveRequest)
Expand All @@ -71,4 +81,11 @@ public void removeGoal(Long goalId)

applicationEventPublisher.publishEvent(new RemoveRelatedDetailGoalEvent(goal.getId()));
}

@Transactional
public void recoverGoal(Long goalId, GoalRecoverRequest goalRecoverRequest)
{
Goal goal = goalRepository.getByIdAndIsDeletedFalse(goalId);
goal.recover(goalRecoverRequest.startDate(), goalRecoverRequest.endDate(), goalRecoverRequest.reminderEnabled());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.backend.goal.application.dto.response;

public record RetrospectEnabledGoalCountResponse(
Long count
) {
}
35 changes: 31 additions & 4 deletions src/main/java/com/backend/goal/domain/Goal.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

import static com.backend.global.common.code.ErrorCode.RECOVER_GOAL_IMPOSSIBLE;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down Expand Up @@ -70,9 +71,9 @@ private void init()
hasRetrospect = Boolean.FALSE;
entireDetailGoalCnt = 0;
completedDetailGoalCnt = 0;
goalStatus = GoalStatus.PROCESS;
}


public void increaseEntireDetailGoalCnt()
{
this.entireDetailGoalCnt +=1;
Expand Down Expand Up @@ -106,6 +107,12 @@ public void decreaseCompletedDetailGoalCnt()

public boolean checkGoalCompleted()
{
// 만약 전체 개수가 0개라면 체크 하면 안됨
if (entireDetailGoalCnt == 0)
{
return false;
}

return completedDetailGoalCnt == entireDetailGoalCnt;
}

Expand All @@ -118,7 +125,7 @@ public void update(final String title, final LocalDate startDate, final LocalDat
this.reminderEnabled = reminderEnabled;
}

public Goal(final Long memberId, final String title, final LocalDate startDate, final LocalDate endDate, final Boolean reminderEnabled)
public Goal(final Long memberId, final String title, final LocalDate startDate, final LocalDate endDate, final Boolean reminderEnabled, final GoalStatus goalStatus)
{
validateTitleLength(title);
validatePeriod(startDate, endDate);
Expand All @@ -127,8 +134,23 @@ public Goal(final Long memberId, final String title, final LocalDate startDate,
this.startDate = startDate;
this.endDate = endDate;
this.reminderEnabled = reminderEnabled;
this.goalStatus = goalStatus;
}

public void recover(final LocalDate startDate, final LocalDate endDate, final Boolean reminderEnabled)
{
if(!isRecoveringEnable())
{
throw new BusinessException(RECOVER_GOAL_IMPOSSIBLE);
}

this.goalStatus = GoalStatus.PROCESS;
this.reminderEnabled = reminderEnabled;
this.startDate = startDate;
this.endDate = endDate;
}


private void validateTitleLength(final String title) {

if (title.length() > MAX_TITLE_LENGTH) {
Expand All @@ -137,6 +159,7 @@ private void validateTitleLength(final String title) {
}

private void validatePeriod(final LocalDate startDate, final LocalDate endDate) {

if (startDate.isAfter(endDate)) {
throw new IllegalArgumentException("종료일시가 시작일시보다 이전일 수 없습니다.");
}
Expand All @@ -147,7 +170,7 @@ private void validatePeriod(final LocalDate startDate, final LocalDate endDate)
}
}

public Long calculateDday(LocalDate now)
public Long calculateDday(final LocalDate now)
{
if(now.isAfter(endDate))
{
Expand All @@ -160,4 +183,8 @@ public Long calculateDday(LocalDate now)
private boolean isNotValidDateTimeRange(final LocalDate date) {
return date.isBefore(MIN_DATE) || date.isAfter(MAX_DATE);
}

private boolean isRecoveringEnable() {
return goalStatus.equals(GoalStatus.STORE);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/backend/goal/domain/GoalQueryRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ public Slice<Goal> getGoalList(Long goalId, Pageable pageable, GoalStatus goalSt
return new SliceImpl<>(goalList, pageable, hasNext);
}

public Long getGoalCountRetrospectEnabled()
{
return query.select(goal.count())
.from(goal)
.where(
goal.isDeleted.isFalse(), // 삭제 되지 않은 것들만 조회
goal.hasRetrospect.isFalse(), // 아직 회고를 작성하지 않는 것들 조회
goal.goalStatus.eq(GoalStatus.COMPLETE) // 완료상태인것들 체크
)
.fetchOne();
}

public Map<GoalStatus, Long> getStatusCounts() {


Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/backend/goal/domain/GoalRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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,5 +14,4 @@ default Goal getByIdAndIsDeletedFalse(Long id){
return findById(id).orElseThrow(() -> {throw new BusinessException(ErrorCode.GOAL_NOT_FOUND);
});
}

}
Loading

0 comments on commit 55d72f7

Please sign in to comment.