@@ -932,37 +932,25 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
932932 endDate = today ;
933933 }
934934
935- // 1. 시작 전날 확인 (streakCount 기준 계산)
936- LocalDate dayBeforeStart = startDate .minusDays (1 );
937- int baseStreakCount = dailyCompletionRepository
938- .findByUserIdAndCompletionDate (userId , dayBeforeStart )
939- .map (DailyCompletion ::getStreakCount )
940- .orElse (0 );
935+ // 복구 전 프리즈 개수 저장
936+ UserStudyReport beforeReport = userStudyReportRepository .findByUserId (userId ).orElse (null );
937+ int freezesBeforeRecovery = beforeReport != null && beforeReport .getAvailableFreezes () != null
938+ ? beforeReport .getAvailableFreezes () : 0 ;
941939
942- // 2 . 복구 처리 및 프리즈 보상 수집
940+ // 1 . 복구 처리 및 프리즈 보상 수집
943941 List <DailyCompletion > completionsToSave = new ArrayList <>();
944942 List <FreezeTransaction > freezeTransactions = new ArrayList <>();
945- int currentStreakCount = baseStreakCount ;
946943 int earnedFreezes = 0 ;
947944
948- // 복구 범위 처리
945+ // 복구 범위 처리 (streakCount는 나중에 재계산)
949946 for (LocalDate date = startDate ; !date .isAfter (endDate ); date = date .plusDays (1 )) {
950947 Optional <DailyCompletion > existingOpt = dailyCompletionRepository
951948 .findByUserIdAndCompletionDate (userId , date );
952949
953950 if (existingOpt .isPresent ()) {
954951 DailyCompletion existing = existingOpt .get ();
955952
956- if (existing .getStreakStatus () == StreakStatus .COMPLETED ) {
957- // 이미 완료됨 - 그대로 유지
958- currentStreakCount ++;
959- } else if (existing .getStreakStatus () == StreakStatus .FREEZE_USED ) {
960- // FREEZE_USED → COMPLETED로 변경 + 프리즈 보상
961- existing .setStreakStatus (StreakStatus .COMPLETED );
962- currentStreakCount ++;
963- existing .setStreakCount (currentStreakCount );
964- completionsToSave .add (existing );
965-
953+ if (existing .getStreakStatus () == StreakStatus .FREEZE_USED ) {
966954 // 프리즈 보상
967955 FreezeTransaction rewardTx = FreezeTransaction .builder ()
968956 .userId (userId )
@@ -975,61 +963,57 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
975963
976964 log .info ("Recovered FREEZE_USED to COMPLETED for user {} on {}. Rewarded 1 freeze." ,
977965 userId , date );
978- } else {
979- // MISSED 또는 기타 → COMPLETED로 변경
980- existing .setStreakStatus (StreakStatus .COMPLETED );
981- currentStreakCount ++;
982- existing .setStreakCount (currentStreakCount );
983- completionsToSave .add (existing );
984966 }
967+
968+ // streakCount는 나중에 재계산할 것이므로 null로 설정
969+ existing .setStreakStatus (StreakStatus .COMPLETED );
970+ existing .setStreakCount (null );
971+ completionsToSave .add (existing );
972+
985973 } else {
986974 // 레코드 없음 (MISSED) → 새로 생성
987- currentStreakCount ++;
988975 DailyCompletion newCompletion = DailyCompletion .builder ()
989976 .userId (userId )
990977 .completionDate (date )
991978 .streakStatus (StreakStatus .COMPLETED )
992- .streakCount (currentStreakCount )
979+ .streakCount (null ) // 나중에 재계산
993980 .firstCompletionCount (0 )
994981 .totalCompletionCount (0 )
995982 .completedContents (new ArrayList <>())
996983 .createdAt (Instant .now ())
997984 .build ();
998985 completionsToSave .add (newCompletion );
999986
1000- log .info ("Created new COMPLETED record for user {} on {}. StreakCount: {}" ,
1001- userId , date , currentStreakCount );
987+ log .info ("Created new COMPLETED record for user {} on {}." , userId , date );
1002988 }
1003989 }
1004990
1005- // 3. 복구 범위 이후 날짜들 처리 (프리즈 자동 사용)
1006- LocalDate yesterday = today .minusDays (1 );
991+ // 2. 복구 범위 이후 날짜들 처리 (프리즈 자동 사용)
1007992 LocalDate currentDate = endDate .plusDays (1 );
1008993 int availableFreezes = earnedFreezes ;
1009994
1010- while (! currentDate .isAfter ( yesterday )) {
995+ while (currentDate .isBefore ( today )) { // 오늘은 제외
1011996 Optional <DailyCompletion > existingOpt = dailyCompletionRepository
1012997 .findByUserIdAndCompletionDate (userId , currentDate );
1013998
1014999 if (existingOpt .isPresent ()) {
10151000 DailyCompletion existing = existingOpt .get ();
10161001
10171002 if (existing .getStreakStatus () == StreakStatus .COMPLETED ) {
1018- // 완료됨 - 스트릭 계속 증가
1019- currentStreakCount ++;
1020- existing .setStreakCount (currentStreakCount );
1003+ // 이미 완료됨 - 그대로 유지 (streakCount는 재계산)
1004+ existing .setStreakCount (null );
10211005 completionsToSave .add (existing );
10221006 currentDate = currentDate .plusDays (1 );
10231007 } else if (existing .getStreakStatus () == StreakStatus .FREEZE_USED ) {
1024- // 이미 프리즈 사용됨 - 스트릭 유지
1025- existing .setStreakCount (currentStreakCount );
1008+ // 이미 프리즈 사용됨 - 유지
1009+ existing .setStreakCount (null );
10261010 completionsToSave .add (existing );
10271011 currentDate = currentDate .plusDays (1 );
10281012 } else {
10291013 // MISSED - 프리즈로 커버 시도
10301014 if (availableFreezes > 0 ) {
10311015 existing .setStreakStatus (StreakStatus .FREEZE_USED );
1032- existing .setStreakCount (currentStreakCount );
1016+ existing .setStreakCount (null );
10331017 completionsToSave .add (existing );
10341018
10351019 // 프리즈 사용
@@ -1060,7 +1044,7 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
10601044 .userId (userId )
10611045 .completionDate (currentDate )
10621046 .streakStatus (StreakStatus .FREEZE_USED )
1063- .streakCount (currentStreakCount )
1047+ .streakCount (null ) // 재계산 예정
10641048 .firstCompletionCount (0 )
10651049 .totalCompletionCount (0 )
10661050 .completedContents (new ArrayList <>())
@@ -1073,8 +1057,6 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
10731057 .userId (userId )
10741058 .amount (-1 )
10751059 .description ("Auto-consumed for recovery on " + currentDate )
1076-
1077-
10781060 .createdAt (Instant .now ())
10791061 .build ();
10801062 freezeTransactions .add (usageTx );
@@ -1093,25 +1075,7 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
10931075 }
10941076 }
10951077
1096- // 4. 오늘(today) 처리 - 프리즈 사용은 안 하지만, 학습했다면 streakCount 업데이트
1097- Optional <DailyCompletion > todayCompletionOpt = dailyCompletionRepository
1098- .findByUserIdAndCompletionDate (userId , today );
1099-
1100- if (todayCompletionOpt .isPresent ()) {
1101- DailyCompletion todayCompletion = todayCompletionOpt .get ();
1102-
1103- if (todayCompletion .getStreakStatus () == StreakStatus .COMPLETED ) {
1104- // 오늘 학습함 - streakCount 업데이트
1105- currentStreakCount ++;
1106- todayCompletion .setStreakCount (currentStreakCount );
1107- completionsToSave .add (todayCompletion );
1108-
1109- log .info ("Updated today's completion for user {} with streakCount: {}" , userId , currentStreakCount );
1110- }
1111- // MISSED나 다른 상태면 배치에서 처리하므로 여기서는 스킵
1112- }
1113-
1114- // 5. 데이터 저장
1078+ // 3. 데이터 저장
11151079 if (!completionsToSave .isEmpty ()) {
11161080 dailyCompletionRepository .saveAll (completionsToSave );
11171081 log .info ("Saved {} DailyCompletion records for user {}" , completionsToSave .size (), userId );
@@ -1122,20 +1086,69 @@ public void recoverStreak(String userId, LocalDate startDate, LocalDate endDate)
11221086 log .info ("Saved {} FreezeTransaction records for user {}" , freezeTransactions .size (), userId );
11231087 }
11241088
1089+ // 4. 전체 streakCount 재계산 (startDate 기준)
1090+ recalculateAllStreakCounts (userId , startDate );
1091+
11251092 // 5. UserStudyReport 재계산 및 프리즈 반영
11261093 UserStudyReport finalReport = recalculateUserStudyReport (userId );
11271094
1128- // 프리즈 개수 업데이트
1129- // availableFreezes는 복구 과정에서 남은 프리즈 개수
1130- // 최종 프리즈 = 기존 보유 + 남은 프리즈 (최대 MAX_FREEZE_COUNT)
1131- int currentFreezes = finalReport .getAvailableFreezes () != null ? finalReport .getAvailableFreezes () : 0 ;
1132- int usedFreezes = earnedFreezes - availableFreezes ;
1133- finalReport .setAvailableFreezes (Math .max (0 , Math .min (MAX_FREEZE_COUNT , currentFreezes + availableFreezes )));
1095+ // 최종 프리즈 = 복구 전 + 획득 - 사용 (최대 MAX_FREEZE_COUNT)
1096+ int finalFreezes = Math .min (MAX_FREEZE_COUNT , freezesBeforeRecovery + availableFreezes );
1097+ finalReport .setAvailableFreezes (finalFreezes );
11341098 userStudyReportRepository .save (finalReport );
11351099
1100+ int usedFreezes = earnedFreezes - availableFreezes ;
11361101 log .info ("Streak recovery completed for user {} from {} to {}. Earned {} freezes, used {} freezes. Final freezes: {}" ,
1137- userId , startDate , endDate , earnedFreezes , usedFreezes , finalReport . getAvailableFreezes () );
1102+ userId , startDate , endDate , earnedFreezes , usedFreezes , finalFreezes );
11381103 }
1139- }
11401104
1141-
1105+ @ Transactional
1106+ public void recalculateAllStreakCounts (String userId , LocalDate recoveryStartDate ) {
1107+ // 복구 시작일 전날의 streakCount를 기준값으로 가져오기
1108+ LocalDate dayBeforeStart = recoveryStartDate .minusDays (1 );
1109+ int baseStreakCount = dailyCompletionRepository
1110+ .findByUserIdAndCompletionDate (userId , dayBeforeStart )
1111+ .map (DailyCompletion ::getStreakCount )
1112+ .orElse (0 );
1113+
1114+ log .info ("Starting recalculation from {} with base streakCount: {}" , recoveryStartDate , baseStreakCount );
1115+
1116+ // 복구 시작일부터 모든 DailyCompletion 가져오기
1117+ List <DailyCompletion > completionsToRecalculate = dailyCompletionRepository
1118+ .findByUserIdAndCompletionDateGreaterThanEqualOrderByCompletionDateAsc (userId , recoveryStartDate );
1119+
1120+ if (completionsToRecalculate .isEmpty ()) {
1121+ log .info ("No completions found for user {} from {}. Skipping streakCount recalculation." ,
1122+ userId , recoveryStartDate );
1123+ return ;
1124+ }
1125+
1126+ int streakCount = baseStreakCount ;
1127+ LocalDate previousDate = dayBeforeStart ;
1128+ List <DailyCompletion > toUpdate = new ArrayList <>();
1129+
1130+ for (DailyCompletion completion : completionsToRecalculate ) {
1131+ LocalDate currentDate = completion .getCompletionDate ();
1132+ StreakStatus status = completion .getStreakStatus ();
1133+
1134+ // 날짜가 연속적이지 않으면 스트릭 리셋
1135+ if (ChronoUnit .DAYS .between (previousDate , currentDate ) > 1 || status == StreakStatus .MISSED ) {
1136+ break ;
1137+ }
1138+
1139+ if (status == StreakStatus .COMPLETED ) {
1140+ streakCount ++;
1141+ }
1142+
1143+ completion .setStreakCount (streakCount );
1144+ toUpdate .add (completion );
1145+ previousDate = currentDate ;
1146+ }
1147+
1148+ if (!toUpdate .isEmpty ()) {
1149+ dailyCompletionRepository .saveAll (toUpdate );
1150+ log .info ("Recalculated {} streakCounts for user {} starting from {} (base: {}). Final streakCount: {}" ,
1151+ toUpdate .size (), userId , recoveryStartDate , baseStreakCount , streakCount );
1152+ }
1153+ }
1154+ }
0 commit comments