diff --git a/Dockerfile b/Dockerfile index 0f52dbc..b239562 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,9 @@ COPY src /usr/local/tomcat/webapps/ROOT/WEB-INF/src # Copy custom Tomcat context configuration for JNDI COPY ./tomcat-conf/context.xml /usr/local/tomcat/conf/context.xml +# 기본 server.xml의 Connector에 URIEncoding="UTF-8" 추가 (한글 GET 파라미터 처리) +RUN sed -i 's/Connector port="8080"/Connector port="8080" URIEncoding="UTF-8"/' /usr/local/tomcat/conf/server.xml + # Copy database driver if needed (PostgreSQL driver is already in WEB-INF/lib) # COPY path/to/postgresql-driver.jar /usr/local/tomcat/lib/ diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp index bbac628..5ee83ec 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp @@ -252,6 +252,7 @@ } var item = { + objId: $row.find(".item-objid").val() || '', partObjId: $row.find(".item-part-objid").val(), partNo: $row.find(".item-part-no").val() ? $row.find(".item-part-no").val().trim() : "", partName: $row.find(".item-part-name").val() ? $row.find(".item-part-name").val().trim() : "", @@ -1450,22 +1451,17 @@ - - - - - + + + diff --git a/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp b/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp index 5ab2aa4..ee85be9 100644 --- a/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp @@ -151,6 +151,23 @@ $(document).ready(function(){ document.form1.submit(); }); + // 수주확정 + $("#btnOrderConfirm").click(function(){ + var selectedData = _tabulGrid.getSelectedData(); + if(selectedData.length < 1){ + Swal.fire("수주확정할 행을 선택해주십시오."); + return false; + } else if(selectedData.length > 1){ + Swal.fire("한번에 한개의 수주만 확정 가능합니다."); + return false; + } + + var contractObjId = fnc_checkNull(selectedData[0].OBJID); + var currentStatus = fnc_checkNull(selectedData[0].CONTRACT_RESULT_NAME); + + fn_openOrderConfirmPopup(contractObjId, currentStatus); + }); + // 수주취소 $("#btnOrderCancel").click(function(){ var selectedData = _tabulGrid.getSelectedData(); @@ -710,6 +727,73 @@ function fn_displaySerialNoList(serialNumbers){ }); } +// 수주확정 팝업 - 수주상태 선택하여 저장 +function fn_openOrderConfirmPopup(contractObjId, currentStatus){ + // 검색 필터의 수주상태 select에서 옵션 목록을 가져와서 SweetAlert에서 사용 + var optionsHtml = ''; + $("#contract_result option").each(function(){ + var val = $(this).val(); + var text = $(this).text(); + if(val !== ''){ + optionsHtml += ''; + } + }); + + Swal.fire({ + title: '수주확정', + html: '
' + + '
현재 수주상태: ' + (currentStatus || '-') + '
' + + '
' + + '' + + '
', + width: 400, + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: '확정', + cancelButtonText: '취소', + preConfirm: function() { + var selectedVal = document.getElementById('swal_contract_result').value; + if(!selectedVal) { + Swal.showValidationMessage('수주상태를 선택해주세요.'); + return false; + } + return selectedVal; + } + }).then(function(result) { + if(result.isConfirmed) { + $.ajax({ + url: "/contractMgmt/updateOrderStatus.do", + type: "POST", + data: { + objId: contractObjId, + contract_result: result.value + }, + dataType: "json", + success: function(data){ + if(data.result === "SUCCESS"){ + Swal.fire({ + title: '수주확정 완료', + text: '수주상태가 변경되었습니다.', + icon: 'success' + }).then(function(){ + fn_search(); + }); + } else { + Swal.fire("수주확정 저장 중 오류가 발생했습니다."); + } + }, + error: function(){ + Swal.fire("서버 통신 오류가 발생했습니다."); + } + }); + } + }); +} + // 수주취소 팝업 - 품목별 취소 수량 입력 function fn_openOrderCancelPopup(contractObjId){ $.ajax({ @@ -971,7 +1055,8 @@ function openProjectFormPopUp(objId){
- + +
diff --git a/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp index 1809215..5065589 100644 --- a/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp @@ -265,7 +265,7 @@ html += ''; } // ORDER_UNIT_PRICE 수정 가능 - html += ''; + html += ''; // ORDER_SUPPLY_PRICE 자동 계산 html += ''; // ORDER_VAT 수정 가능 @@ -444,27 +444,21 @@ - + - + - + - - - - - - - + + + + - - - - - + diff --git a/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp b/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp index 5521539..ebcc627 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/deliveryMngAcceptanceList.jsp @@ -133,37 +133,37 @@ var columns = [ else if(cn.includes('유로') || cn === 'EUR') s = '€'; else if(cn.includes('엔') || cn === 'JPY') s = '¥'; else if(cn.includes('위안') || cn === 'CNY') s = '¥'; - return s + Number(value).toLocaleString(); - } - }, - {headerHozAlign : 'center', hozAlign : 'right', minWidth : 90, widthGrow : 1, title : '입고금액', field : 'TOTAL_DELIVERY_PRICE', - formatter: function(cell) { - var value = cell.getValue(); - if(!value || value === '' || value === '0') return ''; - var cn = cell.getRow().getData().CURRENCY_NAME || ''; - var s = ''; - if(cn.includes('원') || cn === 'KRW') s = '₩'; - else if(cn.includes('달러') || cn === 'USD') s = '$'; - else if(cn.includes('유로') || cn === 'EUR') s = '€'; - else if(cn.includes('엔') || cn === 'JPY') s = '¥'; - else if(cn.includes('위안') || cn === 'CNY') s = '¥'; - return s + Number(value).toLocaleString(); - } - }, - {headerHozAlign : 'center', hozAlign : 'right', minWidth : 90, widthGrow : 1, title : '미입고금액', field : 'TOTAL_NOT_DELIVERY_PRICE', - formatter: function(cell) { - var value = cell.getValue(); - if(!value || value === '' || value === '0') return ''; - var cn = cell.getRow().getData().CURRENCY_NAME || ''; - var s = ''; - if(cn.includes('원') || cn === 'KRW') s = '₩'; - else if(cn.includes('달러') || cn === 'USD') s = '$'; - else if(cn.includes('유로') || cn === 'EUR') s = '€'; - else if(cn.includes('엔') || cn === 'JPY') s = '¥'; - else if(cn.includes('위안') || cn === 'CNY') s = '¥'; - return s + Number(value).toLocaleString(); - } - }, + return s + Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}); + } + }, + {headerHozAlign : 'center', hozAlign : 'right', minWidth : 90, widthGrow : 1, title : '입고금액', field : 'TOTAL_DELIVERY_PRICE', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value === '' || value === '0') return ''; + var cn = cell.getRow().getData().CURRENCY_NAME || ''; + var s = ''; + if(cn.includes('원') || cn === 'KRW') s = '₩'; + else if(cn.includes('달러') || cn === 'USD') s = '$'; + else if(cn.includes('유로') || cn === 'EUR') s = '€'; + else if(cn.includes('엔') || cn === 'JPY') s = '¥'; + else if(cn.includes('위안') || cn === 'CNY') s = '¥'; + return s + Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}); + } + }, + {headerHozAlign : 'center', hozAlign : 'right', minWidth : 90, widthGrow : 1, title : '미입고금액', field : 'TOTAL_NOT_DELIVERY_PRICE', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value === '' || value === '0') return ''; + var cn = cell.getRow().getData().CURRENCY_NAME || ''; + var s = ''; + if(cn.includes('원') || cn === 'KRW') s = '₩'; + else if(cn.includes('달러') || cn === 'USD') s = '$'; + else if(cn.includes('유로') || cn === 'EUR') s = '€'; + else if(cn.includes('엔') || cn === 'JPY') s = '¥'; + else if(cn.includes('위안') || cn === 'CNY') s = '¥'; + return s + Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}); + } + }, {headerHozAlign : 'center', hozAlign : 'center', minWidth : 100, widthGrow : 1, title : '업체성적서', field : 'INSPECTION_FILE_CNT', formatter:fnc_subInfoValueFormatter, cellClick:function(e, cell){ @@ -269,9 +269,9 @@ function fn_calculateTotalAmount(){ } // 합계 표시 - $("#totalOrderAmount").text(Number(Math.round(totalOrderAmount)).toLocaleString()); - $("#deliveredAmount").text(Number(Math.round(totalDeliveredAmount)).toLocaleString()); - $("#notDeliveredAmount").text(Number(Math.round(totalNotDeliveredAmount)).toLocaleString()); + $("#totalOrderAmount").text(Number(totalOrderAmount).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})); + $("#deliveredAmount").text(Number(totalDeliveredAmount).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})); + $("#notDeliveredAmount").text(Number(totalNotDeliveredAmount).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})); } //수입검사등록 diff --git a/WebContent/WEB-INF/view/purchaseOrder/projectPurchaseDeliveryStatus.jsp b/WebContent/WEB-INF/view/purchaseOrder/projectPurchaseDeliveryStatus.jsp index 545dbc1..08ad2c2 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/projectPurchaseDeliveryStatus.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/projectPurchaseDeliveryStatus.jsp @@ -187,8 +187,8 @@ function _fnc_datepick(){ ${code_map.project_no} - - + + diff --git a/WebContent/WEB-INF/view/purchaseOrder/purchaseCloseList.jsp b/WebContent/WEB-INF/view/purchaseOrder/purchaseCloseList.jsp index de1823b..2b661b1 100644 --- a/WebContent/WEB-INF/view/purchaseOrder/purchaseCloseList.jsp +++ b/WebContent/WEB-INF/view/purchaseOrder/purchaseCloseList.jsp @@ -172,27 +172,39 @@ $(document).ready(function(){ changeMonth: true, changeYear: true }); - // 기존 데이터 세팅 - if(taxType) $('#swal_taxType').val(taxType); - if(taxInvoiceDate) $('#swal_taxInvoiceDate').val(taxInvoiceDate); - if(exportDeclNo) $('#swal_exportDeclNo').val(exportDeclNo); - if(loadingDate) $('#swal_loadingDate').val(loadingDate); - if(foreignType) $('#swal_foreignType').val(foreignType); - if(duty) $('#swal_duty').val(duty); - if(importVat) $('#swal_importVat').val(importVat); - if(exchangeRate) $('#swal_exchangeRate').val(exchangeRate); + // 기존 데이터 세팅 + if(taxType) $('#swal_taxType').val(taxType); + if(taxInvoiceDate) $('#swal_taxInvoiceDate').val(taxInvoiceDate); + if(exportDeclNo) $('#swal_exportDeclNo').val(exportDeclNo); + if(loadingDate) $('#swal_loadingDate').val(loadingDate); + if(foreignType) $('#swal_foreignType').val(foreignType); + if(duty) $('#swal_duty').val(formatMoney(duty)); + if(importVat) $('#swal_importVat').val(formatMoney(importVat)); + if(exchangeRate) $('#swal_exchangeRate').val(formatMoney(exchangeRate)); + + // 금액 입력 필드 포맷 (keyup: 천단위 콤마, blur: 소수점 2자리) + $('#swal_exchangeRate, #swal_duty, #swal_importVat').on('keyup', function(){ + var val = this.value.replace(/[^0-9.]/g, ''); + var parts = val.split('.'); + if(parts.length > 2) val = parts[0] + '.' + parts.slice(1).join(''); + if(parts[0]) parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + this.value = parts.join('.'); + }).on('blur', function(){ + var v = this.value.replace(/,/g, ''); + if(v && !isNaN(v)) this.value = formatMoney(v); + }); }, preConfirm: function() { - return { - taxType: $('#swal_taxType').val(), - taxInvoiceDate: $('#swal_taxInvoiceDate').val(), - exportDeclNo: $('#swal_exportDeclNo').val(), - loadingDate: $('#swal_loadingDate').val(), - foreignType: $('#swal_foreignType').val(), - duty: $('#swal_duty').val(), - importVat: $('#swal_importVat').val(), - exchangeRate: $('#swal_exchangeRate').val() - }; + return { + taxType: $('#swal_taxType').val(), + taxInvoiceDate: $('#swal_taxInvoiceDate').val(), + exportDeclNo: $('#swal_exportDeclNo').val(), + loadingDate: $('#swal_loadingDate').val(), + foreignType: $('#swal_foreignType').val(), + duty: removeComma($('#swal_duty').val()), + importVat: removeComma($('#swal_importVat').val()), + exchangeRate: removeComma($('#swal_exchangeRate').val()) + }; } }).then(function(result) { if (result.isConfirmed) { @@ -307,7 +319,7 @@ var columns = [ else if(cn.includes('유로') || cn === 'EUR') s = '€'; else if(cn.includes('엔') || cn === 'JPY') s = '¥'; else if(cn.includes('위안') || cn === 'CNY') s = '¥'; - return s + Number(value).toLocaleString(); + return s + Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}); } }, // {headerHozAlign:'center', hozAlign:'right', minWidth:90, widthGrow:1, title:'미입고금액', field:'TOTAL_NOT_DELIVERY_PRICE', @@ -322,13 +334,19 @@ var columns = [ }, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'계정과목', field:'SUB_LOCATION_NAME'}, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'국내/해외', field:'FOREIGN_TYPE_NAME'}, - {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'환율', field:'EXCHANGE_RATE'}, + {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'환율', field:'EXCHANGE_RATE', + formatter: function(cell){ var v = cell.getValue(); if(!v || v === '') return ''; return formatMoney(v); } + }, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'과세구분', field:'TAX_TYPE_NAME'}, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'세금계산서발행일', field:'TAX_INVOICE_DATE'}, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'수출신고필증신고번호', field:'EXPORT_DECL_NO'}, {headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1.2, title:'선적일자', field:'LOADING_DATE'}, - {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'관세', field:'DUTY'}, - {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'수입부가세', field:'IMPORT_VAT'}, + {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'관세', field:'DUTY', + formatter: function(cell){ var v = cell.getValue(); if(!v || v === '') return ''; return formatMoney(v); } + }, + {headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:1.2, title:'수입부가세', field:'IMPORT_VAT', + formatter: function(cell){ var v = cell.getValue(); if(!v || v === '') return ''; return formatMoney(v); } + }, {headerHozAlign:'center', hozAlign:'center', minWidth:85, widthGrow:0.6, title:'매입마감', field:'PURCHASE_CLOSE_DATE'} ]; diff --git a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/revenueMgmtList.jsp b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/revenueMgmtList.jsp index b443c64..7927085 100644 --- a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/revenueMgmtList.jsp +++ b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/revenueMgmtList.jsp @@ -523,7 +523,61 @@ var columns = [ formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: 2} }, // 19. S/N - {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : 'S/N', field : 'SERIAL_NO'}, + {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : 'S/N', field : 'SERIAL_NO', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return ''; + var snArr = value.split(','); + for(var i = snArr.length - 1; i >= 0; i--) { if(snArr[i].trim() === '') snArr.splice(i, 1); } + if(snArr.length === 0) return ''; + if(snArr.length === 1) return snArr[0].trim(); + return '' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건'; + }, + cellClick: function(e, cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return; + var snArr = value.split(','); + var validSns = []; + for(var i = 0; i < snArr.length; i++) { if(snArr[i].trim() !== '') validSns.push(snArr[i].trim()); } + if(validSns.length <= 1) return; + var html = ''; + html += ''; + for(var i = 0; i < validSns.length; i++) { html += ''; } + html += '
번호S/N
' + (i+1) + '' + validSns[i] + '
'; + Swal.fire({title: 'S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'}); + } + }, + // 19-1. 분할S/N + {headerHozAlign : 'center', hozAlign : 'left', width : '120', title : '분할S/N', field : 'SPLIT_SERIAL_NO', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return ''; + var snArr = value.split(','); + for(var i = snArr.length - 1; i >= 0; i--) { + if(snArr[i].trim() === '') snArr.splice(i, 1); + } + if(snArr.length === 0) return ''; + if(snArr.length === 1) return snArr[0].trim(); + return '' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건'; + }, + cellClick: function(e, cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return; + var snArr = value.split(','); + var validSns = []; + for(var i = 0; i < snArr.length; i++) { + if(snArr[i].trim() !== '') validSns.push(snArr[i].trim()); + } + if(validSns.length === 0) return; + var html = ''; + html += ''; + for(var i = 0; i < validSns.length; i++) { + html += ''; + } + html += '
번호S/N
' + (i+1) + '' + validSns[i] + '
'; + Swal.fire({title: '분할S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'}); + } + }, // 20. 품번 {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PRODUCT_NO'}, // 21. 과세구분 diff --git a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesMgmtList.jsp b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesMgmtList.jsp index e270271..33f1236 100644 --- a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesMgmtList.jsp +++ b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesMgmtList.jsp @@ -442,7 +442,61 @@ var columns = [ formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: 2} }, // 24. S/N - {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : 'S/N', field : 'SERIAL_NO'}, + {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : 'S/N', field : 'SERIAL_NO', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return ''; + var snArr = value.split(','); + for(var i = snArr.length - 1; i >= 0; i--) { if(snArr[i].trim() === '') snArr.splice(i, 1); } + if(snArr.length === 0) return ''; + if(snArr.length === 1) return snArr[0].trim(); + return '' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건'; + }, + cellClick: function(e, cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return; + var snArr = value.split(','); + var validSns = []; + for(var i = 0; i < snArr.length; i++) { if(snArr[i].trim() !== '') validSns.push(snArr[i].trim()); } + if(validSns.length <= 1) return; + var html = ''; + html += ''; + for(var i = 0; i < validSns.length; i++) { html += ''; } + html += '
번호S/N
' + (i+1) + '' + validSns[i] + '
'; + Swal.fire({title: 'S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'}); + } + }, + // 24-1. 분할S/N + {headerHozAlign : 'center', hozAlign : 'left', width : '120', title : '분할S/N', field : 'SPLIT_SERIAL_NO', + formatter: function(cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return ''; + var snArr = value.split(','); + for(var i = snArr.length - 1; i >= 0; i--) { + if(snArr[i].trim() === '') snArr.splice(i, 1); + } + if(snArr.length === 0) return ''; + if(snArr.length === 1) return snArr[0].trim(); + return '' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건'; + }, + cellClick: function(e, cell) { + var value = cell.getValue(); + if(!value || value.trim() === '') return; + var snArr = value.split(','); + var validSns = []; + for(var i = 0; i < snArr.length; i++) { + if(snArr[i].trim() !== '') validSns.push(snArr[i].trim()); + } + if(validSns.length === 0) return; + var html = ''; + html += ''; + for(var i = 0; i < validSns.length; i++) { + html += ''; + } + html += '
번호S/N
' + (i+1) + '' + validSns[i] + '
'; + Swal.fire({title: '분할S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'}); + } + }, // 25. 품번 {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PRODUCT_NO'}, diff --git a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesRegForm.jsp b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesRegForm.jsp index 27ab276..b79cf3f 100644 --- a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesRegForm.jsp +++ b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesRegForm.jsp @@ -547,7 +547,8 @@ function fn_calculateSelectedItem() { console.log("저장 후 확인:", $("#serialNoList").val()); fn_updateSnDisplay(); - + fn_updateSplitSnDisplay(); + Swal.close(); } @@ -556,6 +557,169 @@ function fn_calculateSelectedItem() { Swal.close(); } + // === 분할S/N 관련 함수 === + var usedSplitSerialNos = "${usedSplitSerialNos}"; // 이미 다른 출하에서 사용된 S/N + var selectedSplitSns = []; // 현재 선택된 분할S/N + + // 페이지 로드 시 기존 분할S/N 초기화 + $(function() { + var existingSplitSn = $("#splitSerialNoHidden").val(); + if(existingSplitSn && existingSplitSn.trim() !== '') { + selectedSplitSns = existingSplitSn.split(','); + for(var i = 0; i < selectedSplitSns.length; i++) { + selectedSplitSns[i] = selectedSplitSns[i].trim(); + } + } + fn_updateSplitSnDisplay(); + }); + + // 사용 가능한 S/N 목록 계산 (전체 S/N - 이미 사용된 S/N + 현재 수정 중인 분할S/N) + function fn_getAvailableSns() { + var allSns = []; + var serialNoVal = $("#serialNo").val(); + if(serialNoVal && serialNoVal.trim() !== '') { + var snArr = serialNoVal.split(','); + for(var i = 0; i < snArr.length; i++) { + if(snArr[i].trim() !== '') { + allSns.push(snArr[i].trim()); + } + } + } + + // 이미 사용된 S/N 파싱 + var usedSns = []; + if(usedSplitSerialNos && usedSplitSerialNos.trim() !== '') { + var usedArr = usedSplitSerialNos.split(','); + for(var i = 0; i < usedArr.length; i++) { + if(usedArr[i].trim() !== '') { + usedSns.push(usedArr[i].trim()); + } + } + } + + // 사용 가능한 S/N = 전체 - 이미사용(다른 출하) + var available = []; + for(var i = 0; i < allSns.length; i++) { + var isUsed = false; + for(var j = 0; j < usedSns.length; j++) { + if(allSns[i] === usedSns[j]) { + isUsed = true; + break; + } + } + if(!isUsed) { + available.push(allSns[i]); + } + } + return available; + } + + // 분할S/N 선택 팝업 + function fn_openSplitSnPopup() { + var availableSns = fn_getAvailableSns(); + + if(availableSns.length === 0) { + alert('선택 가능한 S/N이 없습니다.\nS/N을 먼저 등록하거나, 이미 모든 S/N이 다른 출하에 배정되었습니다.'); + return; + } + + var popupHtml = '
'; + popupHtml += '

