diff --git a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/application/GraduationUserAdminFacade.java b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/application/GraduationUserAdminFacade.java index f70efc37..3a63044d 100644 --- a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/application/GraduationUserAdminFacade.java +++ b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/application/GraduationUserAdminFacade.java @@ -4,10 +4,12 @@ import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchApproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest; +import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDisapproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchApproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse; +import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDisapproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserStatusResponse; @@ -190,4 +192,36 @@ public GraduationUserBatchApproveResponse approveGraduationUsers(GraduationUserB return GraduationUserBatchApproveResponse.from(approvedUserIds); } + + public GraduationUserBatchDisapproveResponse disapproveGraduationUsers(GraduationUserBatchDisapproveRequest request) { + List users = request.ids().stream() + .map(graduationUserQueryService::getById) + .toList(); + + List disapprovedUserIds = new ArrayList<>(); + + for(GraduationUser user: users) { + if(user.getGraduationType() == GraduationType.CERTIFICATE) { + if(user.getCertificateId() == null) continue; + boolean disapproved = certificateCommandService.disapprove(user.getCertificateId()); + if(disapproved) disapprovedUserIds.add(user.getId()); + } else if(user.getGraduationType() == GraduationType.THESIS) { + boolean midThesisdisapproved = false; + boolean finalThesisdisapproved = false; + + if(user.getMidThesisId() != null) { + midThesisdisapproved = thesisCommandService.disapprove(user.getMidThesisId()); + } + + if(user.getFinalThesisId() != null) { + finalThesisdisapproved = thesisCommandService.disapprove(user.getFinalThesisId()); + } + + if(midThesisdisapproved || finalThesisdisapproved) + disapprovedUserIds.add(user.getId()); + } + } + + return GraduationUserBatchDisapproveResponse.from(disapprovedUserIds); + } } diff --git a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminController.java b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminController.java index 9d02c825..0e5e552a 100644 --- a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminController.java +++ b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminController.java @@ -12,10 +12,12 @@ import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchApproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest; +import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDisapproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchApproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse; +import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDisapproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse; @@ -148,4 +150,16 @@ ResponseEntity approveGraduationUsers( required = true ) @Valid @RequestBody GraduationUserBatchApproveRequest request ); + + @Operation(summary = "졸업 대상자 일괄 승인 취소 API", description = """ + - Description : 이 API는 선택한 여러 졸업 대상자의 제출을 일괄 승인 취소합니다. + - Assignee : 황호찬 + """) + @ApiResponse(responseCode = "200") + ResponseEntity disapproveGraduationUsers( + @Parameter( + description = "승인 취소할 졸업 대상자 ID 목록", + required = true + ) @Valid @RequestBody GraduationUserBatchDisapproveRequest request + ); } diff --git a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminControllerImpl.java b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminControllerImpl.java index 56bb434e..32caf841 100644 --- a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminControllerImpl.java +++ b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/GraduationUserAdminControllerImpl.java @@ -8,10 +8,12 @@ import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchApproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest; +import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDisapproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchApproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse; +import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDisapproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse; @@ -124,4 +126,13 @@ public ResponseEntity approveGraduationUsers GraduationUserBatchApproveResponse response = graduationUserAdminFacade.approveGraduationUsers(request); return ResponseEntity.ok(response); } + + @Override + @PatchMapping("/batch/disapprove") + public ResponseEntity disapproveGraduationUsers( + @Valid @RequestBody GraduationUserBatchDisapproveRequest request + ) { + GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request); + return ResponseEntity.ok(response); + } } diff --git a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/request/GraduationUserBatchDisapproveRequest.java b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/request/GraduationUserBatchDisapproveRequest.java new file mode 100644 index 00000000..8e9b7247 --- /dev/null +++ b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/request/GraduationUserBatchDisapproveRequest.java @@ -0,0 +1,16 @@ +package kgu.developers.admin.graduationUser.presentation.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Positive; +import lombok.Builder; + +import java.util.List; + +@Builder +public record GraduationUserBatchDisapproveRequest( + @Schema(description = "승인 취소할 졸업 대상자 ID 목록", example = "[1, 2, 3]") + @NotEmpty(message = "승인 취소할 대상자를 선택해주세요.") + List<@Positive(message = "잘못된 ID 형식입니다.") Long> ids +) { +} diff --git a/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/response/GraduationUserBatchDisapproveResponse.java b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/response/GraduationUserBatchDisapproveResponse.java new file mode 100644 index 00000000..a4eed275 --- /dev/null +++ b/aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/response/GraduationUserBatchDisapproveResponse.java @@ -0,0 +1,20 @@ +package kgu.developers.admin.graduationUser.presentation.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +import java.util.List; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Builder +public record GraduationUserBatchDisapproveResponse( + @Schema(description = "승인 취소 처리된 졸업 대상자 ID 목록", example = "[1, 2, 3, 4, 5]", requiredMode = REQUIRED) + List disapprovedIds +) { + public static GraduationUserBatchDisapproveResponse from(List disapprovedUserIds) { + return GraduationUserBatchDisapproveResponse.builder() + .disapprovedIds(disapprovedUserIds) + .build(); + } +} diff --git a/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java b/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java index f98ad605..4cbf9c76 100644 --- a/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java +++ b/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java @@ -4,8 +4,10 @@ import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchApproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest; +import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDisapproveRequest; import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse; +import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDisapproveResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse; import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse; @@ -313,4 +315,64 @@ public void approveGraduationUsers_Success() { assertEquals(true,fakeCertificateRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); } + @Test + @DisplayName("disapproveGraduationUsers는 여러 GraduationUser의 제출 승인을 취소한다.") + public void disapproveGraduationUsers_Success() { + // given + // 먼저 승인된 상태로 만듦 + fakeCertificateRepository.save(Certificate.of(1L, 1L, 1L, true, null, null, null)); + fakeThesisRepository.save(Thesis.of(1L, 1L, 1L, true, null, null, null)); + fakeThesisRepository.save(Thesis.of(2L, 1L, 2L, true, null, null, null)); + + List graduationUserIds = Arrays.asList(1L, 2L); + GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder() + .ids(graduationUserIds) + .build(); + + // when + GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request); + + // then + assertEquals(2, response.disapprovedIds().size()); + assertEquals(false, fakeCertificateRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); + assertEquals(false, fakeThesisRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); + assertEquals(false, fakeThesisRepository.findApprovalByIdAndDeletedAtIsNull(2L).get()); + } + + @Test + @DisplayName("disapproveGraduationUsers는 이미 승인이 취소된 유저는 결과 목록에 포함하지 않는다.") + public void disapproveGraduationUsers_AlreadyDisapproved() { + // given + // 이미 승인 취소 상태로 만듦 + fakeCertificateRepository.save(Certificate.of(1L, 1L, 1L, false, null, null, null)); + + List graduationUserIds = List.of(1L); + GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder() + .ids(graduationUserIds) + .build(); + + // when + GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request); + + // then + assertEquals(0, response.disapprovedIds().size()); + assertEquals(false, fakeCertificateRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); + } + + @Test + @DisplayName("disapproveGraduationUsers는 제출물이 없는 유저는 무시한다.") + public void disapproveGraduationUsers_NoSubmission() { + // given + // graduationUser3은 자격증이나 논문 ID가 없는 상태 + List graduationUserIds = List.of(3L); + GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder() + .ids(graduationUserIds) + .build(); + + // when + GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request); + + // then + assertEquals(0, response.disapprovedIds().size()); + } } diff --git a/aics-domain/src/main/java/kgu/developers/domain/certificate/application/command/CertificateCommandService.java b/aics-domain/src/main/java/kgu/developers/domain/certificate/application/command/CertificateCommandService.java index 1ad742b9..d512f2da 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/certificate/application/command/CertificateCommandService.java +++ b/aics-domain/src/main/java/kgu/developers/domain/certificate/application/command/CertificateCommandService.java @@ -62,4 +62,14 @@ public boolean approve(Long certificateId) { certificateRepository.save(certificate); return true; } + + public boolean disapprove(Long certificateId) { + Certificate certificate = certificateRepository.findByIdAndDeletedAtIsNull(certificateId) + .orElseThrow(CertificateNotFoundException::new); + + if (!certificate.isApproved()) return false; + certificate.disapprove(); + certificateRepository.save(certificate); + return true; + } } diff --git a/aics-domain/src/main/java/kgu/developers/domain/certificate/domain/Certificate.java b/aics-domain/src/main/java/kgu/developers/domain/certificate/domain/Certificate.java index 5decd613..36edc261 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/certificate/domain/Certificate.java +++ b/aics-domain/src/main/java/kgu/developers/domain/certificate/domain/Certificate.java @@ -58,4 +58,8 @@ public boolean isApproved() { public void approve() { this.approval = true; } + + public void disapprove() { + this.approval = false; + } } diff --git a/aics-domain/src/main/java/kgu/developers/domain/thesis/application/command/ThesisCommandService.java b/aics-domain/src/main/java/kgu/developers/domain/thesis/application/command/ThesisCommandService.java index bf602477..a776e03c 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/thesis/application/command/ThesisCommandService.java +++ b/aics-domain/src/main/java/kgu/developers/domain/thesis/application/command/ThesisCommandService.java @@ -67,4 +67,14 @@ public boolean approve(Long thesisId) { thesisRepository.save(thesis); return true; } + + public boolean disapprove(Long thesisId) { + Thesis thesis = thesisRepository.findByIdAndDeletedAtIsNull(thesisId) + .orElseThrow(ThesisNotFoundException::new); + + if (!thesis.isApproved()) return false; + thesis.disapprove(); + thesisRepository.save(thesis); + return true; + } } diff --git a/aics-domain/src/main/java/kgu/developers/domain/thesis/domain/Thesis.java b/aics-domain/src/main/java/kgu/developers/domain/thesis/domain/Thesis.java index 063b7f4d..e1a71f0a 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/thesis/domain/Thesis.java +++ b/aics-domain/src/main/java/kgu/developers/domain/thesis/domain/Thesis.java @@ -55,4 +55,8 @@ public boolean isApproved() { public void approve() { this.approval = true; } + + public void disapprove() { + this.approval = false; + } } diff --git a/aics-domain/src/testFixtures/java/mock/repository/FakeCertificateRepository.java b/aics-domain/src/testFixtures/java/mock/repository/FakeCertificateRepository.java index 51cb6979..0ca90ef3 100644 --- a/aics-domain/src/testFixtures/java/mock/repository/FakeCertificateRepository.java +++ b/aics-domain/src/testFixtures/java/mock/repository/FakeCertificateRepository.java @@ -46,4 +46,6 @@ public Optional findApprovalByIdAndDeletedAtIsNull(Long id) { .findFirst() .map(Certificate::isApproved); } + + }