diff --git a/WebContent/WEB-INF/view/partMng/structurePopupCenter.jsp b/WebContent/WEB-INF/view/partMng/structurePopupCenter.jsp
index 4c5934f..3ec405d 100644
--- a/WebContent/WEB-INF/view/partMng/structurePopupCenter.jsp
+++ b/WebContent/WEB-INF/view/partMng/structurePopupCenter.jsp
@@ -19,7 +19,6 @@ function showConfirm(options) {
var message = '';
if(options.title) message += options.title + '\n\n';
if(options.html) {
- // HTML 태그 제거
message += options.html.replace(/
/gi, '\n').replace(/<[^>]+>/g, '');
} else if(options.text) {
message += options.text;
@@ -34,13 +33,91 @@ function showConfirm(options) {
}
}
-$(function(){
+// 임시 ID 생성
+function generateTempId() {
+ return -Math.floor(Math.random() * 1000000000);
+}
+
+// 반제품 E-BOM 하위 구조 조회 (동기)
+function fetchEbomSubTree(partNo) {
+ var result = null;
+ $.ajax({
+ url: '/partMng/getEbomSubTreeByPartNo.do',
+ method: 'POST',
+ data: { partNo: partNo },
+ dataType: 'json',
+ async: false,
+ success: function(data) { result = data; },
+ error: function(xhr, status, error) { console.error("E-BOM 하위 구조 조회 오류:", error); }
+ });
+ return result;
+}
+
+// 반제품 E-BOM 하위 구조를 flat list로 변환
+function buildSubTreeFlatList(subTree, parentObjid, baseLevel) {
+ var flatList = [];
+ var originalChildToNew = {};
+ for(var i = 0; i < subTree.length; i++) {
+ var item = subTree[i];
+ var itemLevel = parseInt(item.LEVEL || item.level || 1);
+ var newChildObjid = generateTempId();
+ var originalChildObjid = item.CHILD_OBJID || item.child_objid || '';
+ if(originalChildObjid) originalChildToNew[originalChildObjid] = newChildObjid;
+
+ flatList.push({
+ OBJID: generateTempId(),
+ CHILD_OBJID: newChildObjid,
+ PARENT_OBJID: parentObjid,
+ LEVEL: baseLevel + itemLevel,
+ PART_OBJID: item.PART_OBJID || item.part_objid || '',
+ PART_NO: item.PART_NO || item.part_no || '',
+ PART_NAME: item.PART_NAME || item.part_name || '',
+ QTY: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ ITEM_QTY: parseInt(item.ITEM_QTY || item.item_qty || item.QTY || item.qty || 1),
+ QTY_TEMP: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ SPEC: item.SPEC || item.spec || '',
+ REVISION: item.REVISION || item.revision || '',
+ STATUS: 'ACTIVE',
+ _IS_SUB_PART: true
+ });
+ }
+
+ // 2레벨 이상 항목의 부모 OBJID 재매핑
+ for(var i = 0; i < subTree.length; i++) {
+ var originalParent = subTree[i].PARENT_OBJID || subTree[i].parent_objid || '';
+ if(originalParent && originalChildToNew[originalParent]) {
+ flatList[i].PARENT_OBJID = originalChildToNew[originalParent];
+ }
+ }
+ return flatList;
+}
+
+// 그리드 데이터 배열의 삽입 위치 계산
+function findInsertPosition(allData, parentObjid, parentLevel) {
+ var insertPosition = -1;
+ for(var i = 0; i < allData.length; i++) {
+ if(allData[i].CHILD_OBJID == parentObjid) {
+ insertPosition = i + 1;
+ for(var j = i + 1; j < allData.length; j++) {
+ var nextLevel = parseInt(allData[j].LEVEL || allData[j].level || 1);
+ if(nextLevel > parentLevel) {
+ insertPosition = j + 1;
+ } else {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return insertPosition;
+}
+
+$(function(){
$('.select2').select2();
- //Part 연결
+ // E-BOM 파트 추가 (클라이언트 편집)
$("#moveLeft").click(function(){
- // Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
@@ -49,377 +126,189 @@ $(function(){
return false;
}
- // 왼쪽 프레임에서 선택된 행 데이터 가져오기
- var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
+ var leftFrame = parent.frames['leftFrame'];
+ var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
- var leftPartChildObjId = null;
+ var parentObjid = "";
+ var parentLevel = 0;
var leftPartNo = null;
- var leftPartNoQty = null;
- var leftParentObjId = null;
- var leftPartLastObjId = null;
- var leftQtyParObjId = null;
- var leftParentParts = "";
if(leftPartNoObj.length > 0) {
- leftPartChildObjId = leftPartNoObj.val();
+ parentObjid = leftPartNoObj.val();
+ parentLevel = parseInt(leftPartNoObj.attr("data-LEVEL") || 0);
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;
+ // 같은 Part 연결 방지
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
- var rightPartNo = rowData.PART_NO;
- if(rightPartNo == leftPartNo){
+ if(rowData.PART_NO == leftPartNo){
showConfirm({
title: '연결 불가',
- html: '오류 Part No : ['+rightPartNo+']
같은 Part No끼리 연결할 수 없습니다.',
+ html: '오류 Part No : ['+rowData.PART_NO+']
같은 Part No끼리 연결할 수 없습니다.',
icon: 'error'
});
- isSamePart = true;
+ return false;
+ }
+ }
+
+ // 추가할 파트 데이터 준비 (반제품 하위 품목 포함)
+ var newParts = [];
+ var subPartCount = 0;
+
+ for(var i = 0; i < rightSelectedRows.length; i++){
+ var rowData = rightSelectedRows[i].getData();
+ var partChildObjid = generateTempId();
+
+ newParts.push({
+ OBJID: generateTempId(),
+ CHILD_OBJID: partChildObjid,
+ PARENT_OBJID: parentObjid,
+ LEVEL: parentLevel + 1,
+ PART_OBJID: rowData.OBJID,
+ PART_NO: rowData.PART_NO,
+ PART_NAME: rowData.PART_NAME,
+ QTY: 1,
+ ITEM_QTY: 1,
+ QTY_TEMP: 1,
+ SPEC: rowData.SPEC || '',
+ REVISION: rowData.REVISION || '',
+ STATUS: 'ACTIVE'
+ });
+
+ // 반제품 E-BOM 하위 구조 조회
+ var ebomResult = fetchEbomSubTree(rowData.PART_NO);
+ if(ebomResult && ebomResult.hasEbom && ebomResult.subTree && ebomResult.subTree.length > 0) {
+ var subParts = buildSubTreeFlatList(ebomResult.subTree, partChildObjid, parentLevel + 1);
+ for(var j = 0; j < subParts.length; j++) {
+ newParts.push(subParts[j]);
+ }
+ subPartCount += subParts.length;
+ }
+ }
+
+ // 왼쪽 그리드에 추가
+ if(leftFrame && leftFrame._tabulGrid) {
+ if(parentObjid) {
+ var allData = leftFrame._tabulGrid.getData();
+ var insertPosition = findInsertPosition(allData, parentObjid, parentLevel);
+ if(insertPosition >= 0) {
+ for(var k = newParts.length - 1; k >= 0; k--) {
+ allData.splice(insertPosition, 0, newParts[k]);
+ }
+ leftFrame._tabulGrid.setData(allData);
+ } else {
+ for(var k = 0; k < newParts.length; k++) {
+ leftFrame._tabulGrid.addData([newParts[k]], false);
+ }
+ }
+ } else {
+ for(var k = 0; k < newParts.length; k++) {
+ leftFrame._tabulGrid.addData([newParts[k]], false);
+ }
+ }
+
+ var message = rightSelectedRows.length + '개 파트가 추가되었습니다.';
+ if(subPartCount > 0) message += '\n(반제품 하위 ' + subPartCount + '개 품목 포함)';
+ message += '\n저장 버튼을 눌러 확정하세요.';
+ showConfirm({ title: '파트 추가', text: message, icon: 'success' });
+ }
+ });
+
+ // E-BOM 파트 삭제 (클라이언트 편집, 하위 품목 함께 삭제)
+ $("#moveRight").click(function(){
+ var leftFrame = parent.frames['leftFrame'];
+ var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
+
+ if(leftPartNoObj.length === 0) {
+ showConfirm("삭제할 파트를 선택해주세요.");
+ return;
+ }
+
+ var childObjid = leftPartNoObj.val();
+ var selectedLevel = parseInt(leftPartNoObj.attr("data-LEVEL") || 1);
+
+ var allData = leftFrame._tabulGrid.getData();
+ var deleteCount = 0;
+ var foundIndex = -1;
+ for(var i = 0; i < allData.length; i++) {
+ if(allData[i].CHILD_OBJID == childObjid) {
+ foundIndex = i;
+ deleteCount = 1;
+ for(var j = i + 1; j < allData.length; j++) {
+ var nextLevel = parseInt(allData[j].LEVEL || allData[j].level || 1);
+ if(nextLevel > selectedLevel) {
+ deleteCount++;
+ } else {
+ break;
+ }
+ }
break;
}
}
- if(isSamePart) return false;
+ var confirmText = '선택한 Part를 삭제하시겠습니까?';
+ if(deleteCount > 1) confirmText += '\n(하위 ' + (deleteCount - 1) + '개 품목도 함께 삭제됩니다)';
+ confirmText += '\n\n저장 버튼을 눌러야 확정됩니다.';
- // 연결하려는 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
+ title: 'Part 삭제', text: confirmText, icon: 'warning',
+ showCancelButton: true, confirmButtonText: '삭제', cancelButtonText: '취소'
}).then(result => {
- if (result.isConfirmed) {
- fn_relatePartInfo(leftPartChildObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
+ if(result.isConfirmed && foundIndex >= 0) {
+ allData.splice(foundIndex, deleteCount);
+ leftFrame._tabulGrid.setData(allData);
+
+ var message = '파트가 삭제되었습니다.';
+ if(deleteCount > 1) message += '\n(하위 ' + (deleteCount - 1) + '개 품목 포함)';
+ message += '\n저장 버튼을 눌러 확정하세요.';
+ showConfirm({ title: '파트 삭제', text: message, icon: 'success' });
}
});
});
- //end of Part 연결
- //연결된 part 삭제
- $("#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);
- });
- //end of 연결된 part 삭제
-
- //연결된 part 변경
+ // E-BOM 파트 변경 (클라이언트 편집)
$("#moveChange").click(function(){
- var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
+ var leftFrame = parent.frames['leftFrame'];
+ var leftPartNoList = $("input[name=checkedPartNo]:checked", leftFrame.document);
- if(leftPartNoList.length === 0){
- showConfirm("선택된 파트가 없습니다.");
- return false;
- }
-
- if(leftPartNoList.length > 1){
- showConfirm("한번에 1개의 파트만 변경가능합니다.");
- return false;
- }
+ 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");
+ var leftChildObjid = leftPartNoList.val();
- // 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;
- }
+ 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
+ html: '선택한 Part를 변경하시겠습니까?
기존: ' + leftPartNo + '
변경: ' + rightRowData.PART_NO,
+ icon: 'warning', showCancelButton: true, confirmButtonText: '변경', cancelButtonText: '취소'
}).then(result => {
- if (result.isConfirmed) {
- fn_changeRelatePartInfo(leftPartBomQtyObjId, rightObjId, leftPartChildObjId, leftParentPartObjid, leftPartChildObjId, leftPartObjid, rightPartNo, rightPartRev);
+ if(result.isConfirmed && leftFrame && leftFrame._tabulGrid) {
+ var rows = leftFrame._tabulGrid.getRows();
+ for(var i = 0; i < rows.length; i++) {
+ if(rows[i].getData().CHILD_OBJID == leftChildObjid) {
+ rows[i].update({
+ PART_OBJID: rightRowData.OBJID,
+ PART_NO: rightRowData.PART_NO,
+ PART_NAME: rightRowData.PART_NAME,
+ REVISION: rightRowData.REVISION || ''
+ });
+ showConfirm({ title: '파트 변경', text: '파트가 변경되었습니다.\n저장 버튼을 눌러 확정하세요.', icon: 'success' });
+ break;
+ }
+ }
}
});
});
-
});
-
-//1레벨에 같은 Part No가 등록되어있는지 확인.
-function fn_checkSameTopPartNo(rightCheckedArr){
- var result = false;
-
- $.ajax({
- url: "/partMng/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: "/partMng/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: "/partMng/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: "/partMng/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); */
- }
- });
-}
diff --git a/WebContent/WEB-INF/view/partMng/structurePopupLeft.jsp b/WebContent/WEB-INF/view/partMng/structurePopupLeft.jsp
index b8ceb9e..f9ad771 100644
--- a/WebContent/WEB-INF/view/partMng/structurePopupLeft.jsp
+++ b/WebContent/WEB-INF/view/partMng/structurePopupLeft.jsp
@@ -105,7 +105,8 @@ function fn_initGrid() {
'data-LAST_PART_OBJID="' + rowData.LAST_PART_OBJID + '" ' +
'data-PARENT_OBJID="' + rowData.PARENT_OBJID + '" ' +
'data-PART_OBJID="' + rowData.PART_OBJID + '" ' +
- 'data-BOM_LAST_PART_OBJID="' + rowData.BOM_LAST_PART_OBJID + '">';
+ 'data-BOM_LAST_PART_OBJID="' + rowData.BOM_LAST_PART_OBJID + '" ' +
+ 'data-LEVEL="' + (rowData.LEVEL || rowData.level || 1) + '">';
},
}
];
@@ -297,40 +298,118 @@ function fn_initGrid() {
}
);
+ // 초기 데이터 로드 (클라이언트 편집 모드 지원)
+ var initialData = [];
+ $.ajax({
+ url: "/partMng/getStructureTreeJson.do",
+ method: 'GET',
+ data: { objId: "${info.OBJID}", bomReportObjId: "${info.OBJID}", search_type: "working" },
+ dataType: 'json',
+ async: false,
+ success: function(response) {
+ if(response && response.length > 0) {
+ response.forEach(function(item) {
+ var maxLev = item.MAX_LEVEL || 1;
+ for(var i = 1; i <= maxLev; i++) {
+ item['LEVEL_' + i] = (item.LEVEL == i) ? '*' : '';
+ }
+ initialData.push(item);
+ });
+ }
+ }
+ });
+
_tabulGrid = new Tabulator("#structureGrid", {
layout: "fitColumns",
height: "100%",
pagination: false,
- headerSort: false, // 정렬 비활성화
+ headerSort: false,
+ movableRows: true,
columns: columns,
+ data: initialData,
rowFormatter: function(row) {
var data = row.getData();
$(row.getElement()).addClass('level-' + data.LEVEL);
- },
- ajaxURL: "/partMng/getStructureTreeJson.do",
- ajaxParams: {
- objId: "${info.OBJID}",
- bomReportObjId: "${info.OBJID}",
- search_type: "working" // adding 상태 포함해서 조회
- },
- ajaxResponse: function(url, params, response) {
- // 서버 응답 데이터 가공
- var processedData = [];
- if(response && response.length > 0) {
- var maxLevel = response[0].MAX_LEVEL || 1;
-
- response.forEach(function(item) {
- // Level 표시를 위한 동적 필드 생성
- for(var i = 1; i <= maxLevel; i++) {
- item['LEVEL_' + i] = (item.LEVEL == i) ? '*' : '';
- }
- processedData.push(item);
- });
- }
- return processedData;
}
});
+ // 드래그 이동 시 같은 부모 내에서만 허용, 하위 품목도 함께 이동
+ var _lastValidData = initialData.slice();
+ _tabulGrid.on("rowMoved", function(row) {
+ var movedData = row.getData();
+ var movedLevel = parseInt(movedData.LEVEL || 1);
+ var movedParent = movedData.PARENT_OBJID || '';
+ var movedChildObjid = movedData.CHILD_OBJID;
+
+ var currentData = _tabulGrid.getData();
+ var movedIndex = -1;
+ for(var i = 0; i < currentData.length; i++) {
+ if(currentData[i].CHILD_OBJID == movedChildObjid) { movedIndex = i; break; }
+ }
+ if(movedIndex < 0) return;
+
+ var isValidMove = false;
+ if(movedIndex > 0) {
+ var prevData = currentData[movedIndex - 1];
+ var prevLevel = parseInt(prevData.LEVEL || 1);
+ if((prevLevel == movedLevel && prevData.PARENT_OBJID == movedParent) ||
+ (prevLevel == movedLevel - 1 && prevData.CHILD_OBJID == movedParent)) {
+ isValidMove = true;
+ }
+ } else {
+ if(!movedParent || movedParent == '') isValidMove = true;
+ }
+
+ if(!isValidMove) {
+ alert("같은 부모 내에서만 순서를 변경할 수 있습니다.");
+ _tabulGrid.setData(_lastValidData);
+ return;
+ }
+
+ // 이전 데이터에서 이동된 행의 하위 품목 추출
+ var subTree = [];
+ var oldData = _lastValidData;
+ var oldIndex = -1;
+ for(var i = 0; i < oldData.length; i++) {
+ if(oldData[i].CHILD_OBJID == movedChildObjid) { oldIndex = i; break; }
+ }
+ if(oldIndex >= 0) {
+ for(var j = oldIndex + 1; j < oldData.length; j++) {
+ if(parseInt(oldData[j].LEVEL || 1) > movedLevel) {
+ subTree.push(oldData[j]);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // 하위 품목이 있으면 현재 데이터에서 제거 후 부모 뒤에 삽입
+ if(subTree.length > 0) {
+ var subChildObjids = {};
+ for(var k = 0; k < subTree.length; k++) {
+ subChildObjids[subTree[k].CHILD_OBJID] = true;
+ }
+ var filtered = [];
+ for(var i = 0; i < currentData.length; i++) {
+ if(!subChildObjids[currentData[i].CHILD_OBJID]) {
+ filtered.push(currentData[i]);
+ }
+ }
+ var newParentIndex = -1;
+ for(var i = 0; i < filtered.length; i++) {
+ if(filtered[i].CHILD_OBJID == movedChildObjid) { newParentIndex = i; break; }
+ }
+ if(newParentIndex >= 0) {
+ for(var k = subTree.length - 1; k >= 0; k--) {
+ filtered.splice(newParentIndex + 1, 0, subTree[k]);
+ }
+ }
+ _tabulGrid.setData(filtered);
+ }
+
+ _lastValidData = _tabulGrid.getData();
+ });
+
// 행 클릭 시 선택 처리 이벤트
_tabulGrid.on("rowClick", function(e, row) {
// 링크 클릭 시에는 기본 동작 유지 (라디오 버튼은 하이라이트 처리)
@@ -434,6 +513,30 @@ function getSelectedRowData() {
return selectedRowData;
}
+// E-BOM 트리 데이터 수집 (저장용) - SEQ는 현재 화면 순서 기준
+function getEbomTreeData() {
+ var allData = _tabulGrid.getData();
+ return allData.map(function(row, index) {
+ return {
+ bomReportObjId: "${info.OBJID}",
+ objid: row.OBJID || '',
+ parentObjid: row.PARENT_OBJID || '',
+ childObjid: row.CHILD_OBJID || '',
+ parentPartNo: row.PARENT_PART_NO || '',
+ partNo: row.PART_OBJID || row.PART_NO || '',
+ lastPartObjid: row.LAST_PART_OBJID || row.BOM_LAST_PART_OBJID || '',
+ qty: row.QTY || '1',
+ itemQty: row.ITEM_QTY || '1',
+ qtyTemp: row.QTY_TEMP || row.QTY || '1',
+ seq: index + 1,
+ level: row.LEVEL || 1,
+ status: row.STATUS || 'ACTIVE',
+ partNoDisplay: row.PART_NO || '',
+ partName: row.PART_NAME || ''
+ };
+ });
+}
+
// 필터 적용 함수
function fn_applyFilter() {
var partNo = $("#filterPartNo").val().trim();
diff --git a/WebContent/WEB-INF/view/partMng/structurePopupTop.jsp b/WebContent/WEB-INF/view/partMng/structurePopupTop.jsp
index 492993d..b6e316d 100644
--- a/WebContent/WEB-INF/view/partMng/structurePopupTop.jsp
+++ b/WebContent/WEB-INF/view/partMng/structurePopupTop.jsp
@@ -83,9 +83,7 @@ function fn_resetFilter() {
$("#filterPartName").val("");
try {
- // 프레임 구조: parent(headerFs) -> parent(main) -> frames[1](bottomFs) -> frames[0](leftFrame)
var leftFrame = parent.parent.frames[1].frames['leftFrame'];
-
if(leftFrame && leftFrame.fn_resetFilter) {
leftFrame.fn_resetFilter();
}
@@ -93,6 +91,71 @@ function fn_resetFilter() {
console.error("Error accessing leftFrame:", e);
}
}
+
+// E-BOM 저장
+function fn_saveEbom() {
+ try {
+ var leftFrame = parent.parent.frames[1].frames['leftFrame'];
+ if(!leftFrame || !leftFrame.getEbomTreeData) {
+ alert("E-BOM 데이터를 가져올 수 없습니다.");
+ return;
+ }
+
+ var ebomData = leftFrame.getEbomTreeData();
+ if(!ebomData || ebomData.length === 0) {
+ alert("저장할 E-BOM 데이터가 없습니다.");
+ return;
+ }
+
+ if(!confirm("E-BOM을 저장하시겠습니까?")) return;
+
+ $.ajax({
+ url: "/partMng/saveEbom.do",
+ method: 'POST',
+ data: JSON.stringify({
+ bomReportObjId: ebomData[0].bomReportObjId,
+ ebomData: ebomData
+ }),
+ contentType: 'application/json',
+ dataType: 'json',
+ success: function(data) {
+ if(data && data.result === "success") {
+ alert("E-BOM이 저장되었습니다.");
+ // 왼쪽 프레임 데이터 리로드
+ leftFrame.location.reload();
+ // 부모 창 새로고침
+ if(window.opener && window.opener.fn_search) {
+ window.opener.fn_search();
+ }
+ } else {
+ alert("E-BOM 저장에 실패했습니다: " + (data.message || ""));
+ }
+ },
+ error: function(xhr, status, error) {
+ console.error("E-BOM 저장 오류:", error);
+ alert("E-BOM 저장 중 오류가 발생했습니다.");
+ }
+ });
+ } catch(e) {
+ console.error("E-BOM 저장 오류:", e);
+ alert("E-BOM 저장 중 오류가 발생했습니다.");
+ }
+}
+
+// 창 닫기
+function fn_closeEbom() {
+ if(confirm("저장하지 않은 변경사항은 사라집니다. 닫으시겠습니까?")) {
+ try {
+ if(window.top && window.top.opener) {
+ window.top.close();
+ } else {
+ window.top.close();
+ }
+ } catch(e) {
+ window.top.close();
+ }
+ }
+}
@@ -125,10 +188,12 @@ function fn_resetFilter() {
-
-
-
- |
+
+
+
+
+
+ |
diff --git a/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp b/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp
index dbc2dcd..eeab9c0 100644
--- a/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp
+++ b/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp
@@ -191,6 +191,11 @@ $(function(){
fn_closeWindow();
});
+ // 최상위 제품 변경 버튼 클릭
+ $("#btnChangeTopProduct").click(function(){
+ fn_changeTopProduct();
+ });
+
/* 주석처리: 가공납기/연삭납기 일괄 적용 버튼 이벤트
// 일괄 적용 버튼 클릭
$("#btnApplyBulkDeadline").click(function(){
@@ -394,13 +399,23 @@ function fn_saveMbom() {
}
});
+ // M-BOM 품번: 1) 최상위 제품 변경 시 수동 설정된 값 2) 기존 M-BOM 품번 3) 빈값(신규 자동 생성)
+ var manualMbomPartNo = $("#search_mbom_part_no").val().trim();
+ var mbomPartNo = "";
+ if(existingMbom) {
+ mbomPartNo = existingMbom.MBOM_NO;
+ }
+ if(manualMbomPartNo && manualMbomPartNo !== existingMbom?.MBOM_NO) {
+ mbomPartNo = manualMbomPartNo;
+ }
+
var saveData = {
- projectObjId: projectObjId, // PROJECT_MGMT의 OBJID
+ projectObjId: projectObjId,
mbomData: mbomData,
partNo: $("#search_part_no").val().trim(),
partName: $("#search_part_name").val().trim(),
- mbomPartNo: existingMbom ? existingMbom.MBOM_NO : "", // 기존 M-BOM 품번 사용
- isUpdate: existingMbom !== null // 기존 M-BOM이 있으면 업데이트
+ mbomPartNo: mbomPartNo,
+ isUpdate: existingMbom !== null
};
console.log("저장할 데이터:", saveData);
@@ -962,6 +977,7 @@ function compareItemFields(before, after) {
|
+
@@ -990,7 +1006,96 @@ function compareItemFields(before, after) {
// ==================== M-BOM 전용 파트 추가/삭제/변경 함수 ====================
-// M-BOM 파트 추가 (그리드에만 반영)
+// 반제품 E-BOM 하위 구조를 flat list로 변환 (재귀)
+function buildSubTreeFlatList(subTree, parentObjid, baseLevel) {
+ var flatList = [];
+ for(var i = 0; i < subTree.length; i++) {
+ var item = subTree[i];
+ var itemLevel = parseInt(item.LEVEL || item.level || 1);
+ var newChildObjid = generateTempId();
+
+ flatList.push({
+ OBJID: generateTempId(),
+ CHILD_OBJID: newChildObjid,
+ PARENT_OBJID: parentObjid,
+ LEVEL: baseLevel + itemLevel,
+ PART_OBJID: item.PART_OBJID || item.part_objid || '',
+ PART_NO: item.PART_NO || item.part_no || '',
+ PART_NAME: item.PART_NAME || item.part_name || '',
+ QTY: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ ITEM_QTY: parseInt(item.ITEM_QTY || item.item_qty || item.QTY || item.qty || 1),
+ QTY_TEMP: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ UNIT: item.UNIT_TITLE || item.unit_title || '',
+ SPEC: item.SPEC || item.spec || '',
+ REVISION: item.REVISION || item.revision || '',
+ SUPPLY_TYPE: '사급',
+ STATUS: 'ACTIVE',
+ _IS_SUB_PART: true
+ });
+ }
+
+ // E-BOM 트리의 PARENT_OBJID 재매핑 (CHILD_OBJID 기준)
+ // 원본 E-BOM의 parent-child 관계를 새 ID로 매핑
+ var originalChildToNew = {};
+ for(var i = 0; i < subTree.length; i++) {
+ var originalChildObjid = subTree[i].CHILD_OBJID || subTree[i].child_objid || '';
+ if(originalChildObjid) {
+ originalChildToNew[originalChildObjid] = flatList[i].CHILD_OBJID;
+ }
+ }
+
+ // 2레벨 이상의 항목은 부모 OBJID를 재매핑
+ for(var i = 0; i < subTree.length; i++) {
+ var originalParent = subTree[i].PARENT_OBJID || subTree[i].parent_objid || '';
+ if(originalParent && originalChildToNew[originalParent]) {
+ flatList[i].PARENT_OBJID = originalChildToNew[originalParent];
+ }
+ // 1레벨 항목은 이미 parentObjid로 설정됨
+ }
+
+ return flatList;
+}
+
+// 반제품 E-BOM 하위 구조 조회 (동기)
+function fetchEbomSubTree(partNo) {
+ var result = null;
+ $.ajax({
+ url: '/partMng/getEbomSubTreeByPartNo.do',
+ method: 'POST',
+ data: { partNo: partNo },
+ dataType: 'json',
+ async: false,
+ success: function(data) {
+ result = data;
+ },
+ error: function(xhr, status, error) {
+ console.error("E-BOM 하위 구조 조회 오류:", error);
+ }
+ });
+ return result;
+}
+
+// 그리드 데이터 배열의 삽입 위치 계산 (부모의 마지막 하위 항목 다음)
+function findInsertPosition(allData, parentObjid, parentLevel) {
+ var insertPosition = -1;
+ for(var i = 0; i < allData.length; i++) {
+ if(allData[i].CHILD_OBJID == parentObjid) {
+ insertPosition = i + 1;
+ for(var j = i + 1; j < allData.length; j++) {
+ var rowLevel = allData[j].LEVEL || 1;
+ if(rowLevel > parentLevel) {
+ insertPosition = j + 1;
+ } else {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return insertPosition;
+}
+
+// M-BOM 파트 추가 (그리드에만 반영, 반제품은 하위 품목 함께 추가)
function fn_mbomAddPart() {
var rightFrame = parent.frames[1].frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
@@ -1009,23 +1114,19 @@ function fn_mbomAddPart() {
if(leftPartNoObj.length > 0) {
parentObjid = leftPartNoObj.attr("data-CHILD_OBJID");
parentLevel = parseInt(leftPartNoObj.attr("data-LEVEL") || 0);
- console.log("부모 선택됨:", {
- parentObjid: parentObjid,
- parentLevel: parentLevel,
- partNo: leftPartNoObj.attr("data-PART_NO")
- });
- } else {
- console.log("부모 선택 안됨 - 최상위 레벨에 추가");
}
- // 추가할 파트 데이터 준비
+ // 선택된 각 파트에 대해 반제품 여부 확인 후 추가할 데이터 준비
var newParts = [];
+ var subPartCount = 0;
+
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
+ var partChildObjid = generateTempId();
var newPart = {
OBJID: generateTempId(),
- CHILD_OBJID: generateTempId(),
+ CHILD_OBJID: partChildObjid,
PARENT_OBJID: parentObjid,
LEVEL: parentLevel + 1,
PART_OBJID: rowData.OBJID,
@@ -1040,80 +1141,59 @@ function fn_mbomAddPart() {
SUPPLY_TYPE: '사급',
STATUS: 'ACTIVE'
};
-
- console.log("추가할 파트:", {
- partNo: newPart.PART_NO,
- parentObjid: newPart.PARENT_OBJID,
- level: newPart.LEVEL
- });
-
newParts.push(newPart);
+
+ // 반제품 E-BOM 하위 구조 조회
+ console.log("파트 추가 - PART_NO:", rowData.PART_NO, "OBJID:", rowData.OBJID, "PART_TYPE:", rowData.PART_TYPE, "PART_TYPE_TITLE:", rowData.PART_TYPE_TITLE);
+ var ebomResult = fetchEbomSubTree(rowData.PART_NO);
+ console.log("E-BOM 조회 결과:", JSON.stringify(ebomResult));
+ if(ebomResult && ebomResult.hasEbom && ebomResult.subTree && ebomResult.subTree.length > 0) {
+ var subParts = buildSubTreeFlatList(ebomResult.subTree, partChildObjid, parentLevel + 1);
+ for(var j = 0; j < subParts.length; j++) {
+ newParts.push(subParts[j]);
+ }
+ subPartCount += subParts.length;
+ console.log("반제품 [" + rowData.PART_NO + "] 하위 " + subParts.length + "개 품목 함께 추가");
+ }
}
// 왼쪽 프레임의 Tabulator에 추가
if(leftFrame && leftFrame._tabulGrid) {
if(parentObjid) {
- // 부모가 선택된 경우: 데이터 배열에 직접 삽입
var allData = leftFrame._tabulGrid.getData();
- var insertPosition = -1;
+ var insertPosition = findInsertPosition(allData, parentObjid, parentLevel);
- // 부모 행 찾기
- for(var i = 0; i < allData.length; i++) {
- if(allData[i].CHILD_OBJID == parentObjid) {
- insertPosition = i + 1;
-
- // 부모의 모든 하위 항목(자식, 손자, 증손자 등)을 건너뛰기
- for(var j = i + 1; j < allData.length; j++) {
- var rowLevel = allData[j].LEVEL || 1;
-
- if(rowLevel > parentLevel) {
- // 부모보다 하위 레벨이면 계속 건너뛰기
- insertPosition = j + 1;
- } else {
- // 같은 레벨이거나 상위 레벨이 나오면 중단
- break;
- }
- }
-
- console.log("부모 레벨:", parentLevel, "/ 삽입 위치:", insertPosition);
- break;
- }
- }
-
- // 삽입 위치에 추가
if(insertPosition >= 0) {
- console.log("부모 찾음! 삽입 위치:", insertPosition);
-
- // 배열에 직접 삽입
for(var i = newParts.length - 1; i >= 0; i--) {
allData.splice(insertPosition, 0, newParts[i]);
}
-
- // 전체 데이터 다시 설정
leftFrame._tabulGrid.setData(allData);
} else {
- console.log("부모를 못 찾음 - 맨 밑에 추가");
- // 부모를 못 찾으면 맨 밑에 추가
for(var i = 0; i < newParts.length; i++) {
leftFrame._tabulGrid.addData([newParts[i]], false);
}
}
} else {
- // 부모가 선택되지 않은 경우: 최상위 레벨 맨 밑에 추가
for(var i = 0; i < newParts.length; i++) {
leftFrame._tabulGrid.addData([newParts[i]], false);
}
}
+ var message = rightSelectedRows.length + '개 파트가 추가되었습니다.';
+ if(subPartCount > 0) {
+ message += '\n(반제품 하위 ' + subPartCount + '개 품목 포함)';
+ }
+ message += '\n저장 버튼을 눌러 확정하세요.';
+
showConfirm({
title: '파트 추가',
- text: newParts.length + '개 파트가 추가되었습니다.\n저장 버튼을 눌러 확정하세요.',
+ text: message,
icon: 'success'
});
}
}
-// M-BOM 파트 삭제 (그리드에서만 제거)
+// M-BOM 파트 삭제 (그리드에서만 제거, 하위 품목 함께 삭제)
function fn_mbomDeletePart() {
var leftFrame = parent.frames[1].frames['leftFrame'];
var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
@@ -1124,10 +1204,41 @@ function fn_mbomDeletePart() {
}
var childObjid = leftPartNoObj.val();
+ var selectedLevel = parseInt(leftPartNoObj.attr("data-LEVEL") || 1);
+
+ // 하위 품목 개수 미리 계산
+ var allData = leftFrame._tabulGrid.getData();
+ var deleteCount = 0;
+ var foundIndex = -1;
+ console.log("삭제 대상 childObjid:", childObjid, "selectedLevel:", selectedLevel);
+ for(var i = 0; i < allData.length; i++) {
+ if(String(allData[i].CHILD_OBJID) == String(childObjid)) {
+ foundIndex = i;
+ deleteCount = 1;
+ // 하위 품목(레벨이 더 높은 연속된 행) 카운트
+ for(var j = i + 1; j < allData.length; j++) {
+ var nextLevel = parseInt(allData[j].LEVEL) || 1;
+ console.log(" 비교 j=" + j + ", PART_NO=" + allData[j].PART_NO + ", LEVEL=" + allData[j].LEVEL + ", parsed=" + nextLevel + " > " + selectedLevel + " = " + (nextLevel > selectedLevel));
+ if(nextLevel > selectedLevel) {
+ deleteCount++;
+ } else {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ console.log("삭제 예정 개수:", deleteCount, "foundIndex:", foundIndex);
+
+ var confirmText = '선택한 Part를 삭제하시겠습니까?';
+ if(deleteCount > 1) {
+ confirmText += '\n(하위 ' + (deleteCount - 1) + '개 품목도 함께 삭제됩니다)';
+ }
+ confirmText += '\n\n저장 버튼을 눌러야 확정됩니다.';
showConfirm({
title: 'Part 삭제',
- text: '선택한 Part를 삭제하시겠습니까?\n(저장 버튼을 눌러야 확정됩니다)',
+ text: confirmText,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
@@ -1137,21 +1248,22 @@ function fn_mbomDeletePart() {
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
- // Tabulator에서 해당 행 찾아서 삭제
- if(leftFrame && leftFrame._tabulGrid) {
- var rows = leftFrame._tabulGrid.getRows();
- for(var i = 0; i < rows.length; i++) {
- var rowData = rows[i].getData();
- if(rowData.CHILD_OBJID == childObjid) {
- rows[i].delete();
- showConfirm({
- title: '파트 삭제',
- text: '파트가 삭제되었습니다.\n저장 버튼을 눌러 확정하세요.',
- icon: 'success'
- });
- break;
- }
+ if(leftFrame && leftFrame._tabulGrid && foundIndex >= 0) {
+ // 선택한 행 + 하위 품목 일괄 삭제
+ allData.splice(foundIndex, deleteCount);
+ leftFrame._tabulGrid.setData(allData);
+
+ var message = '파트가 삭제되었습니다.';
+ if(deleteCount > 1) {
+ message += '\n(하위 ' + (deleteCount - 1) + '개 품목 포함)';
}
+ message += '\n저장 버튼을 눌러 확정하세요.';
+
+ showConfirm({
+ title: '파트 삭제',
+ text: message,
+ icon: 'success'
+ });
}
}
});
@@ -1238,6 +1350,89 @@ function generateTempId() {
return -Math.floor(Math.random() * 1000000000);
}
+// M-BOM 최상위 제품 변경 (선택한 반제품을 최상위로 승격)
+function fn_changeTopProduct() {
+ var leftFrame = parent.frames[1].frames['leftFrame'];
+ var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
+
+ if(leftPartNoObj.length === 0) {
+ showConfirm("최상위로 설정할 반제품을 왼쪽 트리에서 선택해주세요.");
+ return;
+ }
+
+ var selectedPartNo = leftPartNoObj.attr("data-PART_NO");
+ var selectedPartName = leftPartNoObj.attr("data-PART_NAME") || '';
+ var selectedLevel = parseInt(leftPartNoObj.attr("data-LEVEL") || 1);
+
+ if(!selectedPartNo) {
+ showConfirm("품번 정보를 가져올 수 없습니다.");
+ return;
+ }
+
+ // 반제품 E-BOM 하위 구조 확인
+ var ebomResult = fetchEbomSubTree(selectedPartNo);
+
+ showConfirm({
+ title: '최상위 제품 변경',
+ html: '선택한 품목을 최상위 제품으로 변경하시겠습니까?
' +
+ '품번: ' + selectedPartNo + ' ' +
+ '품명: ' + selectedPartName + ' ' +
+ (ebomResult && ebomResult.hasEbom ? '하위 품목: ' + ebomResult.subTree.length + '개' : '하위 품목: 없음') +
+ '
기존 M-BOM 트리는 교체되고, M-BOM 품번도 변경됩니다.',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonText: '변경',
+ cancelButtonText: '취소'
+ }).then(result => {
+ if(!result.isConfirmed) return;
+
+ // 새로운 M-BOM 트리 구성
+ var newTreeData = [];
+
+ if(ebomResult && ebomResult.hasEbom && ebomResult.subTree && ebomResult.subTree.length > 0) {
+ // E-BOM 하위 구조를 M-BOM 트리로 변환
+ for(var i = 0; i < ebomResult.subTree.length; i++) {
+ var item = ebomResult.subTree[i];
+ var itemLevel = parseInt(item.LEVEL || item.level || 1);
+
+ newTreeData.push({
+ OBJID: generateTempId(),
+ CHILD_OBJID: item.CHILD_OBJID || item.child_objid || generateTempId(),
+ PARENT_OBJID: item.PARENT_OBJID || item.parent_objid || '',
+ LEVEL: itemLevel,
+ PART_OBJID: item.PART_OBJID || item.part_objid || '',
+ PART_NO: item.PART_NO || item.part_no || '',
+ PART_NAME: item.PART_NAME || item.part_name || '',
+ QTY: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ ITEM_QTY: parseInt(item.ITEM_QTY || item.item_qty || 1),
+ QTY_TEMP: parseInt(item.QTY_TEMP || item.qty_temp || item.QTY || item.qty || 1),
+ UNIT: item.UNIT_TITLE || item.unit_title || '',
+ SPEC: item.SPEC || item.spec || '',
+ REVISION: item.REVISION || item.revision || '',
+ SUPPLY_TYPE: '사급',
+ STATUS: 'ACTIVE'
+ });
+ }
+ }
+
+ // 왼쪽 트리에 새 데이터 설정
+ if(leftFrame && leftFrame._tabulGrid) {
+ leftFrame._tabulGrid.setData(newTreeData);
+ }
+
+ // M-BOM 품번을 변경된 최상위 제품의 품번으로 설정
+ $("#search_mbom_part_no").val(selectedPartNo);
+
+ showConfirm({
+ title: '최상위 제품 변경 완료',
+ text: '최상위 제품이 [' + selectedPartNo + ']로 변경되었습니다.\n' +
+ 'M-BOM 품번도 [' + selectedPartNo + ']로 설정되었습니다.\n' +
+ '저장 버튼을 눌러 확정하세요.',
+ icon: 'success'
+ });
+ });
+}
+
// 창 닫기 함수 (프레임 구조 고려)
function fn_closeWindow() {
try {
diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp
index a45b0f7..8158c37 100644
--- a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp
+++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp
@@ -1001,6 +1001,7 @@ function fn_initGrid() {
height: "calc(100vh - 70px)",
columns: columns,
data: bomTreeData,
+ movableRows: true,
rowFormatter: function(row) {
var data = row.getData();
var level = data.LEVEL || 1;
@@ -1008,6 +1009,87 @@ function fn_initGrid() {
}
});
+ // 초기 유효 데이터 저장
+ var _lastValidData = bomTreeData.slice();
+
+ // 드래그 이동 후 같은 부모 내 이동만 허용, 하위 품목도 함께 이동
+ _tabulGrid.on("rowMoved", function(row) {
+ var movedData = row.getData();
+ var movedLevel = parseInt(movedData.LEVEL || 1);
+ var movedParent = movedData.PARENT_OBJID || '';
+ var movedChildObjid = movedData.CHILD_OBJID;
+
+ var currentData = _tabulGrid.getData();
+ var movedIndex = -1;
+ for(var i = 0; i < currentData.length; i++) {
+ if(currentData[i].CHILD_OBJID == movedChildObjid) { movedIndex = i; break; }
+ }
+ if(movedIndex < 0) return;
+
+ // 이동 위치 검증
+ var isValidMove = false;
+ if(movedIndex > 0) {
+ var prevData = currentData[movedIndex - 1];
+ var prevLevel = parseInt(prevData.LEVEL || 1);
+ if((prevLevel == movedLevel && prevData.PARENT_OBJID == movedParent) ||
+ (prevLevel == movedLevel - 1 && prevData.CHILD_OBJID == movedParent)) {
+ isValidMove = true;
+ }
+ } else {
+ if(!movedParent || movedParent == '') isValidMove = true;
+ }
+
+ if(!isValidMove) {
+ alert("같은 부모 내에서만 순서를 변경할 수 있습니다.");
+ _tabulGrid.setData(_lastValidData);
+ return;
+ }
+
+ // 이전 데이터에서 이동된 행의 하위 품목 추출
+ var subTree = [];
+ var oldData = _lastValidData;
+ var oldIndex = -1;
+ for(var i = 0; i < oldData.length; i++) {
+ if(oldData[i].CHILD_OBJID == movedChildObjid) { oldIndex = i; break; }
+ }
+ if(oldIndex >= 0) {
+ for(var j = oldIndex + 1; j < oldData.length; j++) {
+ if(parseInt(oldData[j].LEVEL || 1) > movedLevel) {
+ subTree.push(oldData[j]);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // 하위 품목이 있으면 현재 데이터에서 제거 후 부모 뒤에 삽입
+ if(subTree.length > 0) {
+ var subChildObjids = {};
+ for(var k = 0; k < subTree.length; k++) {
+ subChildObjids[subTree[k].CHILD_OBJID] = true;
+ }
+ var filtered = [];
+ for(var i = 0; i < currentData.length; i++) {
+ if(!subChildObjids[currentData[i].CHILD_OBJID]) {
+ filtered.push(currentData[i]);
+ }
+ }
+ // 부모 위치 다시 찾기
+ var newParentIndex = -1;
+ for(var i = 0; i < filtered.length; i++) {
+ if(filtered[i].CHILD_OBJID == movedChildObjid) { newParentIndex = i; break; }
+ }
+ if(newParentIndex >= 0) {
+ for(var k = subTree.length - 1; k >= 0; k--) {
+ filtered.splice(newParentIndex + 1, 0, subTree[k]);
+ }
+ }
+ _tabulGrid.setData(filtered);
+ }
+
+ _lastValidData = _tabulGrid.getData();
+ });
+
// 행 클릭 시 선택 처리 이벤트
_tabulGrid.on("rowClick", function(e, row) {
// 링크 클릭 시에는 기본 동작 유지
@@ -1174,12 +1256,13 @@ function getMbomTreeData() {
}
// 데이터 구조 변환 (MBOM_DETAIL 테이블에 필요한 모든 필드 포함)
- var mbomData = allData.map(function(row) {
+ // SEQ는 현재 화면 순서(인덱스) 기준으로 재계산
+ var mbomData = allData.map(function(row, index) {
return {
// BOM 구조 정보
parentObjid: row.PARENT_OBJID,
childObjid: row.CHILD_OBJID,
- seq: row.SEQ,
+ seq: index + 1,
level: row.LEVEL,
// 품목 정보
diff --git a/src/com/pms/controller/PartMngController.java b/src/com/pms/controller/PartMngController.java
index f03c754..fa47ebd 100644
--- a/src/com/pms/controller/PartMngController.java
+++ b/src/com/pms/controller/PartMngController.java
@@ -1223,6 +1223,58 @@ public class PartMngController {
return bomTreeList != null ? bomTreeList : new ArrayList |