분할S/N 선택

'; + popupHtml += '
'; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += '
'; + popupHtml += '
'; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += ' '; + + for(var i = 0; i < availableSns.length; i++) { + var isChecked = false; + for(var j = 0; j < selectedSplitSns.length; j++) { + if(availableSns[i] === selectedSplitSns[j]) { + isChecked = true; + break; + } + } + popupHtml += ''; + popupHtml += ''; + popupHtml += ''; + popupHtml += ''; + popupHtml += ''; + } + + popupHtml += '
선택번호S/N
'; + popupHtml += ''; + popupHtml += '' + (i+1) + '' + availableSns[i] + '
'; + popupHtml += '
'; + popupHtml += '
'; + popupHtml += ' 선택한 S/N 개수가 판매수량에 자동 반영됩니다.'; + popupHtml += '
'; + popupHtml += '
'; + popupHtml += ' '; + popupHtml += ' '; + popupHtml += '
'; + popupHtml += '
'; + + Swal.fire({ + html: popupHtml, + width: '500px', + showConfirmButton: false, + showCloseButton: true + }); + } + + // 전체선택 + function fn_splitSnSelectAll() { + $(".swal2-html-container .split-sn-chk").prop('checked', true); + } + + // 전체해제 + function fn_splitSnDeselectAll() { + $(".swal2-html-container .split-sn-chk").prop('checked', false); + } + + // 분할S/N 선택 확인 + function fn_confirmSplitSn() { + selectedSplitSns = []; + $(".swal2-html-container .split-sn-chk:checked").each(function() { + selectedSplitSns.push($(this).val()); + }); + + // hidden 필드에 저장 + $("#splitSerialNoHidden").val(selectedSplitSns.join(',')); + + // 판매수량 자동 업데이트 + $("#salesQuantity").val(selectedSplitSns.length); + fn_calculateSupplyPrice(); + + fn_updateSplitSnDisplay(); + Swal.close(); + } + + // 분할S/N 표시 업데이트 + function fn_updateSplitSnDisplay() { + if(selectedSplitSns.length > 0) { + var displayText = selectedSplitSns.join(', '); + if(displayText.length > 80) { + displayText = displayText.substring(0, 77) + '... (' + selectedSplitSns.length + '건)'; + } + $("#splitSnDisplay").text(displayText).css('color', '#333'); + } else { + var serialNoVal = $("#serialNo").val(); + if(serialNoVal && serialNoVal.trim() !== '') { + $("#splitSnDisplay").text('클릭하여 분할S/N 선택').css('color', '#999'); + } else { + $("#splitSnDisplay").text('S/N을 먼저 등록하세요').css('color', '#999'); + } + } + } + function fn_save() { // 출하지시 상태는 자동으로 설정됨 (hidden 필드에 이미 "출하지시" 값 설정) @@ -735,7 +899,19 @@ function fn_calculateSelectedItem() { readonly /> - + + + + + + +
+ S/N을 먼저 등록하세요 +
+ + + diff --git a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/shippingDetailPopup.jsp b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/shippingDetailPopup.jsp index 3dafa70..7b4cbcc 100644 --- a/WebContent/WEB-INF/view/salesmgmt/salesMgmt/shippingDetailPopup.jsp +++ b/WebContent/WEB-INF/view/salesmgmt/salesMgmt/shippingDetailPopup.jsp @@ -95,13 +95,14 @@ function fn_openEditPopup(logId, orderNo) { 출하수량 출하지시상태 S/N + 분할S/N - 출하 내역이 없습니다. + 출하 내역이 없습니다. @@ -116,6 +117,7 @@ function fn_openEditPopup(logId, orderNo) { ${item.shipping_quantity} ${item.shipping_order_status} ${item.serial_no} + ${item.split_serial_no} diff --git a/src/com/pms/mapper/partMng.xml b/src/com/pms/mapper/partMng.xml index f73d71e..23dec61 100644 --- a/src/com/pms/mapper/partMng.xml +++ b/src/com/pms/mapper/partMng.xml @@ -7704,8 +7704,45 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.* ,NULLIF(#{UNIT_QTY}, '') ) - - + + + + UPDATE PART_MNG + SET PART_NAME = #{PART_NAME} + ,UNIT = #{UNIT} + ,SPEC = #{SPEC} + ,MATERIAL = #{MATERIAL} + ,THICKNESS = NULLIF(#{THICKNESS}, '') + ,WIDTH = NULLIF(#{WIDTH}, '') + ,HEIGHT = NULLIF(#{HEIGHT}, '') + ,OUT_DIAMETER = NULLIF(#{OUT_DIAMETER}, '') + ,IN_DIAMETER = NULLIF(#{IN_DIAMETER}, '') + ,LENGTH = NULLIF(#{LENGTH}, '') + ,REMARK = #{REMARK} + ,PART_TYPE = #{PART_TYPE} + ,SUPPLY_CODE = #{SUPPLY_CODE} + ,MAKER = #{MAKER} + ,POST_PROCESSING = #{POST_PROCESSING} + ,HEAT_TREATMENT_HARDNESS = #{HEAT_TREATMENT_HARDNESS} + ,HEAT_TREATMENT_METHOD = #{HEAT_TREATMENT_METHOD} + ,SURFACE_TREATMENT = #{SURFACE_TREATMENT} + ,ACCTFG = #{ACCTFG} + ,ODRFG = #{ODRFG} + ,UNIT_DC = #{UNIT_DC} + ,UNITMANG_DC = #{UNITMANG_DC} + ,UNITCHNG_NB = COALESCE(NULLIF(#{UNITCHNG_NB}, ''), '0')::NUMERIC + ,LOT_FG = COALESCE(#{LOT_FG}, '0') + ,USE_YN = COALESCE(#{USE_YN}, '1') + ,QC_FG = COALESCE(#{QC_FG}, '0') + ,SETITEM_FG = COALESCE(#{SETITEM_FG}, '0') + ,REQ_FG = COALESCE(#{REQ_FG}, '0') + ,UNIT_LENGTH = NULLIF(#{UNIT_LENGTH}, '') + ,UNIT_QTY = NULLIF(#{UNIT_QTY}, '') + ,EDIT_DATE = now() + WHERE OBJID = #{PART_OBJID} + + + @@ -4935,7 +4946,13 @@ FROM PROJECT_MGMT PM LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID - LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID + LEFT OUTER JOIN CONTRACT_ITEM CI ON ( + CASE + WHEN PM.CONTRACT_ITEM_OBJID IS NOT NULL THEN CI.OBJID::VARCHAR = PM.CONTRACT_ITEM_OBJID + ELSE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID AND CI.PART_OBJID = PM.PART_OBJID + END + ) + AND CI.STATUS = 'ACTIVE' AND PM.PART_OBJID = CI.PART_OBJID AND CI.STATUS = 'ACTIVE' LEFT OUTER JOIN PRODUCTION_PLAN PP ON PP.PROJECT_OBJID = PM.OBJID @@ -5008,8 +5025,12 @@ ), '') AS SERIAL_NO FROM PROJECT_MGMT PM LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID - LEFT JOIN CONTRACT_ITEM CI ON CI.CONTRACT_OBJID = PM.CONTRACT_OBJID - AND CI.PART_OBJID = PM.PART_OBJID + LEFT JOIN CONTRACT_ITEM CI ON ( + CASE + WHEN PM.CONTRACT_ITEM_OBJID IS NOT NULL THEN CI.OBJID::VARCHAR = PM.CONTRACT_ITEM_OBJID + ELSE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID AND CI.PART_OBJID = PM.PART_OBJID + END + ) AND CI.STATUS = 'ACTIVE' WHERE PM.OBJID::VARCHAR = #{projectObjid} @@ -5051,8 +5072,12 @@ COALESCE( (SELECT STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SERIAL_NO) FROM PROJECT_MGMT PM - JOIN CONTRACT_ITEM CI ON CI.CONTRACT_OBJID = PM.CONTRACT_OBJID - AND CI.PART_OBJID = PM.PART_OBJID AND CI.STATUS = 'ACTIVE' + JOIN CONTRACT_ITEM CI ON ( + CASE + WHEN PM.CONTRACT_ITEM_OBJID IS NOT NULL THEN CI.OBJID::VARCHAR = PM.CONTRACT_ITEM_OBJID + ELSE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID AND CI.PART_OBJID = PM.PART_OBJID + END + ) AND CI.STATUS = 'ACTIVE' JOIN CONTRACT_ITEM_SERIAL CIS ON CIS.ITEM_OBJID = CI.OBJID AND UPPER(CIS.STATUS) = 'ACTIVE' AND CIS.SERIAL_NO IS NOT NULL WHERE PM.OBJID::VARCHAR = PP.PROJECT_OBJID), @@ -5346,7 +5371,13 @@ FROM MBOM_HISTORY MH INNER JOIN MBOM_HEADER MHD ON MH.MBOM_HEADER_OBJID = MHD.OBJID INNER JOIN PROJECT_MGMT PM ON MHD.PROJECT_OBJID = PM.OBJID::VARCHAR - LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID + LEFT OUTER JOIN CONTRACT_ITEM CI ON ( + CASE + WHEN PM.CONTRACT_ITEM_OBJID IS NOT NULL THEN CI.OBJID::VARCHAR = PM.CONTRACT_ITEM_OBJID + ELSE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID AND CI.PART_OBJID = PM.PART_OBJID + END + ) + AND CI.STATUS = 'ACTIVE' LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID WHERE 1=1 @@ -5394,5 +5425,60 @@ INNER JOIN PROJECT_MGMT PM ON MHD.PROJECT_OBJID = PM.OBJID::VARCHAR WHERE MH.OBJID::VARCHAR = #{historyObjId} - + + + + + + + + diff --git a/src/com/pms/mapper/project.xml b/src/com/pms/mapper/project.xml index 535257b..bac22cc 100644 --- a/src/com/pms/mapper/project.xml +++ b/src/com/pms/mapper/project.xml @@ -3933,12 +3933,13 @@ AND S.SERIAL_NO IS NOT NULL) AS SERIAL_NO -- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT.DUE_DATE, 없으면 CONTRACT_MGMT.due_date ,COALESCE( - (SELECT CI.DUE_DATE - FROM CONTRACT_ITEM CI - WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID - AND CI.PART_OBJID = T.PART_OBJID - AND CI.STATUS = 'ACTIVE'), - T.DUE_DATE, + (SELECT CI.DUE_DATE + FROM CONTRACT_ITEM CI + WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID + AND CI.PART_OBJID = T.PART_OBJID + AND CI.STATUS = 'ACTIVE' + ORDER BY CI.OBJID DESC LIMIT 1), + T.DUE_DATE, (SELECT CM.due_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) ) AS REQ_DEL_DATE -- 영업관리_주문서관리_수주등록 @@ -3951,9 +3952,10 @@ ,PRODUCTION_TEAM_3 -- 출하일: sales_registration 테이블에서 가져오기 (영업관리_판매관리와 동일) ,COALESCE( - (SELECT TO_CHAR(SR.shipping_date, 'YYYY-MM-DD') - FROM sales_registration SR - WHERE SR.project_no = T.PROJECT_NO), + (SELECT TO_CHAR(SR.shipping_date, 'YYYY-MM-DD') + FROM sales_registration SR + WHERE SR.project_no = T.PROJECT_NO + ORDER BY SR.sale_no DESC LIMIT 1), '' ) AS SHIPMENT_DATE ,(((SELECT SUM(COALESCE(DESIGN_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID) @@ -7468,6 +7470,7 @@ SELECT ,PART_NO ,PART_NAME ,QUANTITY + ,CONTRACT_ITEM_OBJID ) ( @@ -7609,12 +7612,21 @@ SELECT ,#{part_no} ,#{part_name} ,#{quantity} + ,#{contract_item_objid} FROM CONTRACT_MGMT WHERE OBJID=#{objId} ) - - + + + + UPDATE PROJECT_MGMT + SET CONTRACT_ITEM_OBJID = #{contract_item_objid} + WHERE CONTRACT_OBJID = #{contract_objid} + AND PART_OBJID = #{part_objid} + AND CONTRACT_ITEM_OBJID IS NULL + + INSERT INTO diff --git a/src/com/pms/salesmgmt/controller/SalesNcollectMgmtController.java b/src/com/pms/salesmgmt/controller/SalesNcollectMgmtController.java index 0ac0e66..93520cd 100644 --- a/src/com/pms/salesmgmt/controller/SalesNcollectMgmtController.java +++ b/src/com/pms/salesmgmt/controller/SalesNcollectMgmtController.java @@ -441,7 +441,28 @@ public class SalesNcollectMgmtController { // orderInfo로 견적 정보 전달 (saleInfo가 이미 모든 필요한 정보를 포함) request.setAttribute("orderInfo", saleInfo); } - + + // 이미 사용된 분할S/N 조회 (현재 수정 중인 logId 제외) + if(paramMap.get("orderNo") != null && !paramMap.get("orderNo").equals("")) { + Map usedSnParam = new HashMap(); + usedSnParam.put("projectNo", paramMap.get("orderNo")); + if(paramMap.get("logId") != null && !paramMap.get("logId").equals("")) { + usedSnParam.put("excludeLogId", paramMap.get("logId")); + } + List> usedSnList = salesNcollectMgmtService.getUsedSplitSerialNos(usedSnParam); + + // 이미 사용된 S/N들을 하나의 콤마 구분 문자열로 합침 + StringBuilder usedSnBuilder = new StringBuilder(); + for(Map usedSn : usedSnList) { + String splitSn = (String) usedSn.get("split_serial_no"); + if(splitSn != null && !splitSn.isEmpty()) { + if(usedSnBuilder.length() > 0) usedSnBuilder.append(","); + usedSnBuilder.append(splitSn); + } + } + request.setAttribute("usedSplitSerialNos", usedSnBuilder.toString()); + } + // 수정 모드: saleInfo에서 담당자 선택값 반영 if(saleInfo != null && saleInfo.get("MANAGER") != null) { String selectedManager = saleInfo.get("MANAGER").toString(); diff --git a/src/com/pms/salesmgmt/mapper/contractMgmt.xml b/src/com/pms/salesmgmt/mapper/contractMgmt.xml index 7153949..d680e10 100644 --- a/src/com/pms/salesmgmt/mapper/contractMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/contractMgmt.xml @@ -1560,10 +1560,12 @@ ,CONTRACT_DEL_DATE = #{contract_del_date} ,CONTRACT_COMPANY = #{contract_company} ,CONTRACT_DATE = #{contract_date} - ,PO_NO = #{po_no} - ,MANUFACTURE_PLANT = #{manufacture_plant} - ,CONTRACT_RESULT = #{contract_result} - ,PROJECT_NAME = #{project_name} + ,PO_NO = #{po_no} + ,MANUFACTURE_PLANT = #{manufacture_plant} + + ,CONTRACT_RESULT = #{contract_result} + + ,PROJECT_NAME = #{project_name} ,SPEC_USER_ID = #{spec_user_id} ,SPEC_PLAN_DATE = #{spec_plan_date} ,SPEC_COMP_DATE = #{spec_comp_date} @@ -1730,10 +1732,12 @@ ,CONTRACT_DEL_DATE = #{contract_del_date} ,CONTRACT_COMPANY = #{contract_company} ,CONTRACT_DATE = #{contract_date} - ,PO_NO = #{po_no} - ,MANUFACTURE_PLANT = #{manufacture_plant} - ,CONTRACT_RESULT = #{contract_result} - ,PROJECT_NAME = #{project_name} + ,PO_NO = #{po_no} + ,MANUFACTURE_PLANT = #{manufacture_plant} + + ,CONTRACT_RESULT = #{contract_result} + + ,PROJECT_NAME = #{project_name} ,AREA_CD = #{area_cd} --> @@ -4991,7 +4995,9 @@ WHERE UPDATE CONTRACT_MGMT SET + CONTRACT_RESULT = #{contract_result}, + PO_NO = #{po_no}, ORDER_DATE = #{order_date}, CONTRACT_CURRENCY = #{contract_currency}, @@ -5083,7 +5089,9 @@ WHERE PAID_TYPE = #{paid_type}, RECEIPT_DATE = #{receipt_date}, REQ_DEL_DATE = #{req_del_date}, + CONTRACT_RESULT = #{contract_result}, + PO_NO = #{po_no}, ORDER_DATE = #{order_date}, CONTRACT_CURRENCY = #{contract_currency}, @@ -5629,6 +5637,67 @@ WHERE STATUS = 'ACTIVE' + + + INSERT INTO CONTRACT_ITEM ( + OBJID, + CONTRACT_OBJID, + SEQ, + PART_OBJID, + PART_NO, + PART_NAME, + QUANTITY, + DUE_DATE, + CUSTOMER_REQUEST, + RETURN_REASON, + REGDATE, + WRITER, + STATUS, + ORDER_QUANTITY, + ORDER_UNIT_PRICE, + ORDER_SUPPLY_PRICE, + ORDER_VAT, + ORDER_TOTAL_AMOUNT + ) VALUES ( + #{objId}, + #{contractObjId}, + #{seq}, + #{partObjId}, + #{partNo}, + #{partName}, + CASE WHEN #{quantity} = '' OR #{quantity} IS NULL THEN NULL ELSE #{quantity}::INTEGER END, + #{dueDate}, + #{customerRequest}, + #{returnReason}, + NOW(), + #{writer}, + 'ACTIVE', + #{orderQuantity}, + #{orderUnitPrice}, + #{orderSupplyPrice}, + #{orderVat}, + #{orderTotalAmount} + ) + ON CONFLICT (OBJID) DO UPDATE + SET + SEQ = #{seq}, + PART_OBJID = #{partObjId}, + PART_NO = #{partNo}, + PART_NAME = #{partName}, + QUANTITY = CASE WHEN #{quantity} = '' OR #{quantity} IS NULL THEN NULL ELSE #{quantity}::INTEGER END, + DUE_DATE = #{dueDate}, + CUSTOMER_REQUEST = #{customerRequest}, + RETURN_REASON = #{returnReason}, + CHGDATE = NOW(), + CHG_USER_ID = #{writer}, + STATUS = 'ACTIVE', + ORDER_QUANTITY = #{orderQuantity}, + ORDER_UNIT_PRICE = #{orderUnitPrice}, + ORDER_SUPPLY_PRICE = #{orderSupplyPrice}, + ORDER_VAT = #{orderVat}, + ORDER_TOTAL_AMOUNT = #{orderTotalAmount} + + INSERT INTO CONTRACT_ITEM_SERIAL ( diff --git a/src/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml b/src/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml index b66e833..e9b12f2 100644 --- a/src/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml @@ -844,14 +844,9 @@ FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE, T.PART_NO AS PRODUCT_NO, T.PART_NAME AS PRODUCT_NAME, - -- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 없으면 판매등록 텍스트 fallback (그리드 요약용) + -- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 없으면 판매등록 텍스트 fallback (콤마 구분 전체 목록) COALESCE( - (SELECT - CASE - WHEN COUNT(*) = 0 THEN NULL - WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO) - ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건' - END + (SELECT STRING_AGG(CIS.SERIAL_NO, ',' ORDER BY CIS.SEQ) FROM CONTRACT_ITEM CI LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE' WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID @@ -860,23 +855,34 @@ AND CIS.SERIAL_NO IS NOT NULL), SR.serial_no ) AS SERIAL_NO, + -- 분할S/N: shipment_log에서 해당 프로젝트의 모든 분할S/N 집계 + COALESCE( + (SELECT STRING_AGG(SL_SN.split_serial_no, ',' ORDER BY SL_SN.log_id) + FROM shipment_log SL_SN + WHERE SL_SN.target_objid = T.PROJECT_NO + AND SL_SN.split_serial_no IS NOT NULL + AND SL_SN.split_serial_no != ''), + '' + ) AS SPLIT_SERIAL_NO, COALESCE(NULLIF(REPLACE(T.QUANTITY, ',', ''), '')::numeric, 0) AS ORDER_QUANTITY, -- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT COALESCE( - (SELECT CI.DUE_DATE - FROM CONTRACT_ITEM CI - WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID - AND CI.PART_OBJID = T.PART_OBJID - AND CI.STATUS = 'ACTIVE'), - T.DUE_DATE, + (SELECT CI.DUE_DATE + FROM CONTRACT_ITEM CI + WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID + AND CI.PART_OBJID = T.PART_OBJID + AND CI.STATUS = 'ACTIVE' + ORDER BY CI.OBJID DESC LIMIT 1), + T.DUE_DATE, (SELECT CM.due_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) ) AS REQUEST_DATE, -- 고객요청사항: CONTRACT_ITEM에서만 가져옴 (견적관리와 완전히 동일) - (SELECT CI.CUSTOMER_REQUEST - FROM CONTRACT_ITEM CI - WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID - AND CI.PART_OBJID = T.PART_OBJID - AND CI.STATUS = 'ACTIVE') AS CUSTOMER_REQUEST, + (SELECT CI.CUSTOMER_REQUEST + FROM CONTRACT_ITEM CI + WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID + AND CI.PART_OBJID = T.PART_OBJID + AND CI.STATUS = 'ACTIVE' + ORDER BY CI.OBJID DESC LIMIT 1) AS CUSTOMER_REQUEST, CODE_NAME(T.CONTRACT_RESULT) AS ORDER_STATUS, (SELECT CM.PO_NO FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PO_NO, COALESCE(T.CONTRACT_DATE, (SELECT CM.order_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)) AS ORDER_DATE, @@ -1572,20 +1578,22 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC COALESCE(NULLIF(REPLACE(T.QUANTITY, ',', ''), '')::numeric, 0) AS ORDER_QUANTITY, -- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT COALESCE( - (SELECT CI.DUE_DATE - FROM CONTRACT_ITEM CI - WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID - AND CI.PART_OBJID = T.PART_OBJID - AND CI.STATUS = 'ACTIVE'), - T.DUE_DATE, + (SELECT CI.DUE_DATE + FROM CONTRACT_ITEM CI + WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID + AND CI.PART_OBJID = T.PART_OBJID + AND CI.STATUS = 'ACTIVE' + ORDER BY CI.OBJID DESC LIMIT 1), + T.DUE_DATE, (SELECT CM.due_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) ) AS REQUEST_DATE, -- 고객요청사항: CONTRACT_ITEM에서만 가져옴 (견적관리와 완전히 동일) - (SELECT CI.CUSTOMER_REQUEST - FROM CONTRACT_ITEM CI - WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID - AND CI.PART_OBJID = T.PART_OBJID - AND CI.STATUS = 'ACTIVE') AS CUSTOMER_REQUEST, + (SELECT CI.CUSTOMER_REQUEST + FROM CONTRACT_ITEM CI + WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID + AND CI.PART_OBJID = T.PART_OBJID + AND CI.STATUS = 'ACTIVE' + ORDER BY CI.OBJID DESC LIMIT 1) AS CUSTOMER_REQUEST, CODE_NAME(T.CONTRACT_RESULT) AS ORDER_STATUS, T.PO_NO, COALESCE(T.CONTRACT_DATE, (SELECT CM.order_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)) AS ORDER_DATE, @@ -1898,7 +1906,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC remaining_quantity, shipping_status, shipping_date, shipping_method, sales_unit_price, sales_supply_price, sales_vat, sales_total_amount, sales_currency, sales_exchange_rate, manager_user_id, incoterms, - serial_no, parent_sale_no, reg_user_id + serial_no, parent_sale_no, reg_user_id, split_serial_no ) VALUES ( #{targetObjid}, 'SPLIT_SHIPMENT', '분할 출하', #{salesQuantity}::integer, #{originalQuantity}::integer, #{remainingQuantity}::integer, @@ -1912,7 +1920,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC #{shippingMethod}, #{salesUnitPrice}::numeric, #{salesSupplyPrice}::numeric, #{salesVat}::numeric, #{salesTotalAmount}::numeric, #{salesCurrency}, #{salesExchangeRate}::numeric, #{managerUserId}, #{incoterms}, #{serialNo}, - #{parentSaleNo}::integer, #{cretEmpNo} + #{parentSaleNo}::integer, #{cretEmpNo}, #{splitSerialNo} ) @@ -1990,6 +1998,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC WHERE PM.PROJECT_NO = SL.target_objid AND CI.STATUS = 'ACTIVE' ), '-') AS serial_no, SL.target_objid AS project_no, + COALESCE(SL.split_serial_no, '-') AS split_serial_no, TO_CHAR(SL.reg_date, 'YYYY-MM-DD HH24:MI:SS') AS reg_date FROM shipment_log SL WHERE SL.target_objid = #{projectNo} @@ -2021,6 +2030,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC COALESCE(SL.sales_exchange_rate, 0) AS SALES_EXCHANGE_RATE, COALESCE(SL.manager_user_id, '') AS MANAGER, COALESCE(SL.incoterms, '') AS INCOTERMS, + COALESCE(SL.split_serial_no, '') AS SPLIT_SERIAL_NO, COALESCE(SL.original_quantity, 0) AS ORDER_QUANTITY, COALESCE(SL.remaining_quantity, 0) AS REMAINING_QUANTITY FROM shipment_log SL @@ -2066,6 +2076,9 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC , incoterms = #{incoterms} + + , split_serial_no = #{splitSerialNo} + WHERE log_id = #{logId}::integer @@ -2402,6 +2415,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID AND CI.PART_OBJID = T.PART_OBJID AND CI.STATUS = 'ACTIVE' ), '') AS SERIAL_NO, + COALESCE(SL.split_serial_no, '') AS SPLIT_SERIAL_NO, COALESCE(NULLIF(REPLACE(T.QUANTITY, ',', ''), '')::numeric, 0) AS ORDER_QUANTITY, (SELECT CM.PO_NO FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PO_NO, COALESCE(T.CONTRACT_DATE, (SELECT CM.order_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)) AS ORDER_DATE, @@ -2738,6 +2752,22 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC WHERE PROJECT_NO = #{projectNo} + + + diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java index c3ac5b9..72288d3 100644 --- a/src/com/pms/salesmgmt/service/ContractMgmtService.java +++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java @@ -603,75 +603,17 @@ public class ContractMgmtService { PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN); paramMap.put("writer", person.getUserId()); int cnt = sqlSession.update("contractMgmt.saveContractMgmtInfo", paramMap); - - //영업 수주 완료시 자동 프로젝트 등록 로직 - String result_cd= CommonUtils.checkNull(paramMap.get("contract_result")); - String contract_objid= CommonUtils.checkNull(paramMap.get("objId")); - String category_cd= CommonUtils.checkNull(paramMap.get("category_cd")); - String target_project_no= CommonUtils.checkNull(paramMap.get("target_project_no_direct")); - int overhaul_order = Integer.parseInt(CommonUtils.checkNull(paramMap.get("overhaul_order"),"1")); - int project_cnt= Integer.parseInt(CommonUtils.checkNull(paramMap.get("facility_qty"), "1")); - double contract_price_currency= Double.parseDouble(CommonUtils.checkNull(paramMap.get("contract_price_currency"), "0").replace(",", "")); - double contract_price= Double.parseDouble(CommonUtils.checkNull(paramMap.get("contract_price"), "0").replace(",", "")); - - //수주가와 금액은 대수로 나누어서 등록 - paramMap.put("contract_price_currency", String.valueOf(contract_price_currency/project_cnt)); - paramMap.put("contract_price", String.valueOf(contract_price/project_cnt)); - - if("0000964".equals(result_cd)){ - resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); - System.out.println("resultList:::"+resultList); - //resultList = sqlSession.selectOne("contractMgmt.getProjectCnt", paramMap); - if(null==resultList){ - for (int i=0; i projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", paramMap); - - paramMap.put("contract_objid", paramMap.get("objId")); - paramMap.put("customer_product", paramMap.get("mechanical_type")); - List> taskUnitList = (ArrayList)sqlSession.selectList("project.getWbsTaskListByProject", paramMap); - if(CommonUtils.isNotEmpty(taskUnitList) && !taskUnitList.isEmpty()){ - String projectNo = (String)projectInfo.get("project_no"); - String filepath = Constants.FILE_STORAGE+"\\PART_DATA\\"; - for (Map map : taskUnitList) { - File file = new File(filepath+File.separator+projectNo+File.separator+CommonUtils.checkNull((String)map.get("unit_no"))+"-"+CommonUtils.checkNull((String)map.get("task_name"))); - if(!file.exists()){ - file.mkdirs(); - } - } - } - } - }else{ - sqlSession.update("project.ModifyProjectByContract", paramMap); - } + // 프로젝트 생성은 수주확정(updateOrderStatus)에서 처리 + // 프로젝트가 이미 존재하는 경우에는 수량/납기 등 업데이트 반영 + resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); + if(resultList != null) { + sqlSession.update("project.ModifyProjectByContract", paramMap); } + if(cnt > 0){ - //계약완료 일시 메일 - if("0000964".equals(CommonUtils.checkNull(paramMap.get("contract_result")))){ - commonService.SendMail(paramMap,"CONTRACT_COMP",CommonUtils.checkNull(paramMap.get("pm_user_id"))); - //그냥 등록일때 메일 - }else{ - if("regist".equals(CommonUtils.checkNull(paramMap.get("actionType")))){ + if("regist".equals(CommonUtils.checkNull(paramMap.get("actionType")))){ commonService.SendMail(paramMap,"CONTRACT_REG",CommonUtils.checkNull(paramMap.get(""))); - } } } resultMap.put("result", true); @@ -2699,21 +2641,19 @@ private String encodeImageToBase64(String imagePath) { JSONParser parser = new JSONParser(); JSONArray jsonArray = (JSONArray) parser.parse(itemsJson); - // 기존 품목 삭제 (전체 삭제 후 재등록 방식) - Map deleteParam = new HashMap(); - deleteParam.put("contractObjId", contract_objid); - deleteParam.put("userId", person.getUserId()); - sqlSession.update("contractMgmt.deleteContractItems", deleteParam); - + // UPSERT 방식으로 품목 저장 (OBJID 유지 - CONTRACT_ITEM_OBJID 매핑 보존) + Set currentItemObjIds = new HashSet(); + for(int i = 0; i < jsonArray.size(); i++) { JSONObject item = (JSONObject) jsonArray.get(i); Map itemMap = new HashMap(); - // 품목 기본 정보 + // 품목 기본 정보 - 기존 OBJID가 있으면 유지 String itemObjId = item.get("objId") != null ? item.get("objId").toString() : ""; if("".equals(itemObjId) || itemObjId.startsWith("temp_")) { itemObjId = CommonUtils.createObjId(); } + currentItemObjIds.add(itemObjId); itemMap.put("objId", itemObjId); itemMap.put("contractObjId", contract_objid); @@ -2741,8 +2681,13 @@ private String encodeImageToBase64(String imagePath) { itemMap.put("orderTotalAmount", orderTotalAmount); itemMap.put("writer", person.getUserId()); - // 품목 저장 (통합 등록용 - 수주 정보 포함) - sqlSession.insert("contractMgmt.insertContractItemWithOrder", itemMap); + // 품목 UPSERT (기존 OBJID면 UPDATE, 새 OBJID면 INSERT) + sqlSession.insert("contractMgmt.upsertContractItemWithOrder", itemMap); + + // 기존 S/N 삭제 후 재등록 + Map deleteSnParam = new HashMap(); + deleteSnParam.put("itemObjId", itemObjId); + sqlSession.delete("contractMgmt.deleteItemSerials", deleteSnParam); // S/N 저장 (별도 테이블) JSONArray snArray = (JSONArray) item.get("snList"); @@ -2758,7 +2703,6 @@ private String encodeImageToBase64(String imagePath) { sqlSession.insert("contractMgmt.insertContractItemSerial", snMap); } } - // 합계 계산 (소수점 지원) try { totalSupplyPrice += Double.parseDouble(orderSupplyPrice); @@ -2768,12 +2712,21 @@ private String encodeImageToBase64(String imagePath) { System.out.println("합계 계산 실패 - supply:" + orderSupplyPrice + ", vat:" + orderVat + ", total:" + orderTotalAmount); } } + + // 프론트에서 전달되지 않은 품목들은 INACTIVE 처리 (삭제된 품목) + if(!currentItemObjIds.isEmpty()) { + Map inactiveParam = new HashMap(); + inactiveParam.put("contractObjId", contract_objid); + inactiveParam.put("currentItemObjIds", new ArrayList(currentItemObjIds)); + inactiveParam.put("userId", person.getUserId()); + sqlSession.update("contractMgmt.inactivateRemovedItems", inactiveParam); + } } catch (Exception e) { e.printStackTrace(); throw new Exception("품목 정보 저장 중 오류가 발생했습니다."); } } - + // 3. 수주 합계 업데이트 paramMap.put("order_supply_price", String.valueOf(totalSupplyPrice)); paramMap.put("order_vat", String.valueOf(totalVat)); @@ -2781,39 +2734,23 @@ private String encodeImageToBase64(String imagePath) { // 합계만 별도 업데이트 (기본 정보는 이미 saveEstimateAndOrderInfo에서 저장됨) sqlSession.update("contractMgmt.updateOrderTotalAmounts", paramMap); - - // 4. 프로젝트 생성 로직 (수주 또는 수주(FCST)인 경우) - String result_cd = CommonUtils.checkNull(paramMap.get("contract_result")); - String category_cd = CommonUtils.checkNull(paramMap.get("category_cd")); - String target_project_no = CommonUtils.checkNull(paramMap.get("target_project_no_direct")); - - // 수주(0000964) 또는 수주(FCST)(0000968)인 경우 프로젝트 생성 - if("0000964".equals(result_cd) || "0000968".equals(result_cd)){ - // CONTRACT_OBJID로 프로젝트 존재 여부 확인 - Map resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); - boolean hasProject = (resultList != null); - - // 제품구분 확인 (DB에서 조회) + + // 프로젝트 생성은 수주확정(updateOrderStatus)에서 처리 + // 프로젝트가 이미 존재하는 경우에는 수량/납기 등 업데이트 반영 + String contract_objid_for_project = CommonUtils.checkNull(paramMap.get("objId")); + Map projectCheckResult = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); + if(projectCheckResult != null) { Map contractInfo = (Map) sqlSession.selectOne("contractMgmt.getContractBasicInfo", paramMap); contractInfo = CommonUtils.toUpperCaseMapKey(contractInfo); - String product_cd = contractInfo != null ? CommonUtils.checkNull(contractInfo.get("PRODUCT")) : ""; boolean isMachine = "0000928".equals(product_cd); - - if(isMachine) { - System.out.println("제품구분: Machine(0000928) - 품목별 수량만큼 프로젝트 생성"); - } - - // 품목별로 프로젝트 생성 또는 업데이트 (같은 트랜잭션의 sqlSession 사용) - paramMap.put("contractObjId", contract_objid); - List contractItemsRaw = sqlSession.selectList("contractMgmt.getContractItems", paramMap); - List> contractItems = CommonUtils.toUpperCaseMapKey(contractItemsRaw); - + + paramMap.put("contractObjId", contract_objid_for_project); + List contractItemsRaw = sqlSession.selectList("contractMgmt.getContractItems", paramMap); + List> contractItems = CommonUtils.toUpperCaseMapKey(contractItemsRaw); + if(contractItems != null && !contractItems.isEmpty()) { - System.out.println("품목 개수: " + contractItems.size() + "개 - 프로젝트 " + (hasProject ? "업데이트" : "생성") + " 시작" + (isMachine ? " (Machine - 수량별 생성)" : "")); - for(Map item : contractItems) { - // 수량 가져오기 (소수점 형태 "2.00"도 처리) Object quantityObj = item.get("ORDER_QUANTITY") != null ? item.get("ORDER_QUANTITY") : item.get("QUANTITY"); int itemQuantity = 1; try { @@ -2821,108 +2758,22 @@ private String encodeImageToBase64(String imagePath) { } catch (Exception e) { itemQuantity = 1; } - - // Machine인 경우 수량만큼 반복, 아니면 1번만 실행 - int loopCount = (isMachine && !hasProject) ? itemQuantity : 1; - - for(int q = 0; q < loopCount; q++) { - if(!hasProject) { - // 프로젝트가 없으면 모든 품목에 대해 생성 - Map projectParam = new HashMap(); - projectParam.putAll(paramMap); // 기본 정보 복사 - - // 품목별 정보 설정 - projectParam.put("OBJID", CommonUtils.createObjId()); - projectParam.put("is_temp", '1'); - projectParam.put("part_objid", item.get("PART_OBJID")); - projectParam.put("part_no", item.get("PART_NO")); - projectParam.put("part_name", item.get("PART_NAME")); - // Machine인 경우 각 프로젝트의 수량은 1, 아니면 원래 수량 - projectParam.put("quantity", isMachine ? "1" : String.valueOf(itemQuantity)); - projectParam.put("due_date", item.get("DUE_DATE")); - - if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ - projectParam.put("overhaul_project_no", target_project_no); - } - - if(isMachine) { - System.out.println("프로젝트 생성 [" + (q+1) + "/" + loopCount + "] - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: 1"); - } else { - System.out.println("프로젝트 생성 - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: " + itemQuantity); - } - - // 프로젝트 등록 - cnt = sqlSession.update("project.createProject", projectParam); - // WBS 자동생성 주석처리 - // // 프로젝트 TASK 등록 - // cnt = sqlSession.insert("contractMgmt.insertProjectTask", projectParam); - // // 프로젝트 SETUP_TASK 등록 - // cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", projectParam); - - // 동일 품번 M-BOM 자동 복사 제거 - 수주 시점에는 총생산수량 미확정이므로 - // M-BOM관리에서 생산계획 입력 후 수동 복사하도록 변경 - // if(!isMachine) { - // copyMbomIfSamePartNoExists(sqlSession, - // (String)projectParam.get("OBJID"), - // CommonUtils.checkNull(item.get("PART_NO")), - // CommonUtils.checkNull(item.get("PART_NAME")), - // person.getUserId(), - // itemQuantity); - // } - - // WBS 폴더 생성 주석처리 - // // project_no - unit 폴더 생성 - // Map projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam); - // - // projectParam.put("contract_objid", contract_objid); - // projectParam.put("customer_product", projectParam.get("mechanical_type")); - // List> taskUnitList = (ArrayList)sqlSession.selectList("project.getWbsTaskListByProject", projectParam); - // if(CommonUtils.isNotEmpty(taskUnitList) && !taskUnitList.isEmpty()){ - // String projectNo = (String)projectInfo.get("project_no"); - // String filepath = Constants.FILE_STORAGE+"\\PART_DATA\\"; - // for (Map map : taskUnitList) { - // File file = new File(filepath+File.separator+projectNo+File.separator+CommonUtils.checkNull((String)map.get("unit_no"))+"-"+CommonUtils.checkNull((String)map.get("task_name"))); - // if(!file.exists()){ - // file.mkdirs(); - // } - // } - // } - } else { - // 프로젝트가 있으면 모든 품목 업데이트 (수량, 금액 등만) - Map updateParam = new HashMap(); - updateParam.putAll(paramMap); - updateParam.put("part_objid", item.get("PART_OBJID")); - // Machine인 경우 수량은 변경하지 않음 (프로젝트별 1로 유지) - if(isMachine) { - updateParam.remove("quantity"); - } else { - updateParam.put("quantity", String.valueOf(itemQuantity)); - } - updateParam.put("due_date", item.get("DUE_DATE")); - - System.out.println("프로젝트 업데이트 - PART_OBJID: " + item.get("PART_OBJID") + (isMachine ? ", 수량: 변경안함(Machine)" : ", 수량: " + itemQuantity)); - sqlSession.update("project.ModifyProjectByContract", updateParam); - } + Map updateParam = new HashMap(); + updateParam.putAll(paramMap); + updateParam.put("part_objid", item.get("PART_OBJID")); + if(isMachine) { + updateParam.remove("quantity"); + } else { + updateParam.put("quantity", String.valueOf(itemQuantity)); } + updateParam.put("due_date", item.get("DUE_DATE")); + sqlSession.update("project.ModifyProjectByContract", updateParam); } } else { - System.out.println("품목이 없습니다 - 기존 방식으로 프로젝트 생성"); - // 품목이 없는 경우 기존 방식대로 처리 - if(!hasProject){ - paramMap.put("OBJID", CommonUtils.createObjId()); - paramMap.put("is_temp", '1'); - if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ - paramMap.put("overhaul_project_no", target_project_no); - } - cnt = sqlSession.update("project.createProject", paramMap); - cnt = sqlSession.insert("contractMgmt.insertProjectTask", paramMap); - cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap); - }else{ - sqlSession.update("project.ModifyProjectByContract", paramMap); - } + sqlSession.update("project.ModifyProjectByContract", paramMap); } } - + sqlSession.commit(); resultMap.put("result", "true"); resultMap.put("msg", "저장되었습니다."); @@ -3006,48 +2857,23 @@ private String encodeImageToBase64(String imagePath) { // 기본 수주 정보 및 합계 업데이트 int cnt = sqlSession.update("contractMgmt.updateOrderInfo", paramMap); - - //영업 수주 완료시 자동 프로젝트 등록 로직 - String result_cd= CommonUtils.checkNull(paramMap.get("contract_result")); - - String category_cd= CommonUtils.checkNull(paramMap.get("category_cd")); - String target_project_no= CommonUtils.checkNull(paramMap.get("target_project_no_direct")); - //int overhaul_order = Integer.parseInt(CommonUtils.checkNull(paramMap.get("overhaul_order"),"1")); - //int project_cnt= Integer.parseInt(CommonUtils.checkNull(paramMap.get("facility_qty"), "1")); - //long contract_price_currency= Long.parseLong(CommonUtils.checkNull(paramMap.get("contract_price_currency"), "0")); - //long contract_price= Long.parseLong(CommonUtils.checkNull(paramMap.get("contract_price"), "0")); - - //수주가와 금액은 대수로 나누어서 등록 - //paramMap.put("contract_price_currency", contract_price_currency/project_cnt + ""); - //paramMap.put("contract_price", contract_price/project_cnt + ""); - - if("0000964".equals(result_cd) || "0000968".equals(result_cd)){ - // CONTRACT_OBJID로 프로젝트 존재 여부 확인 (한 번만 체크) - resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); - boolean hasProject = (resultList != null); - - // 제품구분 확인 (DB에서 조회) + + // 프로젝트 생성은 수주확정(updateOrderStatus)에서 처리 + // 프로젝트가 이미 존재하는 경우에는 수량/납기 등 업데이트 반영 + resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); + if(resultList != null) { + // 제품구분 확인 Map contractInfo = (Map) sqlSession.selectOne("contractMgmt.getContractBasicInfo", paramMap); - // MyBatis resultType="map"은 소문자로 반환되므로 대문자로 변환 contractInfo = CommonUtils.toUpperCaseMapKey(contractInfo); - String product_cd = contractInfo != null ? CommonUtils.checkNull(contractInfo.get("PRODUCT")) : ""; boolean isMachine = "0000928".equals(product_cd); - - if(isMachine) { - System.out.println("제품구분: Machine(0000928) - 품목별 수량만큼 프로젝트 생성"); - } - - // 품목별로 프로젝트 생성 또는 업데이트 (같은 트랜잭션의 sqlSession 사용) + paramMap.put("contractObjId", contract_objid); List contractItemsRaw2 = sqlSession.selectList("contractMgmt.getContractItems", paramMap); List> contractItems = CommonUtils.toUpperCaseMapKey(contractItemsRaw2); - + if(contractItems != null && !contractItems.isEmpty()) { - System.out.println("품목 개수: " + contractItems.size() + "개 - 프로젝트 " + (hasProject ? "업데이트" : "생성") + " 시작" + (isMachine ? " (Machine - 수량별 생성)" : "")); - for(Map item : contractItems) { - // 수량 가져오기 (소수점 형태 "2.00"도 처리) Object quantityObj = item.get("ORDER_QUANTITY") != null ? item.get("ORDER_QUANTITY") : item.get("QUANTITY"); int itemQuantity = 1; try { @@ -3055,118 +2881,22 @@ private String encodeImageToBase64(String imagePath) { } catch (Exception e) { itemQuantity = 1; } - - // Machine인 경우 수량만큼 반복, 아니면 1번만 실행 - int loopCount = (isMachine && !hasProject) ? itemQuantity : 1; - - for(int q = 0; q < loopCount; q++) { - if(!hasProject) { - // 프로젝트가 없으면 모든 품목에 대해 생성 - Map projectParam = new HashMap(); - projectParam.putAll(paramMap); // 기본 정보 복사 - - // 품목별 정보 설정 - projectParam.put("OBJID", CommonUtils.createObjId()); - projectParam.put("is_temp", '1'); - projectParam.put("part_objid", item.get("PART_OBJID")); - projectParam.put("part_no", item.get("PART_NO")); - projectParam.put("part_name", item.get("PART_NAME")); - // Machine인 경우 각 프로젝트의 수량은 1, 아니면 원래 수량 - projectParam.put("quantity", isMachine ? "1" : String.valueOf(itemQuantity)); - projectParam.put("due_date", item.get("DUE_DATE")); - - if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ - projectParam.put("overhaul_project_no", target_project_no); - } - - if(isMachine) { - System.out.println("프로젝트 생성 [" + (q+1) + "/" + loopCount + "] - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: 1"); - } else { - System.out.println("프로젝트 생성 - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: " + itemQuantity); - } - - // 프로젝트 등록 - cnt = sqlSession.update("project.createProject", projectParam); - // WBS 자동생성 주석처리 - // // 프로젝트 TASK 등록 - // cnt = sqlSession.insert("contractMgmt.insertProjectTask", projectParam); - // // 프로젝트 SETUP_TASK 등록 - // cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", projectParam); - - // 동일 품번 M-BOM 자동 복사 제거 - 수주 시점에는 총생산수량 미확정이므로 - // M-BOM관리에서 생산계획 입력 후 수동 복사하도록 변경 - // if(!isMachine) { - // copyMbomIfSamePartNoExists(sqlSession, - // (String)projectParam.get("OBJID"), - // CommonUtils.checkNull(item.get("PART_NO")), - // CommonUtils.checkNull(item.get("PART_NAME")), - // person.getUserId(), - // itemQuantity); - // } - - // WBS 폴더 생성 주석처리 - // // project_no - unit 폴더 생성 - // Map projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam); - // - // projectParam.put("contract_objid", contract_objid); - // projectParam.put("customer_product", projectParam.get("mechanical_type")); - // List> taskUnitList = (ArrayList)sqlSession.selectList("project.getWbsTaskListByProject", projectParam); - // if(CommonUtils.isNotEmpty(taskUnitList) && !taskUnitList.isEmpty()){ - // String projectNo = (String)projectInfo.get("project_no"); - // String filepath = Constants.FILE_STORAGE+"\\PART_DATA\\"; - // for (Map map : taskUnitList) { - // File file = new File(filepath+File.separator+projectNo+File.separator+CommonUtils.checkNull((String)map.get("unit_no"))+"-"+CommonUtils.checkNull((String)map.get("task_name"))); - // if(!file.exists()){ - // file.mkdirs(); - // } - // } - // } - } else { - // 프로젝트가 있으면 모든 품목 업데이트 (수량, 금액 등만) - Map updateParam = new HashMap(); - updateParam.putAll(paramMap); - updateParam.put("part_objid", item.get("PART_OBJID")); - // Machine인 경우 수량은 변경하지 않음 (프로젝트별 1로 유지) - if(isMachine) { - updateParam.remove("quantity"); - } else { - updateParam.put("quantity", String.valueOf(itemQuantity)); - } - updateParam.put("due_date", item.get("DUE_DATE")); - - System.out.println("프로젝트 업데이트 - PART_OBJID: " + item.get("PART_OBJID") + (isMachine ? ", 수량: 변경안함(Machine)" : ", 수량: " + itemQuantity)); - sqlSession.update("project.ModifyProjectByContract", updateParam); - } + Map updateParam = new HashMap(); + updateParam.putAll(paramMap); + updateParam.put("part_objid", item.get("PART_OBJID")); + if(isMachine) { + updateParam.remove("quantity"); + } else { + updateParam.put("quantity", String.valueOf(itemQuantity)); } + updateParam.put("due_date", item.get("DUE_DATE")); + sqlSession.update("project.ModifyProjectByContract", updateParam); } - } else { - System.out.println("품목이 없습니다 - 기존 방식으로 프로젝트 생성"); - // 품목이 없는 경우 기존 방식대로 처리 - if(!hasProject){ - paramMap.put("OBJID", CommonUtils.createObjId()); - paramMap.put("is_temp", '1'); - if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ - paramMap.put("overhaul_project_no", target_project_no); - } - cnt = sqlSession.update("project.createProject", paramMap); - cnt = sqlSession.insert("contractMgmt.insertProjectTask", paramMap); - cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap); - }else{ + } else { sqlSession.update("project.ModifyProjectByContract", paramMap); } } - } -// if(cnt > 0){ - //계약완료 일시 메일 -// if("0000964".equals(CommonUtils.checkNull(paramMap.get("contract_result")))){ -// commonService.SendMail(paramMap,"CONTRACT_COMP",CommonUtils.checkNull(paramMap.get("pm_user_id"))); - //그냥 등록일때 메일 -// }else{ -// if("regist".equals(CommonUtils.checkNull(paramMap.get("actionType")))){ -// commonService.SendMail(paramMap,"CONTRACT_REG",CommonUtils.checkNull(paramMap.get(""))); -// } -// } -// } + resultMap.put("result", true); resultMap.put("msg", Message.SAVE_SUCCESS); sqlSession.commit(); @@ -3191,21 +2921,122 @@ private String encodeImageToBase64(String imagePath) { SqlSession sqlSession = null; try { sqlSession = SqlMapConfig.getInstance().getSqlSession(); - + // 수주상태 업데이트 sqlSession.update("contractMgmt.updateOrderStatusOnly", paramMap); - - // 수주(0000964) 또는 수주(FCST)(0000968)인 경우 프로젝트 생성 로직 실행 + String contract_result = CommonUtils.checkNull(paramMap.get("contract_result")); - if("0000964".equals(contract_result)) { - // 프로젝트가 없으면 생성 + String objId = CommonUtils.checkNull(paramMap.get("objId")); + + // 수주(0000964) 또는 수주(FCST)(0000968)인 경우 프로젝트 생성 로직 실행 + if("0000964".equals(contract_result) || "0000968".equals(contract_result)){ + + // CONTRACT_OBJID로 프로젝트 존재 여부 확인 Map resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap); - if(resultList == null) { - // 프로젝트 생성 로직은 기존 saveOrderInfo와 동일하게 처리 - System.out.println("수주 확정으로 변경 - 프로젝트 생성 필요시 saveOrderInfo 호출 권장"); + boolean hasProject = (resultList != null); + + // 계약 기본 정보 조회 (제품구분, 주문유형 등) + Map contractInfo = (Map) sqlSession.selectOne("contractMgmt.getContractBasicInfo", paramMap); + contractInfo = CommonUtils.toUpperCaseMapKey(contractInfo); + + String product_cd = contractInfo != null ? CommonUtils.checkNull(contractInfo.get("PRODUCT")) : ""; + String category_cd = contractInfo != null ? CommonUtils.checkNull(contractInfo.get("CATEGORY_CD")) : ""; + boolean isMachine = "0000928".equals(product_cd); + + if(isMachine) { + System.out.println("[수주확정] 제품구분: Machine(0000928) - 품목별 수량만큼 프로젝트 생성"); + } + + // 품목별로 프로젝트 생성 또는 업데이트 + paramMap.put("contractObjId", objId); + List contractItemsRaw = sqlSession.selectList("contractMgmt.getContractItems", paramMap); + List> contractItems = CommonUtils.toUpperCaseMapKey(contractItemsRaw); + + if(contractItems != null && !contractItems.isEmpty()) { + System.out.println("[수주확정] 품목 개수: " + contractItems.size() + "개 - 프로젝트 " + (hasProject ? "업데이트" : "생성") + " 시작" + (isMachine ? " (Machine - 수량별 생성)" : "")); + + for(Map item : contractItems) { + // 수량 가져오기 (소수점 형태 "2.00"도 처리) + Object quantityObj = item.get("ORDER_QUANTITY") != null ? item.get("ORDER_QUANTITY") : item.get("QUANTITY"); + int itemQuantity = 1; + try { + itemQuantity = (int) Double.parseDouble(String.valueOf(quantityObj).replaceAll("[^0-9.]", "")); + } catch (Exception e) { + itemQuantity = 1; + } + + // Machine인 경우 수량만큼 반복, 아니면 1번만 실행 + int loopCount = (isMachine && !hasProject) ? itemQuantity : 1; + + for(int q = 0; q < loopCount; q++) { + if(!hasProject) { + // 프로젝트가 없으면 모든 품목에 대해 생성 + Map projectParam = new HashMap(); + projectParam.putAll(paramMap); + + // 품목별 정보 설정 + projectParam.put("OBJID", CommonUtils.createObjId()); + projectParam.put("is_temp", '1'); + projectParam.put("contract_item_objid", String.valueOf(item.get("OBJID"))); + projectParam.put("part_objid", item.get("PART_OBJID")); + projectParam.put("part_no", item.get("PART_NO")); + projectParam.put("part_name", item.get("PART_NAME")); + // Machine인 경우 각 프로젝트의 수량은 1, 아니면 원래 수량 + projectParam.put("quantity", isMachine ? "1" : String.valueOf(itemQuantity)); + projectParam.put("due_date", item.get("DUE_DATE")); + + // 오버홀/개조인 경우 + String target_project_no = CommonUtils.checkNull(paramMap.get("target_project_no_direct")); + if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ + projectParam.put("overhaul_project_no", target_project_no); + } + + if(isMachine) { + System.out.println("[수주확정] 프로젝트 생성 [" + (q+1) + "/" + loopCount + "] - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: 1"); + } else { + System.out.println("[수주확정] 프로젝트 생성 - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME") + ", 수량: " + itemQuantity); + } + + // 프로젝트 등록 + sqlSession.update("project.createProject", projectParam); + + } else { + // 프로젝트가 있으면 모든 품목 업데이트 (수량, 금액 등만) + Map updateParam = new HashMap(); + updateParam.putAll(paramMap); + updateParam.put("part_objid", item.get("PART_OBJID")); + // Machine인 경우 수량은 변경하지 않음 (프로젝트별 1로 유지) + if(isMachine) { + updateParam.remove("quantity"); + } else { + updateParam.put("quantity", String.valueOf(itemQuantity)); + } + updateParam.put("due_date", item.get("DUE_DATE")); + + System.out.println("[수주확정] 프로젝트 업데이트 - PART_OBJID: " + item.get("PART_OBJID") + (isMachine ? ", 수량: 변경안함(Machine)" : ", 수량: " + itemQuantity)); + sqlSession.update("project.ModifyProjectByContract", updateParam); + } + } + } + } else { + System.out.println("[수주확정] 품목이 없습니다 - 기존 방식으로 프로젝트 생성"); + // 품목이 없는 경우 기존 방식대로 처리 + if(!hasProject){ + paramMap.put("OBJID", CommonUtils.createObjId()); + paramMap.put("is_temp", '1'); + String target_project_no = CommonUtils.checkNull(paramMap.get("target_project_no_direct")); + if("0000170".equals(category_cd) || "0000171".equals(category_cd)){ + paramMap.put("overhaul_project_no", target_project_no); + } + sqlSession.update("project.createProject", paramMap); + sqlSession.insert("contractMgmt.insertProjectTask", paramMap); + sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap); + } else { + sqlSession.update("project.ModifyProjectByContract", paramMap); + } } } - + sqlSession.commit(); } catch(Exception e) { if(sqlSession != null) sqlSession.rollback(); diff --git a/src/com/pms/salesmgmt/service/SalesNcollectMgmtService.java b/src/com/pms/salesmgmt/service/SalesNcollectMgmtService.java index 8506a28..dfaed98 100644 --- a/src/com/pms/salesmgmt/service/SalesNcollectMgmtService.java +++ b/src/com/pms/salesmgmt/service/SalesNcollectMgmtService.java @@ -401,6 +401,10 @@ public Map saveSaleRegistration(HttpServletRequest request, Map< if(logId != null && !"".equals(logId)) { System.out.println("=== shipment_log 수정 모드 (logId: " + logId + ") ==="); paramMap.put("shippingOrderStatus", "출하지시"); + // splitSerialNo가 있으면 전달 + if(paramMap.get("splitSerialNo") != null) { + paramMap.put("splitSerialNo", paramMap.get("splitSerialNo")); + } sqlSession.update("salesNcollectMgmt.updateShipmentLog", paramMap); } else { // 신규 등록 → shipment_log에 INSERT @@ -424,6 +428,7 @@ public Map saveSaleRegistration(HttpServletRequest request, Map< logParam.put("managerUserId", paramMap.get("managerUserId")); logParam.put("incoterms", paramMap.get("incoterms")); logParam.put("serialNo", paramMap.get("serialNo")); + logParam.put("splitSerialNo", paramMap.get("splitSerialNo")); logParam.put("parentSaleNo", null); logParam.put("cretEmpNo", paramMap.get("cretEmpNo")); @@ -518,6 +523,23 @@ public Map saveSaleRegistration(HttpServletRequest request, Map< return resultMap; } + /** + * 프로젝트의 이미 사용된 분할S/N 목록 조회 + */ + public List> getUsedSplitSerialNos(Map paramMap) { + SqlSession sqlSession = null; + List> resultList = new ArrayList>(); + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(true); + resultList = sqlSession.selectList("salesNcollectMgmt.getUsedSplitSerialNos", paramMap); + } catch(Exception e) { + e.printStackTrace(); + } finally { + if(sqlSession != null) sqlSession.close(); + } + return resultList; + } + /** *
 	 * 분할출하 처리 (로그 기반)
diff --git a/src/com/pms/service/PartMngService.java b/src/com/pms/service/PartMngService.java
index 14d4622..e8a7618 100644
--- a/src/com/pms/service/PartMngService.java
+++ b/src/com/pms/service/PartMngService.java
@@ -5274,6 +5274,11 @@ public class PartMngService extends BaseService {
 				resultMap = (HashMap)sqlSession.selectOne("partMng.getPartObjid", partobjMap);	//part_no로 is_last = 1 건 조회
 				if(null!=resultMap){
 					part_no = CommonUtils.checkNull((String)resultMap.get("part_objid"));
+					// [임시] 기존 파트를 CSV 데이터로 업데이트 (원복 예정 - 아래 주석된 원본 로직 해제하고 이 블록 삭제할 것)
+					insertMap.put("PART_OBJID", part_no);
+					sqlSession.update("partMng.updatePartInfoFromCsv", insertMap);
+					/* [원본] 기존 파트가 있으면 그대로 사용 (원복 시 위 update 3줄 삭제하고 이 주석만 제거)
+					*/
 				}else{
 					part_no = CommonUtils.createObjId();
 					//PART저장