From 0a6ca61a12bfcc2945ed54968fd5f2d1a310537c Mon Sep 17 00:00:00 2001 From: chpark Date: Mon, 23 Feb 2026 17:27:51 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=84=EB=A7=88=EB=9E=80=EC=8A=A4=20?= =?UTF-8?q?=EA=B2=B0=EC=9E=AC=20=EB=B0=98=EC=98=81=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../salesmgmt/mapper/salesNcollectMgmt.xml | 52 ++- .../salesMng/purchaseRegProposalMngList.jsp | 6 +- .../pms/api/AmaranthApprovalApiClient.java | 19 +- src/com/pms/common/utils/Constants.java | 4 + .../pms/controller/ApprovalController.java | 23 +- src/com/pms/mapper/approval.xml | 55 ++++ .../controller/SalesMngController.java | 2 + src/com/pms/service/ApprovalService.java | 307 +++++++++++++++++- 8 files changed, 428 insertions(+), 40 deletions(-) diff --git a/WebContent/WEB-INF/classes/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml b/WebContent/WEB-INF/classes/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml index d778578..5c83660 100644 --- a/WebContent/WEB-INF/classes/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml +++ b/WebContent/WEB-INF/classes/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml @@ -820,6 +820,11 @@ T.PROJECT_NO, T.CONTRACT_OBJID, T.SALES_DEADLINE_DATE, + COALESCE(T.TAX_TYPE, '') AS TAX_TYPE, + COALESCE(CODE_NAME(T.TAX_TYPE), '') AS TAX_TYPE_NAME, + COALESCE(T.TAX_INVOICE_DATE, '') AS TAX_INVOICE_DATE, + COALESCE(T.EXPORT_DECL_NO, '') AS EXPORT_DECL_NO, + COALESCE(T.LOADING_DATE, '') AS LOADING_DATE, CODE_NAME(T.CATEGORY_CD) AS ORDER_TYPE, CODE_NAME(T.PRODUCT) AS PRODUCT_TYPE, CODE_NAME(T.AREA_CD) AS NATION, @@ -873,7 +878,7 @@ (SELECT CM.PO_NO FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PO_NO, COALESCE(T.CONTRACT_DATE, (SELECT CM.order_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)) AS ORDER_DATE, (SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.CONTRACT_OBJID AND DOC_TYPE IN ('FTC_ORDER', 'ORDER', 'ORDER_DOC') AND UPPER(STATUS) = 'ACTIVE') AS CU01_CNT, - (SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS, + COALESCE((SELECT SUM(PR.RESULT_QTY) FROM PRODUCTION_RESULT PR WHERE PR.PROJECT_OBJID = T.OBJID::VARCHAR AND PR.RESULT_TYPE = 'SHIP_WAIT' AND PR.STATUS = 'active'), 0) AS PRODUCTION_STATUS, -- 판매 관련 필드들 (sales_registration 테이블에서 한 번에 가져오기) COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS, -- 판매수량: 모든 분할 출하의 합계 @@ -1584,7 +1589,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC AND DOC_TYPE='ORDER_DOC' AND UPPER(STATUS) = 'ACTIVE' ) THEN 'Y' ELSE 'N' END AS ORDER_ATTACH, - (SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS, + COALESCE((SELECT SUM(PR.RESULT_QTY) FROM PRODUCTION_RESULT PR WHERE PR.PROJECT_OBJID = T.OBJID::VARCHAR AND PR.RESULT_TYPE = 'SHIP_WAIT' AND PR.STATUS = 'active'), 0) AS PRODUCTION_STATUS, -- 판매 관련 필드들 (sales_registration 테이블에서 가져오기) SR.sale_no AS SALE_NO, COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS, @@ -2090,12 +2095,53 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC COALESCE(CM.PO_NO, '') AS PO_NO, COALESCE(CM.ORDER_DATE, '') AS ORDER_DATE, COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS SALES_QUANTITY, - COALESCE(T.QUANTITY::NUMERIC, 0) - COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS REMAINING_QUANTITY + COALESCE(T.QUANTITY::NUMERIC, 0) - COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS REMAINING_QUANTITY, + COALESCE(NULLIF(CM.ORDER_UNIT_PRICE, '')::NUMERIC, 0) AS ORDER_UNIT_PRICE, + COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 1) AS EXCHANGE_RATE, + COALESCE(CM.CONTRACT_CURRENCY, '') AS CONTRACT_CURRENCY FROM PROJECT_MGMT T LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID = T.CONTRACT_OBJID LEFT JOIN SUPPLY_MNG SM ON SM.OBJID = CASE WHEN T.CUSTOMER_OBJID ~ '^[0-9]+$' THEN T.CUSTOMER_OBJID::NUMERIC ELSE NULL END WHERE T.PROJECT_NO = #{projectNo} + + + + + + + /* salesNcollectMgmt.saveDeadlineInfo - 마감정보 저장 */ + UPDATE PROJECT_MGMT + SET + TAX_TYPE = #{taxType}, + TAX_INVOICE_DATE = #{taxInvoiceDate}, + EXPORT_DECL_NO = #{exportDeclNo}, + LOADING_DATE = #{loadingDate} + WHERE OBJID::VARCHAR = #{OBJID} + diff --git a/WebContent/WEB-INF/view/salesMng/purchaseRegProposalMngList.jsp b/WebContent/WEB-INF/view/salesMng/purchaseRegProposalMngList.jsp index 9d02348..aad8533 100644 --- a/WebContent/WEB-INF/view/salesMng/purchaseRegProposalMngList.jsp +++ b/WebContent/WEB-INF/view/salesMng/purchaseRegProposalMngList.jsp @@ -182,7 +182,9 @@ function fn_openProposalFormPopUp(objId){ // Amaranth10 전자결재 SSO 팝업 열기 function fn_openAmaranthApproval(objId){ - var title = encodeURIComponent("품의서 결재"); + var selectedData = _tabulGrid.getSelectedData(); + var proposalNo = fnc_checkNull(selectedData[0].PROPOSAL_NO); + var title = "품의서 결재" + (proposalNo ? " - " + proposalNo : ""); $.ajax({ url: "/approval/getAmaranthSsoUrl.do", @@ -191,7 +193,7 @@ function fn_openAmaranthApproval(objId){ "targetType": "PROPOSAL", "targetObjId": objId, "approvalTitle": title, - "outProcessCode": "", + "outProcessCode": "${AMARANTH_OUT_PROCESS_CODE}", "formId": "", "compSeq": "1000", "deptSeq": "" diff --git a/src/com/pms/api/AmaranthApprovalApiClient.java b/src/com/pms/api/AmaranthApprovalApiClient.java index fec6d2e..9c8d40c 100644 --- a/src/com/pms/api/AmaranthApprovalApiClient.java +++ b/src/com/pms/api/AmaranthApprovalApiClient.java @@ -31,7 +31,7 @@ public class AmaranthApprovalApiClient { private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc"; private static final String HASH_KEY = "22519103205540290721741689643674301018832465"; private static final String GROUP_SEQ = "gcmsAmaranth40578"; - private static final String AES_KEY = "gcmsAmaranth40578"; // SSO 암호화 키 + private static final String AES_KEY = "8441e27489d402cd"; // SSO 암호화 키 (API상품연동설정에서 확인) /** * 인증 토큰 발급 @@ -827,11 +827,13 @@ public class AmaranthApprovalApiClient { * @param mod 작성/보기/삭제 구분 (W:작성, V:보기, D:삭제) * @param compSeq 회사 시퀀스 (선택, 겸직별 결재 시 필요) * @param deptSeq 부서 시퀀스 (선택, 겸직별 결재 시 필요) + * @param loginId 로그인 아이디 (appParams에 포함, 선택) * @return API 응답 JSON (resultData.fullUrl 포함) */ public String getSsoUrl(String baseUrl, String empSeq, String outProcessCode, String formId, String approKey, String subjectStr, - String mod, String compSeq, String deptSeq) throws Exception { + String mod, String compSeq, String deptSeq, + String loginId) throws Exception { System.out.println("=== Amaranth SSO URL 생성 시작 ==="); System.out.println("empSeq: " + empSeq + ", outProcessCode: " + outProcessCode + ", formId: " + formId); @@ -885,7 +887,7 @@ public class AmaranthApprovalApiClient { // 요청 본문 구성 String requestBody = buildSsoRequestBody(empSeqEnc, outProcessCode, formId, - approKey, subjectStr, mod, compSeq, deptSeq); + approKey, subjectStr, mod, compSeq, deptSeq, loginId); System.out.println("[2단계] SSO API 호출 - URL: " + fullUrl); System.out.println("[2단계] Request Body: " + requestBody); @@ -940,7 +942,8 @@ public class AmaranthApprovalApiClient { private String buildSsoRequestBody(String empSeqEnc, String outProcessCode, String formId, String approKey, String subjectStr, String mod, - String compSeq, String deptSeq) { + String compSeq, String deptSeq, + String loginId) { StringBuilder json = new StringBuilder(); json.append("{"); json.append("\"header\":{},"); @@ -965,6 +968,11 @@ public class AmaranthApprovalApiClient { json.append("\"approKey\":\"").append(escapeJson(approKey)).append("\","); json.append("\"mod\":\"").append(escapeJson(mod)).append("\""); + // loginId (가이드 예제에 따라 appParams에 포함) + if (loginId != null && !loginId.isEmpty()) { + json.append(",\"loginId\":\"").append(escapeJson(loginId)).append("\""); + } + // outProcessCode 또는 formId (둘 중 하나 필수) if (outProcessCode != null && !outProcessCode.isEmpty()) { json.append(",\"outProcessCode\":\"").append(escapeJson(outProcessCode)).append("\""); @@ -978,6 +986,9 @@ public class AmaranthApprovalApiClient { json.append(",\"subjectStr\":\"").append(escapeJson(subjectStr)).append("\""); } + // 본문 인코딩 (UTF-8) + json.append(",\"contentsEnc\":\"U\""); + json.append("}"); // appParams 닫기 json.append("}"); // body 닫기 json.append("}"); // root 닫기 diff --git a/src/com/pms/common/utils/Constants.java b/src/com/pms/common/utils/Constants.java index 4627607..f79669f 100644 --- a/src/com/pms/common/utils/Constants.java +++ b/src/com/pms/common/utils/Constants.java @@ -76,6 +76,10 @@ public class Constants { // sql config public static final String MYBATIS_CONFIG_FILE_PATH = "./com/pms/mapper/mybatisConf.xml"; + // Amaranth10 전자결재 SSO 연동 설정 + // 결재연동설정에서 등록한 외부시스템 결재연동코드 (Amaranth 관리자 > 결재프로세스관리 > 결재연동설정) + public static final String AMARANTH_OUT_PROCESS_CODE = "RPSPLM_00001"; + //AES 암호화 키 public static final String keyName = "ILJIAESSECRETKEY"; public static final String algorithm = "AES"; diff --git a/src/com/pms/controller/ApprovalController.java b/src/com/pms/controller/ApprovalController.java index 0780429..7450d66 100644 --- a/src/com/pms/controller/ApprovalController.java +++ b/src/com/pms/controller/ApprovalController.java @@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import com.pms.common.SqlMapConfig; import com.pms.common.bean.PersonBean; @@ -388,29 +389,21 @@ public class ApprovalController { * Amaranth10 전자결재 콜백 API * 결재 이벤트(상신/진행/종결/반려/삭제 등) 발생 시 Amaranth10에서 호출 * 결재연동설정의 상신~삭제 URL에 이 엔드포인트를 등록 - * @param request - * @param paramMap processId, approkey, docId, docSts, userId, formId, docTitle 등 - * @return SUCCESS 응답 JSON */ - @RequestMapping("/approval/amaranthApprovalCallback.do") + @ResponseBody + @RequestMapping(value="/approval/amaranthApprovalCallback.do", produces="application/json; charset=UTF-8") public String amaranthApprovalCallback(HttpServletRequest request, @RequestParam Map paramMap)throws Exception{ - String jsonResult = approvalService.handleAmaranthApprovalCallback(paramMap); - request.setAttribute("RESULT", jsonResult); - return "/ajax/ajaxResult"; + return approvalService.handleAmaranthApprovalCallback(paramMap); } /** * Amaranth10 전자결재 본문 조회 API (결재작성 시 호출) - * 결재연동설정의 결재작성 URL에 이 엔드포인트를 등록 - * @param request - * @param paramMap approkey, formId, docId, userId, empSeq 등 - * @return 제목/본문 JSON + * 결재연동설정의 결재작성(WebAPI) URL에 이 엔드포인트를 등록 */ - @RequestMapping("/approval/amaranthApprovalContents.do") + @ResponseBody + @RequestMapping(value="/approval/amaranthApprovalContents.do", produces="application/json; charset=UTF-8") public String amaranthApprovalContents(HttpServletRequest request, @RequestParam Map paramMap)throws Exception{ - String jsonResult = approvalService.getAmaranthApprovalContents(paramMap); - request.setAttribute("RESULT", jsonResult); - return "/ajax/ajaxResult"; + return approvalService.getAmaranthApprovalContents(paramMap); } } diff --git a/src/com/pms/mapper/approval.xml b/src/com/pms/mapper/approval.xml index 55bafb9..3aa3fb5 100644 --- a/src/com/pms/mapper/approval.xml +++ b/src/com/pms/mapper/approval.xml @@ -606,4 +606,59 @@ WHERE OBJID = #{estObjId}::NUMERIC + + + + + INSERT INTO AMARANTH_APPROVAL ( + APPRO_KEY, TARGET_TYPE, TARGET_OBJID, APPROVAL_TITLE, WRITER, STATUS, REGDATE + ) VALUES ( + #{approKey}, #{targetType}, #{targetObjId}, #{approvalTitle}, #{writer}, 'create', NOW() + ) + + + + + + + + UPDATE AMARANTH_APPROVAL SET + AMARANTH_DOC_ID = #{amaranthDocId}, + DOC_STS = #{docSts}, + STATUS = #{status}, + UPDATE_DATE = NOW() + WHERE APPRO_KEY = #{approKey} + + + + + UPDATE SALES_REQUEST_MASTER SET + STATUS = #{status} + WHERE OBJID::VARCHAR = #{targetObjId}::VARCHAR + + \ No newline at end of file diff --git a/src/com/pms/salesmgmt/controller/SalesMngController.java b/src/com/pms/salesmgmt/controller/SalesMngController.java index 2459710..9103273 100644 --- a/src/com/pms/salesmgmt/controller/SalesMngController.java +++ b/src/com/pms/salesmgmt/controller/SalesMngController.java @@ -1369,6 +1369,8 @@ public class SalesMngController { e.printStackTrace(); } request.setAttribute("code_map", code_map); + // Amaranth10 전자결재 연동코드 (결재연동설정에서 등록한 코드) + request.setAttribute("AMARANTH_OUT_PROCESS_CODE", com.pms.common.utils.Constants.AMARANTH_OUT_PROCESS_CODE); return "/salesMng/purchaseRegProposalMngList"; } diff --git a/src/com/pms/service/ApprovalService.java b/src/com/pms/service/ApprovalService.java index 072dc20..5706032 100644 --- a/src/com/pms/service/ApprovalService.java +++ b/src/com/pms/service/ApprovalService.java @@ -1779,6 +1779,7 @@ public class ApprovalService { * @return fullUrl이 포함된 JSON 문자열 */ public String getAmaranthSsoUrl(HttpServletRequest request, Map paramMap){ + SqlSession sqlSession = null; try { HttpSession session = request.getSession(); PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN); @@ -1787,13 +1788,15 @@ public class ApprovalService { } String empSeq = CommonUtils.checkNull(person.getEmpseq()); + String loginId = CommonUtils.checkNull(person.getUserId()); + System.out.println("=== Amaranth SSO - 사용자 정보 ==="); - System.out.println("userId: " + person.getUserId()); + System.out.println("userId(loginId): " + loginId); System.out.println("userName: " + person.getUserName()); System.out.println("empSeq: [" + empSeq + "]"); if(empSeq.isEmpty()){ - return "{\"resultCode\":-1,\"resultMsg\":\"empSeq 정보가 없습니다. (userId: " + person.getUserId() + ") 관리자에게 문의하세요.\"}"; + return "{\"resultCode\":-1,\"resultMsg\":\"empSeq 정보가 없습니다. (userId: " + loginId + ") 관리자에게 문의하세요.\"}"; } // 파라미터 추출 @@ -1809,25 +1812,38 @@ public class ApprovalService { String approKey = "UB_" + java.util.UUID.randomUUID().toString(); System.out.println("=== Amaranth SSO URL 생성 ==="); - System.out.println("empSeq: " + empSeq); + System.out.println("empSeq: " + empSeq + ", loginId: " + loginId); System.out.println("targetType: " + targetType + ", targetObjId: " + targetObjId); System.out.println("outProcessCode: " + outProcessCode + ", formId: " + formId); System.out.println("approKey: " + approKey); - // API 호출 + // API 호출 (loginId 파라미터 추가) com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient(); String apiResponse = apiClient.getSsoUrl( AMARANTH_BASE_URL, empSeq, outProcessCode, formId, - approKey, approvalTitle, "W", compSeq, deptSeq + approKey, approvalTitle, "W", compSeq, deptSeq, loginId ); System.out.println("SSO API 응답: " + apiResponse); - // 응답에서 fullUrl 추출하여 approKey와 함께 반환 + // 응답에서 fullUrl 추출 String fullUrl = extractJsonStringValue(apiResponse, "fullUrl"); String resultCode = extractJsonStringValue(apiResponse, "resultCode"); if("0".equals(resultCode) && !fullUrl.isEmpty()){ + // approKey → targetObjId 매핑을 DB에 저장 (콜백/컨텐츠 조회 시 사용) + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + Map mappingParam = new HashMap(); + mappingParam.put("approKey", approKey); + mappingParam.put("targetType", targetType); + mappingParam.put("targetObjId", targetObjId); + mappingParam.put("approvalTitle", approvalTitle); + mappingParam.put("writer", loginId); + sqlSession.insert("approval.insertAmaranthApproval", mappingParam); + sqlSession.commit(); + + System.out.println("Amaranth 결재 매핑 DB 저장 완료 - approKey: " + approKey); + StringBuilder result = new StringBuilder(); result.append("{\"resultCode\":0,\"resultMsg\":\"SUCCESS\",\"resultData\":{"); result.append("\"fullUrl\":\"").append(escapeJsonValue(fullUrl)).append("\","); @@ -1842,9 +1858,12 @@ public class ApprovalService { } } catch(Exception e){ + if(sqlSession != null) sqlSession.rollback(); System.err.println("Amaranth SSO URL 생성 오류: " + e.getMessage()); e.printStackTrace(); return "{\"resultCode\":-1,\"resultMsg\":\"" + escapeJsonValue(e.getMessage()) + "\"}"; + } finally { + if(sqlSession != null) sqlSession.close(); } } @@ -1862,6 +1881,7 @@ public class ApprovalService { String docTitle = CommonUtils.checkNull(paramMap.get("docTitle")); String userId = CommonUtils.checkNull(paramMap.get("userId")); String processId = CommonUtils.checkNull(paramMap.get("processId")); + String appCancelYn = CommonUtils.checkNull(paramMap.get("appCancelYn"), "0"); System.out.println("=== Amaranth 결재 콜백 수신 ==="); System.out.println("approkey: " + approkey); @@ -1870,14 +1890,97 @@ public class ApprovalService { System.out.println("docTitle: " + docTitle); System.out.println("userId: " + userId); System.out.println("processId: " + processId); + System.out.println("appCancelYn: " + appCancelYn); System.out.println("전체 파라미터: " + paramMap); - // TODO: approkey로 우리 시스템의 대상 문서를 찾아서 상태 업데이트 - // 예: approval 테이블에서 approkey로 조회 → targetObjId의 상태 변경 + SqlSession sqlSession = null; + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + // approkey로 매핑 정보 조회 + Map searchParam = new HashMap(); + searchParam.put("approKey", approkey); + Map mappingInfo = sqlSession.selectOne("approval.selectAmaranthApprovalByApproKey", searchParam); + + if(mappingInfo == null){ + System.err.println("콜백 처리 실패: approkey에 해당하는 매핑 정보 없음 - " + approkey); + return "{\"resultCode\":\"SUCCESS\",\"resultMessage\":\"성공하였습니다.\"}"; + } + + String targetType = CommonUtils.checkNull(mappingInfo.get("target_type")); + String targetObjId = CommonUtils.checkNull(mappingInfo.get("target_objid")); + + System.out.println("매핑 정보 - targetType: " + targetType + ", targetObjId: " + targetObjId); + + // Amaranth 결재 매핑 테이블 상태 업데이트 + String internalStatus = convertDocStsToInternalStatus(docSts, appCancelYn); + Map updateParam = new HashMap(); + updateParam.put("approKey", approkey); + updateParam.put("amaranthDocId", docId); + updateParam.put("docSts", docSts); + updateParam.put("status", internalStatus); + sqlSession.update("approval.updateAmaranthApprovalByCallback", updateParam); + + // 대상 문서(품의서 등)의 상태 업데이트 + if(!targetObjId.isEmpty()){ + String proposalStatus = convertDocStsToProposalStatus(docSts, appCancelYn); + if(!proposalStatus.isEmpty()){ + Map statusParam = new HashMap(); + statusParam.put("targetObjId", targetObjId); + statusParam.put("status", proposalStatus); + + if("PROPOSAL".equals(targetType)){ + sqlSession.update("approval.changeProposalApprovalStatus", statusParam); + System.out.println("품의서 상태 변경 - targetObjId: " + targetObjId + " → " + proposalStatus); + } else if("SALES_REQUEST".equals(targetType)){ + sqlSession.update("approval.salesRequestApprovalStatus", statusParam); + System.out.println("구매요청 상태 변경 - targetObjId: " + targetObjId + " → " + proposalStatus); + } + } + } + + sqlSession.commit(); + System.out.println("콜백 처리 완료 - docSts: " + docSts + " → internalStatus: " + internalStatus); + + } catch(Exception e){ + if(sqlSession != null) sqlSession.rollback(); + System.err.println("콜백 처리 중 오류: " + e.getMessage()); + e.printStackTrace(); + } finally { + if(sqlSession != null) sqlSession.close(); + } return "{\"resultCode\":\"SUCCESS\",\"resultMessage\":\"성공하였습니다.\"}"; } + /** + * Amaranth docSts → 내부 상태 변환 + * 10:임시보관, 20:상신, 30:진행, 90:종결, 100:반려, 110:보류 + */ + private String convertDocStsToInternalStatus(String docSts, String appCancelYn){ + if("1".equals(appCancelYn)) return "create"; + if("10".equals(docSts)) return "create"; + if("20".equals(docSts)) return "inProcess"; + if("30".equals(docSts)) return "inProcess"; + if("90".equals(docSts)) return "complete"; + if("100".equals(docSts)) return "reject"; + if("110".equals(docSts)) return "hold"; + return "inProcess"; + } + + /** + * Amaranth docSts → 품의서(SALES_REQUEST_MASTER) STATUS 변환 + */ + private String convertDocStsToProposalStatus(String docSts, String appCancelYn){ + if("1".equals(appCancelYn)) return "create"; + if("20".equals(docSts)) return "approvalRequest"; + if("30".equals(docSts)) return "approvalRequest"; + if("90".equals(docSts)) return "approvalComplete"; + if("100".equals(docSts)) return "reject"; + if("110".equals(docSts)) return "approvalRequest"; + return ""; + } + /** * Amaranth10 결재작성 시 본문 내용 조회 (Binding WebAPI / WebAPI) * 결재 작성 화면이 열릴 때 Amaranth10에서 호출 @@ -1894,15 +1997,187 @@ public class ApprovalService { System.out.println("docId: " + docId); System.out.println("전체 파라미터: " + paramMap); - // TODO: approkey로 대상 문서 데이터를 조회하여 본문 HTML 또는 Binding JSON 구성 - // Binding WebAPI 방식일 경우 ITEMS/TABLE 구조의 JSON 반환 + SqlSession sqlSession = null; + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + + // approkey로 매핑 정보 조회 + Map searchParam = new HashMap(); + searchParam.put("approKey", approkey); + Map mappingInfo = sqlSession.selectOne("approval.selectAmaranthApprovalByApproKey", searchParam); + + String title = "결재 문서"; + String contentsHtml = "

연동 데이터를 찾을 수 없습니다.

"; + + if(mappingInfo != null){ + String targetType = CommonUtils.checkNull(mappingInfo.get("target_type")); + String targetObjId = CommonUtils.checkNull(mappingInfo.get("target_objid")); + title = CommonUtils.checkNull(mappingInfo.get("approval_title"), "결재 문서"); + + System.out.println("매핑 정보 - targetType: " + targetType + ", targetObjId: " + targetObjId); + + if("PROPOSAL".equals(targetType) && !targetObjId.isEmpty()){ + // 품의서 데이터 조회 + Map proposalParam = new HashMap(); + proposalParam.put("PROPOSAL_OBJID", targetObjId); + Map proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam); + + if(proposalInfo != null){ + proposalInfo = CommonUtils.toUpperCaseMapKey(proposalInfo); + contentsHtml = buildProposalContentsHtml(proposalInfo, sqlSession, targetObjId); + + String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO")); + if(!proposalNo.isEmpty()){ + title = "품의서 결재 - " + proposalNo; + } + } + } + } + + // UTF-8 인코딩 처리 (contentsEnc=U 설정됨) + String encodedTitle = java.net.URLEncoder.encode(title, "UTF-8"); + String encodedContents = java.net.URLEncoder.encode(contentsHtml, "UTF-8"); + + StringBuilder result = new StringBuilder(); + result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{"); + result.append("\"title\":\"").append(escapeJsonValue(encodedTitle)).append("\","); + result.append("\"contents\":\"").append(escapeJsonValue(encodedContents)).append("\""); + result.append("}}"); + return result.toString(); + + } catch(Exception e){ + System.err.println("결재 본문 조회 오류: " + e.getMessage()); + e.printStackTrace(); + StringBuilder result = new StringBuilder(); + result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{"); + result.append("\"title\":\"결재 문서\","); + result.append("\"contents\":\"

