-
Notifications
You must be signed in to change notification settings - Fork 8
feat: 어드민 멘토 승격 지원서 승인/거절 기능, 상태 별 지원서 개수 조회 기능 추가 #577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
9065a9e
79c4558
e9a9f5c
c6f51fb
b7bf005
961b005
c45a67c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| public record MentorApplicationCountResponse( | ||
| long approved, | ||
| long pending, | ||
| long rejected | ||
| ) { | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| public record MentorApplicationRejectRequest( | ||
| @NotBlank(message = "거절 사유는 필수입니다") | ||
| @Size(max = 200, message = "거절 사유는 200자를 초과할 수 없습니다") | ||
|
||
| String rejectedReason | ||
| ) { | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,15 @@ | ||
| package com.example.solidconnection.admin.service; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_NOT_FOUND; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_UNIVERSITY_NOT_SELECTED; | ||
|
|
||
| import com.example.solidconnection.admin.dto.MentorApplicationCountResponse; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationRejectRequest; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationSearchCondition; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationSearchResponse; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.mentor.domain.MentorApplication; | ||
| import com.example.solidconnection.mentor.domain.MentorApplicationStatus; | ||
| import com.example.solidconnection.mentor.repository.MentorApplicationRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.Page; | ||
|
|
@@ -22,4 +30,37 @@ public Page<MentorApplicationSearchResponse> searchMentorApplications( | |
| ) { | ||
| return mentorApplicationRepository.searchMentorApplications(mentorApplicationSearchCondition, pageable); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void approveMentorApplication(Long mentorApplicationId) { | ||
| MentorApplication mentorApplication = mentorApplicationRepository.findById(mentorApplicationId) | ||
| .orElseThrow(() -> new CustomException(MENTOR_APPLICATION_NOT_FOUND)); | ||
|
|
||
| if(mentorApplication.getUniversityId() == null){ | ||
| throw new CustomException(MENTOR_APPLICATION_UNIVERSITY_NOT_SELECTED); | ||
| } | ||
|
|
||
| mentorApplication.approve(); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void rejectMentorApplication(long mentorApplicationId, MentorApplicationRejectRequest request) { | ||
| MentorApplication mentorApplication = mentorApplicationRepository.findById(mentorApplicationId) | ||
| .orElseThrow(() -> new CustomException(MENTOR_APPLICATION_NOT_FOUND)); | ||
|
|
||
| mentorApplication.reject(request.rejectedReason()); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public MentorApplicationCountResponse getMentorApplicationCount() { | ||
| long approvedCount = mentorApplicationRepository.countByMentorApplicationStatus(MentorApplicationStatus.APPROVED); | ||
| long pendingCount = mentorApplicationRepository.countByMentorApplicationStatus(MentorApplicationStatus.PENDING); | ||
| long rejectedCount = mentorApplicationRepository.countByMentorApplicationStatus(MentorApplicationStatus.REJECTED); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 하나의 메서드 안에서 세 개의 쿼리를 실행하는데, 중간에 데이터가 변경되면 의도와 다른 개수가 나올 것 같습니다. 그룹바이로 하나의 쿼리로 될 거 같긴 합니다
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇겠네요, 도중에 상태가 변경된다면 값의 일관성이 깨질수도 있으니 jpql로 한번에 조회하는 식으로 수정하겠습니다!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. jpql 방식으로 조회를 시도 하면 Object[] 혹은, dto 리스트, queryDsl 을 활용하여 map 으로 받는 방식들이 존재하는데, 셋 다 비용이 있어 보여서 고민이 됩니다...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현 격리 수준에서 동일한 스냅샷을 읽기 때문에 정합성 깨짐 문제는 발생하지 않음 + 여러모로 JPQL을 통한 하나의 쿼리보다 현제 쿼리가 효율적인 말씀해주신 내용 전부 동의합니다 ~! 그대로 유지해도 될 거 같습니다. |
||
|
|
||
| return new MentorApplicationCountResponse( | ||
| approvedCount, | ||
| pendingCount, | ||
| rejectedCount | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,8 @@ public enum ErrorCode { | |
| UNIVERSITY_ID_MUST_BE_NULL_FOR_OTHER(HttpStatus.BAD_REQUEST.value(), "기타 학교를 선택한 경우 학교 정보를 입력할 수 없습니다."), | ||
| INVALID_UNIVERSITY_SELECT_TYPE(HttpStatus.BAD_REQUEST.value(), "지원하지 않는 학교 선택 방식입니다."), | ||
| MENTOR_ALREADY_EXISTS(HttpStatus.BAD_REQUEST.value(), "이미 존재하는 멘토입니다."), | ||
| MENTOR_APPLICATION_ALREADY_CONFIRM(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토 승격 요청 입니다."), | ||
|
||
| MENTOR_APPLICATION_UNIVERSITY_NOT_SELECTED(HttpStatus.BAD_REQUEST.value(), "승인하려는 멘토 신청에 대학교가 선택되지 않았습니다."), | ||
|
|
||
| // socket | ||
| UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,21 @@ | ||
| package com.example.solidconnection.admin.service; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_ALREADY_CONFIRM; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_NOT_FOUND; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_UNIVERSITY_NOT_SELECTED; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; | ||
|
|
||
| import com.example.solidconnection.admin.dto.MentorApplicationCountResponse; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationRejectRequest; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationSearchCondition; | ||
| import com.example.solidconnection.admin.dto.MentorApplicationSearchResponse; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.mentor.domain.MentorApplication; | ||
| import com.example.solidconnection.mentor.domain.MentorApplicationStatus; | ||
| import com.example.solidconnection.mentor.domain.UniversitySelectType; | ||
| import com.example.solidconnection.mentor.fixture.MentorApplicationFixture; | ||
| import com.example.solidconnection.mentor.repository.MentorApplicationRepository; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import com.example.solidconnection.siteuser.fixture.SiteUserFixture; | ||
| import com.example.solidconnection.support.TestContainerSpringBootTest; | ||
|
|
@@ -40,6 +48,9 @@ class AdminMentorApplicationServiceTest { | |
| @Autowired | ||
| private UniversityFixture universityFixture; | ||
|
|
||
| @Autowired | ||
| private MentorApplicationRepository mentorApplicationRepository; | ||
|
|
||
| private MentorApplication mentorApplication1; | ||
| private MentorApplication mentorApplication2; | ||
| private MentorApplication mentorApplication3; | ||
|
|
@@ -209,4 +220,156 @@ class 멘토_승격_지원서_목록_조회 { | |
| .containsOnly(regionKoreanName); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| class 멘토_승격_지원서_승인{ | ||
|
|
||
| @Test | ||
| void 대기중인_멘토_지원서를_승인한다() { | ||
| // given | ||
| long pendingMentorApplicationId = mentorApplication2.getId(); | ||
|
|
||
| // when | ||
| adminMentorApplicationService.approveMentorApplication(pendingMentorApplicationId); | ||
|
|
||
| // then | ||
| MentorApplication result = mentorApplicationRepository.findById(mentorApplication2.getId()).get(); | ||
| assertThat(result.getMentorApplicationStatus()).isEqualTo(MentorApplicationStatus.APPROVED); | ||
| assertThat(result.getApprovedAt()).isNotNull(); | ||
| } | ||
|
|
||
| @Test | ||
| void 대학이_선택되지_않은_멘토_지원서를_승인하면_예외가_발생한다(){ | ||
| // given | ||
| SiteUser user = siteUserFixture.사용자(); | ||
| MentorApplication noUniversityIdMentorApplication = mentorApplicationFixture.대기중_멘토신청(user.getId(), UniversitySelectType.OTHER, null); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.approveMentorApplication(noUniversityIdMentorApplication.getId())) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_UNIVERSITY_NOT_SELECTED.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void 이미_승인된_멘토_지원서를_승인하면_예외가_발생한다() { | ||
| // given | ||
| long approvedMentorApplicationId = mentorApplication1.getId(); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.approveMentorApplication(approvedMentorApplicationId)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_ALREADY_CONFIRM.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void 이미_거절된_멘토_지원서를_승인하면_예외가_발생한다() { | ||
| // given | ||
| long rejectedMentorApplicationId = mentorApplication3.getId(); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.approveMentorApplication(rejectedMentorApplicationId)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_ALREADY_CONFIRM.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void 존재하지_않는_멘토_지원서를_승인하면_예외가_발생한다() { | ||
| // given | ||
| long nonExistentId = 99999L; | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.approveMentorApplication(nonExistentId)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_NOT_FOUND.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| class 멘토_승격_지원서_거절{ | ||
|
|
||
| @Test | ||
| void 대기중인_멘토_지원서를_거절한다() { | ||
| // given | ||
| long pendingMentorApplicationId = mentorApplication2.getId(); | ||
| MentorApplicationRejectRequest request = new MentorApplicationRejectRequest("파견학교 인증 자료 누락"); | ||
|
|
||
| // when | ||
| adminMentorApplicationService.rejectMentorApplication(pendingMentorApplicationId, request); | ||
|
|
||
| // then | ||
| MentorApplication result = mentorApplicationRepository.findById(mentorApplication2.getId()).get(); | ||
| assertThat(result.getMentorApplicationStatus()).isEqualTo(MentorApplicationStatus.REJECTED); | ||
| assertThat(result.getRejectedReason()).isEqualTo(request.rejectedReason()); | ||
| } | ||
|
|
||
| @Test | ||
| void 이미_승인된_멘토_지원서를_거절하면_예외가_발생한다() { | ||
| // given | ||
| long approvedMentorApplicationId = mentorApplication1.getId(); | ||
| MentorApplicationRejectRequest request = new MentorApplicationRejectRequest("파견학교 인증 자료 누락"); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.rejectMentorApplication(approvedMentorApplicationId, request)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_ALREADY_CONFIRM.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void 이미_거절된_멘토_지원서를_거절하면_예외가_발생한다() { | ||
| // given | ||
| long rejectedMentorApplicationId = mentorApplication3.getId(); | ||
| MentorApplicationRejectRequest request = new MentorApplicationRejectRequest("파견학교 인증 자료 누락"); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.rejectMentorApplication(rejectedMentorApplicationId, request)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_ALREADY_CONFIRM.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void 존재하지_않는_멘토_지원서를_거절하면_예외가_발생한다() { | ||
| // given | ||
| long nonExistentId = 99999L; | ||
| MentorApplicationRejectRequest request = new MentorApplicationRejectRequest("파견학교 인증 자료 누락"); | ||
|
|
||
| // when & then | ||
| assertThatCode(() -> adminMentorApplicationService.rejectMentorApplication(nonExistentId, request)) | ||
| .isInstanceOf(CustomException.class) | ||
| .hasMessage(MENTOR_APPLICATION_NOT_FOUND.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| class 멘토_지원서_상태별_개수_조회 { | ||
|
|
||
| @Test | ||
| void 상태별_멘토_지원서_개수를_조회한다() { | ||
| // given | ||
| List<MentorApplication> expectedApprovedCount = List.of(mentorApplication1, mentorApplication4); | ||
| List<MentorApplication> expectedPendingCount = List.of(mentorApplication2, mentorApplication5); | ||
| List<MentorApplication> expectedRejectedCount = List.of(mentorApplication3, mentorApplication6); | ||
|
|
||
| // when | ||
| MentorApplicationCountResponse response = adminMentorApplicationService.getMentorApplicationCount(); | ||
|
|
||
| // then | ||
| assertThat(response.approved()).isEqualTo(expectedApprovedCount.size()); | ||
| assertThat(response.pending()).isEqualTo(expectedPendingCount.size()); | ||
| assertThat(response.rejected()).isEqualTo(expectedRejectedCount.size()); | ||
| } | ||
|
||
|
|
||
| @Test | ||
| void 멘토_지원서가_없으면_모든_개수가_0이다() { | ||
| // given | ||
| mentorApplicationRepository.deleteAll(); | ||
|
|
||
| // when | ||
| MentorApplicationCountResponse response = adminMentorApplicationService.getMentorApplicationCount(); | ||
|
|
||
| // then | ||
| assertThat(response.approved()).isEqualTo(0L); | ||
| assertThat(response.pending()).isEqualTo(0L); | ||
| assertThat(response.rejected()).isEqualTo(0L); | ||
|
||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일관성을 위해서 approvedCount, pendingCount, rejectedCount로 수정하면 좋을 것 같습니다!