diff --git a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/MonthlyReportController.java b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/MonthlyReportController.java index 0c2ca90..481d209 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/MonthlyReportController.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/MonthlyReportController.java @@ -2,6 +2,7 @@ import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MonthlyReportResponse; import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MonthlyReportStartResponse; +import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MyMonthlyReportResponse; import com.devkor.ifive.nadab.domain.monthlyreport.application.MonthlyReportQueryService; import com.devkor.ifive.nadab.domain.monthlyreport.application.MonthlyReportService; import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.CompletedCountResponse; @@ -86,13 +87,18 @@ public ResponseEntity> startMonthlyRe @PreAuthorize("isAuthenticated()") @Operation( summary = "나의 월간 리포트 조회", - description = "사용자의 (지난 달) 월간 리포트를 조회합니다.", + description = """ + 사용자의 (지난 달에 대한) 월간 리포트와 이전 월간 리포트를 조회합니다.
+ 이때 ```report```혹은 ```previousReport```가 ```null```인 경우 해당 주간 리포트가 존재하지 않음을 의미합니다.
+ ```report```혹은 ```previousReport```가 + ```null```이 아닌 경우 ```status```필드는 항상 ```COMPLETED```입니다. + """, security = @SecurityRequirement(name = "bearerAuth"), responses = { @ApiResponse( responseCode = "200", description = "나의 월간 리포트 조회 성공", - content = @Content(schema = @Schema(implementation = MonthlyReportResponse.class), mediaType = "application/json") + content = @Content(schema = @Schema(implementation = MyMonthlyReportResponse.class), mediaType = "application/json") ), @ApiResponse( responseCode = "401", @@ -103,17 +109,15 @@ public ResponseEntity> startMonthlyRe responseCode = "404", description = """ - ErrorCode: USER_NOT_FOUND - 사용자를 찾을 수 없음 - - ErrorCode: MONTHLY_REPORT_NOT_FOUND - 월간 리포트를 찾을 수 없음 - - ErrorCode: MONTHLY_REPORT_NOT_COMPLETED - 해당 월간 리포트가 아직 생성 완료되지 않음 """, content = @Content ) } ) - public ResponseEntity> getLastMonthMonthlyReport( + public ResponseEntity> getMyMonthlyReport( @AuthenticationPrincipal UserPrincipal principal ) { - MonthlyReportResponse response = monthlyReportQueryService.getLastMonthMonthlyReport(principal.getId()); + MyMonthlyReportResponse response = monthlyReportQueryService.getMyMonthlyReport(principal.getId()); return ApiResponseEntity.ok(response); } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/dto/response/MyMonthlyReportResponse.java b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/dto/response/MyMonthlyReportResponse.java new file mode 100644 index 0000000..ad2527c --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/api/dto/response/MyMonthlyReportResponse.java @@ -0,0 +1,14 @@ +package com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "나의 월간 리포트 조회 응답") +public record MyMonthlyReportResponse( + + @Schema(description = "이번 월간 리포트", nullable = true) + MonthlyReportResponse report, + + @Schema(description = "이전 월간 리포트", nullable = true) + MonthlyReportResponse previousReport +) { +} \ No newline at end of file diff --git a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/MonthlyReportQueryService.java b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/MonthlyReportQueryService.java index 8108c7f..841d7f2 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/MonthlyReportQueryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/MonthlyReportQueryService.java @@ -1,6 +1,8 @@ package com.devkor.ifive.nadab.domain.monthlyreport.application; import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MonthlyReportResponse; +import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MyMonthlyReportResponse; +import com.devkor.ifive.nadab.domain.monthlyreport.application.mapper.MonthlyReportMapper; import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReport; import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportStatus; import com.devkor.ifive.nadab.domain.monthlyreport.core.repository.MonthlyReportRepository; @@ -22,25 +24,33 @@ public class MonthlyReportQueryService { private final MonthlyReportRepository monthlyReportRepository; private final UserRepository userRepository; - public MonthlyReportResponse getLastMonthMonthlyReport(Long userId) { + public MyMonthlyReportResponse getMyMonthlyReport(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); MonthRangeDto range = MonthRangeCalculator.getLastMonthRange(); + MonthlyReportResponse reportResponse = + monthlyReportRepository.findByUserIdAndMonthStartDateAndStatus( + user.getId(), + range.monthStartDate(), + MonthlyReportStatus.COMPLETED + ) + .map(report -> MonthlyReportMapper.toResponse(range, report)) + .orElse(null); - MonthlyReport report = monthlyReportRepository.findByUserIdAndMonthStartDate(user.getId(), range.monthStartDate()) - .orElseThrow(() -> new NotFoundException(ErrorCode.MONTHLY_REPORT_NOT_FOUND)); - - if (report.getStatus() != MonthlyReportStatus.COMPLETED) { - throw new NotFoundException(ErrorCode.MONTHLY_REPORT_NOT_COMPLETED); - } + MonthRangeDto prevRange = MonthRangeCalculator.getTwoMonthsAgoRange(); + MonthlyReportResponse prevResponse = + monthlyReportRepository.findByUserIdAndMonthStartDateAndStatus( + user.getId(), + prevRange.monthStartDate(), + MonthlyReportStatus.COMPLETED + ) + .map(report -> MonthlyReportMapper.toResponse(prevRange, report)) + .orElse(null); - return new MonthlyReportResponse( - range.monthStartDate().getMonthValue(), - report.getDiscovered(), - report.getGood(), - report.getImprove(), - report.getStatus().name() + return new MyMonthlyReportResponse( + reportResponse, + prevResponse ); } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/mapper/MonthlyReportMapper.java b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/mapper/MonthlyReportMapper.java new file mode 100644 index 0000000..39a21b5 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/application/mapper/MonthlyReportMapper.java @@ -0,0 +1,20 @@ +package com.devkor.ifive.nadab.domain.monthlyreport.application.mapper; + +import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MonthlyReportResponse; +import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReport; +import com.devkor.ifive.nadab.global.shared.util.dto.MonthRangeDto; + +public final class MonthlyReportMapper { + private MonthlyReportMapper() {} + + public static MonthlyReportResponse toResponse(MonthRangeDto range, MonthlyReport report) { + return new MonthlyReportResponse( + range.monthStartDate().getMonthValue(), + report.getDiscovered(), + report.getGood(), + report.getImprove(), + report.getStatus().name() + ); + } +} + diff --git a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/core/repository/MonthlyReportRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/core/repository/MonthlyReportRepository.java index b6b5b17..c64d8c0 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/core/repository/MonthlyReportRepository.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/monthlyreport/core/repository/MonthlyReportRepository.java @@ -14,6 +14,12 @@ public interface MonthlyReportRepository extends JpaRepository findByUserIdAndMonthStartDate(Long userId, LocalDate monthStartDate); + Optional findByUserIdAndMonthStartDateAndStatus( + Long userId, + LocalDate monthStartDate, + MonthlyReportStatus status + ); + /** * PENDING -> COMPLETED 확정 * - 분석 결과(discovered/good/improve) 저장 diff --git a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/WeeklyReportController.java b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/WeeklyReportController.java index 2e5aa0e..11a55fa 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/WeeklyReportController.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/WeeklyReportController.java @@ -1,6 +1,7 @@ package com.devkor.ifive.nadab.domain.weeklyreport.api; import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.CompletedCountResponse; +import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.MyWeeklyReportResponse; import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.WeeklyReportResponse; import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.WeeklyReportStartResponse; import com.devkor.ifive.nadab.domain.weeklyreport.application.WeeklyReportQueryService; @@ -86,13 +87,18 @@ public ResponseEntity> startWeeklyRepo @PreAuthorize("isAuthenticated()") @Operation( summary = "나의 주간 리포트 조회", - description = "사용자의 (지난 주) 주간 리포트를 조회합니다.", + description = """ + 사용자의 (지난 주에 대한) 주간 리포트와 이전 주간 리포트를 조회합니다.
+ 이때 ```report```혹은 ```previousReport```가 ```null```인 경우 해당 주간 리포트가 존재하지 않음을 의미합니다.
+ ```report```혹은 ```previousReport```가 + ```null```이 아닌 경우 ```status```필드는 항상 ```COMPLETED```입니다. + """, security = @SecurityRequirement(name = "bearerAuth"), responses = { @ApiResponse( responseCode = "200", description = "나의 주간 리포트 조회 성공", - content = @Content(schema = @Schema(implementation = WeeklyReportResponse.class), mediaType = "application/json") + content = @Content(schema = @Schema(implementation = MyWeeklyReportResponse.class), mediaType = "application/json") ), @ApiResponse( responseCode = "401", @@ -103,17 +109,15 @@ public ResponseEntity> startWeeklyRepo responseCode = "404", description = """ - ErrorCode: USER_NOT_FOUND - 사용자를 찾을 수 없음 - - ErrorCode: WEEKLY_REPORT_NOT_FOUND - 주간 리포트를 찾을 수 없음 - - ErrorCode: WEEKLY_REPORT_NOT_COMPLETED - 해당 주간 리포트가 아직 생성 완료되지 않음 """, content = @Content ) } ) - public ResponseEntity> getLastWeekWeeklyReport( + public ResponseEntity> getMyWeeklyReport( @AuthenticationPrincipal UserPrincipal principal ) { - WeeklyReportResponse response = weeklyReportQueryService.getLastWeekWeeklyReport(principal.getId()); + MyWeeklyReportResponse response = weeklyReportQueryService.getMyWeeklyReport(principal.getId()); return ApiResponseEntity.ok(response); } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/dto/response/MyWeeklyReportResponse.java b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/dto/response/MyWeeklyReportResponse.java new file mode 100644 index 0000000..835ddf4 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/api/dto/response/MyWeeklyReportResponse.java @@ -0,0 +1,14 @@ +package com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "나의 주간 리포트 조회 응답") +public record MyWeeklyReportResponse( + + @Schema(description = "이번 주간 리포트", nullable = true) + WeeklyReportResponse report, + + @Schema(description = "이전 주간 리포트", nullable = true) + WeeklyReportResponse previousReport +) { +} diff --git a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/WeeklyReportQueryService.java b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/WeeklyReportQueryService.java index fa48203..5c55662 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/WeeklyReportQueryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/WeeklyReportQueryService.java @@ -2,7 +2,9 @@ import com.devkor.ifive.nadab.domain.user.core.entity.User; import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository; +import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.MyWeeklyReportResponse; import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.WeeklyReportResponse; +import com.devkor.ifive.nadab.domain.weeklyreport.application.mapper.WeeklyReportMapper; import com.devkor.ifive.nadab.domain.weeklyreport.core.entity.WeeklyReport; import com.devkor.ifive.nadab.domain.weeklyreport.core.entity.WeeklyReportStatus; import com.devkor.ifive.nadab.domain.weeklyreport.core.repository.WeeklyReportRepository; @@ -22,27 +24,32 @@ public class WeeklyReportQueryService { private final WeeklyReportRepository weeklyReportRepository; private final UserRepository userRepository; - public WeeklyReportResponse getLastWeekWeeklyReport(Long userId) { + public MyWeeklyReportResponse getMyWeeklyReport(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); WeekRangeDto range = WeekRangeCalculator.getLastWeekRange(); + WeeklyReportResponse reportResponse = + weeklyReportRepository.findByUserIdAndWeekStartDateAndStatus( + user.getId(), + range.weekStartDate(), + WeeklyReportStatus.COMPLETED + ). + map(report -> WeeklyReportMapper.toResponse(range, report)) + .orElse(null); - WeeklyReport report = weeklyReportRepository.findByUserAndWeekStartDate(user, range.weekStartDate()) - .orElseThrow(() -> new NotFoundException(ErrorCode.WEEKLY_REPORT_NOT_FOUND)); - if (report.getStatus() != WeeklyReportStatus.COMPLETED) { - throw new NotFoundException(ErrorCode.WEEKLY_REPORT_NOT_COMPLETED); - } + WeekRangeDto prevRange = WeekRangeCalculator.getTwoWeeksAgoRange(); + WeeklyReportResponse prevReportResponse = + weeklyReportRepository.findByUserIdAndWeekStartDateAndStatus( + user.getId(), + prevRange.weekStartDate(), + WeeklyReportStatus.COMPLETED + ) + .map(report -> WeeklyReportMapper.toResponse(prevRange, report)) + .orElse(null); - return new WeeklyReportResponse( - range.weekStartDate().getMonthValue(), - WeekRangeCalculator.getWeekOfMonth(range), - report.getDiscovered(), - report.getGood(), - report.getImprove(), - report.getStatus().name() - ); + return new MyWeeklyReportResponse(reportResponse, prevReportResponse); } public WeeklyReportResponse getWeeklyReportById(Long id) { diff --git a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/mapper/WeeklyReportMapper.java b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/mapper/WeeklyReportMapper.java new file mode 100644 index 0000000..d1ea6c7 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/application/mapper/WeeklyReportMapper.java @@ -0,0 +1,22 @@ +package com.devkor.ifive.nadab.domain.weeklyreport.application.mapper; + +import com.devkor.ifive.nadab.domain.weeklyreport.api.dto.response.WeeklyReportResponse; +import com.devkor.ifive.nadab.domain.weeklyreport.core.entity.WeeklyReport; +import com.devkor.ifive.nadab.global.shared.util.WeekRangeCalculator; +import com.devkor.ifive.nadab.global.shared.util.dto.WeekRangeDto; + +public final class WeeklyReportMapper { + private WeeklyReportMapper() {} + + public static WeeklyReportResponse toResponse(WeekRangeDto range, WeeklyReport report) { + return new WeeklyReportResponse( + range.weekStartDate().getMonthValue(), + WeekRangeCalculator.getWeekOfMonth(range), + report.getDiscovered(), + report.getGood(), + report.getImprove(), + report.getStatus().name() + ); + } +} + diff --git a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/core/repository/WeeklyReportRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/core/repository/WeeklyReportRepository.java index a0c7ef4..379a8e8 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/core/repository/WeeklyReportRepository.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/weeklyreport/core/repository/WeeklyReportRepository.java @@ -19,6 +19,12 @@ public interface WeeklyReportRepository extends JpaRepository findByUserAndWeekStartDate(User user, LocalDate weekStartDate); + Optional findByUserIdAndWeekStartDateAndStatus( + Long userId, + LocalDate weekStartDate, + WeeklyReportStatus status + ); + /** * PENDING -> COMPLETED 확정 * - 분석 결과(discovered/good/improve) 저장 diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/util/MonthRangeCalculator.java b/src/main/java/com/devkor/ifive/nadab/global/shared/util/MonthRangeCalculator.java index db7be2e..d179429 100644 --- a/src/main/java/com/devkor/ifive/nadab/global/shared/util/MonthRangeCalculator.java +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/util/MonthRangeCalculator.java @@ -32,6 +32,15 @@ public static MonthRangeDto getLastMonthRange() { return monthRangeOf(lastMonthDate); } + /** + * KST 기준 "2달 전" 범위를 반환합니다. + */ + public static MonthRangeDto getTwoMonthsAgoRange() { + LocalDate today = LocalDate.now(KST); + LocalDate lastMonthDate = today.minusMonths(2); + return monthRangeOf(lastMonthDate); + } + /** * KST 기준 "이번 달" 범위를 반환합니다. */ diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/util/WeekRangeCalculator.java b/src/main/java/com/devkor/ifive/nadab/global/shared/util/WeekRangeCalculator.java index 26eda8f..eb72771 100644 --- a/src/main/java/com/devkor/ifive/nadab/global/shared/util/WeekRangeCalculator.java +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/util/WeekRangeCalculator.java @@ -32,6 +32,15 @@ public static WeekRangeDto getLastWeekRange() { return weekRangeOf(lastWeekDate); } + /** + * 월요일 시작 ~ 일요일 종료 기준의 "2주 전" 범위를 반환합니다. + */ + public static WeekRangeDto getTwoWeeksAgoRange() { + LocalDate today = LocalDate.now(KST); + LocalDate twoWeeksAgoDate = today.minusWeeks(2); + return weekRangeOf(twoWeeksAgoDate); + } + /** * 주어진 WeekRange가 해당 월의 몇 주차인지 반환합니다. * (월요일 시작 기준, 해당 주의 월요일이 속한 달 기준)