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/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 += '번호 S/N ';
+ for(var i = 0; i < validSns.length; i++) { html += '' + (i+1) + ' ' + validSns[i] + ' '; }
+ html += '
';
+ 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 += '번호 S/N ';
+ for(var i = 0; i < validSns.length; i++) {
+ html += '' + (i+1) + ' ' + validSns[i] + ' ';
+ }
+ html += '
';
+ 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 += '번호 S/N ';
+ for(var i = 0; i < validSns.length; i++) { html += '' + (i+1) + ' ' + validSns[i] + ' '; }
+ html += '
';
+ 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 += '번호 S/N ';
+ for(var i = 0; i < validSns.length; i++) {
+ html += '' + (i+1) + ' ' + validSns[i] + ' ';
+ }
+ html += '
';
+ 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 += ' 선택한 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
+
+
+
+ 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/project.xml b/src/com/pms/mapper/project.xml
index 535257b..35b5794 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)
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/salesNcollectMgmt.xml b/src/com/pms/salesmgmt/mapper/salesNcollectMgmt.xml
index 10618c4..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,6 +855,15 @@
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(
@@ -1902,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,
@@ -1916,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}
)
@@ -1994,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}
@@ -2025,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
@@ -2070,6 +2076,9 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
, incoterms = #{incoterms}
+
+ , split_serial_no = #{splitSerialNo}
+
WHERE log_id = #{logId}::integer
@@ -2406,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,
@@ -2742,6 +2752,22 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE PROJECT_NO = #{projectNo}
+
+
+ /* 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}
+
+ AND SL.log_id != #{excludeLogId}::integer
+
+ AND SL.split_serial_no IS NOT NULL
+ AND SL.split_serial_no != ''
+ ORDER BY SL.log_id
+
+
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;
+ }
+
/**
*
* 분할출하 처리 (로그 기반)