1182 lines
32 KiB
Plaintext
1182 lines
32 KiB
Plaintext
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||
<%@ page import="com.pms.common.utils.*"%>
|
||
<%@ page import="java.util.*" %>
|
||
<%@include file= "/init.jsp" %>
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<title><%=Constants.SYSTEM_NAME%></title>
|
||
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
|
||
<link href="/css/select2/select2.min.css" rel="stylesheet">
|
||
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
|
||
<script type="text/javascript" src="/js/select2/select2.min.js"></script>
|
||
<style>
|
||
::-webkit-scrollbar {
|
||
width: 10px;
|
||
height: 15px;
|
||
}
|
||
#mBomTableWrap {
|
||
width: 99%;
|
||
margin: 10px auto;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
}
|
||
#mBomName {
|
||
margin-bottom: 10px;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
}
|
||
body {
|
||
overflow: hidden;
|
||
}
|
||
/* Tabulator 커스텀 스타일 */
|
||
.tabulator-row.level-1 { background-color: #fde9d9 !important; }
|
||
.tabulator-row.level-2 { background-color: #daeef3 !important; }
|
||
.tabulator-row.level-3 { background-color: #e4dfec !important; }
|
||
.tabulator-row.level-4 { background-color: #ebf1de !important; }
|
||
.tabulator-row.level-5 { background-color: #f2f2f2 !important; }
|
||
.tabulator-row.level-6 { background-color: #f2dcdb !important; }
|
||
.tabulator-row.level-7 { background-color: #eeece1 !important; }
|
||
.tabulator-row.level-8 { background-color: #dce6f1 !important; }
|
||
.tabulator-row.level-9 { background-color: #FFFFEB !important; }
|
||
.tabulator-row.level-10 { background-color: #ffffff !important; }
|
||
|
||
/* 편집 가능한 컬럼 헤더 스타일 */
|
||
.editable-header {
|
||
background-color: #fff9c4 !important;
|
||
}
|
||
|
||
/* 선택된 행 하이라이트 스타일 */
|
||
.tabulator-row.row-selected {
|
||
background-color: #b8daff !important;
|
||
border: 1px solid #007bff !important;
|
||
}
|
||
|
||
/* Select2 in Tabulator 스타일 */
|
||
.tabulator-cell .select2-container {
|
||
width: 100% !important;
|
||
}
|
||
.tabulator-cell .select2-container--default .select2-selection--single {
|
||
height: 100%;
|
||
border: none;
|
||
background: transparent;
|
||
}
|
||
.tabulator-cell .select2-container--default .select2-selection--single .select2-selection__rendered {
|
||
line-height: normal;
|
||
padding-left: 0;
|
||
}
|
||
.tabulator-cell .select2-container--default .select2-selection--single .select2-selection__arrow {
|
||
height: 100%;
|
||
}
|
||
.select2-container--open {
|
||
z-index: 9999 !important;
|
||
}
|
||
.select2-results__option {
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
<script>
|
||
var _tabulGrid;
|
||
var selectedRowData = null; // 선택된 행 데이터
|
||
// 프로젝트 수주수량 (최상위 프레임에서 가져오기)
|
||
var projectQuantity = 1; // 기본값
|
||
|
||
// 소재 목록 전역 변수
|
||
var materialList = [];
|
||
|
||
// 공급업체(가공업체) 목록 전역 변수
|
||
var supplyVendorList = [];
|
||
|
||
$(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);
|
||
}
|
||
|
||
// 소재 목록 로드
|
||
fn_loadMaterialList();
|
||
|
||
// 공급업체(가공업체) 목록 로드
|
||
fn_loadSupplyVendorList();
|
||
|
||
// Tabulator 초기화
|
||
fn_initGrid();
|
||
});
|
||
|
||
// 소재 목록 로드
|
||
function fn_loadMaterialList() {
|
||
$.ajax({
|
||
url: '/admin/getMaterialList.do',
|
||
method: 'POST',
|
||
async: false,
|
||
success: function(response) {
|
||
if(response && response.list) {
|
||
// 중복 제거하여 소재 코드만 추출
|
||
var uniqueMaterials = {};
|
||
response.list.forEach(function(item) {
|
||
uniqueMaterials[item.MATERIAL_CODE] = true;
|
||
});
|
||
materialList = Object.keys(uniqueMaterials);
|
||
console.log("소재 목록 로드 완료:", materialList);
|
||
}
|
||
},
|
||
error: function() {
|
||
console.error("소재 목록 로드 실패");
|
||
materialList = [];
|
||
}
|
||
});
|
||
}
|
||
|
||
// 가공업체 목록 로드 (CLIENT_MNG 테이블)
|
||
function fn_loadSupplyVendorList() {
|
||
$.ajax({
|
||
url: '/common/getClientMngList.do',
|
||
method: 'POST',
|
||
async: false,
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
if(data && data.length > 0) {
|
||
// {id: CODE_ID(OBJID), text: NAME(CLIENT_NM)} 형태로 변환
|
||
supplyVendorList = data.map(function(item) {
|
||
return {
|
||
id: item.CODE_ID,
|
||
text: item.NAME
|
||
};
|
||
});
|
||
console.log("가공업체 목록 로드 완료:", supplyVendorList.length + "개");
|
||
}
|
||
},
|
||
error: function() {
|
||
console.error("가공업체 목록 로드 실패");
|
||
supplyVendorList = [];
|
||
}
|
||
});
|
||
}
|
||
|
||
// 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,
|
||
placeholder: '선택',
|
||
allowClear: true,
|
||
dropdownParent: $('body'), // 드롭다운이 셀 밖으로 나오도록
|
||
templateResult: function(data) {
|
||
return data.text;
|
||
},
|
||
templateSelection: function(data) {
|
||
return data.text;
|
||
}
|
||
});
|
||
|
||
$(select).on('select2:select select2:clear', function(e) {
|
||
success($(select).val() || null);
|
||
});
|
||
|
||
$(select).on('select2:close', function(e) {
|
||
// 포커스 잃었을 때 처리
|
||
});
|
||
|
||
// Select2 열기
|
||
$(select).select2('open');
|
||
});
|
||
|
||
return container;
|
||
};
|
||
}
|
||
|
||
// Tabulator 그리드 초기화
|
||
function fn_initGrid() {
|
||
var maxLevel = ${empty MAXLEV ? 1 : MAXLEV};
|
||
|
||
// 컬럼 정의
|
||
var columns = [];
|
||
|
||
// 라디오 버튼 컬럼 (E-BOM과 동일)
|
||
columns.push({
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 40,
|
||
title: '선택',
|
||
field: 'RADIO',
|
||
frozen: true,
|
||
formatter: function(cell) {
|
||
var rowData = cell.getData();
|
||
return '<input type="radio" name="checkedPartNo" value="' + (rowData.CHILD_OBJID || '') + '" ' +
|
||
'data-OBJID="' + (rowData.OBJID || '') + '" ' +
|
||
'data-PART_NO="' + (rowData.PART_NO || '') + '" ' +
|
||
'data-PARENT_PART_NO="' + (rowData.PARENT_PART_NO || '') + '" ' +
|
||
'data-PART_NO_QTY="' + (rowData.LAST_PART_OBJID || '') + '" ' +
|
||
'data-PARENT_PARTS="' + (rowData.PARENT_PARTS || '') + '" ' +
|
||
'data-LAST_PART_OBJID="' + (rowData.LAST_PART_OBJID || rowData.BOM_LAST_PART_OBJID || '') + '" ' +
|
||
'data-PARENT_OBJID="' + (rowData.PARENT_OBJID || '') + '" ' +
|
||
'data-PART_OBJID="' + (rowData.PART_OBJID || '') + '" ' +
|
||
'data-BOM_LAST_PART_OBJID="' + (rowData.BOM_LAST_PART_OBJID || rowData.LAST_PART_OBJID || '') + '" ' +
|
||
'data-CHILD_OBJID="' + (rowData.CHILD_OBJID || '') + '" ' +
|
||
'data-LEVEL="' + (rowData.LEVEL || 1) + '" ' +
|
||
'data-PART_NO="' + (rowData.PART_NO || '') + '">';
|
||
}
|
||
});
|
||
|
||
// 수준 컬럼 그룹
|
||
var levelColumns = [];
|
||
for(var i = 1; i <= maxLevel; i++) {
|
||
levelColumns.push({
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 25,
|
||
minWidth: 25,
|
||
maxWidth: 25,
|
||
title: i,
|
||
field: 'LEVEL_' + i,
|
||
formatter: function(cell) {
|
||
return cell.getValue() === '*' ? '*' : '';
|
||
}
|
||
});
|
||
}
|
||
|
||
columns.push({
|
||
title: '수준',
|
||
headerHozAlign: 'center',
|
||
frozen: true,
|
||
columns: levelColumns
|
||
});
|
||
|
||
// 기본 정보 컬럼들 (E-BOM 정보)
|
||
columns.push(
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
widthGrow: 2,
|
||
title: '품번',
|
||
field: 'PART_NO',
|
||
frozen: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
widthGrow: 3,
|
||
title: '품명',
|
||
field: 'PART_NAME',
|
||
frozen: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 50,
|
||
title: '수량',
|
||
field: 'QTY_TEMP',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 80,
|
||
title: '항목 수량',
|
||
field: 'ITEM_QTY',
|
||
visible: true
|
||
},
|
||
/* 주석처리: Rev, 규격, 제품구분, 상태 컬럼
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 80,
|
||
title: 'Rev',
|
||
field: 'REVISION'
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '규격',
|
||
field: 'SPEC'
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '제품구분',
|
||
field: 'PRODUCT_NAME'
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '상태',
|
||
field: 'STATUS_NAME'
|
||
},
|
||
*/
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 50,
|
||
title: '3D',
|
||
field: 'CU01_CNT',
|
||
visible: true,
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 50,
|
||
title: '2D',
|
||
field: 'CU02_CNT',
|
||
visible: true,
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 50,
|
||
title: 'PDF',
|
||
field: 'CU03_CNT',
|
||
visible: true,
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value && value > 0 ? '<span class="file_icon"></span>' : '<span class="file_empty_icon"></span>';
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '재료',
|
||
field: 'MATERIAL',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 120,
|
||
title: '열처리경도',
|
||
field: 'HEAT_TREATMENT_HARDNESS',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 120,
|
||
title: '열처리방법',
|
||
field: 'HEAT_TREATMENT_METHOD',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '표면처리',
|
||
field: 'SURFACE_TREATMENT',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '메이커',
|
||
field: 'MAKER',
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 70,
|
||
title: '범주이름',
|
||
field: 'PART_TYPE_TITLE',
|
||
visible: true
|
||
}
|
||
);
|
||
|
||
// 생산관리 컬럼 그룹
|
||
columns.push({
|
||
title: '생산관리',
|
||
headerHozAlign: 'center',
|
||
columns: [
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 70,
|
||
title: '자급/사급',
|
||
field: 'SUPPLY_TYPE',
|
||
titleFormatter: function() { return '<span class="editable-header">자급/사급</span>'; },
|
||
editor: createSelect2Editor([{id: '자급', text: '자급'}, {id: '사급', text: '사급'}]),
|
||
cellEdited: function(cell) {
|
||
var row = cell.getRow();
|
||
var data = row.getData();
|
||
// 자급 선택 시 소재 관련 필드 초기화 (null로 설정하여 DB에서 NULL 처리)
|
||
if(data.SUPPLY_TYPE === '자급') {
|
||
row.update({
|
||
RAW_MATERIAL: null,
|
||
SIZE: null,
|
||
RAW_MATERIAL_NO: null,
|
||
REQUIRED_QTY: null
|
||
});
|
||
} else {
|
||
// 사급 선택 시 초기화
|
||
row.update({
|
||
RAW_MATERIAL: null,
|
||
SIZE: null,
|
||
RAW_MATERIAL_NO: null,
|
||
REQUIRED_QTY: null
|
||
});
|
||
}
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '소재재질',
|
||
field: 'RAW_MATERIAL',
|
||
titleFormatter: function() { return '<span class="editable-header">소재재질</span>'; },
|
||
editor: function(cell, onRendered, success, cancel, editorParams) {
|
||
// 소재 목록을 Select2용 형태로 변환
|
||
var options = materialList.map(function(m) { return {id: m, text: m}; });
|
||
return createSelect2Editor(options)(cell, onRendered, success, cancel, editorParams);
|
||
},
|
||
editable: function(cell) {
|
||
return cell.getRow().getData().SUPPLY_TYPE === '사급';
|
||
},
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
if(data.SUPPLY_TYPE === '자급') return '-';
|
||
return cell.getValue() || '';
|
||
},
|
||
cellEdited: function(cell) {
|
||
// 소재 선택 시 사이즈 초기화
|
||
var row = cell.getRow();
|
||
row.update({
|
||
SIZE: '',
|
||
RAW_MATERIAL_NO: ''
|
||
});
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 100,
|
||
title: '규격',
|
||
field: 'SIZE',
|
||
titleFormatter: function() { return '<span class="editable-header">규격</span>'; },
|
||
editor: function(cell, onRendered, success, cancel, editorParams) {
|
||
// 선택된 소재에 따라 동적으로 사이즈 목록 로드
|
||
var data = cell.getRow().getData();
|
||
var materialCode = data.RAW_MATERIAL;
|
||
|
||
if(!materialCode) {
|
||
cancel();
|
||
return;
|
||
}
|
||
|
||
// 서버에서 해당 소재의 사이즈 목록 가져오기
|
||
var sizes = [];
|
||
$.ajax({
|
||
url: '/admin/getMaterialSizes.do',
|
||
method: 'POST',
|
||
data: {materialCode: materialCode},
|
||
async: false,
|
||
success: function(response) {
|
||
if(response && response.list) {
|
||
sizes = response.list.map(function(item) {
|
||
return {id: item.SIZE_SPEC, text: item.SIZE_SPEC};
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
return createSelect2Editor(sizes)(cell, onRendered, success, cancel, editorParams);
|
||
},
|
||
editable: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
return data.SUPPLY_TYPE === '사급' && data.RAW_MATERIAL;
|
||
},
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
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) {
|
||
// 서버에서 소재품번 조회
|
||
$.ajax({
|
||
url: '/admin/getMaterialPartNo.do',
|
||
method: 'POST',
|
||
data: {
|
||
materialCode: data.RAW_MATERIAL,
|
||
sizeSpec: data.SIZE
|
||
},
|
||
success: function(response) {
|
||
if(response && response.MATERIAL_PART_NO) {
|
||
row.update({RAW_MATERIAL_NO: response.MATERIAL_PART_NO});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 120,
|
||
title: '소재품번',
|
||
field: 'RAW_MATERIAL_NO',
|
||
editor: false,
|
||
formatter: function(cell) {
|
||
var data = cell.getRow().getData();
|
||
if(data.SUPPLY_TYPE === '자급') return '-';
|
||
return cell.getValue() || '';
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 90,
|
||
title: '소재소요량',
|
||
field: 'REQUIRED_QTY',
|
||
titleFormatter: function() { return '<span class="editable-header">소재소요량</span>'; },
|
||
editor: 'number',
|
||
editorParams: {
|
||
min: 0,
|
||
step: 0.01,
|
||
selectContents: true // 편집 시 기존 값 전체 선택
|
||
},
|
||
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: 'right',
|
||
width: 90,
|
||
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;
|
||
|
||
// 실제 데이터에도 저장 (getMbomTreeData에서 사용)
|
||
cell.getRow().update({ORDER_QTY: orderQty}, false);
|
||
|
||
return orderQty.toLocaleString();
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 80,
|
||
title: '항목수량',
|
||
field: 'ITEM_QTY',
|
||
editor: false,
|
||
visible: true
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 80,
|
||
title: '제작수량',
|
||
field: 'PRODUCTION_QTY',
|
||
titleFormatter: function() { return '<span class="editable-header">제작수량</span>'; },
|
||
editor: 'number',
|
||
editorParams: {
|
||
min: 0,
|
||
step: 1,
|
||
selectContents: true // 편집 시 기존 값 전체 선택
|
||
},
|
||
formatter: function(cell) {
|
||
// 저장된 값이 있으면 그대로 사용, 없으면 항목수량 × 수주수량으로 계산
|
||
var value = cell.getValue();
|
||
|
||
// 0은 유효한 값이므로 제외 (undefined, null, '' 만 기본값 계산)
|
||
if(value === undefined || value === null || value === '') {
|
||
var data = cell.getRow().getData();
|
||
var itemQty = parseFloat(data.ITEM_QTY) || 0;
|
||
value = itemQty * projectQuantity;
|
||
|
||
// 실제 데이터에도 저장 (getMbomTreeData에서 사용)
|
||
cell.getRow().update({PRODUCTION_QTY: value}, false);
|
||
}
|
||
return Number(value).toLocaleString();
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '가공업체',
|
||
field: 'PROCESSING_VENDOR',
|
||
titleFormatter: function() { return '<span class="editable-header">가공업체</span>'; },
|
||
editor: function(cell, onRendered, success, cancel, editorParams) {
|
||
// 가공업체 목록 Select2 에디터
|
||
return createSelect2Editor(supplyVendorList)(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 < supplyVendorList.length; i++) {
|
||
if(supplyVendorList[i].id == value) {
|
||
return supplyVendorList[i].text;
|
||
}
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '가공단가',
|
||
field: 'PROCESSING_UNIT_PRICE',
|
||
editor: false, // 구매쪽에서 입력
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '-';
|
||
}
|
||
},
|
||
/* 주석처리: 가공납기, 연삭납기 컬럼
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '가공납기',
|
||
field: 'PROCESSING_DEADLINE',
|
||
titleFormatter: function() { return '<span class="editable-header">가공납기</span>'; },
|
||
editor: 'date'
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'center',
|
||
width: 100,
|
||
title: '연삭납기',
|
||
field: 'GRINDING_DEADLINE',
|
||
titleFormatter: function() { return '<span class="editable-header">연삭납기</span>'; },
|
||
editor: 'date'
|
||
},
|
||
*/
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'left',
|
||
width: 150,
|
||
title: '공급업체',
|
||
field: 'VENDOR_NAME',
|
||
editor: false, // 구매쪽에서 입력
|
||
formatter: function(cell) {
|
||
return cell.getValue() || '-';
|
||
}
|
||
},
|
||
// 숨김 컬럼: 공급업체 코드 (저장 시 필요)
|
||
{
|
||
field: 'VENDOR',
|
||
visible: false
|
||
},
|
||
// 숨김 컬럼: 품의서 작성일 (저장 시 기존 값 유지)
|
||
{
|
||
field: 'PROPOSAL_DATE',
|
||
visible: false
|
||
},
|
||
// 숨김 컬럼: 순수량 (저장 시 기존 값 유지)
|
||
{
|
||
field: 'NET_QTY',
|
||
visible: false
|
||
},
|
||
// 숨김 컬럼: 발주수량 (저장 시 기존 값 유지)
|
||
{
|
||
field: 'PO_QTY',
|
||
visible: false
|
||
},
|
||
{
|
||
headerHozAlign: 'center',
|
||
hozAlign: 'right',
|
||
width: 100,
|
||
title: '소재단가',
|
||
field: 'UNIT_PRICE',
|
||
editor: false, // 구매쪽에서 입력
|
||
formatter: function(cell) {
|
||
var value = cell.getValue();
|
||
return value ? Number(value).toLocaleString() : '-';
|
||
}
|
||
}
|
||
// {
|
||
// headerHozAlign: 'center',
|
||
// hozAlign: 'right',
|
||
// width: 100,
|
||
// title: '금액',
|
||
// field: 'TOTAL_PRICE',
|
||
// editor: false,
|
||
// formatter: function(cell) {
|
||
// // 항목수량 × 단가
|
||
// var data = cell.getRow().getData();
|
||
// var itemQty = parseFloat(data.ITEM_QTY) || 0;
|
||
// var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
|
||
// 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",
|
||
height: "calc(100vh - 70px)",
|
||
columns: columns,
|
||
data: bomTreeData,
|
||
rowFormatter: function(row) {
|
||
var data = row.getData();
|
||
var level = data.LEVEL || 1;
|
||
row.getElement().classList.add('level-' + level);
|
||
}
|
||
});
|
||
|
||
// 행 클릭 시 선택 처리 이벤트
|
||
_tabulGrid.on("rowClick", function(e, row) {
|
||
// 링크 클릭 시에는 기본 동작 유지
|
||
if($(e.target).is('a')) {
|
||
return;
|
||
}
|
||
|
||
// 기존 선택 해제
|
||
$('.tabulator-row.row-selected').removeClass('row-selected');
|
||
$('input[name=checkedPartNo]').prop('checked', false);
|
||
|
||
// 현재 행 선택
|
||
$(row.getElement()).addClass('row-selected');
|
||
var rowData = row.getData();
|
||
selectedRowData = rowData;
|
||
|
||
// 해당 행의 라디오 버튼 선택
|
||
$(row.getElement()).find('input[name=checkedPartNo]').prop('checked', true);
|
||
});
|
||
|
||
// 셀 편집 이벤트 등록
|
||
_tabulGrid.on("cellEdited", function(cell) {
|
||
var field = cell.getField();
|
||
var row = cell.getRow();
|
||
var data = row.getData();
|
||
|
||
// 지급/사급 변경 시 소재, 사이즈, 소요량 필드 재계산
|
||
if(field === 'SUPPLY_TYPE') {
|
||
if(data.SUPPLY_TYPE === '자급') {
|
||
// 자급인 경우 소재, 사이즈, 소요량 초기화 (null로 설정)
|
||
row.update({
|
||
RAW_MATERIAL: null,
|
||
SIZE: null,
|
||
REQUIRED_QTY: null
|
||
});
|
||
}
|
||
// 행 전체 재렌더링
|
||
row.reformat();
|
||
}
|
||
|
||
// 제작수량 변경 시 정미수량 자동 계산
|
||
if(field === 'PRODUCTION_QTY' || field === 'REQUIRED_QTY') {
|
||
var requiredQty = parseFloat(data.REQUIRED_QTY) || 0;
|
||
var productionQty = parseFloat(data.PRODUCTION_QTY) || 0;
|
||
var netQty = Math.ceil(requiredQty * productionQty);
|
||
row.update({NET_QTY: netQty});
|
||
}
|
||
|
||
// 발주수량 또는 단가 변경 시 총단가 자동 계산
|
||
if(field === 'PO_QTY' || field === 'UNIT_PRICE') {
|
||
var poQty = parseFloat(data.PO_QTY) || 0;
|
||
var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
|
||
var totalPrice = poQty * unitPrice;
|
||
row.update({TOTAL_PRICE: totalPrice});
|
||
}
|
||
|
||
// Q'ty 초기값 설정 (발주수량에서 가져오기)
|
||
if(field === 'ORDER_QTY' && !data.QTY) {
|
||
row.update({QTY: data.ORDER_QTY});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 필터 적용 함수
|
||
function fn_applyFilter() {
|
||
var partNo = $("#filterPartNo").val().trim();
|
||
var partName = $("#filterPartName").val().trim();
|
||
|
||
if(!partNo && !partName) {
|
||
_tabulGrid.clearFilter();
|
||
return;
|
||
}
|
||
|
||
_tabulGrid.setFilter([
|
||
[
|
||
{field: "PART_NO", type: "like", value: partNo},
|
||
{field: "PART_NAME", type: "like", value: partName}
|
||
]
|
||
]);
|
||
}
|
||
|
||
// 필터 초기화 함수
|
||
function fn_resetFilter() {
|
||
$("#filterPartNo").val("");
|
||
$("#filterPartName").val("");
|
||
_tabulGrid.clearFilter();
|
||
}
|
||
|
||
// M-BOM 조회 (헤더 프레임에서 호출)
|
||
function fn_searchMbom(searchParams) {
|
||
console.log("fn_searchMbom (left) 호출됨:", searchParams);
|
||
|
||
$.ajax({
|
||
url: "/productionplanning/getMbomList.do",
|
||
method: 'post',
|
||
data: searchParams,
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
console.log("M-BOM 조회 결과:", data);
|
||
if(data && data.list) {
|
||
console.log("데이터 개수:", data.list.length);
|
||
|
||
// ORDER_QTY, PRODUCTION_QTY 제거하여 formatter에서 재계산되도록
|
||
var processedData = data.list.map(function(item) {
|
||
delete item.ORDER_QTY;
|
||
delete item.PRODUCTION_QTY;
|
||
return item;
|
||
});
|
||
|
||
_tabulGrid.setData(processedData);
|
||
} else {
|
||
console.log("데이터 없음");
|
||
_tabulGrid.setData([]);
|
||
}
|
||
},
|
||
error: function(jqxhr, status, error){
|
||
console.error("M-BOM 조회 오류:", error);
|
||
console.error("응답:", jqxhr.responseText);
|
||
_tabulGrid.setData([]);
|
||
}
|
||
});
|
||
}
|
||
|
||
/* 주석처리: 가공납기/연삭납기 일괄 적용
|
||
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;
|
||
}
|
||
*/
|
||
|
||
// 선택된 행 데이터 가져오기 (Center 프레임에서 사용)
|
||
function getSelectedRowData() {
|
||
return selectedRowData;
|
||
}
|
||
|
||
// M-BOM 트리 데이터 수집 (저장용)
|
||
function getMbomTreeData() {
|
||
var allData = _tabulGrid.getData();
|
||
|
||
// 숫자 변환 헬퍼 함수 (빈 문자열, '-', 숫자가 아닌 값 → null)
|
||
function toNumber(value) {
|
||
if (value === null || value === undefined || value === '' || value === '-') return null;
|
||
var num = parseFloat(value);
|
||
return isNaN(num) ? null : num;
|
||
}
|
||
|
||
// 문자열 변환 헬퍼 함수 ('-' → null)
|
||
function toString(value) {
|
||
if (value === null || value === undefined || value === '-') return null;
|
||
return value;
|
||
}
|
||
|
||
// 데이터 구조 변환 (MBOM_DETAIL 테이블에 필요한 모든 필드 포함)
|
||
var mbomData = allData.map(function(row) {
|
||
return {
|
||
// 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: toString(row.RAW_MATERIAL), // 자급 시 '-' → null
|
||
rawMaterialSpec: toString(row.RAW_MATERIAL_SPEC),
|
||
rawMaterialSize: toString(row.SIZE), // 자급 시 '-' → null
|
||
rawMaterialPartNo: toString(row.RAW_MATERIAL_NO), // 자급 시 '-' → null
|
||
processingVendor: row.PROCESSING_VENDOR,
|
||
processingDeadline: row.PROCESSING_DEADLINE,
|
||
grindingDeadline: row.GRINDING_DEADLINE,
|
||
requiredQty: toNumber(row.REQUIRED_QTY), // 자급 시 '-' → null
|
||
orderQty: toNumber(row.ORDER_QTY),
|
||
productionQty: toNumber(row.PRODUCTION_QTY),
|
||
stockQty: toNumber(row.STOCK_QTY),
|
||
shortageQty: toNumber(row.SHORTAGE_QTY),
|
||
|
||
// 구매 정보
|
||
vendor: row.VENDOR || row.VENDOR_PM, // 공급업체 코드/OBJID (기존 값 유지)
|
||
unitPrice: toNumber(row.UNIT_PRICE), // 소재단가
|
||
processingUnitPrice: toNumber(row.PROCESSING_UNIT_PRICE), // 가공단가
|
||
// totalPrice 계산: 항목수량 × 단가
|
||
totalPrice: (function() {
|
||
var itemQty = parseFloat(row.ITEM_QTY) || 0;
|
||
var unitPrice = parseFloat(row.UNIT_PRICE) || 0;
|
||
return itemQty * unitPrice;
|
||
})(),
|
||
currency: row.CURRENCY,
|
||
leadTime: toNumber(row.LEAD_TIME),
|
||
minOrderQty: toNumber(row.MIN_ORDER_QTY),
|
||
netQty: toNumber(row.NET_QTY), // 순수량
|
||
poQty: toNumber(row.PO_QTY), // 발주수량
|
||
proposalDate: row.PROPOSAL_DATE, // 품의서 작성일 (기존 값 유지)
|
||
|
||
// 기타
|
||
status: row.STATUS,
|
||
remark: row.REMARK,
|
||
revision: row.REVISION,
|
||
spec: row.SPEC,
|
||
writer: row.WRITER,
|
||
editer: row.EDITER,
|
||
objid: row.OBJID
|
||
};
|
||
});
|
||
|
||
return mbomData;
|
||
}
|
||
|
||
// 엑셀 다운로드 (CSV 형식)
|
||
function fn_excel() {
|
||
if(!_tabulGrid) {
|
||
Swal.fire('데이터가 없습니다.');
|
||
return;
|
||
}
|
||
|
||
// 파일명 생성 (현재 날짜 포함)
|
||
var today = new Date();
|
||
var dateStr = today.getFullYear() + '_' +
|
||
String(today.getMonth() + 1).padStart(2, '0') + '_' +
|
||
String(today.getDate()).padStart(2, '0') + '_' +
|
||
String(today.getHours()).padStart(2, '0') + '_' +
|
||
String(today.getMinutes()).padStart(2, '0');
|
||
var fileName = 'M-BOM_' + dateStr + '.csv';
|
||
|
||
// Tabulator 내장 다운로드 기능 사용 (CSV)
|
||
_tabulGrid.download("csv", fileName, {
|
||
delimiter: ",",
|
||
bom: true // 한글 깨짐 방지 (UTF-8 BOM)
|
||
});
|
||
}
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div>
|
||
<input type="button" value="Excel Download" class="plm_btns" id="btnExcel" style="float:right; margin-right: 5px; margin-bottom: 5px;" onclick="fn_excel();">
|
||
</div>
|
||
<div id="mBomTableWrap"></div>
|
||
</body>
|
||
</html>
|
||
|