Merge remote-tracking branch 'origin/main' into ycplm_node

This commit is contained in:
2026-03-26 12:00:54 +09:00
20 changed files with 1001 additions and 510 deletions

View File

@@ -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/

View File

@@ -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 @@
</tr>
<tr>
<td class="input_title"><label for="">수주상태 <span style="color:red;">*</span></label></td>
<td>
<select name="contract_result" id="contract_result" required reqTitle="수주상태" type="select" class="select2">
<option value="">선택</option>
${code_map.contract_result}
</select>
</td>
<td class="input_title"><label for="">발주번호</label></td>
<td>
<input type="text" name="po_no" id="po_no" value="${info.PO_NO}" />
</td>
<td class="input_title"><label for="">발주일 <span style="color:red;">*</span></label></td>
<td colspan="3">
<td>
<input type="text" class="date_icon" name="order_date" id="order_date" required reqTitle="발주일" value="${info.ORDER_DATE}">
</td>
<td colspan="4"></td>
</tr>
<input type="hidden" name="contract_result" id="contract_result" value="${info.CONTRACT_RESULT}" />
</table>
</td>
</tr>

View File

@@ -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 += '<option value="' + val + '">' + text + '</option>';
}
});
Swal.fire({
title: '수주확정',
html: '<div style="text-align:left; padding:10px;">' +
'<div style="margin-bottom:10px;"><strong>현재 수주상태:</strong> ' + (currentStatus || '-') + '</div>' +
'<div style="margin-bottom:5px;"><label for="swal_contract_result"><strong>변경할 수주상태 <span style="color:red;">*</span></strong></label></div>' +
'<select id="swal_contract_result" class="swal2-select" style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px;">' +
'<option value="">선택</option>' +
optionsHtml +
'</select>' +
'</div>',
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){
</h2>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch" name="btnSearch">
<input type="button" value="수주등록" class="plm_btns btnRegist">
<input type="button" value="수주입력" class="plm_btns btnRegist">
<input type="button" value="수주확정" class="plm_btns" id="btnOrderConfirm" style="background-color:#2ecc71; color:#fff;">
<input type="button" value="수주취소" class="plm_btns" id="btnOrderCancel" style="background-color:#e74c3c; color:#fff;">
<input type="button" value="결재상신" class="plm_btns" id="btnApproval">
</div>

View File

@@ -265,7 +265,7 @@
html += '<td><input type="text" class="item-quantity" value="' + (item.ORDER_QUANTITY || item.QUANTITY || '') + '" numberOnly required /></td>';
}
// ORDER_UNIT_PRICE 수정 가능
html += '<td><input type="text" class="item-unit-price" value="' + (item.ORDER_UNIT_PRICE || '') + '" numberOnly required /></td>';
html += '<td><input type="text" class="item-unit-price" value="' + (item.ORDER_UNIT_PRICE || '') + '" numberOnly /></td>';
// ORDER_SUPPLY_PRICE 자동 계산
html += '<td><input type="text" class="item-supply-price" value="' + (item.ORDER_SUPPLY_PRICE || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
// ORDER_VAT 수정 가능
@@ -444,27 +444,21 @@
<col width="35%" />
</colgroup>
<!-- 첫번째 행: 수주상태, 발주번호 -->
<!-- 첫번째 행: 발주번호 -->
<tr>
<td class="input_title"><label for="">수주상태 <span style="color:red;">*</span></label></td>
<td class="input_title"><label for="">발주번호</label></td>
<td>
<select name="contract_result" id="contract_result" required reqTitle="수주상태" type="select" class="select2">
<option value="">선택</option>
${code_map.contract_result}
</select>
<input type="text" name="po_no" id="po_no" value="${info.PO_NO}" />
</td>
<td class="input_title"><label for="">발주번호</label></td>
<td>
<input type="text" name="po_no" id="po_no" value="${info.PO_NO}" />
</td>
</tr>
<!-- 두번째 행: 발주일, 견적환종 -->
<tr>
<input type="hidden" name="contract_result" id="contract_result" value="${info.CONTRACT_RESULT}" />
<td class="input_title"><label for="">발주일 <span style="color:red;">*</span></label></td>
<td>
<input type="text" class="date_icon" name="order_date" id="order_date" required reqTitle="발주일" value="${info.ORDER_DATE}">
</td>
</tr>
<tr>
<td class="input_title"><label for="">견적환종</label></td>
<td>
<select name="contract_currency" id="contract_currency" reqTitle="환종" type="select" class="select2">
@@ -472,12 +466,8 @@
${code_map.contract_currency}
</select>
</td>
</tr>
<!-- 세번째 행: 견적환율 -->
<tr>
<td class="input_title"><label for="">견적환율</label></td>
<td colspan="3">
<td colspan="">
<input type="text" name="exchange_rate" id="exchange_rate" reqTitle="환율" value="${info.EXCHANGE_RATE}" numberOnly />
</td>
</tr>

View File

@@ -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}));
}
//수입검사등록

