Files
wace_plm/WebContent/WEB-INF/view/productionplanning/mBomHeaderPopup.jsp

1226 lines
39 KiB
Plaintext

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
margin: 0;
padding: 0;
overflow: hidden;
height: auto;
}
#plmSearchZon td.label,
#plmSearchZon td:first-child {
text-align: right;
padding-right: 5px;
}
</style>
<script>
// confirm/alert 헬퍼 함수
function showConfirm(options) {
if(typeof options === 'string') {
alert(options);
return Promise.resolve({isConfirmed: true});
}
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;
}
if(options.showCancelButton !== false && (options.icon === 'warning' || options.icon === 'question')) {
var result = confirm(message);
return Promise.resolve({isConfirmed: result});
} else {
alert(message);
return Promise.resolve({isConfirmed: true});
}
}
$(function(){
$('.select2').select2();
// 페이지 로드 시 프로젝트 정보로 검색 필드 자동 입력
<c:if test="${not empty info}">
$("#search_part_no").val("${info.PART_NO}");
$("#search_part_name").val("${info.PART_NAME}");
$("#search_quantity").val("${info.QUANTITY}"); // 프로젝트 수주수량
// 저장된 M-BOM 품번 조회 및 표시
var projectObjId = "${info.OBJID}";
$.ajax({
url: "/productionplanning/getLatestMbomByProjectId.do",
type: "POST",
data: { projectObjId: projectObjId },
dataType: "json",
async: false,
success: function(response) {
if(response && response.MBOM_NO) {
console.log("저장된 M-BOM 발견:", response);
$("#search_mbom_part_no").val(response.MBOM_NO);
// 날짜 형식 변환 (타임스탬프 또는 문자열 -> YYYY-MM-DD)
var regDate = response.REGDATE;
if(regDate) {
var dateStr = "";
// 타임스탬프(숫자)인 경우
if(typeof regDate === 'number') {
var date = new Date(regDate);
var year = date.getFullYear();
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
dateStr = year + '-' + month + '-' + day;
}
// 문자열인 경우 (YYYY-MM-DD HH:mm:ss 형식)
else if(typeof regDate === 'string') {
dateStr = regDate.split(' ')[0];
}
$("#search_save_date").val(dateStr);
} else {
$("#search_save_date").val("");
}
} else {
console.log("저장된 M-BOM 없음");
$("#search_mbom_part_no").val("");
$("#search_save_date").val("");
}
},
error: function(xhr, status, error) {
console.error("M-BOM 조회 오류:", error);
}
});
// 할당된 BOM 정보 확인 및 자동 로드
var sourceBomType = "${info.SOURCE_BOM_TYPE}";
var sourceEbomObjId = "${info.SOURCE_EBOM_OBJID}";
var sourceMbomObjId = "${info.SOURCE_MBOM_OBJID}";
var productCode = "${info.PRODUCT_CODE}";
console.log("할당된 BOM 정보:", {
sourceBomType: sourceBomType,
sourceEbomObjId: sourceEbomObjId,
sourceMbomObjId: sourceMbomObjId,
productCode: productCode
});
// Machine 이외 제품: M-BOM 템플릿 자동 로드
<c:if test="${not empty mbomTemplateDetails}">
console.log("M-BOM 템플릿 발견 - 자동 로드 시작");
setTimeout(function() {
fn_loadMbomTemplate();
}, 500);
</c:if>
// Controller에서 이미 데이터를 로드하여 JSP에 전달하므로 자동 조회 불필요
// setTimeout(function() {
// fn_searchMbom();
// }, 500);
</c:if>
//Part 연결 (M-BOM용)
$("#moveLeft").click(function(){
fn_mbomAddPart();
});
//연결된 part 삭제 (M-BOM용)
$("#moveRight").click(function(){
fn_mbomDeletePart();
});
//연결된 part 변경 (M-BOM용)
$("#moveChange").click(function(){
fn_mbomChangePart();
});
// 이력보기 버튼 클릭
$("#btnHistory").click(function(){
fn_showHistory();
});
// 저장 버튼 클릭
$("#btnSave").click(function(){
fn_saveMbom();
});
// 닫기 버튼 클릭
$("#btnClose").click(function(){
fn_closeWindow();
});
/* 주석처리: 가공납기/연삭납기 일괄 적용 버튼 이벤트
// 일괄 적용 버튼 클릭
$("#btnApplyBulkDeadline").click(function(){
fn_applyBulkDeadline();
});
*/
});
/* 주석처리: 가공납기/연삭납기 일괄 적용 함수
function fn_applyBulkDeadline() {
var processingDeadline = $("#bulk_processing_deadline").val();
var grindingDeadline = $("#bulk_grinding_deadline").val();
if(!processingDeadline && !grindingDeadline) {
alert("가공납기 또는 연삭납기를 입력해주세요.");
return;
}
// 확인 메시지
var message = "선택한 날짜를 전체 항목에 일괄 적용하시겠습니까?\n\n";
if(processingDeadline) message += "가공납기: " + processingDeadline + "\n";
if(grindingDeadline) message += "연삭납기: " + grindingDeadline;
if(!confirm(message)) {
return;
}
// 왼쪽 프레임의 함수 호출
var bottomFrame = parent.frames[1];
var leftFrame = bottomFrame ? bottomFrame.frames['leftFrame'] : null;
if(!leftFrame || !leftFrame.applyBulkDeadline) {
alert("M-BOM 화면을 찾을 수 없습니다.");
return;
}
// 일괄 적용 실행
var updatedCount = leftFrame.applyBulkDeadline(processingDeadline, grindingDeadline);
if(updatedCount > 0) {
alert("일괄 적용이 완료되었습니다. (" + updatedCount + "개 항목)");
} else {
alert("적용할 데이터가 없습니다.");
}
}
*/
// M-BOM 템플릿 로드 (Machine 이외 제품)
function fn_loadMbomTemplate() {
console.log("fn_loadMbomTemplate 호출됨");
// 템플릿 데이터를 JSP에서 받아옴
var templateDetails = [];
<c:if test="${not empty mbomTemplateDetails}">
<c:forEach items="${mbomTemplateDetails}" var="item">
templateDetails.push({
OBJID: '', // 새로 생성될 ID
CHILD_OBJID: '${item.CHILD_OBJID}', // 템플릿의 CHILD_OBJID 유지 (트리 구조용)
PARENT_OBJID: '${item.PARENT_OBJID}',
SEQ: ${item.SEQ},
LEVEL: ${item.LEVEL},
PART_OBJID: '${item.PART_OBJID}',
PART_NO: '${item.PART_NO}',
PART_NAME: '${item.PART_NAME}',
QTY: ${item.QTY},
ITEM_QTY: ${item.QTY}, // 항목수량
QTY_TEMP: ${item.QTY},
UNIT: '${item.UNIT}',
SUPPLY_TYPE: '${item.SUPPLY_TYPE}',
MAKE_OR_BUY: '${item.MAKE_OR_BUY}',
RAW_MATERIAL: '${item.RAW_MATERIAL}',
RAW_MATERIAL_SPEC: '${item.RAW_MATERIAL_SPEC}',
SIZE: '${item.RAW_MATERIAL_SIZE}',
RAW_MATERIAL_NO: '${item.RAW_MATERIAL_PART_NO}',
PROCESSING_VENDOR: '${item.PROCESSING_VENDOR}',
PROCESSING_DEADLINE: '${item.PROCESSING_DEADLINE}',
GRINDING_DEADLINE: '${item.GRINDING_DEADLINE}',
REQUIRED_QTY: ${item.REQUIRED_QTY != null ? item.REQUIRED_QTY : 0},
// ORDER_QTY, PRODUCTION_QTY는 명시적으로 설정하지 않음 (undefined)
// 이렇게 하면 formatter에서 자동 계산됨
REMARK: '${item.REMARK}',
STATUS: 'ACTIVE'
});
</c:forEach>
</c:if>
console.log("템플릿 데이터 개수:", templateDetails.length);
// 왼쪽 프레임에 데이터 로드
var bottomFrame = parent.frames[1];
var leftFrame = bottomFrame ? bottomFrame.frames['leftFrame'] : null;
if(leftFrame && leftFrame._tabulGrid) {
leftFrame._tabulGrid.setData(templateDetails);
console.log("템플릿 데이터 로드 완료");
} else {
console.error("왼쪽 프레임 또는 그리드를 찾을 수 없습니다.");
}
}
// M-BOM 조회
function fn_searchMbom() {
var partNo = $("#search_part_no").val().trim();
var partName = $("#search_part_name").val().trim();
var mbomPartNo = $("#search_mbom_part_no").val().trim();
var saveDate = $("#search_save_date").val().trim();
// 할당된 BOM 정보 사용 (우선순위: SOURCE_EBOM_OBJID > SOURCE_MBOM_OBJID > 기존 BOM_REPORT_OBJID)
var sourceBomType = "${info.SOURCE_BOM_TYPE}";
var sourceEbomObjId = "${info.SOURCE_EBOM_OBJID}";
var sourceMbomObjId = "${info.SOURCE_MBOM_OBJID}";
var bomReportObjId = "";
if(sourceBomType === "EBOM" && sourceEbomObjId) {
bomReportObjId = sourceEbomObjId;
} else if(sourceBomType === "MBOM" && sourceMbomObjId) {
bomReportObjId = sourceMbomObjId;
} else {
bomReportObjId = "${info.BOM_REPORT_OBJID}"; // 기존 방식 (하위 호환)
}
console.log("fn_searchMbom 호출:", {
sourceBomType: sourceBomType,
bomReportObjId: bomReportObjId,
partNo: partNo,
partName: partName,
mbomPartNo: mbomPartNo,
saveDate: saveDate
});
// 왼쪽 프레임의 조회 함수 호출
var bottomFrame = parent.frames[1]; // 하단 프레임셋
var leftFrame = bottomFrame ? bottomFrame.frames['leftFrame'] : null;
if(leftFrame && leftFrame.fn_searchMbom) {
leftFrame.fn_searchMbom({
bomReportObjId: bomReportObjId,
sourceBomType: sourceBomType,
partNo: partNo,
partName: partName,
mbomPartNo: mbomPartNo,
saveDate: saveDate
});
} else {
console.error("왼쪽 프레임 또는 fn_searchMbom 함수를 찾을 수 없습니다.");
}
}
// M-BOM 저장
function fn_saveMbom() {
console.log("fn_saveMbom 호출됨");
// 프레임 구조: parent(최상위) -> parent.frames[1](하단) -> parent.frames[1].frames['leftFrame']
var bottomFrame = parent.frames[1]; // 하단 프레임 (mBomPopupFs.jsp)
console.log("bottomFrame:", bottomFrame);
if(!bottomFrame) {
alert("하단 프레임을 찾을 수 없습니다.");
return;
}
var leftFrame = bottomFrame.frames['leftFrame']; // 왼쪽 프레임 (mBomPopupLeft.jsp)
console.log("leftFrame:", leftFrame);
if(!leftFrame) {
alert("M-BOM 데이터를 가져올 수 없습니다.");
return;
}
// 트리 데이터 수집
var mbomData = leftFrame.getMbomTreeData ? leftFrame.getMbomTreeData() : null;
console.log("mbomData:", mbomData);
console.log("mbomData length:", mbomData ? mbomData.length : 0);
if(!mbomData || mbomData.length === 0) {
alert("저장할 M-BOM 데이터가 없습니다.");
return;
}
// 저장 확인
if(!confirm("M-BOM을 저장하시겠습니까?")) {
return;
}
var projectObjId = "${info.OBJID}";
// 기존 M-BOM 존재 여부 확인 (동기 처리)
var existingMbom = null;
$.ajax({
url: "/productionplanning/getLatestMbomByProjectId.do",
type: "POST",
data: { projectObjId: projectObjId },
dataType: "json",
async: false,
success: function(response) {
if(response && response.OBJID) {
existingMbom = response;
console.log("기존 M-BOM 발견:", existingMbom);
}
}
});
var saveData = {
projectObjId: projectObjId, // PROJECT_MGMT의 OBJID
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이 있으면 업데이트
};
console.log("저장할 데이터:", saveData);
console.log("isUpdate:", saveData.isUpdate);
// 저장 API 호출
$.ajax({
url: "/productionplanning/saveMbom.do",
method: 'post',
data: JSON.stringify(saveData),
contentType: 'application/json',
dataType: 'json',
success: function(data) {
console.log("저장 응답:", data);
if(data && data.result === "success") {
alert("M-BOM이 저장되었습니다.");
// 부모 창(M-BOM 목록) 새로고침
try {
// window.opener가 있으면 (팝업으로 열린 경우)
if(window.opener && !window.opener.closed) {
console.log("window.opener 찾음");
if(window.opener.fn_search) {
console.log("window.opener.fn_search 호출");
window.opener.fn_search();
} else if(window.opener.location) {
console.log("window.opener.location.reload 호출");
window.opener.location.reload();
}
}
// window.top이 현재 창이 아니면 (iframe인 경우)
else if(window.top !== window.self && window.top.opener && !window.top.opener.closed) {
console.log("window.top.opener 찾음");
if(window.top.opener.fn_search) {
console.log("window.top.opener.fn_search 호출");
window.top.opener.fn_search();
} else if(window.top.opener.location) {
console.log("window.top.opener.location.reload 호출");
window.top.opener.location.reload();
}
}
} catch(e) {
console.error("부모 창 새로고침 실패:", e);
}
// 현재 창 닫기
fn_closeWindow();
} else {
alert("M-BOM 저장에 실패했습니다: " + (data.message || ""));
}
},
error: function(jqxhr, status, error){
console.error("M-BOM 저장 오류:", error);
console.error("응답:", jqxhr.responseText);
alert("M-BOM 저장 중 오류가 발생했습니다.");
}
});
}
// M-BOM 이력보기
function fn_showHistory() {
var projectObjId = "${info.OBJID}";
if(!projectObjId) {
alert("프로젝트 정보가 없습니다.");
return;
}
// M-BOM 이력 조회 (프로젝트 기준)
$.ajax({
url: "/productionplanning/getMbomHistory.do",
method: 'post',
data: {
projectObjId: projectObjId
},
dataType: 'json',
success: function(data) {
if(data && data.historyList && data.historyList.length > 0) {
// 이력 상세 분석 및 HTML 생성
var historyHtml = generateHistoryHtml(data.historyList);
// 새 창으로 이력 표시
var historyWindow = window.open("", "mbomHistory", "width=1400,height=800,scrollbars=yes,resizable=yes");
historyWindow.document.write("<html><head><title>M-BOM 변경 이력</title>");
historyWindow.document.write("<style>");
historyWindow.document.write("body { font-family: Arial, sans-serif; margin: 20px; }");
historyWindow.document.write("h2 { text-align: center; color: #333; }");
historyWindow.document.write("table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }");
historyWindow.document.write("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
historyWindow.document.write("th { background-color: #4CAF50; color: white; font-weight: bold; }");
historyWindow.document.write(".history-header { background-color: #f0f0f0; font-weight: bold; }");
historyWindow.document.write(".change-add { background-color: #e7f4e7; }");
historyWindow.document.write(".change-delete { background-color: #ffe7e7; }");
historyWindow.document.write(".change-modify { background-color: #fff4e6; }");
historyWindow.document.write(".change-bom { background-color: #e6f3ff; }");
historyWindow.document.write(".field-changed { color: #d9534f; font-weight: bold; }");
historyWindow.document.write(".section-title { background-color: #5bc0de; color: white; font-weight: bold; text-align: center; }");
historyWindow.document.write("</style>");
historyWindow.document.write("</head><body>");
historyWindow.document.write("<h2>M-BOM 변경 이력</h2>");
historyWindow.document.write(historyHtml);
historyWindow.document.write("</body></html>");
historyWindow.document.close();
} else {
alert("이력이 없습니다.");
}
},
error: function(jqxhr, status, error){
console.error("M-BOM 이력 조회 오류:", error);
alert("M-BOM 이력 조회 중 오류가 발생했습니다.");
}
});
}
// 이력 HTML 생성 함수
function generateHistoryHtml(historyList) {
var html = "";
for(var i = 0; i < historyList.length; i++) {
var history = historyList[i];
var changeType = history.CHANGE_TYPE || "UNKNOWN";
var changeDate = history.CHANGE_DATE || history.REGDATE || "";
var changeUser = history.CHANGE_USER || "";
// 이력 헤더
html += "<div style='margin-bottom: 40px;'>";
html += "<h3 style='background-color: #333; color: white; padding: 10px;'>";
html += "이력 #" + (i + 1) + " - " + changeType + " (" + changeDate + " / " + changeUser + ")";
html += "</h3>";
try {
var beforeData = history.BEFORE_DATA ? JSON.parse(history.BEFORE_DATA) : null;
var afterData = history.AFTER_DATA ? JSON.parse(history.AFTER_DATA) : null;
if(changeType === "CREATE") {
// 생성: afterData만 표시
html += generateCreateHistoryTable(afterData);
} else if(changeType === "UPDATE") {
// 수정: 변경 사항 비교
html += generateUpdateHistoryTable(beforeData, afterData);
}
} catch(e) {
console.error("JSON 파싱 오류:", e);
html += "<p style='color: red;'>데이터 파싱 오류</p>";
}
html += "</div>";
}
return html;
}
// 생성 이력 테이블 생성
function generateCreateHistoryTable(afterData) {
var html = "<table>";
html += "<tr class='section-title'><td colspan='4'>생성된 M-BOM 정보</td></tr>";
html += "<tr><th>항목</th><th>값</th><th>항목</th><th>값</th></tr>";
// 헤더 정보
html += "<tr>";
html += "<td>M-BOM 품번</td><td>" + (afterData.mbomNo || afterData.MBOM_NO || "") + "</td>";
html += "<td>품번</td><td>" + (afterData.partNo || afterData.PART_NO || "") + "</td>";
html += "</tr>";
html += "<tr>";
html += "<td>품명</td><td>" + (afterData.partName || afterData.PART_NAME || "") + "</td>";
html += "<td>기준 BOM 유형</td><td>" + (afterData.sourceBomType || afterData.SOURCE_BOM_TYPE || "") + "</td>";
html += "</tr>";
// BOM 상세 항목
var mbomData = afterData.mbomData || [];
if(mbomData.length > 0) {
html += "<tr class='section-title'><td colspan='4'>생성된 BOM 항목 (" + mbomData.length + "개)</td></tr>";
html += "<tr class='change-add'><th>품번</th><th>품명</th><th>수량</th><th>레벨</th></tr>";
for(var i = 0; i < mbomData.length; i++) {
var item = mbomData[i];
html += "<tr class='change-add'>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>" + (item.qty || "") + "</td>";
html += "<td>" + (item.level || "") + "</td>";
html += "</tr>";
}
}
html += "</table>";
return html;
}
// 수정 이력 테이블 생성 (새 형식)
function generateUpdateHistoryTable(beforeData, afterData) {
var html = "";
// beforeData는 이제 변경된 항목 배열
var changedItems = beforeData;
if(!Array.isArray(changedItems) || changedItems.length === 0) {
return "<p>변경 사항이 없습니다.</p>";
}
html += "<table>";
html += "<tr class='section-title'><td colspan='4'>변경된 항목 (" + changedItems.length + "개)</td></tr>";
html += "<tr><th>변경유형</th><th>품번</th><th>품명</th><th>변경 내용</th></tr>";
for(var i = 0; i < changedItems.length; i++) {
var item = changedItems[i];
var changeType = item.changeType;
if(changeType === "ADD") {
// 추가된 항목
var afterData = item.afterData || {};
html += "<tr class='change-add'>";
html += "<td>추가</td>";
html += "<td>" + (afterData.partNo || "") + "</td>";
html += "<td>" + (afterData.partName || "") + "</td>";
html += "<td>새 항목 추가</td>";
html += "</tr>";
} else if(changeType === "DELETE") {
// 삭제된 항목
var beforeDataItem = item.beforeData || {};
html += "<tr class='change-delete'>";
html += "<td>삭제</td>";
html += "<td>" + (beforeDataItem.PART_NO || "") + "</td>";
html += "<td>" + (beforeDataItem.PART_NAME || "") + "</td>";
html += "<td>항목 삭제</td>";
html += "</tr>";
} else if(changeType === "MODIFY") {
// 수정된 항목
html += "<tr class='change-modify'>";
html += "<td>수정</td>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>";
var fieldChanges = item.fieldChanges || [];
for(var j = 0; j < fieldChanges.length; j++) {
var fc = fieldChanges[j];
var fieldNameKr = getFieldNameKorean(fc.field);
html += "<strong>" + fieldNameKr + ":</strong> ";
html += "<span class='before-value'>" + (fc.before || "(없음)") + "</span> → ";
html += "<span class='after-value'>" + (fc.after || "(없음)") + "</span>";
if(j < fieldChanges.length - 1) html += "<br>";
}
html += "</td>";
html += "</tr>";
}
}
html += "</table>";
return html;
}
// 필드명 한글 변환
function getFieldNameKorean(field) {
var fieldMap = {
"QTY": "수량",
"SUPPLY_TYPE": "자급/사급",
"RAW_MATERIAL": "소재",
"RAW_MATERIAL_SIZE": "사이즈",
"RAW_MATERIAL_PART_NO": "소재품번",
"PROCESSING_VENDOR": "가공업체",
"PROCESSING_DEADLINE": "가공납기",
"GRINDING_DEADLINE": "연삭납기",
"REQUIRED_QTY": "소재소요량",
"ORDER_QTY": "소재발주수량",
"PRODUCTION_QTY": "제작수량",
"REMARK": "비고"
};
return fieldMap[field] || field;
}
// 기존 함수들은 사용하지 않지만 남겨둠
function oldGenerateUpdateHistoryTable(beforeData, afterData) {
var html = "";
// 1. 헤더 정보 변경 확인
var headerChanges = compareHeaderData(beforeData, afterData);
if(headerChanges.length > 0) {
html += "<table>";
html += "<tr class='section-title'><td colspan='3'>헤더 정보 변경</td></tr>";
html += "<tr class='change-bom'><th>항목</th><th>변경 전</th><th>변경 후</th></tr>";
for(var i = 0; i < headerChanges.length; i++) {
var change = headerChanges[i];
html += "<tr class='change-bom'>";
html += "<td>" + change.field + "</td>";
html += "<td>" + change.before + "</td>";
html += "<td class='field-changed'>" + change.after + "</td>";
html += "</tr>";
}
html += "</table><br>";
}
// 2. BOM 항목 변경 분석
var beforeItems = beforeData.mbomData || [];
var afterItems = afterData.mbomData || [];
var changes = compareBomItems(beforeItems, afterItems);
// Excel 스타일 통합 테이블 생성
if(false && changes.added.length > 0 || changes.deleted.length > 0 || changes.modified.length > 0) {
html += "<table style='font-size: 11px;'>";
html += "<tr class='section-title'>";
html += "<th>구분</th>";
html += "<th>품번</th>";
html += "<th>품명</th>";
html += "<th>수량</th>";
html += "<th>자급/사급</th>";
html += "<th>소재</th>";
html += "<th>사이즈</th>";
html += "<th>소재품번</th>";
html += "<th>소재소요량</th>";
html += "<th>가공업체</th>";
html += "<th>가공납기</th>";
html += "<th>연삭납기</th>";
html += "<th>소재발주수량</th>";
html += "<th>제작수량</th>";
html += "<th>비고</th>";
html += "</tr>";
// 추가된 항목
for(var i = 0; i < changes.added.length; i++) {
var item = changes.added[i];
html += "<tr class='change-add'>";
html += "<td>추가</td>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>" + (item.qty || "") + "</td>";
html += "<td>" + (item.supplyType || "") + "</td>";
html += "<td>" + (item.rawMaterial || "") + "</td>";
html += "<td>" + (item.rawMaterialSize || "") + "</td>";
html += "<td>" + (item.rawMaterialPartNo || "") + "</td>";
html += "<td>" + (item.requiredQty || "") + "</td>";
html += "<td>" + (item.processingVendor || "") + "</td>";
html += "<td>" + (item.processingDeadline || "") + "</td>";
html += "<td>" + (item.grindingDeadline || "") + "</td>";
html += "<td>" + (item.orderQty || "") + "</td>";
html += "<td>" + (item.productionQty || "") + "</td>";
html += "<td>" + (item.remark || "") + "</td>";
html += "</tr>";
}
// 삭제된 항목
for(var i = 0; i < changes.deleted.length; i++) {
var item = changes.deleted[i];
html += "<tr class='change-delete'>";
html += "<td>삭제</td>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>" + (item.qty || "") + "</td>";
html += "<td>" + (item.supplyType || "") + "</td>";
html += "<td>" + (item.rawMaterial || "") + "</td>";
html += "<td>" + (item.rawMaterialSize || "") + "</td>";
html += "<td>" + (item.rawMaterialPartNo || "") + "</td>";
html += "<td>" + (item.requiredQty || "") + "</td>";
html += "<td>" + (item.processingVendor || "") + "</td>";
html += "<td>" + (item.processingDeadline || "") + "</td>";
html += "<td>" + (item.grindingDeadline || "") + "</td>";
html += "<td>" + (item.orderQty || "") + "</td>";
html += "<td>" + (item.productionQty || "") + "</td>";
html += "<td>" + (item.remark || "") + "</td>";
html += "</tr>";
}
// 수정된 항목 (변경된 필드만 강조)
for(var i = 0; i < changes.modified.length; i++) {
var mod = changes.modified[i];
var changedFields = {};
for(var j = 0; j < mod.changes.length; j++) {
changedFields[mod.changes[j].fieldKey] = true;
}
html += "<tr class='change-modify'>";
html += "<td>수정</td>";
html += "<td>" + (mod.partNo || "") + "</td>";
html += "<td>" + (mod.partName || "") + "</td>";
html += "<td" + (changedFields['qty'] ? " class='field-changed'" : "") + ">" + (mod.after.qty || "") + "</td>";
html += "<td" + (changedFields['supplyType'] ? " class='field-changed'" : "") + ">" + (mod.after.supplyType || "") + "</td>";
html += "<td" + (changedFields['rawMaterial'] ? " class='field-changed'" : "") + ">" + (mod.after.rawMaterial || "") + "</td>";
html += "<td" + (changedFields['rawMaterialSize'] ? " class='field-changed'" : "") + ">" + (mod.after.rawMaterialSize || "") + "</td>";
html += "<td" + (changedFields['rawMaterialPartNo'] ? " class='field-changed'" : "") + ">" + (mod.after.rawMaterialPartNo || "") + "</td>";
html += "<td" + (changedFields['requiredQty'] ? " class='field-changed'" : "") + ">" + (mod.after.requiredQty || "") + "</td>";
html += "<td" + (changedFields['processingVendor'] ? " class='field-changed'" : "") + ">" + (mod.after.processingVendor || "") + "</td>";
html += "<td" + (changedFields['processingDeadline'] ? " class='field-changed'" : "") + ">" + (mod.after.processingDeadline || "") + "</td>";
html += "<td" + (changedFields['grindingDeadline'] ? " class='field-changed'" : "") + ">" + (mod.after.grindingDeadline || "") + "</td>";
html += "<td" + (changedFields['orderQty'] ? " class='field-changed'" : "") + ">" + (mod.after.orderQty || "") + "</td>";
html += "<td" + (changedFields['productionQty'] ? " class='field-changed'" : "") + ">" + (mod.after.productionQty || "") + "</td>";
html += "<td" + (changedFields['remark'] ? " class='field-changed'" : "") + ">" + (mod.after.remark || "") + "</td>";
html += "</tr>";
}
html += "</table>";
}
return html;
}
// 헤더 데이터 비교
function compareHeaderData(before, after) {
var changes = [];
var fieldsToCheck = {
'SOURCE_BOM_TYPE': '기준 BOM 유형',
'SOURCE_EBOM_OBJID': '기준 E-BOM',
'SOURCE_MBOM_OBJID': '기준 M-BOM',
'PART_NO': '품번',
'PART_NAME': '품명',
'MBOM_STATUS': 'M-BOM 상태'
};
for(var key in fieldsToCheck) {
var beforeVal = before[key] || before[key.toLowerCase()] || "";
var afterVal = after[key] || after[key.toLowerCase()] || "";
if(beforeVal != afterVal) {
changes.push({
field: fieldsToCheck[key],
before: beforeVal,
after: afterVal
});
}
}
return changes;
}
// BOM 항목 비교
function compareBomItems(beforeItems, afterItems) {
var added = [];
var deleted = [];
var modified = [];
// childObjid를 키로 사용하여 매핑
var beforeMap = {};
var afterMap = {};
for(var i = 0; i < beforeItems.length; i++) {
var item = beforeItems[i];
var key = item.childObjid || item.objid;
if(key) beforeMap[key] = item;
}
for(var i = 0; i < afterItems.length; i++) {
var item = afterItems[i];
var key = item.childObjid || item.objid;
if(key) afterMap[key] = item;
}
// 추가된 항목 찾기
for(var key in afterMap) {
if(!beforeMap[key]) {
added.push(afterMap[key]);
}
}
// 삭제된 항목 찾기
for(var key in beforeMap) {
if(!afterMap[key]) {
deleted.push(beforeMap[key]);
}
}
// 수정된 항목 찾기
for(var key in afterMap) {
if(beforeMap[key]) {
var itemChanges = compareItemFields(beforeMap[key], afterMap[key]);
if(itemChanges.length > 0) {
modified.push({
partNo: afterMap[key].partNo,
partName: afterMap[key].partName,
before: beforeMap[key],
after: afterMap[key],
changes: itemChanges
});
}
}
}
return {
added: added,
deleted: deleted,
modified: modified
};
}
// 항목 필드 비교
function compareItemFields(before, after) {
var changes = [];
var fieldsToCheck = {
'qty': '수량',
'supplyType': '자급/사급',
'rawMaterial': '소재',
'rawMaterialSize': '사이즈',
'rawMaterialPartNo': '소재품번',
'processingVendor': '가공업체',
'processingDeadline': '가공납기',
'grindingDeadline': '연삭납기',
'requiredQty': '소재소요량',
'orderQty': '소재발주수량',
'productionQty': '제작수량',
'remark': '비고'
};
for(var key in fieldsToCheck) {
var beforeVal = before[key] || "";
var afterVal = after[key] || "";
// 값 비교 (타입 변환 고려)
if(String(beforeVal) != String(afterVal)) {
changes.push({
field: fieldsToCheck[key],
fieldKey: key, // 필드 키 추가 (CSS 클래스 적용용)
before: beforeVal,
after: afterVal
});
}
}
return changes;
}
</script>
</head>
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${param.objId}" />
<input type="hidden" name="search_quantity" id="search_quantity" value="" />
<div id="plmSearchZon">
<table>
<tr>
<td><label for="search_part_no">품번</label></td>
<td>
<input type="text" name="search_part_no" id="search_part_no" style="width: 300px;" value="" readonly>
</td>
<td class="label"><label for="search_part_name">품명</label></td>
<td>
<input type="text" name="search_part_name" id="search_part_name" style="width: 300px;" value="" readonly>
</td>
</tr>
<tr>
<td><label for="search_mbom_part_no">M-BOM 품번</label></td>
<td>
<input type="text" name="search_mbom_part_no" id="search_mbom_part_no" style="width: 300px;" value="" readonly>
</td>
<td class="label"><label for="search_save_date">저장일</label></td>
<td>
<input type="text" name="search_save_date" id="search_save_date" style="width: 300px;" value="" readonly>
</td>
<td style="width: 40px;"></td>
<td >
<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">
</td>
</tr>
<!-- <tr>
<td><label for="bulk_processing_deadline">가공납기 일괄</label></td>
<td>
<input type="date" name="bulk_processing_deadline" id="bulk_processing_deadline" value="">
</td>
<td class="label"><label for="bulk_grinding_deadline">연삭납기 일괄</label></td>
<td>
<input type="date" name="bulk_grinding_deadline" id="bulk_grinding_deadline" value="">
</td>
<td style="width: 40px;"></td>
<td>
<input type="button" value="일괄 적용" class="plm_btns" id="btnApplyBulkDeadline">
</td>
</tr> -->
</table>
</div>
</form>
<script>
// ==================== M-BOM 전용 파트 추가/삭제/변경 함수 ====================
// M-BOM 파트 추가 (그리드에만 반영)
function fn_mbomAddPart() {
var rightFrame = parent.frames[1].frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0) {
showConfirm("선택된 파트가 없습니다.");
return false;
}
var leftFrame = parent.frames[1].frames['leftFrame'];
var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
var parentObjid = "";
var parentLevel = 0;
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 = [];
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var newPart = {
OBJID: generateTempId(),
CHILD_OBJID: generateTempId(),
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,
UNIT: rowData.UNIT,
SPEC: rowData.SPEC,
REVISION: rowData.REVISION,
SUPPLY_TYPE: '사급',
STATUS: 'ACTIVE'
};
console.log("추가할 파트:", {
partNo: newPart.PART_NO,
parentObjid: newPart.PARENT_OBJID,
level: newPart.LEVEL
});
newParts.push(newPart);
}
// 왼쪽 프레임의 Tabulator에 추가
if(leftFrame && leftFrame._tabulGrid) {
if(parentObjid) {
// 부모가 선택된 경우: 데이터 배열에 직접 삽입
var allData = leftFrame._tabulGrid.getData();
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;
}
}
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);
}
}
showConfirm({
title: '파트 추가',
text: newParts.length + '개 파트가 추가되었습니다.\n저장 버튼을 눌러 확정하세요.',
icon: 'success'
});
}
}
// M-BOM 파트 삭제 (그리드에서만 제거)
function fn_mbomDeletePart() {
var leftFrame = parent.frames[1].frames['leftFrame'];
var leftPartNoObj = $("input[name=checkedPartNo]:checked", leftFrame.document);
if(leftPartNoObj.length === 0) {
showConfirm("삭제할 파트를 선택해주세요.");
return;
}
var childObjid = leftPartNoObj.val();
showConfirm({
title: 'Part 삭제',
text: '선택한 Part를 삭제하시겠습니까?\n(저장 버튼을 눌러야 확정됩니다)',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '삭제',
cancelButtonText: '취소',
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;
}
}
}
}
});
}
// M-BOM 파트 변경 (그리드에서만 변경)
function fn_mbomChangePart() {
var leftFrame = parent.frames[1].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;
}
var rightFrame = parent.frames[1].frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0){
showConfirm("변경할 새 파트를 선택해주세요.");
return false;
}
if(rightSelectedRows.length > 1){
showConfirm("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var leftPartNo = leftPartNoList.attr("data-PART_NO");
var leftChildObjid = leftPartNoList.val();
var rightRowData = rightSelectedRows[0].getData();
var rightPartNo = rightRowData.PART_NO;
var rightPartName = rightRowData.PART_NAME;
var rightObjId = rightRowData.OBJID;
var rightRevision = rightRowData.REVISION || "";
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
}).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 == leftChildObjid) {
rows[i].update({
PART_OBJID: rightObjId,
PART_NO: rightPartNo,
PART_NAME: rightPartName,
REVISION: rightRevision
});
showConfirm({
title: '파트 변경',
text: '파트가 변경되었습니다.\n저장 버튼을 눌러 확정하세요.',
icon: 'success'
});
break;
}
}
}
}
});
}
// 임시 ID 생성 (음수 사용)
function generateTempId() {
return -Math.floor(Math.random() * 1000000000);
}
// 창 닫기 함수 (프레임 구조 고려)
function fn_closeWindow() {
try {
// 최상위 창(frameset)을 닫기
if(window.top && window.top.opener) {
// 팝업으로 열린 경우
window.top.close();
} else if(window.parent && window.parent !== window) {
// 프레임 내부인 경우 최상위 창 닫기 시도
window.top.close();
} else {
// 일반 창인 경우
window.close();
}
} catch(e) {
console.error("창 닫기 오류:", e);
// 오류 발생 시 강제로 닫기 시도
window.top.close();
}
}
</script>
</body>
</html>