본문 조회 중 오류가 발생했습니다.

\""); + result.append("}}"); + return result.toString(); + } finally { + if(sqlSession != null) sqlSession.close(); + } + } + + /** + * 품의서 데이터를 HTML 본문으로 구성 + */ + private String buildProposalContentsHtml(Map proposalInfo, SqlSession sqlSession, String targetObjId){ + StringBuilder html = new StringBuilder(); - StringBuilder result = new StringBuilder(); - result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{"); - result.append("\"title\":\"결재 문서\","); - result.append("\"contents\":\"

본문 내용

\""); - result.append("}}"); - return result.toString(); + String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO")); + String projectNumber = CommonUtils.checkNull(proposalInfo.get("PROJECT_NUMBER")); + String projectName = CommonUtils.checkNull(proposalInfo.get("PROJECT_NAME")); + String purchaseTypeName = CommonUtils.checkNull(proposalInfo.get("PURCHASE_TYPE_NAME")); + String orderTypeName = CommonUtils.checkNull(proposalInfo.get("ORDER_TYPE_NAME")); + String productName = CommonUtils.checkNull(proposalInfo.get("PRODUCT_NAME_TITLE")); + String customerName = CommonUtils.checkNull(proposalInfo.get("PROJECT_CUSTOMER_NAME")); + String writerName = CommonUtils.checkNull(proposalInfo.get("WRITER_NAME")); + String regdate = CommonUtils.checkNull(proposalInfo.get("REGDATE_TITLE")); + String remark = CommonUtils.checkNull(proposalInfo.get("REMARK")); + String totalAmount = CommonUtils.checkNull(proposalInfo.get("TOTAL_AMOUNT")); + + html.append("
"); + + html.append(""); + html.append(""); + html.append(""); + + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + + html.append(""); + html.append(""); + html.append(""); + if(!totalAmount.isEmpty()){ + html.append(""); + html.append(""); + } else { + html.append(""); + } + html.append(""); + + html.append("
품의서 No").append(escapeHtml(proposalNo)).append("작성일").append(escapeHtml(regdate)).append("
프로젝트번호").append(escapeHtml(projectNumber)).append("프로젝트명").append(escapeHtml(projectName)).append("
구매유형").append(escapeHtml(purchaseTypeName)).append("주문유형").append(escapeHtml(orderTypeName)).append("
제품구분").append(escapeHtml(productName)).append("고객사").append(escapeHtml(customerName)).append("
작성자").append(escapeHtml(writerName)).append("합계금액").append(escapeHtml(totalAmount)).append("
"); + + // 품의서 품목 리스트 조회 + try { + Map partParam = new HashMap(); + partParam.put("PROPOSAL_OBJID", targetObjId); + List partList = sqlSession.selectList("salesMng.getProposalPartList", partParam); + + if(partList != null && !partList.isEmpty()){ + html.append("
"); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + + int idx = 1; + for(Map partInfo : partList){ + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + } + html.append("
No품번품명규격수량단가금액거래처
").append(idx++).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NO")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NAME")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("SPEC")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("QTY")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("UNIT_PRICE")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("TOTAL_PRICE")))).append("").append(escapeHtml(CommonUtils.checkNull(partInfo.get("VENDOR_NAME")))).append("
"); + } + } catch(Exception e){ + System.err.println("품의서 품목 리스트 조회 오류: " + e.getMessage()); + } + + // 비고 + if(!remark.isEmpty()){ + html.append("

비고: ").append(escapeHtml(remark)).append("

"); + } + + html.append("
"); + return html.toString(); + } + + /** + * HTML 이스케이프 (XSS 방지) + */ + private String escapeHtml(String value){ + if(value == null || value.isEmpty()) return ""; + return value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """); } /**