Skip to content

Commit 0147bc3

Browse files
authored
feat(report): 일간 리포트의 날짜 경계 엣지 케이스 처리 로직 구현 (#108)
* feat(report): 일간 리포트의 날짜 경계 엣지 케이스 처리 로직 구현 n일에 할당된 오늘의 질문에 대한 답변이 n+1일에 제출된 경우 * refactor(report): AnswerEntryService, PendingDailyReportService의 조회 로직 변경 createdAt 시간 범위(Range) 기반 조회 -> 비즈니스 날짜(Date) 필드 기반 조회 * refactor(report): QuestionCommandService의 조회 로직 변경 createdAt 시간 범위(Range) 기반 조회 -> 비즈니스 날짜(Date) 필드 기반 조회
1 parent f90ca83 commit 0147bc3

10 files changed

Lines changed: 35 additions & 59 deletions

File tree

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportQueryService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import com.devkor.ifive.nadab.global.core.response.ErrorCode;
1111
import com.devkor.ifive.nadab.global.exception.NotFoundException;
1212
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
13-
import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto;
1413
import lombok.RequiredArgsConstructor;
1514
import org.springframework.stereotype.Service;
1615
import org.springframework.transaction.annotation.Transactional;
1716

17+
import java.time.LocalDate;
18+
1819

1920
@Service
2021
@RequiredArgsConstructor
@@ -29,12 +30,12 @@ public DailyReportResponse getDailyReport(Long id) {
2930
User user = userRepository.findById(id)
3031
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
3132

32-
TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange();
33+
LocalDate today = TodayDateTimeProvider.getTodayDate();
3334

34-
AnswerEntry entry = answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow())
35+
AnswerEntry entry = answerEntryRepository.findByUserAndDate(user, today)
3536
.orElseThrow(() -> new NotFoundException(ErrorCode.ANSWER_NOT_FOUND));
3637

37-
DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow())
38+
DailyReport report = dailyReportRepository.findByAnswerEntryAndDate(entry, today)
3839
.orElseThrow(() -> new NotFoundException(ErrorCode.DAILY_REPORT_NOT_FOUND));
3940

