From 5645544b5e2823cad7e11114193d5cdaa6976436 Mon Sep 17 00:00:00 2001 From: hoTan35 Date: Wed, 25 Mar 2026 15:42:12 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feature/KD-73:=20=EC=9E=90=EA=B2=A9?= =?UTF-8?q?=EC=A6=9D=20=EC=8B=A0=EC=B2=AD=20=EA=B4=80=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=EC=99=80=20=EC=A1=B8=EC=97=85=20=EB=85=BC=EB=AC=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=A9=94=EB=89=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=EB=90=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20=EC=B7=A8=EC=86=8C=20=EA=B8=B0=EB=8A=A5=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 --- .../GraduationUserAdminFacade.java | 34 +++++++++++++++++++ .../GraduationUserAdminController.java | 14 ++++++++ .../GraduationUserAdminControllerImpl.java | 11 ++++++ .../GraduationUserBatchDisapproveRequest.java | 16 +++++++++ ...GraduationUserBatchDisapproveResponse.java | 20 +++++++++++ .../GraduationUserAdminFacadeTest.java | 25 ++++++++++++++ .../command/CertificateCommandService.java | 10 ++++++ .../certificate/domain/Certificate.java | 4 +++ .../command/ThesisCommandService.java | 10 ++++++ .../domain/thesis/domain/Thesis.java | 4 +++ 10 files changed, 148 insertions(+) create mode 100644 aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/request/GraduationUserBatchDisapproveRequest.java create mode 100644 aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/response/GraduationUserBatchDisapproveResponse.java 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..976ede41 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..9c3ce685 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,27 @@ public void approveGraduationUsers_Success() { assertEquals(true,fakeCertificateRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); } + @Test + @DisplayName("disapproveGraduationUsers는 여러 GraduationUser의 제출 승인을 취소한다.") + public void disapproveGraduationUsers_Success() { + // given + // 먼저 승인된 상태로 만듦 + fakeThesisRepository.save(Thesis.of(1L, 1L, 1L, true, null, null, null)); + fakeThesisRepository.save(Thesis.of(2L, 1L, 2L, true, null, null, null)); + fakeCertificateRepository.save(Certificate.of(1L, 1L, 1L, true, null, null, null)); + + List graduationUserIds = Arrays.asList(1L, 2L); + GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder() + .ids(graduationUserIds) + .build(); + + // when + graduationUserAdminFacade.disapproveGraduationUsers(request); + + // then + assertEquals(false, fakeThesisRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); + assertEquals(false, fakeThesisRepository.findApprovalByIdAndDeletedAtIsNull(2L).get()); + assertEquals(false, fakeCertificateRepository.findApprovalByIdAndDeletedAtIsNull(1L).get()); + } + } 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; + } } From bc90741da08415080c9d2bd7ddbaab7958234fc1 Mon Sep 17 00:00:00 2001 From: hoTan35 Date: Wed, 25 Mar 2026 17:48:37 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20GraduationUserAdminController?= =?UTF-8?q?=20assignee=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/GraduationUserAdminController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 976ede41..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 @@ -153,7 +153,7 @@ ResponseEntity approveGraduationUsers( @Operation(summary = "졸업 대상자 일괄 승인 취소 API", description = """ - Description : 이 API는 선택한 여러 졸업 대상자의 제출을 일괄 승인 취소합니다. - - Assignee : 장영후 + - Assignee : 황호찬 """) @ApiResponse(responseCode = "200") ResponseEntity disapproveGraduationUsers( From c7030622e239653f2d719fc4acae2f9b2f7d341b Mon Sep 17 00:00:00 2001 From: hoTan35 Date: Fri, 27 Mar 2026 18:36:54 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=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 --- .../GraduationUserAdminFacadeTest.java | 41 ++++++++++++++++++- .../repository/FakeCertificateRepository.java | 2 + 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java b/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java index 9c3ce685..4cbf9c76 100644 --- a/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java +++ b/aics-admin/src/testFixtures/java/graduationUser/application/GraduationUserAdminFacadeTest.java @@ -320,9 +320,9 @@ public void approveGraduationUsers_Success() { 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)); - fakeCertificateRepository.save(Certificate.of(1L, 1L, 1L, true, null, null, null)); List graduationUserIds = Arrays.asList(1L, 2L); GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder() @@ -330,12 +330,49 @@ public void disapproveGraduationUsers_Success() { .build(); // when - graduationUserAdminFacade.disapproveGraduationUsers(request); + 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/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); } + + }