Merge remote-tracking branch 'origin/main' into V2025111703

This commit is contained in:
leeheejin
2025-11-25 12:03:35 +09:00
12 changed files with 1811 additions and 467 deletions

View File

@@ -248,7 +248,7 @@ $(document).ready(function(){
grid = $("#expenseDetailGrid").jqGrid({
url: ""
,datatype: "local"
,colNames: ["상태","모품번","품번","품명","수량","항목수량","재료","열처리경도","열처리방법","표면처리","공급업체","범주이름"]
,colNames: ["상태","모품번","품번","품명","수량","항목수량","재료","열처리경도","열처리방법","표면처리","메이커","범주이름"]
,colModel: [
{name:"NOTE",index:"NOTE", width: 200, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
@@ -320,7 +320,7 @@ $(document).ready(function(){
}
}
}
// 공급업체 - MAKER 컬럼으로 변경, 일반 텍스트 입력 (2025-10-29)
// 메이커 - MAKER 컬럼으로 변경, 일반 텍스트 입력 (2025-10-29)
,{name:"MAKER",index:"MAKER", width: 120, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){

View File

@@ -134,30 +134,30 @@ var selectedBomType = null; // 'EBOM' 또는 'MBOM'
var bomGridData = []; // BOM 그리드 데이터
$(function(){
// 페이지 로드 시 URL 파라미터 또는 프로젝트 정보에서 품번/품명 자동 입력
var urlPartNo = "${param.partNo}";
var urlPartName = "${param.partName}";
var productCode = "${projectInfo.PRODUCT}";
// Select2 초기화
$('.select2').select2();
if(urlPartNo && urlPartNo !== "") {
console.log("URL 파라미터에서 품번/품명 설정 중...");
$("#COPY_PART_NO").val(decodeURIComponent(urlPartNo));
$("#COPY_PART_NAME").val(decodeURIComponent(urlPartName));
}
<c:if test="${not empty projectInfo}">
else {
console.log("projectInfo가 있습니다. 품번/품명 설정 중...");
$("#COPY_PART_NO").val("${projectInfo.PART_NO}");
$("#COPY_PART_NAME").val("${projectInfo.PART_NAME}");
}
// Machine이 아닌 경우, 동일 품번의 기존 M-BOM 확인 및 자동 로드
var isMachine = productCode && (productCode === '0001807' || productCode.toUpperCase().indexOf('MACHINE') >= 0);
if(!isMachine && "${projectInfo.PART_NO}" !== "") {
console.log("Machine이 아닌 제품입니다. 기존 M-BOM 확인 중...");
fn_checkExistingMbom("${projectInfo.PART_NO}");
}
</c:if>
// 페이지 로드 시 품번/품명 자동 입력
// 우선순위: urlParamInfo(POST 파라미터) > projectInfo(DB 조회)
<c:choose>
<c:when test="${not empty urlParamInfo}">
// POST 파라미터로 전달된 품번/품명 사용
$("#COPY_PART_NO").val("${urlParamInfo.PART_NO}");
$("#COPY_PART_NAME").val("${urlParamInfo.PART_NAME}");
</c:when>
<c:when test="${not empty projectInfo}">
// DB에서 조회한 프로젝트 정보 사용
$("#COPY_PART_NO").val("${projectInfo.PART_NO}");
$("#COPY_PART_NAME").val("${projectInfo.PART_NAME}");
// Machine이 아닌 경우, 동일 품번의 기존 M-BOM 확인 및 자동 로드
var productCode = "${projectInfo.PRODUCT}";
var isMachine = productCode && (productCode === '0001807' || productCode.toUpperCase().indexOf('MACHINE') >= 0);
if(!isMachine && "${projectInfo.PART_NO}" !== "") {
fn_checkExistingMbom("${projectInfo.PART_NO}");
}
</c:when>
</c:choose>
// 담기 버튼 - 선택한 BOM을 복사 대상으로 설정
$("#btnAddItem").click(function(){
@@ -187,28 +187,40 @@ $(function(){
fn_saveBomCopy();
});
// E-BOM 선택 버튼
$("#btnSelectEbom").click(function(){
var ebomPartNo = $("#EBOM_PART_NO").val().trim();
if(!ebomPartNo) {
Swal.fire('E-BOM 품번을 입력해주세요.');
return;
}
// E-BOM 조회 후 미리보기 로드
fn_loadBomPreview(ebomPartNo, 'EBOM');
// E-BOM 셀렉트박스 변경 이벤트
$("#EBOM_SELECT").change(function(){
var selectedObjId = $(this).val();
if(!selectedObjId) {
// 선택 해제 시 M-BOM 셀렉트박스 활성화 및 그리드 초기화
$("#MBOM_SELECT").prop("disabled", false);
_tabulGrid.clearData();
$("#bomPartName").text("");
return;
}
// E-BOM 선택 시 M-BOM 셀렉트박스 비활성화
$("#MBOM_SELECT").val("").prop("disabled", true);
// 선택된 E-BOM으로 미리보기 로드
fn_loadBomPreviewByObjId(selectedObjId, 'EBOM');
});
// M-BOM 선택 버튼
$("#btnSelectMbom").click(function(){
var mbomPartNo = $("#MBOM_PART_NO").val().trim();
if(!mbomPartNo) {
Swal.fire('M-BOM 품번을 입력해주세요.');
return;
}
// M-BOM 조회 후 미리보기 로드
fn_loadBomPreview(mbomPartNo, 'MBOM');
// M-BOM 셀렉트박스 변경 이벤트
$("#MBOM_SELECT").change(function(){
var selectedObjId = $(this).val();
if(!selectedObjId) {
// 선택 해제 시 E-BOM 셀렉트박스 활성화 및 그리드 초기화
$("#EBOM_SELECT").prop("disabled", false);
_tabulGrid.clearData();
$("#bomPartName").text("");
return;
}
// M-BOM 선택 시 E-BOM 셀렉트박스 비활성화
$("#EBOM_SELECT").val("").prop("disabled", true);
// 선택된 M-BOM으로 미리보기 로드
fn_loadBomPreviewByObjId(selectedObjId, 'MBOM');
});
// Excel 다운로드 버튼
@@ -269,7 +281,7 @@ function fn_checkExistingMbom(partNo) {
});
}
// BOM 미리보기 로드
// BOM 미리보기 로드 (품번으로)
function fn_loadBomPreview(partNo, bomType) {
console.log("fn_loadBomPreview 호출:", partNo, bomType);
@@ -301,6 +313,23 @@ function fn_loadBomPreview(partNo, bomType) {
});
}
// BOM 미리보기 로드 (OBJID로 직접)
function fn_loadBomPreviewByObjId(objId, bomType) {
console.log("fn_loadBomPreviewByObjId 호출:", objId, bomType);
// 전역 변수에 저장
selectedBomObjId = objId;
selectedBomType = bomType;
// 셀렉트박스에서 선택된 텍스트를 품명으로 표시
var selectBox = bomType === 'EBOM' ? $("#EBOM_SELECT") : $("#MBOM_SELECT");
var selectedText = selectBox.find("option:selected").text();
$("#bomPartName").text(selectedText);
// BOM 트리 데이터 로드
fn_loadBomTree(objId);
}
// BOM 트리 데이터 로드
function fn_loadBomTree(bomObjId) {
console.log("fn_loadBomTree 호출:", bomObjId);
@@ -375,6 +404,7 @@ function fn_initGrid(data, maxLevel) {
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
headerSort: false,
title: '선택',
field: 'RADIO',
formatter: function(cell) {
@@ -397,6 +427,7 @@ function fn_initGrid(data, maxLevel) {
levelColumns.push({
headerHozAlign: 'center',
hozAlign: 'center',
headerSort: false,
width: 30,
title: i,
field: 'LEVEL_' + i,
@@ -417,20 +448,23 @@ function fn_initGrid(data, maxLevel) {
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
headerSort: false,
width: 220,
title: '품번',
field: 'PART_NO'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
headerSort: false,
// width: 200,
title: '품명',
field: 'PART_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
headerSort: false,
width: 60,
title: '수량',
field: 'QTY_TEMP'
@@ -438,13 +472,15 @@ function fn_initGrid(data, maxLevel) {
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
headerSort: false,
width: 80,
title: '항목 수량',
field: 'ITEM_QTY'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
headerSort: false,
width: 60,
title: '3D',
field: 'CU01_CNT',
@@ -457,6 +493,7 @@ function fn_initGrid(data, maxLevel) {
{
headerHozAlign: 'center',
hozAlign: 'center',
headerSort: false,
width: 60,
title: '2D',
field: 'CU02_CNT',
@@ -468,7 +505,8 @@ function fn_initGrid(data, maxLevel) {
},
{
headerHozAlign: 'center',
hozAlign: 'center',
hozAlign: 'center',
headerSort: false,
width: 60,
title: 'PDF',
field: 'CU03_CNT',
@@ -481,16 +519,50 @@ function fn_initGrid(data, maxLevel) {
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
headerSort: false,
width: 130,
title: '재료',
field: 'MATERIAL'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
hozAlign: 'left',
headerSort: false,
width: 120,
title: '열처리경도',
field: 'HEAT_TREATMENT_HARDNESS'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
headerSort: false,
width: 130,
title: '열처리방법',
field: 'HEAT_TREATMENT_METHOD'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
headerSort: false,
width: 130,
title: '표면처리',
field: 'SURFACE_TREATMENT'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
headerSort: false,
width: 120,
title: '메이커',
field: 'MAKER'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
headerSort: false,
width: 100,
title: '범주이름',
field: 'CATEGORY_NAME'
}
);
@@ -515,13 +587,18 @@ function fn_initGrid(data, maxLevel) {
});
}
// BOM 복사 저장
// BOM 복사 저장 - PROJECT_MGMT에 할당 정보만 저장
function fn_saveBomCopy() {
var copyPartNo = $("#COPY_PART_NO").val().trim();
var copyPartName = $("#COPY_PART_NAME").val().trim();
var targetObjId = $("#TARGET_OBJID").val();
// 유효성 검사
if(!targetObjId) {
Swal.fire('프로젝트가 선택되지 않았습니다.');
return;
}
if(!copyPartNo || !copyPartName) {
Swal.fire('품번과 품명을 입력해주세요.');
return;
@@ -538,50 +615,35 @@ function fn_saveBomCopy() {
}
// 확인 메시지
var confirmMessage = targetObjId
? '선택한 ' + selectedBomType + '을(를) M-BOM으로 복사하시겠습니까?\n기존 M-BOM이 있다면 초기화됩니다.'
: '선택한 ' + selectedBomType + '을(를) 새로운 품번(' + copyPartNo + ')으로 복사하시겠습니까?';
var confirmMessage = '선택한 ' + selectedBomType + '을(를) M-BOM 기준으로 할당하시겠습니까?\n' +
'실제 M-BOM 생성은 M-BOM 상세 팝업에서 저장 시 이루어집니다.';
Swal.fire({
title: 'BOM 복사',
title: 'BOM 할당',
text: confirmMessage,
icon: 'question',
showCancelButton: true,
confirmButtonText: '복사',
confirmButtonText: '할당',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
// 저장 처리
// 할당 처리
Swal.fire({
title: '저장 중...',
text: 'BOM을 복사하고 있습니다.',
title: '할당 중...',
text: 'BOM 할당 정보를 저장하고 있습니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
// 제품구분 가져오기
var productCode = "${projectInfo.PRODUCT}";
// 기존 M-BOM 품번 가져오기 (M-BOM 선택 시 사용)
var existingMbomPartNo = "";
if(selectedBomType === 'MBOM') {
existingMbomPartNo = $("#MBOM_PART_NO").val().trim();
}
$.ajax({
url: "/partMng/saveBomCopy.do",
url: "/productionplanning/saveBomAssignment.do",
type: "POST",
data: JSON.stringify({
targetObjId: targetObjId, // M-BOM 관리에서 선택한 프로젝트 OBJID
sourceBomObjId: selectedBomObjId,
sourceBomType: selectedBomType,
targetPartNo: copyPartNo,
targetPartName: copyPartName,
productCode: productCode, // 제품구분 추가
existingMbomPartNo: existingMbomPartNo, // 기존 M-BOM 품번 추가
bomData: bomGridData
projectObjId: targetObjId,
sourceBomType: selectedBomType, // 'EBOM' or 'MBOM'
sourceBomObjId: selectedBomObjId
}),
contentType: "application/json",
dataType: "json",
@@ -589,30 +651,22 @@ function fn_saveBomCopy() {
Swal.close();
if(response && response.result === 'success') {
Swal.fire({
title: '저장 완료',
text: 'M-BOM이 성공적으로 생성되었습니다.',
title: '할당 완료',
text: 'BOM 할당 정보가 저장되었습니다.\nM-BOM 셀을 클릭하여 실제 M-BOM을 생성하세요.',
icon: 'success'
}).then(() => {
// 부모 창(M-BOM 관리)의 검색 함수 호출하여 그리드 업데이트
// 부모 창(M-BOM 관리)의 검색 함수 호출하여 그리드 업데이트
if(window.opener && !window.opener.closed) {
// 부모 창의 검색 조건 유지하면서 그리드만 새로고침
if(typeof window.opener.fn_search === 'function') {
window.opener.fn_search();
}
// M-BOM 품번과 저장일을 부모 창 검색 조건에 설정
// if(response.mbomPartNo) {
// window.opener.$("#search_mbom_part_no").val(response.mbomPartNo);
// }
// if(response.saveDate) {
// window.opener.$("#search_save_date").val(response.saveDate);
// }
}
window.close();
});
} else {
Swal.fire({
title: '저장 실패',
text: response.message || 'BOM 복사 중 오류가 발생했습니다.',
title: '할당 실패',
text: response.message || 'BOM 할당 중 오류가 발생했습니다.',
icon: 'error'
});
}
@@ -621,7 +675,7 @@ function fn_saveBomCopy() {
Swal.close();
console.error('Save error:', error);
Swal.fire({
title: '저장 실패',
title: '할당 실패',
text: '서버 오류가 발생했습니다: ' + error,
icon: 'error'
});
@@ -653,55 +707,55 @@ function fn_excel() {
<div class="top-input-section">
<table class="pmsPopuptable" style="margin-bottom: 10px;">
<colgroup>
<col width="10%">
<col width="25%">
<col width="10%">
<col width="25%">
<col width="30%">
<col width="10%">
<col width="20%">
<col width="10%">
<col width="20%">
<col width="30%">
<col width="10%">
</colgroup>
<tr>
<td class="input_title"><label for="COPY_PART_NO">품번</label></td>
<td>
<input type="text" id="COPY_PART_NO" name="COPY_PART_NO" style="width: 100%;">
</td>
</td>
<td class="input_title"><label for="COPY_PART_NAME">품명</label></td>
<td>
<input type="text" id="COPY_PART_NAME" name="COPY_PART_NAME" style="width: 100%;">
</td>
</td>
<td></td>
<td style="text-align: center;">
<input type="button" value="담기" class="plm_btns" id="btnAddItem">
<!-- <input type="button" value="담기" class="plm_btns" id="btnAddItem"> -->
<input type="button" class="plm_btns" value="닫기" onclick="window.close();" >
<input type="button" value="저장" class="plm_btns" id="btnSaveItem">
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="pmsPopuptable">
<colgroup>
<col width="10%">
<col width="25%">
<col width="10%">
<col width="25%">
<col width="30%">
<col width="90%">
</colgroup>
<tr>
<td class="input_title"><label for="EBOM_PART_NO">E-BOM 품번</label></td>
<td colspan="3">
<input type="text" id="EBOM_PART_NO" name="EBOM_PART_NO" style="width: 100%;">
</td>
<td style="text-align: center;">
<input type="button" value="E-BOM 선택" class="plm_btns" id="btnSelectEbom">
</td>
</tr>
<td class="input_title"><label for="EBOM_SELECT">E-BOM 선택</label></td>
<td>
<select id="EBOM_SELECT" name="EBOM_SELECT" style="width: 100%;" class="select2">
<option value="">선택</option>
${code_map.ebom_list}
</select>
</td>
</tr>
<tr>
<td class="input_title"><label for="MBOM_PART_NO">M-BOM 품번</label></td>
<td colspan="3">
<input type="text" id="MBOM_PART_NO" name="MBOM_PART_NO" style="width: 100%;">
</td>
<td style="text-align: center;">
<input type="button" value="M-BOM 선택" class="plm_btns" id="btnSelectMbom">
</td>
</tr>
</table>
<td class="input_title"><label for="MBOM_SELECT">M-BOM 선택</label></td>
<td>
<select id="MBOM_SELECT" name="MBOM_SELECT" style="width: 100%;" class="select2">
<option value="">선택</option>
${code_map.mbom_list}
</select>
</td>
</tr>
</table>
</div>
<!-- 중간: BOM 미리보기 그리드 -->

View File

@@ -278,7 +278,7 @@ function fn_initGrid() {
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '공급업체',
title: '메이커',
field: 'MAKER',
headerSort: false
},

View File

@@ -151,7 +151,7 @@ function fn_initRightGrid(){
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: 'MAKER',
title: '메이커',
field: 'MAKER'
}
];
@@ -301,7 +301,7 @@ function clearSelection() {
<input type="text" name="search_spec" id="search_spec" value="">
</td>
<td class="label"><label for="search_maker">MAKER</label></td>
<td class="label"><label for="search_maker">메이커</label></td>
<td>
<input type="text" name="search_maker" id="search_maker" value="">
</td>

View File

@@ -54,13 +54,47 @@ $(function(){
<c:if test="${not empty info}">
$("#search_part_no").val("${info.PART_NO}");
$("#search_part_name").val("${info.PART_NAME}");
$("#search_mbom_part_no").val("${info.MBOM_PART_NO}"); // M-BOM 품번 (자동 생성된 값)
$("#search_save_date").val("${info.MBOM_REGDATE}");
$("#search_quantity").val("${info.QUANTITY}"); // 프로젝트 수주수량
// 자동으로 M-BOM 조회
setTimeout(function() {
fn_searchMbom();
}, 500);
// 저장된 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);
$("#search_save_date").val(response.REGDATE);
} 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}";
console.log("할당된 BOM 정보:", {
sourceBomType: sourceBomType,
sourceEbomObjId: sourceEbomObjId,
sourceMbomObjId: sourceMbomObjId
});
// Controller에서 이미 데이터를 로드하여 JSP에 전달하므로 자동 조회 불필요
// setTimeout(function() {
// fn_searchMbom();
// }, 500);
</c:if>
//Part 연결
@@ -268,6 +302,11 @@ $(function(){
window.close();
});
// 일괄 적용 버튼 클릭
$("#btnApplyBulkDeadline").click(function(){
fn_applyBulkDeadline();
});
});
//1레벨에 같은 Part No가 등록되어있는지 확인.
@@ -461,15 +500,67 @@ function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPa
});
}
// 가공납기/연삭납기 일괄 적용
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 조회
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();
var bomReportObjId = "${info.BOM_REPORT_OBJID}"; // M-BOM의 BOM_REPORT_OBJID
// 할당된 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,
@@ -478,15 +569,20 @@ function fn_searchMbom() {
});
// 왼쪽 프레임의 조회 함수 호출
var leftFrame = parent.frames['leftFrame'];
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 함수를 찾을 수 없습니다.");
}
}
@@ -494,8 +590,16 @@ function fn_searchMbom() {
function fn_saveMbom() {
console.log("fn_saveMbom 호출됨");
// 왼쪽 프레임에서 M-BOM 트리 데이터 가져오기
var leftFrame = parent.frames['leftFrame'];
// 프레임 구조: 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) {
@@ -518,15 +622,35 @@ function fn_saveMbom() {
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: $("#search_mbom_part_no").val().trim(),
isUpdate: $("#search_mbom_part_no").val().trim() !== ""
mbomPartNo: existingMbom ? existingMbom.MBOM_NO : "", // 기존 M-BOM 품번 사용
isUpdate: existingMbom !== null // 기존 M-BOM이 있으면 업데이트
};
console.log("저장할 데이터:", saveData);
console.log("isUpdate:", saveData.isUpdate);
// 저장 API 호출
$.ajax({
@@ -540,13 +664,13 @@ function fn_saveMbom() {
if(data && data.result === "success") {
alert("M-BOM이 저장되었습니다.");
// 조회 새로고침
fn_searchMbom();
// 부모 창 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
// 현재 창 닫기
window.close();
} else {
alert("M-BOM 저장에 실패했습니다: " + (data.message || ""));
}
@@ -561,50 +685,45 @@ function fn_saveMbom() {
// M-BOM 이력보기
function fn_showHistory() {
var mbomPartNo = $("#search_mbom_part_no").val().trim();
var projectObjId = "${info.OBJID}";
if(!mbomPartNo) {
alert("M-BOM 품번이 없습니다. 먼저 M-BOM을 저장해주세요.");
if(!projectObjId) {
alert("프로젝트 정보가 없습니다.");
return;
}
// M-BOM 이력 조회
// M-BOM 이력 조회 (프로젝트 기준)
$.ajax({
url: "/productionplanning/getMbomHistory.do",
method: 'post',
data: {
mbomPartNo: mbomPartNo
projectObjId: projectObjId
},
dataType: 'json',
success: function(data) {
if(data && data.historyList && data.historyList.length > 0) {
// 이력 팝업 표시
var historyHtml = "<table border='1' style='width:100%; border-collapse:collapse;'>";
historyHtml += "<thead><tr style='background:#f0f0f0;'>";
historyHtml += "<th style='padding:8px;'>순번</th>";
historyHtml += "<th style='padding:8px;'>M-BOM 품번</th>";
historyHtml += "<th style='padding:8px;'>저장일</th>";
historyHtml += "<th style='padding:8px;'>수정자</th>";
historyHtml += "<th style='padding:8px;'>비고</th>";
historyHtml += "</tr></thead><tbody>";
for(var i = 0; i < data.historyList.length; i++) {
var item = data.historyList[i];
historyHtml += "<tr>";
historyHtml += "<td style='padding:5px; text-align:center;'>" + (i + 1) + "</td>";
historyHtml += "<td style='padding:5px;'>" + (item.MBOM_PART_NO || "") + "</td>";
historyHtml += "<td style='padding:5px; text-align:center;'>" + (item.REGDATE || "") + "</td>";
historyHtml += "<td style='padding:5px; text-align:center;'>" + (item.REGUSER || "") + "</td>";
historyHtml += "<td style='padding:5px;'>" + (item.REMARK || "") + "</td>";
historyHtml += "</tr>";
}
historyHtml += "</tbody></table>";
// 이력 상세 분석 및 HTML 생성
var historyHtml = generateHistoryHtml(data.historyList);
// 새 창으로 이력 표시
var historyWindow = window.open("", "mbomHistory", "width=800,height=600,scrollbars=yes,resizable=yes");
historyWindow.document.write("<html><head><title>M-BOM 이력</title></head><body>");
historyWindow.document.write("<h2 style='text-align:center;'>M-BOM 수정 이력</h2>");
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();
@@ -618,11 +737,419 @@ function fn_showHistory() {
}
});
}
// 이력 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>
@@ -654,6 +1181,21 @@ function fn_showHistory() {
<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>
<input type="button" value="일괄 적용" class="plm_btns" id="btnApplyBulkDeadline">
</td>
</tr>
</table>
</div>
</form>

View File

@@ -207,24 +207,9 @@ var columns = [
cellClick: function(e, cell) {
var rowData = cell.getData();
var objid = fnc_checkNull(rowData.OBJID);
var mbomStatus = fnc_checkNull(rowData.MBOM_STATUS);
// 파란색(저장된 M-BOM)일 때만 팝업 열기
if(mbomStatus !== '' && mbomStatus !== '0') {
// 검색 조건에 해당 행의 데이터 자동 입력 (주석처리)
// $("#search_part_no").val(fnc_checkNull(rowData.PART_NO));
// $("#search_part_name").val(fnc_checkNull(rowData.PART_NAME));
// $("#search_mbom_part_no").val(fnc_checkNull(rowData.MBOM_PART_NO)); // M-BOM 품번 (자동 생성된 값)
// $("#search_save_date").val(fnc_checkNull(rowData.MBOM_REGDATE));
fn_openMBomFormPopup(objid);
} else {
Swal.fire({
title: '알림',
text: 'M-BOM이 생성되지 않았습니다.\nBOM 복사 버튼을 통해 먼저 M-BOM을 생성해주세요.',
icon: 'info'
});
}
// 할당 정보 확인 후 M-BOM 팝업 열기
fn_checkAssignmentAndOpenMbom(objid);
}
},
@@ -365,19 +350,72 @@ function fn_openBomCopyPopupWindow(objId) {
var selectedRow = _tabulGrid.searchRows("OBJID", "=", objId);
if(selectedRow.length > 0) {
var rowData = selectedRow[0].getData();
var partNo = encodeURIComponent(fnc_checkNull(rowData.PART_NO));
var partName = encodeURIComponent(fnc_checkNull(rowData.PART_NAME));
var partNo = fnc_checkNull(rowData.PART_NO);
var partName = fnc_checkNull(rowData.PART_NAME);
// hiddenForm을 사용하여 POST 방식으로 팝업 열기 (한글 인코딩 문제 해결)
var hiddenForm = document.hiddenForm;
var url = "/partMng/structureBomCopyFormPopup.do";
var target = "bomCopyPopup";
hiddenForm.objId.value = objId;
hiddenForm.partNo.value = partNo;
hiddenForm.partName.value = partName;
var popup_width = 1800;
var popup_height = 900;
var url = "/partMng/structureBomCopyFormPopup.do?objId=" + objId + "&partNo=" + partNo + "&partName=" + partName;
fn_centerPopup(popup_width, popup_height, url, 'bomCopyPopup');
window.open('', target, 'width=' + popup_width + ', height=' + popup_height + ', resizable=yes');
hiddenForm.action = url;
hiddenForm.target = target;
hiddenForm.submit();
} else {
Swal.fire('선택된 데이터를 찾을 수 없습니다.');
}
}
// 할당 정보 확인 후 M-BOM 팝업 열기
function fn_checkAssignmentAndOpenMbom(projectObjId) {
// PROJECT_MGMT의 BOM 할당 정보 조회
$.ajax({
url: "/productionplanning/getMbomAssignmentInfo.do",
type: "POST",
data: { projectObjId: projectObjId },
dataType: "json",
success: function(response) {
console.log("BOM 할당 정보:", response);
if(!response || !response.SOURCE_BOM_TYPE) {
// 할당 정보가 없는 경우
Swal.fire({
title: '알림',
text: 'BOM 할당 정보가 없습니다.\nBOM 복사 버튼을 통해 먼저 BOM을 할당해주세요.',
icon: 'info'
});
return;
}
// 할당된 BOM 정보가 있는 경우 기존 M-BOM 팝업 열기
fn_openMBomFormPopup(projectObjId);
},
error: function(xhr, status, error) {
console.error("BOM 할당 정보 조회 오류:", error);
Swal.fire({
title: '오류',
text: 'BOM 할당 정보를 조회하는 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
</script>
<!-- hiddenForm: POST 방식 팝업 전송용 -->
<form name="hiddenForm" id="hiddenForm" method="post">
<input type="hidden" name="objId" id="objId">
<input type="hidden" name="partNo" id="partNo">
<input type="hidden" name="partName" id="partName">
</form>
<form name="form1" id="form1" method="post">
<input type="hidden" name="actionType" id="actionType">
<div class="content-box">
@@ -388,6 +426,7 @@ function fn_openBomCopyPopupWindow(objId) {
</h2>
<div class="btnArea">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
<input type="button" class="plm_btns" value="구매리스트 생성" id="btnCreatePurchaseList">
<input type="button" class="plm_btns" value="BOM 복사" id="btnBomCopy">
</div>
</div>

View File

@@ -6,9 +6,18 @@ if(map == null || map.isEmpty()) {
return;
}
String objId = com.pms.common.utils.CommonUtils.checkNull(map.get("OBJID"));
String quantity = com.pms.common.utils.CommonUtils.checkNull(map.get("QUANTITY"));
%>
<html>
<head>
<script>
// 프로젝트 수주수량을 전역 변수로 설정 (하위 프레임에서 접근 가능)
var PROJECT_QUANTITY = <%=quantity.isEmpty() ? "1" : quantity%>;
</script>
</head>
<frameset rows="100px, *" border="0" noresize>
<frame src="/productionplanning/mBomHeaderPopup.do?objId=<%=objId%>">
<frame src="/productionplanning/mBomBottomPopupFS.do?objId=<%=objId%>">
</frameset><noframes></noframes>
</html>

View File

@@ -42,8 +42,22 @@ body {
</style>
<script>
var _tabulGrid;
// 프로젝트 수주수량 (최상위 프레임에서 가져오기)
var projectQuantity = 1; // 기본값
$(function(){
// 최상위 프레임(mBomPopupHeaderFs.jsp)에서 프로젝트 수주수량 가져오기
try {
if(parent && parent.parent && parent.parent.PROJECT_QUANTITY) {
projectQuantity = parseFloat(parent.parent.PROJECT_QUANTITY) || 1;
console.log("프로젝트 수주수량:", projectQuantity);
} else {
console.log("PROJECT_QUANTITY를 찾을 수 없습니다. 기본값 1 사용");
}
} catch(e) {
console.log("프로젝트 수주수량 가져오기 실패:", e);
}
// Tabulator 초기화
fn_initGrid();
});
@@ -59,10 +73,10 @@ function fn_initGrid() {
columns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
width: 40,
title: '선택',
field: 'RADIO',
headerSort: false,
frozen: true,
formatter: function(cell) {
var rowData = cell.getData();
return '<input type="radio" name="checkedPartNo" value="' + (rowData.CHILD_OBJID || '') + '" ' +
@@ -85,7 +99,7 @@ function fn_initGrid() {
levelColumns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 25,
width: 10,
title: i,
field: 'LEVEL_' + i,
formatter: function(cell) {
@@ -97,6 +111,7 @@ function fn_initGrid() {
columns.push({
title: '수준',
headerHozAlign: 'center',
frozen: true,
columns: levelColumns
});
@@ -107,14 +122,16 @@ function fn_initGrid() {
hozAlign: 'left',
widthGrow: 2,
title: '품번',
field: 'PART_NO'
field: 'PART_NO',
frozen: true
},
{
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 3,
title: '품명',
field: 'PART_NAME'
field: 'PART_NAME',
frozen: true
},
{
headerHozAlign: 'center',
@@ -122,7 +139,7 @@ function fn_initGrid() {
width: 60,
title: '수량',
field: 'QTY_TEMP',
visible: false
visible: true
},
{
headerHozAlign: 'center',
@@ -130,7 +147,7 @@ function fn_initGrid() {
width: 80,
title: '항목 수량',
field: 'ITEM_QTY',
visible: false
visible: true
},
/* 주석처리: Rev, 규격, 제품구분, 상태 컬럼
{
@@ -168,7 +185,7 @@ function fn_initGrid() {
width: 60,
title: '3D',
field: 'CU01_CNT',
visible: false,
visible: true,
formatter: function(cell) {
var value = cell.getValue();
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
@@ -180,7 +197,7 @@ function fn_initGrid() {
width: 60,
title: '2D',
field: 'CU02_CNT',
visible: false,
visible: true,
formatter: function(cell) {
var value = cell.getValue();
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
@@ -192,7 +209,7 @@ function fn_initGrid() {
width: 60,
title: 'PDF',
field: 'CU03_CNT',
visible: false,
visible: true,
formatter: function(cell) {
var value = cell.getValue();
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
@@ -204,7 +221,7 @@ function fn_initGrid() {
width: 100,
title: '재료',
field: 'MATERIAL',
visible: false
visible: true
},
{
headerHozAlign: 'center',
@@ -212,7 +229,7 @@ function fn_initGrid() {
width: 120,
title: '열처리경도',
field: 'HEAT_TREATMENT_HARDNESS',
visible: false
visible: true
},
{
headerHozAlign: 'center',
@@ -220,7 +237,7 @@ function fn_initGrid() {
width: 120,
title: '열처리방법',
field: 'HEAT_TREATMENT_METHOD',
visible: false
visible: true
},
{
headerHozAlign: 'center',
@@ -228,23 +245,23 @@ function fn_initGrid() {
width: 100,
title: '표면처리',
field: 'SURFACE_TREATMENT',
visible: false
visible: true
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '공급업체',
field: 'SUPPLIER',
visible: false
title: '메이커',
field: 'MAKER',
visible: true
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '범주이름',
field: 'CATEGORY_NAME',
visible: false
field: 'PART_TYPE_TITLE',
visible: true
}
);
@@ -257,11 +274,32 @@ function fn_initGrid() {
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '급/사급',
title: '급/사급',
field: 'SUPPLY_TYPE',
editor: 'list',
editorParams: {
values: ['급', '사급', '자급']
values: ['급', '사급']
},
cellEdited: function(cell) {
var row = cell.getRow();
var data = row.getData();
// 자급 선택 시 소재 관련 필드 초기화
if(data.SUPPLY_TYPE === '자급') {
row.update({
RAW_MATERIAL: '-',
SIZE: '-',
RAW_MATERIAL_NO: '-',
REQUIRED_QTY: '-'
});
} else {
// 사급 선택 시 초기화
row.update({
RAW_MATERIAL: '',
SIZE: '',
RAW_MATERIAL_NO: '',
REQUIRED_QTY: ''
});
}
}
},
{
@@ -270,10 +308,17 @@ function fn_initGrid() {
width: 100,
title: '소재',
field: 'RAW_MATERIAL',
editor: 'input',
editor: 'list',
editorParams: {
values: ['SM45C', 'STS304', 'STS316', 'AL6061', 'AL7075'] // TODO: 실제 소재 목록으로 교체
},
editable: function(cell) {
// 자급인 경우 입력 불가
return cell.getRow().getData().SUPPLY_TYPE !== '자급';
return cell.getRow().getData().SUPPLY_TYPE === '사급';
},
formatter: function(cell) {
var data = cell.getRow().getData();
if(data.SUPPLY_TYPE === '자급') return '-';
return cell.getValue() || '';
}
},
{
@@ -282,50 +327,89 @@ function fn_initGrid() {
width: 100,
title: '사이즈',
field: 'SIZE',
editor: false,
editor: 'list',
editorParams: {
values: ['Φ10', 'Φ20', 'Φ30', '10x10', '20x20'] // TODO: 실제 사이즈 목록으로 교체
},
editable: function(cell) {
return cell.getRow().getData().SUPPLY_TYPE === '사급';
},
formatter: function(cell) {
// 항목수량 × 수주수량 (수정 불가)
var data = cell.getRow().getData();
var itemQty = data.ITEM_QTY || 0;
var orderQty = data.ORDER_QTY || 0;
return itemQty * orderQty;
if(data.SUPPLY_TYPE === '자급') return '-';
return cell.getValue() || '';
},
cellEdited: function(cell) {
// 소재, 사이즈 선택 시 소재품번 자동 생성
var row = cell.getRow();
var data = row.getData();
if(data.RAW_MATERIAL && data.SIZE) {
var materialNo = data.RAW_MATERIAL + '-' + data.SIZE;
row.update({RAW_MATERIAL_NO: materialNo});
}
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '소요량',
field: 'REQUIRED_QTY',
editor: false
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '발주수량',
field: 'ORDER_QTY',
width: 120,
title: '소재품번',
field: 'RAW_MATERIAL_NO',
editor: false,
formatter: function(cell) {
// 항목수량 (수정 불가)
return cell.getRow().getData().ITEM_QTY || 0;
var data = cell.getRow().getData();
if(data.SUPPLY_TYPE === '자급') return '-';
return cell.getValue() || '';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: "Q'ty",
field: 'QTY',
hozAlign: 'right',
width: 100,
title: '소재소요량',
field: 'REQUIRED_QTY',
editor: 'number',
editorParams: {
min: 0,
step: 1
step: 0.01 // 소수 가능
},
editable: function(cell) {
return cell.getRow().getData().SUPPLY_TYPE === '사급';
},
formatter: function(cell) {
var data = cell.getRow().getData();
if(data.SUPPLY_TYPE === '자급') return '-';
var value = cell.getValue();
return value ? Number(value).toLocaleString() : '0';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
hozAlign: 'right',
width: 120,
title: '소재발주수량',
field: 'ORDER_QTY',
editor: false,
formatter: function(cell) {
// 항목수량 × 프로젝트 수주수량 (수정 불가)
var data = cell.getRow().getData();
var itemQty = parseFloat(data.ITEM_QTY) || 0;
var orderQty = itemQty * projectQuantity;
return orderQty.toLocaleString();
}
},
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 80,
title: '항목수량',
field: 'ITEM_QTY',
editor: false,
visible: true
},
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '제작수량',
field: 'PRODUCTION_QTY',
@@ -333,6 +417,16 @@ function fn_initGrid() {
editorParams: {
min: 0,
step: 1
},
formatter: function(cell) {
// 초기값은 소재발주수량 (항목수량 × 프로젝트 수주수량)
var value = cell.getValue();
if(!value) {
var data = cell.getRow().getData();
var itemQty = parseFloat(data.ITEM_QTY) || 0;
value = itemQty * projectQuantity;
}
return Number(value).toLocaleString();
}
},
{
@@ -341,7 +435,10 @@ function fn_initGrid() {
width: 150,
title: '가공업체',
field: 'PROCESSING_VENDOR',
editor: 'input'
editor: 'list',
editorParams: {
values: ['업체A', '업체B', '업체C'] // TODO: 실제 가공업체 목록으로 교체
}
},
{
headerHozAlign: 'center',
@@ -358,50 +455,6 @@ function fn_initGrid() {
title: '연삭납기',
field: 'GRINDING_DEADLINE',
editor: 'date'
}
]
});
// 구매 컬럼 그룹 (제작수량 기준)
columns.push({
title: '구매',
headerHozAlign: 'center',
columns: [
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '소재품번',
field: 'MATERIAL_PART_NO',
editor: 'input'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '정미수량',
field: 'NET_QTY',
editor: false,
formatter: function(cell) {
// 소요량 × 제작수량 (소수점 무조건 올림)
var data = cell.getRow().getData();
var requiredQty = parseFloat(data.REQUIRED_QTY) || 0;
var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
var netQty = requiredQty * productionQty;
return Math.ceil(netQty); // 무조건 올림
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '발주수량',
field: 'PO_QTY',
editor: 'number',
editorParams: {
min: 0,
step: 1
}
},
{
headerHozAlign: 'center',
@@ -409,7 +462,10 @@ function fn_initGrid() {
width: 150,
title: '공급업체',
field: 'VENDOR',
editor: 'input'
editor: false, // 구매쪽에서 입력
formatter: function(cell) {
return cell.getValue() || '-';
}
},
{
headerHozAlign: 'center',
@@ -417,40 +473,141 @@ function fn_initGrid() {
width: 100,
title: '단가',
field: 'UNIT_PRICE',
editor: 'number',
editorParams: {
min: 0,
step: 0.01
},
editor: false, // 구매쪽에서 입력
formatter: function(cell) {
var value = cell.getValue();
return value ? Number(value).toLocaleString() : '0';
return value ? Number(value).toLocaleString() : '-';
}
},
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '총단가',
title: '금액',
field: 'TOTAL_PRICE',
editor: false,
formatter: function(cell) {
// 발주수량 × 단가
// 항목수량 × 단가
var data = cell.getRow().getData();
var poQty = parseFloat(data.PO_QTY) || 0;
var itemQty = parseFloat(data.ITEM_QTY) || 0;
var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
var totalPrice = poQty * unitPrice;
return totalPrice.toLocaleString();
var totalPrice = itemQty * unitPrice;
return totalPrice > 0 ? totalPrice.toLocaleString() : '-';
}
}
]
});
// 구매 컬럼 그룹 (제작수량 기준)
// columns.push({
// title: '구매',
// headerHozAlign: 'center',
// columns: [
// {
// headerHozAlign: 'center',
// hozAlign: 'left',
// width: 150,
// title: '소재품번',
// field: 'MATERIAL_PART_NO',
// editor: 'input'
// },
// {
// headerHozAlign: 'center',
// hozAlign: 'center',
// width: 100,
// title: '정미수량',
// field: 'NET_QTY',
// editor: false,
// formatter: function(cell) {
// // 소요량 × 제작수량 (소수점 무조건 올림)
// var data = cell.getRow().getData();
// var requiredQty = parseFloat(data.REQUIRED_QTY) || 0;
// var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
// var netQty = requiredQty * productionQty;
// return Math.ceil(netQty); // 무조건 올림
// }
// },
// {
// headerHozAlign: 'center',
// hozAlign: 'center',
// width: 100,
// title: '발주수량',
// field: 'PO_QTY',
// editor: 'number',
// editorParams: {
// min: 0,
// step: 1
// }
// },
// {
// headerHozAlign: 'center',
// hozAlign: 'left',
// width: 150,
// title: '공급업체',
// field: 'VENDOR',
// editor: 'input'
// },
// {
// headerHozAlign: 'center',
// hozAlign: 'right',
// width: 100,
// title: '단가',
// field: 'UNIT_PRICE',
// editor: 'number',
// editorParams: {
// min: 0,
// step: 0.01
// },
// formatter: function(cell) {
// var value = cell.getValue();
// return value ? Number(value).toLocaleString() : '0';
// }
// },
// {
// headerHozAlign: 'center',
// hozAlign: 'right',
// width: 100,
// title: '총단가',
// field: 'TOTAL_PRICE',
// editor: false,
// formatter: function(cell) {
// // 발주수량 × 단가
// var data = cell.getRow().getData();
// var poQty = parseFloat(data.PO_QTY) || 0;
// var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
// var totalPrice = poQty * unitPrice;
// return totalPrice.toLocaleString();
// }
// }
// ]
// });
// 서버에서 전달받은 데이터 확인
var bomTreeData = ${bomTreeListJson};
console.log("bomTreeData:", bomTreeData);
console.log("bomTreeData length:", bomTreeData ? bomTreeData.length : 0);
// 데이터 전처리: SUPPLY_TYPE 기본값 설정
if(bomTreeData && bomTreeData.length > 0) {
bomTreeData.forEach(function(row) {
// SUPPLY_TYPE이 없으면 '사급'으로 설정
if(!row.SUPPLY_TYPE) {
row.SUPPLY_TYPE = '사급';
}
});
}
// 모든 컬럼에 headerSort: false 일괄 적용
columns.forEach(function(col) {
col.headerSort = false;
// 중첩된 컬럼이 있는 경우 (수준 컬럼 등)
if(col.columns) {
col.columns.forEach(function(subCol) {
subCol.headerSort = false;
});
}
});
// Tabulator 생성
_tabulGrid = new Tabulator("#mBomTableWrap", {
layout: "fitData",
@@ -559,22 +716,102 @@ function fn_searchMbom(searchParams) {
});
}
// 가공납기/연삭납기 일괄 적용
function applyBulkDeadline(processingDeadline, grindingDeadline) {
if(!_tabulGrid) {
console.error("그리드가 초기화되지 않았습니다.");
return 0;
}
console.log("일괄 적용 시작");
console.log("가공납기:", processingDeadline);
console.log("연삭납기:", grindingDeadline);
var allRows = _tabulGrid.getRows();
console.log("전체 행 수:", allRows.length);
var updatedCount = 0;
allRows.forEach(function(row) {
var updateData = {};
if(processingDeadline) {
updateData.PROCESSING_DEADLINE = processingDeadline;
}
if(grindingDeadline) {
updateData.GRINDING_DEADLINE = grindingDeadline;
}
// 행 직접 업데이트
row.update(updateData);
updatedCount++;
});
console.log("업데이트 완료 - 업데이트된 행:", updatedCount);
return updatedCount;
}
// M-BOM 트리 데이터 수집 (저장용)
function getMbomTreeData() {
var allData = _tabulGrid.getData();
// 데이터 구조 변환 (필요한 필드만 추출)
// 숫자 변환 헬퍼 함수
function toNumber(value) {
if (value === null || value === undefined || value === '') return null;
var num = parseFloat(value);
return isNaN(num) ? null : num;
}
// 데이터 구조 변환 (MBOM_DETAIL 테이블에 필요한 모든 필드 포함)
var mbomData = allData.map(function(row) {
return {
PART_NO: row.PART_NO,
PART_NAME: row.PART_NAME,
QTY: row.QTY_TEMP || row.ITEM_QTY,
LEVEL: row.LEVEL,
REVISION: row.REVISION,
SPEC: row.SPEC,
PRODUCT_NAME: row.PRODUCT_NAME,
STATUS_NAME: row.STATUS_NAME,
OBJID: row.OBJID
// BOM 구조 정보
parentObjid: row.PARENT_OBJID,
childObjid: row.CHILD_OBJID,
seq: row.SEQ,
level: row.LEVEL,
// 품목 정보
partObjid: row.PART_OBJID || row.LAST_PART_OBJID,
partNo: row.PART_NO,
partName: row.PART_NAME,
// 수량 정보 (숫자로 변환)
qty: toNumber(row.QTY_TEMP || row.QTY || row.ITEM_QTY),
unit: row.UNIT,
// 생산 정보
supplyType: row.SUPPLY_TYPE,
makeOrBuy: row.MAKE_OR_BUY,
rawMaterial: row.RAW_MATERIAL,
rawMaterialSpec: row.RAW_MATERIAL_SPEC,
rawMaterialSize: row.SIZE,
rawMaterialPartNo: row.RAW_MATERIAL_NO,
processingVendor: row.PROCESSING_VENDOR,
processingDeadline: row.PROCESSING_DEADLINE,
grindingDeadline: row.GRINDING_DEADLINE,
requiredQty: toNumber(row.REQUIRED_QTY),
orderQty: toNumber(row.ORDER_QTY),
productionQty: toNumber(row.PRODUCTION_QTY),
stockQty: toNumber(row.STOCK_QTY),
shortageQty: toNumber(row.SHORTAGE_QTY),
// 구매 정보
vendor: row.VENDOR,
unitPrice: toNumber(row.UNIT_PRICE),
totalPrice: toNumber(row.TOTAL_PRICE),
currency: row.CURRENCY,
leadTime: toNumber(row.LEAD_TIME),
minOrderQty: toNumber(row.MIN_ORDER_QTY),
// 기타
status: row.STATUS,
remark: row.REMARK,
revision: row.REVISION,
spec: row.SPEC,
writer: row.WRITER,
editer: row.EDITER,
objid: row.OBJID
};
});

View File

@@ -898,12 +898,26 @@ public class PartMngController {
public String structureBomCopyFormPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
try {
String objId = CommonUtils.checkNull((String)paramMap.get("objId"));
String partNo = CommonUtils.checkNull((String)paramMap.get("partNo"));
String partName = CommonUtils.checkNull((String)paramMap.get("partName"));
System.out.println("========== structureBomCopyFormPopup ==========");
System.out.println("objId: " + objId);
System.out.println("partNo: " + partNo);
System.out.println("partName: " + partName);
// objId를 항상 설정 (음수 포함)
request.setAttribute("TARGET_OBJID", objId);
// URL 파라미터로 전달된 품번/품명이 있으면 우선 사용
if(!"".equals(partNo) && !"".equals(partName)) {
Map<String, Object> urlParamInfo = new HashMap<>();
urlParamInfo.put("PART_NO", partNo);
urlParamInfo.put("PART_NAME", partName);
request.setAttribute("urlParamInfo", urlParamInfo);
System.out.println("URL 파라미터 사용: " + partNo + " / " + partName);
}
// objId가 유효한 경우 프로젝트 정보 조회 (음수도 유효한 OBJID임)
if(!"".equals(objId) && !"-1".equals(objId)) {
try {
@@ -929,6 +943,13 @@ public class PartMngController {
Map code_map = new HashMap();
code_map.put("rev", commonService.bizMakeOptionList("", "", "common.getRevNoselect"));
code_map.put("product_code", commonService.bizMakeOptionList("", "", "common.getProductNoselect"));
// E-BOM 목록 (기존 getActiveBomList 사용)
code_map.put("ebom_list", commonService.bizMakeOptionList("", "", "partMng.getActiveBomList"));
// M-BOM 목록 (bizMakeOptionList 사용)
code_map.put("mbom_list", commonService.bizMakeOptionList("", "", "productionplanning.getMbomListForSelect2"));
request.setAttribute("code_map", code_map);
} catch(Exception e) {

View File

@@ -1084,17 +1084,63 @@ public class ProductionPlanningController extends BaseService {
if(projectInfo != null) {
System.out.println("BOM_REPORT_OBJID: " + projectInfo.get("BOM_REPORT_OBJID"));
System.out.println("SOURCE_BOM_TYPE: " + projectInfo.get("SOURCE_BOM_TYPE"));
System.out.println("SOURCE_EBOM_OBJID: " + projectInfo.get("SOURCE_EBOM_OBJID"));
System.out.println("SOURCE_MBOM_OBJID: " + projectInfo.get("SOURCE_MBOM_OBJID"));
System.out.println("MBOM_STATUS: " + projectInfo.get("MBOM_STATUS"));
// 1. 먼저 저장된 M-BOM 데이터가 있는지 확인
String bomReportObjid = CommonUtils.checkNull(projectInfo.get("BOM_REPORT_OBJID"));
if(!"".equals(bomReportObjid)) {
Map<String, Object> bomParam = new HashMap<>();
bomParam.put("bomReportObjId", bomReportObjid);
bomParam.put("search_type", "working");
String bomReportObjid = "";
boolean isSavedMbom = false;
// 1. 먼저 저장된 M-BOM이 있는지 확인 (MBOM_HEADER 테이블)
Map<String, Object> savedMbomParam = new HashMap<>();
savedMbomParam.put("projectObjId", objId);
Map<String, Object> savedMbom = commonService.selectOne("productionplanning.getLatestMbomByProjectId", request, savedMbomParam);
if(savedMbom != null && savedMbom.get("OBJID") != null) {
// 저장된 M-BOM이 있으면 해당 M-BOM 사용
bomReportObjid = CommonUtils.checkNull(savedMbom.get("OBJID"));
isSavedMbom = true;
System.out.println("저장된 M-BOM 사용: " + bomReportObjid);
} else {
// 저장된 M-BOM이 없으면 할당된 BOM 정보 사용
String sourceBomType = CommonUtils.checkNull(projectInfo.get("SOURCE_BOM_TYPE"));
String sourceEbomObjId = CommonUtils.checkNull(projectInfo.get("SOURCE_EBOM_OBJID"));
String sourceMbomObjId = CommonUtils.checkNull(projectInfo.get("SOURCE_MBOM_OBJID"));
List<Map<String, Object>> mbomDetailList = commonService.selectList("partMng.getBOMTreeList", request, bomParam);
System.out.println("mbomDetailList size: " + (mbomDetailList != null ? mbomDetailList.size() : 0));
if("EBOM".equals(sourceBomType) && !"".equals(sourceEbomObjId)) {
bomReportObjid = sourceEbomObjId;
System.out.println("할당된 E-BOM 사용: " + bomReportObjid);
} else if("MBOM".equals(sourceBomType) && !"".equals(sourceMbomObjId)) {
bomReportObjid = sourceMbomObjId;
System.out.println("할당된 M-BOM 사용: " + bomReportObjid);
} else {
bomReportObjid = CommonUtils.checkNull(projectInfo.get("BOM_REPORT_OBJID"));
System.out.println("기존 BOM_REPORT_OBJID 사용: " + bomReportObjid);
}
}
// 2. BOM 데이터 조회
List<Map<String, Object>> mbomDetailList = null;
if(!"".equals(bomReportObjid)) {
if(isSavedMbom) {
// 저장된 M-BOM: MBOM_DETAIL 테이블에서 조회
Map<String, Object> mbomParam = new HashMap<>();
mbomParam.put("mbomHeaderObjid", bomReportObjid);
List tempList = commonService.selectList("productionplanning.getSavedMbomTreeList", request, mbomParam);
// MyBatis resultType="map"은 소문자로 반환하므로 대문자로 변환 필요
mbomDetailList = (List<Map<String, Object>>) (List<?>) CommonUtils.keyChangeUpperList(tempList);
System.out.println("저장된 M-BOM 조회 - mbomDetailList size: " + (mbomDetailList != null ? mbomDetailList.size() : 0));
} else {
// 할당된 E-BOM/M-BOM: BOM_PART_QTY 테이블에서 조회
Map<String, Object> bomParam = new HashMap<>();
bomParam.put("bomReportObjId", bomReportObjid);
bomParam.put("search_type", "working");
mbomDetailList = commonService.selectList("partMng.getBOMTreeList", request, bomParam);
System.out.println("할당된 BOM 조회 - mbomDetailList size: " + (mbomDetailList != null ? mbomDetailList.size() : 0));
}
}
if(mbomDetailList != null && !mbomDetailList.isEmpty()) {
// 저장된 M-BOM 데이터가 있으면 이를 표시
@@ -1121,8 +1167,6 @@ public class ProductionPlanningController extends BaseService {
request.setAttribute("bomTreeListJson", gson.toJson(mbomDetailList));
return "/productionplanning/mBomPopupLeft";
}
}
}
}
} catch(Exception e) {
@@ -1196,49 +1240,6 @@ public class ProductionPlanningController extends BaseService {
return result;
}
/**
* M-BOM 저장
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/saveMbom.do")
public Map<String, Object> saveMbom(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
// M-BOM 데이터 저장 로직
List<Map<String, Object>> mbomData = (List<Map<String, Object>>) paramMap.get("mbomData");
String partNo = CommonUtils.checkNull(paramMap.get("partNo"));
String partName = CommonUtils.checkNull(paramMap.get("partName"));
String mbomPartNo = CommonUtils.checkNull(paramMap.get("mbomPartNo")); // 기존 M-BOM 품번
boolean isUpdate = paramMap.get("isUpdate") != null && (Boolean)paramMap.get("isUpdate"); // 수정 여부
if(mbomData == null || mbomData.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "저장할 데이터가 없습니다.");
return resultMap;
}
// M-BOM 저장 서비스 호출
// isUpdate가 true이면 기존 M-BOM 품번 유지, false이면 새로 생성
int saveResult = productionPlanningService.saveMbom(request, mbomData, partNo, partName, mbomPartNo, isUpdate);
if(saveResult > 0) {
resultMap.put("result", "success");
resultMap.put("message", isUpdate ? "M-BOM이 수정되었습니다." : "M-BOM이 저장되었습니다.");
} else {
resultMap.put("result", "fail");
resultMap.put("message", "저장에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "fail");
resultMap.put("message", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 품번으로 최신 M-BOM 조회 (Machine 이외 제품용)
* @param request
@@ -1279,16 +1280,21 @@ public class ProductionPlanningController extends BaseService {
public Map<String, Object> getMbomHistory(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
String mbomPartNo = CommonUtils.checkNull(paramMap.get("mbomPartNo"));
String projectObjId = CommonUtils.checkNull(paramMap.get("projectObjId"));
if("".equals(mbomPartNo)) {
if("".equals(projectObjId)) {
resultMap.put("historyList", new ArrayList<>());
return resultMap;
}
// M-BOM 이력 조회 (PART_BOM_REPORT 테이블에서 동일 PART_NO로 조회)
System.out.println("========== getMbomHistory ==========");
System.out.println("projectObjId: " + projectObjId);
// M-BOM 이력 조회 (PROJECT_OBJID로 조회)
List<Map<String, Object>> historyList = commonService.selectList("productionplanning.getMbomHistory", request, paramMap);
System.out.println("조회된 이력 수: " + (historyList != null ? historyList.size() : 0));
resultMap.put("historyList", historyList != null ? historyList : new ArrayList<>());
} catch(Exception e) {
e.printStackTrace();
@@ -1357,4 +1363,170 @@ public class ProductionPlanningController extends BaseService {
}
return resultMap;
}
/**
* BOM 할당 정보 저장 (M-BOM 기준 설정)
* PROJECT_MGMT 테이블에 E-BOM 또는 M-BOM 할당 정보만 저장
* 실제 M-BOM 생성은 M-BOM 상세 팝업에서 수행
*/
@RequestMapping("/productionplanning/saveBomAssignment.do")
@ResponseBody
public Map<String, Object> saveBomAssignment(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
System.out.println("========== saveBomAssignment ==========");
System.out.println("projectObjId: " + paramMap.get("projectObjId"));
System.out.println("sourceBomType: " + paramMap.get("sourceBomType"));
System.out.println("sourceBomObjId: " + paramMap.get("sourceBomObjId"));
System.out.println("partNo: " + paramMap.get("partNo"));
System.out.println("partName: " + paramMap.get("partName"));
// PROJECT_MGMT 테이블 업데이트
boolean updateResult = productionPlanningService.saveBomAssignment(request, paramMap);
if(updateResult) {
resultMap.put("result", "success");
resultMap.put("message", "M-BOM 기준 정보가 저장되었습니다.");
} else {
resultMap.put("result", "fail");
resultMap.put("message", "저장에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
@RequestMapping("/productionplanning/getMbomAssignmentInfo.do")
@ResponseBody
public Map<String, Object> getMbomAssignmentInfo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
String projectObjId = CommonUtils.checkNull((String)paramMap.get("projectObjId"));
System.out.println("========== getMbomAssignmentInfo ==========");
System.out.println("projectObjId: " + projectObjId);
// PROJECT_MGMT에서 BOM 할당 정보 조회
Map<String, Object> assignmentInfo = productionPlanningService.getMbomAssignment(projectObjId);
if(assignmentInfo != null && !assignmentInfo.isEmpty()) {
resultMap.putAll(assignmentInfo);
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("error", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 프로젝트별 최신 M-BOM 조회
*/
@RequestMapping("/productionplanning/getLatestMbomByProjectId.do")
@ResponseBody
public Map<String, Object> getLatestMbomByProjectId(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
String projectObjId = CommonUtils.checkNull((String)paramMap.get("projectObjId"));
paramMap.put("projectObjId", projectObjId);
// MBOM_HEADER에서 해당 프로젝트의 최신 M-BOM 조회
Map<String, Object> savedMbom = commonService.selectOne("productionplanning.getLatestMbomByProjectId", request, paramMap);
if(savedMbom != null && !savedMbom.isEmpty()) {
resultMap = (Map<String, Object>) CommonUtils.toUpperCaseMapKey(savedMbom);
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("error", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* M-BOM 저장
* 최초 저장 시: M-BOM 품번 생성 + 데이터 저장
* 수정 저장 시: 동일 품번으로 업데이트 + 이력 저장
*/
@RequestMapping("/productionplanning/saveMbom.do")
@ResponseBody
public Map<String, Object> saveMbom(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
System.out.println("========== saveMbom ==========");
String projectObjId = CommonUtils.checkNull((String)paramMap.get("projectObjId"));
String mbomPartNo = CommonUtils.checkNull((String)paramMap.get("mbomPartNo"));
boolean isUpdate = "true".equals(String.valueOf(paramMap.get("isUpdate")));
System.out.println("projectObjId: " + projectObjId);
System.out.println("mbomPartNo: " + mbomPartNo);
System.out.println("isUpdate: " + isUpdate);
// PROJECT_MGMT에서 할당 정보 조회
Map<String, Object> assignment = (Map<String, Object>) commonService.selectOne("productionplanning.getMbomAssignment", request, paramMap);
if(assignment == null || assignment.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "M-BOM 기준 정보가 없습니다. BOM 복사 팝업에서 먼저 기준을 설정해주세요.");
return resultMap;
}
String sourceBomType = CommonUtils.checkNull(assignment.get("SOURCE_BOM_TYPE"));
String sourceBomObjId = "";
String baseBomPartNo = "";
if("EBOM".equals(sourceBomType)) {
sourceBomObjId = CommonUtils.checkNull(assignment.get("SOURCE_EBOM_OBJID"));
baseBomPartNo = CommonUtils.checkNull(assignment.get("EBOM_PART_NO"));
} else if("MBOM".equals(sourceBomType)) {
sourceBomObjId = CommonUtils.checkNull(assignment.get("SOURCE_MBOM_OBJID"));
baseBomPartNo = CommonUtils.checkNull(assignment.get("MBOM_NO"));
}
if(sourceBomObjId.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "기준 BOM 정보가 올바르지 않습니다.");
return resultMap;
}
// M-BOM 품번 생성 또는 사용
String finalMbomNo = "";
if(!isUpdate || mbomPartNo.isEmpty()) {
// 최초 저장: M-BOM 품번 생성
finalMbomNo = productionPlanningService.generateMbomNo(request, sourceBomType, baseBomPartNo);
System.out.println("생성된 M-BOM 품번: " + finalMbomNo);
} else {
// 수정 저장: 기존 품번 사용
finalMbomNo = mbomPartNo;
System.out.println("기존 M-BOM 품번 사용: " + finalMbomNo);
}
paramMap.put("mbomNo", finalMbomNo);
paramMap.put("sourceBomType", sourceBomType);
paramMap.put("sourceBomObjId", sourceBomObjId);
paramMap.put("isUpdate", isUpdate);
// M-BOM 저장
boolean saveResult = productionPlanningService.saveMbom(request, paramMap);
if(saveResult) {
resultMap.put("result", "success");
resultMap.put("message", isUpdate ? "M-BOM이 수정되었습니다." : "M-BOM이 생성되었습니다.");
resultMap.put("mbomNo", finalMbomNo);
} else {
resultMap.put("result", "fail");
resultMap.put("message", "M-BOM 저장에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
}

View File

@@ -8099,4 +8099,18 @@ SELECT PM.OBJID
)
</update>
<!-- E-BOM 목록 조회 (셀렉트박스용) -->
<select id="getEbomList" parameterType="map" resultType="map">
SELECT
OBJID,
PART_NO,
PART_NAME,
REV,
SPEC_NAME,
REGDATE
FROM PART_BOM_REPORT
WHERE STATUS = 'Y'
ORDER BY REGDATE DESC, PART_NO
</select>
</mapper>

View File

@@ -2944,29 +2944,47 @@
LIMIT 1),
''
) AS EBOM_REGDATE,
COALESCE(PM.MBOM_STATUS, '') AS MBOM_STATUS,
-- M-BOM 상태: 새 MBOM_HEADER 테이블에서 조회
COALESCE(
(SELECT PBR.PART_NO
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.BOM_REPORT_OBJID
AND PM.MBOM_STATUS = 'Y'
LIMIT 1),
(SELECT
CASE
WHEN COUNT(*) > 0 THEN 'Y'
ELSE COALESCE(PM.MBOM_STATUS, '')
END
FROM MBOM_HEADER MH
WHERE MH.PROJECT_OBJID = PM.OBJID::VARCHAR
AND MH.STATUS = 'Y'
LIMIT 1),
COALESCE(PM.MBOM_STATUS, '')
) AS MBOM_STATUS,
-- M-BOM 품번: 새 MBOM_HEADER 테이블에서 조회
COALESCE(
(SELECT MH.MBOM_NO
FROM MBOM_HEADER MH
WHERE MH.PROJECT_OBJID = PM.OBJID::VARCHAR
AND MH.STATUS = 'Y'
ORDER BY MH.REGDATE DESC
LIMIT 1),
''
) AS MBOM_PART_NO,
'1.0' AS MBOM_VERSION,
-- M-BOM 저장일: 새 MBOM_HEADER 테이블에서 조회
COALESCE(
(SELECT TO_CHAR(PBR.REGDATE, 'YYYY-MM-DD')
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.BOM_REPORT_OBJID
AND PM.MBOM_STATUS = 'Y'
LIMIT 1),
(SELECT TO_CHAR(MH.REGDATE, 'YYYY-MM-DD')
FROM MBOM_HEADER MH
WHERE MH.PROJECT_OBJID = PM.OBJID::VARCHAR
AND MH.STATUS = 'Y'
ORDER BY MH.REGDATE DESC
LIMIT 1),
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD')
) AS MBOM_REGDATE
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID
AND PM.PART_OBJID = CI.PART_OBJID
-- CONTRACT_ITEM과 LEFT JOIN하여 품목별로 펼쳐서 보이기
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID
-- LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID
AND CI.STATUS = 'ACTIVE'
WHERE 1=1
AND PM.PROJECT_NO IS NOT NULL
@@ -3024,6 +3042,10 @@
PM.BOM_REPORT_OBJID,
PM.PART_NO,
PM.PART_NAME,
PM.SOURCE_BOM_TYPE,
PM.SOURCE_EBOM_OBJID,
PM.SOURCE_MBOM_OBJID,
PM.QUANTITY,
COALESCE(
(SELECT PBR.PART_NO
FROM PART_BOM_REPORT PBR
@@ -3268,21 +3290,6 @@
BPQ.SEQ
</select>
<!-- M-BOM 품번 최대 순번 조회 (같은 날짜) -->
<select id="getMaxMbomSeqByDate" parameterType="map" resultType="int">
SELECT
COALESCE(MAX(
CAST(
SUBSTRING(PART_NO FROM '.+-([0-9]+)$') AS INTEGER
)
), 0) AS MAX_SEQ
FROM
PART_BOM_REPORT
WHERE
PART_NO LIKE #{prefix} || '%'
AND STATUS = 'Y'
</select>
<!-- 품번으로 최신 M-BOM 조회 (Machine 이외 제품용) -->
<select id="getLatestMbomByPartNo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
@@ -3314,85 +3321,26 @@
<!-- M-BOM 이력 조회 -->
<select id="getMbomHistory" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PBR.OBJID,
PBR.PART_NO AS MBOM_PART_NO,
TO_CHAR(PBR.REGDATE, 'YYYY-MM-DD HH24:MI:SS') AS REGDATE,
PBR.REGUSER,
COALESCE(PBR.REMARK, '') AS REMARK,
PBR.STATUS
MH.OBJID,
MH.MBOM_HEADER_OBJID,
MH.CHANGE_TYPE,
MH.CHANGE_DESCRIPTION,
MH.BEFORE_DATA,
MH.AFTER_DATA,
MH.CHANGE_USER,
TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD HH24:MI:SS') AS CHANGE_DATE,
-- MBOM_HEADER 정보 조인
MHD.MBOM_NO AS MBOM_PART_NO,
MHD.PROJECT_OBJID,
TO_CHAR(MHD.REGDATE, 'YYYY-MM-DD HH24:MI:SS') AS REGDATE
FROM
PART_BOM_REPORT PBR
MBOM_HISTORY MH
INNER JOIN MBOM_HEADER MHD ON MH.MBOM_HEADER_OBJID = MHD.OBJID
WHERE
PBR.PART_NO = #{mbomPartNo}
ORDER BY PBR.REGDATE DESC
MHD.PROJECT_OBJID = #{projectObjId}
ORDER BY MH.CHANGE_DATE DESC
</select>
<!-- M-BOM 헤더 저장 -->
<insert id="insertMbomHeader" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_HEADER (
OBJID,
MBOM_NO,
PART_NO,
PART_NAME,
SAVE_DATE,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
CONCAT('MBOM-', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')),
#{PART_NO},
#{PART_NAME},
NOW(),
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- M-BOM 상세 저장 -->
<insert id="insertMbomDetail" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_DETAIL (
OBJID,
MBOM_HEADER_OBJID,
PART_NO,
PART_NAME,
QTY,
LEVEL,
REVISION,
SPEC,
PRODUCT_NAME,
STATUS_NAME,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
#{MBOM_HEADER_OBJID},
#{PART_NO},
#{PART_NAME},
#{QTY},
#{LEVEL},
#{REVISION},
#{SPEC},
#{PRODUCT_NAME},
#{STATUS_NAME},
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- E-BOM을 M-BOM으로 복사 -->
<insert id="saveMbomFromEbom" parameterType="map">
/* productionplanning.saveMbomFromEbom - E-BOM을 M-BOM으로 복사 */
@@ -3463,4 +3411,312 @@
PART_NAME = #{PART_NAME}
WHERE OBJID::VARCHAR = #{PROJECT_OBJID}
</insert>
<!-- M-BOM 관련 쿼리 -->
<!-- BOM 할당 정보 저장 (M-BOM 기준 설정) -->
<update id="saveBomAssignment" parameterType="map">
UPDATE PROJECT_MGMT
SET
SOURCE_BOM_TYPE = #{sourceBomType},
SOURCE_EBOM_OBJID = CASE WHEN #{sourceBomType} = 'EBOM' THEN #{sourceBomObjId} ELSE NULL END,
SOURCE_MBOM_OBJID = CASE WHEN #{sourceBomType} = 'MBOM' THEN #{sourceBomObjId} ELSE NULL END
WHERE OBJID::VARCHAR = #{projectObjId}
</update>
<!-- 프로젝트의 최신 저장된 M-BOM 조회 -->
<select id="getLatestMbomByProjectId" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
OBJID,
MBOM_NO,
SOURCE_BOM_TYPE,
SOURCE_EBOM_OBJID,
SOURCE_MBOM_OBJID,
PROJECT_OBJID,
STATUS,
REGDATE
FROM MBOM_HEADER
WHERE PROJECT_OBJID = #{projectObjId}
AND STATUS = 'Y'
ORDER BY REGDATE DESC
LIMIT 1
</select>
<!-- M-BOM 할당 정보 조회 -->
<select id="getMbomAssignment" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.SOURCE_BOM_TYPE,
PM.SOURCE_EBOM_OBJID,
PM.SOURCE_MBOM_OBJID,
PM.PART_NO,
PM.PART_NAME,
PM.PRODUCT,
CODE_NAME(PM.PRODUCT) AS PRODUCT_NAME,
-- E-BOM 정보
CASE WHEN PM.SOURCE_BOM_TYPE = 'EBOM' THEN
(SELECT PART_NO FROM PART_BOM_REPORT WHERE OBJID = PM.SOURCE_EBOM_OBJID)
END AS EBOM_PART_NO,
CASE WHEN PM.SOURCE_BOM_TYPE = 'EBOM' THEN
(SELECT PART_NAME FROM PART_BOM_REPORT WHERE OBJID = PM.SOURCE_EBOM_OBJID)
END AS EBOM_PART_NAME,
-- M-BOM 정보
CASE WHEN PM.SOURCE_BOM_TYPE = 'MBOM' THEN
(SELECT MBOM_NO FROM MBOM_HEADER WHERE OBJID = PM.SOURCE_MBOM_OBJID)
END AS MBOM_NO,
CASE WHEN PM.SOURCE_BOM_TYPE = 'MBOM' THEN
(SELECT PART_NAME FROM MBOM_HEADER WHERE OBJID = PM.SOURCE_MBOM_OBJID)
END AS MBOM_PART_NAME
FROM PROJECT_MGMT PM
WHERE PM.OBJID::VARCHAR = #{projectObjId}
</select>
<!-- M-BOM 품번 생성 - 같은 날짜의 최대 순번 조회 -->
<select id="getMaxMbomSeqByDate" parameterType="map" resultType="int">
SELECT COALESCE(MAX(
CAST(
SUBSTRING(
MBOM_NO FROM LENGTH(MBOM_NO) - 1 FOR 2
) AS INTEGER
)
), 0) AS MAX_SEQ
FROM MBOM_HEADER
WHERE MBOM_NO LIKE #{mbomPrefix} || '%'
AND REGDATE::DATE = CURRENT_DATE
</select>
<!-- M-BOM 품번으로 조회 -->
<select id="getMbomByNo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT * FROM MBOM_HEADER
WHERE PROJECT_OBJID = #{projectObjId}
ORDER BY REGDATE DESC
LIMIT 1
</select>
<!-- MBOM_HEADER 삽입 -->
<insert id="insertMbomHeader" parameterType="map">
INSERT INTO MBOM_HEADER (
OBJID, MBOM_NO, SOURCE_BOM_TYPE, SOURCE_EBOM_OBJID, SOURCE_MBOM_OBJID,
PROJECT_OBJID, PART_NO, PART_NAME, STATUS, MBOM_STATUS,
WRITER, REGDATE
) VALUES (
#{objid}, #{mbomNo}, #{sourceBomType}, #{sourceEbomObjid}, #{sourceMbomObjid},
#{projectObjId}, #{partNo}, #{partName}, 'Y', 'DRAFT',
#{sessionUserId}, NOW()
)
</insert>
<!-- MBOM_DETAIL 삽입 -->
<insert id="insertMbomDetail" parameterType="map">
INSERT INTO MBOM_DETAIL (
OBJID, MBOM_HEADER_OBJID, PARENT_OBJID, CHILD_OBJID, SEQ, LEVEL,
PART_OBJID, PART_NO, PART_NAME, QTY, UNIT,
SUPPLY_TYPE, MAKE_OR_BUY,
RAW_MATERIAL_PART_NO, RAW_MATERIAL_SPEC, RAW_MATERIAL, RAW_MATERIAL_SIZE,
PROCESSING_VENDOR, PROCESSING_DEADLINE, GRINDING_DEADLINE,
REQUIRED_QTY, ORDER_QTY, PRODUCTION_QTY, STOCK_QTY, SHORTAGE_QTY,
VENDOR, UNIT_PRICE, TOTAL_PRICE, CURRENCY, LEAD_TIME, MIN_ORDER_QTY,
STATUS, WRITER, REGDATE, REMARK
) VALUES (
#{objid}, #{mbomHeaderObjid}, #{parentObjid}, #{childObjid}, #{seq}, #{level},
#{partObjid}, #{partNo}, #{partName}, #{qty}, #{unit},
#{supplyType}, #{makeOrBuy},
#{rawMaterialPartNo}, #{rawMaterialSpec}, #{rawMaterial}, #{rawMaterialSize},
#{processingVendor}, #{processingDeadline}, #{grindingDeadline},
#{requiredQty}, #{orderQty}, #{productionQty}, #{stockQty}, #{shortageQty},
#{vendor}, #{unitPrice}, #{totalPrice}, #{currency}, #{leadTime}, #{minOrderQty},
'ACTIVE', #{sessionUserId}, NOW(), #{remark}
)
</insert>
<!-- MBOM_HEADER 업데이트 -->
<update id="updateMbomHeader" parameterType="map">
UPDATE MBOM_HEADER
SET
PART_NO = #{partNo},
PART_NAME = #{partName},
EDITER = #{sessionUserId},
EDIT_DATE = NOW()
WHERE OBJID = #{mbomHeaderObjid}
</update>
<!-- MBOM_DETAIL 삭제 (수정 시 기존 데이터 삭제 후 재삽입) -->
<delete id="deleteMbomDetail" parameterType="map">
DELETE FROM MBOM_DETAIL
WHERE MBOM_HEADER_OBJID = #{mbomHeaderObjid}
</delete>
<!-- MBOM_HISTORY 삽입 -->
<insert id="insertMbomHistory" parameterType="map">
INSERT INTO MBOM_HISTORY (
OBJID, MBOM_HEADER_OBJID, CHANGE_TYPE, CHANGE_DESCRIPTION,
BEFORE_DATA, AFTER_DATA, CHANGE_USER, CHANGE_DATE
) VALUES (
#{objid}, #{mbomHeaderObjid}, #{changeType}, #{changeDescription},
#{beforeData}::JSONB, #{afterData}::JSONB, #{sessionUserId}, NOW()
)
</insert>
<!-- PROJECT_MGMT MBOM_STATUS 업데이트 -->
<update id="updateProjectMbomStatus" parameterType="map">
UPDATE PROJECT_MGMT
SET
MBOM_STATUS = 'Y',
MBOM_WRITER = #{sessionUserId},
MBOM_REGDATE = NOW()
WHERE OBJID::VARCHAR = #{projectObjId}
</update>
<!-- M-BOM 목록 조회 (셀렉트박스용) -->
<select id="getMbomListForSelect" parameterType="map" resultType="map">
SELECT
OBJID,
MBOM_NO,
SOURCE_BOM_TYPE,
REGDATE,
STATUS
FROM MBOM_HEADER
WHERE STATUS = 'Y'
ORDER BY REGDATE DESC, MBOM_NO
</select>
<!-- M-BOM 목록 (bizMakeOptionList용) -->
<select id="getMbomListForSelect2" parameterType="map" resultType="map">
SELECT
OBJID AS CODE,
COALESCE(MBOM_NO, '') ||
CASE WHEN SOURCE_BOM_TYPE IS NOT NULL AND SOURCE_BOM_TYPE != '' THEN ' (' || SOURCE_BOM_TYPE || ')' ELSE '' END AS NAME
FROM MBOM_HEADER
WHERE STATUS = 'Y'
ORDER BY REGDATE DESC, MBOM_NO
</select>
<!-- M-BOM 상세 리스트 조회 (이력용) -->
<select id="getMbomDetailList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
OBJID,
MBOM_HEADER_OBJID,
PARENT_OBJID,
CHILD_OBJID,
SEQ,
LEVEL,
PART_OBJID,
PART_NO,
PART_NAME,
QTY,
UNIT,
SUPPLY_TYPE,
MAKE_OR_BUY,
RAW_MATERIAL_PART_NO,
RAW_MATERIAL_SPEC,
RAW_MATERIAL,
RAW_MATERIAL_SIZE,
PROCESSING_VENDOR,
PROCESSING_DEADLINE,
GRINDING_DEADLINE,
REQUIRED_QTY,
ORDER_QTY,
PRODUCTION_QTY,
STOCK_QTY,
SHORTAGE_QTY,
VENDOR,
UNIT_PRICE,
TOTAL_PRICE,
CURRENCY,
LEAD_TIME,
MIN_ORDER_QTY,
STATUS,
REMARK
FROM
MBOM_DETAIL
WHERE
MBOM_HEADER_OBJID = #{mbomHeaderObjid}
AND STATUS = 'ACTIVE'
ORDER BY SEQ
</select>
<!-- 저장된 M-BOM 트리 조회 (MBOM_DETAIL 테이블) -->
<select id="getSavedMbomTreeList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MD.OBJID,
MD.MBOM_HEADER_OBJID AS BOM_REPORT_OBJID,
MD.PARENT_OBJID,
MD.CHILD_OBJID,
MD.SEQ,
MD.LEVEL,
MD.PART_OBJID,
MD.PART_NO,
MD.PART_NAME,
MD.QTY,
MD.QTY AS QTY_TEMP,
MD.QTY AS ITEM_QTY,
MD.UNIT,
-- 생산 정보
MD.SUPPLY_TYPE,
MD.MAKE_OR_BUY,
MD.RAW_MATERIAL_PART_NO AS RAW_MATERIAL_NO, -- JSP 그리드 필드명에 맞춤
MD.RAW_MATERIAL_SPEC,
MD.RAW_MATERIAL,
MD.RAW_MATERIAL_SIZE AS SIZE, -- JSP 그리드 필드명에 맞춤
MD.PROCESSING_VENDOR,
MD.PROCESSING_DEADLINE,
MD.GRINDING_DEADLINE,
MD.REQUIRED_QTY,
MD.ORDER_QTY,
MD.PRODUCTION_QTY,
MD.STOCK_QTY,
MD.SHORTAGE_QTY,
-- 구매 정보
MD.VENDOR,
MD.UNIT_PRICE,
MD.TOTAL_PRICE,
MD.CURRENCY,
MD.LEAD_TIME,
MD.MIN_ORDER_QTY,
-- 기타
MD.STATUS,
MD.WRITER,
TO_CHAR(MD.REGDATE, 'YYYY-MM-DD HH24:MI:SS') AS REGDATE,
MD.EDITER,
CASE WHEN MD.EDIT_DATE IS NOT NULL THEN TO_CHAR(MD.EDIT_DATE, 'YYYY-MM-DD HH24:MI:SS') ELSE NULL END AS EDIT_DATE,
MD.REMARK,
-- E-BOM 호환을 위한 추가 컬럼
MD.PART_OBJID AS LAST_PART_OBJID,
MD.PART_OBJID AS BOM_LAST_PART_OBJID,
NULL AS PARENT_PART_NO,
NULL AS CONTRACT_OBJID,
0 AS SUB_PART_CNT,
CASE WHEN MD.LEVEL = 1 THEN MD.OBJID ELSE NULL END AS ROOT_OBJID,
CASE WHEN MD.LEVEL = 1 THEN MD.OBJID ELSE NULL END AS SUB_ROOT_OBJID,
1 AS LEAF,
-- PART_MNG 테이블에서 추가 정보 조회
PM.SPEC,
PM.MATERIAL,
PM.WEIGHT,
PM.PART_TYPE,
PM.REMARK,
PM.REVISION,
PM.MAKER,
PM.THICKNESS,
PM.WIDTH,
PM.HEIGHT,
PM.OUT_DIAMETER,
PM.IN_DIAMETER,
PM.LENGTH,
PM.SOURCING_CODE,
PM.HEAT_TREATMENT_HARDNESS,
PM.HEAT_TREATMENT_METHOD,
PM.SURFACE_TREATMENT,
(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PM.UNIT) AS UNIT_TITLE,
(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PM.PART_TYPE) AS PART_TYPE_TITLE,
(SELECT COUNT(1) FROM ATTACH_FILE_INFO F WHERE PM.OBJID = F.TARGET_OBJID AND F.STATUS = 'Active' AND F.DOC_TYPE IN ('3D_CAD')) AS CU01_CNT,
(SELECT COUNT(1) FROM ATTACH_FILE_INFO F WHERE PM.OBJID = F.TARGET_OBJID AND F.STATUS = 'Active' AND F.DOC_TYPE IN ('2D_DRAWING_CAD')) AS CU02_CNT,
(SELECT COUNT(1) FROM ATTACH_FILE_INFO F WHERE PM.OBJID = F.TARGET_OBJID AND F.STATUS = 'Active' AND F.DOC_TYPE IN ('2D_PDF_CAD')) AS CU03_CNT
FROM MBOM_DETAIL MD
LEFT JOIN PART_MNG PM ON MD.PART_OBJID = PM.OBJID
WHERE MD.MBOM_HEADER_OBJID = #{mbomHeaderObjid}
AND MD.STATUS = 'ACTIVE'
ORDER BY MD.SEQ
</select>
</mapper>