ebom, mbom
E-BOM & M-BOM 파트 추가 삭제시 반제품 추가 삭제하면 하위 품목 같이 추가 삭제 되는 기능 E-BOM & M-BOM 파트 추가시 원하는 위치로 집어 넣는 기능(현재는 왼쪽에서 선택한 하위레벨의 제일 밑으로만 들어감) E-BOM & M-BOM 파트 추가 삭제시 자동저장이 아니라, 저장/닫기 버튼이 있어서 저장버튼 누를때만 저장되도록 변경 필요 신규 프로젝트 생성하고, M-BOM 처음 만들때 E-BOM 을 가져와서 할당할때 최상위 제품을 변경하는 로직이 필요(최상위 제품을 삭제하고 반제품을 최상위 품으로 만드는 로직) 최상위 제품으로 만들려고 하는 반제품의 하위 부품도 딸려와서 구성이 되어야 하며, M-BOM 의 이름도 변경된 최상위 제품의 품번으로 변경되어 저장되어야 함
This commit is contained in:
@@ -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(/<br\s*\/?>/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 : <strong>['+rightPartNo+']</strong><br>같은 Part No끼리 연결할 수 없습니다.',
|
||||
html: '오류 Part No : <strong>['+rowData.PART_NO+']</strong><br>같은 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 : <strong>['+rightPartNo+']</strong><br>이미 상위에 등록된 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를 변경하시겠습니까?<br><br>' +
|
||||
'<strong>기존:</strong> ' + leftPartNo + '<br>' +
|
||||
'<strong>변경:</strong> ' + rightPartNo,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: '변경',
|
||||
cancelButtonText: '취소',
|
||||
reverseButtons: false
|
||||
html: '선택한 Part를 변경하시겠습니까?<br><br><strong>기존:</strong> ' + leftPartNo + '<br><strong>변경:</strong> ' + 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정보가 없습니다.<br>이대로 연결하면 1레벨로 등록됩니다.<br><br>진행하시겠습니까?',
|
||||
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); */
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="backcolor" style="border:border:1px solid #ccc;">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="backcolor">
|
||||
@@ -125,10 +188,12 @@ function fn_resetFilter() {
|
||||
<input type="text" id="filterPartName" name="filterPartName" value="${partName}">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="plm_btns" onclick="fn_applyFilter()">검색</button>
|
||||
<button type="button" class="plm_btns" onclick="fn_resetFilter()">초기화</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="plm_btns" onclick="fn_applyFilter()">검색</button>
|
||||
<button type="button" class="plm_btns" onclick="fn_resetFilter()">초기화</button>
|
||||
<button type="button" class="plm_btns" onclick="fn_saveEbom()" style="background-color:#4CAF50; color:white; margin-left:20px;">저장</button>
|
||||
<button type="button" class="plm_btns" onclick="fn_closeEbom()">닫기</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -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) {
|
||||
</td>
|
||||
<td style="width: 40px;"></td>
|
||||
<td >
|
||||
<input type="button" value="최상위 제품 변경" class="plm_btns" id="btnChangeTopProduct" style="background-color:#FF9800; color:white;">
|
||||
<input type="button" value="이력보기" class="plm_btns" id="btnHistory">
|
||||
<input type="button" value="저장" class="plm_btns" id="btnSave">
|
||||
<input type="button" value="닫기" class="plm_btns" id="btnClose">
|
||||
@@ -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: '선택한 품목을 최상위 제품으로 변경하시겠습니까?<br><br>' +
|
||||
'<strong>품번:</strong> ' + selectedPartNo + '<br>' +
|
||||
'<strong>품명:</strong> ' + selectedPartName + '<br>' +
|
||||
(ebomResult && ebomResult.hasEbom ? '<strong>하위 품목:</strong> ' + ebomResult.subTree.length + '개' : '<strong>하위 품목:</strong> 없음') +
|
||||
'<br><br>기존 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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
// 품목 정보
|
||||
|
||||
@@ -1223,6 +1223,58 @@ public class PartMngController {
|
||||
return bomTreeList != null ? bomTreeList : new ArrayList<Map>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 품번 기준 E-BOM 하위 구조 조회 (반제품 추가 시 하위 품목 연동용)
|
||||
* 품번에 해당하는 E-BOM이 있으면 하위 트리를 반환, 없으면 빈 리스트 반환
|
||||
*/
|
||||
@RequestMapping("/partMng/getEbomSubTreeByPartNo.do")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getEbomSubTreeByPartNo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String partNo = CommonUtils.checkNull((String)paramMap.get("partNo"));
|
||||
if(partNo.isEmpty()) {
|
||||
result.put("hasEbom", false);
|
||||
result.put("subTree", new ArrayList<>());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 1차: 품번으로 단독 E-BOM 존재 여부 확인 (PART_BOM_REPORT)
|
||||
Map bomReport = partMngService.getBomObjIdByPartNo(partNo);
|
||||
if(bomReport != null && !bomReport.isEmpty()) {
|
||||
String bomReportObjId = CommonUtils.checkNull(bomReport.get("OBJID"));
|
||||
if(bomReportObjId.isEmpty()) {
|
||||
bomReportObjId = CommonUtils.checkNull(bomReport.get("objid"));
|
||||
}
|
||||
Map<String, Object> treeParam = new HashMap<>();
|
||||
treeParam.put("bomReportObjId", bomReportObjId);
|
||||
treeParam.put("search_type", "working");
|
||||
List subTree = partMngService.getBOMPartTreeListSimple(treeParam);
|
||||
|
||||
result.put("hasEbom", true);
|
||||
result.put("bomReportObjId", bomReportObjId);
|
||||
result.put("subTree", subTree != null ? subTree : new ArrayList<>());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2차 fallback: 다른 E-BOM 안에서 해당 품번이 하위 구조를 가진 경우 조회
|
||||
List subTreeFallback = partMngService.findEbomSubTreeForPart(partNo);
|
||||
if(subTreeFallback != null && !subTreeFallback.isEmpty()) {
|
||||
result.put("hasEbom", true);
|
||||
result.put("subTree", subTreeFallback);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.put("hasEbom", false);
|
||||
result.put("subTree", new ArrayList<>());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
result.put("hasEbom", false);
|
||||
result.put("subTree", new ArrayList<>());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping("/partMng/structurePopupCenter.do")
|
||||
public String structurePopupCenter(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
|
||||
|
||||
@@ -1250,6 +1302,44 @@ public class PartMngController {
|
||||
return "/partMng/structurePopupCenter";
|
||||
}
|
||||
|
||||
/**
|
||||
* E-BOM 일괄 저장 (클라이언트 편집 후 저장)
|
||||
*/
|
||||
@RequestMapping("/partMng/saveEbom.do")
|
||||
@ResponseBody
|
||||
public Map<String, Object> saveEbom(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String bomReportObjId = CommonUtils.checkNull((String)paramMap.get("bomReportObjId"));
|
||||
List<Map<String, Object>> ebomData = (List<Map<String, Object>>) paramMap.get("ebomData");
|
||||
|
||||
if(bomReportObjId.isEmpty() || ebomData == null || ebomData.isEmpty()) {
|
||||
result.put("result", "fail");
|
||||
result.put("message", "저장할 데이터가 없습니다.");
|
||||
return result;
|
||||
}
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
|
||||
Map info = person.getLoginInfo();
|
||||
String userId = CommonUtils.checkNull(info.get("userId"));
|
||||
|
||||
boolean saved = partMngService.saveEbomBatch(bomReportObjId, ebomData, userId);
|
||||
|
||||
if(saved) {
|
||||
result.put("result", "success");
|
||||
} else {
|
||||
result.put("result", "fail");
|
||||
result.put("message", "저장에 실패했습니다.");
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
result.put("result", "fail");
|
||||
result.put("message", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조등록 우측 프레임
|
||||
* @param request
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
A.SEQ,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE
|
||||
FROM
|
||||
BOM_PART_QTY A
|
||||
@@ -53,7 +53,7 @@
|
||||
B.SEQ,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH)
|
||||
FROM
|
||||
BOM_PART_QTY B
|
||||
@@ -3322,7 +3322,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
(SELECT PART_NO FROM PART_MNG P WHERE 1=1 AND P.OBJID::varchar = A.PARENT_PART_NO) AS PARENT_PART_MNG_NO,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE
|
||||
FROM
|
||||
BOM_PART_QTY A
|
||||
@@ -3361,7 +3361,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
(SELECT PART_NO FROM PART_MNG P WHERE 1=1 AND P.OBJID::varchar = B.PARENT_PART_NO) AS PARENT_PART_MNG_NO,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH)
|
||||
FROM
|
||||
BOM_PART_QTY B
|
||||
@@ -3949,6 +3949,122 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
</insert>
|
||||
|
||||
|
||||
<!-- E-BOM 내에서 특정 품번이 하위 품목을 가진 채 존재하는 BOM 찾기 (fallback용) -->
|
||||
<select id="findPartInEbomWithChildren" parameterType="map" resultType="map">
|
||||
SELECT BPQ.BOM_REPORT_OBJID, BPQ.CHILD_OBJID, BPQ.PART_NO AS PART_OBJID
|
||||
FROM BOM_PART_QTY BPQ
|
||||
INNER JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO AND PM.IS_LAST = '1'
|
||||
WHERE PM.PART_NO = #{partNo}
|
||||
AND (BPQ.STATUS NOT IN ('deleting', 'deleted') OR BPQ.STATUS IS NULL)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM BOM_PART_QTY SUB
|
||||
WHERE SUB.PARENT_OBJID = BPQ.CHILD_OBJID
|
||||
AND SUB.BOM_REPORT_OBJID = BPQ.BOM_REPORT_OBJID
|
||||
AND (SUB.STATUS NOT IN ('deleting', 'deleted') OR SUB.STATUS IS NULL)
|
||||
)
|
||||
ORDER BY BPQ.REGDATE DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 특정 CHILD_OBJID 아래의 하위 트리 조회 (반제품 하위 구조 fallback용) -->
|
||||
<select id="getSubTreeByParentChildObjid" parameterType="map" resultType="map">
|
||||
WITH RECURSIVE sub_tree(
|
||||
BOM_REPORT_OBJID, OBJID, PARENT_OBJID, CHILD_OBJID,
|
||||
PARENT_PART_NO, PART_NO, LAST_PART_OBJID,
|
||||
QTY, ITEM_QTY, QTY_TEMP, SEQ, STATUS, LEV
|
||||
) AS (
|
||||
SELECT A.BOM_REPORT_OBJID, A.OBJID, A.PARENT_OBJID, A.CHILD_OBJID,
|
||||
A.PARENT_PART_NO, A.PART_NO, A.LAST_PART_OBJID,
|
||||
A.QTY, A.ITEM_QTY, A.QTY_TEMP, A.SEQ, A.STATUS, 1
|
||||
FROM BOM_PART_QTY A
|
||||
WHERE A.PARENT_OBJID = #{parentChildObjid}
|
||||
AND A.BOM_REPORT_OBJID = #{bomReportObjId}
|
||||
AND (A.STATUS NOT IN ('deleting', 'deleted') OR A.STATUS IS NULL)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT B.BOM_REPORT_OBJID, B.OBJID, B.PARENT_OBJID, B.CHILD_OBJID,
|
||||
B.PARENT_PART_NO, B.PART_NO, B.LAST_PART_OBJID,
|
||||
B.QTY, B.ITEM_QTY, B.QTY_TEMP, B.SEQ, B.STATUS, S.LEV + 1
|
||||
FROM BOM_PART_QTY B
|
||||
JOIN sub_tree S ON B.PARENT_OBJID = S.CHILD_OBJID
|
||||
AND B.BOM_REPORT_OBJID = S.BOM_REPORT_OBJID
|
||||
WHERE (B.STATUS NOT IN ('deleting', 'deleted') OR B.STATUS IS NULL)
|
||||
)
|
||||
SELECT
|
||||
S.BOM_REPORT_OBJID
|
||||
,S.OBJID
|
||||
,S.PARENT_OBJID
|
||||
,S.CHILD_OBJID
|
||||
,S.PARENT_PART_NO
|
||||
,S.PART_NO AS PART_OBJID
|
||||
,S.LAST_PART_OBJID AS BOM_LAST_PART_OBJID
|
||||
,P.OBJID AS LAST_PART_OBJID
|
||||
,P.PART_NO
|
||||
,P.PART_NAME
|
||||
,S.QTY
|
||||
,S.ITEM_QTY
|
||||
,(CASE WHEN S.STATUS = 'deploy' THEN S.QTY
|
||||
WHEN S.STATUS = 'beforeEdit' THEN S.QTY
|
||||
WHEN S.STATUS != 'editing' AND (S.QTY_TEMP IS NULL OR S.QTY_TEMP = '') THEN S.QTY
|
||||
ELSE COALESCE(S.QTY_TEMP, S.QTY) END) AS QTY_TEMP
|
||||
,S.LEV AS LEVEL
|
||||
,(SELECT COUNT(*) FROM BOM_PART_QTY WHERE PARENT_OBJID = S.CHILD_OBJID) AS SUB_PART_CNT
|
||||
,S.SEQ
|
||||
,S.STATUS
|
||||
,P.SPEC
|
||||
,P.MATERIAL
|
||||
,P.REVISION
|
||||
,(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = P.PART_TYPE) AS PART_TYPE_TITLE
|
||||
,(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = P.UNIT) AS UNIT_TITLE
|
||||
FROM sub_tree S
|
||||
INNER JOIN PART_MNG P ON P.OBJID = COALESCE(NULLIF(S.LAST_PART_OBJID, ''), S.PART_NO)
|
||||
ORDER BY S.LEV, S.SEQ
|
||||
</select>
|
||||
|
||||
<!-- E-BOM 차분 저장: 기존 데이터 조회 -->
|
||||
<select id="getExistingBomPartQty" parameterType="map" resultType="map">
|
||||
SELECT CHILD_OBJID, PARENT_OBJID, PART_NO, PARENT_PART_NO, QTY, ITEM_QTY, QTY_TEMP, SEQ, STATUS
|
||||
FROM BOM_PART_QTY
|
||||
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
|
||||
</select>
|
||||
|
||||
<!-- E-BOM 차분 저장: CHILD_OBJID 기준 개별 삭제 -->
|
||||
<delete id="deleteBomPartQtyByChildObjid" parameterType="map">
|
||||
DELETE FROM BOM_PART_QTY
|
||||
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
|
||||
AND CHILD_OBJID = #{childObjid}
|
||||
</delete>
|
||||
|
||||
<!-- E-BOM 차분 저장: CHILD_OBJID 기준 수정 (수량, 순서, 부모 등) -->
|
||||
<update id="updateBomPartQtyByChildObjid" parameterType="map">
|
||||
UPDATE BOM_PART_QTY
|
||||
SET PARENT_OBJID = #{PARENT_OBJID},
|
||||
QTY = COALESCE(NULLIF(#{QTY}, ''), '0')::NUMERIC,
|
||||
ITEM_QTY = COALESCE(NULLIF(#{ITEM_QTY}, ''), '0')::NUMERIC,
|
||||
QTY_TEMP = COALESCE(NULLIF(#{QTY_TEMP}, ''), '0')::NUMERIC,
|
||||
SEQ = #{SEQ}
|
||||
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
|
||||
AND CHILD_OBJID = #{childObjid}
|
||||
</update>
|
||||
|
||||
<!-- E-BOM 차분 저장: 신규 행 INSERT -->
|
||||
<insert id="insertBomPartQtyBatch" parameterType="map">
|
||||
INSERT INTO BOM_PART_QTY (
|
||||
BOM_REPORT_OBJID, OBJID, PARENT_OBJID, CHILD_OBJID,
|
||||
PARENT_PART_NO, PART_NO, LAST_PART_OBJID,
|
||||
QTY, ITEM_QTY, QTY_TEMP,
|
||||
REGDATE, WRITER, SEQ, STATUS
|
||||
) VALUES (
|
||||
#{BOM_REPORT_OBJID}, #{OBJID}, #{PARENT_OBJID}, #{CHILD_OBJID},
|
||||
#{PARENT_PART_NO}, #{PART_NO}, #{LAST_PART_OBJID},
|
||||
COALESCE(NULLIF(#{QTY}, ''), '0')::NUMERIC,
|
||||
COALESCE(NULLIF(#{ITEM_QTY}, ''), '0')::NUMERIC,
|
||||
COALESCE(NULLIF(#{QTY_TEMP}, ''), '0')::NUMERIC,
|
||||
NOW(), #{WRITER}, #{SEQ}, #{STATUS}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- //BOM 구조등록 -->
|
||||
<insert id="relatePartInfo" parameterType="map">
|
||||
INSERT INTO BOM_PART_QTY
|
||||
@@ -5778,7 +5894,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
B.LENGTH,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.SEQ,
|
||||
B.MAKER,
|
||||
@@ -5899,7 +6015,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
B.LENGTH,
|
||||
LEV + 1,
|
||||
PATH||A.CHILD_OBJID::TEXT,
|
||||
PATH2||A.SEQ::TEXT,
|
||||
PATH2||LPAD(A.SEQ::TEXT, 10, '0'),
|
||||
A.PARENT_OBJID = ANY(PATH),
|
||||
A.SEQ,
|
||||
B.MAKER,
|
||||
@@ -6058,7 +6174,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
COALESCE(BPQ.LAST_PART_OBJID, BPQ.PART_NO) AS LAST_PART_OBJID,
|
||||
1 AS LEV,
|
||||
ARRAY[BPQ.CHILD_OBJID::TEXT] AS PATH,
|
||||
ARRAY[BPQ.SEQ::TEXT] AS PATH2,
|
||||
ARRAY[LPAD(BPQ.SEQ::TEXT, 10, '0')] AS PATH2,
|
||||
0 AS LEAF
|
||||
FROM BOM_PART_QTY BPQ
|
||||
LEFT JOIN PART_BOM_REPORT PBR ON BPQ.BOM_REPORT_OBJID = PBR.OBJID
|
||||
@@ -6109,7 +6225,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
|
||||
COALESCE(BPQ.LAST_PART_OBJID, BPQ.PART_NO) AS LAST_PART_OBJID,
|
||||
BT.LEV + 1,
|
||||
BT.PATH || BPQ.CHILD_OBJID::TEXT,
|
||||
BT.PATH2 || BPQ.SEQ::TEXT,
|
||||
BT.PATH2 || LPAD(BPQ.SEQ::TEXT, 10, '0'),
|
||||
0 AS LEAF
|
||||
FROM BOM_PART_QTY BPQ
|
||||
INNER JOIN BOM_TREE BT ON BPQ.PARENT_OBJID = BT.CHILD_OBJID
|
||||
|
||||
@@ -1238,7 +1238,7 @@
|
||||
-->
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.SEQ,
|
||||
A.LAST_PART_OBJID
|
||||
@@ -1337,7 +1337,7 @@
|
||||
-->
|
||||
LEV + 1,
|
||||
PATH||A.CHILD_OBJID::TEXT,
|
||||
PATH2||A.SEQ::TEXT,
|
||||
PATH2||LPAD(A.SEQ::TEXT, 10, '0'),
|
||||
A.PARENT_OBJID = ANY(PATH),
|
||||
A.SEQ,
|
||||
A.LAST_PART_OBJID
|
||||
@@ -3626,7 +3626,7 @@
|
||||
A.STATUS,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.UNIT,
|
||||
A.SUPPLY_TYPE,
|
||||
@@ -3668,7 +3668,7 @@
|
||||
B.STATUS,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH),
|
||||
B.UNIT,
|
||||
B.SUPPLY_TYPE,
|
||||
@@ -4155,7 +4155,7 @@
|
||||
A.STATUS,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.UNIT,
|
||||
A.SUPPLY_TYPE,
|
||||
@@ -4208,7 +4208,7 @@
|
||||
B.STATUS,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH),
|
||||
B.UNIT,
|
||||
B.SUPPLY_TYPE,
|
||||
@@ -4381,7 +4381,7 @@
|
||||
A.STATUS,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.UNIT,
|
||||
A.WRITER,
|
||||
@@ -4411,7 +4411,7 @@
|
||||
B.STATUS,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH),
|
||||
B.UNIT,
|
||||
B.WRITER,
|
||||
|
||||
@@ -2235,7 +2235,7 @@ WITH RECURSIVE VIEW_BOM(
|
||||
A.SEQ,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE
|
||||
FROM
|
||||
BOM_PART_QTY A
|
||||
@@ -2268,8 +2268,8 @@ WITH RECURSIVE VIEW_BOM(
|
||||
B.SEQ,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
B.PARENT_OBJID = ANY(PATH)
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH)
|
||||
FROM
|
||||
BOM_PART_QTY B
|
||||
JOIN
|
||||
@@ -3312,7 +3312,7 @@ WITH RECURSIVE VIEW_BOM(
|
||||
A.STATUS,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.UNIT,
|
||||
A.SUPPLY_TYPE,
|
||||
@@ -3375,7 +3375,7 @@ WITH RECURSIVE VIEW_BOM(
|
||||
B.STATUS,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH),
|
||||
B.UNIT,
|
||||
B.SUPPLY_TYPE,
|
||||
@@ -3691,7 +3691,7 @@ WITH RECURSIVE VIEW_BOM(
|
||||
A.STATUS,
|
||||
1,
|
||||
ARRAY [A.CHILD_OBJID::TEXT],
|
||||
ARRAY [A.SEQ::TEXT],
|
||||
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
|
||||
FALSE,
|
||||
A.UNIT,
|
||||
A.SUPPLY_TYPE,
|
||||
@@ -3755,7 +3755,7 @@ WITH RECURSIVE VIEW_BOM(
|
||||
B.STATUS,
|
||||
LEV + 1,
|
||||
PATH||B.CHILD_OBJID::TEXT,
|
||||
PATH2||B.SEQ::TEXT,
|
||||
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
|
||||
B.PARENT_OBJID = ANY(PATH),
|
||||
B.UNIT,
|
||||
B.SUPPLY_TYPE,
|
||||
|
||||
@@ -1475,8 +1475,8 @@ public class PartMngService extends BaseService {
|
||||
sqlParamMap.put("PARENT_PART_OBJID", CommonUtils.checkNull(paramMap.get("leftObjId")));
|
||||
sqlParamMap.put("PARENT_PART_NO", CommonUtils.checkNull(paramMap.get("leftPartNoQty")));
|
||||
sqlParamMap.put("PARENT_QTY_CHILD_OBJID", CommonUtils.checkNull(paramMap.get("leftPartChildObjId")));
|
||||
sqlParamMap.put("QTY", 1);
|
||||
sqlParamMap.put("QTY_TEMP", 1);
|
||||
sqlParamMap.put("QTY", "1");
|
||||
sqlParamMap.put("QTY_TEMP", "1");
|
||||
sqlParamMap.put("STATUS", "adding");
|
||||
sqlParamMap.put("WRITER", userId);
|
||||
//sqlParamMap.put("bomReportObjId", CommonUtils.checkNull(paramMap.get("OBJID")));
|
||||
@@ -3840,6 +3840,195 @@ public class PartMngService extends BaseService {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 품번(String)으로 E-BOM 존재 여부 조회
|
||||
*/
|
||||
public Map<String, Object> getBomObjIdByPartNo(String partNo) throws Exception {
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
paramMap.put("partNo", partNo);
|
||||
return getBomObjIdByPartNo(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* E-BOM 하위 트리 조회 (반제품 추가 시 하위 품목 연동용)
|
||||
* getBOMPartTreeList와 동일하지만 HttpServletRequest 없이 직접 bomReportObjId로 조회
|
||||
*/
|
||||
public List getBOMPartTreeListSimple(Map<String, Object> paramMap) {
|
||||
List<Map<String,Object>> resultList = new ArrayList();
|
||||
List<Map<String,Object>> finalList = new ArrayList();
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
try {
|
||||
resultList = sqlSession.selectList("partMng.getBOMTreeList", paramMap);
|
||||
int maxLevel = 0;
|
||||
if(null != resultList && 0 < resultList.size()) {
|
||||
for(int i = 0; i < resultList.size(); i++) {
|
||||
Map resultMap = (HashMap) resultList.get(i);
|
||||
int resultLevel = Integer.parseInt(CommonUtils.checkNull(resultMap.get("level"), "0"));
|
||||
if(maxLevel < resultLevel) {
|
||||
maxLevel = resultLevel;
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < resultList.size(); i++) {
|
||||
Map resultMap = (HashMap) resultList.get(i);
|
||||
resultMap.put("MAX_LEVEL", maxLevel);
|
||||
finalList.add(resultMap);
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
sqlSession.close();
|
||||
}
|
||||
return finalList;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-BOM 내에서 특정 품번이 하위 구조를 가진 채 존재하는 경우 그 하위 트리를 조회
|
||||
* 단독 E-BOM(PART_BOM_REPORT)이 없는 반제품도 다른 E-BOM 안에서 하위 구조를 찾아 반환
|
||||
*/
|
||||
public List findEbomSubTreeForPart(String partNo) {
|
||||
List resultList = new ArrayList();
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
try {
|
||||
Map<String, Object> findParam = new HashMap<>();
|
||||
findParam.put("partNo", partNo);
|
||||
Map<String, Object> found = (Map<String, Object>) sqlSession.selectOne("partMng.findPartInEbomWithChildren", findParam);
|
||||
|
||||
if(found == null || found.isEmpty()) {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
String bomReportObjId = CommonUtils.checkNull(found.get("bom_report_objid"), CommonUtils.checkNull(found.get("BOM_REPORT_OBJID")));
|
||||
String parentChildObjid = CommonUtils.checkNull(found.get("child_objid"), CommonUtils.checkNull(found.get("CHILD_OBJID")));
|
||||
|
||||
if(bomReportObjId.isEmpty() || parentChildObjid.isEmpty()) {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
Map<String, Object> treeParam = new HashMap<>();
|
||||
treeParam.put("bomReportObjId", bomReportObjId);
|
||||
treeParam.put("parentChildObjid", parentChildObjid);
|
||||
resultList = sqlSession.selectList("partMng.getSubTreeByParentChildObjid", treeParam);
|
||||
|
||||
if(resultList != null && resultList.size() > 0) {
|
||||
int maxLevel = 0;
|
||||
for(int i = 0; i < resultList.size(); i++) {
|
||||
Map row = (HashMap) resultList.get(i);
|
||||
int lev = Integer.parseInt(CommonUtils.checkNull(row.get("level"), "0"));
|
||||
if(maxLevel < lev) maxLevel = lev;
|
||||
}
|
||||
for(int i = 0; i < resultList.size(); i++) {
|
||||
Map row = (HashMap) resultList.get(i);
|
||||
row.put("MAX_LEVEL", maxLevel);
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
sqlSession.close();
|
||||
}
|
||||
return resultList != null ? resultList : new ArrayList();
|
||||
}
|
||||
|
||||
/**
|
||||
* E-BOM 일괄 저장 (차분 방식: 기존 CHILD_OBJID 보존, 추가/삭제/수정 분리)
|
||||
* ASSEMBLY_WBS_TASK, SALES_BOM_REPORT_PART 등이 CHILD_OBJID를 참조하므로 전체 삭제 불가
|
||||
*/
|
||||
public boolean saveEbomBatch(String bomReportObjId, List<Map<String, Object>> ebomData, String userId) {
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
|
||||
try {
|
||||
// 1. 기존 데이터 조회
|
||||
Map<String, Object> queryParam = new HashMap<>();
|
||||
queryParam.put("bomReportObjId", bomReportObjId);
|
||||
List<Map<String, Object>> existingList = sqlSession.selectList("partMng.getExistingBomPartQty", queryParam);
|
||||
|
||||
// 기존 CHILD_OBJID 맵 (CHILD_OBJID -> 행 데이터)
|
||||
Map<String, Map<String, Object>> existingMap = new HashMap<>();
|
||||
for(Map<String, Object> row : existingList) {
|
||||
String childObjid = CommonUtils.checkNull(row.get("child_objid"), CommonUtils.checkNull(row.get("CHILD_OBJID")));
|
||||
if(!childObjid.isEmpty()) {
|
||||
existingMap.put(childObjid, row);
|
||||
}
|
||||
}
|
||||
|
||||
// 새 데이터의 CHILD_OBJID 셋
|
||||
Set<String> newChildObjids = new HashSet<>();
|
||||
for(Map<String, Object> item : ebomData) {
|
||||
String childObjid = CommonUtils.checkNull(item.get("childObjid"));
|
||||
if(!childObjid.isEmpty()) {
|
||||
newChildObjids.add(childObjid);
|
||||
}
|
||||
}
|
||||
|
||||
// 새 데이터에서 참조하는 PARENT_OBJID 셋 (삭제 보호용)
|
||||
Set<String> referencedParentObjids = new HashSet<>();
|
||||
for(Map<String, Object> item : ebomData) {
|
||||
String parentObjid = CommonUtils.checkNull(item.get("parentObjid"));
|
||||
if(!parentObjid.isEmpty()) {
|
||||
referencedParentObjids.add(parentObjid);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 삭제: 기존에 있지만 새 데이터에 없는 항목 (다른 행이 참조하는 부모는 보호)
|
||||
for(String existingChildObjid : existingMap.keySet()) {
|
||||
if(!newChildObjids.contains(existingChildObjid)) {
|
||||
if(referencedParentObjids.contains(existingChildObjid)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> deleteParam = new HashMap<>();
|
||||
deleteParam.put("bomReportObjId", bomReportObjId);
|
||||
deleteParam.put("childObjid", existingChildObjid);
|
||||
sqlSession.delete("partMng.deleteBomPartQtyByChildObjid", deleteParam);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 추가/수정
|
||||
for(int i = 0; i < ebomData.size(); i++) {
|
||||
Map<String, Object> item = ebomData.get(i);
|
||||
String childObjid = CommonUtils.checkNull(item.get("childObjid"));
|
||||
|
||||
if(existingMap.containsKey(childObjid)) {
|
||||
// 기존 항목 → 수정 (수량, 순서 등만 업데이트)
|
||||
Map<String, Object> updateParam = new HashMap<>();
|
||||
updateParam.put("bomReportObjId", bomReportObjId);
|
||||
updateParam.put("childObjid", childObjid);
|
||||
updateParam.put("PARENT_OBJID", CommonUtils.checkNull(item.get("parentObjid")));
|
||||
updateParam.put("QTY", CommonUtils.checkNull(item.get("qty"), "1"));
|
||||
updateParam.put("ITEM_QTY", CommonUtils.checkNull(item.get("itemQty"), "1"));
|
||||
updateParam.put("QTY_TEMP", CommonUtils.checkNull(item.get("qtyTemp"), "1"));
|
||||
updateParam.put("SEQ", i + 1);
|
||||
sqlSession.update("partMng.updateBomPartQtyByChildObjid", updateParam);
|
||||
} else {
|
||||
// 신규 항목 → INSERT
|
||||
Map<String, Object> insertParam = new HashMap<>();
|
||||
insertParam.put("BOM_REPORT_OBJID", bomReportObjId);
|
||||
insertParam.put("OBJID", CommonUtils.createObjId());
|
||||
insertParam.put("PARENT_OBJID", CommonUtils.checkNull(item.get("parentObjid")));
|
||||
insertParam.put("CHILD_OBJID", childObjid.startsWith("-") ? CommonUtils.createObjId() : childObjid);
|
||||
insertParam.put("PARENT_PART_NO", CommonUtils.checkNull(item.get("parentPartNo")));
|
||||
insertParam.put("PART_NO", CommonUtils.checkNull(item.get("partNo")));
|
||||
insertParam.put("LAST_PART_OBJID", CommonUtils.checkNull(item.get("lastPartObjid")));
|
||||
insertParam.put("QTY", CommonUtils.checkNull(item.get("qty"), "1"));
|
||||
insertParam.put("ITEM_QTY", CommonUtils.checkNull(item.get("itemQty"), "1"));
|
||||
insertParam.put("QTY_TEMP", CommonUtils.checkNull(item.get("qtyTemp"), "1"));
|
||||
insertParam.put("SEQ", i + 1);
|
||||
insertParam.put("STATUS", "editing");
|
||||
insertParam.put("WRITER", userId);
|
||||
sqlSession.insert("partMng.insertBomPartQtyBatch", insertParam);
|
||||
}
|
||||
}
|
||||
|
||||
sqlSession.commit();
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
sqlSession.rollback();
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
sqlSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM 복사를 위한 데이터 조회 (엑셀 파싱 형식과 동일하게 반환)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user