View File

@@ -187,8 +187,8 @@ function _fnc_datepick(){
${code_map.project_no}
</select>
</td>
</tr>
<tr>
<!-- </tr>
<tr> -->
<td><label for="product">제품구분</label></td>
<td>
<select name="product" id="product" class="select2" autocomplete="off">
@@ -199,7 +199,7 @@ function _fnc_datepick(){
<td><label for="part_no">품번</label></td>
<td>
<input type="text" name="part_no" id="part_no" value="${param.part_no}" style="width:130px" />
<input type="text" name="part_no" id="part_no" value="${param.part_no}" style="width:200px" />
</td>
<td><label for="part_name">품명</label></td>

View File

@@ -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'}
];

View File

@@ -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 '<a href="javascript:void(0);" style="color:#2196F3;">' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건</a>';
},
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 = '<table style="width:100%; border-collapse:collapse; border:1px solid #ddd;">';
html += '<thead><tr style="background:#f5f5f5;"><th style="padding:8px; border:1px solid #ddd; text-align:center; width:50px;">번호</th><th style="padding:8px; border:1px solid #ddd; text-align:center;">S/N</th></tr></thead><tbody>';
for(var i = 0; i < validSns.length; i++) { html += '<tr><td style="text-align:center; padding:6px; border:1px solid #ddd;">' + (i+1) + '</td><td style="padding:6px; border:1px solid #ddd;">' + validSns[i] + '</td></tr>'; }
html += '</tbody></table>';
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 '<a href="javascript:void(0);" style="color:#2196F3;">' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건</a>';
},
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 = '<table style="width:100%; border-collapse:collapse; border:1px solid #ddd;">';
html += '<thead><tr style="background:#f5f5f5;"><th style="padding:8px; border:1px solid #ddd; text-align:center; width:50px;">번호</th><th style="padding:8px; border:1px solid #ddd; text-align:center;">S/N</th></tr></thead><tbody>';
for(var i = 0; i < validSns.length; i++) {
html += '<tr><td style="text-align:center; padding:6px; border:1px solid #ddd;">' + (i+1) + '</td><td style="padding:6px; border:1px solid #ddd;">' + validSns[i] + '</td></tr>';
}
html += '</tbody></table>';
Swal.fire({title: '분할S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'});
}
},
// 20. 품번
{headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PRODUCT_NO'},
// 21. 과세구분

View File

@@ -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 '<a href="javascript:void(0);" style="color:#2196F3;">' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건</a>';
},
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 = '<table style="width:100%; border-collapse:collapse; border:1px solid #ddd;">';
html += '<thead><tr style="background:#f5f5f5;"><th style="padding:8px; border:1px solid #ddd; text-align:center; width:50px;">번호</th><th style="padding:8px; border:1px solid #ddd; text-align:center;">S/N</th></tr></thead><tbody>';
for(var i = 0; i < validSns.length; i++) { html += '<tr><td style="text-align:center; padding:6px; border:1px solid #ddd;">' + (i+1) + '</td><td style="padding:6px; border:1px solid #ddd;">' + validSns[i] + '</td></tr>'; }
html += '</tbody></table>';
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 '<a href="javascript:void(0);" style="color:#2196F3;">' + snArr[0].trim() + ' 외 ' + (snArr.length - 1) + '건</a>';
},
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 = '<table style="width:100%; border-collapse:collapse; border:1px solid #ddd;">';
html += '<thead><tr style="background:#f5f5f5;"><th style="padding:8px; border:1px solid #ddd; text-align:center; width:50px;">번호</th><th style="padding:8px; border:1px solid #ddd; text-align:center;">S/N</th></tr></thead><tbody>';
for(var i = 0; i < validSns.length; i++) {
html += '<tr><td style="text-align:center; padding:6px; border:1px solid #ddd;">' + (i+1) + '</td><td style="padding:6px; border:1px solid #ddd;">' + validSns[i] + '</td></tr>';
}
html += '</tbody></table>';
Swal.fire({title: '분할S/N 목록 (' + validSns.length + '건)', html: html, width: '500px', confirmButtonText: '닫기'});
}
},
// 25. 품번
{headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PRODUCT_NO'},

View File

@@ -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 = '<div style="padding:10px; color:#333;">';
popupHtml += ' <h3 style="margin:0 0 15px 0; text-align:center; color:#333;">분할S/N 선택</h3>';
popupHtml += ' <div style="margin-bottom:10px; text-align:right;">';
popupHtml += ' <button type="button" onclick="fn_splitSnSelectAll()" class="plm_btns" style="padding:4px 10px; font-size:12px;">전체선택</button>';
popupHtml += ' <button type="button" onclick="fn_splitSnDeselectAll()" class="plm_btns" style="padding:4px 10px; font-size:12px;">전체해제</button>';
popupHtml += ' </div>';
popupHtml += ' <div id="splitSnListContainer" style="max-height:300px; overflow-y:auto;">';
popupHtml += ' <table style="width:100%; border-collapse:collapse; border:1px solid #ddd;">';
popupHtml += ' <colgroup><col width="15%"><col width="15%"><col width="70%"></colgroup>';
popupHtml += ' <thead><tr style="background:#f5f5f5;">';
popupHtml += ' <th style="padding:8px; border:1px solid #ddd; text-align:center;">선택</th>';
popupHtml += ' <th style="padding:8px; border:1px solid #ddd; text-align:center;">번호</th>';
popupHtml += ' <th style="padding:8px; border:1px solid #ddd; text-align:center;">S/N</th>';
popupHtml += ' </tr></thead><tbody>';
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 += '<tr>';
popupHtml += '<td style="text-align:center; padding:6px; border:1px solid #ddd;">';
popupHtml += '<input type="checkbox" class="split-sn-chk" value="' + availableSns[i] + '" ' + (isChecked ? 'checked' : '') + ' />';
popupHtml += '</td>';
popupHtml += '<td style="text-align:center; padding:6px; border:1px solid #ddd;">' + (i+1) + '</td>';
popupHtml += '<td style="padding:6px; border:1px solid #ddd;">' + availableSns[i] + '</td>';
popupHtml += '</tr>';
}
popupHtml += ' </tbody></table>';
popupHtml += ' </div>';
popupHtml += ' <div style="margin-top:10px; text-align:center; color:#666; font-size:12px;">';
popupHtml += ' 선택한 S/N 개수가 판매수량에 자동 반영됩니다.';
popupHtml += ' </div>';
popupHtml += ' <div style="text-align:center; margin-top:15px; display:flex; gap:10px; justify-content:center;">';
popupHtml += ' <button type="button" onclick="fn_confirmSplitSn()" class="plm_btns">확인</button>';
popupHtml += ' <button type="button" onclick="Swal.close()" class="plm_btns">취소</button>';
popupHtml += ' </div>';
popupHtml += '</div>';
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 />
</td>
</tr>
<!-- 2-1행: 분할S/N -->
<tr>
<td class="input_title"><label for="splitSerialNo">분할S/N</label></td>
<td colspan="3">
<input type="hidden" name="splitSerialNo" id="splitSerialNoHidden" value="${saleInfo.SPLIT_SERIAL_NO}" />
<div id="splitSnContainer" style="width:100%; min-height:34px; border:1px solid #ddd; border-radius:4px; padding:5px; background-color:#f8f9fa; cursor:pointer;"
onclick="fn_openSplitSnPopup()">
<span id="splitSnDisplay" style="color:#999;">S/N을 먼저 등록하세요</span>
</div>
</td>
</tr>
<!-- 3행: 판매수량, 출하일 -->
<tr>
<td class="input_title">

