diff --git a/src/main/java/com/gdschongik/gdsc/domain/membership/application/MembershipService.java b/src/main/java/com/gdschongik/gdsc/domain/membership/application/MembershipService.java index 717fef1bf..e013616f4 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/membership/application/MembershipService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/membership/application/MembershipService.java @@ -69,10 +69,10 @@ public void submitMembership(Long recruitmentRoundId) { .findById(recruitmentRoundId) .orElseThrow(() -> new CustomException(RECRUITMENT_ROUND_NOT_FOUND)); - boolean isMembershipAlreadySubmitted = - membershipRepository.existsByMemberAndRecruitment(currentMember, recruitmentRound.getRecruitment()); + boolean isMembershipDuplicate = membershipRepository.existsByMemberAndRecruitmentWithSatisfiedRequirements( + currentMember, recruitmentRound.getRecruitment()); - membershipValidator.validateMembershipSubmit(currentMember, recruitmentRound, isMembershipAlreadySubmitted); + membershipValidator.validateMembershipSubmit(currentMember, recruitmentRound, isMembershipDuplicate); Membership membership = Membership.createMembership(currentMember, recruitmentRound); membershipRepository.save(membership); diff --git a/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepository.java index c1eea6740..29e62e480 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepository.java @@ -5,5 +5,5 @@ public interface MembershipCustomRepository { - boolean existsByMemberAndRecruitment(Member member, Recruitment recruitment); + boolean existsByMemberAndRecruitmentWithSatisfiedRequirements(Member member, Recruitment recruitment); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepositoryImpl.java index 9ccce0a56..70f03d09d 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/membership/dao/MembershipCustomRepositoryImpl.java @@ -2,9 +2,11 @@ import static com.gdschongik.gdsc.domain.membership.domain.QMembership.*; +import com.gdschongik.gdsc.domain.common.model.RequirementStatus; import com.gdschongik.gdsc.domain.member.domain.Member; import com.gdschongik.gdsc.domain.recruitment.domain.Recruitment; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.EnumPath; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -14,11 +16,14 @@ public class MembershipCustomRepositoryImpl implements MembershipCustomRepositor private final JPAQueryFactory queryFactory; @Override - public boolean existsByMemberAndRecruitment(Member member, Recruitment recruitment) { + public boolean existsByMemberAndRecruitmentWithSatisfiedRequirements(Member member, Recruitment recruitment) { Integer fetchOne = queryFactory .selectOne() .from(membership) - .where(eqMember(member), eqRecruitment(recruitment)) + .where( + eqMember(member), + eqRecruitment(recruitment), + eqRequirementStatus(membership.regularRequirement.paymentStatus, RequirementStatus.SATISFIED)) .fetchFirst(); return fetchOne != null; @@ -31,4 +36,9 @@ private BooleanExpression eqMember(Member member) { private BooleanExpression eqRecruitment(Recruitment recruitment) { return recruitment != null ? membership.recruitmentRound.recruitment.eq(recruitment) : null; } + + private BooleanExpression eqRequirementStatus( + EnumPath requirement, RequirementStatus requirementStatus) { + return requirementStatus != null ? requirement.eq(requirementStatus) : null; + } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/membership/domain/MembershipValidator.java b/src/main/java/com/gdschongik/gdsc/domain/membership/domain/MembershipValidator.java index 4d0114f73..126db4b79 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/membership/domain/MembershipValidator.java +++ b/src/main/java/com/gdschongik/gdsc/domain/membership/domain/MembershipValidator.java @@ -13,15 +13,15 @@ public class MembershipValidator { public void validateMembershipSubmit( - Member currentMember, RecruitmentRound recruitmentRound, boolean isMembershipAlreadySubmitted) { + Member currentMember, RecruitmentRound recruitmentRound, boolean isMembershipDuplicate) { // 준회원인지 검증 // TODO: 어드민인 경우 리쿠르팅 지원 및 결제에 대한 정책 검토 필요. 현재는 불가능하도록 설정 if (!currentMember.isAssociate()) { throw new CustomException(MEMBERSHIP_NOT_APPLICABLE); } - // 이미 접수한 멤버십이 있는지 검증 - if (isMembershipAlreadySubmitted) { + // 이미 멤버십이 있는지 검증 + if (isMembershipDuplicate) { throw new CustomException(MEMBERSHIP_ALREADY_SUBMITTED); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyDetailController.java b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyDetailController.java index a2c5dfafd..67917a2bd 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyDetailController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyDetailController.java @@ -30,18 +30,18 @@ public class MentorStudyDetailController { @Operation(summary = "스터디 과제 수정", description = "멘토만 과제를 수정할 수 있습니다.") @PatchMapping("/{studyDetailId}/assignments") - public ResponseEntity updateStudyAssignment( + public ResponseEntity updateStudyAssignment( @PathVariable Long studyDetailId, @Valid @RequestBody AssignmentCreateUpdateRequest request) { - mentorStudyDetailService.updateStudyAssignment(studyDetailId, request); - return ResponseEntity.ok().build(); + AssignmentResponse response = mentorStudyDetailService.updateStudyAssignment(studyDetailId, request); + return ResponseEntity.ok().body(response); } @Operation(summary = "스터디 과제 개설", description = "멘토만 과제를 개설할 수 있습니다.") @PutMapping("/{studyDetailId}/assignments") - public ResponseEntity publishStudyAssignment( + public ResponseEntity publishStudyAssignment( @PathVariable Long studyDetailId, @Valid @RequestBody AssignmentCreateUpdateRequest request) { - mentorStudyDetailService.publishStudyAssignment(studyDetailId, request); - return ResponseEntity.ok().build(); + AssignmentResponse response = mentorStudyDetailService.publishStudyAssignment(studyDetailId, request); + return ResponseEntity.ok().body(response); } @Operation(summary = "스터디 주차별 과제 목록 조회", description = "주차별 스터디 과제 목록을 조회합니다.") diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyDetailService.java b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyDetailService.java index bb34eb745..8d60df0ca 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyDetailService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyDetailService.java @@ -58,7 +58,7 @@ public void cancelStudyAssignment(Long studyDetailId) { } @Transactional - public void publishStudyAssignment(Long studyDetailId, AssignmentCreateUpdateRequest request) { + public AssignmentResponse publishStudyAssignment(Long studyDetailId, AssignmentCreateUpdateRequest request) { Member currentMember = memberUtil.getCurrentMember(); StudyDetail studyDetail = studyDetailRepository .findById(studyDetailId) @@ -67,13 +67,15 @@ public void publishStudyAssignment(Long studyDetailId, AssignmentCreateUpdateReq studyDetailValidator.validatePublishStudyAssignment(currentMember, studyDetail, request); studyDetail.publishAssignment(request.title(), request.deadLine(), request.descriptionNotionLink()); - studyDetailRepository.save(studyDetail); + StudyDetail savedStudyDetail = studyDetailRepository.save(studyDetail); log.info("[MentorStudyDetailService] 과제 개설 완료: studyDetailId={}", studyDetailId); + + return AssignmentResponse.from(savedStudyDetail); } @Transactional - public void updateStudyAssignment(Long studyDetailId, AssignmentCreateUpdateRequest request) { + public AssignmentResponse updateStudyAssignment(Long studyDetailId, AssignmentCreateUpdateRequest request) { Member currentMember = memberUtil.getCurrentMember(); StudyDetail studyDetail = studyDetailRepository .findById(studyDetailId) @@ -82,9 +84,11 @@ public void updateStudyAssignment(Long studyDetailId, AssignmentCreateUpdateRequ studyDetailValidator.validateUpdateStudyAssignment(currentMember, studyDetail, request); studyDetail.updateAssignment(request.title(), request.deadLine(), request.descriptionNotionLink()); - studyDetailRepository.save(studyDetail); + StudyDetail savedStudyDetail = studyDetailRepository.save(studyDetail); log.info("[MentorStudyDetailService] 과제 수정 완료: studyDetailId={}", studyDetailId); + + return AssignmentResponse.from(savedStudyDetail); } @Transactional(readOnly = true) diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyService.java b/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyService.java index da90b4240..1daeb72b0 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/application/StudentStudyService.java @@ -17,7 +17,6 @@ import com.gdschongik.gdsc.global.exception.CustomException; import com.gdschongik.gdsc.global.util.MemberUtil; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -43,8 +42,8 @@ public StudyApplicableResponse getAllApplicableStudies() { Member currentMember = memberUtil.getCurrentMember(); List studyHistories = studyHistoryRepository.findAllByStudent(currentMember); Optional appliedStudy = studyHistories.stream() + .filter(StudyHistory::isWithinApplicationAndCourse) .map(StudyHistory::getStudy) - .filter(Study::isStudyOngoing) .findFirst(); List studyResponses = studyRepository.findAll().stream() .filter(Study::isApplicable) @@ -107,8 +106,7 @@ public void attend(Long studyDetailId, StudyAttendCreateRequest request) { public StudentMyCurrentStudyResponse getMyCurrentStudy() { Member currentMember = memberUtil.getCurrentMember(); StudyHistory studyHistory = studyHistoryRepository.findAllByStudent(currentMember).stream() - .filter(s -> s.getStudy().getApplicationPeriod().getStartDate().isBefore(LocalDateTime.now()) - && s.getStudy().getPeriod().getEndDate().isAfter(LocalDateTime.now())) + .filter(StudyHistory::isWithinApplicationAndCourse) .findFirst() .orElse(null); return StudentMyCurrentStudyResponse.from(studyHistory); diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/Study.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/Study.java index 0e3cf1185..04856c315 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/Study.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/Study.java @@ -175,8 +175,10 @@ public boolean isApplicable() { return applicationPeriod.isOpen(); } - public boolean isStudyOngoing() { - return period.isOpen(); + public boolean isWithinApplicationAndCourse() { + LocalDateTime now = LocalDateTime.now(); + return applicationPeriod.getStartDate().isBefore(now) + && period.getEndDate().isAfter(now); } public LocalDate getStartDate() { diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java index 52f2fa3f4..e8cd313d8 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java @@ -56,7 +56,7 @@ public void updateRepositoryLink(String repositoryLink) { } // 데이터 전달 로직 - public boolean isStudyOngoing() { - return study.isStudyOngoing(); + public boolean isWithinApplicationAndCourse() { + return study.isWithinApplicationAndCourse(); } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryValidator.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryValidator.java index 2e2d19d82..2376df7b4 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryValidator.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryValidator.java @@ -24,10 +24,10 @@ public void validateApplyStudy(Study study, List currentMemberStud } // 이미 듣고 있는 스터디가 있는 경우 - // todo: StudyHistory가 아닌 Study의 isOngoning 호출하도록 수정 - boolean isInOngoingStudy = currentMemberStudyHistories.stream().anyMatch(StudyHistory::isStudyOngoing); + boolean hasAppliedStudy = + currentMemberStudyHistories.stream().anyMatch(StudyHistory::isWithinApplicationAndCourse); - if (isInOngoingStudy) { + if (hasAppliedStudy) { throw new CustomException(STUDY_HISTORY_ONGOING_ALREADY_EXISTS); } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyType.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyType.java index 093c0f7cb..9b9ce52b0 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyType.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyType.java @@ -7,8 +7,8 @@ @AllArgsConstructor public enum StudyType { ASSIGNMENT("과제 스터디"), - ONLINE("온라인 커리큘럼"), - OFFLINE("오프라인 커리큘럼"); + ONLINE("온라인 스터디"), + OFFLINE("오프라인 스터디"); private final String value; } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentHistoryStatusResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentHistoryStatusResponse.java index 3209dc8cc..6ec0c5feb 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentHistoryStatusResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentHistoryStatusResponse.java @@ -1,7 +1,5 @@ package com.gdschongik.gdsc.domain.study.dto.response; -import static com.gdschongik.gdsc.domain.study.domain.SubmissionFailureType.NOT_SUBMITTED; - import com.gdschongik.gdsc.domain.study.domain.*; import com.gdschongik.gdsc.domain.study.domain.vo.Assignment; import io.swagger.v3.oas.annotations.media.Schema; @@ -34,7 +32,7 @@ public static AssignmentHistoryStatusResponse of(StudyDetail studyDetail, Assign studyDetail.getAssignment().getDescriptionLink(), studyDetail.getAssignment().getDeadline(), null, - NOT_SUBMITTED, + null, null); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentResponse.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentResponse.java index 3f0656984..1a020d83f 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentResponse.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentResponse.java @@ -8,6 +8,7 @@ public record AssignmentResponse( Long studyDetailId, + @Schema(description = "스터디 이름") String studyTitle, @Schema(description = "과제 제목") String title, @Schema(description = "마감 기한") LocalDateTime deadline, @Schema(description = "주차") Long week, @@ -17,6 +18,7 @@ public static AssignmentResponse from(StudyDetail studyDetail) { Assignment assignment = studyDetail.getAssignment(); return new AssignmentResponse( studyDetail.getId(), + studyDetail.getStudy().getTitle(), assignment.getTitle(), assignment.getDeadline(), studyDetail.getWeek(), diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmittableDto.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmittableDto.java index 5935778c8..037665c40 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmittableDto.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/response/AssignmentSubmittableDto.java @@ -28,7 +28,7 @@ public static AssignmentSubmittableDto of(StudyDetail studyDetail, AssignmentHis } if (assignmentHistory == null) { - return notSubmittedAssignment(studyDetail, assignment); + return beforeAssignmentSubmit(studyDetail, assignment); } return new AssignmentSubmittableDto( @@ -48,16 +48,16 @@ private static AssignmentSubmittableDto cancelledAssignment(StudyDetail studyDet studyDetail.getId(), assignment.getStatus(), studyDetail.getWeek(), null, null, null, null, null, null); } - private static AssignmentSubmittableDto notSubmittedAssignment(StudyDetail studyDetail, Assignment assignment) { + private static AssignmentSubmittableDto beforeAssignmentSubmit(StudyDetail studyDetail, Assignment assignment) { return new AssignmentSubmittableDto( studyDetail.getId(), assignment.getStatus(), studyDetail.getWeek(), assignment.getTitle(), - AssignmentSubmissionStatus.FAILURE, + null, assignment.getDescriptionLink(), assignment.getDeadline(), null, - SubmissionFailureType.NOT_SUBMITTED); + null); } } diff --git a/src/test/java/com/gdschongik/gdsc/domain/membership/application/MembershipServiceTest.java b/src/test/java/com/gdschongik/gdsc/domain/membership/application/MembershipServiceTest.java index 5a16a404e..de3092c77 100644 --- a/src/test/java/com/gdschongik/gdsc/domain/membership/application/MembershipServiceTest.java +++ b/src/test/java/com/gdschongik/gdsc/domain/membership/application/MembershipServiceTest.java @@ -37,6 +37,8 @@ class 멤버십_가입신청시 { .isInstanceOf(CustomException.class) .hasMessage(RECRUITMENT_ROUND_NOT_FOUND.getMessage()); } + + // todo: 1차 모집시 멤버십 생성 후 실제 가입은 하지 않고 2차 모집 시 가입하려고 하는 케이스 추가 } @Test