1015 lines
28 KiB
Plaintext
1015 lines
28 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"%>
|
||
<c:set var="resolvedProjectId"
|
||
value="${not empty param.PROJECT_MGMT_OBJID and param.PROJECT_MGMT_OBJID ne 'null'
|
||
? param.PROJECT_MGMT_OBJID
|
||
: (not empty resultMap.PROJECT_MGMT_OBJID
|
||
? resultMap.PROJECT_MGMT_OBJID
|
||
: (not empty resultMap.PROJECT_NO ? resultMap.PROJECT_NO : ''))}" />
|
||
<c:set var="resolvedBomReportObjid"
|
||
value="${not empty param.BOM_REPORT_OBJID and param.BOM_REPORT_OBJID ne 'null'
|
||
? param.BOM_REPORT_OBJID
|
||
: (not empty resultMap.BOM_REPORT_OBJID ? resultMap.BOM_REPORT_OBJID : '')}" />
|
||
<%-- MBOM_HEADER.OBJID를 직접 사용 (M-BOM 관리 화면에서 사용하는 것과 동일) --%>
|
||
<c:set var="resolvedMbomHeaderObjid"
|
||
value="${not empty param.MBOM_HEADER_OBJID and param.MBOM_HEADER_OBJID ne 'null'
|
||
? param.MBOM_HEADER_OBJID
|
||
: (not empty resultMap.MBOM_HEADER_OBJID ? resultMap.MBOM_HEADER_OBJID : '')}" />
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<title><%=Constants.SYSTEM_NAME%></title>
|
||
<script type="text/javascript" src="/js/tabulator/tabulator_custom.js"></script>
|
||
<style>
|
||
body, html {
|
||
margin: 0;
|
||
padding: 0;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
.container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
.header {
|
||
padding: 15px 20px;
|
||
background: #f5f5f5;
|
||
border-bottom: 2px solid #ddd;
|
||
text-align: right;
|
||
}
|
||
.header h3 {
|
||
margin: 0 0 10px 0;
|
||
color: #333;
|
||
}
|
||
.info-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
}
|
||
.info-table td {
|
||
padding: 8px 15px;
|
||
border: 1px solid #eee;
|
||
}
|
||
.info-table label {
|
||
font-weight: bold;
|
||
color: #555;
|
||
margin-right: 8px;
|
||
}
|
||
.info-table span {
|
||
color: #333;
|
||
}
|
||
.content {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow: auto;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h3 style="margin: 0 0 10px 0; float: left;">구매리스트</h3>
|
||
<div style="float: right;">
|
||
<input type="button" value="저장" class="plm_btns" onclick="fn_save();" style="margin-right: 5px;">
|
||
<input type="button" value="닫기" class="plm_btns" onclick="window.close();" style="margin-right: 5px;">
|
||
</div>
|
||
<div style="clear: both;"></div>
|
||
|
||
<!-- 프로젝트 정보 -->
|
||
<table class="info-table" style="margin-top: 10px;">
|
||
<tr>
|
||
<td>
|
||
<label>프로젝트번호:</label>
|
||
<span id="infoProjectNo">${resultMap.PROJECT_NUMBER}</span>
|
||
</td>
|
||
<td>
|
||
<label>고객사:</label>
|
||
<span id="infoCustomer">${resultMap.CUSTOMER_NAME}</span>
|
||
</td>
|
||
<td>
|
||
<label>품번:</label>
|
||
<span id="infoPartNo">${resultMap.PART_NO}</span>
|
||
</td>
|
||
<td>
|
||
<label>품명:</label>
|
||
<span id="infoPartName">${resultMap.PART_NAME}</span>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<label>구매유형:</label>
|
||
<span id="infoPurchaseType">${resultMap.PURCHASE_TYPE_NAME}</span>
|
||
</td>
|
||
<td>
|
||
<label>요청번호:</label>
|
||
<span id="infoRequestNo">${resultMap.REQUEST_MNG_NO}</span>
|
||
</td>
|
||
<td>
|
||
<label>요청인:</label>
|
||
<span id="infoRequestUser">${resultMap.REQUEST_USER_NAME}</span>
|
||
</td>
|
||
<td>
|
||
<label>입고요청일:</label>
|
||
<span id="infoDeliveryDate">${resultMap.DUE_DATE}</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<div id="purchaseListTable"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
var _tabulGrid;
|
||
var salesRequestMasterObjid = "${param.SALES_REQUEST_MASTER_OBJID}";
|
||
var projectMgmtObjid = "${resolvedProjectId}";
|
||
var bomReportObjid = "${resolvedBomReportObjid}";
|
||
var mbomHeaderObjid = "${resolvedMbomHeaderObjid}"; // MBOM_HEADER.OBJID (M-BOM 관리 화면에서 사용하는 ID)
|
||
var vendorList = []; // 공급업체 목록
|
||
var processingVendorList = []; // 가공업체 목록 (Select2용 배열)
|
||
|
||
// 디버그: resultMap 내용 확인 (주석처리)
|
||
// console.log("=== JSP resultMap 디버그 ===");
|
||
// console.log("resultMap.PROJECT_MGMT_OBJID:", "${resultMap.PROJECT_MGMT_OBJID}");
|
||
// console.log("resultMap.BOM_REPORT_OBJID:", "${resultMap.BOM_REPORT_OBJID}");
|
||
// console.log("resultMap.MBOM_HEADER_OBJID:", "${resultMap.MBOM_HEADER_OBJID}");
|
||
// console.log("resultMap.PROJECT_NO:", "${resultMap.PROJECT_NO}");
|
||
// console.log("resolvedProjectId:", projectMgmtObjid);
|
||
// console.log("resolvedBomReportObjid:", bomReportObjid);
|
||
// console.log("resolvedMbomHeaderObjid:", mbomHeaderObjid);
|
||
function logDebug(){
|
||
if(window.console && typeof window.console.log === "function"){
|
||
console.log.apply(console, arguments);
|
||
}
|
||
}
|
||
function logError(){
|
||
if(window.console && typeof window.console.error === "function"){
|
||
console.error.apply(console, arguments);
|
||
}
|
||
}
|
||
|
||
$(document).ready(function(){
|
||
// 공급업체 목록 로드 (가공업체도 동일 테이블 사용)
|
||
fn_loadVendorList(function(){
|
||
// vendorList에서 processingVendorList 변환 (Select2용 배열 형태)
|
||
processingVendorList = [];
|
||
for(var key in vendorList) {
|
||
if(key !== '') { // 빈 값 제외
|
||
processingVendorList.push({id: key, text: vendorList[key]});
|
||
}
|
||
}
|
||
console.log("가공업체 목록 변환 완료:", processingVendorList.length + "개");
|
||
|
||
fn_initGrid();
|
||
logDebug("purchaseListFormPopUp :: grid initialized");
|
||
fn_loadInitialData();
|
||
});
|
||
});
|
||
|
||
// 공급업체 목록 로드 (일반거래처관리 CLIENT_MNG)
|
||
function fn_loadVendorList(callback) {
|
||
vendorList = {}; // 객체 형태로 변경
|
||
vendorList[''] = '선택'; // 빈 값
|
||
|
||
$.ajax({
|
||
url: "/admin/clientMngListPagingGridList.do",
|
||
method: 'post',
|
||
data: {
|
||
countPerPage: 9999
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
if(data && data.RESULTLIST) {
|
||
data.RESULTLIST.forEach(function(item) {
|
||
var name = item.CLIENT_NM || '';
|
||
var objid = item.OBJID || '';
|
||
if(objid && name) {
|
||
vendorList[objid] = name;
|
||
}
|
||
});
|
||
}
|
||
console.log("공급업체 로드 완료:", Object.keys(vendorList).length + "개", vendorList);
|
||
if(callback) callback();
|
||
},
|
||
error: function(xhr, status, error) {
|
||
logError("공급업체 목록 로드 실패:", error);
|
||
if(callback) callback();
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
function fn_loadInitialData(){
|
||
logDebug("purchaseListFormPopUp :: fn_loadInitialData start",
|
||
"projectMgmtObjid=", projectMgmtObjid,
|
||
"mbomHeaderObjid=", mbomHeaderObjid,
|
||
"salesRequestMasterObjid=", salesRequestMasterObjid);
|
||
var hasProject = projectMgmtObjid && projectMgmtObjid !== "" && projectMgmtObjid !== "null";
|
||
var hasMbomHeader = mbomHeaderObjid && mbomHeaderObjid !== "" && mbomHeaderObjid !== "null";
|
||
var hasMaster = salesRequestMasterObjid && salesRequestMasterObjid !== "" && salesRequestMasterObjid !== "null";
|
||
|
||
// M-BOM에서 생성된 경우만 M-BOM 데이터를 로드 (MBOM_HEADER_OBJID가 있어야 함)
|
||
if(hasMbomHeader){
|
||
logDebug("purchaseListFormPopUp :: M-BOM에서 생성된 구매리스트 - MBOM 로드");
|
||
fn_loadFromMBom(function(mbomList){
|
||
if(hasMaster){
|
||
// M-BOM 데이터가 있으면 merge, 없으면 SALES_REQUEST_PART만 로드
|
||
var hasMbomData = mbomList && mbomList.length > 0;
|
||
logDebug("purchaseListFormPopUp :: MBOM data count:", mbomList ? mbomList.length : 0);
|
||
fn_loadPurchaseList(hasMbomData); // M-BOM 있으면 merge, 없으면 단독 로드
|
||
}
|
||
});
|
||
} else if(hasMaster){
|
||
logDebug("purchaseListFormPopUp :: 수동 작성된 구매리스트 - SALES_REQUEST_PART만 로드");
|
||
fn_loadPurchaseList(false);
|
||
} else {
|
||
logDebug("purchaseListFormPopUp :: no data to load");
|
||
}
|
||
}
|
||
|
||
// Select2 커스텀 에디터 생성 함수
|
||
function createSelect2Editor(options) {
|
||
return function(cell, onRendered, success, cancel, editorParams) {
|
||
var cellValue = cell.getValue();
|
||
var container = document.createElement("span");
|
||
var select = document.createElement("select");
|
||
select.style.width = "100%";
|
||
|
||
// 빈 옵션 추가
|
||
var emptyOption = document.createElement("option");
|
||
emptyOption.value = "";
|
||
emptyOption.text = "선택";
|
||
select.appendChild(emptyOption);
|
||
|
||
// 옵션 추가
|
||
options.forEach(function(opt) {
|
||
var option = document.createElement("option");
|
||
if(typeof opt === 'object') {
|
||
option.value = opt.id || opt.value || '';
|
||
option.text = opt.text || opt.label || '';
|
||
} else {
|
||
option.value = opt;
|
||
option.text = opt;
|
||
}
|
||
if(option.value == cellValue) {
|
||
option.selected = true;
|
||
}
|
||
select.appendChild(option);
|
||
});
|
||
|
||
container.appendChild(select);
|
||
|
||
onRendered(function() {
|
||
$(select).select2({
|
||
width: '100%',
|
||
dropdownAutoWidth: true,
|
||
allowClear: true,
|
||
placeholder: '선택'
|
||
});
|
||
$(select).select2('open');
|
||
});
|
||
|
||
$(select).on('select2:select select2:clear', function(e) {
|
||
success($(select).val());
|
||
});
|
||
|
||
$(select).on('select2:close', function() {
|
||
setTimeout(function() {
|
||
success($(select).val());
|
||
}, 100);
|
||
});
|
||
|
||
return container;
|
||
};
|
||
}
|
||
|
||
// Tabulator 그리드 초기화
|
||
function fn_initGrid() {
|
||
var columns = [
|
||
// 1. 체크박스
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 40,
|
||
frozen: true,
|
||
title: '<input type="checkbox" id="checkAll">',
|
||
field: 'CHK',
|
||
formatter: function(cell) {
|
||
return '<input type="checkbox" class="rowCheck">';
|
||
},
|
||
headerSort: false
|
||
},
|
||
// 2. No
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 50,
|
||
title: 'No',
|
||
field: 'ROW_NUM',
|
||
formatter: "rownum",
|
||
frozen: true
|
||
},
|
||
// 2-1. 구분 (M-BOM / 수동)
|
||
// {
|
||
// headerHozAlign: 'center',
|
||
// hozAlign: 'center',
|
||
// width: 70,
|
||
// title: '구분',
|
||
// frozen: true,
|
||
// field: 'DATA_SOURCE',
|
||
// formatter: function(cell) {
|
||
// var value = cell.getValue();
|
||
// if(value === 'MBOM') {
|
||
// return '<span style="color: #2196F3; font-weight: bold;">M-BOM</span>';
|
||
// } else if(value === 'SRP') {
|
||
// return '<span style="color: #4CAF50; font-weight: bold;">수동</span>';
|
||
// }
|
||
// return value || '';
|
||
// }
|
||
// },
|
||
// 3. 품번
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
widthGrow: 2,
|
||
title: '품번',
|
||
field: 'PART_NO',
|
||
frozen: true
|
||
},
|
||
// 4. 품명
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
widthGrow: 3,
|
||
title: '품명',
|
||
field: 'PART_NAME',
|
||
frozen: true
|
||
},
|
||
// 5. 수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 60,
|
||
title: '수량',
|
||
field: 'QTY'
|
||
},
|
||
// 6. 항목 수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 80,
|
||
title: '항목 수량',
|
||
field: 'ITEM_QTY'
|
||
},
|
||
// 7. 3D
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 60,
|
||
title: '3D',
|
||
field: 'CU01_CNT',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
// 8. 2D
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 60,
|
||
title: '2D',
|
||
field: 'CU02_CNT',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
// 9. PDF
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 60,
|
||
title: 'PDF',
|
||
field: 'CU03_CNT',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
// 10. 재료
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '재료',
|
||
field: 'MATERIAL'
|
||
},
|
||
// 11. 열처리경도
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 120,
|
||
title: '열처리경도',
|
||
field: 'HEAT_TREATMENT_HARDNESS'
|
||
},
|
||
// 12. 열처리방법
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 120,
|
||
title: '열처리방법',
|
||
field: 'HEAT_TREATMENT_METHOD'
|
||
},
|
||
// 13. 표면처리
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '표면처리',
|
||
field: 'SURFACE_TREATMENT'
|
||
},
|
||
// 14. 메이커
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '메이커',
|
||
field: 'VENDOR'
|
||
},
|
||
// 15. 범주 이름
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '범주 이름',
|
||
field: 'PART_TYPE_TITLE'
|
||
},
|
||
// 16. 지급/사급
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '지급/사급',
|
||
field: 'SUPPLY_TYPE'
|
||
},
|
||
// 20. 소재소요량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '소재소요량',
|
||
field: 'REQUIRED_QTY',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 21. 소재발주수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 120,
|
||
title: '소재발주수량',
|
||
field: 'ORDER_QTY',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 22. 항목수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 80,
|
||
title: '항목수량',
|
||
field: 'ITEM_QTY'
|
||
},
|
||
// 23. 제작수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '제작수량',
|
||
field: 'PRODUCTION_QTY',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
|
||
// 17. 소재 -> 소재재질
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '소재재질',
|
||
field: 'RAW_MATERIAL'
|
||
},
|
||
// 18. 사이즈 -> 규격
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '규격',
|
||
field: 'SIZE'
|
||
},
|
||
// 19. 소재품번
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 120,
|
||
title: '소재품번',
|
||
field: 'RAW_MATERIAL_NO'
|
||
},
|
||
// 30. 공급업체 (수정가능 - Select2 에디터)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">공급업체</span>',
|
||
field: 'VENDOR_PM',
|
||
editor: function(cell, onRendered, success, cancel, editorParams) {
|
||
// Select2 에디터 (가공업체와 동일한 목록 사용)
|
||
return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams);
|
||
},
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
if(!value) return '';
|
||
// processingVendorList에서 해당 값의 이름 찾기
|
||
for(var i = 0; i < processingVendorList.length; i++) {
|
||
if(processingVendorList[i].id == value) {
|
||
return processingVendorList[i].text;
|
||
}
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
// 31. 단가 (수정가능) -> 소재단가
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">소재단가</span>',
|
||
field: 'UNIT_PRICE',
|
||
editor: 'number',
|
||
editable: true,
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 32. 총단가 -> 소재총단가
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '소재총단가',
|
||
field: 'TOTAL_PRICE',
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
var qty = parseFloat(data.PO_QTY) || 0;
|
||
var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
|
||
var totalPrice = qty * unitPrice;
|
||
return totalPrice > 0 ? totalPrice.toLocaleString() : '0';
|
||
}
|
||
},
|
||
|
||
// 27. 사용여부 (수정가능)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 90,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">사용여부</span>',
|
||
field: 'USE_YN',
|
||
editor: 'list',
|
||
editorParams: {
|
||
values: ['사용', '미사용']
|
||
},
|
||
mutator: function(value, data) {
|
||
// DB 값 변환: Y/N → 사용/미사용
|
||
if(value === 'Y' || value === '사용') return '사용';
|
||
if(value === 'N' || value === '미사용') return '미사용';
|
||
// 값이 없으면 기본값 '사용'
|
||
return '사용';
|
||
}
|
||
},
|
||
// 28. 정미수량 (편집 불가) - 소재소요량 × 제작수량
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '정미수량',
|
||
field: 'NET_QTY',
|
||
mutator: function(value, data) {
|
||
// 저장된 값이 있으면 우선 사용
|
||
if(value !== null && value !== undefined && value !== '' && parseFloat(value) > 0) {
|
||
return parseFloat(value);
|
||
}
|
||
// 계산: 소재소요량 × 제작수량 (올림 없음)
|
||
var requiredQty = parseFloat(data.REQUIRED_QTY) || 0;
|
||
var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
|
||
var calculated = requiredQty * productionQty;
|
||
return calculated > 0 ? calculated : 0;
|
||
},
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 29. 발주수량 (수정가능) - 정미수량 올림
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">발주수량</span>',
|
||
field: 'PO_QTY',
|
||
editor: 'number',
|
||
mutator: function(value, data) {
|
||
// 저장된 값이 있으면 우선 사용
|
||
if(value !== null && value !== undefined && value !== '' && parseFloat(value) > 0) {
|
||
return parseFloat(value);
|
||
}
|
||
// 계산: 정미수량 올림
|
||
var netQty = parseFloat(data.NET_QTY) || 0;
|
||
return netQty > 0 ? Math.ceil(netQty) : 0;
|
||
},
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
|
||
// 24. 가공업체 (수정가능 - Select2 에디터)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">가공업체</span>',
|
||
field: 'PROCESSING_VENDOR',
|
||
editor: function(cell, onRendered, success, cancel, editorParams) {
|
||
// Select2 에디터
|
||
return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams);
|
||
},
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
|
||
// 저장된 값이 없으면 기본값 '5001'(RPS) 설정
|
||
if(value === undefined || value === null || value === '') {
|
||
value = '5001';
|
||
cell.getRow().update({PROCESSING_VENDOR: value}, false);
|
||
}
|
||
|
||
// OBJID로 업체명 조회하여 표시
|
||
for(var i = 0; i < processingVendorList.length; i++) {
|
||
if(processingVendorList[i].id == value) {
|
||
return processingVendorList[i].text;
|
||
}
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
// 가공단가 (수정가능)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">가공단가</span>',
|
||
field: 'PROCESSING_UNIT_PRICE',
|
||
editor: 'number',
|
||
editable: true,
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 가공총단가 (가공단가 × 제작수량)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '가공총단가',
|
||
field: 'PROCESSING_TOTAL_PRICE',
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
|
||
var processingUnitPrice = parseFloat(data.PROCESSING_UNIT_PRICE) || 0;
|
||
var processingTotalPrice = productionQty * processingUnitPrice;
|
||
return processingTotalPrice > 0 ? processingTotalPrice.toLocaleString() : '0';
|
||
}
|
||
},
|
||
// 총합계 (소재총단가 + 가공총단가)
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '총합계',
|
||
field: 'GRAND_TOTAL_PRICE',
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
// 소재총단가: 발주수량 × 소재단가
|
||
var poQty = parseFloat(data.PO_QTY) || 0;
|
||
var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
|
||
var materialTotalPrice = poQty * unitPrice;
|
||
// 가공총단가: 제작수량 × 가공단가
|
||
var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
|
||
var processingUnitPrice = parseFloat(data.PROCESSING_UNIT_PRICE) || 0;
|
||
var processingTotalPrice = productionQty * processingUnitPrice;
|
||
// 총합계
|
||
var grandTotal = materialTotalPrice + processingTotalPrice;
|
||
return grandTotal > 0 ? grandTotal.toLocaleString() : '0';
|
||
}
|
||
},
|
||
/* // 25. 가공납기 - 주석처리
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '가공납기',
|
||
field: 'PROCESSING_DEADLINE'
|
||
},
|
||
// 26. 연삭납기
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '연삭납기',
|
||
field: 'GRINDING_DEADLINE'
|
||
}, */
|
||
|
||
|
||
// 33. 소재 품의서작성일
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 110,
|
||
title: '소재품의서일',
|
||
field: 'PROPOSAL_DATE',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
if(!value) return '';
|
||
// YYYY-MM-DD 형식으로 표시
|
||
if(value.length >= 10) {
|
||
return value.substring(0, 10);
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
// 34. 가공 품의서작성일
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 110,
|
||
title: '가공품의서일',
|
||
field: 'PROCESSING_PROPOSAL_DATE',
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
if(!value) return '';
|
||
// YYYY-MM-DD 형식으로 표시
|
||
if(value.length >= 10) {
|
||
return value.substring(0, 10);
|
||
}
|
||
return value;
|
||
}
|
||
}
|
||
];
|
||
|
||
_tabulGrid = new Tabulator("#purchaseListTable", {
|
||
layout: "fitData",
|
||
height: "calc(100vh - 200px)",
|
||
columns: columns,
|
||
data: [],
|
||
placeholder: "데이터가 없습니다."
|
||
});
|
||
|
||
// 전체 체크박스 이벤트
|
||
$(document).on('click', '#checkAll', function() {
|
||
$('.rowCheck').prop('checked', $(this).prop('checked'));
|
||
});
|
||
|
||
// 셀 편집 이벤트
|
||
_tabulGrid.on("cellEdited", function(cell) {
|
||
var field = cell.getField();
|
||
var row = cell.getRow();
|
||
var data = row.getData();
|
||
|
||
// 발주수량, 소재단가, 가공단가 변경 시 총단가 자동 계산
|
||
if(field === 'PO_QTY' || field === 'UNIT_PRICE' || field === 'PROCESSING_UNIT_PRICE') {
|
||
row.reformat();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 기존 구매리스트 조회
|
||
function fn_loadPurchaseList(mergeMode) {
|
||
logDebug("purchaseListFormPopUp :: fn_loadPurchaseList", "mergeMode=", mergeMode, "masterId=", salesRequestMasterObjid);
|
||
$.ajax({
|
||
url: "/salesMng/getPurchaseListDetail.do",
|
||
method: 'post',
|
||
data: {
|
||
SALES_REQUEST_MASTER_OBJID: salesRequestMasterObjid
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
logDebug("purchaseListFormPopUp :: purchase list loaded", data);
|
||
if(data && data.list) {
|
||
if(mergeMode){
|
||
fn_mergeSavedData(data.list);
|
||
}else{
|
||
_tabulGrid.setData(data.list);
|
||
}
|
||
}
|
||
},
|
||
error: function(jqxhr, status, error){
|
||
logError("구매리스트 조회 오류:", error);
|
||
Swal.fire({
|
||
title: '오류',
|
||
text: '구매리스트 조회 중 오류가 발생했습니다.',
|
||
icon: 'error'
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// M-BOM에서 구매리스트 생성
|
||
function fn_loadFromMBom(callback) {
|
||
logDebug("=== fn_loadFromMBom 호출 ===");
|
||
logDebug("MBOM_HEADER_OBJID:", mbomHeaderObjid);
|
||
|
||
// MBOM_HEADER_OBJID가 없으면 M-BOM 데이터를 로드하지 않음
|
||
if(!mbomHeaderObjid || mbomHeaderObjid === '' || mbomHeaderObjid === 'null') {
|
||
logDebug("MBOM_HEADER_OBJID가 없어서 M-BOM 로드 안 함");
|
||
if(callback) callback([]);
|
||
return;
|
||
}
|
||
|
||
$.ajax({
|
||
url: "/salesMng/getMBomForPurchaseList.do",
|
||
method: 'post',
|
||
data: {
|
||
PROJECT_MGMT_OBJID: mbomHeaderObjid, // MBOM_HEADER_OBJID를 직접 사용
|
||
bomReportObjId: bomReportObjid
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
// console.log("=== M-BOM AJAX 응답 ===");
|
||
// console.log("전체 응답:", JSON.stringify(data));
|
||
// console.log("data.list:", data ? data.list : "data가 null");
|
||
// console.log("list 길이:", (data && data.list) ? data.list.length : 0);
|
||
// if(data && data.list && data.list.length > 0) {
|
||
// console.log("첫번째 항목 전체 키:", Object.keys(data.list[0]));
|
||
// console.log("첫번째 항목 PM_HEAT_RAW:", data.list[0].PM_HEAT_RAW);
|
||
// console.log("첫번째 항목 PM_METHOD_RAW:", data.list[0].PM_METHOD_RAW);
|
||
// console.log("첫번째 항목 PM_SURFACE_RAW:", data.list[0].PM_SURFACE_RAW);
|
||
// console.log("첫번째 항목 HEAT_TREATMENT_HARDNESS:", data.list[0].HEAT_TREATMENT_HARDNESS);
|
||
// }
|
||
|
||
var list = (data && data.list) ? data.list : [];
|
||
if(list.length > 0) {
|
||
// console.log("M-BOM 데이터 " + list.length + "건 로드됨");
|
||
_tabulGrid.setData(list);
|
||
} else {
|
||
// console.log("M-BOM 데이터 없음!");
|
||
// 알림 제거 - 머지 모드에서는 알림 안 띄움
|
||
}
|
||
if(typeof callback === "function"){
|
||
callback(list);
|
||
}
|
||
},
|
||
error: function(jqxhr, status, error){
|
||
console.error("=== M-BOM AJAX 오류 ===");
|
||
console.error("status:", status);
|
||
console.error("error:", error);
|
||
console.error("responseText:", jqxhr.responseText);
|
||
Swal.fire({
|
||
title: '오류',
|
||
text: 'M-BOM 조회 중 오류가 발생했습니다.',
|
||
icon: 'error'
|
||
});
|
||
if(typeof callback === "function"){
|
||
callback([]);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function fn_mergeSavedData(savedList){
|
||
logDebug("purchaseListFormPopUp :: merging saved data", savedList);
|
||
if(!savedList || savedList.length === 0) return;
|
||
var rowMap = {};
|
||
_tabulGrid.getRows().forEach(function(row){
|
||
var rowData = row.getData();
|
||
if(rowData && rowData.PART_OBJID){
|
||
rowMap[rowData.PART_OBJID] = row;
|
||
}
|
||
});
|
||
|
||
savedList.forEach(function(item){
|
||
var key = item.PART_OBJID;
|
||
var targetRow = rowMap[key];
|
||
if(targetRow){
|
||
targetRow.update(item);
|
||
}else{
|
||
_tabulGrid.addData([item], true);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 저장
|
||
function fn_save() {
|
||
var gridData = _tabulGrid.getData();
|
||
|
||
if(!gridData || gridData.length === 0) {
|
||
Swal.fire({
|
||
title: '알림',
|
||
text: '저장할 데이터가 없습니다.',
|
||
icon: 'warning'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 저장 전 데이터 가공
|
||
gridData.forEach(function(item) {
|
||
// 소재총단가 계산 (PO_QTY * UNIT_PRICE)
|
||
var poQty = parseFloat(item.PO_QTY) || 0;
|
||
var unitPrice = parseFloat(item.UNIT_PRICE) || 0;
|
||
item.TOTAL_PRICE = poQty * unitPrice;
|
||
|
||
// 가공총단가 계산 (PRODUCTION_QTY * PROCESSING_UNIT_PRICE)
|
||
var productionQty = parseFloat(item.PRODUCTION_QTY) || 0;
|
||
var processingUnitPrice = parseFloat(item.PROCESSING_UNIT_PRICE) || 0;
|
||
item.PROCESSING_TOTAL_PRICE = productionQty * processingUnitPrice;
|
||
|
||
// 총합계 계산 (소재총단가 + 가공총단가)
|
||
item.GRAND_TOTAL_PRICE = item.TOTAL_PRICE + item.PROCESSING_TOTAL_PRICE;
|
||
|
||
// 사용여부 변환: 사용/미사용 → Y/N
|
||
if(item.USE_YN === '사용') {
|
||
item.USE_YN = 'Y';
|
||
} else if(item.USE_YN === '미사용') {
|
||
item.USE_YN = 'N';
|
||
}
|
||
});
|
||
|
||
Swal.fire({
|
||
title: '확인',
|
||
text: '저장하시겠습니까?',
|
||
icon: 'question',
|
||
showCancelButton: true,
|
||
confirmButtonText: '확인',
|
||
cancelButtonText: '취소'
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
$.ajax({
|
||
url: "/salesMng/savePurchaseList.do",
|
||
method: 'post',
|
||
data: {
|
||
SALES_REQUEST_MASTER_OBJID: salesRequestMasterObjid,
|
||
PROJECT_MGMT_OBJID: projectMgmtObjid,
|
||
purchaseListData: JSON.stringify(gridData)
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
if(data && (data.resultFlag === 'S' || data.RESULTFLAG === 'S')) {
|
||
Swal.fire({
|
||
title: '성공',
|
||
text: '저장되었습니다.',
|
||
icon: 'success'
|
||
}).then(() => {
|
||
if(opener && opener.fn_search) {
|
||
opener.fn_search();
|
||
}
|
||
window.close();
|
||
});
|
||
} else {
|
||
Swal.fire({
|
||
title: '실패',
|
||
text: data.message || data.MESSAGE || '저장에 실패했습니다.',
|
||
icon: 'error'
|
||
});
|
||
}
|
||
},
|
||
error: function(jqxhr, status, error){
|
||
console.error("저장 오류:", error);
|
||
Swal.fire({
|
||
title: '오류',
|
||
text: '저장 중 오류가 발생했습니다.',
|
||
icon: 'error'
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|