View File

@@ -95,13 +95,14 @@ function fn_openEditPopup(logId, orderNo) {
<th>출하수량</th>
<th>출하지시상태</th>
<th>S/N</th>
<th>분할S/N</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty shippingList}">
<tr>
<td colspan="4">출하 내역이 없습니다.</td>
<td colspan="5">출하 내역이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
@@ -116,6 +117,7 @@ function fn_openEditPopup(logId, orderNo) {
<td>${item.shipping_quantity}</td>
<td>${item.shipping_order_status}</td>
<td>${item.serial_no}</td>
<td>${item.split_serial_no}</td>
</tr>
</c:forEach>
</c:otherwise>

View File

@@ -7704,8 +7704,45 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
,NULLIF(#{UNIT_QTY}, '')
)
</insert>
<!-- 계약제품, 업체명으로 BOM에 등록된 부품정보 조회 -->
<!-- CSV 업로드 시 기존 파트 정보 업데이트 (임시 - 원복 예정) -->
<update id="updatePartInfoFromCsv" parameterType="map">
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}
</update>
<!-- 계약제품, 업체명으로 BOM에 등록된 부품정보 조회 -->
<select id="partLinkedBomContractList" parameterType="map" resultType="map">
SELECT P.OBJID AS PART_OBJID
,P.PART_NO AS DO_NO

