diff --git a/WebContent/WEB-INF/view/partMng/partMngList.jsp b/WebContent/WEB-INF/view/partMng/partMngList.jsp index d5e8dc0..daa1064 100644 --- a/WebContent/WEB-INF/view/partMng/partMngList.jsp +++ b/WebContent/WEB-INF/view/partMng/partMngList.jsp @@ -49,11 +49,16 @@ String connector = person.getUserId(); fn_deletePartMng(); }); - $("#btnDeploy").click(function(){ - fn_partMngDeploy(); - }); - - //image src encoding + $("#btnDeploy").click(function(){ + fn_partMngDeploy(); + }); + + //ERP 전송 + $("#btnSendErp").click(function(){ + fn_sendErp(); + }); + + //image src encoding $("img").each(function(i) { var imgSrc = $(this).attr("data-SRC"); $(this).attr("src", encodeURI(imgSrc)); @@ -473,6 +478,56 @@ String connector = person.getUserId(); }); } + // ERP 전송 함수 + function fn_sendErp(){ + if(confirm("전체 PART 정보를 ERP로 전송하시겠습니까?\n(시간이 다소 소요될 수 있습니다)")){ + // 로딩 표시 + Swal.fire({ + title: '전송 중...', + text: 'PART 정보를 ERP로 전송하는 중입니다. 잠시만 기다려주세요.', + allowOutsideClick: false, + didOpen: () => { + Swal.showLoading(); + } + }); + + $.ajax({ + type : "POST", + url : "/admin/sendAllPartsToErp.do", + dataType:"json", + success:function(data){ + Swal.close(); + if(data.success){ + var message = data.message; + if(data.errors){ + message += "\n\n에러 상세:\n" + data.errors; + } + Swal.fire({ + icon: 'success', + title: '전송 완료', + text: message, + width: '600px' + }); + } else { + Swal.fire({ + icon: 'error', + title: '전송 실패', + text: data.message + }); + } + }, + error: function(jqxhr, status, error){ + Swal.close(); + Swal.fire({ + icon: 'error', + title: '오류 발생', + text: 'PART 전송 중 오류가 발생했습니다.' + }); + } + }); + } + } +
@@ -498,6 +553,7 @@ String connector = person.getUserId(); + diff --git a/WebContent/WEB-INF/view/partMng/partMngTempList.jsp b/WebContent/WEB-INF/view/partMng/partMngTempList.jsp index 6218ccc..a87a365 100644 --- a/WebContent/WEB-INF/view/partMng/partMngTempList.jsp +++ b/WebContent/WEB-INF/view/partMng/partMngTempList.jsp @@ -286,18 +286,27 @@ ui-jqgrid tr.jqgrow td { param.dataListJson = JSON.stringify(_tabulGrid.getSelectedData()); $.ajax({ - url:"/partMng/partMngDeploy.do", - type:"POST", - data: param, - //data:{"OBJID":OBJID}, - dataType:"json", - success:function(data){ - Swal.fire(data.msg); - fn_search(); - }, - error: function(jqxhr, status, error){ - } - }); + url:"/partMng/partMngDeploy.do", + type:"POST", + data: param, + //data:{"OBJID":OBJID}, + dataType:"json", + success:function(data){ + Swal.fire(data.msg); + fn_search(); + + // 확정 성공 시 ERP로 전송 + if(data.result == true || data.result == 'true'){ + var selectedData = _tabulGrid.getSelectedData(); + if(selectedData && selectedData.length > 0){ + var partObjid = selectedData[0].OBJID; + fn_sendSinglePartToErp(partObjid); + } + } + }, + error: function(jqxhr, status, error){ + } + }); } }); } @@ -577,6 +586,26 @@ ui-jqgrid tr.jqgrow td { window.open(url, target,"width=1520, height=860, menubars=no, scrollbars=yes, resizable=yes"); } + // 단일 PART ERP 전송 + function fn_sendSinglePartToErp(partObjid){ + $.ajax({ + type : "POST", + url : "/admin/sendSinglePartToErp.do", + data : {partObjid: partObjid}, + dataType:"json", + success:function(data){ + if(data.success){ + console.log("ERP 전송 성공: " + data.message); + } else { + console.log("ERP 전송 실패: " + data.message); + } + }, + error: function(jqxhr, status, error){ + console.log("ERP 전송 오류"); + } + }); + } + diff --git a/src/com/pms/api/PartErpApiClient.java b/src/com/pms/api/PartErpApiClient.java new file mode 100644 index 0000000..dae45a8 --- /dev/null +++ b/src/com/pms/api/PartErpApiClient.java @@ -0,0 +1,293 @@ +package com.pms.api; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Base64; +import java.util.Random; + +/** + * PART 정보 ERP 전송 API 클라이언트 + * PLM의 PART 정보를 ERP로 전송합니다. + */ +public class PartErpApiClient { + + private static final String API_URL = "~/apiproxy/api20A00I00701"; + private static final String CALLER_NAME = "API_gcmsAmaranth40578"; + private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc"; + private static final String HASH_KEY = "22519103205540290721741689643674301018832465"; + private static final String GROUP_SEQ = "gcmsAmaranth40578"; + + /** + * PART 정보를 ERP로 전송합니다. + * + * @param baseUrl API 서버의 기본 URL + * @param coCd 회사코드 (4자리) + * @param itemCd 품번 (30자리) + * @param itemNm 품명 (100자리) + * @param acctFg 계정구분 (0:원재료,1:부재료,2:제품,4:반제품,5:상품,6:저장품,7:비용,8:수익) + * @param odrFg 조달구분 (0:구매,1:생산,8:Phantom) + * @param unitDc 단위 (기본값: "1") + * @param unitmangDc 단위관리 (기본값: "1") + * @param unitchngNb 단위변환 (기본값: 1) + * @param lotFg LOT구분 (기본값: "0") + * @param qcFg 검사구분 (기본값: "0") + * @param reqFg 청구구분 (기본값: "0") + * @param setitemFg 세트품목구분 (기본값: "0") + * @param useYn 사용여부 (기본값: "1") + * @return API 응답 결과 (JSON 문자열) + * @throws Exception API 호출 중 발생하는 예외 + */ + public String sendPartToErp(String baseUrl, String coCd, String itemCd, String itemNm, + String acctFg, String odrFg, String unitDc, String unitmangDc, + int unitchngNb, String lotFg, String qcFg, String reqFg, + String setitemFg, String useYn) throws Exception { + if (coCd == null || coCd.trim().isEmpty()) { + throw new IllegalArgumentException("회사코드(coCd)는 필수입니다."); + } + if (itemCd == null || itemCd.trim().isEmpty()) { + throw new IllegalArgumentException("품번(itemCd)는 필수입니다."); + } + if (itemNm == null || itemNm.trim().isEmpty()) { + throw new IllegalArgumentException("품명(itemNm)는 필수입니다."); + } + if (acctFg == null || acctFg.trim().isEmpty()) { + throw new IllegalArgumentException("계정구분(acctFg)는 필수입니다."); + } + if (odrFg == null || odrFg.trim().isEmpty()) { + throw new IllegalArgumentException("조달구분(odrFg)는 필수입니다."); + } + + // JDK 1.7에서 TLS 1.2 활성화 + System.setProperty("https.protocols", "TLSv1.2"); + + // SSL 인증서 검증 우회 + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + }; + + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() { + public boolean verify(String hostname, javax.net.ssl.SSLSession session) { + return true; + } + }); + + // API URL 구성 + String urlPath = API_URL.replace("~", ""); + String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; + String fullUrl = cleanBaseUrl + urlPath; + URL url = new URL(fullUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setConnectTimeout(30000); + connection.setReadTimeout(30000); + connection.setInstanceFollowRedirects(false); + + try { + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + connection.setRequestProperty("Accept", "application/json"); + + // 인증 헤더 설정 + connection.setRequestProperty("callerName", CALLER_NAME); + connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); + + String transactionId = generateTransactionId(); + connection.setRequestProperty("transaction-id", transactionId); + + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + connection.setRequestProperty("timestamp", timestamp); + + connection.setRequestProperty("groupSeq", GROUP_SEQ); + + String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath); + connection.setRequestProperty("wehago-sign", wehagoSign); + + // 요청 본문 작성 + String requestBody = buildRequestBody(coCd, itemCd, itemNm, acctFg, odrFg, + unitDc, unitmangDc, unitchngNb, lotFg, qcFg, reqFg, setitemFg, useYn); + + connection.setDoOutput(true); + connection.setDoInput(true); + + OutputStreamWriter writer = new OutputStreamWriter( + connection.getOutputStream(), StandardCharsets.UTF_8); + writer.write(requestBody); + writer.flush(); + writer.close(); + + int responseCode = connection.getResponseCode(); + + // 리다이렉트 처리 + if (responseCode == 301 || responseCode == 302 || responseCode == 303 || + responseCode == 307 || responseCode == 308) { + String location = connection.getHeaderField("Location"); + connection.disconnect(); + + if (location != null) { + URL redirectUrl = new URL(location); + connection = (HttpURLConnection) redirectUrl.openConnection(); + connection.setConnectTimeout(30000); + connection.setReadTimeout(30000); + connection.setInstanceFollowRedirects(false); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + connection.setRequestProperty("Accept", "application/json"); + + connection.setRequestProperty("callerName", CALLER_NAME); + connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); + connection.setRequestProperty("transaction-id", transactionId); + connection.setRequestProperty("timestamp", timestamp); + connection.setRequestProperty("groupSeq", GROUP_SEQ); + connection.setRequestProperty("wehago-sign", wehagoSign); + + connection.setDoOutput(true); + connection.setDoInput(true); + + OutputStreamWriter redirectWriter = new OutputStreamWriter( + connection.getOutputStream(), StandardCharsets.UTF_8); + redirectWriter.write(requestBody); + redirectWriter.flush(); + redirectWriter.close(); + + responseCode = connection.getResponseCode(); + } + } + + // 응답 읽기 + BufferedReader reader = null; + StringBuilder response = new StringBuilder(); + + try { + if (responseCode >= 200 && responseCode < 300) { + reader = new BufferedReader(new InputStreamReader( + connection.getInputStream(), StandardCharsets.UTF_8)); + } else { + java.io.InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) { + reader = new BufferedReader(new InputStreamReader( + errorStream, StandardCharsets.UTF_8)); + } else { + throw new Exception("API 호출 실패: HTTP " + responseCode); + } + } + + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + } finally { + if (reader != null) { + reader.close(); + } + } + + if (responseCode >= 200 && responseCode < 300) { + return response.toString(); + } else { + throw new Exception("API 호출 실패: HTTP " + responseCode + " - " + response.toString()); + } + + } finally { + connection.disconnect(); + } + } + + /** + * 요청 본문 JSON 생성 + */ + private String buildRequestBody(String coCd, String itemCd, String itemNm, + String acctFg, String odrFg, String unitDc, + String unitmangDc, int unitchngNb, String lotFg, + String qcFg, String reqFg, String setitemFg, String useYn) { + StringBuilder json = new StringBuilder(); + json.append("{"); + json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\""); + json.append(",\"itemCd\":\"").append(escapeJson(itemCd)).append("\""); + json.append(",\"itemNm\":\"").append(escapeJson(itemNm)).append("\""); + json.append(",\"acctFg\":\"").append(escapeJson(acctFg)).append("\""); + json.append(",\"odrFg\":\"").append(escapeJson(odrFg)).append("\""); + json.append(",\"unitDc\":\"").append(escapeJson(unitDc)).append("\""); + json.append(",\"unitmangDc\":\"").append(escapeJson(unitmangDc)).append("\""); + json.append(",\"unitchngNb\":").append(unitchngNb); + json.append(",\"lotFg\":\"").append(escapeJson(lotFg)).append("\""); + json.append(",\"qcFg\":\"").append(escapeJson(qcFg)).append("\""); + json.append(",\"reqFg\":\"").append(escapeJson(reqFg)).append("\""); + json.append(",\"setitemFg\":\"").append(escapeJson(setitemFg)).append("\""); + json.append(",\"useYn\":\"").append(escapeJson(useYn)).append("\""); + json.append("}"); + return json.toString(); + } + + /** + * JSON 문자열 이스케이프 처리 + */ + private String escapeJson(String value) { + if (value == null) { + return ""; + } + return value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } + + /** + * 32자리 랜덤 transaction-id 생성 + */ + private String generateTransactionId() { + String chars = "0123456789abcdef"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(32); + for (int i = 0; i < 32; i++) { + sb.append(chars.charAt(random.nextInt(chars.length()))); + } + return sb.toString(); + } + + /** + * Wehago-sign 생성 + */ + private String generateWehagoSign(String accessToken, String transactionId, + String timestamp, String urlPath) throws Exception { + try { + String value = accessToken + transactionId + timestamp + urlPath; + + SecretKeySpec keySpec = new SecretKeySpec( + HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(keySpec); + byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8)); + + String base64Binary = Base64.encodeBase64String(encrypted); + return base64Binary; + + } catch (Exception e) { + System.err.println("Wehago-sign 생성 오류: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } +} diff --git a/src/com/pms/controller/AdminController.java b/src/com/pms/controller/AdminController.java index 498528d..3395dc5 100644 --- a/src/com/pms/controller/AdminController.java +++ b/src/com/pms/controller/AdminController.java @@ -5308,4 +5308,66 @@ public String clientImportFileProc(HttpServletRequest request, HttpSession sessi return resultMap; } + + /** + * 전체 PART 정보 ERP 전송 + * @param request + * @param paramMap + * @return + */ + @RequestMapping("/admin/sendAllPartsToErp.do") + @ResponseBody + public Map