diff --git a/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp b/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp index 951d68c..ddb36dc 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp @@ -97,196 +97,23 @@ $(function(){ // }, 500); - //Part 연결 + //Part 연결 (M-BOM용) $("#moveLeft").click(function(){ - // Tabulator에서 선택된 오른쪽 행 데이터 가져오기 - var rightFrame = parent.frames['rightFrame']; - var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : []; - - if(rightSelectedRows.length === 0) { - showConfirm("선택된 파트가 없습니다."); - return false; - } - - // 왼쪽 프레임에서 선택된 행 데이터 가져오기 - var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document); - - var leftPartChildObjId = null; - var leftPartNo = null; - var leftPartNoQty = null; - var leftParentObjId = null; - var leftPartLastObjId = null; - var leftQtyParObjId = null; - var leftParentParts = ""; - - if(leftPartNoObj.length > 0) { - leftPartChildObjId = leftPartNoObj.val(); - leftPartNo = leftPartNoObj.attr("data-PART_NO"); - leftPartNoQty = leftPartNoObj.attr("data-PART_NO_QTY"); - leftParentObjId = leftPartNoObj.attr("data-OBJID"); - leftPartLastObjId = leftPartNoObj.attr("data-LAST_PART_OBJID"); - leftQtyParObjId = leftPartNoObj.attr("data-PART_OBJID"); - leftParentParts = leftPartNoObj.attr("data-PARENT_PARTS") || ""; - } - - // 같은 Part를 연결한건지 체크 - var isSamePart = false; - for(var i = 0; i < rightSelectedRows.length; i++){ - var rowData = rightSelectedRows[i].getData(); - var rightPartNo = rowData.PART_NO; - if(rightPartNo == leftPartNo){ - showConfirm({ - title: '연결 불가', - html: '오류 Part No : ['+rightPartNo+']
같은 Part No끼리 연결할 수 없습니다.', - icon: 'error' - }); - isSamePart = true; - break; - } - } - - if(isSamePart) return false; - - // 연결하려는 part가 상위에 있는 part인지 확인 - var deniedPartArr = []; - if(fnc_checkNull(leftParentParts).indexOf(",") > 0){ - deniedPartArr = leftParentParts.split(","); - } - var isDeniedPart = false; - - for(var i = 0; i < rightSelectedRows.length; i++){ - var rowData = rightSelectedRows[i].getData(); - var rightPartNo = rowData.PART_NO; - var rightPartType = rowData.PART_TYPE; - - if("unique" == rightPartType){ - for(var j = 0 ; j < deniedPartArr.length ; j++){ - if(rightPartNo == deniedPartArr[j]){ - showConfirm({ - title: '연결 불가', - html: '오류 Part No : ['+rightPartNo+']
이미 상위에 등록된 Part No 입니다.', - icon: 'error' - }); - isDeniedPart = true; - break; - } - } - if(isDeniedPart) break; - } - } - - if(isDeniedPart) return; - - // 선택된 파트의 OBJID 배열 생성 - var rightCheckedArr = []; - for(var i = 0; i < rightSelectedRows.length; i++){ - var rowData = rightSelectedRows[i].getData(); - rightCheckedArr.push(rowData.OBJID); - } - - if(fnc_checkNull(leftPartNo) == ""){ - var flag = fn_checkSameTopPartNo(rightCheckedArr); - if(flag == "true"){ - showConfirm({ - title: '중복 등록 불가', - text: '1레벨에 같은 Part No가 중복 등록될 수 없습니다.', - icon: 'error' - }); - return; - } - } - - // Part 연결 확인 - showConfirm({ - title: 'Part 연결', - text: '선택한 Part를 연결하시겠습니까?', - icon: 'question', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: '연결', - cancelButtonText: '취소', - reverseButtons: false - }).then(result => { - if (result.isConfirmed) { - fn_relatePartInfo(leftPartChildObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId); - } - }); + fn_mbomAddPart(); }); - //end of Part 연결 - //연결된 part 삭제 + //연결된 part 삭제 (M-BOM용) $("#moveRight").click(function(){ - var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document); - var leftPartChildObjId = leftPartNoObj.val(); - var leftPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO"); - var leftParentPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_PART_NO"); - var leftParentObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_OBJID"); - var leftPartLastObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-LAST_PART_OBJID"); - - fn_deletePartRelateInfo(leftPartNoObj.val(), leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId); + fn_mbomDeletePart(); }); - //end of 연결된 part 삭제 - //연결된 part 변경 + //연결된 part 변경 (M-BOM용) $("#moveChange").click(function(){ - var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document); - - if(leftPartNoList.length === 0){ - showConfirm("선택된 파트가 없습니다."); - return false; - } - - if(leftPartNoList.length > 1){ - showConfirm("한번에 1개의 파트만 변경가능합니다."); - return false; - } - - var leftPartNo = leftPartNoList.attr("data-PART_NO"); - var leftPartObjid = leftPartNoList.attr("data-BOM_LAST_PART_OBJID"); - var leftParentPartObjid = leftPartNoList.attr("data-PARENT_PART_NO"); - var leftPartChildObjId = leftPartNoList.val(); - var leftPartBomQtyObjId = leftPartNoList.attr("data-OBJID"); - - // Tabulator에서 선택된 오른쪽 행 데이터 가져오기 - var rightFrame = parent.frames['rightFrame']; - var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : []; - - if(rightSelectedRows.length === 0){ - showConfirm("선택된 파트가 없습니다."); - return false; - } - - if(rightSelectedRows.length > 1){ - showConfirm("한번에 1개의 파트만 변경가능합니다."); - return false; - } - - var rightRowData = rightSelectedRows[0].getData(); - var rightPartNo = rightRowData.PART_NO; - var rightPartRev = rightRowData.REVISION || ""; - var rightObjId = rightRowData.OBJID; - - // Part 변경 확인 - showConfirm({ - title: 'Part 변경', - html: '선택한 Part를 변경하시겠습니까?

' + - '기존: ' + leftPartNo + '
' + - '변경: ' + rightPartNo, - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: '변경', - cancelButtonText: '취소', - reverseButtons: false - }).then(result => { - if (result.isConfirmed) { - fn_changeRelatePartInfo(leftPartBomQtyObjId, rightObjId, leftPartChildObjId, leftParentPartObjid, leftPartChildObjId, leftPartObjid, rightPartNo, rightPartRev); - } - }); + fn_mbomChangePart(); }); + + // 이력보기 버튼 클릭 $("#btnHistory").click(function(){ fn_showHistory(); @@ -309,197 +136,6 @@ $(function(){ }); -//1레벨에 같은 Part No가 등록되어있는지 확인. -function fn_checkSameTopPartNo(rightCheckedArr){ - var result = false; - - $.ajax({ - url: "/productionplanning/checkSameTopPartNo.do", - method: 'post', - traditional: true, // 배열 파라미터 처리를 위한 옵션 - data: {"OBJID":$("#objId").val(), "rightCheckedArr[]":rightCheckedArr}, // [] 추가 - dataType: 'json', - async:false, - success: function(data) { - result = data.result; - } - , error: function(jqxhr, status, error){ - /* alert(jqxhr.statusText + ", " + status + ", " + error); - alert(jqxhr.status); - alert(jqxhr.responseText); */ - } - }); - return result; -} -//end of 1레벨에 같은 Part No가 등록되어있는지 확인. - -//구조 연결 해제 -function fn_deletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){ - if(leftObjId == null){ - showConfirm("연결 해제할 Part를 선택해 주시기 바랍니다."); - return; - } - - showConfirm({ - title: 'Part 연결 해제', - text: '선택한 Part의 연결을 해제하시겠습니까?', - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: '연결 해제', - cancelButtonText: '취소', - reverseButtons: false - }).then(result => { - if (!result.isConfirmed) return; - - fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId); - }); -} - -// 실제 Part 연결 해제 실행 함수 -function fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){ - - $.ajax({ - url: "/productionplanning/deleteStatusPartRelateInfo.do", - method: 'post', - data: {"OBJID":$("#objId").val(), "leftObjId":leftObjId, "partObjId":leftPartLastObjId, "BOM_REPORT_OBJID":$("#objId").val() - , "leftPartChildObjId":leftPartChildObjId, "leftParentPartNo":leftParentPartNo, "leftParentObjId":leftParentObjId}, - dataType: 'json', - success: function(data) { - if(data.result){ - $(parent.frames['leftFrame'].document.location.reload()); - //$(parent.frames['rightFrame'].fn_searchPart()); - - // 부모 창(E-BOM 목록) 새로고침 - if(window.opener && window.opener.fn_search) { - window.opener.fn_search(); - } - } - } - , error: function(jqxhr, status, error){ - /* alert(jqxhr.statusText + ", " + status + ", " + error); - alert(jqxhr.status); - alert(jqxhr.responseText); */ - } - }); -} -//end of 구조 연결 해제 - -//구조 연결 -function fn_relatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){ - if(typeof rightCheckedArr != "undefined" && rightCheckedArr.length == 0){ - showConfirm("선택된 Part가 없습니다."); - return; - } - - if(leftObjId == null){ - showConfirm({ - title: '1레벨 등록 확인', - html: '좌측에 선택된 Part정보가 없습니다.
이대로 연결하면 1레벨로 등록됩니다.

진행하시겠습니까?', - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: '진행', - cancelButtonText: '취소', - reverseButtons: false - }).then(result => { - if (result.isConfirmed) { - fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId); - } - }); - return; - } - - fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId); -} - -// 실제 Part 연결 실행 함수 -function fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){ - - $.ajax({ - url: "/productionplanning/relatePartInfo.do", - method: 'post', - traditional: true, // 배열 파라미터 처리를 위한 옵션 - data: { - "leftObjId": leftObjId, - "leftPartNoQty": leftPartNoQty, - "OBJID": $("#objId").val(), - "rightCheckedArr[]": rightCheckedArr, // Spring의 @RequestParam(value = "rightCheckedArr[]") 형식 - "partObjId": leftPartLastObjId, - "BOM_REPORT_OBJID": $("#objId").val(), - "leftPartChildObjId": leftPartChildObjId, - "leftQtyParObjId": leftQtyParObjId - }, - dataType: 'json', - async:false, - success: function(data) { - if(data.result){ - // 왼쪽 프레임 새로고침 - $(parent.frames['leftFrame'].document.location.reload()); - - // 오른쪽 프레임 선택 해제 (Tabulator) - var rightFrame = parent.frames['rightFrame']; - if(rightFrame.clearSelection) { - rightFrame.clearSelection(); - } - - // 부모 창(E-BOM 목록) 새로고침 - if(window.opener && window.opener.fn_search) { - window.opener.fn_search(); - } - } - } - , error: function(jqxhr, status, error){ - /* alert(jqxhr.statusText + ", " + status + ", " + error); - alert(jqxhr.status); - alert(jqxhr.responseText); */ - } - }); -} -//end of 구조 연결 - -//구조 연결 변경 -function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPartChildObjId,leftPartObjid,rightPartNo,rightPartRev){ - $.ajax({ - url: "/productionplanning/changeRelatePartInfo.do", - method: 'post', - data: {"rightObjId":rightObjId, "OBJID":objId,"BOM_REPORT_OBJID":$("#objId").val(), - "leftObjId":leftObjId,"leftPartNoQty":leftPartNoQty,"leftPartChildObjId":leftPartChildObjId, - "leftPartObjid":leftPartObjid, "rightPartNo":rightPartNo, "rightPartRev":rightPartRev}, - dataType: 'json', - async:false, - success: function(data) { - if(data.result){ - // 왼쪽 프레임 새로고침 - $(parent.frames['leftFrame'].document.location.reload()); - - // 오른쪽 프레임 검색 다시 수행 - var rightFrame = parent.frames['rightFrame']; - if(rightFrame.fn_searchPart) { - rightFrame.fn_searchPart(); - } - - // 오른쪽 프레임 선택 해제 (Tabulator) - if(rightFrame.clearSelection) { - rightFrame.clearSelection(); - } - - // 부모 창(E-BOM 목록) 새로고침 - if(window.opener && window.opener.fn_search) { - window.opener.fn_search(); - } - } - } - , error: function(jqxhr, status, error){ - /* alert(jqxhr.statusText + ", " + status + ", " + error); - alert(jqxhr.status); - alert(jqxhr.responseText); */ - } - }); -} - // 가공납기/연삭납기 일괄 적용 function fn_applyBulkDeadline() { var processingDeadline = $("#bulk_processing_deadline").val(); @@ -1198,6 +834,260 @@ function compareItemFields(before, after) { - + + + + - + \ No newline at end of file diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupCenter.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupCenter.jsp index 03abffe..b83ce3e 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupCenter.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupCenter.jsp @@ -38,8 +38,27 @@ $(function(){ $('.select2').select2(); - //Part 연결 (<< 버튼) + //Part 연결 (<< 버튼) - M-BOM용 $("#moveLeft").click(function(){ + parent.parent.frames[0].fn_mbomAddPart(); + }); + + //Part 삭제 (>> 버튼) - M-BOM용 + $("#moveRight").click(function(){ + parent.parent.frames[0].fn_mbomDeletePart(); + }); + + //Part 변경 - M-BOM용 + $("#moveChange").click(function(){ + parent.parent.frames[0].fn_mbomChangePart(); + }); + +}); + +/* ==================== E-BOM 원본 코드 (주석 처리) ==================== +$(function(){ + //Part 연결 (<< 버튼) - E-BOM 원본 + $("#moveLeft_EBOM_ORIGINAL").click(function(){ // Tabulator에서 선택된 오른쪽 행 데이터 가져오기 var rightFrame = parent.frames['rightFrame']; var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : []; @@ -346,6 +365,8 @@ function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPa } }); } +// ==================== E-BOM 원본 코드 주석 끝 ==================== +*/ diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp index a5f3263..6ff698b 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp @@ -89,7 +89,9 @@ function fn_initGrid() { 'data-PARENT_OBJID="' + (rowData.PARENT_OBJID || '') + '" ' + 'data-PART_OBJID="' + (rowData.PART_OBJID || '') + '" ' + 'data-BOM_LAST_PART_OBJID="' + (rowData.BOM_LAST_PART_OBJID || rowData.LAST_PART_OBJID || '') + '" ' + - 'data-CHILD_OBJID="' + (rowData.CHILD_OBJID || '') + '">'; + 'data-CHILD_OBJID="' + (rowData.CHILD_OBJID || '') + '" ' + + 'data-LEVEL="' + (rowData.LEVEL || 1) + '" ' + + 'data-PART_NO="' + (rowData.PART_NO || '') + '">'; } }); diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp index 37b97f9..07a204e 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp @@ -132,7 +132,7 @@ function initEbomTable() { }); } -// E-BOM 검색 +// 파트 검색 (PART_MNG 테이블에서 조회) function fn_searchEbom() { var partNo = $("#search_part_no").val().trim(); var partName = $("#search_part_name").val().trim(); @@ -140,19 +140,20 @@ function fn_searchEbom() { var supplier = $("#search_supplier").val().trim(); $.ajax({ - url: "/productionplanning/getEbomList.do", + url: "/partMng/getPartMngList_ajax.do", method: 'post', data: { "search_part_no": partNo, "search_part_name": partName, "search_material": material, - "search_supplier": supplier + "search_maker": supplier, + "status": "release" // 승인된 파트만 조회 }, dataType: 'json', - success: function(data) { - if(data && data.list) { + success: function(response) { + if(response && response.data) { // 품번과 품명이 비어있지 않은 데이터만 필터링 - allData = data.list.filter(function(item) { + allData = response.data.filter(function(item) { return item.PART_NO && item.PART_NO.trim() !== '' && item.PART_NAME && item.PART_NAME.trim() !== ''; }); @@ -163,7 +164,7 @@ function fn_searchEbom() { fnc_goPage(1); }, error: function(jqxhr, status, error){ - console.error("E-BOM 조회 오류:", error); + console.error("파트 조회 오류:", error); allData = []; ebomTable.setData([]); updatePagination(); diff --git a/src/com/pms/mapper/productionplanning.xml b/src/com/pms/mapper/productionplanning.xml index 3bb1f8c..869dea1 100644 --- a/src/com/pms/mapper/productionplanning.xml +++ b/src/com/pms/mapper/productionplanning.xml @@ -3637,86 +3637,230 @@ diff --git a/src/com/pms/service/ProductionPlanningService.java b/src/com/pms/service/ProductionPlanningService.java index 3ac4614..ad6c8de 100644 --- a/src/com/pms/service/ProductionPlanningService.java +++ b/src/com/pms/service/ProductionPlanningService.java @@ -1007,4 +1007,486 @@ public class ProductionPlanningService { return result; } + + /** + * BOM 할당 정보 저장 + */ + public boolean saveBomAssignment(HttpServletRequest request, Map paramMap) { + SqlSession sqlSession = null; + boolean result = false; + + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + int updateCount = sqlSession.update("productionplanning.saveBomAssignment", paramMap); + result = (updateCount > 0); + sqlSession.commit(); + } catch(Exception e) { + if(sqlSession != null) { + sqlSession.rollback(); + } + e.printStackTrace(); + } finally { + if(sqlSession != null) { + sqlSession.close(); + } + } + + return result; + } + + /** + * M-BOM 할당 정보 조회 + */ + public Map getMbomAssignment(String projectObjId) { + SqlSession sqlSession = null; + Map result = null; + + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + Map paramMap = new HashMap<>(); + paramMap.put("projectObjId", projectObjId); + result = sqlSession.selectOne("productionplanning.getMbomAssignment", paramMap); + } catch(Exception e) { + e.printStackTrace(); + } finally { + if(sqlSession != null) { + sqlSession.close(); + } + } + + return result; + } + + /** + * M-BOM 품번 생성 + * E-BOM 기준: M-{E-BOM품번}-YYMMDD-01 + * M-BOM 기준: {기존품번앞부분}-YYMMDD-01 + */ + public String generateMbomNo(HttpServletRequest request, String sourceBomType, String baseBomPartNo) { + SqlSession sqlSession = null; + String mbomNo = ""; + + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + + // 현재 날짜 (YYMMDD) + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyMMdd"); + String dateStr = sdf.format(new java.util.Date()); + + String mbomPrefix = ""; + + if("EBOM".equals(sourceBomType)) { + // E-BOM 기준: M-{E-BOM품번}-YYMMDD + // E-BOM 품번이 이미 M-으로 시작하면 중복 방지 + String cleanPartNo = baseBomPartNo; + if(cleanPartNo.startsWith("M-")) { + cleanPartNo = cleanPartNo.substring(2); // "M-" 제거 + } + mbomPrefix = "M-" + cleanPartNo + "-" + dateStr; + } else if("MBOM".equals(sourceBomType)) { + // M-BOM 기준: 기존 M-BOM 품번에서 날짜/순번 부분 제거 후 새 날짜 추가 + // 예: M-000AN014000-251124-01 → M-000AN014000 → M-000AN014000-251124 + + String basePart = baseBomPartNo; + + // 잘못된 형식 정규화 (M-M-..., M-E-... 등) + // 예: M-M-000AN014000-251124-01-251124 → M-000AN014000-251124-01-251124 + while(basePart.startsWith("M-M-") || basePart.startsWith("M-E-")) { + basePart = "M-" + basePart.substring(4); + } + + // 마지막 두 개의 "-XX" 부분 제거 (날짜-순번) + // 여러 번 중복된 경우를 대비해 반복 제거 + for(int i = 0; i < 2; i++) { + int lastDashIndex = basePart.lastIndexOf("-"); + if(lastDashIndex > 0) { + String suffix = basePart.substring(lastDashIndex + 1); + // 날짜(6자리) 또는 순번(2자리)인 경우만 제거 + if(suffix.matches("\\d{2}") || suffix.matches("\\d{6}")) { + basePart = basePart.substring(0, lastDashIndex); + } else { + break; + } + } + } + + // 새 날짜 추가 + mbomPrefix = basePart + "-" + dateStr; + } + + // 같은 날짜의 최대 순번 조회 + Map seqParam = new HashMap<>(); + seqParam.put("mbomPrefix", mbomPrefix); + + Integer maxSeq = (Integer) sqlSession.selectOne("productionplanning.getMaxMbomSeqByDate", seqParam); + int nextSeq = (maxSeq != null ? maxSeq : 0) + 1; + + // 최종 M-BOM 품번 생성 + mbomNo = mbomPrefix + "-" + String.format("%02d", nextSeq); + + System.out.println("생성된 M-BOM 품번: " + mbomNo); + + } catch(Exception e) { + e.printStackTrace(); + } finally { + if(sqlSession != null) { + sqlSession.close(); + } + } + + return mbomNo; + } + + /** + * M-BOM 저장 + * 최초 저장: MBOM_HEADER, MBOM_DETAIL 생성 + * 수정 저장: 기존 데이터 업데이트 + MBOM_HISTORY 이력 저장 + */ + public boolean saveMbom(HttpServletRequest request, Map paramMap) { + SqlSession sqlSession = null; + boolean result = false; + + try { + // 트랜잭션 처리를 위해 autoCommit = false로 설정 + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + // 세션에서 사용자 정보 가져오기 + HttpSession session = request.getSession(); + PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN); + String userId = CommonUtils.checkNull(person.getUserId()); + paramMap.put("sessionUserId", userId); + + boolean isUpdate = (Boolean) paramMap.get("isUpdate"); + String mbomNo = (String) paramMap.get("mbomNo"); + + if(isUpdate) { + // 수정: 기존 M-BOM 조회 (최신 버전) + System.out.println("========== UPDATE M-BOM =========="); + System.out.println("projectObjId: " + paramMap.get("projectObjId")); + System.out.println("mbomNo: " + paramMap.get("mbomNo")); + + Map existingMbom = sqlSession.selectOne("productionplanning.getMbomByNo", paramMap); + + if(existingMbom != null) { + System.out.println("조회된 기존 M-BOM:"); + System.out.println(" OBJID: " + existingMbom.get("OBJID")); + System.out.println(" MBOM_NO: " + existingMbom.get("MBOM_NO")); + System.out.println(" REGDATE: " + existingMbom.get("REGDATE")); + + // 기존 M-BOM 상세 데이터 조회 + Map detailParam = new HashMap<>(); + detailParam.put("mbomHeaderObjid", existingMbom.get("OBJID")); + List> existingDetails = sqlSession.selectList("productionplanning.getMbomDetailList", detailParam); + + System.out.println("기존 M-BOM 상세 항목 수: " + existingDetails.size()); + + // 새 데이터 + List> mbomData = (List>) paramMap.get("mbomData"); + + // 변경 사항 비교 (childObjid 기준) + Map> beforeMap = new HashMap<>(); + Map> afterMap = new HashMap<>(); + + // 기존 데이터 매핑 + System.out.println("========== 기존 데이터 매핑 (beforeMap) =========="); + for(Map detail : existingDetails) { + String childObjid = CommonUtils.checkNull(detail.get("CHILD_OBJID")); + if(!"".equals(childObjid)) { + beforeMap.put(childObjid, detail); + System.out.println("Before - childObjid: " + childObjid + ", partNo: " + detail.get("PART_NO")); + } + } + + // 새 데이터 매핑 + System.out.println("========== 새 데이터 매핑 (afterMap) =========="); + if(mbomData != null) { + for(Map item : mbomData) { + String childObjid = CommonUtils.checkNull(item.get("childObjid")); + if(!"".equals(childObjid)) { + afterMap.put(childObjid, item); + System.out.println("After - childObjid: " + childObjid + ", partNo: " + item.get("partNo")); + } + } + } + + // 변경된 항목만 추출 + List> changedItems = new ArrayList<>(); + + System.out.println("========== 변경 사항 비교 =========="); + System.out.println("기존 항목 수 (beforeMap): " + beforeMap.size()); + System.out.println("새 항목 수 (afterMap): " + afterMap.size()); + + // 추가된 항목 + for(String childObjid : afterMap.keySet()) { + if(!beforeMap.containsKey(childObjid)) { + Map change = new HashMap<>(); + change.put("changeType", "ADD"); + change.put("childObjid", childObjid); + change.put("afterData", afterMap.get(childObjid)); + changedItems.add(change); + System.out.println("추가된 항목: " + childObjid + " - " + afterMap.get(childObjid).get("partNo")); + } + } + + // 삭제된 항목 + for(String childObjid : beforeMap.keySet()) { + if(!afterMap.containsKey(childObjid)) { + Map change = new HashMap<>(); + change.put("changeType", "DELETE"); + change.put("childObjid", childObjid); + change.put("beforeData", beforeMap.get(childObjid)); + changedItems.add(change); + System.out.println("삭제된 항목: " + childObjid + " - " + beforeMap.get(childObjid).get("PART_NO")); + } + } + + // 수정된 항목 + for(String childObjid : afterMap.keySet()) { + if(beforeMap.containsKey(childObjid)) { + Map before = beforeMap.get(childObjid); + Map after = afterMap.get(childObjid); + + // 필드별 비교 (실제 편집 가능한 필드만) + List> fieldChanges = new ArrayList<>(); + + // [DB 필드명, JSP 전송 필드명] 매핑 + // JSP getMbomTreeData()에서 전송하는 필드명 기준 + String[][] fieldMapping = { + {"PART_OBJID", "partObjid"}, // 파트 변경 감지 + {"PART_NO", "partNo"}, // 파트 변경 감지 + {"PART_NAME", "partName"}, // 파트 변경 감지 + {"SUPPLY_TYPE", "supplyType"}, + {"RAW_MATERIAL", "rawMaterial"}, + {"RAW_MATERIAL_SIZE", "rawMaterialSize"}, + {"RAW_MATERIAL_PART_NO", "rawMaterialPartNo"}, + {"REQUIRED_QTY", "requiredQty"}, + {"PRODUCTION_QTY", "productionQty"}, + {"PROCESSING_VENDOR", "processingVendor"}, + {"PROCESSING_DEADLINE", "processingDeadline"}, + {"GRINDING_DEADLINE", "grindingDeadline"} + }; + + for(String[] mapping : fieldMapping) { + String dbField = mapping[0]; + String jsField = mapping[1]; + + // before는 DB에서 대문자로, after는 JSP에서 camelCase로 + Object beforeObj = before.get(dbField); + Object afterObj = after.get(jsField); + + String beforeValue = (beforeObj == null) ? "" : String.valueOf(beforeObj); + String afterValue = (afterObj == null) ? "" : String.valueOf(afterObj); + + // 숫자 필드는 소수점 정규화 (1.0000 vs 1 비교 문제 해결) + if(dbField.contains("QTY") || dbField.equals("QTY")) { + try { + double beforeNum = Double.parseDouble(beforeValue); + double afterNum = Double.parseDouble(afterValue); + if(Math.abs(beforeNum - afterNum) < 0.0001) { + continue; // 실질적으로 같은 값 + } + } catch(Exception e) { + // 숫자 변환 실패 시 문자열 비교 + } + } + + if(!beforeValue.equals(afterValue)) { + Map fieldChange = new HashMap<>(); + fieldChange.put("field", dbField); + fieldChange.put("before", beforeValue); + fieldChange.put("after", afterValue); + fieldChanges.add(fieldChange); + + System.out.println("필드 변경 감지: " + dbField + " - Before: [" + beforeValue + "] After: [" + afterValue + "]"); + } + } + + if(!fieldChanges.isEmpty()) { + Map change = new HashMap<>(); + change.put("changeType", "MODIFY"); + change.put("childObjid", childObjid); + change.put("partNo", after.get("partNo")); + change.put("partName", after.get("partName")); + change.put("fieldChanges", fieldChanges); + changedItems.add(change); + + System.out.println("변경된 항목 추가: " + after.get("partNo") + " - " + fieldChanges.size() + "개 필드 변경"); + } + } + } + + // M-BOM 업데이트 + paramMap.put("mbomHeaderObjid", existingMbom.get("OBJID")); + sqlSession.update("productionplanning.updateMbomHeader", paramMap); + sqlSession.delete("productionplanning.deleteMbomDetail", paramMap); + + // 새 상세 데이터 삽입 + if(mbomData != null && !mbomData.isEmpty()) { + for(Map item : mbomData) { + item.put("mbomHeaderObjid", existingMbom.get("OBJID")); + item.put("sessionUserId", userId); + + // SEQ가 없으면 기존 SEQ 유지 (JSP에서 전송된 seq 사용) + // 없는 경우에만 새로 부여 + if(item.get("seq") == null || "".equals(item.get("seq"))) { + item.put("seq", 999); // 임시값 (나중에 정렬 필요) + } + + // 기존 항목인 경우 objid와 childObjid 유지 + // 새 항목인 경우에만 생성 + String objid = CommonUtils.checkNull(item.get("objid")); + String childObjid = CommonUtils.checkNull(item.get("childObjid")); + + if("".equals(objid)) { + objid = CommonUtils.createObjId(); + item.put("objid", objid); + } + + if("".equals(childObjid)) { + childObjid = objid; // 새 항목은 objid와 동일 + item.put("childObjid", childObjid); + } + + // LEVEL이 없으면 기본값 1 설정 + if(item.get("level") == null || "".equals(item.get("level"))) { + item.put("level", 1); + } + + System.out.println("UPDATE M-BOM DETAIL: seq=" + item.get("seq") + + ", level=" + item.get("level") + + ", partNo=" + item.get("partNo") + + ", parentObjid=" + item.get("parentObjid") + + ", childObjid=" + childObjid); + + sqlSession.insert("productionplanning.insertMbomDetail", item); + } + } + + // 변경 사항이 있을 때만 이력 저장 + if(!changedItems.isEmpty()) { + String historyObjid = CommonUtils.createObjId(); + Map historyParam = new HashMap<>(); + historyParam.put("objid", historyObjid); + historyParam.put("mbomHeaderObjid", existingMbom.get("OBJID")); + historyParam.put("changeType", "UPDATE"); + historyParam.put("changeDescription", changedItems.size() + "개 항목 변경"); + historyParam.put("beforeData", new com.google.gson.Gson().toJson(changedItems)); + historyParam.put("afterData", "{}"); + historyParam.put("sessionUserId", userId); + sqlSession.insert("productionplanning.insertMbomHistory", historyParam); + } + + result = true; + } + } else { + // 최초 저장: 새 M-BOM 생성 + String newObjid = CommonUtils.createObjId(); + paramMap.put("objid", newObjid); + paramMap.put("mbomNo", mbomNo); + + // sourceBomType에 따라 sourceEbomObjid 또는 sourceMbomObjid 설정 + String sourceBomType = (String) paramMap.get("sourceBomType"); + String sourceBomObjId = (String) paramMap.get("sourceBomObjId"); + + if("EBOM".equals(sourceBomType)) { + paramMap.put("sourceEbomObjid", sourceBomObjId); + paramMap.put("sourceMbomObjid", null); + } else if("MBOM".equals(sourceBomType)) { + paramMap.put("sourceEbomObjid", null); + paramMap.put("sourceMbomObjid", sourceBomObjId); + } + + // MBOM_HEADER 삽입 + sqlSession.insert("productionplanning.insertMbomHeader", paramMap); + + // MBOM_DETAIL 삽입 + List> mbomData = (List>) paramMap.get("mbomData"); + if(mbomData != null && !mbomData.isEmpty()) { + // CHILD_OBJID 매핑 (기존 E-BOM의 CHILD_OBJID -> 새 M-BOM의 CHILD_OBJID) + Map childObjidMap = new HashMap<>(); + + // 1단계: 모든 항목에 대해 새 CHILD_OBJID 생성 + for(Map item : mbomData) { + String oldChildObjid = CommonUtils.checkNull(item.get("childObjid")); + if(!"".equals(oldChildObjid) && !childObjidMap.containsKey(oldChildObjid)) { + String newChildObjid = CommonUtils.createObjId(); + childObjidMap.put(oldChildObjid, newChildObjid); + } + } + + // 2단계: PARENT_OBJID와 CHILD_OBJID를 새 ID로 매핑하여 삽입 + for(Map item : mbomData) { + String oldChildObjid = CommonUtils.checkNull(item.get("childObjid")); + String oldParentObjid = CommonUtils.checkNull(item.get("parentObjid")); + + // 새 CHILD_OBJID 설정 + String newChildObjid = childObjidMap.get(oldChildObjid); + if(newChildObjid == null) { + newChildObjid = CommonUtils.createObjId(); + } + + // 새 PARENT_OBJID 설정 (부모가 있는 경우에만) + String newParentObjid = ""; + if(!"".equals(oldParentObjid)) { + newParentObjid = childObjidMap.get(oldParentObjid); + if(newParentObjid == null) { + newParentObjid = oldParentObjid; // 매핑 실패 시 원본 유지 + } + } + + item.put("mbomHeaderObjid", newObjid); + item.put("objid", newChildObjid); // OBJID = CHILD_OBJID + item.put("childObjid", newChildObjid); + item.put("parentObjid", newParentObjid); + item.put("sessionUserId", userId); + + // SEQ 유지 (JSP에서 전송된 seq 사용) + if(item.get("seq") == null || "".equals(item.get("seq"))) { + item.put("seq", 999); // 임시값 + } + + // LEVEL이 없으면 기본값 1 설정 + if(item.get("level") == null || "".equals(item.get("level"))) { + item.put("level", 1); + } + + System.out.println("INSERT M-BOM DETAIL: seq=" + item.get("seq") + + ", level=" + item.get("level") + + ", partNo=" + item.get("partNo") + + ", parentObjid=" + newParentObjid + + ", childObjid=" + newChildObjid); + + sqlSession.insert("productionplanning.insertMbomDetail", item); + } + } + + // 이력 저장 (생성) + String historyObjid = CommonUtils.createObjId(); + paramMap.put("objid", historyObjid); // MBOM_HISTORY의 OBJID + paramMap.put("mbomHeaderObjid", newObjid); + paramMap.put("changeType", "CREATE"); + paramMap.put("afterData", new com.google.gson.Gson().toJson(paramMap)); + sqlSession.insert("productionplanning.insertMbomHistory", paramMap); + + // PROJECT_MGMT 업데이트 (MBOM_STATUS = 'Y') + sqlSession.update("productionplanning.updateProjectMbomStatus", paramMap); + + result = true; + } + + sqlSession.commit(); + + } catch(Exception e) { + if(sqlSession != null) { + sqlSession.rollback(); + } + e.printStackTrace(); + } finally { + if(sqlSession != null) { + sqlSession.close(); + } + } + + return result; + } }