View File

@@ -3031,10 +3031,12 @@
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
AND PM.PART_OBJID = CI.PART_OBJID
-- CONTRACT_ITEM과 LEFT JOIN하여 품목별로 펼쳐서 보이기
-- LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = 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'
WHERE 1=1
AND PM.PROJECT_NO IS NOT NULL
@@ -4597,8 +4599,12 @@
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
AND PM.PART_OBJID = CI.PART_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 OUTER JOIN PRODUCTION_PLAN PP ON PP.PROJECT_OBJID = PM.OBJID
AND PP.STATUS = 'active'
@@ -4731,8 +4737,13 @@
COALESCE(CI.PART_NAME, PM.PART_NAME, '') AS PART_NAME
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
AND PM.PART_OBJID = CI.PART_OBJID AND CI.STATUS = 'ACTIVE'
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'
WHERE PM.OBJID::VARCHAR = #{projectObjid}::VARCHAR
</select>
@@ -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
<if test="search_category_cd != null and search_category_cd != ''">
@@ -5394,5 +5425,60 @@
INNER JOIN PROJECT_MGMT PM ON MHD.PROJECT_OBJID = PM.OBJID::VARCHAR
WHERE MH.OBJID::VARCHAR = #{historyObjId}
</select>
<!-- M-BOM → ERP BOM 동기화용: 모품목-자품목 관계 추출 -->
<select id="selectMbomBomRelationsForErp" parameterType="map" resultType="map">
SELECT
ITEM_PARENT_CD,
ITEM_CHILD_CD,
JUST_QT,
SUPPLY_TYPE,
ROW_NUMBER() OVER (PARTITION BY ITEM_PARENT_CD ORDER BY SEQ, CHILD_OBJID) AS BOM_SQ
FROM (
SELECT
CASE
WHEN MD.PARENT_OBJID IS NULL OR MD.PARENT_OBJID = ''
THEN MH.PART_NO
ELSE PARENT_MD.PART_NO
END AS ITEM_PARENT_CD,
MD.PART_NO AS ITEM_CHILD_CD,
COALESCE(MD.QTY, 1) AS JUST_QT,
COALESCE(MD.SUPPLY_TYPE, '') AS SUPPLY_TYPE,
MD.SEQ,
MD.CHILD_OBJID
FROM MBOM_DETAIL MD
JOIN MBOM_HEADER MH ON MH.OBJID::VARCHAR = MD.MBOM_HEADER_OBJID::VARCHAR
LEFT JOIN MBOM_DETAIL PARENT_MD
ON MD.PARENT_OBJID = PARENT_MD.CHILD_OBJID
AND MD.MBOM_HEADER_OBJID = PARENT_MD.MBOM_HEADER_OBJID
AND PARENT_MD.STATUS = 'ACTIVE'
WHERE MD.MBOM_HEADER_OBJID = #{mbomHeaderObjid}
AND MD.STATUS = 'ACTIVE'
) SUB
ORDER BY ITEM_PARENT_CD, BOM_SQ
</select>
<!-- M-BOM → ERP BOM 동기화용: DISTINCT 모품목 목록 -->
<select id="selectDistinctParentItemsForErp" parameterType="map" resultType="string">
SELECT DISTINCT ITEM_PARENT_CD
FROM (
SELECT
CASE
WHEN MD.PARENT_OBJID IS NULL OR MD.PARENT_OBJID = ''
THEN MH.PART_NO
ELSE PARENT_MD.PART_NO
END AS ITEM_PARENT_CD
FROM MBOM_DETAIL MD
JOIN MBOM_HEADER MH ON MH.OBJID::VARCHAR = MD.MBOM_HEADER_OBJID::VARCHAR
LEFT JOIN MBOM_DETAIL PARENT_MD
ON MD.PARENT_OBJID = PARENT_MD.CHILD_OBJID
AND MD.MBOM_HEADER_OBJID = PARENT_MD.MBOM_HEADER_OBJID
AND PARENT_MD.STATUS = 'ACTIVE'
WHERE MD.MBOM_HEADER_OBJID = #{mbomHeaderObjid}
AND MD.STATUS = 'ACTIVE'
) SUB
WHERE ITEM_PARENT_CD IS NOT NULL AND ITEM_PARENT_CD != ''
</select>
</mapper>

