테스트 전 저장

This commit is contained in:
leeheejin
2025-12-17 11:59:03 +09:00
parent b910545f22
commit 3b33b0c133
5 changed files with 713 additions and 98 deletions

View File

@@ -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>
<!-- 반제품검사 행 잠금 (IS_LOCKED = 'Y') -->
<update id="lockSemiProductInspection" parameterType="map">
UPDATE PMS_QUALITY_SEMI_PRODUCT_INSPECTION
SET IS_LOCKED = 'Y'
WHERE OBJID = #{OBJID}
</update>
<!-- 반제품검사 데이터 삭제 (OBJID로 단건 삭제) -->
<delete id="deleteSemiProductInspectionByObjId" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
WHERE OBJID = #{OBJID}
</delete>
<!-- 반제품검사 데이터 삭제 (특정 OBJID 제외) -->
<delete id="deleteSemiProductInspectionExcludeObjIds" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
@@ -1734,6 +1748,19 @@
</if>
</delete>
<!-- 반제품검사 데이터 삭제 (타입별, 특정 OBJID 제외) -->
<delete id="deleteSemiProductInspectionByType" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
WHERE INSPECTION_GROUP_ID = #{INSPECTION_GROUP_ID}
AND DATA_TYPE = #{DATA_TYPE}
<if test="EXCLUDE_OBJIDS != null and EXCLUDE_OBJIDS.size() > 0">
AND OBJID NOT IN
<foreach collection="EXCLUDE_OBJIDS" item="objId" open="(" separator="," close=")">
#{objId}
</foreach>
</if>
</delete>
<!-- =====================================================
고객 CS 관리
===================================================== -->

View File

@@ -232,6 +232,7 @@ String loginUserId = CommonUtils.checkNull(person.getUserId());
<div class="btn_group">
<button type="button" class="btn_add" id="btnAddLeft">+ 행 추가</button>
<button type="button" class="btn_del" id="btnDelLeft">- 행 삭제</button>
<button type="button" class="btn_save" id="btnSaveLeft" style="margin-left:10px;">저장</button>
</div>
</div>
<div class="panel_body">
@@ -246,6 +247,7 @@ String loginUserId = CommonUtils.checkNull(person.getUserId());
<div class="btn_group">
<button type="button" class="btn_add" id="btnAddRight">+ 행 추가</button>
<button type="button" class="btn_del" id="btnDelRight">- 행 삭제</button>
<button type="button" class="btn_save" id="btnSaveRight" style="margin-left:10px;">저장</button>
</div>
</div>
<div class="panel_body" id="rightPanelBody">
@@ -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);
}
}
</script>
</body>
</html>

View File

@@ -664,6 +664,24 @@ public class QualityController {
return service.getSemiProductInspectionDetail(paramMap);
}
/**
* 반제품검사 삭제
*/
@ResponseBody
@RequestMapping("/quality/deleteSemiProductInspection.do")
public Map deleteSemiProductInspection(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
return service.deleteSemiProductInspection(paramMap);
}
/**
* 반제품검사 행 잠금 (수정 불가 처리)
*/
@ResponseBody
@RequestMapping("/quality/lockSemiProductInspection.do")
public Map lockSemiProductInspection(HttpServletRequest request, @RequestParam Map<String, Object> 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<String, Object> 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;
}
}

View File

@@ -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>
<!-- 반제품검사 행 잠금 (IS_LOCKED = 'Y') -->
<update id="lockSemiProductInspection" parameterType="map">
UPDATE PMS_QUALITY_SEMI_PRODUCT_INSPECTION
SET IS_LOCKED = 'Y'
WHERE OBJID = #{OBJID}
</update>
<!-- 반제품검사 데이터 삭제 (OBJID로 단건 삭제) -->
<delete id="deleteSemiProductInspectionByObjId" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
WHERE OBJID = #{OBJID}
</delete>
<!-- 반제품검사 데이터 삭제 (특정 OBJID 제외) -->
<delete id="deleteSemiProductInspectionExcludeObjIds" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
@@ -1734,6 +1748,19 @@
</if>
</delete>
<!-- 반제품검사 데이터 삭제 (타입별, 특정 OBJID 제외) -->
<delete id="deleteSemiProductInspectionByType" parameterType="map">
DELETE FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION
WHERE INSPECTION_GROUP_ID = #{INSPECTION_GROUP_ID}
AND DATA_TYPE = #{DATA_TYPE}
<if test="EXCLUDE_OBJIDS != null and EXCLUDE_OBJIDS.size() > 0">
AND OBJID NOT IN
<foreach collection="EXCLUDE_OBJIDS" item="objId" open="(" separator="," close=")">
#{objId}
</foreach>
</if>
</delete>
<!-- =====================================================
고객 CS 관리
===================================================== -->

View File

@@ -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());