From 2df4dadd39f66d50fc4661a00a5ad7ed35ae37c7 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:05:07 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20NumberUtil=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 반올림 메서드 사용 용도 --- .../java/com/join/core/common/util/NumberUtil.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/join/core/common/util/NumberUtil.java diff --git a/src/main/java/com/join/core/common/util/NumberUtil.java b/src/main/java/com/join/core/common/util/NumberUtil.java new file mode 100644 index 0000000..703a066 --- /dev/null +++ b/src/main/java/com/join/core/common/util/NumberUtil.java @@ -0,0 +1,10 @@ +package com.join.core.common.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class NumberUtil { + public static double round(int newScale, double value) { + return BigDecimal.valueOf(value).setScale(newScale, RoundingMode.HALF_UP).doubleValue(); + } +} From bf21c6c001a4ac07aa1c0c196bd143478c7e849e Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:10:38 +0900 Subject: [PATCH 02/14] =?UTF-8?q?fix:=20getMyPageInfo=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=20=EA=B0=92=20=EB=B0=98=EC=98=AC=EB=A6=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/service/AttendanceRateService.java | 6 ++++-- .../join/core/avatar/domain/MyPageServiceImpl.java | 4 ++-- .../repository/MeetingQueryRepositoryImpl.java | 12 +++++------- .../join/core/proof/service/ProofRateService.java | 6 ++++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java index c873003..7cf892b 100644 --- a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java +++ b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java @@ -1,6 +1,7 @@ package com.join.core.attendance.service; import com.join.core.attendance.domain.Attendance; +import com.join.core.common.util.NumberUtil; import com.join.core.enrollment.constant.EnrollmentStatus; import com.join.core.meeting.domain.MeetingReader; import lombok.RequiredArgsConstructor; @@ -18,7 +19,7 @@ public class AttendanceRateService { private final MeetingReader meetingReader; @Transactional(readOnly = true) - public double calculateAttendanceRate(Long avatarId) { + public double calculateIndividualAttendanceRate(Long avatarId) { List attendancesForJoinedStudies = attendanceReader.findAttendanceForJoinedStudy(avatarId); List attendancesForLeftStudies = attendanceReader.findAttendanceForLeftStudy(avatarId); @@ -41,6 +42,7 @@ public double calculateAttendanceRate(Long avatarId) { if (totalAttendance == 0) return 0.0; - return totalAttendance / totalMeetings * 100; + return NumberUtil.round(2, totalAttendance / totalMeetings * 100); + } } } diff --git a/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java b/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java index da18e7e..75f0dcd 100644 --- a/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java +++ b/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java @@ -21,9 +21,9 @@ public class MyPageServiceImpl implements MyPageService { public MyPageInfoResponse getMyPageInfo(Long avatarId) { Avatar avatar = avatarReader.getAvatarById(avatarId); - double averageAttendanceRate = attendanceRateService.calculateAttendanceRate(avatarId); + double averageAttendanceRate = attendanceRateService.calculateIndividualAttendanceRate(avatarId); - double averageProofRate = proofRateService.calculateProofRate(avatarId); + double averageProofRate = proofRateService.calculateIndividualProofRate(avatarId); return MyPageInfoResponse.of(avatar, averageAttendanceRate, averageProofRate); } diff --git a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java index 339cab8..1c41cdf 100644 --- a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java @@ -1,10 +1,7 @@ package com.join.core.meeting.repository; import com.join.core.enrollment.constant.EnrollmentStatus; -import com.join.core.enrollment.domain.QEnrollment; import com.join.core.meeting.domain.Meeting; -import com.join.core.meeting.domain.QMeeting; -import com.join.core.study.domain.QStudy; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -12,17 +9,18 @@ import java.util.List; +import static com.join.core.enrollment.domain.QEnrollment.*; +import static com.join.core.meeting.domain.QMeeting.*; +import static com.join.core.study.domain.QStudy.*; + @RequiredArgsConstructor @Component public class MeetingQueryRepositoryImpl implements MeetingQueryRepository { private final JPAQueryFactory queryFactory; + @Override public List findMeetingsByAvatarIdAndEnrollmentStatuses(Long avatarId, List statuses) { - QEnrollment enrollment = QEnrollment.enrollment; - QMeeting meeting = QMeeting.meeting; - QStudy study = QStudy.study; - return queryFactory .selectFrom(meeting) .join(meeting.study, study) diff --git a/src/main/java/com/join/core/proof/service/ProofRateService.java b/src/main/java/com/join/core/proof/service/ProofRateService.java index 2985434..02a6556 100644 --- a/src/main/java/com/join/core/proof/service/ProofRateService.java +++ b/src/main/java/com/join/core/proof/service/ProofRateService.java @@ -1,5 +1,6 @@ package com.join.core.proof.service; +import com.join.core.common.util.NumberUtil; import com.join.core.enrollment.constant.EnrollmentStatus; import com.join.core.meeting.domain.MeetingReader; import com.join.core.proof.domain.Proof; @@ -18,7 +19,7 @@ public class ProofRateService { private final ProofReader proofReader; @Transactional(readOnly = true) - public double calculateProofRate(Long avatarId) { + public double calculateIndividualProofRate(Long avatarId) { List proofsForJoinedStudies = proofReader.findProofsByAvatarIdForJoinedStudies(avatarId); List proofsForLeftStudies = proofReader.findProofsByAvatarIdForLeftStudies(avatarId); @@ -43,6 +44,7 @@ public double calculateProofRate(Long avatarId) { return 0.0; } - return totalProof / totalMeetings * 100; + return NumberUtil.round(2, totalProof / totalMeetings * 100); + } } } From 620e51c2a83517b58587ce907379d8170eef5b4c Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:11:26 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20study=20kakao=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20url=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttendanceRateService.java | 56 +++++++++++++++++++ .../com/join/core/study/domain/Study.java | 2 + 2 files changed, 58 insertions(+) diff --git a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java index 7cf892b..ed47165 100644 --- a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java +++ b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java @@ -1,6 +1,7 @@ package com.join.core.attendance.service; import com.join.core.attendance.domain.Attendance; +import com.join.core.avatar.domain.AvatarReader; import com.join.core.common.util.NumberUtil; import com.join.core.enrollment.constant.EnrollmentStatus; import com.join.core.meeting.domain.MeetingReader; @@ -44,5 +45,60 @@ public double calculateIndividualAttendanceRate(Long avatarId) { return NumberUtil.round(2, totalAttendance / totalMeetings * 100); } + + @Transactional(readOnly = true) + public double calculateTeamAttendanceRateForStudy(Long studyId) { + List attendancesForJoinedStudies = attendanceReader.findByStudyIdForJoinedStudy(studyId); + List attendancesForLeftStudies = attendanceReader.findByStudyIdForLeftStudy(studyId); + + long countStudyMembers = avatarReader.findAvatarsExceptLeftByStudyId(studyId).size(); + long countAllMeetings = meetingReader.findMeetingsByStudyId(studyId).size(); + long totalMeetings = countAllMeetings * countStudyMembers; + + double totalAttendance = Stream.concat( + attendancesForJoinedStudies.stream(), + attendancesForLeftStudies.stream() + ) + .mapToDouble(attendance -> { + double reflectionRate = attendance.getStatus().getReflectionRate(); + + if (attendancesForLeftStudies.contains(attendance)) { + reflectionRate *= EnrollmentStatus.LEFT.getReflectionRate(); + } + + return reflectionRate; + }) + .sum(); + + if (totalAttendance == 0) return 0.0; + + return NumberUtil.round(2, totalAttendance / totalMeetings * 100); + } + + @Transactional(readOnly = true) + public double calculateMemberAttendanceRateForStudy(Long avatarId, Long studyId) { + List attendancesForJoinedStudies = attendanceReader.findByAvatarIdAndStudyIdForJoinedStudy(avatarId, studyId); + List attendancesForLeftStudies = attendanceReader.findByAvatarIdAndStudyIdForLeftStudy(avatarId, studyId); + + long totalMeetings = meetingReader.findMeetingsByStudyId(studyId).size(); + + double totalAttendance = Stream.concat( + attendancesForJoinedStudies.stream(), + attendancesForLeftStudies.stream() + ) + .mapToDouble(attendance -> { + double reflectionRate = attendance.getStatus().getReflectionRate(); + + if (attendancesForLeftStudies.contains(attendance)) { + reflectionRate *= EnrollmentStatus.LEFT.getReflectionRate(); + } + + return reflectionRate; + }) + .sum(); + + if (totalAttendance == 0) return 0.0; + + return NumberUtil.round(2, totalAttendance / totalMeetings * 100); } } diff --git a/src/main/java/com/join/core/study/domain/Study.java b/src/main/java/com/join/core/study/domain/Study.java index 1a8a6a2..f9c691c 100644 --- a/src/main/java/com/join/core/study/domain/Study.java +++ b/src/main/java/com/join/core/study/domain/Study.java @@ -116,6 +116,8 @@ public class Study { @OneToMany(mappedBy = "study", cascade = CascadeType.ALL, orphanRemoval = true) private List schedules; + private String kakaoUrl; + public Study(StudyRecruitRequest recruitRequest, Avatar writer, Address address, Category category) { if (writer == null) throw new InvalidParamException(INVALID_PARAMETER, "Study.writer"); From 47cff6a1140dfff0bac3279e17f687d50350a4d5 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:15:40 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EB=A6=AC=EB=8D=94=EC=9D=98=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/study/repository/StudyQueryRepository.java | 2 ++ .../study/repository/StudyQueryRepositoryImpl.java | 11 +++++++++++ .../join/core/study/repository/StudyReaderImpl.java | 6 ++++++ .../java/com/join/core/study/service/StudyReader.java | 1 + 4 files changed, 20 insertions(+) diff --git a/src/main/java/com/join/core/study/repository/StudyQueryRepository.java b/src/main/java/com/join/core/study/repository/StudyQueryRepository.java index 60330f3..74a2547 100644 --- a/src/main/java/com/join/core/study/repository/StudyQueryRepository.java +++ b/src/main/java/com/join/core/study/repository/StudyQueryRepository.java @@ -1,5 +1,6 @@ package com.join.core.study.repository; +import com.join.core.enrollment.constant.StudyRole; import com.join.core.study.domain.Study; import com.join.core.study.repository.condition.CustomStudyCondition; import com.join.core.study.repository.condition.EssentialStudyCondition; @@ -14,4 +15,5 @@ public interface StudyQueryRepository { Page getStudiesOrderByPopularity(EssentialStudyCondition condition, LocalDateTime now, Pageable pageable); List getStudiesOrderByRecommendations(EssentialStudyCondition essentialStudyCondition, CustomStudyCondition customStudyCondition); boolean existsByEnrollmentsAvatarToken(String subjectToken, String targetToken); + List findAllByAvatarIdAndRole(Long avatarId, StudyRole status); } diff --git a/src/main/java/com/join/core/study/repository/StudyQueryRepositoryImpl.java b/src/main/java/com/join/core/study/repository/StudyQueryRepositoryImpl.java index 023528d..3c05c3f 100644 --- a/src/main/java/com/join/core/study/repository/StudyQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/study/repository/StudyQueryRepositoryImpl.java @@ -2,6 +2,7 @@ import com.join.core.enrollment.constant.EnrollmentStatus; import com.join.core.study.constant.StudyStatus; +import com.join.core.enrollment.constant.StudyRole; import com.join.core.study.domain.Study; import com.join.core.study.repository.condition.CustomStudyCondition; import com.join.core.study.repository.condition.EssentialStudyCondition; @@ -119,4 +120,14 @@ public boolean existsByEnrollmentsAvatarToken(String subjectToken, String target .fetchFirst() != null; } + + @Override + public List findAllByAvatarIdAndRole(Long avatarId, StudyRole status) { + return queryFactory.selectFrom(study) + .leftJoin(enrollment).on( + enrollment.avatar.id.eq(avatarId), + enrollment.role.eq(status) + ) + .fetch(); + } } diff --git a/src/main/java/com/join/core/study/repository/StudyReaderImpl.java b/src/main/java/com/join/core/study/repository/StudyReaderImpl.java index 101c1b2..d2b27f6 100644 --- a/src/main/java/com/join/core/study/repository/StudyReaderImpl.java +++ b/src/main/java/com/join/core/study/repository/StudyReaderImpl.java @@ -3,6 +3,7 @@ import com.join.core.common.exception.ErrorCode; import com.join.core.common.exception.impl.EntityNotFoundException; import com.join.core.common.exception.impl.InvalidStateException; +import com.join.core.enrollment.constant.StudyRole; import com.join.core.study.domain.Study; import com.join.core.study.repository.condition.CustomStudyCondition; import com.join.core.study.repository.condition.EssentialStudyCondition; @@ -49,6 +50,11 @@ public List getStudiesOrderByRecommendations(EssentialStudyCondition cond return studyQueryRepository.getStudiesOrderByRecommendations(condition, customStudyCondition); } + @Override + public List getStudiesByLeaderAvatarId(Long avatarId) { + return studyQueryRepository.findAllByAvatarIdAndRole(avatarId, StudyRole.LEADER); + } + @Override public Page getStudiesByTitleContaining(String keyword, Pageable pageable) { return studyRepository.findAllByTitleContaining(keyword, pageable); diff --git a/src/main/java/com/join/core/study/service/StudyReader.java b/src/main/java/com/join/core/study/service/StudyReader.java index 3146591..2e395a5 100644 --- a/src/main/java/com/join/core/study/service/StudyReader.java +++ b/src/main/java/com/join/core/study/service/StudyReader.java @@ -14,6 +14,7 @@ public interface StudyReader { Study getStudyById(Long studyId); Page getStudyOrderByPopularity(EssentialStudyCondition condition, LocalDateTime now, Pageable pageable); List getStudiesOrderByRecommendations(EssentialStudyCondition condition, CustomStudyCondition customStudyCondition); + List getStudiesByLeaderAvatarId(Long avatarId); Page getStudiesByTitleContaining(String keyword, Pageable pageable); Study validateStudyCompletion(Long studyId); boolean existsByEnrollmentsAvatarToken(String subjectToken, String targetToken); From 71a1f4ea6c3fc99651fcbf66e77cf7d83d2c5717 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:17:50 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EC=8A=A4=ED=84=B0=EB=94=94?= =?UTF-8?q?=EC=97=90=20=EC=86=8C=EC=86=8D=EB=90=9C=20=ED=9A=8C=EC=B0=A8=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/join/core/meeting/domain/MeetingReader.java | 1 + .../meeting/repository/MeetingQueryRepository.java | 1 + .../repository/MeetingQueryRepositoryImpl.java | 11 +++++++++++ .../core/meeting/repository/MeetingReaderImpl.java | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/src/main/java/com/join/core/meeting/domain/MeetingReader.java b/src/main/java/com/join/core/meeting/domain/MeetingReader.java index 85e4d48..1df6f30 100644 --- a/src/main/java/com/join/core/meeting/domain/MeetingReader.java +++ b/src/main/java/com/join/core/meeting/domain/MeetingReader.java @@ -9,4 +9,5 @@ public interface MeetingReader { Meeting getMeetingById(Long meetingId); Meeting findByStudyIdAndMeetingNo(Long studyId, int meetingNo); List findMeetingsByAvatarIdForStudies(Long avatarId); + List findMeetingsByStudyId(Long studyId); } diff --git a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepository.java b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepository.java index 1b41631..94b244b 100644 --- a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepository.java +++ b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepository.java @@ -8,4 +8,5 @@ public interface MeetingQueryRepository { List findMeetingsByAvatarIdAndEnrollmentStatuses(Long avatarId, List statuses); + List findMeetingsByStudyId(Long studyId); } diff --git a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java index 1c41cdf..8b9835c 100644 --- a/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/meeting/repository/MeetingQueryRepositoryImpl.java @@ -37,4 +37,15 @@ public List findMeetingsByAvatarIdAndEnrollmentStatuses(Long avatarId, ) .fetch(); } + + @Override + public List findMeetingsByStudyId(Long studyId) { + return queryFactory + .selectFrom(meeting) + .join(meeting.study, study) + .where( + study.id.eq(studyId) + ) + .fetch(); + } } diff --git a/src/main/java/com/join/core/meeting/repository/MeetingReaderImpl.java b/src/main/java/com/join/core/meeting/repository/MeetingReaderImpl.java index bf88ef4..4ca7360 100644 --- a/src/main/java/com/join/core/meeting/repository/MeetingReaderImpl.java +++ b/src/main/java/com/join/core/meeting/repository/MeetingReaderImpl.java @@ -43,4 +43,9 @@ public List findMeetingsByAvatarIdForStudies(Long avatarId) { return Stream.concat(meetingsForJoinedStudies.stream(), meetingsForLeftStudies.stream()).toList(); } + + @Override + public List findMeetingsByStudyId(Long studyId) { + return meetingQueryRepository.findMeetingsByStudyId(studyId); + } } From 4942f42e89ceb083952254e3427ca4bb58e5740e Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:22:34 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=EC=97=90=20=EC=86=8C=EC=86=8D=EB=90=9C=20?= =?UTF-8?q?=EC=95=84=EB=B0=94=ED=83=80=20=EC=A4=91=20=EB=8C=80=EA=B8=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=9C=EC=99=B8=ED=95=9C=20=EC=95=84=EB=B0=94?= =?UTF-8?q?=ED=83=80=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../join/core/avatar/domain/AvatarReader.java | 3 +++ .../core/avatar/repository/AvatarReaderImpl.java | 11 ++++++++++- .../repository/EnrollmentQueryRepository.java | 3 +++ .../EnrollmentQueryRepositoryImpl.java | 16 +++++++++++++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/join/core/avatar/domain/AvatarReader.java b/src/main/java/com/join/core/avatar/domain/AvatarReader.java index 92aedf4..65c0997 100644 --- a/src/main/java/com/join/core/avatar/domain/AvatarReader.java +++ b/src/main/java/com/join/core/avatar/domain/AvatarReader.java @@ -1,5 +1,7 @@ package com.join.core.avatar.domain; +import java.util.List; + public interface AvatarReader { Avatar getAvatarById(Long avatarId); @@ -11,4 +13,5 @@ public interface AvatarReader { Avatar getAvatarByAvatarToken(String avatarToken); + List findAvatarsExceptPendingByStudyId(Long studyId); } diff --git a/src/main/java/com/join/core/avatar/repository/AvatarReaderImpl.java b/src/main/java/com/join/core/avatar/repository/AvatarReaderImpl.java index 6dc48fc..4b5e185 100644 --- a/src/main/java/com/join/core/avatar/repository/AvatarReaderImpl.java +++ b/src/main/java/com/join/core/avatar/repository/AvatarReaderImpl.java @@ -1,21 +1,25 @@ package com.join.core.avatar.repository; +import com.join.core.avatar.domain.Avatar; import com.join.core.avatar.domain.AvatarInfo; import com.join.core.avatar.domain.AvatarInfoMapper; import com.join.core.avatar.domain.AvatarReader; -import com.join.core.avatar.domain.Avatar; import com.join.core.common.exception.ErrorCode; import com.join.core.common.exception.impl.EntityNotFoundException; +import com.join.core.enrollment.repository.EnrollmentQueryRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @RequiredArgsConstructor @Repository public class AvatarReaderImpl implements AvatarReader { private final AvatarRepository avatarRepository; private final AvatarInfoMapper avatarInfoMapper; + private final EnrollmentQueryRepository enrollmentQueryRepository; @Override public Avatar getAvatarById(Long id) { @@ -47,4 +51,9 @@ public Avatar getAvatarByAvatarToken(String avatarToken) { .orElseThrow(() -> new EntityNotFoundException(ErrorCode.AVATAR_NOT_FOUND)); } + @Transactional(readOnly = true) + @Override + public List findAvatarsExceptPendingByStudyId(Long studyId) { + return enrollmentQueryRepository.findAvatarsExceptPendingByStudyId(studyId); + } } diff --git a/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepository.java b/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepository.java index 962f57c..628148b 100644 --- a/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepository.java +++ b/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepository.java @@ -2,8 +2,11 @@ import com.join.core.avatar.domain.Avatar; +import java.util.List; + public interface EnrollmentQueryRepository { Double getMemberAverageByStudyId(Long studyId); Avatar getLeaderByStudyId(Long studyId); + List findAvatarsExceptPendingByStudyId(Long studyId); } diff --git a/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepositoryImpl.java b/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepositoryImpl.java index 6a98710..f51c402 100644 --- a/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/enrollment/repository/EnrollmentQueryRepositoryImpl.java @@ -7,7 +7,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import static com.join.core.enrollment.domain.QEnrollment.enrollment; +import java.util.List; + +import static com.join.core.enrollment.domain.QEnrollment.*; @RequiredArgsConstructor @Repository @@ -40,4 +42,16 @@ public Avatar getLeaderByStudyId(Long studyId) { ) .fetchOne(); } + + @Override + public List findAvatarsExceptPendingByStudyId(Long studyId) { + return queryFactory + .select(enrollment.avatar) + .from(enrollment) + .where( + enrollment.study.id.eq(studyId), + enrollment.status.in(EnrollmentStatus.JOINED, EnrollmentStatus.LEFT, EnrollmentStatus.REQUEST_LEAVE) + ) + .fetch(); + } } From 618e452a9c493f468e3fd1651d702fbde6101ce7 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:28:47 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pending을 제외한 가입 및 탈퇴 유저 조회 분 - 스터디 전체 출석 리스트 조회 추가 - 스터디에서 특정 유저의 출석 리스트 조회 추가 --- .../repository/AttendanceQueryRepository.java | 11 +++ .../AttendanceQueryRepositoryImpl.java | 68 ++++++++++++++----- .../repository/AttendanceReaderImpl.java | 19 ++++++ .../attendance/service/AttendanceReader.java | 8 +++ 4 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepository.java b/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepository.java index cd0353a..9a57d7d 100644 --- a/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepository.java +++ b/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepository.java @@ -7,4 +7,15 @@ public interface AttendanceQueryRepository { List findAttendancesByAvatarIdAndEnrollmentStatuses(Long avatarId, List statuses); + + List findAttendancesByStudyIdInEnrollmentStatuses( + Long studyId, + List statuses + ); + + List findAttendancesByAvatarIdAndStudyIdInEnrollmentStatuses( + Long avatarId, + Long studyId, + List statuses + ); } diff --git a/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepositoryImpl.java b/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepositoryImpl.java index 872efae..d8a5914 100644 --- a/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/attendance/repository/AttendanceQueryRepositoryImpl.java @@ -1,11 +1,7 @@ package com.join.core.attendance.repository; import com.join.core.attendance.domain.Attendance; -import com.join.core.attendance.domain.QAttendance; import com.join.core.enrollment.constant.EnrollmentStatus; -import com.join.core.enrollment.domain.QEnrollment; -import com.join.core.meeting.domain.QMeeting; -import com.join.core.study.domain.QStudy; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -13,6 +9,12 @@ import java.util.List; +import static com.join.core.attendance.domain.QAttendance.*; +import static com.join.core.avatar.domain.QAvatar.*; +import static com.join.core.enrollment.domain.QEnrollment.*; +import static com.join.core.meeting.domain.QMeeting.*; +import static com.join.core.study.domain.QStudy.*; + @RequiredArgsConstructor @Component public class AttendanceQueryRepositoryImpl implements AttendanceQueryRepository { @@ -21,26 +23,56 @@ public class AttendanceQueryRepositoryImpl implements AttendanceQueryRepository @Override public List findAttendancesByAvatarIdAndEnrollmentStatuses(Long avatarId, List statuses) { - QAttendance attendance = QAttendance.attendance; - QMeeting meeting = QMeeting.meeting; - QStudy study = QStudy.study; - QEnrollment enrollment = QEnrollment.enrollment; - return queryFactory .selectFrom(attendance) .distinct() .join(attendance.meeting, meeting) .join(meeting.study, study) .where( - study.id.in( - JPAExpressions - .select(enrollment.study.id) - .from(enrollment) - .where( - enrollment.avatar.id.eq(avatarId) - .and(enrollment.status.in(statuses)) - ) - ) + JPAExpressions + .selectOne() + .from(enrollment) + .where( + enrollment.study.id.eq(study.id) + .and(enrollment.avatar.id.eq(avatarId)) + .and(enrollment.status.in(statuses)) + ) + .exists(), + attendance.avatar.id.eq(avatarId) + ) + .fetch(); + } + + @Override + public List findAttendancesByStudyIdInEnrollmentStatuses(Long studyId, List statuses) { + return queryFactory + .selectFrom(attendance) + .join(attendance.meeting, meeting) + .join(attendance.avatar, avatar) + .join(meeting.study, study).on(study.id.eq(studyId)) + .leftJoin(enrollment).on( + enrollment.study.eq(study) + .and(enrollment.avatar.eq(avatar)) + ) + .where( + enrollment.status.in(statuses) + ) + .fetch(); + } + + @Override + public List findAttendancesByAvatarIdAndStudyIdInEnrollmentStatuses(Long avatarId, Long studyId, List statuses) { + return queryFactory + .selectFrom(attendance) + .join(attendance.meeting, meeting) + .join(attendance.avatar, avatar).on(avatar.id.eq(avatarId)) + .join(meeting.study, study).on(study.id.eq(studyId)) + .leftJoin(enrollment).on( + enrollment.study.eq(study) + .and(enrollment.avatar.eq(avatar)) + ) + .where( + enrollment.status.in(statuses) ) .fetch(); } diff --git a/src/main/java/com/join/core/attendance/repository/AttendanceReaderImpl.java b/src/main/java/com/join/core/attendance/repository/AttendanceReaderImpl.java index 679cdd1..badc123 100644 --- a/src/main/java/com/join/core/attendance/repository/AttendanceReaderImpl.java +++ b/src/main/java/com/join/core/attendance/repository/AttendanceReaderImpl.java @@ -36,4 +36,23 @@ public List findAttendanceForLeftStudy(Long avatarId) { return attendanceQueryRepository.findAttendancesByAvatarIdAndEnrollmentStatuses(avatarId, List.of(EnrollmentStatus.LEFT)); } + @Override + public List findByStudyIdForJoinedStudy(Long studyId) { + return attendanceQueryRepository.findAttendancesByStudyIdInEnrollmentStatuses(studyId, List.of(EnrollmentStatus.JOINED, EnrollmentStatus.REQUEST_LEAVE)); + } + + @Override + public List findByStudyIdForLeftStudy(Long studyId) { + return attendanceQueryRepository.findAttendancesByStudyIdInEnrollmentStatuses(studyId, List.of(EnrollmentStatus.LEFT)); + } + + @Override + public List findByAvatarIdAndStudyIdForJoinedStudy(Long avatarId, Long studyId) { + return attendanceQueryRepository.findAttendancesByAvatarIdAndStudyIdInEnrollmentStatuses(avatarId, studyId, List.of(EnrollmentStatus.JOINED, EnrollmentStatus.REQUEST_LEAVE)); + } + + @Override + public List findByAvatarIdAndStudyIdForLeftStudy(Long avatarId, Long studyId) { + return attendanceQueryRepository.findAttendancesByAvatarIdAndStudyIdInEnrollmentStatuses(avatarId, studyId, List.of(EnrollmentStatus.LEFT)); + } } diff --git a/src/main/java/com/join/core/attendance/service/AttendanceReader.java b/src/main/java/com/join/core/attendance/service/AttendanceReader.java index d9bd4ee..12b533b 100644 --- a/src/main/java/com/join/core/attendance/service/AttendanceReader.java +++ b/src/main/java/com/join/core/attendance/service/AttendanceReader.java @@ -14,4 +14,12 @@ public interface AttendanceReader { List findAttendanceForJoinedStudy(Long avatarId); List findAttendanceForLeftStudy(Long avatarId); + + List findByStudyIdForJoinedStudy(Long studyId); + + List findByStudyIdForLeftStudy(Long studyId); + + List findByAvatarIdAndStudyIdForJoinedStudy(Long avatarId, Long studyId); + + List findByAvatarIdAndStudyIdForLeftStudy(Long avatarId, Long studyId); } From 8009469871c0df544547249a371358657402f6e9 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:29:41 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pending을 제외한 가입 및 탈퇴 유저 조회 분리 - 스터디 전체 인증 리스트 조회 추가 - 스터디에서 특정 유저의 인증 리스트 조회 추가 - 스터디에서 특정 유저의 인증 확인이 필요한지에 대한 여부 조회 추가 --- .../repository/ProofQueryRepository.java | 3 + .../repository/ProofQueryRepositoryImpl.java | 82 +++++++++++++++---- .../proof/repository/ProofReaderImpl.java | 25 ++++++ .../join/core/proof/service/ProofReader.java | 5 ++ 4 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/join/core/proof/repository/ProofQueryRepository.java b/src/main/java/com/join/core/proof/repository/ProofQueryRepository.java index 5af918b..807c367 100644 --- a/src/main/java/com/join/core/proof/repository/ProofQueryRepository.java +++ b/src/main/java/com/join/core/proof/repository/ProofQueryRepository.java @@ -7,4 +7,7 @@ public interface ProofQueryRepository { List findProofsByAvatarIdAndEnrollmentStatus(Long avatarId, List statuses); + List findProofsByStudyIdInEnrollmentStatuses(Long studyId, List statuses); + List findByAvatarIdAndStudyIdInEnrollmentStatuses(Long avatarId, Long studyId, List statuses); + boolean allProofsHaveApprovedStatus(Long avatarId, Long studyId); } diff --git a/src/main/java/com/join/core/proof/repository/ProofQueryRepositoryImpl.java b/src/main/java/com/join/core/proof/repository/ProofQueryRepositoryImpl.java index e3e7da8..eb4d5ec 100644 --- a/src/main/java/com/join/core/proof/repository/ProofQueryRepositoryImpl.java +++ b/src/main/java/com/join/core/proof/repository/ProofQueryRepositoryImpl.java @@ -1,11 +1,8 @@ package com.join.core.proof.repository; import com.join.core.enrollment.constant.EnrollmentStatus; -import com.join.core.enrollment.domain.QEnrollment; -import com.join.core.meeting.domain.QMeeting; +import com.join.core.proof.constant.ProofStatus; import com.join.core.proof.domain.Proof; -import com.join.core.proof.domain.QProof; -import com.join.core.study.domain.QStudy; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -13,6 +10,12 @@ import java.util.List; +import static com.join.core.avatar.domain.QAvatar.*; +import static com.join.core.enrollment.domain.QEnrollment.*; +import static com.join.core.meeting.domain.QMeeting.*; +import static com.join.core.proof.domain.QProof.*; +import static com.join.core.study.domain.QStudy.*; + @RequiredArgsConstructor @Component public class ProofQueryRepositoryImpl implements ProofQueryRepository { @@ -21,27 +24,70 @@ public class ProofQueryRepositoryImpl implements ProofQueryRepository { @Override public List findProofsByAvatarIdAndEnrollmentStatus(Long avatarId, List statuses) { - QProof proof = QProof.proof; - QMeeting meeting = QMeeting.meeting; - QStudy study = QStudy.study; - QEnrollment enrollment = QEnrollment.enrollment; - return queryFactory .selectFrom(proof) .distinct() .join(proof.meeting, meeting) .join(meeting.study, study) .where( - study.id.in( - JPAExpressions - .select(enrollment.study.id) - .from(enrollment) - .where( - enrollment.avatar.id.eq(avatarId) - .and(enrollment.status.in(statuses)) - ) - ) + JPAExpressions + .selectOne() + .from(enrollment) + .where( + enrollment.study.id.eq(study.id) + .and(enrollment.avatar.id.eq(avatarId)) + .and(enrollment.status.in(statuses)) + ) + .exists(), + proof.avatar.id.eq(avatarId) ) .fetch(); } + + @Override + public List findProofsByStudyIdInEnrollmentStatuses(Long studyId, List statuses) { + return queryFactory + .selectFrom(proof) + .join(proof.meeting, meeting) + .join(proof.avatar, avatar) + .join(meeting.study, study).on(study.id.eq(studyId)) + .leftJoin(enrollment).on( + enrollment.study.eq(study) + .and(enrollment.avatar.eq(avatar)) + ) + .where( + enrollment.status.in(statuses) + ) + .fetch(); + } + + @Override + public List findByAvatarIdAndStudyIdInEnrollmentStatuses(Long avatarId, Long studyId, List statuses) { + return queryFactory + .selectFrom(proof) + .join(proof.avatar, avatar).on(proof.avatar.id.eq(avatarId)) + .join(proof.meeting, meeting) + .join(meeting.study, study).on(study.id.eq(studyId)) + .leftJoin(enrollment).on( + enrollment.study.eq(study) + .and(enrollment.avatar.eq(avatar))) + .where(enrollment.status.in(statuses)) + .fetch(); + } + + @Override + public boolean allProofsHaveApprovedStatus(Long avatarId, Long studyId) { + Long countNonApproved = queryFactory + .select(proof.count()) + .from(proof) + .join(avatar).on(proof.avatar.id.eq(avatarId)) + .join(meeting).on(proof.meeting.id.eq(meeting.id)) + .join(study).on(study.id.eq(studyId)) + .where( + proof.status.ne(ProofStatus.APPROVED) + ) + .fetchOne(); + + return countNonApproved == null || countNonApproved == 0; + } } diff --git a/src/main/java/com/join/core/proof/repository/ProofReaderImpl.java b/src/main/java/com/join/core/proof/repository/ProofReaderImpl.java index 5edd5ea..ef0f904 100644 --- a/src/main/java/com/join/core/proof/repository/ProofReaderImpl.java +++ b/src/main/java/com/join/core/proof/repository/ProofReaderImpl.java @@ -45,4 +45,29 @@ public Proof getProofById(Long proofId) { return proofRepository.findById(proofId) .orElseThrow(() -> new BadRequestException(ErrorCode.INVALID_PROOF_ID)); } + + @Override + public List findByStudyIdForJoinedStudy(Long studyId) { + return proofQueryRepository.findProofsByStudyIdInEnrollmentStatuses(studyId, List.of(EnrollmentStatus.JOINED, EnrollmentStatus.REQUEST_LEAVE)); + } + + @Override + public List findByStudyIdForLeftStudy(Long studyId) { + return proofQueryRepository.findProofsByStudyIdInEnrollmentStatuses(studyId, List.of(EnrollmentStatus.LEFT)); + } + + @Override + public List findByAvatarIdAndStudyIdForJoinedStudy(Long avatarId, Long studyId) { + return proofQueryRepository.findByAvatarIdAndStudyIdInEnrollmentStatuses(avatarId, studyId, List.of(EnrollmentStatus.JOINED, EnrollmentStatus.REQUEST_LEAVE)); + } + + @Override + public List findByAvatarIdAndStudyIdForLeftStudy(Long avatarId, Long studyId) { + return proofQueryRepository.findByAvatarIdAndStudyIdInEnrollmentStatuses(avatarId, studyId, List.of(EnrollmentStatus.LEFT)); + } + + @Override + public boolean isFullyApproved(Long avatarId, Long studyId) { + return proofQueryRepository.allProofsHaveApprovedStatus(avatarId, studyId); + } } diff --git a/src/main/java/com/join/core/proof/service/ProofReader.java b/src/main/java/com/join/core/proof/service/ProofReader.java index b7f1acb..33caa1b 100644 --- a/src/main/java/com/join/core/proof/service/ProofReader.java +++ b/src/main/java/com/join/core/proof/service/ProofReader.java @@ -12,4 +12,9 @@ public interface ProofReader { List findProofsByAvatarIdForJoinedStudies(Long avatarId); List findProofsByAvatarIdForLeftStudies(Long avatarId); Proof getProofById(Long proofId); + List findByStudyIdForJoinedStudy(Long studyId); + List findByStudyIdForLeftStudy(Long studyId); + List findByAvatarIdAndStudyIdForJoinedStudy(Long avatarId, Long studyId); + List findByAvatarIdAndStudyIdForLeftStudy(Long avatarId, Long studyId); + boolean isFullyApproved(Long avatarId, Long studyId); } From f8c8f4840dca2e489590061a0ac0411a5372e766 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:31:24 +0900 Subject: [PATCH 09/14] =?UTF-8?q?fix:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../join/core/attendance/service/AttendanceRateService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java index ed47165..6d89d57 100644 --- a/src/main/java/com/join/core/attendance/service/AttendanceRateService.java +++ b/src/main/java/com/join/core/attendance/service/AttendanceRateService.java @@ -16,6 +16,7 @@ @RequiredArgsConstructor public class AttendanceRateService { + private final AvatarReader avatarReader; private final AttendanceReader attendanceReader; private final MeetingReader meetingReader; @@ -51,7 +52,7 @@ public double calculateTeamAttendanceRateForStudy(Long studyId) { List attendancesForJoinedStudies = attendanceReader.findByStudyIdForJoinedStudy(studyId); List attendancesForLeftStudies = attendanceReader.findByStudyIdForLeftStudy(studyId); - long countStudyMembers = avatarReader.findAvatarsExceptLeftByStudyId(studyId).size(); + long countStudyMembers = avatarReader.findAvatarsExceptPendingByStudyId(studyId).size(); long countAllMeetings = meetingReader.findMeetingsByStudyId(studyId).size(); long totalMeetings = countAllMeetings * countStudyMembers; From 4b15039d2a328e7908b1cbc008c3369e2a99711d Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:31:58 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EC=9C=A8(=ED=8C=80,=20=EA=B0=9C=EC=9D=B8)=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/proof/service/ProofRateService.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/main/java/com/join/core/proof/service/ProofRateService.java b/src/main/java/com/join/core/proof/service/ProofRateService.java index 02a6556..530d6a2 100644 --- a/src/main/java/com/join/core/proof/service/ProofRateService.java +++ b/src/main/java/com/join/core/proof/service/ProofRateService.java @@ -1,5 +1,6 @@ package com.join.core.proof.service; +import com.join.core.avatar.domain.AvatarReader; import com.join.core.common.util.NumberUtil; import com.join.core.enrollment.constant.EnrollmentStatus; import com.join.core.meeting.domain.MeetingReader; @@ -15,6 +16,7 @@ @RequiredArgsConstructor public class ProofRateService { + private final AvatarReader avatarReader; private final MeetingReader meetingReader; private final ProofReader proofReader; @@ -46,5 +48,62 @@ public double calculateIndividualProofRate(Long avatarId) { return NumberUtil.round(2, totalProof / totalMeetings * 100); } + + @Transactional(readOnly = true) + public double calculateTeamAttendanceRateForStudy(Long studyId) { + List attendancesForJoinedStudies = proofReader.findByStudyIdForJoinedStudy(studyId); + List attendancesForLeftStudies = proofReader.findByStudyIdForLeftStudy(studyId); + + long countStudyMembers = avatarReader.findAvatarsExceptPendingByStudyId(studyId).size(); + long countMeetings = meetingReader.findMeetingsByStudyId(studyId).size(); + long totalMeetings = countMeetings * countStudyMembers; + + double totalAttendance = Stream.concat( + attendancesForJoinedStudies.stream(), + attendancesForLeftStudies.stream() + ) + .mapToDouble(attendance -> { + double reflectionRate = attendance.getStatus().getReflectionRate(); + + if (attendancesForLeftStudies.contains(attendance)) { + reflectionRate *= EnrollmentStatus.LEFT.getReflectionRate(); + } + + return reflectionRate; + }) + .sum(); + + if (totalAttendance == 0) return 0.0; + + return NumberUtil.round(2, totalAttendance / totalMeetings * 100); + } + + @Transactional(readOnly = true) + public double calculateMembersAttendanceRateForStudy(Long avatarId, Long studyId) { + List proofsForJoinedStudies = proofReader.findByAvatarIdAndStudyIdForJoinedStudy(avatarId, studyId); + List proofsForLeftStudies = proofReader.findByAvatarIdAndStudyIdForLeftStudy(avatarId, studyId); + + long totalMeetings = meetingReader.findMeetingsByStudyId(studyId).size(); + + double totalProof = Stream.concat( + proofsForJoinedStudies.stream(), + proofsForLeftStudies.stream() + ) + .mapToDouble(proof -> { + double reflectionRate = proof.getStatus().getReflectionRate(); + + if (proofsForLeftStudies.contains(proof)) { + reflectionRate *= EnrollmentStatus.LEFT.getReflectionRate(); + } + + return reflectionRate; + }) + .sum(); + + if (totalProof == 0) { + return 0.0; + } + + return NumberUtil.round(2, totalProof / totalMeetings * 100); } } From 0924160a82a893dc468979a29923c694df5c623a Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:32:21 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/MyManagedStudyInfoResponse.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/com/join/core/avatar/dto/response/MyManagedStudyInfoResponse.java diff --git a/src/main/java/com/join/core/avatar/dto/response/MyManagedStudyInfoResponse.java b/src/main/java/com/join/core/avatar/dto/response/MyManagedStudyInfoResponse.java new file mode 100644 index 0000000..8e530d7 --- /dev/null +++ b/src/main/java/com/join/core/avatar/dto/response/MyManagedStudyInfoResponse.java @@ -0,0 +1,58 @@ +package com.join.core.avatar.dto.response; + +import com.join.core.avatar.domain.Avatar; +import com.join.core.study.constant.StudyStatus; +import com.join.core.study.domain.Study; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +public record MyManagedStudyInfoResponse( + @Schema(description = "스터디 이름", example = "직장인 영어 회화 스터디") + String name, + @Schema(description = "모집 상태", example = "RECRUITING, READY, ACTIVE, COMPLETED 중 하나(모집중, 모집완료, 활동중, 활동완료)") + StudyStatus status, + @Schema(description = "평균 출결률", example = "56.25") + double teamAverageAttendanceRate, + @Schema(description = "평균 인증률", example = "56.25") + double teamAverageProofRate, + @Schema(description = "스터디원 별 평균 출결율, 인증률 및 모든 인증 승인 여부 리스트") + List studyMembersInfos, + @Schema(description = "스터디 카카오톡 링크", example = "https://open.kakao.com/o/joinjoinjoin") + String kakaoUrl +) { + public record StudyMemberAchievementDto( + @Schema(description = "스터디원 아바타 토큰", example = "token") + String avatarToken, + @Schema(description = "스터디원 별명", example = "닉네임") + String nickname, + @Schema(description = "이 스터디에서의 평균 출석률", example = "87.5") + double averageAttendanceRate, + @Schema(description = "이 스터디에서의 평균 인증률", example = "87.5") + double averageProofRate, + @Schema(description = "이 스터디의 모든 인증이 승인되었는지에 대한 여부", example = "true") + boolean isFullyApproved + ) { + + public static StudyMemberAchievementDto of(Avatar avatar, double averageAttendanceRate, double averageProofRate, boolean isFullyVerified) { + return new StudyMemberAchievementDto( + avatar.getAvatarToken(), + avatar.getNickname(), + averageAttendanceRate, + averageProofRate, + isFullyVerified + ); + } + } + + public static MyManagedStudyInfoResponse of(Study study, double teamAverageAttendanceRate, double teamAverageProofRate, List achievementDtos) { + return new MyManagedStudyInfoResponse( + study.getStudyName(), + study.getStatus(), + teamAverageAttendanceRate, + teamAverageProofRate, + achievementDtos, + study.getKakaoUrl() + ); + } +} From be34dd4e66f72a3ebb9086b4eb66d270e5c0f314 Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:32:37 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/avatar/domain/MyPageService.java | 4 +++ .../core/avatar/domain/MyPageServiceImpl.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/main/java/com/join/core/avatar/domain/MyPageService.java b/src/main/java/com/join/core/avatar/domain/MyPageService.java index db82b90..83f70b3 100644 --- a/src/main/java/com/join/core/avatar/domain/MyPageService.java +++ b/src/main/java/com/join/core/avatar/domain/MyPageService.java @@ -1,8 +1,12 @@ package com.join.core.avatar.domain; +import com.join.core.avatar.dto.response.MyManagedStudyInfoResponse; import com.join.core.avatar.dto.response.MyPageInfoResponse; +import java.util.List; + public interface MyPageService { MyPageInfoResponse getMyPageInfo(Long avatarId); + List getMyManagedStudies(Long avatarId); } diff --git a/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java b/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java index 75f0dcd..73457ab 100644 --- a/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java +++ b/src/main/java/com/join/core/avatar/domain/MyPageServiceImpl.java @@ -1,19 +1,27 @@ package com.join.core.avatar.domain; import com.join.core.attendance.service.AttendanceRateService; +import com.join.core.avatar.dto.response.MyManagedStudyInfoResponse; import com.join.core.avatar.dto.response.MyPageInfoResponse; import com.join.core.proof.service.ProofRateService; +import com.join.core.proof.service.ProofReader; +import com.join.core.study.domain.Study; +import com.join.core.study.service.StudyReader; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @RequiredArgsConstructor @Transactional(readOnly = true) @Service public class MyPageServiceImpl implements MyPageService { private final AvatarReader avatarReader; + private final StudyReader studyReader; private final AttendanceRateService attendanceRateService; + private final ProofReader proofReader; private final ProofRateService proofRateService; @Transactional(readOnly = true) @@ -27,4 +35,31 @@ public MyPageInfoResponse getMyPageInfo(Long avatarId) { return MyPageInfoResponse.of(avatar, averageAttendanceRate, averageProofRate); } + + @Transactional(readOnly = true) + @Override + public List getMyManagedStudies(Long avatarId) { + List managedStudies = studyReader.getStudiesByLeaderAvatarId(avatarId); + + return managedStudies.stream().map( + study -> { + double teamAttendanceRateForStudy = attendanceRateService.calculateTeamAttendanceRateForStudy(study.getId()); + double teamProofRateForStudy = proofRateService.calculateTeamAttendanceRateForStudy(study.getId()); + List achievementDtos = getMembersRatesAndApprovedStatus(study); + + return MyManagedStudyInfoResponse.of(study, teamAttendanceRateForStudy, teamProofRateForStudy, achievementDtos); + } + ).toList(); + } + + private List getMembersRatesAndApprovedStatus(Study study) { + List avatars = avatarReader.findAvatarsExceptPendingByStudyId(study.getId()); + return avatars.stream() + .map(avatar -> { + double attendanceRate = attendanceRateService.calculateMemberAttendanceRateForStudy(avatar.getId(), study.getId()); + double proofRate = proofRateService.calculateMembersAttendanceRateForStudy(avatar.getId(), study.getId()); + boolean isFullyApproved = proofReader.isFullyApproved(avatar.getId(), study.getId()); + return MyManagedStudyInfoResponse.StudyMemberAchievementDto.of(avatar, attendanceRate, proofRate, isFullyApproved); + }).toList(); + } } From 7da243ba3576f119607050da2866f8c58745b0fe Mon Sep 17 00:00:00 2001 From: moveuk Date: Sat, 1 Feb 2025 02:33:10 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20api=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../join/core/avatar/controller/MyPageController.java | 10 ++++++++++ .../specification/MyPageControllerSpecification.java | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/com/join/core/avatar/controller/MyPageController.java b/src/main/java/com/join/core/avatar/controller/MyPageController.java index 9c600e1..0f96cd8 100644 --- a/src/main/java/com/join/core/avatar/controller/MyPageController.java +++ b/src/main/java/com/join/core/avatar/controller/MyPageController.java @@ -3,6 +3,7 @@ import com.join.core.auth.domain.UserPrincipal; import com.join.core.avatar.controller.specification.MyPageControllerSpecification; import com.join.core.avatar.domain.MyPageService; +import com.join.core.avatar.dto.response.MyManagedStudyInfoResponse; import com.join.core.avatar.dto.response.MyPageInfoResponse; import com.join.core.common.response.ApiResponse; import lombok.RequiredArgsConstructor; @@ -12,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("${api.prefix}/my-page") @@ -25,4 +28,11 @@ public ApiResponse getMyPageInfo( @AuthenticationPrincipal UserPrincipal principal) { return ApiResponse.ok(myPageService.getMyPageInfo(principal.getAvatarId())); } + + @PreAuthorize("isAuthenticated()") + @GetMapping("/manage-study") + public ApiResponse> getMyManagedStudies( + @AuthenticationPrincipal UserPrincipal principal) { + return ApiResponse.ok(myPageService.getMyManagedStudies(principal.getAvatarId())); + } } diff --git a/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java b/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java index 65b8375..151d1ed 100644 --- a/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java +++ b/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java @@ -1,6 +1,7 @@ package com.join.core.avatar.controller.specification; import com.join.core.auth.domain.UserPrincipal; +import com.join.core.avatar.dto.response.MyManagedStudyInfoResponse; import com.join.core.avatar.dto.response.MyPageInfoResponse; import com.join.core.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -8,6 +9,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import java.util.List; + public interface MyPageControllerSpecification { @Tag(name = "${swagger.tag.user}") @@ -16,4 +19,11 @@ public interface MyPageControllerSpecification { security = {@SecurityRequirement(name = "session-token")}) ApiResponse getMyPageInfo( @AuthenticationPrincipal UserPrincipal principal); + + @Tag(name = "${swagger.tag.user}") + @Operation(summary = "마이페이지 운영중인 스터디 목록 조회 - 인증 필수", + description = "마이페이지 운영중인 스터디 목록을 조회해옵니다.", + security = {@SecurityRequirement(name = "session-token")}) + ApiResponse> getMyManagedStudies( + @AuthenticationPrincipal UserPrincipal principal); } From 9120a605ec418544a1987abc7c47ebfb1c6075dd Mon Sep 17 00:00:00 2001 From: moveuk Date: Sun, 2 Feb 2025 00:20:19 +0900 Subject: [PATCH 14/14] =?UTF-8?q?fix:=20swagger=20mypage=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specification/MyPageControllerSpecification.java | 4 ++-- src/main/resources/application-swagger.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java b/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java index 151d1ed..404fcb0 100644 --- a/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java +++ b/src/main/java/com/join/core/avatar/controller/specification/MyPageControllerSpecification.java @@ -13,14 +13,14 @@ public interface MyPageControllerSpecification { - @Tag(name = "${swagger.tag.user}") + @Tag(name = "${swagger.tag.my-page}") @Operation(summary = "마이페이지 조회 - 인증 필수", description = "마이페이지 조회", security = {@SecurityRequirement(name = "session-token")}) ApiResponse getMyPageInfo( @AuthenticationPrincipal UserPrincipal principal); - @Tag(name = "${swagger.tag.user}") + @Tag(name = "${swagger.tag.my-page}") @Operation(summary = "마이페이지 운영중인 스터디 목록 조회 - 인증 필수", description = "마이페이지 운영중인 스터디 목록을 조회해옵니다.", security = {@SecurityRequirement(name = "session-token")}) diff --git a/src/main/resources/application-swagger.yml b/src/main/resources/application-swagger.yml index 437607c..73f60fb 100644 --- a/src/main/resources/application-swagger.yml +++ b/src/main/resources/application-swagger.yml @@ -13,3 +13,4 @@ swagger: proof: '11. 회차 인증' evaluation: '12. 평가' block: '13. 차단' + my-page: '14. 마이페이지'