View File

@@ -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}
)
</insert>
<!-- 기존 프로젝트에 CONTRACT_ITEM_OBJID 매핑 업데이트 -->
<update id="updateProjectContractItemObjid" parameterType="map">
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
</update>
<!-- //계약정보를 받아 프로젝트 TASK 최초저장 -->
<insert id="insertProjectTask" parameterType="map">
INSERT INTO

View File

@@ -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<String, Object> usedSnParam = new HashMap<String, Object>();
usedSnParam.put("projectNo", paramMap.get("orderNo"));
if(paramMap.get("logId") != null && !paramMap.get("logId").equals("")) {
usedSnParam.put("excludeLogId", paramMap.get("logId"));
}
List<Map<String, Object>> usedSnList = salesNcollectMgmtService.getUsedSplitSerialNos(usedSnParam);
// 이미 사용된 S/N들을 하나의 콤마 구분 문자열로 합침
StringBuilder usedSnBuilder = new StringBuilder();
for(Map<String, Object> 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();

View File

@@ -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}
<if test="contract_result != null and contract_result != ''">
,CONTRACT_RESULT = #{contract_result}
</if>
,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}
<if test="contract_result != null and contract_result != ''">
,CONTRACT_RESULT = #{contract_result}
</if>
,PROJECT_NAME = #{project_name}
,AREA_CD = #{area_cd} -->
</update>
@@ -4991,7 +4995,9 @@ WHERE
<update id="updateOrderInfo" parameterType="map">
UPDATE CONTRACT_MGMT
SET
<if test="contract_result != null and contract_result != ''">
CONTRACT_RESULT = #{contract_result},
</if>
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},
<if test="contract_result != null and contract_result != ''">
CONTRACT_RESULT = #{contract_result},
</if>
PO_NO = #{po_no},
ORDER_DATE = #{order_date},
CONTRACT_CURRENCY = #{contract_currency},
@@ -5629,6 +5637,67 @@ WHERE
STATUS = 'ACTIVE'
</insert>
<!-- 품목 UPSERT (수주 정보 포함 - 통합등록용) -->
<insert id="upsertContractItemWithOrder" parameterType="map">
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>
<!-- S/N UPSERT (INSERT or UPDATE) -->
<insert id="upsertContractItemSerial" parameterType="map">
INSERT INTO CONTRACT_ITEM_SERIAL (

View File

@@ -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}
)
</insert>
@@ -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
<if test="incoterms != null and incoterms != ''">
, incoterms = #{incoterms}
</if>
<if test="splitSerialNo != null and splitSerialNo != ''">
, split_serial_no = #{splitSerialNo}
</if>
WHERE log_id = #{logId}::integer
</update>
@@ -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}
</update>
<!-- 프로젝트의 이미 사용된 분할S/N 목록 조회 -->
<select id="getUsedSplitSerialNos" parameterType="map" resultType="map">
/* salesNcollectMgmt.getUsedSplitSerialNos - 이미 사용된 분할S/N 조회 */
SELECT
SL.log_id,
COALESCE(SL.split_serial_no, '') AS split_serial_no
FROM shipment_log SL
WHERE SL.target_objid = #{projectNo}
<if test="excludeLogId != null and excludeLogId != ''">
AND SL.log_id != #{excludeLogId}::integer
</if>
AND SL.split_serial_no IS NOT NULL
AND SL.split_serial_no != ''
ORDER BY SL.log_id
</select>
</mapper>

