From 9856d4ea788b1b639c85313eec46937ab50ddf3c Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 11 Aug 2026 14:15:33 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B3=80=EA=B2=BD/=ED=95=B4=EC=A7=80=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=82=AC?= =?UTF-8?q?=EC=A0=84=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD/=ED=95=B4=EC=A7=80=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20-=20=ED=95=B4=EC=A7=80=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EC=82=AC=EC=A0=84=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=BD=EA=B3=A0=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84=20-=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EC=8A=B9=EC=9D=B8=20=EC=9D=B4=EC=A0=84=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=EB=90=98=EB=8A=94=20=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statemachine/ProcessingState.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/main/java/com/eactive/apim/portal/approval/statemachine/ProcessingState.java b/src/main/java/com/eactive/apim/portal/approval/statemachine/ProcessingState.java index cc08b9e..2b8d0b0 100644 --- a/src/main/java/com/eactive/apim/portal/approval/statemachine/ProcessingState.java +++ b/src/main/java/com/eactive/apim/portal/approval/statemachine/ProcessingState.java @@ -23,6 +23,9 @@ public class ProcessingState implements ApprovalState { case DENY: handleDeny(approval, options); break; + case CANCEL: + handleCancel(approval, options); + break; default: logger.warn("Invalid event {} for ProcessingState in approval {}", event, approval.getId()); throw new UnsupportedOperationException("Invalid event for Processing state"); @@ -91,6 +94,38 @@ public class ProcessingState implements ApprovalState { } } + /** + * 신청자 취소. 심사중(PROCESSING)이라도 아직 아무도 승인하지 않은 경우에 한해 허용한다. + * + *

관리자가 결재 상세를 열어보기만 해도 REQUESTED → PROCESSING 으로 바뀌므로(admin + * {@code PortalApprovalManService.selectDetail}), 이 상태의 상당수는 실제로 결재가 진행된 것이 + * 아니라 열람만 된 건이다. 그런 건이 취소 불가로 남으면 해당 인증키의 후속 신청(변경/해지)이 + * 무기한 막힌다. 승인 이력이 하나라도 있으면 기존 정책대로 거절한다.

+ */ + private void handleCancel(Approval approval, Map options) { + boolean anyApproved = approval.getApprovers().stream() + .anyMatch(approver -> ApproverStatus.APPROVED.equals(approver.getApproverStatus())); + if (anyApproved) { + logger.warn("Cancel rejected for approval {} - already approved by at least one approver", approval.getId()); + throw new UnsupportedOperationException("Cannot cancel approval already approved by an approver"); + } + + String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, ""); + if (!isAuthorizedApprover(approval, approverId, options)) { + logger.warn("Unauthorized cancel attempt for approval {} by {}", approval.getId(), approverId); + throw new UnauthorizedApprovalException("Unauthorized approver"); + } + + approval.getApprovers().forEach(approver -> { + approver.setApproverStatus(ApproverStatus.CANCELED); + approver.setApprovedDate(LocalDateTime.now()); + }); + approval.setApprovalDate(LocalDateTime.now()); + getApprovalListener(options).rollback(approval); + approval.setApprovalStatus(new CanceledState()); + logger.info("Approval {} cancelled in Processing state by {}", approval.getId(), approverId); + } + @Override public String toString() { return ApprovalStatus.PROCESSING.toString();