Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -190,4 +192,36 @@ public GraduationUserBatchApproveResponse approveGraduationUsers(GraduationUserB

return GraduationUserBatchApproveResponse.from(approvedUserIds);
}

public GraduationUserBatchDisapproveResponse disapproveGraduationUsers(GraduationUserBatchDisapproveRequest request) {
List<GraduationUser> users = request.ids().stream()
.map(graduationUserQueryService::getById)
.toList();

List<Long> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -148,4 +150,16 @@ ResponseEntity<GraduationUserBatchApproveResponse> approveGraduationUsers(
required = true
) @Valid @RequestBody GraduationUserBatchApproveRequest request
);

@Operation(summary = "졸업 대상자 일괄 승인 취소 API", description = """
- Description : 이 API는 선택한 여러 졸업 대상자의 제출을 일괄 승인 취소합니다.
- Assignee : 황호찬
""")
@ApiResponse(responseCode = "200")
ResponseEntity<GraduationUserBatchDisapproveResponse> disapproveGraduationUsers(
@Parameter(
description = "승인 취소할 졸업 대상자 ID 목록",
required = true
) @Valid @RequestBody GraduationUserBatchDisapproveRequest request
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,4 +126,13 @@ public ResponseEntity<GraduationUserBatchApproveResponse> approveGraduationUsers
GraduationUserBatchApproveResponse response = graduationUserAdminFacade.approveGraduationUsers(request);
return ResponseEntity.ok(response);
}

@Override
@PatchMapping("/batch/disapprove")
public ResponseEntity<GraduationUserBatchDisapproveResponse> disapproveGraduationUsers(
@Valid @RequestBody GraduationUserBatchDisapproveRequest request
) {
GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
@@ -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<Long> disapprovedIds
) {
public static GraduationUserBatchDisapproveResponse from(List<Long> disapprovedUserIds) {
return GraduationUserBatchDisapproveResponse.builder()
.disapprovedIds(disapprovedUserIds)
.build();
Comment on lines +15 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

from()에서 입력 리스트 참조를 그대로 보관하면 응답 값이 외부 변경에 오염될 수 있습니다.

호출자가 disapprovedUserIds를 이후에 수정하면 응답 객체의 내용도 함께 바뀔 수 있어, 방어적 복사를 권장합니다.

제안 수정안
 import java.util.List;
+import java.util.Objects;
@@
     public static GraduationUserBatchDisapproveResponse from(List<Long> disapprovedUserIds) {
             return GraduationUserBatchDisapproveResponse.builder()
-                    .disapprovedIds(disapprovedUserIds)
+                    .disapprovedIds(List.copyOf(Objects.requireNonNull(disapprovedUserIds)))
                     .build();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static GraduationUserBatchDisapproveResponse from(List<Long> disapprovedUserIds) {
return GraduationUserBatchDisapproveResponse.builder()
.disapprovedIds(disapprovedUserIds)
.build();
public static GraduationUserBatchDisapproveResponse from(List<Long> disapprovedUserIds) {
return GraduationUserBatchDisapproveResponse.builder()
.disapprovedIds(List.copyOf(Objects.requireNonNull(disapprovedUserIds)))
.build();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@aics-admin/src/main/java/kgu/developers/admin/graduationUser/presentation/response/GraduationUserBatchDisapproveResponse.java`
around lines 15 - 18, In GraduationUserBatchDisapproveResponse.from(List<Long>
disapprovedUserIds) avoid storing the incoming list reference directly; create a
defensive copy (e.g., new ArrayList<>(disapprovedUserIds)) and pass that to the
builder for the disapprovedIds field (or wrap it with
Collections.unmodifiableList(...) if immutability is desired) so external
mutations of disapprovedUserIds cannot alter the response object's state.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Long> 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<Long> 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<Long> graduationUserIds = List.of(3L);
GraduationUserBatchDisapproveRequest request = GraduationUserBatchDisapproveRequest.builder()
.ids(graduationUserIds)
.build();

// when
GraduationUserBatchDisapproveResponse response = graduationUserAdminFacade.disapproveGraduationUsers(request);

// then
assertEquals(0, response.disapprovedIds().size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ public boolean isApproved() {
public void approve() {
this.approval = true;
}

public void disapprove() {
this.approval = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ public boolean isApproved() {
public void approve() {
this.approval = true;
}

public void disapprove() {
this.approval = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ public Optional<Boolean> findApprovalByIdAndDeletedAtIsNull(Long id) {
.findFirst()
.map(Certificate::isApproved);
}


}
Loading