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();