This commit is contained in:
2025-10-30 17:41:30 +09:00
14 changed files with 2018 additions and 555 deletions

View File

@@ -2985,6 +2985,14 @@
WHERE OBJID = #{projectMgmtObjid}
</update>
<!-- M-BOM에서 E-BOM 제거 -->
<update id="removeEbomFromProject" parameterType="map">
UPDATE PROJECT_MGMT
SET
PART_OBJID = NULL
WHERE OBJID = #{projectMgmtObjid}
</update>
<!-- E-BOM 정보 조회 -->
<select id="getEbomInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
@@ -3003,4 +3011,326 @@
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
WHERE T.OBJID::VARCHAR = #{objid}
</select>
<!-- PROJECT_MGMT 정보 조회 -->
<select id="getProjectMgmtInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
PM.PART_NO,
PM.PART_NAME,
PM.PART_OBJID,
PM.QUANTITY,
PM.REQ_DEL_DATE,
PM.BOM_REPORT_OBJID,
CM.CUSTOMER_OBJID,
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID::VARCHAR = CM.CUSTOMER_OBJID LIMIT 1) AS CUSTOMER_NAME,
CM.PRODUCT,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1) AS PRODUCT_NAME
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID = PM.CONTRACT_OBJID
WHERE PM.OBJID = #{objid}
</select>
<!-- E-BOM 데이터를 기반으로 M-BOM 데이터 조회 -->
<select id="getMBomDataFromEbom" parameterType="map" resultType="com.pms.common.UpperKeyMap">
WITH RECURSIVE BOM_TREE AS (
-- 최상위 레벨 (LEVEL = 0)
SELECT
BPQ.OBJID,
BPQ.BOM_REPORT_OBJID,
BPQ.PARENT_OBJID,
BPQ.CHILD_OBJID,
BPQ.PARENT_PART_NO,
BPQ.PART_NO,
BPQ.QTY,
0 AS LEVEL,
CAST(BPQ.SEQ AS VARCHAR) AS SORT_PATH,
PM.PART_NO AS PART_NO_TITLE,
PM.PART_NAME,
PM.MATERIAL,
PM.SPEC,
PM.UNIT,
PM.WEIGHT,
PM.REVISION,
'' AS HEAT_TREAT_HARDNESS,
'' AS HEAT_TREAT_METHOD,
'' AS SURFACE_TREATMENT,
'' AS SUPPLIER_NAME,
'' AS CATEGORY_NAME,
'' AS RAW_MATERIAL,
'' AS SIZE,
0 AS ORDER_QTY,
BPQ.QTY AS QUANTITY,
0 AS PRODUCTION_QTY,
'' AS PROCESSOR_NAME,
'' AS PROCESS_DUE_DATE,
'' AS GRINDING_DUE_DATE,
-- 파일 첨부 여부 (3D, 2D, PDF)
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_3D'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_3D,
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_2D'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_2D,
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_PDF'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_PDF
FROM
BOM_PART_QTY BPQ
LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO
WHERE
BPQ.BOM_REPORT_OBJID::VARCHAR = #{bomReportObjid}
AND (BPQ.PARENT_OBJID IS NULL OR BPQ.PARENT_OBJID = '')
UNION ALL
-- 하위 레벨 (재귀)
SELECT
BPQ.OBJID,
BPQ.BOM_REPORT_OBJID,
BPQ.PARENT_OBJID,
BPQ.CHILD_OBJID,
BPQ.PARENT_PART_NO,
BPQ.PART_NO,
BPQ.QTY,
BT.LEVEL + 1,
BT.SORT_PATH || '-' || CAST(BPQ.SEQ AS VARCHAR),
PM.PART_NO AS PART_NO_TITLE,
PM.PART_NAME,
PM.MATERIAL,
PM.SPEC,
PM.UNIT,
PM.WEIGHT,
PM.REVISION,
'' AS HEAT_TREAT_HARDNESS,
'' AS HEAT_TREAT_METHOD,
'' AS SURFACE_TREATMENT,
'' AS SUPPLIER_NAME,
'' AS CATEGORY_NAME,
'' AS RAW_MATERIAL,
'' AS SIZE,
0 AS ORDER_QTY,
BPQ.QTY AS QUANTITY,
0 AS PRODUCTION_QTY,
'' AS PROCESSOR_NAME,
'' AS PROCESS_DUE_DATE,
'' AS GRINDING_DUE_DATE,
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_3D'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_3D,
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_2D'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_2D,
CASE WHEN EXISTS(
SELECT 1 FROM attach_file_info AF
WHERE AF.DOC_TYPE = 'PART_PDF'
AND AF.OBJID = PM.OBJID::NUMERIC
AND AF.STATUS = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS HAS_PDF
FROM
BOM_PART_QTY BPQ
INNER JOIN BOM_TREE BT ON BT.CHILD_OBJID = BPQ.PARENT_OBJID
LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO
)
SELECT
OBJID,
BOM_REPORT_OBJID,
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
QTY,
LEVEL,
PART_NO_TITLE,
PART_NAME,
QTY AS AGGREGATE_QTY,
HAS_3D,
HAS_2D,
HAS_PDF,
MATERIAL,
HEAT_TREAT_HARDNESS,
HEAT_TREAT_METHOD,
SURFACE_TREATMENT,
SUPPLIER_NAME,
CATEGORY_NAME,
RAW_MATERIAL,
SIZE,
ORDER_QTY,
QUANTITY,
PRODUCTION_QTY,
PROCESSOR_NAME,
PROCESS_DUE_DATE,
GRINDING_DUE_DATE
FROM
BOM_TREE
ORDER BY
SORT_PATH
</select>
<!-- M-BOM 데이터 저장 -->
<insert id="insertMBomData" parameterType="map">
INSERT INTO m_bom_data (
objid,
project_mgmt_objid,
bom_report_objid,
parent_objid,
child_objid,
parent_part_no,
part_no,
part_name,
qty,
aggregate_qty,
level,
material,
heat_treat_hardness,
heat_treat_method,
surface_treatment,
supplier_name,
category_name,
raw_material,
size,
order_qty,
quantity,
production_qty,
processor_name,
process_due_date,
grinding_due_date,
writer,
regdate
) VALUES (
#{OBJID}::NUMERIC,
#{projectMgmtObjid}::NUMERIC,
#{BOM_REPORT_OBJID}::NUMERIC,
<choose>
<when test="PARENT_OBJID != null and PARENT_OBJID != ''">
#{PARENT_OBJID}::NUMERIC,
</when>
<otherwise>
NULL,
</otherwise>
</choose>
<choose>
<when test="CHILD_OBJID != null and CHILD_OBJID != ''">
#{CHILD_OBJID}::NUMERIC,
</when>
<otherwise>
NULL,
</otherwise>
</choose>
#{PARENT_PART_NO},
#{PART_NO},
#{PART_NAME},
#{QTY}::NUMERIC,
#{AGGREGATE_QTY}::NUMERIC,
#{LEVEL}::INTEGER,
#{MATERIAL},
#{HEAT_TREAT_HARDNESS},
#{HEAT_TREAT_METHOD},
#{SURFACE_TREATMENT},
#{SUPPLIER_NAME},
#{CATEGORY_NAME},
#{RAW_MATERIAL},
#{SIZE},
#{ORDER_QTY}::NUMERIC,
#{QUANTITY}::NUMERIC,
#{PRODUCTION_QTY}::NUMERIC,
#{PROCESSOR_NAME},
#{PROCESS_DUE_DATE},
#{GRINDING_DUE_DATE},
#{writer},
NOW()
)
</insert>
<!-- M-BOM 버전 업데이트 -->
<update id="updateMBomVersion" parameterType="map">
UPDATE project_mgmt
SET
mbom_version = COALESCE(mbom_version, 0) + 1,
mbom_regdate = NOW(),
mbom_writer = #{writer},
mbom_status = 'Y'
WHERE objid = #{projectMgmtObjid}
</update>
<!-- M-BOM 데이터 삭제 (STATUS를 'DELETED'로 업데이트) -->
<delete id="deleteMBomData" parameterType="map">
UPDATE m_bom_data
SET
STATUS = 'DELETED',
EDIT_DATE = NOW()
WHERE PROJECT_MGMT_OBJID = #{projectMgmtObjid}::NUMERIC
AND STATUS = 'ACTIVE'
</delete>
<!-- M-BOM 상태 초기화 -->
<update id="resetMBomStatus" parameterType="map">
UPDATE project_mgmt
SET
mbom_status = NULL,
mbom_version = NULL,
mbom_regdate = NULL,
mbom_writer = NULL
WHERE objid = #{projectMgmtObjid}
</update>
<!-- 저장된 M-BOM 데이터 조회 -->
<select id="getSavedMBomData" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
OBJID,
PROJECT_MGMT_OBJID,
BOM_REPORT_OBJID,
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
PART_NAME AS PART_NO_TITLE,
PART_NAME,
QTY,
AGGREGATE_QTY,
LEVEL,
MATERIAL,
HEAT_TREAT_HARDNESS,
HEAT_TREAT_METHOD,
SURFACE_TREATMENT,
SUPPLIER_NAME,
CATEGORY_NAME,
RAW_MATERIAL,
SIZE,
ORDER_QTY,
QUANTITY,
PRODUCTION_QTY,
PROCESSOR_NAME,
PROCESS_DUE_DATE,
GRINDING_DUE_DATE,
-- 파일 첨부 여부 확인 (저장된 경우)
'N' AS HAS_3D,
'N' AS HAS_2D,
'N' AS HAS_PDF
FROM
m_bom_data
WHERE
PROJECT_MGMT_OBJID = #{projectMgmtObjid}::NUMERIC
AND STATUS = 'ACTIVE'
ORDER BY
LEVEL, OBJID
</select>
</mapper>

View File

@@ -434,17 +434,8 @@ var gridFn = {
,datatype : "json"
,postData:{"targetObjId":"${objid}","docType":"PART_EXCEL_IMPORT","OBJID":"${CONTRACT_OBJID}"}
,loadComplete : function(data) {
// CSV 파일에서만 LEVEL 값을 PARENT_PART_NO 컬럼에 표시
if(data && data.rows) {
for(var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
// CSV 파일이고 LEVEL 값이 있는 경우
if(row.IS_CSV === 'Y' && row.LEVEL && row.LEVEL !== '') {
// LEVEL 값을 PARENT_PART_NO 컬럼에 표시 (화면용)
grid.jqGrid('setCell', row.id, 'PARENT_PART_NO', row.LEVEL);
}
}
}
// CSV 파일: 모품번을 그대로 표시 (수정 불필요)
// PARENT_PART_NO 컬럼에 이미 모품번이 들어있음
gridFn.footerSummary();
}
,gridComplete : function() {
@@ -516,108 +507,103 @@ function fn_checkDuplicatePartNo(){
function fn_save(){
var ids = grid.jqGrid("getDataIDs");
// 필수 필드 검증 (제품구분, 품번, 품명)
if(!fnc_valitate("form1")){
return;
}
// 품번 중복 검증 (PART_BOM_REPORT 테이블 - 헤더 품번)
if(fn_checkDuplicatePartNo()){
Swal.fire('입력한 품번이 이미 존재합니다. 다른 품번을 입력해주세요.');
$('#bom_part_no').focus();
return;
}
// 그리드에 데이터가 있는 경우에만 검증 수행
if(ids!=""){
if(fnc_valitate("form1")){
var valid = true;
var existPart = true;
var part = "";
if (fnc_isEmpty(${bomInfo.OBJID})) {
var partNo1 = "";
var valid = true;
var existPart = true;
var part = "";
if (fnc_isEmpty(${bomInfo.OBJID})) {
var partNo1 = "";
gridFn.closeEdit();
$.each(grid.getRowData(), function(i, d) {
if (i == 0) {
partNo1 = d["PART_NO"];
}
var partType = d["PART_TYPE"];
var partNo = d["PART_NO"];
if (partType == '0001788') {
if (!fn_existPartNo(partNo)) {
existPart = false;
part = partNo;
return false; // Exit the loop
}
}
if(!fnc_isEmpty(d["NOTE"])){
valid = false;
}
});
gridFn.opennEdit();
var project_no = $("#project_name").find("option:selected").text();
if (partNo1.indexOf(project_no) < 0) {
valid = false;
}
}
if (!existPart) {
Swal.fire(part+'품번에 해당하는 구매품표준이 없습니다. 확인해 주세요.');
return;
}
if (!valid) {
Swal.fire('1레벨 품번에 프로젝트번호가 없습니다. 확인해 주세요.');
return;
}
// 품번 중복 검증 (PART_BOM_REPORT 테이블 - 헤더 품번)
if(fn_checkDuplicatePartNo()){
Swal.fire('입력한 품번이 이미 존재합니다. 다른 품번을 입력해주세요.');
$('#bom_part_no').focus();
return;
}
var existsDup = false;
var ARR_APPLICATION_PROJECT_NO = new Array();
$(".APPLICATION_PROJECT_NO").each(function(){
for(var i=0; i<ARR_APPLICATION_PROJECT_NO.length; i++){
if(this.value != '' && ARR_APPLICATION_PROJECT_NO[i] == this.value){
existsDup = true;
break;
gridFn.closeEdit();
$.each(grid.getRowData(), function(i, d) {
if (i == 0) {
partNo1 = d["PART_NO"];
}
var partType = d["PART_TYPE"];
var partNo = d["PART_NO"];
if (partType == '0001788') {
if (!fn_existPartNo(partNo)) {
existPart = false;
part = partNo;
return false; // Exit the loop
}
}
ARR_APPLICATION_PROJECT_NO.push($(this).val());
if(!fnc_isEmpty(d["NOTE"])){
valid = false;
}
});
if(existsDup){
Swal.fire('동시적용 프로젝트번호에 중복건이 존재합니다.');
return;
}
gridFn.opennEdit();
if(confirm("저장 하시겠습니까?")){
gridFn.closeEdit();
$.ajax({
url:"/partMng/partBomApplySave.do"
,type:"POST"
,data: $("#form1").serialize() + "&jqGrid="+ encodeURIComponent(JSON.stringify(grid.getRowData()))
,dataType:"json"
,success:function(data){
/*
if(data =="SUCCESS"){
alert("저장되었습니다.");
top.opener.fn_search();
self.close();
};
*/
console.log(data);
if(data && data.RESULT == 'S'){
alert("저장되었습니다.");
if(typeof opener.fn_search =="function"){ opener.fn_search() };
self.close();
}else{
alert(data.MSG);
}
}
,error: function(jqxhr, status, error){
alert('에러가 발생하였습니다. 시스템 관리자에게 문의하여 주세요.');
}
});
var project_no = $("#project_name").find("option:selected").text();
if (partNo1.indexOf(project_no) < 0) {
valid = false;
}
}
}else{
Swal.fire('저장할 데이터가 없습니다.');
if (!existPart) {
Swal.fire(part+'품번에 해당하는 구매품표준이 없습니다. 확인해 주세요.');
return;
}
if (!valid) {
Swal.fire('1레벨 품번에 프로젝트번호가 없습니다. 확인해 주세요.');
return;
}
var existsDup = false;
var ARR_APPLICATION_PROJECT_NO = new Array();
$(".APPLICATION_PROJECT_NO").each(function(){
for(var i=0; i<ARR_APPLICATION_PROJECT_NO.length; i++){
if(this.value != '' && ARR_APPLICATION_PROJECT_NO[i] == this.value){
existsDup = true;
break;
}
}
ARR_APPLICATION_PROJECT_NO.push($(this).val());
});
if(existsDup){
Swal.fire('동시적용 프로젝트번호에 중복건이 존재합니다.');
return;
}
}
// 저장 확인 (그리드 데이터가 없어도 저장 가능)
if(confirm("저장 하시겠습니까?")){
gridFn.closeEdit();
$.ajax({
url:"/partMng/partBomApplySave.do"
,type:"POST"
,data: $("#form1").serialize() + "&jqGrid="+ encodeURIComponent(JSON.stringify(grid.getRowData()))
,dataType:"json"
,success:function(data){
console.log(data);
if(data && data.RESULT == 'S'){
alert("저장되었습니다.");
if(typeof opener.fn_search =="function"){ opener.fn_search() };
self.close();
}else{
alert(data.MSG);
}
}
,error: function(jqxhr, status, error){
alert('에러가 발생하였습니다. 시스템 관리자에게 문의하여 주세요.');
}
});
}
}

View File

@@ -403,12 +403,13 @@ function fn_delete() {
var selectedData = _tabulGrid.getSelectedData();
var STATUS = fnc_checkNull(selectedData[0].STATUS);
if('deploy' == STATUS || 'changeDesign' == STATUS){
// 'Y' 상태만 삭제 가능 ('N', 'create' 등 작업중인 상태만 삭제 허용)
if('Y' == STATUS || 'deploy' == STATUS || 'changeDesign' == STATUS){
checkStatus = false;
}
if(!checkStatus){
Swal.fire('배포완료/설계변경미배포 건은 삭제 할 수 없습니다.');
Swal.fire('배포완료 건은 삭제 할 수 없습니다.');
return;
}
@@ -434,10 +435,16 @@ function fn_delete() {
data:{"checkArr":chkArray.join(",")},
dataType:"json",
success:function(data){
Swal.fire(data.msg);
fn_search();
Swal.fire(data.msg).then(() => {
// 검색 플래그 초기화 후 목록 새로고침
isSearching = false;
fn_search();
});
},
error: function(jqxhr, status, error){
Swal.fire('삭제 중 오류가 발생했습니다.');
// 에러 시에도 플래그 초기화
isSearching = false;
}
});
}

View File

@@ -12,8 +12,27 @@ $(function(){
$('.select2').select2();
// 서버에서 전달받은 상태값 확인 (info.STATUS)
var status = '${info.STATUS}';
// 상태가 'Y'인 경우 버튼 비활성화
if(status === 'Y') {
$("#moveLeft, #moveRight, #moveChange").prop('disabled', true)
.css({
'opacity': '0.5',
'cursor': 'not-allowed'
})
.attr('title', '상태가 Y인 데이터는 수정할 수 없습니다.');
}
//Part 연결
$("#moveLeft").click(function(){
// 상태가 'Y'인 경우 동작 방지
if(status === 'Y') {
alert('상태가 Y인 데이터는 수정할 수 없습니다.');
return false;
}
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
@@ -105,6 +124,12 @@ $(function(){
//연결된 part 삭제
$("#moveRight").click(function(){
// 상태가 'Y'인 경우 동작 방지
if(status === 'Y') {
alert('상태가 Y인 데이터는 수정할 수 없습니다.');
return false;
}
var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartChildObjId = leftPartNoObj.val();
var leftPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO");
@@ -118,6 +143,12 @@ $(function(){
//연결된 part 변경
$("#moveChange").click(function(){
// 상태가 'Y'인 경우 동작 방지
if(status === 'Y') {
alert('상태가 Y인 데이터는 수정할 수 없습니다.');
return false;
}
var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
if(leftPartNoList.length === 0){

View File

@@ -28,32 +28,33 @@
}
.current-ebom-section {
background: #f9f9f9;
padding: 20px;
padding: 10px 15px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.current-ebom-section h4 {
margin: 0 0 15px 0;
margin: 0 0 8px 0;
color: #333;
font-size: 16px;
font-size: 13px;
}
.ebom-info-table {
width: 100%;
border-collapse: collapse;
background: white;
margin-bottom: 15px;
margin-bottom: 8px;
font-size: 12px;
}
.ebom-info-table th {
background: #f5f5f5;
padding: 10px;
padding: 6px 8px;
text-align: left;
border: 1px solid #ddd;
width: 150px;
width: 120px;
font-weight: bold;
}
.ebom-info-table td {
padding: 10px;
padding: 6px 8px;
border: 1px solid #ddd;
}
.ebom-list-section {
@@ -87,6 +88,21 @@ $(document).ready(function(){
// 날짜 선택기 초기화
fnc_datepick("search_fromDate", "search_toDate");
// 초기화 버튼과 Excel Download 버튼 숨기기 (계속 감시)
fnc_hideResetButton();
// Excel Download 버튼 완전히 제거 (반복 확인)
var hideExcelBtn = function() {
$('.excelBtn').remove();
$('.resetBtn').remove();
$('.btnArea input[value="Excel Download"]').remove();
$('.btnArea input[value="초기화"]').remove();
};
// 즉시 실행 및 반복 실행
hideExcelBtn();
setInterval(hideExcelBtn, 100);
// Enter 키로 검색
$("input").keyup(function(e) {
if (e.keyCode == 13) {
@@ -104,6 +120,11 @@ $(document).ready(function(){
fn_showEbomList();
});
// 제거 버튼
$("#btnRemove").click(function(){
fn_removeEbom();
});
// 초기 상태 설정
if(hasCurrentEbom) {
// 할당된 E-BOM이 있으면 상세보기만 표시
@@ -219,6 +240,36 @@ function fn_assignEbom() {
});
}
}
// E-BOM 제거
function fn_removeEbom() {
var projectMgmtObjid = "${param.projectMgmtObjid}";
if(confirm("할당된 E-BOM을 제거하시겠습니까?")) {
$.ajax({
url: "/productionplanning/removeEbomFromMbom.do",
type: "POST",
data: {
projectMgmtObjid: projectMgmtObjid
},
dataType: "json",
success: function(result) {
if(result.success) {
alert("E-BOM이 제거되었습니다.");
if(window.opener && window.opener.fn_search) {
window.opener.fn_search(); // 부모 창 새로고침
}
window.close();
} else {
alert(result.message || "제거에 실패했습니다.");
}
},
error: function(xhr, status, error) {
alert("오류가 발생했습니다: " + error);
}
});
}
}
</script>
</head>
<body>
@@ -232,6 +283,7 @@ function fn_assignEbom() {
<h3>E-BOM ${not empty currentEbom ? '상세 및 변경' : '선택'} - 품번: ${param.partNo} / 품명: ${param.partName}</h3>
<div>
<c:if test="${not empty currentEbom}">
<input type="button" value="E-BOM 제거" class="plm_btns" id="btnRemove" style="background-color: #F44336; color: white;">
<input type="button" value="E-BOM 변경" class="plm_btns" id="btnChange" style="background-color: #FF9800; color: white;">
</c:if>
<input type="button" value="닫기" class="plm_btns" onclick="window.close()">
@@ -263,7 +315,7 @@ function fn_assignEbom() {
</tr>
</table>
<div style="text-align: center;">
<p style="color: #666; margin: 10px 0;">다른 E-BOM으로 변경하려면 "E-BOM 변경" 버튼을 클릭하세요.</p>
<p style="color: #666; margin: 5px 0; font-size: 11px;">다른 E-BOM으로 변경하려면 "E-BOM 변경" 버튼을 클릭하세요.</p>
</div>
</div>
</c:if>

View File

@@ -0,0 +1,694 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ 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>
<script type="text/javascript" src="/js/tabulator/tabulator_custom.js"></script>
<style>
.mbom-popup-container {
padding: 10px;
}
.header-section {
background: #f5f5f5;
padding: 15px;
margin-bottom: 10px;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-section h3 {
margin: 0;
color: #333;
}
.info-section {
background: #f9f9f9;
padding: 15px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.info-table {
width: 100%;
border-collapse: collapse;
background: white;
margin-bottom: 10px;
}
.info-table th {
background: #f5f5f5;
padding: 8px;
text-align: left;
border: 1px solid #ddd;
width: 120px;
font-weight: bold;
}
.info-table td {
padding: 8px;
border: 1px solid #ddd;
}
.grid-section {
margin-top: 10px;
height: 500px;
}
/* Tabulator 수준별 로우 색상 */
.tabulator-row.level-0 { background-color: #ffffff !important; }
.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 type="text/javascript">
// Tabulator 그리드 전역 변수
var _tabulGrid;
var projectMgmtObjid = "${projectInfo.OBJID}";
var hasBomData = ${not empty ebomInfo};
$(document).ready(function(){
console.log("projectMgmtObjid:", projectMgmtObjid);
console.log("hasBomData:", hasBomData);
// 저장 버튼
$("#btnSave").click(function(){
fn_saveMBomData();
});
// 삭제 버튼
$("#btnDelete").click(function(){
fn_deleteMBomData();
});
// 닫기 버튼
$("#btnClose").click(function(){
window.close();
});
// 초기 데이터 로드
if(projectMgmtObjid) {
fn_loadMBomData();
} else {
console.error("projectMgmtObjid가 없습니다!");
}
});
// M-BOM 그리드 컬럼 정의
var columns = [
{title: 'OBJID', field: 'OBJID', visible: false},
{title: 'BOM_REPORT_OBJID', field: 'BOM_REPORT_OBJID', visible: false},
{title: 'PARENT_OBJID', field: 'PARENT_OBJID', visible: false},
{title: 'CHILD_OBJID', field: 'CHILD_OBJID', visible: false},
{title: 'PARENT_PART_NO', field: 'PARENT_PART_NO', visible: false},
{title: 'LEVEL', field: 'LEVEL', visible: false},
// 수준 (서브 컬럼 그룹)
{
title: '수준',
headerHozAlign: 'center',
columns: [
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 30,
title: '1',
field: 'LEVEL_1',
formatter: function(cell) {
return cell.getValue() === '*' ? '*' : '';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 30,
title: '2',
field: 'LEVEL_2',
formatter: function(cell) {
return cell.getValue() === '*' ? '*' : '';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 30,
title: '3',
field: 'LEVEL_3',
formatter: function(cell) {
return cell.getValue() === '*' ? '*' : '';
}
}
]
},
// 품번
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO_TITLE',
editor: 'input'
},
// 품명
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '품명',
field: 'PART_NAME',
editor: 'input'
},
// 수량
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 80,
title: '수량',
field: 'QTY',
editor: 'input',
validator: 'numeric'
},
// 항목 수량
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 80,
title: '항목 수량',
field: 'AGGREGATE_QTY',
editor: 'input',
validator: 'numeric'
},
// 3D
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '3D',
field: 'HAS_3D',
formatter: function(cell) {
return cell.getValue() === 'Y' ? '✓' : '';
}
},
// 2D
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '2D',
field: 'HAS_2D',
formatter: function(cell) {
return cell.getValue() === 'Y' ? '✓' : '';
}
},
// PDF
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: 'PDF',
field: 'HAS_PDF',
formatter: function(cell) {
return cell.getValue() === 'Y' ? '✓' : '';
}
},
// 재료
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '재료',
field: 'MATERIAL',
editor: 'input'
},
// 열처리경도
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '열처리경도',
field: 'HEAT_TREAT_HARDNESS',
editor: 'input'
},
// 열처리방법
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '열처리방법',
field: 'HEAT_TREAT_METHOD',
editor: 'input'
},
// 표면처리
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '표면처리',
field: 'SURFACE_TREATMENT',
editor: 'input'
},
// 공급업체
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '공급업체',
field: 'SUPPLIER_NAME',
editor: 'input'
},
// 범주 이름
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '범주 이름',
field: 'CATEGORY_NAME',
editor: 'input'
},
// 소재
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '소재',
field: 'RAW_MATERIAL',
editor: 'input'
},
// 사이즈
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '사이즈',
field: 'SIZE',
editor: 'input'
},
// 발주수량
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '발주수량',
field: 'ORDER_QTY',
editor: 'input',
validator: 'numeric'
},
// Q'ty
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 80,
title: "Q'ty",
field: 'QUANTITY',
editor: 'input',
validator: 'numeric'
},
// 제작수량
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '제작수량',
field: 'PRODUCTION_QTY',
editor: 'input',
validator: 'numeric'
},
// 가공업체
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '가공업체',
field: 'PROCESSOR_NAME',
editor: 'input'
},
// 가공납기
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 120,
title: '가공납기',
field: 'PROCESS_DUE_DATE',
editor: 'input'
},
// 연삭납기
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 120,
title: '연삭납기',
field: 'GRINDING_DUE_DATE',
editor: 'input'
}
];
// M-BOM 데이터 로드
function fn_loadMBomData() {
console.log("fn_loadMBomData 호출됨");
$.ajax({
url: '/productionplanning/getMBomGridData.do',
type: 'POST',
data: {
projectMgmtObjid: projectMgmtObjid
},
dataType: 'json',
success: function(response) {
console.log("서버 응답:", response);
console.log("response.success:", response.success);
console.log("response.data:", response.data);
console.log("hasBomData:", hasBomData);
if(response.success) {
var hasData = response.data && response.data.length > 0;
console.log("hasData:", hasData);
// M-BOM 데이터가 없고 E-BOM이 할당되어 있는 경우
if(!hasData && hasBomData) {
console.log("케이스 1: M-BOM 없음 + E-BOM 있음");
Swal.fire({
title: 'M-BOM 데이터 없음',
text: 'M-BOM 데이터가 없습니다. 할당된 E-BOM 데이터를 가져오시겠습니까?',
icon: 'question',
showCancelButton: true,
confirmButtonText: '예',
cancelButtonText: '아니오'
}).then((result) => {
if(result.isConfirmed) {
fn_copyEbomToMbom();
} else {
fn_createEmptyGrid();
}
});
}
// M-BOM 데이터가 없고 E-BOM도 없는 경우
else if(!hasData) {
console.log("케이스 2: M-BOM 없음 + E-BOM 없음");
Swal.fire({
title: 'E-BOM 미할당',
text: 'E-BOM이 할당되지 않았습니다. 먼저 E-BOM을 할당해주세요.',
icon: 'info'
});
fn_createEmptyGrid();
}
// M-BOM 데이터가 있는 경우
else {
console.log("케이스 3: M-BOM 있음");
fn_createGrid(response.data);
}
} else {
console.error("서버 오류:", response.message);
Swal.fire('오류', response.message, 'error');
}
},
error: function(jqxhr, status, error) {
console.error("AJAX 오류:", error);
console.error("jqxhr:", jqxhr);
Swal.fire('오류', '데이터 조회 중 오류가 발생했습니다.', 'error');
}
});
}
// 빈 그리드 생성
function fn_createEmptyGrid() {
_tabulGrid = new Tabulator("#mbomGrid", {
layout: "fitDataStretch",
height: "480px",
data: [],
columns: columns,
rowHeader: {formatter:"rownum", headerSort:false, hozAlign:"center", resizable:false, frozen:true, width:50},
placeholder: "E-BOM 데이터가 없습니다. E-BOM을 먼저 할당해주세요."
});
}
// 그리드 생성 (데이터 포함)
function fn_createGrid(data) {
// 데이터 변환: LEVEL 값에 따라 LEVEL_1, LEVEL_2, LEVEL_3 필드 추가
var transformedData = data.map(function(row) {
var level = parseInt(row.LEVEL) || 0;
// LEVEL 0 → 1번 컬럼에 *, LEVEL 1 → 2번 컬럼에 *, LEVEL 2 → 3번 컬럼에 *
row.LEVEL_1 = (level === 0) ? '*' : '';
row.LEVEL_2 = (level === 1) ? '*' : '';
row.LEVEL_3 = (level === 2) ? '*' : '';
return row;
});
_tabulGrid = new Tabulator("#mbomGrid", {
layout: "fitDataStretch",
height: "480px",
data: transformedData,
columns: columns,
rowHeader: {formatter:"rownum", headerSort:false, hozAlign:"center", resizable:false, frozen:true, width:50},
placeholder: "데이터가 없습니다.",
rowFormatter: function(row) {
var data = row.getData();
var level = parseInt(data.LEVEL) || 0;
// LEVEL 값에 1을 더해서 클래스 추가 (LEVEL 0 → level-1, LEVEL 1 → level-2)
row.getElement().classList.add('level-' + (level + 1));
}
});
}
// E-BOM을 M-BOM으로 복사
function fn_copyEbomToMbom() {
console.log("fn_copyEbomToMbom 호출됨");
console.log("전달할 projectMgmtObjid:", projectMgmtObjid);
if(!projectMgmtObjid) {
console.error("projectMgmtObjid가 없습니다!");
Swal.fire('오류', '프로젝트 정보가 없습니다.', 'error');
return;
}
$.ajax({
url: '/productionplanning/copyEbomToMbom.do',
type: 'POST',
data: {
projectMgmtObjid: projectMgmtObjid
},
dataType: 'json',
success: function(response) {
console.log("copyEbomToMbom 응답:", response);
if(response.success) {
fn_createGrid(response.data);
// 자동으로 저장
setTimeout(function() {
fn_autoSaveMBomData(response.data);
}, 500);
} else {
Swal.fire('오류', response.message, 'error');
fn_createEmptyGrid();
}
},
error: function(jqxhr, status, error) {
console.error("copyEbomToMbom 오류:", error);
Swal.fire('오류', 'E-BOM 복사 중 오류가 발생했습니다.', 'error');
fn_createEmptyGrid();
}
});
}
// M-BOM 데이터 저장
function fn_saveMBomData() {
if(!_tabulGrid) {
Swal.fire('경고', '저장할 데이터가 없습니다.', 'warning');
return;
}
var gridData = _tabulGrid.getData();
if(!gridData || gridData.length === 0) {
Swal.fire('경고', '저장할 데이터가 없습니다.', 'warning');
return;
}
Swal.fire({
title: 'M-BOM 저장',
text: 'M-BOM 데이터를 저장하시겠습니까?',
icon: 'question',
showCancelButton: true,
confirmButtonText: '저장',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
$.ajax({
url: '/productionplanning/saveMBomData.do',
type: 'POST',
data: {
projectMgmtObjid: projectMgmtObjid,
gridData: JSON.stringify(gridData)
},
dataType: 'json',
success: function(response) {
if(response.success) {
Swal.fire('성공', response.message, 'success').then(() => {
// 부모창 새로고침
if(window.opener && !window.opener.closed) {
window.opener.fn_search();
}
window.close();
});
} else {
Swal.fire('오류', response.message, 'error');
}
},
error: function(jqxhr, status, error) {
Swal.fire('오류', '저장 중 오류가 발생했습니다.', 'error');
console.error(error);
}
});
}
});
}
// E-BOM 복사 후 자동 저장
function fn_autoSaveMBomData(gridData) {
console.log("fn_autoSaveMBomData 호출됨");
if(!gridData || gridData.length === 0) {
Swal.fire('경고', '저장할 데이터가 없습니다.', 'warning');
return;
}
$.ajax({
url: '/productionplanning/saveMBomData.do',
type: 'POST',
data: {
projectMgmtObjid: projectMgmtObjid,
gridData: JSON.stringify(gridData)
},
dataType: 'json',
success: function(response) {
if(response.success) {
Swal.fire({
title: 'E-BOM 복사 및 저장 완료',
text: 'E-BOM 데이터를 M-BOM으로 복사하여 저장했습니다.',
icon: 'success'
}).then(() => {
// 부모창 새로고침
if(window.opener && !window.opener.closed) {
window.opener.fn_search();
}
// 현재 팝업 새로고침
location.reload();
});
} else {
Swal.fire('오류', response.message, 'error');
}
},
error: function(jqxhr, status, error) {
Swal.fire('오류', '저장 중 오류가 발생했습니다.', 'error');
console.error(error);
}
});
}
// M-BOM 데이터 삭제
function fn_deleteMBomData() {
if(!_tabulGrid) {
Swal.fire('경고', '삭제할 데이터가 없습니다.', 'warning');
return;
}
var gridData = _tabulGrid.getData();
if(!gridData || gridData.length === 0) {
Swal.fire('경고', '삭제할 데이터가 없습니다.', 'warning');
return;
}
Swal.fire({
title: 'M-BOM 삭제',
text: 'M-BOM 데이터를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '삭제',
cancelButtonText: '취소',
confirmButtonColor: '#d33'
}).then((result) => {
if(result.isConfirmed) {
$.ajax({
url: '/productionplanning/deleteMBomData.do',
type: 'POST',
data: {
projectMgmtObjid: projectMgmtObjid
},
dataType: 'json',
success: function(response) {
if(response.success) {
Swal.fire('성공', 'M-BOM 데이터가 삭제되었습니다.', 'success').then(() => {
// 부모창 새로고침
if(window.opener && !window.opener.closed) {
window.opener.fn_search();
}
window.close();
});
} else {
Swal.fire('오류', response.message, 'error');
}
},
error: function(jqxhr, status, error) {
Swal.fire('오류', '삭제 중 오류가 발생했습니다.', 'error');
console.error(error);
}
});
}
});
}
</script>
</head>
<body>
<form name="form1" id="form1" method="post">
<input type="hidden" name="projectMgmtObjid" id="projectMgmtObjid" value="${projectInfo.OBJID}">
<div class="mbom-popup-container">
<!-- 상단: 정보 및 버튼 -->
<div class="header-section">
<h3>M-BOM 관리 - 프로젝트: ${projectInfo.PROJECT_NO} / 품번: ${projectInfo.PART_NO} / 품명: ${projectInfo.PART_NAME}</h3>
<div>
<input type="button" value="저장" class="plm_btns" id="btnSave" style="background-color: #4CAF50; color: white;">
<input type="button" value="삭제" class="plm_btns" id="btnDelete" style="background-color: #f44336; color: white;">
<input type="button" value="닫기" class="plm_btns" id="btnClose">
</div>
</div>
<!-- M-BOM 그리드 -->
<div class="grid-section">
<div id="mbomGrid"></div>
</div>
</div>
</form>
</body>
</html>

View File

@@ -283,7 +283,7 @@ function fn_openEBomSelectPopup(projectMgmtObjid, partNo, partName, bomReportObj
// M-BOM 팝업
function fn_openMBomPopup(objId) {
var popup_width = 1800;
var popup_width = 1400;
var popup_height = 800;
var url = "/productionplanning/mBomFormPopup.do?objId=" + objId;
fn_centerPopup(popup_width, popup_height, url, 'mbomPopup');