From 98d4681ccd7c5176d412dd7ab65b2bd7b64beea9 Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:32:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=88=98=EA=B0=95=EC=83=9D=20=EB=AA=85?= =?UTF-8?q?=EB=8B=A8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 수강생 명단 조회를 위한 레포지토리 메서드 추가 * refactor: 조회 쿼리가 한번만 나가도록 수정 * feat: curriculum 휴강 여부 전달 메서드 추가 * feat: 페이징 처리 추가 * feat: 휴강 enum 값 추가 * feat: 수강생 명단 조회 서비스 구현 * fix: 휴강일 경우 발생하는 NPE 제거 * refactor: static import 하도록 수정 * refactor: 중복 로직 메서드로 추출 * refactor: 과제 휴강시에도 정적 팩토리 메서드 사용하도록 수정 * fix: 0으로 나누는 일이 발생하지 않도록 수정 * fix: 페이지의 content를 가져와서 forEach를 돌리도록 수정 * refactor: errorcode static import 하도록 수정 * refactor: filter를 map으로 대체 * refactor: where의 조건 순서 최적화 * refactor: 삼항 연산자 메서드로 추출 --- .../study/api/MentorStudyController.java | 8 +- .../study/application/MentorStudyService.java | 90 ++++++++++++++++--- .../AssignmentHistoryCustomRepository.java | 2 + ...AssignmentHistoryCustomRepositoryImpl.java | 31 +++---- .../dao/AssignmentHistoryQueryMethod.java | 31 +++++++ .../dao/AssignmentHistoryRepository.java | 1 + .../study/dao/AttendanceCustomRepository.java | 2 + .../dao/AttendanceCustomRepositoryImpl.java | 11 ++- .../dao/StudyAchievementCustomRepository.java | 8 ++ .../StudyAchievementCustomRepositoryImpl.java | 27 ++++++ .../study/dao/StudyAchievementRepository.java | 7 ++ .../study/dao/StudyHistoryRepository.java | 6 +- .../domain/study/domain/vo/Curriculum.java | 4 + .../AssignmentSubmissionStatusResponse.java | 9 +- .../response/AttendanceStatusResponse.java | 7 +- .../StudyStudentCurriculumResponse.java | 2 +- .../dto/response/StudyStudentResponse.java | 61 ++++++++++++- .../study/dto/response/StudyTodoResponse.java | 24 ++++- 18 files changed, 283 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryQueryMethod.java create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepository.java create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepositoryImpl.java create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementRepository.java diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyController.java b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyController.java index d85ba3af1..06eb3695d 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyController.java @@ -10,6 +10,8 @@ import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -35,10 +37,10 @@ public ResponseEntity> getStudiesInCharge() { return ResponseEntity.ok(response); } - @Operation(summary = "스터디 수강생 명단 조회", description = "해당 스터디의 수강생 명단을 조회합니다") + @Operation(summary = "스터디 수강생 관리", description = "해당 스터디의 수강생을 관리합니다") @GetMapping("/{studyId}/students") - public ResponseEntity> getStudyStudents(@PathVariable Long studyId) { - List response = mentorStudyService.getStudyStudents(studyId); + public ResponseEntity> getStudyStudents(@PathVariable Long studyId, Pageable pageable) { + Page response = mentorStudyService.getStudyStudents(studyId, pageable); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyService.java b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyService.java index ed972f338..0988720a0 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyService.java @@ -1,8 +1,12 @@ package com.gdschongik.gdsc.domain.study.application; -import static com.gdschongik.gdsc.global.exception.ErrorCode.STUDY_NOT_FOUND; +import static com.gdschongik.gdsc.global.exception.ErrorCode.*; +import static java.util.stream.Collectors.*; import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository; +import com.gdschongik.gdsc.domain.study.dao.AttendanceRepository; +import com.gdschongik.gdsc.domain.study.dao.StudyAchievementRepository; import com.gdschongik.gdsc.domain.study.dao.StudyAnnouncementRepository; import com.gdschongik.gdsc.domain.study.dao.StudyDetailRepository; import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; @@ -17,14 +21,19 @@ import com.gdschongik.gdsc.domain.study.dto.request.StudyUpdateRequest; import com.gdschongik.gdsc.domain.study.dto.response.StudyResponse; import com.gdschongik.gdsc.domain.study.dto.response.StudyStudentResponse; +import com.gdschongik.gdsc.domain.study.dto.response.StudyTodoResponse; import com.gdschongik.gdsc.global.exception.CustomException; -import com.gdschongik.gdsc.global.exception.ErrorCode; import com.gdschongik.gdsc.global.util.MemberUtil; +import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,12 +43,15 @@ public class MentorStudyService { private final MemberUtil memberUtil; + private final StudyValidator studyValidator; + private final StudyDetailValidator studyDetailValidator; private final StudyRepository studyRepository; private final StudyAnnouncementRepository studyAnnouncementRepository; private final StudyHistoryRepository studyHistoryRepository; - private final StudyValidator studyValidator; private final StudyDetailRepository studyDetailRepository; - private final StudyDetailValidator studyDetailValidator; + private final StudyAchievementRepository studyAchievementRepository; + private final AttendanceRepository attendanceRepository; + private final AssignmentHistoryRepository assignmentHistoryRepository; @Transactional(readOnly = true) public List getStudiesInCharge() { @@ -49,15 +61,67 @@ public List getStudiesInCharge() { } @Transactional(readOnly = true) - public List getStudyStudents(Long studyId) { + public Page getStudyStudents(Long studyId, Pageable pageable) { Member currentMember = memberUtil.getCurrentMember(); - Study study = - studyRepository.findById(studyId).orElseThrow(() -> new CustomException(ErrorCode.STUDY_NOT_FOUND)); - + Study study = studyRepository.findById(studyId).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND)); studyValidator.validateStudyMentor(currentMember, study); - List studyHistories = studyHistoryRepository.findByStudyId(studyId); - return studyHistories.stream().map(StudyStudentResponse::from).toList(); + List studyDetails = studyDetailRepository.findAllByStudyId(studyId); + Page studyHistories = studyHistoryRepository.findByStudyId(studyId, pageable); + List studentIds = studyHistories.getContent().stream() + .map(studyHistory -> studyHistory.getStudent().getId()) + .toList(); + List studyAchievements = + studyAchievementRepository.findByStudyIdAndMemberIds(studyId, studentIds); + List attendances = attendanceRepository.findByStudyIdAndMemberIds(studyId, studentIds); + List assignmentHistories = + assignmentHistoryRepository.findByStudyIdAndMemberIds(studyId, studentIds); + + // StudyAchievement, Attendance, AssignmentHistory에 대해 Member의 id를 key로 하는 Map 생성 + Map> studyAchievementMap = studyAchievements.stream() + .collect(groupingBy( + studyAchievement -> studyAchievement.getStudent().getId())); + Map> attendanceMap = attendances.stream() + .collect(groupingBy(attendance -> attendance.getStudent().getId())); + Map> assignmentHistoryMap = assignmentHistories.stream() + .collect(groupingBy( + assignmentHistory -> assignmentHistory.getMember().getId())); + + List response = new ArrayList<>(); + studyHistories.getContent().forEach(studyHistory -> { + List currentStudyAchievements = + studyAchievementMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>()); + List currentAttendances = + attendanceMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>()); + List currentAssignmentHistories = + assignmentHistoryMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>()); + + List studyTodos = new ArrayList<>(); + studyDetails.forEach(studyDetail -> { + studyTodos.add(StudyTodoResponse.createAttendanceType( + studyDetail, LocalDate.now(), isAttended(currentAttendances, studyDetail))); + studyTodos.add(StudyTodoResponse.createAssignmentType( + studyDetail, getSubmittedAssignment(currentAssignmentHistories, studyDetail))); + }); + + response.add(StudyStudentResponse.of(studyHistory, currentStudyAchievements, studyTodos)); + }); + + return new PageImpl<>(response, pageable, studyHistories.getTotalElements()); + } + + private boolean isAttended(List attendances, StudyDetail studyDetail) { + return attendances.stream() + .anyMatch(attendance -> attendance.getStudyDetail().getId().equals(studyDetail.getId())); + } + + private AssignmentHistory getSubmittedAssignment( + List assignmentHistories, StudyDetail studyDetail) { + return assignmentHistories.stream() + .filter(assignmentHistory -> + assignmentHistory.getStudyDetail().getId().equals(studyDetail.getId())) + .findFirst() + .orElse(null); } @Transactional @@ -109,12 +173,12 @@ public void updateStudy(Long studyId, StudyUpdateRequest request) { List studyDetails = studyDetailRepository.findAllByStudyIdOrderByWeekAsc(studyId); // StudyDetail ID를 추출하여 Set으로 저장 - Set studyDetailIds = studyDetails.stream().map(StudyDetail::getId).collect(Collectors.toSet()); + Set studyDetailIds = studyDetails.stream().map(StudyDetail::getId).collect(toSet()); // 요청된 StudyCurriculumCreateRequest의 StudyDetail ID를 추출하여 Set으로 저장 Set requestIds = request.studyCurriculums().stream() .map(StudyCurriculumCreateRequest::studyDetailId) - .collect(Collectors.toSet()); + .collect(toSet()); studyDetailValidator.validateUpdateStudyDetail(studyDetailIds, requestIds); diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepository.java index cd5addbc8..0bda9e1c1 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepository.java @@ -11,5 +11,7 @@ public interface AssignmentHistoryCustomRepository { List findAssignmentHistoriesByStudentAndStudyId(Member member, Long studyId); + List findByStudyIdAndMemberIds(Long studyId, List memberIds); + void deleteByStudyIdAndMemberId(Long studyId, Long memberId); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepositoryImpl.java index df884bfc6..7b3f98724 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryCustomRepositoryImpl.java @@ -1,19 +1,18 @@ package com.gdschongik.gdsc.domain.study.dao; -import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.*; import static com.gdschongik.gdsc.domain.study.domain.QAssignmentHistory.*; import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.studyDetail; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory; import com.gdschongik.gdsc.domain.study.domain.Study; -import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public class AssignmentHistoryCustomRepositoryImpl implements AssignmentHistoryCustomRepository { +public class AssignmentHistoryCustomRepositoryImpl + implements AssignmentHistoryCustomRepository, AssignmentHistoryQueryMethod { private final JPAQueryFactory queryFactory; @@ -28,18 +27,6 @@ public boolean existsSubmittedAssignmentByMemberAndStudy(Member member, Study st return fetchOne != null; } - private BooleanExpression eqMember(Member member) { - return member == null ? null : assignmentHistory.member.eq(member); - } - - private BooleanExpression eqStudy(Study study) { - return study == null ? null : assignmentHistory.studyDetail.study.eq(study); - } - - private BooleanExpression isSubmitted() { - return assignmentHistory.submissionStatus.in(FAILURE, SUCCESS); - } - @Override public List findAssignmentHistoriesByStudentAndStudyId(Member currentMember, Long studyId) { return queryFactory @@ -50,10 +37,6 @@ public List findAssignmentHistoriesByStudentAndStudyId(Member .fetch(); } - private BooleanExpression eqStudyId(Long studyId) { - return studyId != null ? studyDetail.study.id.eq(studyId) : null; - } - @Override public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) { queryFactory @@ -62,7 +45,13 @@ public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) { .execute(); } - private BooleanExpression eqMemberId(Long memberId) { - return memberId != null ? assignmentHistory.member.id.eq(memberId) : null; + @Override + public List findByStudyIdAndMemberIds(Long studyId, List memberIds) { + return queryFactory + .selectFrom(assignmentHistory) + .innerJoin(assignmentHistory.studyDetail, studyDetail) + .fetchJoin() + .where(assignmentHistory.member.id.in(memberIds), eqStudyId(studyId)) + .fetch(); } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryQueryMethod.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryQueryMethod.java new file mode 100644 index 000000000..6b06147d2 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryQueryMethod.java @@ -0,0 +1,31 @@ +package com.gdschongik.gdsc.domain.study.dao; + +import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.*; +import static com.gdschongik.gdsc.domain.study.domain.QAssignmentHistory.*; +import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.*; + +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.study.domain.Study; +import com.querydsl.core.types.dsl.BooleanExpression; + +public interface AssignmentHistoryQueryMethod { + default BooleanExpression eqMember(Member member) { + return member == null ? null : assignmentHistory.member.eq(member); + } + + default BooleanExpression eqStudy(Study study) { + return study == null ? null : assignmentHistory.studyDetail.study.eq(study); + } + + default BooleanExpression isSubmitted() { + return assignmentHistory.submissionStatus.in(FAILURE, SUCCESS); + } + + default BooleanExpression eqStudyId(Long studyId) { + return studyId != null ? studyDetail.study.id.eq(studyId) : null; + } + + default BooleanExpression eqMemberId(Long memberId) { + return memberId != null ? assignmentHistory.member.id.eq(memberId) : null; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryRepository.java index 1c882f5ee..4e11bf86c 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AssignmentHistoryRepository.java @@ -8,5 +8,6 @@ public interface AssignmentHistoryRepository extends JpaRepository, AssignmentHistoryCustomRepository { + // todo: public 제거 public Optional findByMemberAndStudyDetail(Member member, StudyDetail studyDetail); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepository.java index e04482f5d..f7a488e8e 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepository.java @@ -7,5 +7,7 @@ public interface AttendanceCustomRepository { List findByMemberAndStudyId(Member member, Long studyId); + List findByStudyIdAndMemberIds(Long studyId, List memberIds); + void deleteByStudyIdAndMemberId(Long studyId, Long memberId); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepositoryImpl.java index af382f4d2..91cccd2f3 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/AttendanceCustomRepositoryImpl.java @@ -1,6 +1,5 @@ package com.gdschongik.gdsc.domain.study.dao; -import static com.gdschongik.gdsc.domain.member.domain.QMember.member; import static com.gdschongik.gdsc.domain.study.domain.QAttendance.attendance; import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.studyDetail; @@ -26,6 +25,16 @@ public List findByMemberAndStudyId(Member member, Long studyId) { .fetch(); } + @Override + public List findByStudyIdAndMemberIds(Long studyId, List memberIds) { + return queryFactory + .selectFrom(attendance) + .innerJoin(attendance.studyDetail, studyDetail) + .fetchJoin() + .where(attendance.student.id.in(memberIds), eqStudyId(studyId)) + .fetch(); + } + private BooleanExpression eqMemberId(Long memberId) { return memberId != null ? attendance.student.id.eq(memberId) : null; } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepository.java new file mode 100644 index 000000000..2b14404f6 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepository.java @@ -0,0 +1,8 @@ +package com.gdschongik.gdsc.domain.study.dao; + +import com.gdschongik.gdsc.domain.study.domain.StudyAchievement; +import java.util.List; + +public interface StudyAchievementCustomRepository { + List findByStudyIdAndMemberIds(Long studyId, List memberIds); +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepositoryImpl.java new file mode 100644 index 000000000..8eb3a9f46 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementCustomRepositoryImpl.java @@ -0,0 +1,27 @@ +package com.gdschongik.gdsc.domain.study.dao; + +import static com.gdschongik.gdsc.domain.study.domain.QStudyAchievement.*; + +import com.gdschongik.gdsc.domain.study.domain.StudyAchievement; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class StudyAchievementCustomRepositoryImpl implements StudyAchievementCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findByStudyIdAndMemberIds(Long studyId, List memberIds) { + return queryFactory + .selectFrom(studyAchievement) + .where(eqStudyId(studyId), studyAchievement.student.id.in(memberIds)) + .fetch(); + } + + private BooleanExpression eqStudyId(Long studyId) { + return studyId != null ? studyAchievement.study.id.eq(studyId) : null; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementRepository.java new file mode 100644 index 000000000..361e1a35d --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyAchievementRepository.java @@ -0,0 +1,7 @@ +package com.gdschongik.gdsc.domain.study.dao; + +import com.gdschongik.gdsc.domain.study.domain.StudyAchievement; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StudyAchievementRepository + extends JpaRepository, StudyAchievementCustomRepository {} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryRepository.java index 4bf4303ea..02b61fceb 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryRepository.java @@ -5,12 +5,12 @@ import com.gdschongik.gdsc.domain.study.domain.StudyHistory; import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface StudyHistoryRepository extends JpaRepository { - List findByStudyId(Long studyId); - List findAllByStudent(Member member); Optional findByStudentAndStudy(Member member, Study study); @@ -18,4 +18,6 @@ public interface StudyHistoryRepository extends JpaRepository findByStudentAndStudyId(Member member, Long studyId); + + Page findByStudyId(Long studyId, Pageable pageable); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/vo/Curriculum.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/vo/Curriculum.java index 7f299e45c..0a982af34 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/vo/Curriculum.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/vo/Curriculum.java @@ -60,4 +60,8 @@ public static Curriculum generateCurriculum( public boolean isOpen() { return status == StudyStatus.OPEN; } + + public boolean isCancelled() { + return status == StudyStatus.CANCELLED; + } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmissionStatusResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmissionStatusResponse.java index 50fb311df..b0d9b3935 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmissionStatusResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmissionStatusResponse.java @@ -1,6 +1,7 @@ package com.gdschongik.gdsc.domain.study.dto.response; import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory; +import com.gdschongik.gdsc.domain.study.domain.StudyDetail; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -9,11 +10,15 @@ public enum AssignmentSubmissionStatusResponse { NOT_SUBMITTED("미제출"), FAILURE("제출 실패"), - SUCCESS("제출 성공"); + SUCCESS("제출 성공"), + CANCELLED("휴강"); private final String value; - public static AssignmentSubmissionStatusResponse from(AssignmentHistory assignmentHistory) { + public static AssignmentSubmissionStatusResponse of(AssignmentHistory assignmentHistory, StudyDetail studyDetail) { + if (studyDetail.getAssignment().isCancelled()) { + return CANCELLED; + } if (assignmentHistory == null) { return NOT_SUBMITTED; } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AttendanceStatusResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AttendanceStatusResponse.java index 3eb730bd9..6fac26b65 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AttendanceStatusResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AttendanceStatusResponse.java @@ -11,11 +11,16 @@ public enum AttendanceStatusResponse { ATTENDED("출석"), NOT_ATTENDED("미출석"), - BEFORE_ATTENDANCE("출석전"); + BEFORE_ATTENDANCE("출석전"), + CANCELLED("휴강"); private final String value; public static AttendanceStatusResponse of(StudyDetail studyDetail, LocalDate now, boolean isAttended) { + if (studyDetail.getCurriculum().isCancelled()) { + return CANCELLED; + } + if (studyDetail.getAttendanceDay().isAfter(now)) { return BEFORE_ATTENDANCE; } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentCurriculumResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentCurriculumResponse.java index d2c1aaa4a..0c6c84e0b 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentCurriculumResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentCurriculumResponse.java @@ -37,7 +37,7 @@ public static StudyStudentCurriculumResponse of( studyDetail.getCurriculum().getDifficulty(), AttendanceStatusResponse.of(studyDetail, now.toLocalDate(), isAttended), studyDetail.getAssignment().getStatus(), - AssignmentSubmissionStatusResponse.from(assignmentHistory), + AssignmentSubmissionStatusResponse.of(assignmentHistory, studyDetail), assignmentHistory != null ? assignmentHistory.getSubmissionFailureType() : NOT_SUBMITTED, assignmentHistory != null ? assignmentHistory.getSubmissionLink() : null); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentResponse.java index fd616fdb2..598aa160b 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyStudentResponse.java @@ -1,7 +1,14 @@ package com.gdschongik.gdsc.domain.study.dto.response; +import static com.gdschongik.gdsc.domain.study.domain.AchievementType.*; +import static com.gdschongik.gdsc.domain.study.dto.response.AssignmentSubmissionStatusResponse.*; +import static com.gdschongik.gdsc.domain.study.dto.response.StudyTodoResponse.StudyTodoType.*; + +import com.gdschongik.gdsc.domain.study.domain.AchievementType; +import com.gdschongik.gdsc.domain.study.domain.StudyAchievement; import com.gdschongik.gdsc.domain.study.domain.StudyHistory; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; public record StudyStudentResponse( @Schema(description = "멤버 아이디") Long memberId, @@ -9,14 +16,62 @@ public record StudyStudentResponse( @Schema(description = "학번") String studentId, @Schema(description = "디스코드 사용자명") String discordUsername, @Schema(description = "디스코드 닉네임") String nickname, - @Schema(description = "깃허브 링크") String githubLink) { - public static StudyStudentResponse from(StudyHistory studyHistory) { + @Schema(description = "깃허브 링크") String githubLink, + @Schema(description = "1차 우수 스터디원") boolean isFirstRoundOutstandingStudent, + @Schema(description = "2차 우수 스터디원") boolean isSecondRoundOutstandingStudent, + @Schema(description = "과제 및 출석 이력") List studyTodos, + @Schema(description = "과제 수행률") double assignmentRate, + @Schema(description = "출석률") double attendanceRate) { + public static StudyStudentResponse of( + StudyHistory studyHistory, List studyAchievements, List studyTodos) { + List assignments = studyTodos.stream() + .filter(studyTodoResponse -> studyTodoResponse.todoType() == ASSIGNMENT) + .toList(); + + List attendances = studyTodos.stream() + .filter(studyTodoResponse -> studyTodoResponse.todoType() == ATTENDANCE) + .toList(); + + long successAssignmentsCount = countAssignmentByStatus(assignments, SUCCESS); + long cancelledAssignmentsCount = countAssignmentByStatus(assignments, CANCELLED); + + long attendedCount = countAttendanceByStatus(attendances, AttendanceStatusResponse.ATTENDED); + long cancelledAttendanceCount = countAttendanceByStatus(attendances, AttendanceStatusResponse.CANCELLED); + return new StudyStudentResponse( studyHistory.getStudent().getId(), studyHistory.getStudent().getName(), studyHistory.getStudent().getStudentId(), studyHistory.getStudent().getDiscordUsername(), studyHistory.getStudent().getNickname(), - studyHistory.getRepositoryLink()); + studyHistory.getRepositoryLink(), + isOutstandingStudent(FIRST_ROUND_OUTSTANDING_STUDENT, studyAchievements), + isOutstandingStudent(SECOND_ROUND_OUTSTANDING_STUDENT, studyAchievements), + studyTodos, + calculateRateOrZero(successAssignmentsCount, assignments.size() - cancelledAssignmentsCount), + calculateRateOrZero(attendedCount, attendances.size() - cancelledAttendanceCount)); + } + + private static boolean isOutstandingStudent( + AchievementType achievementType, List studyAchievements) { + return studyAchievements.stream() + .anyMatch(studyAchievement -> studyAchievement.getAchievementType() == achievementType); + } + + private static long countAssignmentByStatus( + List assignments, AssignmentSubmissionStatusResponse status) { + return assignments.stream() + .filter(studyTodoResponse -> studyTodoResponse.assignmentSubmissionStatus() == status) + .count(); + } + + private static long countAttendanceByStatus(List attendances, AttendanceStatusResponse status) { + return attendances.stream() + .filter(studyTodoResponse -> studyTodoResponse.attendanceStatus() == status) + .count(); + } + + private static double calculateRateOrZero(long dividend, long divisor) { + return divisor == 0 ? 0 : (double) dividend * 100 / divisor; } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyTodoResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyTodoResponse.java index 7399b6181..ce7d58577 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyTodoResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/StudyTodoResponse.java @@ -11,6 +11,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +// todo: 활용이 다양해졌으므로 rename 필요 public record StudyTodoResponse( Long studyDetailId, @Schema(description = "현 주차수") Long week, @@ -21,6 +22,16 @@ public record StudyTodoResponse( @Schema(description = "과제 제출 상태 (과제타입일 때만 사용)") AssignmentSubmissionStatusResponse assignmentSubmissionStatus) { public static StudyTodoResponse createAttendanceType(StudyDetail studyDetail, LocalDate now, boolean isAttended) { + if (studyDetail.getCurriculum().isCancelled()) { + return new StudyTodoResponse( + studyDetail.getId(), + studyDetail.getWeek(), + ATTENDANCE, + null, + AttendanceStatusResponse.CANCELLED, + null, + null); + } return new StudyTodoResponse( studyDetail.getId(), studyDetail.getWeek(), @@ -32,6 +43,17 @@ public static StudyTodoResponse createAttendanceType(StudyDetail studyDetail, Lo } public static StudyTodoResponse createAssignmentType(StudyDetail studyDetail, AssignmentHistory assignmentHistory) { + if (studyDetail.getAssignment().isCancelled()) { + return new StudyTodoResponse( + studyDetail.getId(), + studyDetail.getWeek(), + ASSIGNMENT, + null, + null, + null, + AssignmentSubmissionStatusResponse.of(null, studyDetail)); + } + return new StudyTodoResponse( studyDetail.getId(), studyDetail.getWeek(), @@ -39,7 +61,7 @@ public static StudyTodoResponse createAssignmentType(StudyDetail studyDetail, As studyDetail.getAssignment().getDeadline(), null, studyDetail.getAssignment().getTitle(), - AssignmentSubmissionStatusResponse.from(assignmentHistory)); + AssignmentSubmissionStatusResponse.of(assignmentHistory, studyDetail)); } @Getter