From 1ad393e939f37e1b10e4b81e4e1aa34cb31c5357 Mon Sep 17 00:00:00 2001 From: Johngreen Date: Fri, 31 Oct 2025 12:00:17 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=ED=8A=B8=EB=93=B1=EB=A1=9D,=20?= =?UTF-8?q?=ED=8C=8C=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EC=97=90=EB=8F=84=20=EB=8F=84=EB=A9=B4=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WEB-INF/view/common/FileDetailPopup.jsp | 99 +++++---- .../WEB-INF/view/partMng/partMngList.jsp | 180 ++++++++++++++++ .../WEB-INF/view/partMng/partMngTempList.jsp | 167 +++++++++++++++ src/com/pms/controller/PartMngController.java | 193 ++++++++++++++++++ src/com/pms/mapper/partMng.xml | 15 ++ 5 files changed, 615 insertions(+), 39 deletions(-) diff --git a/WebContent/WEB-INF/view/common/FileDetailPopup.jsp b/WebContent/WEB-INF/view/common/FileDetailPopup.jsp index 74a224b..e9b09cf 100644 --- a/WebContent/WEB-INF/view/common/FileDetailPopup.jsp +++ b/WebContent/WEB-INF/view/common/FileDetailPopup.jsp @@ -102,19 +102,15 @@ function fn_fileCallback(areaId,fileType){ appendText +=" "; appendText +=" "; */ - appendText+= ""; - appendText+= " "+[i+1]+""; - appendText+= "   "+data[i].REAL_FILE_NAME+""; - /* if(data[i].WRITER=="${connectUserId}" || 'plm_admin'== "${connectUserId}"){ - - appendText+= "
"; - - } - */ - appendText+= ""; - appendText+= " "+data[i].REGDATE+"" - appendText+= " "+data[i].FILE_SIZE+"" - appendText+= ""; + appendText+= ""; + appendText+= " "+[i+1]+""; + appendText+= "   "+data[i].REAL_FILE_NAME+""; + // 도면 삭제 버튼 활성화 + appendText+= "
"; + appendText+= ""; + appendText+= " "+data[i].REGDATE+"" + appendText+= " "+data[i].FILE_SIZE+"" + appendText+= ""; } //Swal.fire(appendText); $("#"+areaId+"FileArea").append(appendText); @@ -138,32 +134,57 @@ function fn_fileCallback(areaId,fileType){ /*첨부 파일 삭제 */ function fileDelete(fileObjId){ - if(confirm("파일을 삭제하시겠습니까?")){ - $.ajax({ - url:"/common/deleteFileInfo.do", - type:"POST", - data:{"objId":fileObjId}, - dataType:"json", - async:true, - success:function(data){ - fn_fileCallback("sr","${docType}"); - // 부모 창의 그리드 새로고침 - if(opener && typeof opener.fn_search == "function"){ - opener.fn_search(); - } - // Tabulator 그리드가 있는 경우 - if(opener && opener._tabulGrid){ - opener._tabulGrid.replaceData(); - } - // 기존 콜백 함수도 실행 - if(fnc_checkNull(callbackFnc) != ""){ - opener.eval(callbackFnc+"();"); - } - }, - error: function(jqxhr, status, error){ - } - }); - } + Swal.fire({ + title: '파일을 삭제하시겠습니까?', + text: '삭제된 파일은 복구할 수 없습니다.', + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: '삭제', + cancelButtonText: '취소', + reverseButtons: false + }).then(function(result) { + if (result.isConfirmed) { + $.ajax({ + url:"/common/deleteFileInfo.do", + type:"POST", + data:{"objId":fileObjId}, + dataType:"json", + async:true, + success:function(data){ + Swal.fire({ + title: '삭제 완료', + text: '파일이 삭제되었습니다.', + icon: 'success', + confirmButtonText: '확인' + }).then(function() { + fn_fileCallback("sr","${docType}"); + // 부모 창의 그리드 새로고침 + if(opener && typeof opener.fn_search == "function"){ + opener.fn_search(); + } + // Tabulator 그리드가 있는 경우 + if(opener && opener._tabulGrid){ + opener._tabulGrid.replaceData(); + } + // 기존 콜백 함수도 실행 + if(fnc_checkNull(callbackFnc) != ""){ + opener.eval(callbackFnc+"();"); + } + }); + }, + error: function(jqxhr, status, error){ + Swal.fire({ + title: '삭제 실패', + text: '파일 삭제 중 오류가 발생했습니다.', + icon: 'error', + confirmButtonText: '확인' + }); + } + }); + } + }); } diff --git a/WebContent/WEB-INF/view/partMng/partMngList.jsp b/WebContent/WEB-INF/view/partMng/partMngList.jsp index 2324081..44fe4ba 100644 --- a/WebContent/WEB-INF/view/partMng/partMngList.jsp +++ b/WebContent/WEB-INF/view/partMng/partMngList.jsp @@ -116,6 +116,16 @@ String connector = person.getUserId(); fn_centerPopup(popup_width, popup_height, url); }); + // 도면 다중 업로드 버튼 클릭 + $("#btnDrawingUpload").click(function() { + $("#drawingFiles").click(); + }); + + // 파일 선택 이벤트 + $("#drawingFiles").change(function() { + fn_uploadDrawingFiles(this.files); + }); + fn_search(); }); }); @@ -292,6 +302,174 @@ String connector = person.getUserId(); fn_centerPopup(popup_width, popup_height, url); } + // 도면 다중 업로드 처리 함수 + function fn_uploadDrawingFiles(files) { + if(!files || files.length === 0) { + Swal.fire('파일을 선택해주세요.'); + return; + } + + // 선택된 파트 확인 (필수 아님 - 전체 파트 대상) + var selectedParts = _tabulGrid.getSelectedData(); + if(!selectedParts || selectedParts.length === 0) { + // 선택 없으면 전체 파트 대상으로 진행 + var confirmMsg = '파트를 선택하지 않았습니다.\n'; + confirmMsg += '전체 파트를 대상으로 파일명과 일치하는 품번에 업로드됩니다.\n'; + confirmMsg += '계속하시겠습니까?'; + + if(!confirm(confirmMsg)) { + return; + } + } + + // 파일 분류 및 처리 + var filesByType = { + '3D': [], // stp 파일 + '2D': [], // dwg 파일 + 'PDF': [] // pdf 파일 + }; + + // 파일 확장자 확인 및 분류 + for(var i = 0; i < files.length; i++) { + var file = files[i]; + var fileName = file.name; + var lastDotIndex = fileName.lastIndexOf('.'); + + if(lastDotIndex === -1) { + continue; // 확장자가 없는 파일은 스킵 + } + + var ext = fileName.substring(lastDotIndex + 1).toLowerCase(); + + if(ext === 'stp' || ext === 'step') { + filesByType['3D'].push(file); + } else if(ext === 'dwg') { + filesByType['2D'].push(file); + } else if(ext === 'pdf') { + filesByType['PDF'].push(file); + } + } + + // 업로드할 파일이 있는지 확인 + var totalFiles = filesByType['3D'].length + filesByType['2D'].length + filesByType['PDF'].length; + if(totalFiles === 0) { + Swal.fire('업로드 가능한 파일 형식이 없습니다. (stp, dwg, pdf만 가능)'); + return; + } + + // 확인 메시지 + var msg = '총 ' + totalFiles + '개의 파일을 업로드하시겠습니까?\n'; + msg += '- 3D (STP): ' + filesByType['3D'].length + '개\n'; + msg += '- 2D (DWG): ' + filesByType['2D'].length + '개\n'; + msg += '- PDF: ' + filesByType['PDF'].length + '개'; + + Swal.fire({ + title: '도면 다중 업로드', + text: msg, + icon: 'question', + showCancelButton: true, + confirmButtonText: '업로드', + cancelButtonText: '취소' + }).then(function(result) { + if(result.isConfirmed) { + fn_processDrawingUpload(filesByType); + } + }); + } + + // 실제 업로드 처리 + function fn_processDrawingUpload(filesByType) { + // 현재 그리드에 표시된 파트 데이터 가져오기 + var gridData = _tabulGrid.getData(); + if(!gridData || gridData.length === 0) { + Swal.fire('페이지에 표시된 파트가 없습니다.'); + return; + } + + // 품번 목록 생성 (현재 화면에 보이는 파트만) + var partNoList = []; + for(var i = 0; i < gridData.length; i++) { + var partNo = gridData[i].PART_NO; + if(partNo) { + partNoList.push(partNo); + } + } + + // FormData 생성 + var formData = new FormData(); + + // 현재 화면의 품번 목록 전송 + formData.append('partNoList', JSON.stringify(partNoList)); + + // 모든 파일을 files 이름으로 추가 + var allFiles = filesByType['3D'].concat(filesByType['2D']).concat(filesByType['PDF']); + for(var i = 0; i < allFiles.length; i++) { + formData.append('files', allFiles[i]); + } + + // 로딩 표시 + Swal.fire({ + title: '업로드 중...', + text: '파일을 업로드하는 중입니다. 잠시만 기다려주세요.', + allowOutsideClick: false, + allowEscapeKey: false, + allowEnterKey: false, + showConfirmButton: false, + onOpen: function() { + Swal.showLoading(); + } + }); + + // AJAX 업로드 + $.ajax({ + url: '/partMng/uploadDrawingFilesForPartList.do', + type: 'POST', + data: formData, + processData: false, + contentType: false, + success: function(response) { + Swal.close(); + + if(response.result === 'success') { + var successMsg = '도면 업로드가 완료되었습니다.\n\n'; + successMsg += '- 성공: ' + response.successCount + '개\n'; + if(response.failCount > 0) { + successMsg += '- 실패: ' + response.failCount + '개\n'; + } + if(response.notFoundCount > 0) { + successMsg += '- 품번 미존재: ' + response.notFoundCount + '개\n'; + } + + Swal.fire({ + title: '업로드 완료', + text: successMsg, + icon: response.failCount > 0 ? 'warning' : 'success' + }).then(function() { + // 그리드 새로고침 + fn_search(); + // 파일 input 초기화 + $("#drawingFiles").val(''); + }); + } else { + Swal.fire({ + title: '업로드 실패', + text: response.message || '도면 업로드 중 오류가 발생했습니다.', + icon: 'error' + }); + } + }, + error: function(xhr, status, error) { + Swal.close(); + console.error('Upload error:', error); + Swal.fire({ + title: '업로드 실패', + text: '서버 오류가 발생했습니다: ' + error, + icon: 'error' + }); + } + }); + } + @@ -314,8 +492,10 @@ String connector = person.getUserId(); + +
diff --git a/WebContent/WEB-INF/view/partMng/partMngTempList.jsp b/WebContent/WEB-INF/view/partMng/partMngTempList.jsp index 291e6c1..960734e 100644 --- a/WebContent/WEB-INF/view/partMng/partMngTempList.jsp +++ b/WebContent/WEB-INF/view/partMng/partMngTempList.jsp @@ -115,6 +115,16 @@ ui-jqgrid tr.jqgrow td { }); }); + + // 도면 다중 업로드 버튼 클릭 + $("#btnDrawingUpload").click(function() { + $("#drawingFiles").click(); + }); + + // 파일 선택 이벤트 + $("#drawingFiles").change(function() { + fn_uploadDrawingFiles(this.files); + }); }); @@ -410,6 +420,161 @@ ui-jqgrid tr.jqgrow td { fn_centerPopup(popup_width, popup_height, url); } + // 도면 다중 업로드 처리 함수 + function fn_uploadDrawingFiles(files) { + if(!files || files.length === 0) { + Swal.fire('파일을 선택해주세요.'); + return; + } + + // 파일 분류 및 처리 + var filesByType = { + '3D': [], // stp 파일 + '2D': [], // dwg 파일 + 'PDF': [] // pdf 파일 + }; + + // 파일 확장자 확인 및 분류 + for(var i = 0; i < files.length; i++) { + var file = files[i]; + var fileName = file.name; + var lastDotIndex = fileName.lastIndexOf('.'); + + if(lastDotIndex === -1) { + continue; // 확장자가 없는 파일은 스킵 + } + + var ext = fileName.substring(lastDotIndex + 1).toLowerCase(); + + if(ext === 'stp' || ext === 'step') { + filesByType['3D'].push(file); + } else if(ext === 'dwg') { + filesByType['2D'].push(file); + } else if(ext === 'pdf') { + filesByType['PDF'].push(file); + } + } + + // 업로드할 파일이 있는지 확인 + var totalFiles = filesByType['3D'].length + filesByType['2D'].length + filesByType['PDF'].length; + if(totalFiles === 0) { + Swal.fire('업로드 가능한 파일 형식이 없습니다. (stp, dwg, pdf만 가능)'); + return; + } + + // 확인 메시지 + var msg = '총 ' + totalFiles + '개의 파일을 업로드하시겠습니까?\n'; + msg += '- 3D (STP): ' + filesByType['3D'].length + '개\n'; + msg += '- 2D (DWG): ' + filesByType['2D'].length + '개\n'; + msg += '- PDF: ' + filesByType['PDF'].length + '개'; + + Swal.fire({ + title: '도면 다중 업로드', + text: msg, + icon: 'question', + showCancelButton: true, + confirmButtonText: '업로드', + cancelButtonText: '취소' + }).then(function(result) { + if(result.isConfirmed) { + fn_processDrawingUpload(filesByType); + } + }); + } + + // 실제 업로드 처리 + function fn_processDrawingUpload(filesByType) { + // 현재 그리드에 표시된 파트 데이터 가져오기 + var gridData = _tabulGrid.getData(); + if(!gridData || gridData.length === 0) { + Swal.fire('페이지에 표시된 파트가 없습니다.'); + return; + } + + // 품번 목록 생성 (현재 화면에 보이는 파트만) + var partNoList = []; + for(var i = 0; i < gridData.length; i++) { + var partNo = gridData[i].PART_NO; + if(partNo) { + partNoList.push(partNo); + } + } + + // FormData 생성 + var formData = new FormData(); + + // 현재 화면의 품번 목록 전송 + formData.append('partNoList', JSON.stringify(partNoList)); + + // 모든 파일을 files 이름으로 추가 + var allFiles = filesByType['3D'].concat(filesByType['2D']).concat(filesByType['PDF']); + for(var i = 0; i < allFiles.length; i++) { + formData.append('files', allFiles[i]); + } + + // 로딩 표시 + Swal.fire({ + title: '업로드 중...', + text: '파일을 업로드하는 중입니다. 잠시만 기다려주세요.', + allowOutsideClick: false, + allowEscapeKey: false, + allowEnterKey: false, + showConfirmButton: false, + onOpen: function() { + Swal.showLoading(); + } + }); + + // AJAX 업로드 + $.ajax({ + url: '/partMng/uploadDrawingFilesForPartList.do', + type: 'POST', + data: formData, + processData: false, + contentType: false, + success: function(response) { + Swal.close(); + + if(response.result === 'success') { + var successMsg = '도면 업로드가 완료되었습니다.\n\n'; + successMsg += '- 성공: ' + response.successCount + '개\n'; + if(response.failCount > 0) { + successMsg += '- 실패: ' + response.failCount + '개\n'; + } + if(response.notFoundCount > 0) { + successMsg += '- 품번 미존재: ' + response.notFoundCount + '개\n'; + } + + Swal.fire({ + title: '업로드 완료', + text: successMsg, + icon: response.failCount > 0 ? 'warning' : 'success' + }).then(function() { + // 그리드 새로고침 + fn_search(); + // 파일 input 초기화 + $("#drawingFiles").val(''); + }); + } else { + Swal.fire({ + title: '업로드 실패', + text: response.message || '도면 업로드 중 오류가 발생했습니다.', + icon: 'error' + }); + } + }, + error: function(xhr, status, error) { + Swal.close(); + console.error('Upload error:', error); + Swal.fire({ + title: '업로드 실패', + text: '서버 오류가 발생했습니다: ' + error, + icon: 'error' + }); + } + }); + } + function openExcelPopup() { /* if($("#customer_cd").val()==""){ @@ -448,7 +613,9 @@ ui-jqgrid tr.jqgrow td { + +
diff --git a/src/com/pms/controller/PartMngController.java b/src/com/pms/controller/PartMngController.java index fe4e20d..4515630 100644 --- a/src/com/pms/controller/PartMngController.java +++ b/src/com/pms/controller/PartMngController.java @@ -33,6 +33,9 @@ import com.pms.service.PartMgmtService; import com.pms.service.PartMngService; import com.pms.service.ProductMgmtService; import com.pms.service.ProjectConceptService; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; @Controller public class PartMngController { @@ -2335,4 +2338,194 @@ public class PartMngController { return resultMap; } + + /** + * PART 목록 화면에서 도면 파일 일괄 업로드 + * BOM 정보 없이 전체 파트를 대상으로 파일명 매칭 + * + * @param request + * @param session + * @return + */ + @RequestMapping(value="/partMng/uploadDrawingFilesForPartList.do", method=RequestMethod.POST) + @ResponseBody + public Map uploadDrawingFilesForPartList( + HttpServletRequest request, + HttpSession session) { + + Map resultMap = new HashMap<>(); + MultipartRequest multi = null; + FileRenameClass frc = null; + + try { + PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN); + String userId = person != null ? person.getUserId() : "plmAdmin"; + + // MultipartRequest로 파일 업로드 처리 + String storagePath = Constants.FILE_STORAGE; + int maxSize = 1024*1024*1024*9; // 9GB + + File storage = new File(storagePath); + if(!storage.exists()) storage.mkdirs(); + + frc = new FileRenameClass(); + multi = new MultipartRequest(request, storagePath, maxSize, "UTF-8", frc); + java.util.List fileList = frc.getFileList(); + + // 화면에서 전달된 품번 목록 가져오기 + String partNoListJson = multi.getParameter("partNoList"); + if(partNoListJson == null || partNoListJson.isEmpty()) { + resultMap.put("result", "fail"); + resultMap.put("message", "품번 목록이 전달되지 않았습니다."); + return resultMap; + } + + // JSON 파싱 (Gson 사용) + Gson gson = new Gson(); + Type listType = new TypeToken>(){}.getType(); + java.util.List partNoList = gson.fromJson(partNoListJson, listType); + + if(partNoList.isEmpty()) { + resultMap.put("result", "fail"); + resultMap.put("message", "페이지에 표시된 파트가 없습니다."); + return resultMap; + } + + System.out.println("========== 화면에서 전달된 품번 목록 =========="); + System.out.println("품번 개수: " + partNoList.size()); + System.out.println("품번 목록: " + partNoList); + System.out.println("=========================================="); + + // 전달된 품번 목록에 해당하는 파트만 조회 + Map partParam = new HashMap<>(); + partParam.put("IS_LAST", "1"); + partParam.put("PART_NO_LIST", partNoList); + ArrayList partList = commonService.selectList("partMng.partMngListByPartNos", request, partParam); + + if(partList == null || partList.isEmpty()) { + resultMap.put("result", "fail"); + resultMap.put("message", "조회된 파트가 없습니다."); + return resultMap; + } + + // 품번 기준 파트 맵 생성 (화면에 표시된 파트만) + Map partNoMap = new HashMap<>(); + for(Map part : partList) { + String partNo = CommonUtils.checkNull((String)part.get("PART_NO")); + if(!partNo.isEmpty()) { + partNoMap.put(partNo, part); + } + } + + System.out.println("매칭 가능한 품번 개수: " + partNoMap.size()); + + int successCount = 0; + int failCount = 0; + int notFoundCount = 0; + + // 업로드된 파일 처리 + if(fileList != null && !fileList.isEmpty()) { + for(Object fileObj : fileList) { + Map fileInfo = (Map)fileObj; + String originalFileName = CommonUtils.checkNull((String)fileInfo.get("realFileName")); + String savedFileName = CommonUtils.checkNull((String)fileInfo.get("savedFileName")); + String fileExt = CommonUtils.checkNull((String)fileInfo.get("fileExt")); + long fileSize = Long.parseLong(CommonUtils.checkNull(fileInfo.get("fileSize"), "0")); + + // 확장자 대문자 변환 + fileExt = fileExt.toUpperCase(); + + System.out.println("========== 파트 목록 파일 업로드 처리 =========="); + System.out.println("원본 파일명: " + originalFileName); + System.out.println("저장 파일명: " + savedFileName); + System.out.println("확장자: " + fileExt); + System.out.println("=========================================="); + + // 파일 확장자에 따른 문서 타입 결정 + String docType = ""; + String docTypeName = ""; + + if("STP".equals(fileExt) || "STEP".equals(fileExt)) { + docType = "3D_CAD"; + docTypeName = "3D CAD 첨부파일"; + } else if("DWG".equals(fileExt)) { + docType = "2D_DRAWING_CAD"; + docTypeName = "2D(Drawing) CAD 첨부파일"; + } else if("PDF".equals(fileExt)) { + docType = "2D_PDF_CAD"; + docTypeName = "2D(PDF) CAD 첨부파일"; + } else { + // 지원하지 않는 확장자는 스킵 + System.out.println("지원하지 않는 확장자: " + fileExt + ", 파일명: " + originalFileName); + continue; + } + + // 파일명에서 확장자 제거하여 품번 추출 + String fileNameWithoutExt = originalFileName; + int lastDotIndex = originalFileName.lastIndexOf('.'); + if(lastDotIndex > 0) { + fileNameWithoutExt = originalFileName.substring(0, lastDotIndex); + } + + // 품번과 정확히 일치하는 경우만 매칭 + String matchedPartNo = null; + System.out.println("품번 매칭 시작 - 파일명(확장자 제외): " + fileNameWithoutExt); + + // 정확한 매칭 (품번과 파일명이 정확히 일치) + if(partNoMap.containsKey(fileNameWithoutExt)) { + matchedPartNo = fileNameWithoutExt; + System.out.println(" ✓ 품번 정확 매칭 성공: " + matchedPartNo); + } + + if(matchedPartNo == null) { + System.out.println(" ✗ 품번 매칭 실패 - 파일명과 일치하는 품번이 없음"); + notFoundCount++; + continue; + } + + // 해당 파트에 파일 정보 저장 + Map partInfo = partNoMap.get(matchedPartNo); + String partObjId = CommonUtils.checkNull((String)partInfo.get("OBJID")); + + Map fileMap = new HashMap<>(); + fileMap.put("OBJID", CommonUtils.createObjId()); + fileMap.put("TARGET_OBJID", partObjId); + fileMap.put("SAVED_FILE_NAME", savedFileName); + fileMap.put("REAL_FILE_NAME", originalFileName); + fileMap.put("DOC_TYPE", docType); + fileMap.put("DOC_TYPE_NAME", docTypeName); + fileMap.put("FILE_SIZE", String.valueOf(fileSize)); + fileMap.put("FILE_EXT", fileExt); + fileMap.put("FILE_PATH", storagePath); + fileMap.put("WRITER", userId); + + try { + // 파일 정보 DB 저장 + partMngService.insertDrawingFile(fileMap); + successCount++; + } catch(Exception e) { + e.printStackTrace(); + failCount++; + } + } + } + + resultMap.put("result", "success"); + resultMap.put("successCount", successCount); + resultMap.put("failCount", failCount); + resultMap.put("notFoundCount", notFoundCount); + resultMap.put("message", "업로드가 완료되었습니다."); + + } catch(Exception e) { + e.printStackTrace(); + resultMap.put("result", "error"); + resultMap.put("message", "업로드 중 오류가 발생했습니다: " + e.getMessage()); + } finally { + if(frc != null) { + frc.clear(); + } + } + + return resultMap; + } } diff --git a/src/com/pms/mapper/partMng.xml b/src/com/pms/mapper/partMng.xml index 3b98ecf..01b0b45 100644 --- a/src/com/pms/mapper/partMng.xml +++ b/src/com/pms/mapper/partMng.xml @@ -2027,6 +2027,21 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.* ) + + +