diff --git a/src/main/java/com/dokdok/meeting/api/MyMeetingListApi.java b/src/main/java/com/dokdok/meeting/api/MyMeetingListApi.java index 8efdc864..dcc6cbed 100644 --- a/src/main/java/com/dokdok/meeting/api/MyMeetingListApi.java +++ b/src/main/java/com/dokdok/meeting/api/MyMeetingListApi.java @@ -62,7 +62,8 @@ public interface MyMeetingListApi { "endDateTime": "2025-02-01T16:00:00", "meetingStatus": "CONFIRMED", "myRole": "LEADER", - "progressStatus": "UPCOMING" + "progressStatus": "UPCOMING", + "preOpinionTemplateConfirmed": true } ], "totalCount": 8, diff --git a/src/main/java/com/dokdok/meeting/dto/MyMeetingListItemResponse.java b/src/main/java/com/dokdok/meeting/dto/MyMeetingListItemResponse.java index 4aea3cb3..835454b0 100644 --- a/src/main/java/com/dokdok/meeting/dto/MyMeetingListItemResponse.java +++ b/src/main/java/com/dokdok/meeting/dto/MyMeetingListItemResponse.java @@ -38,6 +38,9 @@ public record MyMeetingListItemResponse( MeetingMyRole myRole, @Schema(description = "약속 진행 상태(시간 기준)", example = "UPCOMING") - MeetingProgressStatus progressStatus + MeetingProgressStatus progressStatus, + + @Schema(description = "사전 의견 템플릿 확정 여부", example = "true") + boolean preOpinionTemplateConfirmed ) { } diff --git a/src/main/java/com/dokdok/meeting/repository/MeetingMemberRepository.java b/src/main/java/com/dokdok/meeting/repository/MeetingMemberRepository.java index 95fb54c8..da6c4fee 100644 --- a/src/main/java/com/dokdok/meeting/repository/MeetingMemberRepository.java +++ b/src/main/java/com/dokdok/meeting/repository/MeetingMemberRepository.java @@ -3,6 +3,7 @@ import com.dokdok.meeting.entity.MeetingMember; import com.dokdok.meeting.entity.MeetingStatus; import com.dokdok.meeting.entity.Meeting; +import com.dokdok.retrospective.entity.PersonalMeetingRetrospective; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -105,6 +106,23 @@ int countMyMeetingsByStatus( @Param("meetingStatus") MeetingStatus meetingStatus ); + @Query(""" + SELECT count(mm) FROM MeetingMember mm + JOIN mm.meeting m + WHERE mm.user.id = :userId + AND mm.canceledAt IS NULL + AND m.meetingStatus = :meetingStatus + AND NOT EXISTS ( + SELECT 1 FROM PersonalMeetingRetrospective pmr + WHERE pmr.meeting = m + AND pmr.user.id = :userId + ) + """) + int countMyMeetingsByStatusWithoutPersonalRetrospective( + @Param("userId") Long userId, + @Param("meetingStatus") MeetingStatus meetingStatus + ); + @Query(""" SELECT count(mm) FROM MeetingMember mm JOIN mm.meeting m @@ -216,6 +234,34 @@ List findMyMeetingsByStatusAfterCursor( Pageable pageable ); + @Query( + value = """ + SELECT m FROM MeetingMember mm + JOIN mm.meeting m + JOIN FETCH m.book + JOIN FETCH m.gathering + WHERE mm.user.id = :userId + AND mm.canceledAt IS NULL + AND m.meetingStatus = :meetingStatus + AND NOT EXISTS ( + SELECT 1 FROM PersonalMeetingRetrospective pmr + WHERE pmr.meeting = m + AND pmr.user.id = :userId + ) + AND (CAST(:cursorStartDateTime AS timestamp) IS NULL + OR m.meetingStartDate > :cursorStartDateTime + OR (m.meetingStartDate = :cursorStartDateTime AND m.id > :cursorMeetingId)) + ORDER BY m.meetingStartDate ASC, m.id ASC + """ + ) + List findMyDoneMeetingsWithoutPersonalRetrospectiveAfterCursor( + @Param("userId") Long userId, + @Param("meetingStatus") MeetingStatus meetingStatus, + @Param("cursorStartDateTime") LocalDateTime cursorStartDateTime, + @Param("cursorMeetingId") Long cursorMeetingId, + Pageable pageable + ); + @Query( value = """ SELECT m FROM MeetingMember mm diff --git a/src/main/java/com/dokdok/meeting/service/MeetingService.java b/src/main/java/com/dokdok/meeting/service/MeetingService.java index 8b0d69f5..b3ffdac8 100644 --- a/src/main/java/com/dokdok/meeting/service/MeetingService.java +++ b/src/main/java/com/dokdok/meeting/service/MeetingService.java @@ -666,7 +666,7 @@ public CursorResponse getMyMeeting cursorMeetingId(cursor), pageable ); - case DONE -> meetingMemberRepository.findMyMeetingsByStatusAfterCursor( + case DONE -> meetingMemberRepository.findMyDoneMeetingsWithoutPersonalRetrospectiveAfterCursor( userId, MeetingStatus.DONE, cursorStartDateTime(cursor), @@ -691,7 +691,7 @@ public CursorResponse getMyMeeting now, now.plusDays(3) ); - case DONE -> meetingMemberRepository.countMyMeetingsByStatus( + case DONE -> meetingMemberRepository.countMyMeetingsByStatusWithoutPersonalRetrospective( userId, MeetingStatus.DONE ); @@ -724,7 +724,7 @@ public MyMeetingTabCountsResponse getMyMeetingTabCounts() { now, now.plusDays(3) ); - int doneCount = meetingMemberRepository.countMyMeetingsByStatus( + int doneCount = meetingMemberRepository.countMyMeetingsByStatusWithoutPersonalRetrospective( userId, MeetingStatus.DONE ); @@ -899,6 +899,13 @@ private List buildMyMeetingItems(List meetin LocalDateTime now = LocalDateTime.now(); List items = new ArrayList<>(); + List meetingIds = meetings.stream() + .map(Meeting::getId) + .toList(); + Set meetingIdsWithConfirmedTopics = new HashSet<>( + topicRepository.findMeetingIdsWithConfirmedTopics(meetingIds) + ); + for (Meeting meeting : meetings) { MeetingProgressStatus progressStatus = resolveProgressStatus( meeting.getMeetingStartDate(), @@ -906,6 +913,7 @@ private List buildMyMeetingItems(List meetin now ); MeetingMyRole myRole = resolveMyMeetingRole(meeting, userId); + boolean preOpinionTemplateConfirmed = meetingIdsWithConfirmedTopics.contains(meeting.getId()); items.add(new MyMeetingListItemResponse( meeting.getId(), @@ -918,7 +926,8 @@ private List buildMyMeetingItems(List meetin meeting.getMeetingEndDate(), meeting.getMeetingStatus(), myRole, - progressStatus + progressStatus, + preOpinionTemplateConfirmed )); } return items; diff --git a/src/main/java/com/dokdok/topic/repository/TopicRepository.java b/src/main/java/com/dokdok/topic/repository/TopicRepository.java index f5c8af8b..a8df5a5f 100644 --- a/src/main/java/com/dokdok/topic/repository/TopicRepository.java +++ b/src/main/java/com/dokdok/topic/repository/TopicRepository.java @@ -228,6 +228,17 @@ List findTopicsInfoByMeetingIds( @Param("meetingIds") List meetingIds ); + @Query(""" + SELECT DISTINCT t.meeting.id + FROM Topic t + WHERE t.meeting.id IN :meetingIds + AND t.topicStatus = com.dokdok.topic.entity.TopicStatus.CONFIRMED + AND t.deletedAt IS NULL + """) + List findMeetingIdsWithConfirmedTopics( + @Param("meetingIds") List meetingIds + ); + @Query(""" SELECT MAX(t.updatedAt) FROM Topic t diff --git a/src/test/java/com/dokdok/meeting/service/MeetingServiceTest.java b/src/test/java/com/dokdok/meeting/service/MeetingServiceTest.java index d04f792c..b827d639 100644 --- a/src/test/java/com/dokdok/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dokdok/meeting/service/MeetingServiceTest.java @@ -1438,6 +1438,8 @@ void givenNullFilter_whenGetMyMeetingList_thenReturnItems() { any(), any() )).willReturn(List.of(myMeeting)); + given(topicRepository.findMeetingIdsWithConfirmedTopics(List.of(myMeeting.getId()))) + .willReturn(List.of(myMeeting.getId())); try (MockedStatic mock = mockStatic(SecurityUtil.class)) { mock.when(SecurityUtil::getCurrentUserId).thenReturn(userId); @@ -1482,6 +1484,8 @@ void givenUpcomingFilter_whenGetMyMeetingList_thenReturnUpcomingItems() { any(), any() )).willReturn(List.of(upcomingMeeting)); + given(topicRepository.findMeetingIdsWithConfirmedTopics(List.of(upcomingMeeting.getId()))) + .willReturn(List.of()); try (MockedStatic mock = mockStatic(SecurityUtil.class)) { mock.when(SecurityUtil::getCurrentUserId).thenReturn(userId); @@ -1512,8 +1516,10 @@ void givenUser_whenGetMyMeetingTabCounts_thenReturnCounts() { any(), any() )).willReturn(2); - given(meetingMemberRepository.countMyMeetingsByStatus(userId, MeetingStatus.DONE)) - .willReturn(3); + given(meetingMemberRepository.countMyMeetingsByStatusWithoutPersonalRetrospective( + userId, + MeetingStatus.DONE + )).willReturn(3); try (MockedStatic mock = mockStatic(SecurityUtil.class)) { mock.when(SecurityUtil::getCurrentUserId).thenReturn(userId);