diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupFs.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupFs.jsp index b15b1f6..b955381 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupFs.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupFs.jsp @@ -1,5 +1,5 @@ <%String objId = com.pms.common.utils.CommonUtils.checkNull(request.getParameter("objId"));%> - + diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp index d6a05df..6c7848a 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp @@ -372,7 +372,7 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'center', - width: 60, + width: 50, title: 'PDF', field: 'CU03_CNT', visible: true, @@ -416,7 +416,7 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'left', - width: 150, + width: 100, title: '메이커', field: 'MAKER', visible: true @@ -424,7 +424,7 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'left', - width: 100, + width: 70, title: '범주이름', field: 'PART_TYPE_TITLE', visible: true @@ -470,9 +470,9 @@ function fn_initGrid() { headerHozAlign: 'center', hozAlign: 'left', width: 100, - title: '소재', + title: '소재재질', field: 'RAW_MATERIAL', - titleFormatter: function() { return '소재'; }, + titleFormatter: function() { return '소재재질'; }, editor: function(cell, onRendered, success, cancel, editorParams) { // 소재 목록을 Select2용 형태로 변환 var options = materialList.map(function(m) { return {id: m, text: m}; }); @@ -499,9 +499,9 @@ function fn_initGrid() { headerHozAlign: 'center', hozAlign: 'left', width: 100, - title: '사이즈', + title: '규격', field: 'SIZE', - titleFormatter: function() { return '사이즈'; }, + titleFormatter: function() { return '규격'; }, editor: function(cell, onRendered, success, cancel, editorParams) { // 선택된 소재에 따라 동적으로 사이즈 목록 로드 var data = cell.getRow().getData(); @@ -578,14 +578,15 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'right', - width: 100, + width: 90, title: '소재소요량', field: 'REQUIRED_QTY', titleFormatter: function() { return '소재소요량'; }, editor: 'number', editorParams: { min: 0, - step: 0.01 // 소수 가능 + step: 0.01, + selectContents: true // 편집 시 기존 값 전체 선택 }, editable: function(cell) { return cell.getRow().getData().SUPPLY_TYPE === '사급'; @@ -600,7 +601,7 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'right', - width: 120, + width: 90, title: '소재발주수량', field: 'ORDER_QTY', editor: false, @@ -628,20 +629,22 @@ function fn_initGrid() { { headerHozAlign: 'center', hozAlign: 'right', - width: 100, + width: 80, title: '제작수량', field: 'PRODUCTION_QTY', titleFormatter: function() { return '제작수량'; }, editor: 'number', editorParams: { min: 0, - step: 1 + step: 1, + selectContents: true // 편집 시 기존 값 전체 선택 }, formatter: function(cell) { // 저장된 값이 있으면 그대로 사용, 없으면 항목수량 × 수주수량으로 계산 var value = cell.getValue(); - if(value === undefined || value === null || value === '' || value === 0) { + // 0은 유효한 값이므로 제외 (undefined, null, '' 만 기본값 계산) + if(value === undefined || value === null || value === '') { var data = cell.getRow().getData(); var itemQty = parseFloat(data.ITEM_QTY) || 0; value = itemQty * projectQuantity; @@ -681,6 +684,18 @@ function fn_initGrid() { return value; } }, + { + headerHozAlign: 'center', + hozAlign: 'right', + width: 100, + title: '가공단가', + field: 'PROCESSING_UNIT_PRICE', + editor: false, // 구매쪽에서 입력 + formatter: function(cell) { + var value = cell.getValue(); + return value ? Number(value).toLocaleString() : '-'; + } + }, /* 주석처리: 가공납기, 연삭납기 컬럼 { headerHozAlign: 'center', @@ -736,30 +751,30 @@ function fn_initGrid() { headerHozAlign: 'center', hozAlign: 'right', width: 100, - title: '단가', + title: '소재단가', field: 'UNIT_PRICE', editor: false, // 구매쪽에서 입력 formatter: function(cell) { var value = cell.getValue(); return value ? Number(value).toLocaleString() : '-'; } - }, - { - headerHozAlign: 'center', - hozAlign: 'right', - width: 100, - title: '금액', - field: 'TOTAL_PRICE', - editor: false, - formatter: function(cell) { - // 항목수량 × 단가 - var data = cell.getRow().getData(); - var itemQty = parseFloat(data.ITEM_QTY) || 0; - var unitPrice = parseFloat(data.UNIT_PRICE) || 0; - var totalPrice = itemQty * unitPrice; - return totalPrice > 0 ? totalPrice.toLocaleString() : '-'; - } } + // { + // headerHozAlign: 'center', + // hozAlign: 'right', + // width: 100, + // title: '금액', + // field: 'TOTAL_PRICE', + // editor: false, + // formatter: function(cell) { + // // 항목수량 × 단가 + // var data = cell.getRow().getData(); + // var itemQty = parseFloat(data.ITEM_QTY) || 0; + // var unitPrice = parseFloat(data.UNIT_PRICE) || 0; + // var totalPrice = itemQty * unitPrice; + // return totalPrice > 0 ? totalPrice.toLocaleString() : '-'; + // } + // } ] }); @@ -1103,7 +1118,8 @@ function getMbomTreeData() { // 구매 정보 vendor: row.VENDOR || row.VENDOR_PM, // 공급업체 코드/OBJID (기존 값 유지) - unitPrice: toNumber(row.UNIT_PRICE), + unitPrice: toNumber(row.UNIT_PRICE), // 소재단가 + processingUnitPrice: toNumber(row.PROCESSING_UNIT_PRICE), // 가공단가 // totalPrice 계산: 항목수량 × 단가 totalPrice: (function() { var itemQty = parseFloat(row.ITEM_QTY) || 0; @@ -1130,19 +1146,35 @@ function getMbomTreeData() { return mbomData; } + +// 엑셀 다운로드 (CSV 형식) +function fn_excel() { + if(!_tabulGrid) { + Swal.fire('데이터가 없습니다.'); + return; + } + + // 파일명 생성 (현재 날짜 포함) + var today = new Date(); + var dateStr = today.getFullYear() + '_' + + String(today.getMonth() + 1).padStart(2, '0') + '_' + + String(today.getDate()).padStart(2, '0') + '_' + + String(today.getHours()).padStart(2, '0') + '_' + + String(today.getMinutes()).padStart(2, '0'); + var fileName = 'M-BOM_' + dateStr + '.csv'; + + // Tabulator 내장 다운로드 기능 사용 (CSV) + _tabulGrid.download("csv", fileName, { + delimiter: ",", + bom: true // 한글 깨짐 방지 (UTF-8 BOM) + }); +} - +
+ +
diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp index 1bdb52e..50474ff 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupRight.jsp @@ -51,8 +51,11 @@ body, html { + + + - + @@ -126,8 +127,8 @@ function initEbomTable() { {formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", vertAlign:"middle", headerSort:false, width: 40}, {title: "품번", field: "PART_NO", widthGrow: 1.5, vertAlign:"middle"}, {title: "품명", field: "PART_NAME", widthGrow: 2, vertAlign:"middle"}, - {title: "재료", field: "MATERIAL", widthGrow: 1.2, vertAlign:"middle"}, - {title: "메이커", field: "MAKER", widthGrow: 1.5, vertAlign:"middle"} + //{title: "재료", field: "MATERIAL", widthGrow: 1.2, vertAlign:"middle"}, + //{title: "메이커", field: "MAKER", widthGrow: 1.5, vertAlign:"middle"} ], placeholder: "검색 결과가 없습니다." }); diff --git a/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp b/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp index b917b69..491d89b 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp @@ -88,6 +88,7 @@ $(document).ready(function(){ var columns = [ // 요구사항: 품의서 No, 발주서 No, 프로젝트번호, 품번, 품명, 공급업체, 발주수량, 입고수량, 미입고수량, 검사성적서, 입고결과 + {title:'STATUS' ,field:'STATUS' ,visible:false, frozen:true}, {title:'TOTAL_SUPPLY_PRICE' ,field:'TOTAL_SUPPLY_PRICE' ,visible:false, frozen:true}, {title:'TOTAL_DELIVERY_PRICE' ,field:'TOTAL_DELIVERY_PRICE' ,visible:false, frozen:true}, {title:'TOTAL_NOT_DELIVERY_PRICE',field:'TOTAL_NOT_DELIVERY_PRICE',visible:false, frozen:true}, @@ -127,15 +128,36 @@ var columns = [ } }, {headerHozAlign : 'center', hozAlign : 'center', minWidth : 90, widthGrow : 1, title : '입고결과', field : 'DELIVERY_STATUS', - formatter:fnc_createGridAnchorTag, - cellClick:function(e, cell){ + formatter: function(cell, formatterParams, onRendered){ + var status = fnc_checkNull(cell.getData().STATUS); + var deliveryStatus = fnc_checkNull(cell.getValue()); + + // 발주취소 상태인 경우 + if(status === 'orderCancel'){ + return '발주취소'; + } + + // 일반 상태 - 링크로 표시 + if(deliveryStatus != ''){ + return '' + deliveryStatus + ''; + } + return deliveryStatus; + }, + cellClick:function(e, cell){ + var status = fnc_checkNull(cell.getData().STATUS); + + // 발주취소 상태인 경우 팝업 열지 않음 + if(status === 'orderCancel'){ + return; + } + var objId = fnc_checkNull(cell.getData().OBJID); var DELIVERY_STATUS = fnc_checkNull(cell.getData().DELIVERY_STATUS); var purchaseOrderNo = fnc_checkNull(cell.getData().PURCHASE_ORDER_NO); fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS); } - }, - {headerHozAlign : 'center', hozAlign : 'center', minWidth : 140, widthGrow : 1, title : '매입마감', field : 'PURCHASE_CLOSE_DATE'} + } + // {headerHozAlign : 'center', hozAlign : 'center', minWidth : 140, widthGrow : 1, title : '매입마감', field : 'PURCHASE_CLOSE_DATE'} ]; //var grid; @@ -219,7 +241,14 @@ function fn_deliveryAcceptancePopUp(){ if(selected.length > 1){ Swal.fire("한건씩 등록 가능합니다."); return; - }else{ + }else{ + // 발주취소 상태 체크 + var status = fnc_checkNull(selected[0].STATUS); + if(status === 'orderCancel'){ + Swal.fire("발주취소된 건은 입고등록할 수 없습니다."); + return; + } + var MULTI_MASTER_YN = fnc_checkNull(selected[0].MULTI_MASTER_YN); var MULTI_YN = fnc_checkNull(selected[0].MULTI_YN); @@ -472,7 +501,7 @@ function fn_purchaseClose(){
- + <%-- --%>
diff --git a/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderList_new.jsp b/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderList_new.jsp index 5315041..0e0f59c 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderList_new.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderList_new.jsp @@ -76,6 +76,11 @@ $(document).ready(function(){ fn_sendPurchaseOrder(); }); + // 발주 취소 버튼 클릭 + $("#btnOrderCancel").click(function(){ + fn_orderCancel(); + }); + //수주활동 복사 팝업 $("#btnCopy").click(function(){ var checkedObj = _tabulGrid.getSelectedData(); @@ -352,7 +357,11 @@ var columns = [ {headerHozAlign:'center', hozAlign:'center', widthGrow:1, title:'메일발송', field:'MAIL_SEND_YN', formatter: function(cell, formatterParams, onRendered){ var value = fnc_checkNull(cell.getValue()); - if(value === 'Y'){ + var status = fnc_checkNull(cell.getData().STATUS); + // 발주취소 상태인 경우 + if(status === 'orderCancel'){ + return '발주취소'; + } else if(value === 'Y'){ return '발송완료'; } else { return ''; @@ -771,7 +780,7 @@ function fn_sendPurchaseOrder(){ var mailSendYn = fnc_checkNull(selectedData[0].MAIL_SEND_YN); // 취소 상태 확인 - if(status === "cancel"){ + if(status === "cancel" || status === "orderCancel"){ Swal.fire("취소된 발주서는 발송할 수 없습니다."); return false; } @@ -817,6 +826,119 @@ function fn_openMailFormPopup(purchaseOrderObjId){ window.open(url, "purchaseOrderMailForm", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes"); } +// 발주 취소 +function fn_orderCancel(){ + var selectedData = _tabulGrid.getSelectedData(); + + if(selectedData.length < 1){ + Swal.fire("발주 취소할 행을 선택해주세요."); + return false; + } else if(selectedData.length > 1){ + Swal.fire("한번에 한 개의 발주서만 취소 가능합니다."); + return false; + } + + var objId = fnc_checkNull(selectedData[0].OBJID); + var status = fnc_checkNull(selectedData[0].STATUS); + var MULTI_YN = fnc_checkNull(selectedData[0].MULTI_YN); + var MULTI_MASTER_YN = fnc_checkNull(selectedData[0].MULTI_MASTER_YN); + var purchaseOrderNo = fnc_checkNull(selectedData[0].PURCHASE_ORDER_NO); + + // 이미 취소된 상태 확인 + if(status === 'cancel'){ + Swal.fire("이미 취소된 발주서입니다."); + return false; + } + + // 이미 발주취소된 상태 확인 + if(status === 'orderCancel'){ + Swal.fire("이미 발주취소된 발주서입니다."); + return false; + } + + // 동시발주 하위건 확인 + if(MULTI_YN === 'Y' && MULTI_MASTER_YN !== 'Y'){ + Swal.fire("동시발주 하위건은 마스터건으로 취소해주세요."); + return false; + } + + // 입고 여부 확인 후 취소 진행 + $.ajax({ + type: "POST", + url: "/purchaseOrder/checkReceiptForCancel.do", + data: { PURCHASE_ORDER_MASTER_OBJID: objId }, + dataType: "json", + success: function(data){ + if(data.hasReceipt){ + // 입고된 항목이 있는 경우 + Swal.fire({ + title: '취소 불가', + html: '입고된 항목이 있어 발주 취소가 불가합니다.

' + + '입고수량: ' + data.totalReceiptQty + '개', + icon: 'error' + }); + } else { + // 입고된 항목이 없는 경우 - 취소 확인 + Swal.fire({ + title: '발주 취소', + html: '발주서 ' + purchaseOrderNo + '을(를) 취소하시겠습니까?

' + + '취소 후에도 목록에서 확인 가능하며, 메일발송 컬럼에 "발주취소"로 표시됩니다.', + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + confirmButtonText: '취소하기', + cancelButtonText: '닫기' + }).then((result) => { + if(result.isConfirmed){ + fn_executeOrderCancel(objId); + } + }); + } + }, + error: function(jqxhr, status, error){ + console.error("입고 확인 오류:", error); + Swal.fire("입고 확인 중 오류가 발생했습니다."); + } + }); +} + +// 발주 취소 실행 +function fn_executeOrderCancel(objId){ + $.ajax({ + type: "POST", + url: "/purchaseOrder/executeOrderCancel.do", + data: { PURCHASE_ORDER_MASTER_OBJID: objId }, + dataType: "json", + beforeSend: function(){ + _startLoading("처리중입니다."); + }, + complete: function(){ + _endLoading(); + }, + success: function(data){ + if(data.result){ + Swal.fire({ + title: '완료', + text: '발주가 취소되었습니다.', + icon: 'success' + }).then(() => { + fn_search(); + }); + } else { + Swal.fire({ + title: '오류', + text: data.message || '발주 취소 중 오류가 발생했습니다.', + icon: 'error' + }); + } + }, + error: function(jqxhr, status, error){ + console.error("발주 취소 오류:", error); + Swal.fire("발주 취소 중 오류가 발생했습니다."); + } + }); +} +
@@ -842,6 +964,7 @@ function fn_openMailFormPopup(purchaseOrderObjId){ --> + diff --git a/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderMailFormPopup.jsp b/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderMailFormPopup.jsp index 8fab87c..fa529e1 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderMailFormPopup.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/purchaseOrderMailFormPopup.jsp @@ -191,7 +191,14 @@ String purchaseOrderObjId = request.getParameter("purchaseOrderObjId");
- 첨부파일: 발주서(PDF) 및 도면 파일이 자동으로 첨부됩니다. + 첨부파일: 발주서(PDF)가 자동으로 첨부됩니다. +
+ + +
@@ -291,6 +298,9 @@ function fn_loadPurchaseOrderInfo(){ } else { $("#managerListContainer").html('
공급업체 정보가 없습니다.
'); } + + // 도면 파일 개수 조회 + fn_loadDrawingFileCount(purchaseOrderObjId); } else { Swal.fire({ title: '오류', @@ -313,6 +323,31 @@ function fn_loadPurchaseOrderInfo(){ }); } +// 도면 파일 개수 조회 +function fn_loadDrawingFileCount(purchaseOrderObjId){ + $.ajax({ + url: "/purchaseOrder/getDrawingFileCount.do", + type: "POST", + data: { objId: purchaseOrderObjId }, + dataType: "json", + success: function(data){ + if(data.result === "success"){ + var count = parseInt(data.count) || 0; + if(count > 0){ + $("#drawingFileCount").text("(" + count + "개 파일)"); + } else { + $("#drawingFileCount").text("(파일 없음)"); + $("#includeDrawingFiles").prop("checked", false); + $("#includeDrawingFiles").prop("disabled", true); + } + } + }, + error: function(){ + $("#drawingFileCount").text("(조회 실패)"); + } + }); +} + // 공급업체 담당자 목록 로드 function fn_loadPartnerManagers(partnerObjId){ $.ajax({ @@ -669,7 +704,8 @@ function fn_submitMailForm(){ toEmails: $("#toEmails").val(), ccEmails: $("#ccEmails").val(), subject: $("#subject").val(), - contents: $("#contents").val() + contents: $("#contents").val(), + includeDrawingFiles: $("#includeDrawingFiles").is(":checked") ? "Y" : "N" }; $.ajax({ diff --git a/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp b/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp index 9ede541..9d954ce 100644 --- a/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp +++ b/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp @@ -461,32 +461,8 @@ function fn_initGrid() { title: '지급/사급', field: 'SUPPLY_TYPE' }, - // 17. 소재 - { - headerHozAlign: 'center', - hozAlign: 'left', - width: 100, - title: '소재', - field: 'RAW_MATERIAL' - }, - // 18. 사이즈 - { - headerHozAlign: 'center', - hozAlign: 'left', - width: 100, - title: '사이즈', - field: 'SIZE' - }, - // 19. 소재품번 - { - headerHozAlign: 'center', - hozAlign: 'center', - width: 120, - title: '소재품번', - field: 'RAW_MATERIAL_NO' - }, - // 20. 소재소요량 - { +// 20. 소재소요량 +{ headerHozAlign: 'center', hozAlign: 'right', width: 100, @@ -529,27 +505,46 @@ function fn_initGrid() { return value ? Number(value).toLocaleString() : '0'; } }, - // 24. 가공업체 (수정가능 - Select2 에디터) + + // 17. 소재 -> 소재재질 + { + headerHozAlign: 'center', + hozAlign: 'left', + width: 100, + title: '소재재질', + field: 'RAW_MATERIAL' + }, + // 18. 사이즈 -> 규격 + { + headerHozAlign: 'center', + hozAlign: 'left', + width: 100, + title: '규격', + field: 'SIZE' + }, + // 19. 소재품번 + { + headerHozAlign: 'center', + hozAlign: 'center', + width: 120, + title: '소재품번', + field: 'RAW_MATERIAL_NO' + }, + // 30. 공급업체 (수정가능 - Select2 에디터) { headerHozAlign: 'center', hozAlign: 'left', width: 150, - title: '가공업체', - field: 'PROCESSING_VENDOR', + title: '공급업체', + field: 'VENDOR_PM', editor: function(cell, onRendered, success, cancel, editorParams) { - // Select2 에디터 + // Select2 에디터 (가공업체와 동일한 목록 사용) return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams); }, formatter: function(cell) { var value = cell.getValue(); - - // 저장된 값이 없으면 기본값 '5001'(RPS) 설정 - if(value === undefined || value === null || value === '') { - value = '5001'; - cell.getRow().update({PROCESSING_VENDOR: value}, false); - } - - // OBJID로 업체명 조회하여 표시 + if(!value) return ''; + // processingVendorList에서 해당 값의 이름 찾기 for(var i = 0; i < processingVendorList.length; i++) { if(processingVendorList[i].id == value) { return processingVendorList[i].text; @@ -558,27 +553,41 @@ function fn_initGrid() { return value; } }, - /* // 25. 가공납기 - 주석처리 + // 31. 단가 (수정가능) -> 소재단가 { headerHozAlign: 'center', - hozAlign: 'center', + hozAlign: 'right', width: 100, - title: '가공납기', - field: 'PROCESSING_DEADLINE' + title: '소재단가', + field: 'UNIT_PRICE', + editor: 'number', + editable: true, + formatter: function(cell) { + var value = cell.getValue(); + return value ? Number(value).toLocaleString() : '0'; + } }, - // 26. 연삭납기 + // 32. 총단가 -> 소재총단가 { headerHozAlign: 'center', - hozAlign: 'center', + hozAlign: 'right', width: 100, - title: '연삭납기', - field: 'GRINDING_DEADLINE' - }, */ + title: '소재총단가', + field: 'TOTAL_PRICE', + formatter: function(cell) { + var data = cell.getRow().getData(); + var qty = parseFloat(data.PO_QTY) || 0; + var unitPrice = parseFloat(data.UNIT_PRICE) || 0; + var totalPrice = qty * unitPrice; + return totalPrice > 0 ? totalPrice.toLocaleString() : '0'; + } + }, + // 27. 사용여부 (수정가능) { headerHozAlign: 'center', hozAlign: 'center', - width: 80, + width: 90, title: '사용여부', field: 'USE_YN', editor: 'list', @@ -638,21 +647,28 @@ function fn_initGrid() { return value ? Number(value).toLocaleString() : '0'; } }, - // 30. 공급업체 (수정가능 - Select2 에디터) + + // 24. 가공업체 (수정가능 - Select2 에디터) { headerHozAlign: 'center', hozAlign: 'left', width: 150, - title: '공급업체', - field: 'VENDOR_PM', + title: '가공업체', + field: 'PROCESSING_VENDOR', editor: function(cell, onRendered, success, cancel, editorParams) { - // Select2 에디터 (가공업체와 동일한 목록 사용) + // Select2 에디터 return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams); }, formatter: function(cell) { var value = cell.getValue(); - if(!value) return ''; - // processingVendorList에서 해당 값의 이름 찾기 + + // 저장된 값이 없으면 기본값 '5001'(RPS) 설정 + if(value === undefined || value === null || value === '') { + value = '5001'; + cell.getRow().update({PROCESSING_VENDOR: value}, false); + } + + // OBJID로 업체명 조회하여 표시 for(var i = 0; i < processingVendorList.length; i++) { if(processingVendorList[i].id == value) { return processingVendorList[i].text; @@ -661,13 +677,13 @@ function fn_initGrid() { return value; } }, - // 31. 단가 (수정가능) + // 가공단가 (수정가능) { headerHozAlign: 'center', hozAlign: 'right', width: 100, - title: '단가', - field: 'UNIT_PRICE', + title: '가공단가', + field: 'PROCESSING_UNIT_PRICE', editor: 'number', editable: true, formatter: function(cell) { @@ -675,28 +691,94 @@ function fn_initGrid() { return value ? Number(value).toLocaleString() : '0'; } }, - // 32. 총단가 + // 가공총단가 (가공단가 × 제작수량) { headerHozAlign: 'center', hozAlign: 'right', width: 100, - title: '총단가', - field: 'TOTAL_PRICE', + title: '가공총단가', + field: 'PROCESSING_TOTAL_PRICE', formatter: function(cell) { var data = cell.getRow().getData(); - var qty = parseFloat(data.PO_QTY) || 0; - var unitPrice = parseFloat(data.UNIT_PRICE) || 0; - var totalPrice = qty * unitPrice; - return totalPrice > 0 ? totalPrice.toLocaleString() : '0'; + var productionQty = parseFloat(data.PRODUCTION_QTY) || 0; + var processingUnitPrice = parseFloat(data.PROCESSING_UNIT_PRICE) || 0; + var processingTotalPrice = productionQty * processingUnitPrice; + return processingTotalPrice > 0 ? processingTotalPrice.toLocaleString() : '0'; } }, - // 33. 품의서작성일 + // 총합계 (소재총단가 + 가공총단가) + { + headerHozAlign: 'center', + hozAlign: 'right', + width: 100, + title: '총합계', + field: 'GRAND_TOTAL_PRICE', + formatter: function(cell) { + var data = cell.getRow().getData(); + // 소재총단가: 발주수량 × 소재단가 + var poQty = parseFloat(data.PO_QTY) || 0; + var unitPrice = parseFloat(data.UNIT_PRICE) || 0; + var materialTotalPrice = poQty * unitPrice; + // 가공총단가: 제작수량 × 가공단가 + var productionQty = parseFloat(data.PRODUCTION_QTY) || 0; + var processingUnitPrice = parseFloat(data.PROCESSING_UNIT_PRICE) || 0; + var processingTotalPrice = productionQty * processingUnitPrice; + // 총합계 + var grandTotal = materialTotalPrice + processingTotalPrice; + return grandTotal > 0 ? grandTotal.toLocaleString() : '0'; + } + }, + /* // 25. 가공납기 - 주석처리 { headerHozAlign: 'center', hozAlign: 'center', width: 100, - title: '품의서작성일', - field: 'PROPOSAL_DATE' + title: '가공납기', + field: 'PROCESSING_DEADLINE' + }, + // 26. 연삭납기 + { + headerHozAlign: 'center', + hozAlign: 'center', + width: 100, + title: '연삭납기', + field: 'GRINDING_DEADLINE' + }, */ + + + // 33. 소재 품의서작성일 + { + headerHozAlign: 'center', + hozAlign: 'center', + width: 110, + title: '소재품의서일', + field: 'PROPOSAL_DATE', + formatter: function(cell) { + var value = cell.getValue(); + if(!value) return ''; + // YYYY-MM-DD 형식으로 표시 + if(value.length >= 10) { + return value.substring(0, 10); + } + return value; + } + }, + // 34. 가공 품의서작성일 + { + headerHozAlign: 'center', + hozAlign: 'center', + width: 110, + title: '가공품의서일', + field: 'PROCESSING_PROPOSAL_DATE', + formatter: function(cell) { + var value = cell.getValue(); + if(!value) return ''; + // YYYY-MM-DD 형식으로 표시 + if(value.length >= 10) { + return value.substring(0, 10); + } + return value; + } } ]; @@ -719,8 +801,8 @@ function fn_initGrid() { var row = cell.getRow(); var data = row.getData(); - // 발주수량 또는 단가 변경 시 총단가 자동 계산 - if(field === 'PO_QTY' || field === 'UNIT_PRICE') { + // 발주수량, 소재단가, 가공단가 변경 시 총단가 자동 계산 + if(field === 'PO_QTY' || field === 'UNIT_PRICE' || field === 'PROCESSING_UNIT_PRICE') { row.reformat(); } }); @@ -856,11 +938,19 @@ function fn_save() { // 저장 전 데이터 가공 gridData.forEach(function(item) { - // TOTAL_PRICE 계산 (PO_QTY * UNIT_PRICE) + // 소재총단가 계산 (PO_QTY * UNIT_PRICE) var poQty = parseFloat(item.PO_QTY) || 0; var unitPrice = parseFloat(item.UNIT_PRICE) || 0; item.TOTAL_PRICE = poQty * unitPrice; + // 가공총단가 계산 (PRODUCTION_QTY * PROCESSING_UNIT_PRICE) + var productionQty = parseFloat(item.PRODUCTION_QTY) || 0; + var processingUnitPrice = parseFloat(item.PROCESSING_UNIT_PRICE) || 0; + item.PROCESSING_TOTAL_PRICE = productionQty * processingUnitPrice; + + // 총합계 계산 (소재총단가 + 가공총단가) + item.GRAND_TOTAL_PRICE = item.TOTAL_PRICE + item.PROCESSING_TOTAL_PRICE; + // 사용여부 변환: 사용/미사용 → Y/N if(item.USE_YN === '사용') { item.USE_YN = 'Y'; diff --git a/WebContent/WEB-INF/view/salesMng/purchaseRequestRegList.jsp b/WebContent/WEB-INF/view/salesMng/purchaseRequestRegList.jsp new file mode 100644 index 0000000..10fdff3 --- /dev/null +++ b/WebContent/WEB-INF/view/salesMng/purchaseRequestRegList.jsp @@ -0,0 +1,714 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ page import="com.pms.common.utils.*"%> +<%@ page import="java.util.*" %> +<%@include file= "/init.jsp" %> + + +<% +// DB에서 메뉴명 조회 (공통 유틸 사용) +String menuObjId = request.getParameter("menuObjId"); +String menuName = CommonUtils.getMenuName(menuObjId, "구매관리_구매요청서작성"); +%> + + + + +<%=Constants.SYSTEM_NAME%> + + + + + + + +
+ +
+
+
+
+

+ <%=menuName%> +

+
+ + + +
+
+
+ + + + <%-- 품번 활성화 --%> + + + <%-- 품명 활성화 --%> + + + <%-- 작성일 활성화 --%> + + + + +
+ + + + + + + + + + ~ + +
+
+ + <%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %> +
+
+
+
+ + + diff --git a/WebContent/WEB-INF/view/salesMng/salesRequestFormPopUp.jsp b/WebContent/WEB-INF/view/salesMng/salesRequestFormPopUp.jsp index e79a02a..e24d242 100644 --- a/WebContent/WEB-INF/view/salesMng/salesRequestFormPopUp.jsp +++ b/WebContent/WEB-INF/view/salesMng/salesRequestFormPopUp.jsp @@ -10,6 +10,7 @@ ( CommonUtils.checkNull(info.get("STATUS_TITLE")).equals( "결재중" ) ||CommonUtils.checkNull(info.get("STATUS_TITLE")).equals( "결재완료" ) ||CommonUtils.checkNull(info.get("STATUS_TITLE")).equals( "접수" ) + ||CommonUtils.checkNull(info.get("STATUS")).equals( "confirmed" ) ) ){ isModify = false; //수정불가 @@ -84,6 +85,11 @@ $(function(){ fn_deleteRow(); }); + // 확정 버튼 클릭 + $("#btnConfirm").click(function(){ + fn_confirm(); + }); + $("#btnAppr").click(function(){ //결재상신 if(fnc_valitate("form1")){ var objId = "${resultMap.OBJID}"; @@ -285,17 +291,16 @@ function fn_getSalesRequestTargetPartList(masterObjId,bomObjId){ appendText += " "; appendText += " "; appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; + // 공급업체 + appendText += " "; + appendText += " "; + appendText += " "; + // 단가 + var PARTNER_PRICE = fnc_checkNull(resultData[i].PARTNER_PRICE); + appendText += " "; + appendText += " "; + appendText += " "; appendText += " "; @@ -316,10 +321,9 @@ function fn_getSalesRequestTargetPartList(masterObjId,bomObjId){ var PART_NO = fnc_checkNull(resultData[i].PART_NO); var PART_NAME = fnc_checkNull(resultData[i].PART_NAME); - // 공급업체 제거로 주석처리 - // var PARTNER_OBJID = fnc_checkNull(resultData[i].PARTNER_OBJID); - // fnc_getAdminSupCdListAppend("", "PARTNER_OBJID_"+rowObjId, PARTNER_OBJID); - // $("#PARTNER_OBJID_"+rowObjId).val(PARTNER_OBJID); + // 공급업체 셋팅 (CLIENT_MNG 테이블) + var PARTNER_OBJID = fnc_checkNull(resultData[i].PARTNER_OBJID); + fnc_getClientMngListAppend("PARTNER_OBJID_"+rowObjId, PARTNER_OBJID); fn_addBomPart("PART_OBJID_"+rowObjId, PART_OBJID, "PART_NAME_"+rowObjId,""); @@ -377,31 +381,29 @@ function fn_AddRow(){ //appendText += " "; //appendText += " "; //appendText += " "; - //appendText += " "; - appendText += " "; - appendText += " "; - appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - //appendText += " "; - appendText += " "; - - if(0 < $("#partListArea tr:first").lenght || typeof $("#partListArea tr:first").lenght != "undefined") { - $("#partListArea tr:first").before(appendText); - }else{ - $("#partListArea").append(appendText); - } - // 공급업체 제거로 주석처리 - // fnc_getAdminSupCdListAppend("", "PARTNER_OBJID_"+rowObjId, ""); - fn_addBomPart("PART_OBJID_"+rowObjId, "", "PART_NAME_"+rowObjId, ""); + //appendText += " "; + appendText += " "; + appendText += " "; + appendText += " "; + // 공급업체 + appendText += " "; + appendText += " "; + appendText += " "; + // 단가 + appendText += " "; + appendText += " "; + appendText += " "; + appendText += " "; + +if(0 < $("#partListArea tr:first").lenght || typeof $("#partListArea tr:first").lenght != "undefined") { + $("#partListArea tr:first").before(appendText); +}else{ + $("#partListArea").append(appendText); +} +// 공급업체 셋팅 (CLIENT_MNG 테이블) +fnc_getClientMngListAppend("PARTNER_OBJID_"+rowObjId, ""); +fn_addBomPart("PART_OBJID_"+rowObjId, "", "PART_NAME_"+rowObjId, ""); fnc_datepick(); $(".select2").select2(); } @@ -498,6 +500,61 @@ function fn_Supply_save(){ } } +// 구매요청서 확정 처리 +function fn_confirm(){ + var masterObjId = $("#SALES_REQUEST_MASTER_OBJID").val(); + + if(fnc_checkNull(masterObjId) == ""){ + Swal.fire("먼저 저장해주세요."); + return; + } + + // 품목이 있는지 확인 + if($("#partListArea tr").length < 1){ + Swal.fire("품목이 없습니다. 먼저 품목을 추가하고 저장해주세요."); + return; + } + + Swal.fire({ + title: '확정', + text: '확정하시겠습니까? 확정 후에는 수정이 불가능합니다.', + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#5cb85c', + cancelButtonColor: '#d33', + confirmButtonText: '확정', + cancelButtonText: '취소' + }).then((result) => { + if (result.isConfirmed) { + $.ajax({ + type: "POST", + url: "/salesMng/confirmSalesRequest.do", + data: { "SALES_REQUEST_MASTER_OBJID": masterObjId }, + dataType: "json", + success: function(data){ + if(data.result){ + Swal.fire({ + title: '완료', + text: '확정되었습니다.', + icon: 'success' + }).then(() => { + if(typeof opener.fn_search == "function"){ + opener.fn_search(); + } + self.close(); + }); + } else { + Swal.fire('오류', data.msg || '확정 처리 중 오류가 발생했습니다.', 'error'); + } + }, + error: function(jqxhr, status, error){ + Swal.fire('오류', '서버 통신 중 오류가 발생했습니다.', 'error'); + } + }); + } + }); +} + // ★★★ 프로젝트 선택 시 M-BOM 품목 자동 로드 ★★★ function fn_loadMbomParts(projectObjId){ if(fnc_checkNull(projectObjId) == "") return; @@ -539,12 +596,23 @@ function fn_loadMbomParts(projectObjId){ appendText += " "; appendText += " "; appendText += " "; + // 공급업체 + appendText += " "; + appendText += " "; + appendText += " "; + // 단가 + appendText += " "; + appendText += " "; + appendText += " "; appendText += ""; $("#partListArea").append(appendText); // 품번 드롭다운에 M-BOM 전체 품목 옵션 추가 fn_addBomPart("PART_OBJID_"+rowObjId, PART_OBJID, "PART_NAME_"+rowObjId, ""); + // 공급업체 셋팅 (CLIENT_MNG 테이블) + fnc_getClientMngListAppend("PARTNER_OBJID_"+rowObjId, ""); }); $(".select2").select2(); @@ -633,6 +701,7 @@ function fn_callbackFnc(){
+
@@ -668,27 +737,27 @@ function fn_callbackFnc(){ ${code_map.project_no} - + - - - + + + - - - - - - - - - + +
@@ -731,12 +801,14 @@ function fn_callbackFnc(){ - <% }else{ %> - - <% } %> - + + - + <% }else{ %> +
+ +
+ <% } %>
@@ -748,36 +820,42 @@ function fn_callbackFnc(){ --> -
- - - - - - - - - - - - - - - -
품번품명수량
-
-
- - - - - - - - - -
-
+
+ + + + + + + + + + + + + + + + + + + +
품번품명수량공급업체단가
+
+
+ + + + + + + + + + + +
+
diff --git a/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp b/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp index edf6190..1282524 100644 --- a/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp +++ b/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp @@ -196,7 +196,7 @@ var columns = [ ,{headerHozAlign : 'center', hozAlign : 'center', title : "유/무상", field :"PAID_TYPE_NAME" , widthGrow:0.9 } ,{headerHozAlign : 'center', hozAlign : 'left', title : "품번", field :"PART_NO" , widthGrow:1.4} ,{headerHozAlign : 'center', hozAlign : 'left' , title : "품명", field :"PART_NAME" , widthGrow:1.8 } - ,{headerHozAlign : 'center', hozAlign : 'center', title : "구매요청서", field :"HAS_PURCHASE_REQUEST" , widthGrow:1.1, + ,{headerHozAlign : 'center', hozAlign : 'center', title : "견적요청서", field :"HAS_PURCHASE_REQUEST" , widthGrow:1.1, formatter: function(cell, formatterParams, onRendered){ // 구매요청서 작성 여부: HAS_PURCHASE_REQUEST가 'Y'이면 구매요청서 작성됨 var data = cell.getData(); @@ -214,6 +214,24 @@ var columns = [ } } } + // ,{headerHozAlign : 'center', hozAlign : 'center', title : "구매요청서", field :"HAS_PURCHASE_REQUEST" , widthGrow:1.1, + // formatter: function(cell, formatterParams, onRendered){ + // // 구매요청서 작성 여부: HAS_PURCHASE_REQUEST가 'Y'이면 구매요청서 작성됨 + // var data = cell.getData(); + // var hasPurchaseRequest = fnc_checkNull(data.HAS_PURCHASE_REQUEST); + // var iconClass = (hasPurchaseRequest == 'Y') ? 'file_icon' : 'file_empty_icon'; + // return ''; + // }, + // cellClick : function(e, cell) { + // var data = cell.getData(); + // var hasPurchaseRequest = fnc_checkNull(data.HAS_PURCHASE_REQUEST); + + // // 구매요청서가 작성된 경우(파란색 아이콘)만 팝업 열기 + // if(hasPurchaseRequest == 'Y') { + // fn_openSalesRequestFormPopUp(data.OBJID); + // } + // } + // } ,{headerHozAlign : 'center', hozAlign : 'center', title : "요청인", field :"REQUEST_USER_NAME" , widthGrow:1.1, // 요청인: 구매요청서 작성 시에만 표시 formatter: function(cell, formatterParams, onRendered){ @@ -297,7 +315,7 @@ function fn_openSalesRequestPopUp(objId){ if(mbomHeaderObjid) { url += "&MBOM_HEADER_OBJID=" + mbomHeaderObjid; } - window.open(url,"purchaseListPopUp","width=1400,height=800,scrollbars=yes,resizable=yes"); + window.open(url,"purchaseListPopUp","width=1800,height=800,scrollbars=yes,resizable=yes"); } //구매요청서 작성 팝업 (구매요청서 파일 아이콘 클릭 시) @@ -489,9 +507,9 @@ function fn_formPopUp(objId,sales_request_objid){ /** * 품의서 생성 함수 - * - 선택된 구매요청서에서 단가가 입력된 품목만 필터링 + * - 선택된 구매요청서에서 소재단가/가공단가가 입력된 품목만 필터링 * - 이미 품의서가 생성된 품목은 제외 - * - 하나의 품의서로 생성 + * - 소재/가공 각각 별도 품의서 생성 */ function fn_createProposal() { // 1. 선택된 행 확인 @@ -528,79 +546,73 @@ function fn_createProposal() { dataType: "json", success: function(response) { if(response.resultFlag === "S") { - var targetParts = response.data; - var excludedParts = response.excludedParts || []; // 공급업체 미입력 품목 + var materialParts = response.materialTargetParts || []; + var processingParts = response.processingTargetParts || []; + var materialExcluded = response.materialExcludedParts || []; + var processingExcluded = response.processingExcludedParts || []; // 3. 대상 품목 확인 - if(!targetParts || targetParts.length == 0) { + if(materialParts.length == 0 && processingParts.length == 0) { + var excludedHtml = fn_buildExcludedHtml(materialExcluded, processingExcluded); Swal.fire({ title: '알림', - text: '품의서를 생성할 품목이 없습니다.\n(단가와 공급업체가 모두 입력되고 품의서가 생성되지 않은 품목만 대상)', + html: '품의서를 생성할 품목이 없습니다.
(소재단가+공급업체 또는 가공단가+가공업체가 입력되고 품의서가 생성되지 않은 품목만 대상)' + excludedHtml, icon: 'info' }); return; } - // 4. 품의서 생성 확인 - var partCount = targetParts.length; - var partListHtml = targetParts.map(function(part) { - return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); - }).join('
'); + // 4. 품의서 생성 확인 - 소재/가공 구분 표시 + var confirmHtml = '
'; - // 공급업체 미입력으로 제외된 품목이 있는 경우 알림 추가 - var excludedHtml = ''; - if(excludedParts && excludedParts.length > 0) { - var excludedListHtml = excludedParts.map(function(part) { + // 소재 품의서 대상 + if(materialParts.length > 0) { + var materialListHtml = materialParts.map(function(part) { return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); }).join('
'); - excludedHtml = '
' + - '

⚠️ 공급업체 미입력으로 제외된 품목 (' + excludedParts.length + '건)

' + - '
' + - excludedListHtml + - '
' + + confirmHtml += '
' + + '

📦 소재 품의서 (' + materialParts.length + '건)

' + + '
' + materialListHtml + '
' + '
'; } + // 가공 품의서 대상 + if(processingParts.length > 0) { + var processingListHtml = processingParts.map(function(part) { + return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); + }).join('
'); + + confirmHtml += '
' + + '

⚙️ 가공 품의서 (' + processingParts.length + '건)

' + + '
' + processingListHtml + '
' + + '
'; + } + + // 제외된 품목 표시 + confirmHtml += fn_buildExcludedHtml(materialExcluded, processingExcluded); + confirmHtml += '
'; + + // 생성될 품의서 개수 표시 + var proposalCount = (materialParts.length > 0 ? 1 : 0) + (processingParts.length > 0 ? 1 : 0); + Swal.fire({ title: '품의서 생성', - html: '
' + - '

' + partCount + '건의 품목으로 품의서를 생성합니다.

' + - '
' + - partListHtml + - '
' + - excludedHtml + - '
', + html: '

' + proposalCount + '개의 품의서가 생성됩니다.

' + confirmHtml, icon: 'question', showCancelButton: true, confirmButtonText: '생성', - cancelButtonText: '취소' + cancelButtonText: '취소', + width: '600px' }).then((result) => { if (result.isConfirmed) { - fn_executeCreateProposal(salesRequestObjid, targetParts); + fn_executeCreateProposal(salesRequestObjid, null); } }); } else { - // 실패 시에도 공급업체 미입력 품목 정보 표시 - var excludedParts = response.excludedParts || []; - var excludedHtml = ''; - - if(excludedParts && excludedParts.length > 0) { - var excludedListHtml = excludedParts.map(function(part) { - return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); - }).join('
'); - - excludedHtml = '

' + - '

⚠️ 공급업체 미입력 품목 (' + excludedParts.length + '건)

' + - '
' + - excludedListHtml + - '
' + - '
'; - } - Swal.fire({ title: '알림', - html: (response.message || '품목 조회 중 오류가 발생했습니다.') + excludedHtml, + html: response.message || '품목 조회 중 오류가 발생했습니다.', icon: 'info' }); } @@ -608,13 +620,44 @@ function fn_createProposal() { error: function(xhr, status, error) { Swal.fire({ title: '오류', - text: '서버 통신 중 오류가 발생했습니다.22', + text: '서버 통신 중 오류가 발생했습니다.', icon: 'error' }); } }); } +/** + * 제외된 품목 HTML 생성 + */ +function fn_buildExcludedHtml(materialExcluded, processingExcluded) { + var html = ''; + + if(materialExcluded && materialExcluded.length > 0) { + var listHtml = materialExcluded.map(function(part) { + return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); + }).join('
'); + + html += '
' + + '

⚠️ 소재 - 공급업체 미입력 (' + materialExcluded.length + '건)

' + + '
' + listHtml + '
' + + '
'; + } + + if(processingExcluded && processingExcluded.length > 0) { + var listHtml = processingExcluded.map(function(part) { + return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); + }).join('
'); + + html += '
' + + '

⚠️ 가공 - 가공업체 미입력 (' + processingExcluded.length + '건)

' + + '
' + listHtml + '
' + + '
'; + } + + return html; +} + /** * 품의서 생성 실행 */ @@ -623,15 +666,16 @@ function fn_executeCreateProposal(salesRequestObjid, targetParts) { url: "/salesMng/createProposalFromPurchaseList.do", type: "POST", data: { - SALES_REQUEST_MASTER_OBJID: salesRequestObjid, - TARGET_PARTS: JSON.stringify(targetParts) + SALES_REQUEST_MASTER_OBJID: salesRequestObjid }, dataType: "json", success: function(response) { if(response.resultFlag === "S") { + // 서버에서 반환한 메시지 사용 (소재/가공 품의서 번호 포함) + var message = response.message || '품의서가 생성되었습니다.'; Swal.fire({ title: '생성 완료', - text: '품의서가 생성되었습니다.\n품의서 관리에서 확인하세요.', + html: message.replace(/\n/g, '
'), icon: 'success' }).then(() => { fn_search(); // 목록 새로고침 @@ -639,12 +683,13 @@ function fn_executeCreateProposal(salesRequestObjid, targetParts) { } else { Swal.fire({ title: '오류', - text: response.message || '품의서 생성 중 오류가 발생했습니다.', + html: (response.message || '품의서 생성 중 오류가 발생했습니다.').replace(/\n/g, '
'), icon: 'error' }); } }, error: function(xhr, status, error) { + console.error("품의서 생성 오류:", status, error); Swal.fire({ title: '오류', text: '서버 통신 중 오류가 발생했습니다.', @@ -670,7 +715,7 @@ function fn_executeCreateProposal(salesRequestObjid, targetParts) {
- +
diff --git a/WebContent/js/common.js b/WebContent/js/common.js index 2dfc6bb..d8eb7c2 100644 --- a/WebContent/js/common.js +++ b/WebContent/js/common.js @@ -1165,7 +1165,7 @@ function fnc_getSupplyCodeListAppend2(supplyCode,selectboxId,selectedVal){ }); } -//공급업체 정보목록을 가져온다. +//공급업체 정보목록을 가져온다. (ADMIN_SUPPLY_MNG 테이블) function fnc_getAdminSupCdListAppend(supplyCode,selectboxId,selectedVal){ $.ajax({ url:"/common/getAdminSupCdList.do", @@ -1195,6 +1195,37 @@ function fnc_getAdminSupCdListAppend(supplyCode,selectboxId,selectedVal){ }); } +// 일반거래처(공급업체) 정보목록을 가져온다. (CLIENT_MNG 테이블) +function fnc_getClientMngListAppend(selectboxId, selectedVal){ + $.ajax({ + url:"/common/getClientMngList.do", + type:"POST", + data:{}, + dataType:"json", + async:false, + success:function(data){ + var resultList = data; + + $("#"+selectboxId).empty(); + $("#"+selectboxId).append(""); + + if(resultList && resultList.length > 0){ + for (var i = 0; i < resultList.length; i++) { + var commonCodeId = resultList[i].CODE_ID; + var commonCodeName = resultList[i].CODE_NAME || resultList[i].NAME; + $("#"+selectboxId).append(""); + } + if(selectedVal){ + $("#"+selectboxId).val(selectedVal); + } + } + }, + error: function(jqxhr, status, error){ + console.error("일반거래처 목록 조회 실패"); + } + }); +} + //targetCode에 해당하는 대상구분명을 반환한다. function fnc_getApprovalTargetName(targetCode){ var targetTypeTitle = ""; diff --git a/src/com/pms/controller/PurchaseOrderController.java b/src/com/pms/controller/PurchaseOrderController.java index 6e192fb..09e3374 100644 --- a/src/com/pms/controller/PurchaseOrderController.java +++ b/src/com/pms/controller/PurchaseOrderController.java @@ -2112,6 +2112,44 @@ public class PurchaseOrderController { return resultMap; } + /** + * 도면 파일 개수 조회 (AJAX) + * @param request + * @param paramMap - objId (PURCHASE_ORDER_MASTER_OBJID) + * @return + */ + @ResponseBody + @RequestMapping("/purchaseOrder/getDrawingFileCount.do") + public Map getDrawingFileCount(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + + try { + String objId = CommonUtils.checkNull(paramMap.get("objId")); + + if("".equals(objId)){ + resultMap.put("result", "error"); + resultMap.put("message", "잘못된 요청입니다."); + return resultMap; + } + + // 도면 파일 개수 조회 + paramMap.put("PURCHASE_ORDER_MASTER_OBJID", objId); + paramMap.put("MULTI_MASTER_OBJID", objId); + List fileList = commonService.selectList("purchaseOrder.purchaseOrderPartFileListForMail", null, paramMap); + + int count = (fileList != null) ? fileList.size() : 0; + resultMap.put("result", "success"); + resultMap.put("count", count); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("result", "error"); + resultMap.put("message", "도면 파일 조회 중 오류가 발생했습니다."); + } + + return resultMap; + } + /** * PDF 청크 업로드 (AJAX) * @param request @@ -2234,4 +2272,48 @@ public class PurchaseOrderController { return resultMap; } + + /** + * 발주 취소 전 입고 여부 확인 + * @param request + * @param paramMap + * @return + */ + @ResponseBody + @RequestMapping("/purchaseOrder/checkReceiptForCancel.do") + public Map checkReceiptForCancel(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + + try { + resultMap = purchaseOrderService.checkReceiptForCancel(request, paramMap); + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("hasReceipt", true); + resultMap.put("message", "입고 확인 중 오류가 발생했습니다."); + } + + return resultMap; + } + + /** + * 발주 취소 실행 (입고가 없는 경우만) + * @param request + * @param paramMap + * @return + */ + @ResponseBody + @RequestMapping("/purchaseOrder/executeOrderCancel.do") + public Map executeOrderCancel(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + + try { + resultMap = purchaseOrderService.executeOrderCancel(request, paramMap); + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("result", false); + resultMap.put("message", "발주 취소 중 오류가 발생했습니다: " + e.getMessage()); + } + + return resultMap; + } } diff --git a/src/com/pms/mapper/productionplanning.xml b/src/com/pms/mapper/productionplanning.xml index 841269f..f79103a 100644 --- a/src/com/pms/mapper/productionplanning.xml +++ b/src/com/pms/mapper/productionplanning.xml @@ -3294,6 +3294,7 @@ 0 AS PO_QTY, '' AS VENDOR, 0 AS UNIT_PRICE, + 0 AS PROCESSING_UNIT_PRICE, 0 AS TOTAL_PRICE, 1 AS LEVEL FROM @@ -3374,6 +3375,7 @@ 0 AS PO_QTY, '' AS VENDOR, 0 AS UNIT_PRICE, + 0 AS PROCESSING_UNIT_PRICE, 0 AS TOTAL_PRICE, 1 AS LEVEL FROM @@ -3850,7 +3852,7 @@ PROCESSING_VENDOR, PROCESSING_DEADLINE, GRINDING_DEADLINE, REQUIRED_QTY, ORDER_QTY, PRODUCTION_QTY, STOCK_QTY, SHORTAGE_QTY, NET_QTY, PO_QTY, - VENDOR, UNIT_PRICE, TOTAL_PRICE, CURRENCY, LEAD_TIME, MIN_ORDER_QTY, + VENDOR, UNIT_PRICE, PROCESSING_UNIT_PRICE, TOTAL_PRICE, CURRENCY, LEAD_TIME, MIN_ORDER_QTY, PROPOSAL_DATE, @@ -3863,7 +3865,7 @@ #{processingVendor}, #{processingDeadline}, #{grindingDeadline}, #{requiredQty}, #{orderQty}, #{productionQty}, #{stockQty}, #{shortageQty}, #{netQty}, #{poQty}, - #{vendor}, #{unitPrice}, #{totalPrice}, #{currency}, #{leadTime}, #{minOrderQty}, + #{vendor}, #{unitPrice}, #{processingUnitPrice}, #{totalPrice}, #{currency}, #{leadTime}, #{minOrderQty}, #{proposalDate}, @@ -4049,6 +4051,7 @@ MD.PO_QTY, MD.VENDOR, MD.UNIT_PRICE, + MD.PROCESSING_UNIT_PRICE, MD.TOTAL_PRICE, MD.CURRENCY, MD.LEAD_TIME, @@ -4102,6 +4105,7 @@ SHORTAGE_QTY, VENDOR, UNIT_PRICE, + PROCESSING_UNIT_PRICE, TOTAL_PRICE, CURRENCY, LEAD_TIME, @@ -4146,6 +4150,7 @@ A.SHORTAGE_QTY, A.VENDOR, A.UNIT_PRICE, + A.PROCESSING_UNIT_PRICE, A.TOTAL_PRICE, A.CURRENCY, A.LEAD_TIME, @@ -4198,6 +4203,7 @@ B.SHORTAGE_QTY, B.VENDOR, B.UNIT_PRICE, + B.PROCESSING_UNIT_PRICE, B.TOTAL_PRICE, B.CURRENCY, B.LEAD_TIME, @@ -4250,6 +4256,7 @@ V.VENDOR, (SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = V.VENDOR) AS VENDOR_NAME, V.UNIT_PRICE, + V.PROCESSING_UNIT_PRICE, V.TOTAL_PRICE, V.CURRENCY, V.LEAD_TIME, @@ -4412,6 +4419,7 @@ NULL AS SHORTAGE_QTY, NULL AS VENDOR, NULL AS UNIT_PRICE, + NULL AS PROCESSING_UNIT_PRICE, NULL AS TOTAL_PRICE, NULL AS CURRENCY, NULL AS LEAD_TIME, diff --git a/src/com/pms/mapper/purchaseOrder.xml b/src/com/pms/mapper/purchaseOrder.xml index 56624c4..b509da5 100644 --- a/src/com/pms/mapper/purchaseOrder.xml +++ b/src/com/pms/mapper/purchaseOrder.xml @@ -4246,6 +4246,7 @@ SELECT T.* + + + + + + UPDATE PURCHASE_ORDER_MASTER + SET STATUS = #{STATUS} + WHERE OBJID = #{OBJID} + + + + + UPDATE PURCHASE_ORDER_MASTER + SET STATUS = #{STATUS} + WHERE MULTI_MASTER_OBJID = #{OBJID} + + \ No newline at end of file diff --git a/src/com/pms/mapper/salesMng.xml b/src/com/pms/mapper/salesMng.xml index 3009f6a..9143a35 100644 --- a/src/com/pms/mapper/salesMng.xml +++ b/src/com/pms/mapper/salesMng.xml @@ -613,7 +613,7 @@ VALUES ( #{SALES_REQUEST_MASTER_OBJID }, - (SELECT 'R'||TO_CHAR(NOW(),'YYYYMMDD')||'-'||LPAD((COALESCE(MAX(SUBSTR(REQUEST_MNG_NO,11,13)),'0')::INTEGER+1)::TEXT,3,'0') FROM SALES_REQUEST_MASTER WHERE DOC_TYPE = 'PURCHASE_REQUEST' OR DOC_TYPE IS NULL), + (SELECT 'R'||TO_CHAR(NOW(),'YYYYMMDD')||'-'||LPAD((COALESCE(MAX(SUBSTR(REQUEST_MNG_NO,11,13)),'0')::INTEGER+1)::TEXT,3,'0') FROM SALES_REQUEST_MASTER WHERE DOC_TYPE IN ('PURCHASE_REQUEST', 'PURCHASE_REG') OR DOC_TYPE IS NULL), #{REQUEST_CD }, #{PROJECT_NO }, #{RELEASE }, @@ -633,7 +633,7 @@ VALUES #{AREA_CD }, #{CUSTOMER_OBJID }, #{PAID_TYPE }, - 'PURCHASE_REQUEST' + COALESCE(NULLIF(#{DOC_TYPE}, ''), 'PURCHASE_REQUEST') ) ON CONFLICT (OBJID) DO UPDATE SET @@ -757,7 +757,7 @@ VALUES INSERT INTO SALES_REQUEST_PART - ( + ( OBJID, SALES_BOM_QTY_OBJID, PART_OBJID, @@ -771,8 +771,8 @@ VALUES REGDATE, STATUS - ) - VALUES + ) + VALUES ( #{SALES_REQUEST_PART_OBJID }, #{SALES_BOM_QTY_OBJID }, @@ -788,8 +788,8 @@ VALUES #{STATUS} ) ON CONFLICT (OBJID) DO - UPDATE - SET + UPDATE + SET SALES_BOM_QTY_OBJID = #{SALES_BOM_QTY_OBJID }, PART_OBJID = #{PART_OBJID }, SALES_REQUEST_MASTER_OBJID = #{SALES_REQUEST_MASTER_OBJID}, @@ -1117,6 +1117,15 @@ VALUES (SELECT O.UNIT_NO || '-' || O.TASK_NAME FROM PMS_WBS_TASK AS O WHERE O.OBJID = SRM.UNIT_NAME) AS UNIT_CODE_NAME, SRM.STATUS, CASE + -- PURCHASE_REG(구매요청서작성) 페이지용 상태 + WHEN SRM.DOC_TYPE = 'PURCHASE_REG' THEN + CASE + -- 품의서 생성 여부 확인 (PROPOSAL 타입의 SALES_REQUEST_MASTER가 연결되어 있는지) + WHEN EXISTS (SELECT 1 FROM SALES_REQUEST_MASTER P WHERE P.DOC_TYPE = 'PROPOSAL' AND P.PROJECT_NO = SRM.OBJID::VARCHAR) THEN '품의서생성' + WHEN SRM.STATUS = 'confirmed' THEN '확정' + ELSE '작성중' + END + -- 기존 PURCHASE_REQUEST용 상태 WHEN ((SELECT COUNT(1) FROM PURCHASE_ORDER_MASTER AS O WHERE O.SALES_REQUEST_OBJID IN (SRM.OBJID)) >= (SELECT COUNT(1) FROM SALES_REQUEST_PART AS SRP WHERE srp.SALES_REQUEST_master_OBJID = SRM.OBJID )) and ((SELECT COUNT(1) FROM PURCHASE_ORDER_MASTER AS O WHERE O.SALES_REQUEST_OBJID IN (SRM.OBJID)) > 0) THEN '발주완료' WHEN ((SELECT COUNT(1) FROM PURCHASE_ORDER_MASTER AS O WHERE O.SALES_REQUEST_OBJID IN (SRM.OBJID)) < (SELECT COUNT(1) FROM SALES_REQUEST_PART AS SRP WHERE srp.SALES_REQUEST_master_OBJID = SRM.OBJID )) and ((SELECT COUNT(1) FROM PURCHASE_ORDER_MASTER AS O WHERE O.SALES_REQUEST_OBJID IN (SRM.OBJID)) > 0) THEN '발주부분완료' WHEN SRM.STATUS = 'create' THEN '등록' @@ -1125,7 +1134,7 @@ VALUES WHEN SRM.STATUS = 'approvalRequest' THEN '결재중' WHEN SRM.STATUS = 'approvalComplete' THEN '결재완료' WHEN SRM.STATUS = 'reject' THEN '반려' - + WHEN SRM.STATUS = 'confirmed' THEN '확정' ELSE '' END STATUS_TITLE, SRM.RECEIPT_USER_ID, @@ -1224,7 +1233,15 @@ VALUES AND SRP.SUB_RNUM = 1 --> WHERE 1=1 -- 구매요청서만 조회 (품의서 제외) - AND (SRM.DOC_TYPE = 'PURCHASE_REQUEST' OR SRM.DOC_TYPE IS NULL) + -- DOC_TYPE_FILTER가 있으면 해당 값으로 필터링, 없으면 기존 조건 적용 + + + AND SRM.DOC_TYPE = #{DOC_TYPE_FILTER} + + + AND (SRM.DOC_TYPE = 'PURCHASE_REQUEST' OR SRM.DOC_TYPE IS NULL) + + AND TO_CHAR(REGDATE,'YYYY') = #{Year} @@ -1403,24 +1420,24 @@ VALUES @@ -3382,6 +3420,9 @@ ORDER BY V.PATH2 PROCESSING_VENDOR = #{PROCESSING_VENDOR}, UNIT_PRICE = COALESCE(NULLIF(TRIM(#{UNIT_PRICE}::TEXT), '')::NUMERIC, 0), TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), + PROCESSING_UNIT_PRICE = COALESCE(NULLIF(TRIM(#{PROCESSING_UNIT_PRICE}::TEXT), '')::NUMERIC, 0), + PROCESSING_TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{PROCESSING_TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), + GRAND_TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{GRAND_TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), EDITER = #{EDITER}, EDIT_DATE = NOW() WHERE OBJID::VARCHAR = #{OBJID} @@ -3397,6 +3438,9 @@ ORDER BY V.PATH2 PROCESSING_VENDOR = #{PROCESSING_VENDOR}, UNIT_PRICE = COALESCE(NULLIF(TRIM(#{UNIT_PRICE}::TEXT), '')::NUMERIC, 0), TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), + PROCESSING_UNIT_PRICE = COALESCE(NULLIF(TRIM(#{PROCESSING_UNIT_PRICE}::TEXT), '')::NUMERIC, 0), + PROCESSING_TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{PROCESSING_TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), + GRAND_TOTAL_PRICE = COALESCE(NULLIF(TRIM(#{GRAND_TOTAL_PRICE}::TEXT), '')::NUMERIC, 0), WRITER = #{EDITER} WHERE OBJID::VARCHAR = #{OBJID} @@ -3418,9 +3462,9 @@ ORDER BY V.PATH2 @@ -3459,7 +3503,7 @@ ORDER BY V.PATH2 - + - + + + + - + + + + - + + + + + + + INSERT INTO SALES_REQUEST_MASTER ( @@ -3610,7 +3780,7 @@ ORDER BY V.PATH2 ) - + INSERT INTO SALES_REQUEST_PART ( OBJID, @@ -3645,7 +3815,42 @@ ORDER BY V.PATH2 WHERE OBJID = #{SOURCE_OBJID} - + + + INSERT INTO SALES_REQUEST_PART ( + OBJID, + SALES_REQUEST_MASTER_OBJID, + PART_OBJID, + QTY, + UNIT_PRICE, + TOTAL_PRICE, + VENDOR_PM, + NET_QTY, + PO_QTY, + USE_YN, + PROPOSAL_DATE, + WRITER, + REGDATE + ) + SELECT + #{NEW_OBJID}, + #{PROPOSAL_MASTER_OBJID}, + PART_OBJID, + PRODUCTION_QTY AS QTY, + PROCESSING_UNIT_PRICE AS UNIT_PRICE, + PROCESSING_TOTAL_PRICE AS TOTAL_PRICE, + PROCESSING_VENDOR AS VENDOR_PM, + NET_QTY, + PO_QTY, + USE_YN, + NOW() AS PROPOSAL_DATE, + #{WRITER}, + NOW() + FROM MBOM_DETAIL + WHERE OBJID = #{SOURCE_OBJID} + + + INSERT INTO SALES_REQUEST_PART ( OBJID, @@ -3680,7 +3885,42 @@ ORDER BY V.PATH2 WHERE OBJID = #{SOURCE_OBJID} - + + + INSERT INTO SALES_REQUEST_PART ( + OBJID, + SALES_REQUEST_MASTER_OBJID, + PART_OBJID, + QTY, + UNIT_PRICE, + TOTAL_PRICE, + VENDOR_PM, + NET_QTY, + PO_QTY, + USE_YN, + PROPOSAL_DATE, + WRITER, + REGDATE + ) + SELECT + #{NEW_OBJID}, + #{PROPOSAL_MASTER_OBJID}, + PART_OBJID, + PRODUCTION_QTY AS QTY, + PROCESSING_UNIT_PRICE AS UNIT_PRICE, + PROCESSING_TOTAL_PRICE AS TOTAL_PRICE, + PROCESSING_VENDOR AS VENDOR_PM, + NET_QTY, + PO_QTY, + USE_YN, + NOW() AS PROPOSAL_DATE, + #{WRITER}, + NOW() + FROM SALES_REQUEST_PART + WHERE OBJID = #{SOURCE_OBJID} + + + UPDATE SALES_REQUEST_PART SET PROPOSAL_DATE = NOW() @@ -3690,7 +3930,17 @@ ORDER BY V.PATH2 - + + + UPDATE SALES_REQUEST_PART + SET PROCESSING_PROPOSAL_DATE = NOW() + WHERE OBJID IN + + #{objid} + + + + UPDATE MBOM_DETAIL SET PROPOSAL_DATE = NOW() @@ -3700,6 +3950,16 @@ ORDER BY V.PATH2 + + + UPDATE MBOM_DETAIL + SET PROCESSING_PROPOSAL_DATE = NOW() + WHERE OBJID IN + + #{objid} + + +