View File

@@ -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<project_cnt; i++){
paramMap.put("OBJID", CommonUtils.createObjId());
paramMap.put("is_temp", '1');
paramMap.put("facility_qty", '1');
if("0000170".equals(category_cd) || "0000171".equals(category_cd)){
paramMap.put("overhaul_project_no", target_project_no);
paramMap.put("overhaul_order", overhaul_order+i);
}else{
}
//프로젝트 등록
cnt = sqlSession.update("project.createProject", paramMap);
//프로젝트 TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectTask", paramMap);
//프로젝트 SETUP_TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap);
//project_no - unit 폴더 생성
//paramMap.put("OBJID", paramMap.get("OBJID"));
Map<String,Object> projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", paramMap);
paramMap.put("contract_objid", paramMap.get("objId"));
paramMap.put("customer_product", paramMap.get("mechanical_type"));
List<Map<String,Object>> 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<String, Object> 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<String, Object> deleteParam = new HashMap<String, Object>();
deleteParam.put("contractObjId", contract_objid);
deleteParam.put("userId", person.getUserId());
sqlSession.update("contractMgmt.deleteContractItems", deleteParam);
// UPSERT 방식으로 품목 저장 (OBJID 유지 - CONTRACT_ITEM_OBJID 매핑 보존)
Set<String> currentItemObjIds = new HashSet<String>();
for(int i = 0; i < jsonArray.size(); i++) {
JSONObject item = (JSONObject) jsonArray.get(i);
Map<String, Object> itemMap = new HashMap<String, Object>();
// 품목 기본 정보
// 품목 기본 정보 - 기존 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<String, Object> deleteSnParam = new HashMap<String, Object>();
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<String, Object> inactiveParam = new HashMap<String, Object>();
inactiveParam.put("contractObjId", contract_objid);
inactiveParam.put("currentItemObjIds", new ArrayList<String>(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<String, Object> contractInfo = (Map<String, Object>) 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<Map<String, Object>> contractItems = CommonUtils.toUpperCaseMapKey(contractItemsRaw);
paramMap.put("contractObjId", contract_objid_for_project);
List contractItemsRaw = sqlSession.selectList("contractMgmt.getContractItems", paramMap);
List<Map<String, Object>> 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<String, Object> projectParam = new HashMap<String, Object>();
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<String,Object> projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam);
//
// projectParam.put("contract_objid", contract_objid);
// projectParam.put("customer_product", projectParam.get("mechanical_type"));
// List<Map<String,Object>> 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<String, Object> 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<String, Object> updateParam = new HashMap<String, Object>();
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<String, Object> updateParam = new HashMap<String, Object>();
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<String, Object> contractInfo = (Map<String, Object>) 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<Map<String, Object>> 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<String, Object> projectParam = new HashMap<String, Object>();
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<String,Object> projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam);
//
// projectParam.put("contract_objid", contract_objid);
// projectParam.put("customer_product", projectParam.get("mechanical_type"));
// List<Map<String,Object>> 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<String, Object> 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<String, Object> updateParam = new HashMap<String, Object>();
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<String, Object> updateParam = new HashMap<String, Object>();
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<String, Object> contractInfo = (Map<String, Object>) 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<Map<String, Object>> 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<String, Object> projectParam = new HashMap<String, Object>();
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<String, Object> updateParam = new HashMap<String, Object>();
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();

View File

@@ -401,6 +401,10 @@ public Map<String, Object> 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<String, Object> 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<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
return resultMap;
}
/**
* 프로젝트의 이미 사용된 분할S/N 목록 조회
*/
public List<Map<String, Object>> getUsedSplitSerialNos(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
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;
}
/**
* <pre>
* 분할출하 처리 (로그 기반)

View File

@@ -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저장