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

855 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%@ 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">
<script type="text/javascript" src="/js/tabulator/tabulator.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; }
</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();
});
// 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: 10,
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: 60,
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: 60,
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: 60,
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: 60,
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: 150,
title: '메이커',
field: 'MAKER',
visible: true
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '범주이름',
field: 'PART_TYPE_TITLE',
visible: true
}
);
// 생산관리 컬럼 그룹
columns.push({
title: '생산관리',
headerHozAlign: 'center',
columns: [
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '자급/사급',
field: 'SUPPLY_TYPE',
editor: 'list',
editorParams: {
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: ''
});
}
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '소재',
field: 'RAW_MATERIAL',
editor: 'list',
editorParams: {
values: ['SM45C', 'STS304', 'STS316', 'AL6061', 'AL7075'] // TODO: 실제 소재 목록으로 교체
},
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() || '';
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '사이즈',
field: 'SIZE',
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();
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: 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: 100,
title: '소재소요량',
field: 'REQUIRED_QTY',
editor: 'number',
editorParams: {
min: 0,
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: '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;
// 실제 데이터에도 저장 (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: 100,
title: '제작수량',
field: 'PRODUCTION_QTY',
editor: 'number',
editorParams: {
min: 0,
step: 1
},
formatter: function(cell) {
// 저장된 값이 있으면 그대로 사용, 없으면 항목수량 × 수주수량으로 계산
var value = cell.getValue();
if(value === undefined || value === null || value === '' || value === 0) {
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',
editor: 'list',
editorParams: {
values: ['업체A', '업체B', '업체C'] // TODO: 실제 가공업체 목록으로 교체
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '가공납기',
field: 'PROCESSING_DEADLINE',
editor: 'date'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '연삭납기',
field: 'GRINDING_DEADLINE',
editor: 'date'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '공급업체',
field: 'VENDOR',
editor: false, // 구매쪽에서 입력
formatter: function(cell) {
return cell.getValue() || '-';
}
},
{
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("cellEdited", function(cell) {
var field = cell.getField();
var row = cell.getRow();
var data = row.getData();
// 지급/사급 변경 시 소재, 사이즈, 소요량 필드 재계산
if(field === 'SUPPLY_TYPE') {
if(data.SUPPLY_TYPE === '자급') {
// 자급인 경우 소재, 사이즈, 소요량 초기화
row.update({
RAW_MATERIAL: '',
SIZE: 0,
REQUIRED_QTY: 0
});
}
// 행 전체 재렌더링
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;
}
// 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 {
// 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
};
});
return mbomData;
}
</script>
</head>
<body>
<!-- <div id="mBomName">
<c:choose>
<c:when test="${not empty ebomInfo}">
E-BOM: ${ebomInfo.PART_NO} - ${ebomInfo.PART_NAME}
</c:when>
<c:otherwise>
할당된 E-BOM이 없습니다.
</c:otherwise>
</c:choose>
</div> -->
<div id="mBomTableWrap"></div>
</body>
</html>