4041
return new DailyReportResponse(

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportService.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ public CreateDailyReportResponse generateDailyReport(Long userId, DailyReportReq
4444
.orElseThrow(() -> new NotFoundException(ErrorCode.QUESTION_NOT_FOUND));
4545

4646
LocalDate today = TodayDateTimeProvider.getTodayDate();
47+
48+
// 1. 오늘 -> 어제 순서로 조회 (없으면 예외)
4749
UserDailyQuestion udq = userDailyQuestionRepository.findByUserIdAndDate(userId, today)
50+
.or(() -> userDailyQuestionRepository.findByUserIdAndDate(userId, today.minusDays(1)))
4851
.orElseThrow(() -> new NotFoundException(ErrorCode.DAILY_QUESTION_NOT_FOUND));
52+
53+
// 2. 결과 날짜를 비교하여 플래그 설정
54+
boolean isDayPassed = !udq.getDate().isEqual(today);
55+
4956
if (!udq.getDailyQuestion().getId().equals(request.questionId())) {
5057
throw new BadRequestException(ErrorCode.DAILY_QUESTION_MISMATCH);
5158
}
5259

53-
PrepareDailyResultDto prep = dailyReportTxService.prepareDaily(user, question, request.answer());
60+
PrepareDailyResultDto prep = dailyReportTxService.prepareDaily(user, question, request.answer(), isDayPassed);
5461

5562
AnswerEntry answerEntry = prep.entry();
5663

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportTxService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ public class DailyReportTxService {
4242

4343
private static final long DAILY_REPORT_REWARD = 10L;
4444

45-
protected PrepareDailyResultDto prepareDaily(User user, DailyQuestion dq, String answerText) {
45+
protected PrepareDailyResultDto prepareDaily(User user, DailyQuestion dq, String answerText, boolean isDayPassed) {
4646

4747
// AnswerEntry 생성 또는 조회 (별도의 트랜잭션)
48-
AnswerEntry entry = answerEntryService.getOrCreateTodayAnswerEntry(user, dq, answerText);
48+
AnswerEntry entry = answerEntryService.getOrCreateTodayAnswerEntry(user, dq, answerText, isDayPassed);
4949

5050
// DailyReport PENDING 생성 또는 조회 (별도의 트랜잭션)
51-
DailyReport report = pendingDailyReportService.getOrCreatePendingDailyReport(entry);
51+
DailyReport report = pendingDailyReportService.getOrCreatePendingDailyReport(entry, isDayPassed);
5252

5353
return new PrepareDailyResultDto(entry, report.getId(), user.getId());
5454
}

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/AnswerEntryRepository.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.data.jpa.repository.Query;
77
import org.springframework.data.repository.query.Param;
88

9+
import java.time.LocalDate;
910
import java.time.OffsetDateTime;
1011
import java.util.Optional;
1112

@@ -22,7 +23,7 @@ boolean existsActiveAnswer(
2223
@Param("questionId") Long questionId
2324
);
2425

25-
Optional<AnswerEntry> findByUserAndCreatedAtBetween(User user, OffsetDateTime start, OffsetDateTime end);
26+
Optional<AnswerEntry> findByUserAndDate(User user, LocalDate date);
2627

27-
boolean existsByUserAndCreatedAtBetween(User user, OffsetDateTime start, OffsetDateTime end);
28+
boolean existsByUserAndDate(User user, LocalDate date);
2829
}

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/DailyReportRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.devkor.ifive.nadab.domain.dailyreport.core.entity.DailyReport;
77
import com.devkor.ifive.nadab.domain.dailyreport.core.entity.DailyReportStatus;
88
import com.devkor.ifive.nadab.domain.user.core.entity.InterestCode;
9+
import com.devkor.ifive.nadab.domain.user.core.entity.User;
910
import org.springframework.data.jpa.repository.JpaRepository;
1011
import org.springframework.data.jpa.repository.Modifying;
1112
import org.springframework.data.jpa.repository.Query;
@@ -37,7 +38,7 @@ public interface DailyReportRepository extends JpaRepository<DailyReport, Long>
3738
""")
3839
int markFailed(@Param("reportId") Long reportId);
3940

40-
Optional<DailyReport> findByAnswerEntryAndCreatedAtBetween(AnswerEntry answerEntry, OffsetDateTime start, OffsetDateTime end);
41+
Optional<DailyReport> findByAnswerEntryAndDate(AnswerEntry answerEntry, LocalDate date);
4142

4243
/**
4344
* 특정 유저의 주간 범위 내 COMPLETED 일간 리포트 개수를 반환합니다.

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion;
66
import com.devkor.ifive.nadab.domain.user.core.entity.User;
77
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
8-
import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto;
98
import lombok.RequiredArgsConstructor;
109
import org.springframework.dao.DataIntegrityViolationException;
1110
import org.springframework.stereotype.Service;
@@ -22,20 +21,18 @@ public class AnswerEntryService {
2221

2322

2423
@Transactional
25-
public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, String answerText) {
24+
public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, String answerText, boolean isDayPassed) {
2625

27-
TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange();
26+
LocalDate targetDate =
27+
isDayPassed ? TodayDateTimeProvider.getTodayDate().minusDays(1) : TodayDateTimeProvider.getTodayDate();
2828

29-
LocalDate today = TodayDateTimeProvider.getTodayDate();
30-
31-
return answerEntryRepository.
32-
findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow())
29+
return answerEntryRepository.findByUserAndDate(user, targetDate)
3330
.orElseGet(() -> {
3431
try {
35-
return answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, today));
32+
return answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, targetDate));
3633
} catch (DataIntegrityViolationException e) {
3734
// 동시 요청에서 이미 누가 만들었을 수 있음 -> 재조회로 멱등 처리
38-
return answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow())
35+
return answerEntryRepository.findByUserAndDate(user, targetDate)
3936
.orElseThrow(() -> e);
4037
}
4138
});

src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.devkor.ifive.nadab.global.core.response.ErrorCode;
88
import com.devkor.ifive.nadab.global.exception.ConflictException;
99
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
10-
import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto;
1110
import lombok.RequiredArgsConstructor;
1211
import org.springframework.dao.DataIntegrityViolationException;
1312
import org.springframework.stereotype.Service;
@@ -24,20 +23,19 @@ public class PendingDailyReportService {
2423

2524

2625
@Transactional
27-
public DailyReport getOrCreatePendingDailyReport(AnswerEntry entry) {
26+
public DailyReport getOrCreatePendingDailyReport(AnswerEntry entry, boolean isDayPassed) {
2827

29-
TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange();
30-
31-
LocalDate today = TodayDateTimeProvider.getTodayDate();
28+
LocalDate targetDate =
29+
isDayPassed ? TodayDateTimeProvider.getTodayDate().minusDays(1) : TodayDateTimeProvider.getTodayDate();
3230

3331
DailyReport report = dailyReportRepository.
34-
findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow())
32+
findByAnswerEntryAndDate(entry, targetDate)
3533
.orElseGet(() -> {
3634
try {
37-
return dailyReportRepository.save(DailyReport.createPending(entry, today));
35+
return dailyReportRepository.save(DailyReport.createPending(entry, targetDate));
3836
} catch (DataIntegrityViolationException e) {
3937
// 동시 요청에서 이미 누가 만들었을 수 있음 -> 재조회로 멱등 처리
40-
return dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow())
38+
return dailyReportRepository.findByAnswerEntryAndDate(entry, targetDate)
4139
.orElseThrow(() -> e);
4240
}
4341
});

src/main/java/com/devkor/ifive/nadab/domain/question/application/QuestionCommandService.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import com.devkor.ifive.nadab.global.exception.ConflictException;
1515
import com.devkor.ifive.nadab.global.exception.NotFoundException;
1616
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
17-
import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto;
1817
import lombok.RequiredArgsConstructor;
1918
import org.springframework.dao.DataIntegrityViolationException;
2019
import org.springframework.stereotype.Service;
@@ -100,8 +99,6 @@ public UserDailyQuestion createTodayQuestion(Long userId, LocalDate todayKst) {
10099
public DailyQuestionResponse rerollTodayQuestion(Long userId) {
101100
LocalDate today = TodayDateTimeProvider.getTodayDate();
102101

103-
TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange();
104-
105102
UserDailyQuestion udq = userDailyQuestionRepository.findByUserIdAndDate(userId, today)
106103
.orElseThrow(() -> new NotFoundException(ErrorCode.DAILY_QUESTION_NOT_FOUND));
107104

@@ -111,7 +108,7 @@ public DailyQuestionResponse rerollTodayQuestion(Long userId) {
111108

112109
User user = udq.getUser();
113110

114-
boolean alreadyAnswered = answerEntryRepository.existsByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow());
111+
boolean alreadyAnswered = answerEntryRepository.existsByUserAndDate(user, today);
115112
if (alreadyAnswered) {
116113
throw new ConflictException(ErrorCode.QUESTION_ALREADY_ANSWERED);
117114
}

src/main/java/com/devkor/ifive/nadab/global/shared/util/TodayDateTimeProvider.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package com.devkor.ifive.nadab.global.shared.util;
22

3-
import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto;
4-
53
import java.time.LocalDate;
6-
import java.time.OffsetDateTime;
74
import java.time.ZoneId;
85

96
/**
10-
* 오늘 날짜, 오늘 날짜의 시작과 내일 날짜의 시작 등을 제공하는 유틸리티 클래스
7+
* 오늘 날짜를 제공하는 유틸리티 클래스
118
* repository 등에서 오늘 날짜 범위 조회 시 사용
129
*/
1310
public class TodayDateTimeProvider {
@@ -17,20 +14,6 @@ public class TodayDateTimeProvider {
1714
private TodayDateTimeProvider() {
1815
}
1916

20-
public static TodayDateTimeRangeDto getRange() {
21-
LocalDate today = LocalDate.now(SEOUL);
22-
23-
OffsetDateTime startOfToday =
24-
today.atStartOfDay(SEOUL).toOffsetDateTime();
25-
26-
OffsetDateTime startOfTomorrow =
27-
today.plusDays(1)
28-
.atStartOfDay(SEOUL)
29-
.toOffsetDateTime();
30-
31-
return new TodayDateTimeRangeDto(startOfToday, startOfTomorrow);
32-
}
33-
3417
public static LocalDate getTodayDate() {
3518
return LocalDate.now(SEOUL);
3619
}

src/main/java/com/devkor/ifive/nadab/global/shared/util/dto/TodayDateTimeRangeDto.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)