From 3b33b0c1333dc0b967e6d0358383f7d292da50c7 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 17 Dec 2025 11:59:03 +0900 Subject: [PATCH] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=84=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classes/com/pms/mapper/quality.xml | 27 + .../semiProductInspectionFormPopUp.jsp | 559 +++++++++++++++--- src/com/pms/controller/QualityController.java | 37 ++ src/com/pms/mapper/quality.xml | 27 + src/com/pms/service/QualityService.java | 161 ++++- 5 files changed, 713 insertions(+), 98 deletions(-) diff --git a/WebContent/WEB-INF/classes/com/pms/mapper/quality.xml b/WebContent/WEB-INF/classes/com/pms/mapper/quality.xml index 5179bf6..1028007 100644 --- a/WebContent/WEB-INF/classes/com/pms/mapper/quality.xml +++ b/WebContent/WEB-INF/classes/com/pms/mapper/quality.xml @@ -1582,6 +1582,7 @@ , COALESCE(SPI.REMARK, '') AS "REMARK" , SPI.DATA_TYPE AS "DATA_TYPE" , COALESCE(SPI.INSPECTION_GROUP_ID, '') AS "INSPECTION_GROUP_ID" + , COALESCE(SPI.IS_LOCKED, 'N') AS "IS_LOCKED" , (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = SPI.OBJID AND AFI.DOC_TYPE = 'SEMI_INSPECTION_IMAGE' AND AFI.STATUS = 'Active') AS "IMAGE_FILE_CNT" , (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = SPI.OBJID AND AFI.DOC_TYPE = 'SEMI_INSPECTION_NCR' AND AFI.STATUS = 'Active') AS "NCR_FILE_CNT" @@ -1722,6 +1723,19 @@ WHERE OBJID = #{OBJID} + + + UPDATE PMS_QUALITY_SEMI_PRODUCT_INSPECTION + SET IS_LOCKED = 'Y' + WHERE OBJID = #{OBJID} + + + + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION + WHERE OBJID = #{OBJID} + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION @@ -1734,6 +1748,19 @@ + + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION + WHERE INSPECTION_GROUP_ID = #{INSPECTION_GROUP_ID} + AND DATA_TYPE = #{DATA_TYPE} + + AND OBJID NOT IN + + #{objId} + + + + diff --git a/WebContent/WEB-INF/view/quality/semiProductInspectionFormPopUp.jsp b/WebContent/WEB-INF/view/quality/semiProductInspectionFormPopUp.jsp index ca3445d..4aac99e 100644 --- a/WebContent/WEB-INF/view/quality/semiProductInspectionFormPopUp.jsp +++ b/WebContent/WEB-INF/view/quality/semiProductInspectionFormPopUp.jsp @@ -232,6 +232,7 @@ String loginUserId = CommonUtils.checkNull(person.getUserId());
+
@@ -246,6 +247,7 @@ String loginUserId = CommonUtils.checkNull(person.getUserId());
+
@@ -311,7 +313,9 @@ $(document).ready(function(){ } // 버튼 이벤트 - $("#btnSave").click(fn_save); + $("#btnSave").click(fn_save); // 하단 저장: 전체 DB 저장 + $("#btnSaveLeft").click(fn_saveSelectedLeft); // 좌측 저장: UI 잠금만 (DB 저장 X) + $("#btnSaveRight").click(fn_saveSelectedRight); // 우측 저장: UI 잠금만 (DB 저장 X) $("#btnClose").click(function(){ window.close(); }); $("#btnAddLeft").click(fn_addLeftRow); $("#btnDelLeft").click(fn_delLeftRow); @@ -393,6 +397,11 @@ function createSelect2Editor(options, allowClear) { }; } +// 저장된 행인지 확인하는 함수 (편집 가능 여부 결정) +function isEditable(cell){ + return !cell.getRow().getData().IS_SAVED; +} + // ===================================================== // 좌측 그리드 (양품 정보) 초기화 // ===================================================== @@ -400,14 +409,19 @@ function fn_initLeftGrid(){ var columns = [ {formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", headerSort:false, width:30}, {title:"품명(모델명)", field:"MODEL_NAME", minWidth:120, headerSort:false, - editor: createSelect2Editor(modelNameList) + editor: createSelect2Editor(modelNameList), + editable: isEditable }, {title:"제품구분", field:"PRODUCT_TYPE", minWidth:80, headerSort:false, - editor: createSelect2Editor(productTypeList) + editor: createSelect2Editor(productTypeList), + editable: isEditable + }, + {title:"작업지시번호", field:"WORK_ORDER_NO", editor:"input", minWidth:100, headerSort:false, + editable: isEditable }, - {title:"작업지시번호", field:"WORK_ORDER_NO", editor:"input", minWidth:100, headerSort:false}, {title:"부품품번", field:"PART_NO", minWidth:100, headerSort:false, editor: createSelect2Editor(partNoList), + editable: isEditable, cellEdited: function(cell){ var partNo = cell.getValue(); if(partNo){ @@ -423,6 +437,7 @@ function fn_initLeftGrid(){ }, {title:"부품명", field:"PART_NAME", minWidth:120, headerSort:false, editor: createSelect2Editor(partNameList), + editable: isEditable, cellEdited: function(cell){ var partName = cell.getValue(); if(partName){ @@ -438,6 +453,7 @@ function fn_initLeftGrid(){ }, {title:"입고수량", field:"RECEIPT_QTY", editor:"number", hozAlign:"right", minWidth:70, headerSort:false, editorParams:{min:0, step:1}, + editable: isEditable, formatter: function(cell){ var val = cell.getValue(); return (val !== null && val !== undefined && val !== "") ? Number(val).toLocaleString() : "0"; @@ -445,6 +461,7 @@ function fn_initLeftGrid(){ }, {title:"양품수량", field:"GOOD_QTY", editor:"number", hozAlign:"right", minWidth:70, headerSort:false, editorParams:{min:0, step:1}, + editable: isEditable, formatter: function(cell){ var val = cell.getValue(); return (val !== null && val !== undefined && val !== "") ? Number(val).toLocaleString() : "0"; @@ -458,7 +475,14 @@ function fn_initLeftGrid(){ columns: columns, data: [], placeholder: "행을 추가해주세요.", - selectable: 1 // 단일 선택 + selectable: 1, // 단일 선택 + // 저장된 행 시각적 표시 + rowFormatter: function(row){ + if(row.getData().IS_SAVED){ + row.getElement().style.backgroundColor = "#e8f5e9"; // 연한 초록색 + row.getElement().style.color = "#555"; + } + } }); // 행 선택 이벤트 - 해당 행의 불량 정보를 우측에 표시 @@ -493,65 +517,72 @@ function fn_initRightGrid(){ {formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", headerSort:false, width:30}, {title:"불량수량", field:"DEFECT_QTY", editor:"number", hozAlign:"right", minWidth:70, headerSort:false, editorParams:{min:0, step:1}, + editable: isEditable, formatter: function(cell){ var val = cell.getValue(); return (val !== null && val !== undefined && val !== "") ? Number(val).toLocaleString() : "0"; } }, {title:"불량유형", field:"DEFECT_TYPE", minWidth:85, headerSort:false, - editor: createSelect2Editor(defectTypeList) + editor: createSelect2Editor(defectTypeList), + editable: isEditable }, {title:"불량원인", field:"DEFECT_CAUSE", minWidth:90, headerSort:false, - editor: createSelect2Editor(defectCauseList) + editor: createSelect2Editor(defectCauseList), + editable: isEditable }, {title:"귀책부서", field:"RESPONSIBLE_DEPT", minWidth:80, headerSort:false, - editor: createSelect2Editor(responsibleDeptList) + editor: createSelect2Editor(responsibleDeptList), + editable: isEditable }, {title:"부적합보고서", field:"NCR_FILE_CNT", minWidth:75, headerSort:false, hozAlign:"center", formatter: fnc_subInfoValueFormatter, cellClick: function(e, cell){ var objId = fnc_checkNull(cell.getData().OBJID); - if(objId && !objId.startsWith("DEFECT_")){ + if(objId){ fn_openNCRFilePopUp(objId); } else { - Swal.fire({icon:'info', title:'알림', text:'먼저 저장 후 파일을 등록할 수 있습니다.'}); + Swal.fire({icon:'info', title:'알림', text:'행을 추가한 후 파일을 등록할 수 있습니다.'}); } } }, {title:"처리현황", field:"PROCESS_STATUS", minWidth:75, headerSort:false, - editor: createSelect2Editor(processStatusList) + editor: createSelect2Editor(processStatusList), + editable: isEditable }, {title:"이미지", field:"IMAGE_FILE_CNT", minWidth:60, headerSort:false, hozAlign:"center", formatter: fnc_subInfoValueFormatter, cellClick: function(e, cell){ var objId = fnc_checkNull(cell.getData().OBJID); - if(objId && !objId.startsWith("DEFECT_")){ + if(objId){ fn_openImageFilePopUp(objId); } else { - Swal.fire({icon:'info', title:'알림', text:'먼저 저장 후 이미지를 등록할 수 있습니다.'}); + Swal.fire({icon:'info', title:'알림', text:'행을 추가한 후 이미지를 등록할 수 있습니다.'}); } } }, {title:"검사일", field:"INSPECTION_DATE", minWidth:100, headerSort:false, editor: "input", - editorParams: { elementAttributes: { type: "date" } } + editorParams: { elementAttributes: { type: "date" } }, + editable: isEditable }, - {title:"검사자", field:"INSPECTOR", editor:"input", minWidth:70, headerSort:false}, + {title:"검사자", field:"INSPECTOR", editor:"input", minWidth:70, headerSort:false, editable: isEditable}, {title:"처리결과", field:"DISPOSITION_TYPE", minWidth:75, headerSort:false, - editor: createSelect2Editor(dispositionTypeList) + editor: createSelect2Editor(dispositionTypeList), + editable: isEditable }, {title:"검사성적서", field:"REPORT_FILE_CNT", minWidth:70, headerSort:false, hozAlign:"center", formatter: fnc_subInfoValueFormatter, cellClick: function(e, cell){ var objId = fnc_checkNull(cell.getData().OBJID); - if(objId && !objId.startsWith("DEFECT_")){ + if(objId){ fn_openReportFilePopUp(objId); } else { - Swal.fire({icon:'info', title:'알림', text:'먼저 저장 후 파일을 등록할 수 있습니다.'}); + Swal.fire({icon:'info', title:'알림', text:'행을 추가한 후 파일을 등록할 수 있습니다.'}); } } }, - {title:"비고", field:"REMARK", editor:"input", minWidth:100, headerSort:false} + {title:"비고", field:"REMARK", editor:"input", minWidth:100, headerSort:false, editable: isEditable} ]; rightGrid = new Tabulator("#rightGrid", { @@ -560,7 +591,14 @@ function fn_initRightGrid(){ columns: columns, data: [], placeholder: "좌측에서 양품 정보를 선택하세요.", - selectable: true + selectable: true, + // 저장된 행 시각적 표시 + rowFormatter: function(row){ + if(row.getData().IS_SAVED){ + row.getElement().style.backgroundColor = "#e8f5e9"; // 연한 초록색 + row.getElement().style.color = "#555"; + } + } }); } @@ -608,22 +646,38 @@ function fn_clearRightGrid(){ // 좌측 그리드 행 추가 // ===================================================== function fn_addLeftRow(){ - rowSeq++; - var newRowId = "NEW_" + rowSeq; - - leftGrid.addRow({ - ROW_ID: newRowId, - MODEL_NAME: "", - PRODUCT_TYPE: "", - WORK_ORDER_NO: "", - PART_NO: "", - PART_NAME: "", - RECEIPT_QTY: 0, - GOOD_QTY: 0 + // 서버에서 OBJID 미리 생성 + $.ajax({ + url: "/quality/generateObjId.do", + type: "POST", + async: false, + dataType: "json", + success: function(result){ + if(result.result){ + rowSeq++; + var newObjId = result.OBJID; + + leftGrid.addRow({ + ROW_ID: newObjId, // OBJID를 ROW_ID로 사용 + OBJID: newObjId, // 서버에서 생성한 실제 OBJID + MODEL_NAME: "", + PRODUCT_TYPE: "", + WORK_ORDER_NO: "", + PART_NO: "", + PART_NAME: "", + RECEIPT_QTY: 0, + GOOD_QTY: 0 + }); + + // 새 행에 대한 불량 데이터 배열 초기화 + allDefectData[newObjId] = []; + } + }, + error: function(xhr, status, error){ + console.error("OBJID 생성 실패:", error); + Swal.fire({ icon: 'error', title: '오류', text: 'OBJID 생성에 실패했습니다.' }); + } }); - - // 새 행에 대한 불량 데이터 배열 초기화 - allDefectData[newRowId] = []; } // 좌측 그리드 행 삭제 @@ -645,13 +699,54 @@ function fn_delLeftRow(){ cancelButtonText: '취소' }).then(function(result){ if(result.isConfirmed){ + var objIdsToDelete = []; + selectedRows.forEach(function(row){ - var rowId = row.getData().ROW_ID; - // 해당 행의 불량 데이터도 삭제 - delete allDefectData[rowId]; + var rowData = row.getData(); + var rowId = rowData.ROW_ID; + var objId = rowData.OBJID; + + // DB에 저장된 데이터면 삭제 목록에 추가 + if(objId && !String(objId).startsWith("NEW_")){ + objIdsToDelete.push(objId); + } + + // 해당 행의 불량 데이터도 삭제 목록에 추가 + if(allDefectData[rowId]){ + allDefectData[rowId].forEach(function(defect){ + if(defect.OBJID && !String(defect.OBJID).startsWith("DEFECT_")){ + objIdsToDelete.push(defect.OBJID); + } + }); + delete allDefectData[rowId]; + } + row.delete(); }); + // DB에서 삭제 + if(objIdsToDelete.length > 0){ + $.ajax({ + url: "/quality/deleteSemiProductInspection.do", + type: "POST", + data: { objIds: JSON.stringify(objIdsToDelete) }, + dataType: "json", + success: function(result){ + if(result.result){ + console.log("DB 삭제 완료:", objIdsToDelete.length + "건"); + if(window.opener && window.opener.fn_search){ + window.opener.fn_search(); + } + } else { + console.error("DB 삭제 실패:", result.msg); + } + }, + error: function(xhr, status, error){ + console.error("삭제 오류:", error); + } + }); + } + // 우측 그리드 초기화 selectedLeftRowId = null; selectedLeftRowData = null; @@ -669,22 +764,39 @@ function fn_addRightRow(){ return; } - rowSeq++; - rightGrid.addRow({ - ROW_ID: "DEFECT_" + rowSeq, - PARENT_ROW_ID: selectedLeftRowId, // 부모(좌측) 행 ID 연결 - DEFECT_QTY: 0, - DEFECT_TYPE: "", - DEFECT_CAUSE: "", - RESPONSIBLE_DEPT: "", - NCR_FILE_CNT: 0, - PROCESS_STATUS: "", - IMAGE_FILE_CNT: 0, - INSPECTION_DATE: today, - INSPECTOR: loginUserName, - DISPOSITION_TYPE: "", - REPORT_FILE_CNT: 0, - REMARK: "" + // 서버에서 OBJID 미리 생성 (첨부파일 등록을 위해) + $.ajax({ + url: "/quality/generateObjId.do", + type: "POST", + async: false, + dataType: "json", + success: function(result){ + if(result.result){ + rowSeq++; + rightGrid.addRow({ + OBJID: result.OBJID, // 서버에서 생성한 실제 OBJID + ROW_ID: result.OBJID, // ROW_ID도 동일하게 + PARENT_ROW_ID: selectedLeftRowId, + DEFECT_QTY: 0, + DEFECT_TYPE: "", + DEFECT_CAUSE: "", + RESPONSIBLE_DEPT: "", + NCR_FILE_CNT: 0, + PROCESS_STATUS: "", + IMAGE_FILE_CNT: 0, + INSPECTION_DATE: today, + INSPECTOR: loginUserName, + DISPOSITION_TYPE: "", + REPORT_FILE_CNT: 0, + REMARK: "" + }); + } else { + Swal.fire({icon:'error', title:'오류', text:'행 추가 중 오류가 발생했습니다.'}); + } + }, + error: function(){ + Swal.fire({icon:'error', title:'오류', text:'서버 통신 오류가 발생했습니다.'}); + } }); } @@ -695,8 +807,52 @@ function fn_delRightRow(){ Swal.fire("삭제할 행을 선택해주세요."); return; } - selectedRows.forEach(function(row){ - row.delete(); + + Swal.fire({ + title: '삭제 확인', + text: '선택한 불량 정보를 삭제하시겠습니까?', + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: '삭제', + cancelButtonText: '취소' + }).then(function(result){ + if(result.isConfirmed){ + var objIdsToDelete = []; + + selectedRows.forEach(function(row){ + var objId = row.getData().OBJID; + // DB에 저장된 데이터면 삭제 목록에 추가 + if(objId && !String(objId).startsWith("DEFECT_")){ + objIdsToDelete.push(objId); + } + row.delete(); + }); + + // DB에서 삭제 + if(objIdsToDelete.length > 0){ + $.ajax({ + url: "/quality/deleteSemiProductInspection.do", + type: "POST", + data: { objIds: JSON.stringify(objIdsToDelete) }, + dataType: "json", + success: function(result){ + if(result.result){ + console.log("DB 삭제 완료:", objIdsToDelete.length + "건"); + if(window.opener && window.opener.fn_search){ + window.opener.fn_search(); + } + } else { + console.error("DB 삭제 실패:", result.msg); + } + }, + error: function(xhr, status, error){ + console.error("삭제 오류:", error); + } + }); + } + } }); } @@ -730,6 +886,7 @@ function fn_openReportFilePopUp(objId) { fn_watchPopupClose(popup, objId, 'REPORT_FILE_CNT', 'SEMI_INSPECTION_REPORT'); } + // 팝업 닫힘 감지 및 파일 카운트 업데이트 function fn_watchPopupClose(popup, objId, fieldName, docType) { var checkPopup = setInterval(function() { @@ -801,9 +958,11 @@ function fn_loadData(objid, inspectionGroupId){ console.log("수정 모드 - INSPECTION_GROUP_ID:", currentInspectionGroupId); } - // 좌측 데이터에 ROW_ID 부여 + // 좌측 데이터에 ROW_ID 부여 + IS_LOCKED='Y'인 행만 수정 불가 result.leftData.forEach(function(item, idx){ if(!item.ROW_ID) item.ROW_ID = item.OBJID || ("EXIST_" + (idx + 1)); + // IS_LOCKED='Y'인 행만 수정 불가 (IS_SAVED=true) + item.IS_SAVED = (item.IS_LOCKED == 'Y'); rowSeq = Math.max(rowSeq, idx + 1); }); leftGrid.setData(result.leftData); @@ -824,6 +983,8 @@ function fn_loadData(objid, inspectionGroupId){ if(!allDefectData[parentId]) allDefectData[parentId] = []; defect.PARENT_ROW_ID = parentId; + // IS_LOCKED='Y'인 행만 수정 불가 (IS_SAVED=true) + defect.IS_SAVED = (defect.IS_LOCKED == 'Y'); allDefectData[parentId].push(defect); }); } @@ -838,34 +999,124 @@ function fn_loadData(objid, inspectionGroupId){ } // ===================================================== -// 저장 +// 좌측 행 잠금: 선택된 양품 행을 수정 불가로 변경 (DB에 잠금 상태 저장) // ===================================================== -function fn_save(){ - // 현재 우측 데이터 저장 - fn_saveRightGridData(); - - var leftData = leftGrid.getData(); - - if(leftData.length == 0){ - Swal.fire({ icon: 'warning', title: '알림', text: '저장할 양품 정보가 없습니다.' }); +function fn_saveSelectedLeft(){ + if(!selectedLeftRowId){ + Swal.fire({ icon: 'warning', title: '알림', text: '잠금할 양품 정보를 선택해주세요.' }); return; } - // 모든 불량 데이터 수집 - var rightData = []; - Object.keys(allDefectData).forEach(function(leftRowId){ - var defects = allDefectData[leftRowId] || []; - defects.forEach(function(defect){ - defect.PARENT_ROW_ID = leftRowId; - rightData.push(defect); - }); + // 선택된 좌측 행 + var selectedRow = leftGrid.getSelectedRows()[0]; + if(!selectedRow){ + Swal.fire({ icon: 'warning', title: '알림', text: '잠금할 양품 정보를 선택해주세요.' }); + return; + } + + var rowData = selectedRow.getData(); + var objId = rowData.OBJID; + + // 아직 DB에 저장되지 않은 행은 잠금 불가 + if(!objId || String(objId).startsWith("NEW_")){ + Swal.fire({ icon: 'warning', title: '알림', text: '먼저 하단 저장 버튼을 눌러 DB에 저장한 후 잠금할 수 있습니다.' }); + return; + } + + // DB에 잠금 요청 + $.ajax({ + url: "/quality/lockSemiProductInspection.do", + type: "POST", + data: { objIds: JSON.stringify([objId]) }, + dataType: "json", + success: function(result){ + if(result.result){ + // UI에서도 수정 불가 처리 + selectedRow.update({ IS_SAVED: true, IS_LOCKED: 'Y' }); + Swal.fire({ icon: 'success', title: '완료', text: '양품 정보가 잠금 처리되었습니다.' }); + } else { + Swal.fire({ icon: 'error', title: '오류', text: result.msg || '잠금 처리에 실패했습니다.' }); + } + }, + error: function(xhr, status, error){ + Swal.fire({ icon: 'error', title: '오류', text: '잠금 처리 중 오류가 발생했습니다.' }); + } + }); +} + +// ===================================================== +// 우측 행 잠금: 선택된 불량 행을 수정 불가로 변경 (DB에 잠금 상태 저장) +// ===================================================== +function fn_saveSelectedRight(){ + if(!selectedLeftRowId){ + Swal.fire({ icon: 'warning', title: '알림', text: '좌측에서 양품 정보를 먼저 선택해주세요.' }); + return; + } + + // 선택된 우측 행만 가져오기 + var selectedRightRows = rightGrid.getSelectedRows(); + + if(selectedRightRows.length == 0){ + Swal.fire({ icon: 'warning', title: '알림', text: '잠금할 불량 정보를 선택해주세요.' }); + return; + } + + // DB에 저장된 행만 잠금 가능 + var objIdsToLock = []; + var unsavedCount = 0; + selectedRightRows.forEach(function(row){ + var objId = row.getData().OBJID; + if(objId && !String(objId).startsWith("DEFECT_") && !String(objId).startsWith("NEW_")){ + objIdsToLock.push(objId); + } else { + unsavedCount++; + } }); - // 저장 확인 + if(unsavedCount > 0){ + Swal.fire({ icon: 'warning', title: '알림', text: '아직 DB에 저장되지 않은 행이 ' + unsavedCount + '건 있습니다. 먼저 하단 저장 버튼을 눌러 저장해주세요.' }); + return; + } + + if(objIdsToLock.length == 0){ + Swal.fire({ icon: 'warning', title: '알림', text: '잠금할 행이 없습니다.' }); + return; + } + + // DB에 잠금 요청 + $.ajax({ + url: "/quality/lockSemiProductInspection.do", + type: "POST", + data: { objIds: JSON.stringify(objIdsToLock) }, + dataType: "json", + success: function(result){ + if(result.result){ + // UI에서도 수정 불가 처리 + selectedRightRows.forEach(function(row){ + row.update({ IS_SAVED: true, IS_LOCKED: 'Y' }); + }); + Swal.fire({ icon: 'success', title: '완료', text: '불량 정보 ' + objIdsToLock.length + '건이 잠금 처리되었습니다.' }); + } else { + Swal.fire({ icon: 'error', title: '오류', text: result.msg || '잠금 처리에 실패했습니다.' }); + } + }, + error: function(xhr, status, error){ + Swal.fire({ icon: 'error', title: '오류', text: '잠금 처리 중 오류가 발생했습니다.' }); + } + }); +} + +// ===================================================== +// 실제 저장 실행 함수 +// saveType: 'left' (좌측만), 'right' (우측만), 'all' (전체) +// ===================================================== +function fn_doSave(leftData, rightData, confirmMsg, saveType){ + saveType = saveType || 'all'; // 기본값은 전체 저장 + Swal.fire({ icon: 'question', title: '저장 확인', - text: '양품 ' + leftData.length + '건, 불량 ' + rightData.length + '건을 저장하시겠습니까?', + text: confirmMsg, showCancelButton: true, confirmButtonText: '저장', cancelButtonText: '취소' @@ -874,7 +1125,8 @@ function fn_save(){ var paramData = { leftData: JSON.stringify(leftData), rightData: JSON.stringify(rightData), - INSPECTION_GROUP_ID: currentInspectionGroupId || "" // 수정 모드일 때 기존 그룹 ID 전달 + INSPECTION_GROUP_ID: currentInspectionGroupId || "", + saveType: saveType // 저장 타입 추가 }; $.ajax({ @@ -884,15 +1136,23 @@ function fn_save(){ dataType: "json", success: function(result){ if(result.result == true || result.result == "true"){ + // 저장된 INSPECTION_GROUP_ID 유지 (다음 저장 시 같은 그룹으로 저장) + if(result.inspectionGroupId){ + currentInspectionGroupId = result.inspectionGroupId; + console.log("INSPECTION_GROUP_ID 설정:", currentInspectionGroupId); + } + Swal.fire({ icon: 'success', title: '저장 완료', text: '저장되었습니다.' }).then(function(){ + // 부모 창 새로고침 if(window.opener && window.opener.fn_search){ window.opener.fn_search(); } - window.close(); + // 데이터 다시 로드 (OBJID 동기화를 위해) + fn_loadData('', currentInspectionGroupId); }); } else { Swal.fire({ @@ -913,6 +1173,149 @@ function fn_save(){ } }); } + +// 저장된 행을 읽기 전용으로 마킹하는 함수 +function fn_markAsSaved(savedLeftData, savedRightData){ + // 좌측 그리드 행 업데이트 + if(savedLeftData && savedLeftData.length > 0){ + savedLeftData.forEach(function(leftItem){ + var rowId = leftItem.ROW_ID; + var rows = leftGrid.getRows(); + rows.forEach(function(row){ + if(row.getData().ROW_ID === rowId){ + row.update({ IS_SAVED: true }); + } + }); + }); + } + + // 우측 그리드 행 업데이트 (현재 표시된 데이터) + if(savedRightData && savedRightData.length > 0){ + savedRightData.forEach(function(rightItem){ + var objId = rightItem.OBJID; + var rows = rightGrid.getRows(); + rows.forEach(function(row){ + if(row.getData().OBJID === objId || row.getData().ROW_ID === rightItem.ROW_ID){ + row.update({ IS_SAVED: true }); + } + }); + + // allDefectData에도 IS_SAVED 설정 + var parentId = rightItem.PARENT_ROW_ID; + if(parentId && allDefectData[parentId]){ + allDefectData[parentId].forEach(function(defect){ + if(defect.OBJID === objId || defect.ROW_ID === rightItem.ROW_ID){ + defect.IS_SAVED = true; + } + }); + } + }); + } +} + +// ===================================================== +// 전체 저장 +// ===================================================== +function fn_save(){ + try { + console.log("fn_save 시작"); + + // 현재 선택된 좌측 행이 있으면 우측 데이터를 allDefectData에 저장 + if(selectedLeftRowId){ + var currentRightData = rightGrid.getData(); + allDefectData[selectedLeftRowId] = currentRightData; + console.log("현재 우측 그리드 데이터 저장:", selectedLeftRowId, currentRightData.length + "건"); + } + + var leftData = leftGrid.getData(); + console.log("좌측 데이터 수:", leftData.length); + + if(leftData.length == 0){ + Swal.fire({ icon: 'warning', title: '알림', text: '저장할 양품 정보가 없습니다.' }); + return; + } + + // 모든 불량 데이터 수집 (allDefectData에서) + var rightData = []; + Object.keys(allDefectData).forEach(function(leftRowId){ + var defects = allDefectData[leftRowId] || []; + defects.forEach(function(defect){ + defect.PARENT_ROW_ID = leftRowId; + // 좌측 데이터에서 부모 정보 찾아서 복사 + var parentLeft = leftData.find(function(l){ return l.ROW_ID == leftRowId; }); + if(parentLeft){ + defect.MODEL_NAME = parentLeft.MODEL_NAME || ''; + defect.PRODUCT_TYPE = parentLeft.PRODUCT_TYPE || ''; + defect.WORK_ORDER_NO = parentLeft.WORK_ORDER_NO || ''; + defect.PART_NO = parentLeft.PART_NO || ''; + defect.PART_NAME = parentLeft.PART_NAME || ''; + } + rightData.push(defect); + }); + }); + console.log("우측 데이터 수:", rightData.length); + + // 바로 저장 실행 (확인 팝업 생략) + console.log("저장 시작 - currentInspectionGroupId:", currentInspectionGroupId); + console.log("좌측 데이터:", leftData); + console.log("우측 데이터:", rightData); + + var paramData = { + leftData: JSON.stringify(leftData), + rightData: JSON.stringify(rightData), + INSPECTION_GROUP_ID: currentInspectionGroupId || "", + saveType: "all" + }; + + $.ajax({ + url: "/quality/saveSemiProductInspection.do", + type: "POST", + data: paramData, + dataType: "json", + success: function(result){ + console.log("저장 결과:", result); + if(result.result == true || result.result == "true"){ + // 저장된 INSPECTION_GROUP_ID 유지 + if(result.inspectionGroupId){ + currentInspectionGroupId = result.inspectionGroupId; + console.log("INSPECTION_GROUP_ID 설정:", currentInspectionGroupId); + } + + // 저장된 행에 IS_SAVED 플래그 설정 + fn_markAsSaved(leftData, rightData); + + Swal.fire({ + icon: 'success', + title: '저장 완료', + text: '저장되었습니다.' + }).then(function(){ + if(window.opener && window.opener.fn_search){ + window.opener.fn_search(); + } + window.close(); + }); + } else { + Swal.fire({ + icon: 'error', + title: '저장 실패', + text: result.msg || '저장에 실패했습니다.' + }); + } + }, + error: function(xhr, status, error){ + console.error("AJAX 오류:", error); + Swal.fire({ + icon: 'error', + title: '오류 발생', + text: '저장 중 오류가 발생했습니다: ' + error + }); + } + }); + } catch(e) { + console.error("fn_save 오류:", e); + alert("저장 중 오류 발생: " + e.message); + } +} diff --git a/src/com/pms/controller/QualityController.java b/src/com/pms/controller/QualityController.java index 499278b..65e6fc3 100644 --- a/src/com/pms/controller/QualityController.java +++ b/src/com/pms/controller/QualityController.java @@ -664,6 +664,24 @@ public class QualityController { return service.getSemiProductInspectionDetail(paramMap); } + /** + * 반제품검사 삭제 + */ + @ResponseBody + @RequestMapping("/quality/deleteSemiProductInspection.do") + public Map deleteSemiProductInspection(HttpServletRequest request, @RequestParam Map paramMap){ + return service.deleteSemiProductInspection(paramMap); + } + + /** + * 반제품검사 행 잠금 (수정 불가 처리) + */ + @ResponseBody + @RequestMapping("/quality/lockSemiProductInspection.do") + public Map lockSemiProductInspection(HttpServletRequest request, @RequestParam Map paramMap){ + return service.lockSemiProductInspection(paramMap); + } + /** * 반제품검사 엑셀 다운로드 (JSP 방식) */ @@ -904,4 +922,23 @@ public class QualityController { return service.saveIncomingInspectionProgress(request, paramMap); } + /** + * OBJID 생성 (첨부파일 등록을 위한 미리 생성) + */ + @ResponseBody + @RequestMapping("/quality/generateObjId.do") + public Map generateObjId(HttpServletRequest request, @RequestParam Map paramMap){ + Map result = new HashMap(); + try { + String objId = CommonUtils.createObjId(); + result.put("OBJID", objId); + result.put("result", true); + } catch(Exception e) { + e.printStackTrace(); + result.put("result", false); + result.put("msg", e.getMessage()); + } + return result; + } + } diff --git a/src/com/pms/mapper/quality.xml b/src/com/pms/mapper/quality.xml index 5179bf6..1028007 100644 --- a/src/com/pms/mapper/quality.xml +++ b/src/com/pms/mapper/quality.xml @@ -1582,6 +1582,7 @@ , COALESCE(SPI.REMARK, '') AS "REMARK" , SPI.DATA_TYPE AS "DATA_TYPE" , COALESCE(SPI.INSPECTION_GROUP_ID, '') AS "INSPECTION_GROUP_ID" + , COALESCE(SPI.IS_LOCKED, 'N') AS "IS_LOCKED" , (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = SPI.OBJID AND AFI.DOC_TYPE = 'SEMI_INSPECTION_IMAGE' AND AFI.STATUS = 'Active') AS "IMAGE_FILE_CNT" , (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = SPI.OBJID AND AFI.DOC_TYPE = 'SEMI_INSPECTION_NCR' AND AFI.STATUS = 'Active') AS "NCR_FILE_CNT" @@ -1722,6 +1723,19 @@ WHERE OBJID = #{OBJID} + + + UPDATE PMS_QUALITY_SEMI_PRODUCT_INSPECTION + SET IS_LOCKED = 'Y' + WHERE OBJID = #{OBJID} + + + + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION + WHERE OBJID = #{OBJID} + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION @@ -1734,6 +1748,19 @@ + + + DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION + WHERE INSPECTION_GROUP_ID = #{INSPECTION_GROUP_ID} + AND DATA_TYPE = #{DATA_TYPE} + + AND OBJID NOT IN + + #{objId} + + + + diff --git a/src/com/pms/service/QualityService.java b/src/com/pms/service/QualityService.java index d19ab0b..e3ab8af 100644 --- a/src/com/pms/service/QualityService.java +++ b/src/com/pms/service/QualityService.java @@ -1043,6 +1043,86 @@ public class QualityService extends BaseService{ return resultMap; } + /** + * 반제품검사 행 잠금 (IS_LOCKED = 'Y') + */ + public Map lockSemiProductInspection(Map paramMap){ + Map resultMap = new HashMap(); + SqlSession sqlSession = null; + try{ + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + String objIdsJson = CommonUtils.checkNull(paramMap.get("objIds")); + + if(!objIdsJson.equals("") && !objIdsJson.equals("[]")){ + org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser(); + org.json.simple.JSONArray objIdArr = (org.json.simple.JSONArray) parser.parse(objIdsJson); + + for(int i = 0; i < objIdArr.size(); i++){ + String objId = CommonUtils.checkNull(objIdArr.get(i)); + if(!objId.equals("")){ + Map lockParam = new HashMap(); + lockParam.put("OBJID", objId); + sqlSession.update("quality.lockSemiProductInspection", lockParam); + } + } + } + + sqlSession.commit(); + resultMap.put("result", true); + resultMap.put("msg", "잠금 처리되었습니다."); + + }catch(Exception e){ + resultMap.put("result", false); + resultMap.put("msg", "잠금 처리에 실패했습니다."); + if(sqlSession != null) sqlSession.rollback(); + e.printStackTrace(); + }finally{ + if(sqlSession != null) sqlSession.close(); + } + return resultMap; + } + + /** + * 반제품검사 삭제 (OBJID 목록으로 삭제) + */ + public Map deleteSemiProductInspection(Map paramMap){ + Map resultMap = new HashMap(); + SqlSession sqlSession = null; + try{ + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + String objIdsJson = CommonUtils.checkNull(paramMap.get("objIds")); + + if(!objIdsJson.equals("") && !objIdsJson.equals("[]")){ + org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser(); + org.json.simple.JSONArray objIdArr = (org.json.simple.JSONArray) parser.parse(objIdsJson); + + for(int i = 0; i < objIdArr.size(); i++){ + String objId = CommonUtils.checkNull(objIdArr.get(i)); + if(!objId.equals("")){ + Map deleteParam = new HashMap(); + deleteParam.put("OBJID", objId); + sqlSession.delete("quality.deleteSemiProductInspectionByObjId", deleteParam); + } + } + } + + sqlSession.commit(); + resultMap.put("result", true); + resultMap.put("msg", "삭제되었습니다."); + + }catch(Exception e){ + resultMap.put("result", false); + resultMap.put("msg", "삭제에 실패했습니다."); + if(sqlSession != null) sqlSession.rollback(); + e.printStackTrace(); + }finally{ + if(sqlSession != null) sqlSession.close(); + } + return resultMap; + } + /** * 반제품검사 저장 (새로운 팝업 형식) * - 기존 INSPECTION_GROUP_ID가 있으면 수정 모드 (기존 OBJID 유지하면서 UPSERT) @@ -1061,12 +1141,14 @@ public class QualityService extends BaseService{ String leftDataStr = CommonUtils.checkNull(paramMap.get("leftData")); String rightDataStr = CommonUtils.checkNull(paramMap.get("rightData")); String existingGroupId = CommonUtils.checkNull(paramMap.get("INSPECTION_GROUP_ID")); + String saveType = CommonUtils.checkNull(paramMap.get("saveType"), "all"); // 저장 타입: left, right, all org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser(); // 검사 그룹 ID 결정 (기존 있으면 유지, 없으면 새로 생성) String inspectionGroupId = ""; - java.util.Set existingObjIds = new java.util.HashSet(); // 저장할 OBJID 목록 (삭제 대상 판별용) + java.util.Set existingLeftObjIds = new java.util.HashSet(); // 저장할 좌측 OBJID 목록 + java.util.Set existingRightObjIds = new java.util.HashSet(); // 저장할 우측 OBJID 목록 if(!existingGroupId.equals("")){ // 수정 모드: 기존 그룹 ID 유지 @@ -1082,13 +1164,17 @@ public class QualityService extends BaseService{ for(int i = 0; i < leftArr.size(); i++){ org.json.simple.JSONObject item = (org.json.simple.JSONObject) leftArr.get(i); - // 기존 OBJID가 있고 임시 ID가 아니면 유지, 아니면 새로 생성 + // OBJID 처리 String existingObjId = CommonUtils.checkNull(item.get("OBJID")); String rowId = CommonUtils.checkNull(item.get("ROW_ID")); - boolean isNewRow = existingObjId.equals("") || existingObjId.startsWith("NEW_") || existingObjId.startsWith("EXIST_"); + String objId = existingObjId; - String objId = isNewRow ? CommonUtils.createObjId() : existingObjId; - existingObjIds.add(objId); + // OBJID가 비어있거나 임시 ID이면 새로 생성 + if(objId.equals("") || objId.startsWith("NEW_") || objId.startsWith("EXIST_")){ + objId = CommonUtils.createObjId(); + } + + existingLeftObjIds.add(objId); // 좌측 OBJID 목록에 추가 Map sqlParamMap = new HashMap(); sqlParamMap.put("OBJID", objId); @@ -1114,11 +1200,11 @@ public class QualityService extends BaseService{ sqlParamMap.put("DISPOSITION_TYPE", ""); sqlParamMap.put("REMARK", ""); - if(isNewRow){ + // UPSERT: UPDATE 시도 후 실패하면 INSERT + int updateCnt = sqlSession.update("quality.updateSemiProductInspectionData", sqlParamMap); + if(updateCnt == 0){ + // UPDATE 실패 시 INSERT (신규 데이터) sqlSession.insert("quality.insertSemiProductInspectionData", sqlParamMap); - } else { - // 기존 데이터 업데이트 - sqlSession.update("quality.updateSemiProductInspectionData", sqlParamMap); } } } @@ -1154,19 +1240,31 @@ public class QualityService extends BaseService{ String partName = ""; if(parentItem != null){ + // 좌측 데이터가 함께 전송된 경우 (전체 저장) workOrderNo = CommonUtils.checkNull(parentItem.get("WORK_ORDER_NO")); modelName = CommonUtils.checkNull(parentItem.get("MODEL_NAME")); productType = CommonUtils.checkNull(parentItem.get("PRODUCT_TYPE")); partNo = CommonUtils.checkNull(parentItem.get("PART_NO")); partName = CommonUtils.checkNull(parentItem.get("PART_NAME")); + } else { + // 우측만 저장하는 경우: item에 직접 부모 정보가 포함되어 있음 + workOrderNo = CommonUtils.checkNull(item.get("WORK_ORDER_NO")); + modelName = CommonUtils.checkNull(item.get("MODEL_NAME")); + productType = CommonUtils.checkNull(item.get("PRODUCT_TYPE")); + partNo = CommonUtils.checkNull(item.get("PART_NO")); + partName = CommonUtils.checkNull(item.get("PART_NAME")); } - // 기존 OBJID가 있고 임시 ID가 아니면 유지, 아니면 새로 생성 + // OBJID 처리 String existingObjId = CommonUtils.checkNull(item.get("OBJID")); - boolean isNewRow = existingObjId.equals("") || existingObjId.startsWith("DEFECT_") || existingObjId.startsWith("NEW_"); + String objId = existingObjId; - String objId = isNewRow ? CommonUtils.createObjId() : existingObjId; - existingObjIds.add(objId); + // OBJID가 비어있으면 새로 생성 + if(objId.equals("") || objId.startsWith("DEFECT_") || objId.startsWith("NEW_")){ + objId = CommonUtils.createObjId(); + } + + existingRightObjIds.add(objId); // 우측 OBJID 목록에 추가 Map sqlParamMap = new HashMap(); sqlParamMap.put("OBJID", objId); @@ -1192,26 +1290,49 @@ public class QualityService extends BaseService{ sqlParamMap.put("RECEIPT_QTY", "0"); sqlParamMap.put("GOOD_QTY", "0"); - if(isNewRow){ + // UPSERT: UPDATE 시도 후 실패하면 INSERT + int updateCnt = sqlSession.update("quality.updateSemiProductInspectionData", sqlParamMap); + if(updateCnt == 0){ + // UPDATE 실패 시 INSERT (신규 데이터) sqlSession.insert("quality.insertSemiProductInspectionData", sqlParamMap); - } else { - // 기존 데이터 업데이트 - sqlSession.update("quality.updateSemiProductInspectionData", sqlParamMap); } } } - // 수정 모드일 때: 전송된 데이터에 없는 기존 데이터는 삭제 + // 수정 모드일 때: 자동 삭제 비활성화 (사용자가 직접 삭제 버튼으로만 삭제) + // 데이터 손실 방지를 위해 자동 삭제 로직 제거 + // 삭제는 사용자가 그리드에서 행을 선택하고 삭제 버튼을 누를 때만 수행 + /* if(!existingGroupId.equals("")){ Map deleteParam = new HashMap(); deleteParam.put("INSPECTION_GROUP_ID", inspectionGroupId); - deleteParam.put("EXCLUDE_OBJIDS", existingObjIds); - sqlSession.delete("quality.deleteSemiProductInspectionExcludeObjIds", deleteParam); + + if("left".equals(saveType)){ + if(existingLeftObjIds.size() > 0){ + deleteParam.put("EXCLUDE_OBJIDS", existingLeftObjIds); + deleteParam.put("DATA_TYPE", "GOOD"); + sqlSession.delete("quality.deleteSemiProductInspectionByType", deleteParam); + } + } else if("right".equals(saveType)){ + if(existingRightObjIds.size() > 0){ + deleteParam.put("EXCLUDE_OBJIDS", existingRightObjIds); + deleteParam.put("DATA_TYPE", "DEFECT"); + sqlSession.delete("quality.deleteSemiProductInspectionByType", deleteParam); + } + } else { + java.util.Set allObjIds = new java.util.HashSet(); + allObjIds.addAll(existingLeftObjIds); + allObjIds.addAll(existingRightObjIds); + deleteParam.put("EXCLUDE_OBJIDS", allObjIds); + sqlSession.delete("quality.deleteSemiProductInspectionExcludeObjIds", deleteParam); + } } + */ sqlSession.commit(); resultMap.put("result", true); resultMap.put("msg", "저장되었습니다."); + resultMap.put("inspectionGroupId", inspectionGroupId); // 생성된 그룹 ID 반환 }catch(Exception e){ resultMap.put("result", false); resultMap.put("msg", "저장 중 오류가 발생했습니다: " + e.getMessage());