diff --git a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp index 3413aec..a691c5e 100644 --- a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp +++ b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp @@ -176,11 +176,52 @@ } }); + $("#btn_app_delete").click(function () { + if (!confirm("이 승인 요청을 삭제하시겠습니까?\n삭제 후에는 복구할 수 없습니다.")) { + return; + } + $.ajax({ + type: "POST", + url: url, + data: {cmd: "DELETE", id: key}, + success: function (args) { + alert("삭제되었습니다."); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } + window.close(); + }, + error: function (e) { + alert(approvalErrorMessage(e)); + } + }); + }); + $("#btn_close").click(function () { window.close(); }); }); + function approvalErrorMessage(e) { + try { + var json = JSON.parse(e.responseText); + return json.errorMsg || e.responseText; + } catch (ex) { + return e.responseText; + } + } + + // 요청자(PTL_USER) row 가 삭제되면 상세 조회 자체가 불가능하다. 이 경우 삭제만 가능하도록 노출한다. + function showLoadFailure(message) { + $("#approvalSubject").text("상세 정보를 조회할 수 없는 승인 요청"); + $("#approvalDetail").text(message); + $("#btn_modify, #btn_app_approve, #btn_app_reject, #btn_app_force_reject, #btn_app_redeploy").hide(); + $("#gwActionSection, #gwActionResult").hide(); + document.getElementById("btn_app_delete").style.display = ""; + } + var currentRequestType = null; // 현재 신청 유형 (DELETE 승인 시 GW 처리방식 선택 노출용) // 값이 있을 때만 행을 노출한다. (해지 신청처럼 해당 항목이 없는 유형에서 빈 행이 남지 않도록) @@ -344,7 +385,9 @@ $("#apisTable tbody").html(apisHtml); }, error: function (e) { - alert(e.responseText); + var message = approvalErrorMessage(e); + alert(message); + showLoadFailure(message); } }); } @@ -454,6 +497,9 @@ + diff --git a/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp b/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp index c8bca97..64f3217 100644 --- a/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp +++ b/WebContent/jsp/onl/apim/approval/portalUserApprovalManPopup.jsp @@ -181,7 +181,7 @@ }); $("#btn_user_delete").click(function() { - if (!confirm("삭제된 사용자 또는 법인이 포함되어 승인·반려할 수 없는 요청입니다.\n이 승인 요청을 삭제하시겠습니까?")) { + if (!confirm("삭제된 요청자·사용자 또는 법인이 포함되어 승인·반려할 수 없는 요청입니다.\n이 승인 요청을 삭제하시겠습니까?\n삭제 후에는 복구할 수 없습니다.")) { return; } $.ajax({ @@ -198,7 +198,7 @@ window.close(); }, error: function(e) { - alert(e.responseText); + alert(approvalErrorMessage(e)); } }); }); @@ -362,10 +362,31 @@ } }, error: function(e) { - alert(e.responseText); + var message = approvalErrorMessage(e); + alert(message); + showLoadFailure(message); } }); } + + function approvalErrorMessage(e) { + try { + var json = JSON.parse(e.responseText); + return json.errorMsg || e.responseText; + } catch (ex) { + return e.responseText; + } + } + + // 요청자(PTL_USER) row 가 삭제되면 상세 조회 자체가 불가능하다. 이 경우 삭제만 가능하도록 노출한다. + function showLoadFailure(message) { + $("#approvalSubject").text("상세 정보를 조회할 수 없는 승인 요청"); + $("#approvalDetail").text(message); + $("#approvalDetailRow").show(); + $("#btn_unmask").hide(); + $("#btn_user_approve, #btn_user_reject, #btn_user_force_reject, #btn_user_redeploy").hide(); + document.getElementById("btn_user_delete").style.display = ""; + } function updateDetailView(data) { $("#approvalType").text(data.approvalType === 'USER' ? "법인사용자" : "API 사용"); $("#approvalStatus").text(data.approvalStatus.description); diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java index 46173cb..6c3d5d4 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java @@ -468,8 +468,40 @@ public class PortalApprovalManService extends BaseService { * 임의 삭제 방지를 위해 실제로 삭제된 사용자/법인이 포함된 USER 유형 요청만 허용한다. */ public void deleteApprovalRequest(String id) { - Approval approval = approvalService.findById(id) - .orElseThrow(() -> new BizException("승인 정보를 찾을 수 없습니다. ID: " + id)); + Approval approval = null; + try { + approval = approvalService.findById(id).orElse(null); + } catch (Exception e) { + // 요청자/승인자 row 가 사라진 건은 엔티티 hydration 자체가 실패한다. 네이티브 조회로 우회한다. + logger.warn("Approval {} 엔티티 로딩 실패. 네이티브 조회로 대체합니다. 원인: {}", id, e.getMessage()); + } + + if (approval != null) { + assertDeletable(approval); + } else { + // Approval.requester 는 @JoinColumn(nullable=false) + @NotFound(IGNORE) 조합이라 + // 요청자(PTL_USER) row 가 삭제되면 findById 가 빈 결과를 반환한다(조회·승인·반려 모두 불가). + assertDeletableByNativeQuery(id); + } + + entityManager.createNativeQuery("DELETE FROM PTL_APPROVER WHERE APPROVAL_ID = :id") + .setParameter("id", id) + .executeUpdate(); + int affected = entityManager.createNativeQuery("DELETE FROM PTL_APPROVAL WHERE ID = :id") + .setParameter("id", id) + .executeUpdate(); + if (affected == 0) { + throw new BizException("승인 정보를 찾을 수 없습니다. ID: " + id); + } + logger.info("Approval {} deleted (deleted requester/target user/org present)", id); + } + + /** 엔티티 로딩이 가능한 건: 요청자 삭제이거나, 대상 사용자·법인이 삭제된 법인사용자 요청만 삭제 허용 */ + private void assertDeletable(Approval approval) { + boolean requesterDeleted = approval.getRequester() == null; + if (requesterDeleted) { + return; + } if (!approval.getApprovalType().equals(ApprovalType.USER)) { throw new BizException("법인사용자 승인 요청만 삭제할 수 있습니다."); } @@ -486,19 +518,38 @@ public class PortalApprovalManService extends BaseService { || "삭제된법인".equals(org.getOrgName()); } if (!(userDeleted || orgDeleted)) { - throw new BizException("삭제된 사용자 또는 법인이 포함된 요청만 삭제할 수 있습니다."); + throw new BizException("삭제된 요청자·사용자 또는 법인이 포함된 요청만 삭제할 수 있습니다."); } + } - entityManager.createNativeQuery("DELETE FROM PTL_APPROVER WHERE APPROVAL_ID = :id") + /** 엔티티 로딩이 불가능한 건: PTL_APPROVAL 원본 row 를 직접 읽어 요청자/대상 사용자 삭제 여부를 확인한다. */ + @SuppressWarnings("unchecked") + private void assertDeletableByNativeQuery(String id) { + List rows = entityManager.createNativeQuery( + "SELECT APPROVAL_TYPE, TARGET_ID, REQUESTER_ID FROM PTL_APPROVAL WHERE ID = :id") .setParameter("id", id) - .executeUpdate(); - int affected = entityManager.createNativeQuery("DELETE FROM PTL_APPROVAL WHERE ID = :id") - .setParameter("id", id) - .executeUpdate(); - if (affected == 0) { + .getResultList(); + if (rows.isEmpty()) { throw new BizException("승인 정보를 찾을 수 없습니다. ID: " + id); } - logger.info("Approval {} deleted (deleted target user/org present)", id); + Object[] row = rows.get(0); + String approvalType = row[0] == null ? null : String.valueOf(row[0]); + String targetId = row[1] == null ? null : String.valueOf(row[1]); + String requesterId = row[2] == null ? null : String.valueOf(row[2]); + + boolean requesterDeleted = requesterId == null || !existsPortalUser(requesterId); + boolean targetUserDeleted = ApprovalType.USER.name().equals(approvalType) + && (targetId == null || !existsPortalUser(targetId)); + if (!(requesterDeleted || targetUserDeleted)) { + throw new BizException("삭제된 요청자·사용자가 포함된 요청만 삭제할 수 있습니다."); + } + } + + private boolean existsPortalUser(String userId) { + Number count = (Number) entityManager.createNativeQuery("SELECT COUNT(*) FROM PTL_USER WHERE ID = :id") + .setParameter("id", userId) + .getSingleResult(); + return count != null && count.intValue() > 0; } public void sendEvent(String id, ApprovalEvent event, Map options) {