3737import org .layer .admin .retrospect .repository .dto .RetrospectAnswerCompletionDto ;
3838import org .layer .admin .retrospect .repository .dto .SpaceRetrospectCountDto ;
3939import org .layer .admin .space .repository .AdminSpaceRepository ;
40+ import org .layer .domain .retrospect .entity .Retrospect ;
41+ import org .layer .domain .retrospect .entity .RetrospectStatus ;
42+ import org .layer .domain .retrospect .repository .RetrospectRepository ;
43+ import org .layer .domain .space .entity .MemberSpaceRelation ;
44+ import org .layer .domain .space .entity .Team ;
45+ import org .layer .domain .space .repository .MemberSpaceRelationRepository ;
4046import org .layer .event .retrospect .ClickRetrospectEvent ;
4147import org .layer .event .retrospect .CreateRetrospectEvent ;
4248import org .layer .event .retrospect .AnswerRetrospectEndEvent ;
@@ -58,6 +64,8 @@ public class AdminRetrospectService {
5864 private final AdminRetrospectClickRepository adminRetrospectClickRepository ;
5965 private final AdminMemberRepository adminMemberRepository ;
6066 private final AdminSpaceRepository adminSpaceRepository ;
67+ private final RetrospectRepository retrospectRepository ;
68+ private final MemberSpaceRelationRepository memberSpaceRelationRepository ;
6169
6270 public MeaningfulRetrospectMemberResponse getAllMeaningfulRetrospect (
6371 LocalDateTime startTime , LocalDateTime endTime , int retrospectLength , int retrospectCount ) {
@@ -203,13 +211,60 @@ public RetrospectCompletionRateResponse getRetrospectCompletionRate(LocalDateTim
203211 List <RetrospectAnswerCompletionDto > answerHistories = adminRetrospectAnswerRepository .findRetrospectAnswerCompletionStatsBetween (
204212 startTime , endTime );
205213
206- // 회고별 완수율 계산 (단위: %)
214+ if (answerHistories .isEmpty ()) {
215+ return new RetrospectCompletionRateResponse (0.0 );
216+ }
217+
218+ // 필요한 회고/스페이스/팀 정보를 미리 한 번에 로딩해서 N+1 방지
219+ List <Long > retrospectIds = answerHistories .stream ()
220+ .map (RetrospectAnswerCompletionDto ::retrospectId )
221+ .distinct ()
222+ .toList ();
223+
224+ List <Retrospect > retrospects = retrospectRepository .findAllById (retrospectIds );
225+ Map <Long , Retrospect > retrospectMap = retrospects .stream ()
226+ .collect (Collectors .toMap (Retrospect ::getId , r -> r ));
227+
228+ List <Long > spaceIds = retrospects .stream ()
229+ .map (Retrospect ::getSpaceId )
230+ .distinct ()
231+ .toList ();
232+
233+ List <MemberSpaceRelation > allRelations = memberSpaceRelationRepository .findAllBySpaceIdIn (spaceIds );
234+ Map <Long , Team > teamBySpaceId = allRelations .stream ()
235+ .collect (Collectors .groupingBy (
236+ relation -> relation .getSpace ().getId (),
237+ Collectors .collectingAndThen (Collectors .toList (), Team ::new )
238+ ));
239+
240+ // 회고별 분모를 도메인 로직(Team, RetrospectStatus, deadline) 기반으로 계산
207241 List <Double > completionRates = answerHistories .stream ()
208- .filter (dto -> dto .targetAnswerCount () > 0 ) // division by zero 방지
209- .map (dto -> (double ) dto .actualAnswerCount () / dto .targetAnswerCount () * 100.0 )
242+ .map (dto -> {
243+ Retrospect retrospect = retrospectMap .get (dto .retrospectId ());
244+ if (retrospect == null ) {
245+ return null ;
246+ }
247+
248+ Team team = teamBySpaceId .get (retrospect .getSpaceId ());
249+ if (team == null ) {
250+ return null ;
251+ }
252+
253+ long totalCount = team .getTeamMemberCount ();
254+ if (retrospect .getRetrospectStatus ().equals (RetrospectStatus .DONE )) {
255+ // 회고가 종료된 경우, deadline 시점의 팀원 수를 분모로 사용
256+ totalCount = team .getTeamMemberCountBefore (retrospect .getDeadline ());
257+ }
258+
259+ if (totalCount == 0 ) {
260+ return null ; // division by zero 방지
261+ }
262+
263+ return (double ) dto .actualAnswerCount () / totalCount * 100.0 ;
264+ })
265+ .filter (Objects ::nonNull )
210266 .toList ();
211267
212- // 평균 완수율 계산
213268 double averageCompletionRate = completionRates .isEmpty ()
214269 ? 0.0
215270 : completionRates .stream ()
0 commit comments