V20260210 #162

Merged
hjjeong merged 6 commits from V20260210 into main 2026-03-06 09:02:28 +00:00
12 changed files with 502 additions and 299 deletions

View File

@@ -307,7 +307,19 @@ function fn_loadProjectInfo(projectObjid){
$("#REQ_DEL_DATE").val(fnc_checkNull(info.req_del_date));
// S/N
$("#SERIAL_NO").val(fnc_checkNull(info.serial_no));
var serialNoVal = fnc_checkNull(info.serial_no);
$("#SERIAL_NO").val(serialNoVal);
// snList 동기화 (AJAX 로드 시에도 팝업 목록 갱신)
snList = [];
snCounter = 1;
if(serialNoVal && serialNoVal.trim() != '') {
var snArr = serialNoVal.split(',');
for(var si = 0; si < snArr.length; si++) {
if(snArr[si].trim() != '') {
snList.push({ id: snCounter++, value: snArr[si].trim() });
}
}
}
// 수주수량
$("#ORDER_QTY").val(fnc_checkNull(info.order_qty) || 0);
@@ -479,7 +491,7 @@ function fn_openSnManagePopup() {
width: '700px',
showConfirmButton: false,
showCloseButton: true,
didOpen: function() {
onOpen: function() {
setTimeout(function() {
fn_renderSnList();
// 엔터키로 추가

View File

@@ -227,7 +227,21 @@
Swal.fire("매출마감 전 데이터만 매출마감 가능합니다.");
return false;
}
// 선택된 항목의 고객사가 모두 동일한지 체크
var firstCustomer = fnc_checkNull(targetObj[0].CUSTOMER);
var hasDiffCustomer = false;
for(var i = 1; i < targetObj.length; i++){
if(fnc_checkNull(targetObj[i].CUSTOMER) != firstCustomer){
hasDiffCustomer = true;
break;
}
}
if(hasDiffCustomer){
Swal.fire("동일한 고객사의 데이터만 매출마감할 수 있습니다.");
return false;
}
// 매출마감일 입력 팝업
Swal.fire({
title: '매출마감 처리',

View File

@@ -215,7 +215,16 @@
return;
}
// 거래명세서 출력 버튼은 항상 새로 작성 (저장된 데이터 무시)
// 이미 거래명세서가 생성된 항목이 있는지 확인
var alreadyCreated = selectedData.filter(function(row) {
return row.HAS_TRANSACTION_STATEMENT === 'Y';
});
if(alreadyCreated.length > 0) {
var projectNames = alreadyCreated.map(function(row) { return row.PROJECT_NO; }).join(', ');
alert("이미 거래명세서가 생성된 항목이 있습니다.\n(" + projectNames + ")\n파란 폴더 아이콘을 클릭하여 확인해주세요.");
return;
}
localStorage.setItem('loadSavedStatement', 'false');
// 같은 거래처인지 확인
@@ -233,15 +242,19 @@
console.log(selectedData);
localStorage.setItem('transactionStatementData', JSON.stringify(selectedData));
// 프로젝트 번호들을 수집
// 프로젝트 번호/OBJID 수집
var projectNos = selectedData.map(function(row) {
return row.PROJECT_NO;
}).join(',');
var projectObjids = selectedData.map(function(row) {
return row.OBJID;
}).join(',');
// 새로 만든 거래명세서 팝업 열기
var popup_width = 1000;
var popup_height = 800;
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNos);
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNos)
+ "&projectObjids=" + encodeURIComponent(projectObjids);
fn_centerPopup(popup_width, popup_height, url);
}
@@ -463,8 +476,9 @@ var columns = [
cellClick: function(e, cell) {
var data = cell.getRow().getData();
var projectNo = data.PROJECT_NO;
var projectObjid = data.OBJID;
var hasStatement = data.HAS_TRANSACTION_STATEMENT === 'Y';
if(projectNo) fn_openTransactionStatementPopup(projectNo, hasStatement);
if(projectNo) fn_openTransactionStatementPopup(projectNo, projectObjid, hasStatement);
}
}
];
@@ -555,9 +569,9 @@ function fn_search(){
// 출하일 상세 내역 팝업
// 거래명세서 팝업 열기 (단일 프로젝트)
function fn_openTransactionStatementPopup(projectNo, hasStatement) {
function fn_openTransactionStatementPopup(projectNo, projectObjid, hasStatement) {
console.log("=== 거래명세서 팝업 열기 ===");
console.log("projectNo:", projectNo);
console.log("projectNo:", projectNo, "projectObjid:", projectObjid);
console.log("hasStatement:", hasStatement);
if(!projectNo) {
@@ -583,11 +597,11 @@ function fn_openTransactionStatementPopup(projectNo, hasStatement) {
}
localStorage.setItem('transactionStatementData', JSON.stringify(projectData));
// 파란폴더(저장된 경우)만 여기까지 도달 → DB 데이터 로드
localStorage.setItem('loadSavedStatement', 'true');
// 거래명세서 팝업 열기
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNo);
// 거래명세서 팝업 열기 (OBJID 포함)
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNo)
+ "&projectObjids=" + encodeURIComponent(projectObjid);
var popup_width = 900;
var popup_height = 800;
var left = (screen.width - popup_width) / 2;
@@ -688,8 +702,13 @@ function fn_bulkRegister(){
}
}
// 판매등록 팝업 열기 (그리드 데이터 전달)
fn_openSaleRegPopupWithData(selectedRow);
if(selectedRow.SALES_REG_NO) {
// 기존 판매등록 수정 모드
fn_openSaleRegPopup(selectedRow.PROJECT_NO, selectedRow.SALES_REG_NO);
} else {
// 신규 판매등록
fn_openSaleRegPopupWithData(selectedRow);
}
}
</script>
<style>

View File

@@ -36,10 +36,24 @@
// 날짜 선택기 초기화
_fnc_datepick();
// 출하일: 저장된 값이 없으면 오늘 날짜 설정
if(!$("#shippingDate").val() || $("#shippingDate").val().trim() === '') {
var today = new Date();
var yyyy = today.getFullYear();
var mm = String(today.getMonth() + 1).padStart(2, '0');
var dd = String(today.getDate()).padStart(2, '0');
$("#shippingDate").val(yyyy + '-' + mm + '-' + dd);
}
// 기존 담당자 값 설정
var managerValue = "${saleInfo.MANAGER}";
console.log("MANAGER 값:", managerValue);
console.log("SHIPPING_METHOD:", "${saleInfo.SHIPPING_METHOD}");
console.log("INCOTERMS:", "${saleInfo.INCOTERMS}");
console.log("SERIAL_NO:", "${saleInfo.SERIAL_NO}");
if(managerValue) {
$("#manager").val(managerValue).trigger('change');
console.log("담당자 설정 후 선택값:", $("#manager").val());
}
// 판매환종 초기값 설정 (견적환종과 동기화)
@@ -729,26 +743,17 @@ function fn_calculateSelectedItem() {
<tr>
<td class="input_title">
<label for="salesQuantity">판매수량</label>
<c:if test="${param.saleNo == null || param.saleNo == ''}">
<c:if test="${true}">
<span style="color:#666; font-size:11px; display:block; margin-top:2px;">
(잔량: ${saleInfo.REMAINING_QUANTITY != null ? saleInfo.REMAINING_QUANTITY : saleInfo.ORDER_QUANTITY}개)
</span>
</c:if>
</td>
<td>
<c:choose>
<c:when test="${param.saleNo != null && param.saleNo != ''}">
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY}"
readonly style="background-color: #f5f5f5;" />
</c:when>
<c:otherwise>
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY != null && saleInfo.SALES_QUANTITY != '' && saleInfo.SALES_QUANTITY != 0 ? saleInfo.SALES_QUANTITY : ''}"
onchange="fn_calculateSupplyPrice()"
placeholder="판매할 수량 입력" />
</c:otherwise>
</c:choose>
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY != null && saleInfo.SALES_QUANTITY != '' && saleInfo.SALES_QUANTITY != 0 ? saleInfo.SALES_QUANTITY : ''}"
onchange="fn_calculateSupplyPrice()"
placeholder="판매할 수량 입력" />
</td>
<td class="input_title"><label for="shippingDate">출하일</label></td>
<td>

View File

@@ -58,7 +58,7 @@ body {
.info-table td {
border: 1px solid #000;
padding: 6px 10px;
padding: 6px 8px;
font-size: 9pt;
vertical-align: middle;
}
@@ -386,6 +386,43 @@ body {
font-size: 10pt;
}
/* 좌우 테두리 겹침 방지 (container 2px + cell 1px = 3px 문제 해결) */
.info-table td.date-label-cell,
.info-table td.receiver-cell,
.info-table td.supply-text-cell {
border-left: none;
}
.info-table tr > td:last-child {
border-right: none;
}
.info-table tr:first-child > td {
border-top: none;
}
.items-table tr > th:first-child,
.items-table tr > td:first-child {
border-left: none;
}
.items-table tr > th:last-child,
.items-table tr > td:last-child {
border-right: none;
}
.items-table thead tr:first-child > th {
border-top: none;
}
.footer-table tr > td:first-child {
border-left: none;
}
.footer-table tr > td:last-child {
border-right: none;
}
/* 버튼 */
.button-area {
text-align: center;
@@ -416,7 +453,7 @@ body {
$(document).ready(function(){
var today = new Date();
var days = ['일', '월', '화', '수', '목', '금', '토'];
var dateStr = days[today.getDay()] + "요일, " + (today.getMonth() + 1) + "월 " + today.getDate() + ", " + today.getFullYear();
var dateStr = today.getFullYear() + " " + (today.getMonth() + 1) + "월 " + today.getDate() + " " + days[today.getDay()] + "요일";
// 표시용 날짜 (한글)
$("#deliveryDate").text(dateStr);
@@ -435,12 +472,12 @@ $(document).ready(function(){
var dateText = $(this).text().trim();
// 날짜 파싱 시도
try {
// "목요일, 11월 15, 2025" 형식 파싱
var match = dateText.match(/(\d+)월\s*(\d+),?\s*(\d{4})/);
// "2025년 3월 5일 수요일" 형식 파싱
var match = dateText.match(/(\d{4})년\s*(\d+)\s*(\d+)일/);
if (match) {
var year = match[3];
var month = String(match[1]).padStart(2, '0');
var day = String(match[2]).padStart(2, '0');
var year = match[1];
var month = String(match[2]).padStart(2, '0');
var day = String(match[3]).padStart(2, '0');
var isoDate = year + "-" + month + "-" + day;
$("#deliveryDateISO").val(isoDate);
console.log("날짜 업데이트: " + dateText + " → " + isoDate);
@@ -469,8 +506,10 @@ function fn_loadData() {
console.log("저장된 데이터 불러오기:", loadSaved);
if(loadSaved && gridData.length > 0) {
// 저장된 거래명세서 불러오기
fn_loadSavedStatement(gridData[0].PROJECT_NO, gridData);
// 저장된 거래명세서 불러오기 (OBJID 기반 조회)
var objid = fnc_checkNull("${param.projectObjids}").split(",")[0];
if(!objid) objid = gridData[0].OBJID;
fn_loadSavedStatement(objid, gridData);
return;
}
@@ -565,14 +604,14 @@ function fn_loadEmptyStatement(gridData) {
}
// 저장된 거래명세서 불러오기
function fn_loadSavedStatement(projectNo, gridData) {
function fn_loadSavedStatement(projectObjid, gridData) {
console.log("=== 저장된 거래명세서 불러오기 ===");
console.log("projectNo:", projectNo);
console.log("projectObjid:", projectObjid);
$.ajax({
url: '/salesMgmt/getSavedTransactionStatement.do',
type: 'POST',
data: { projectNo: projectNo },
data: { projectObjid: projectObjid },
success: function(response) {
if(response.success && response.data && response.data.length > 0) {
console.log("저장된 데이터:", response.data);
@@ -603,8 +642,8 @@ function fn_loadSavedStatement(projectNo, gridData) {
totalVat += vat;
var row = $("<tr>");
row.append($("<td contenteditable='true' class='text-left'>").text(item.imitemid || item.IMITEMID || ""));
row.append($("<td contenteditable='true'>").text(item.prodcd || item.PRODCD || ""));
row.append($("<td contenteditable='true' class='text-left'>").text(item.prodcd || item.PRODCD || ""));
row.append($("<td contenteditable='true'>").text(item.imitemid || item.IMITEMID || ""));
row.append($("<td contenteditable='true'>").text(quantity));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(item.isprice || item.ISPRICE || 0)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(supply)));
@@ -755,8 +794,8 @@ function fn_fillData(data) {
totalVat += vat;
var row = $("<tr>");
row.append($("<td contenteditable='true' class='text-left'>").text(item.PRODUCTNAME || item.productName || ""));
row.append($("<td contenteditable='true'>").text(item.SPEC || item.spec || ""));
row.append($("<td contenteditable='true' class='text-left'>").html("&nbsp;"));
row.append($("<td contenteditable='true'>").text(item.PRODUCTNAME || item.productName || ""));
row.append($("<td contenteditable='true'>").text(quantity));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(item.UNITPRICE || item.unitPrice || 0)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(supply)));
@@ -913,6 +952,7 @@ function fn_save() {
// 수정된 데이터 수집
var data = {
projectNos: "${param.projectNos}",
projectObjids: "${param.projectObjids}",
deliveryDate: $("#deliveryDateISO").val(),
receiverName: $("#receiverName").text(),
registrationNo: $("#registrationNo").text(),
@@ -1000,7 +1040,7 @@ function fn_close() {
<tr class="row-1">
<td class="date-label-cell">납품일</td>
<td class="date-value-cell" colspan="4">
<span id="deliveryDate" contenteditable="true">수요일, 9월 24, 2025</span>
<span id="deliveryDate" contenteditable="true">2025년 3월 5일 수요일</span>
<input type="hidden" id="deliveryDateISO" value="">
</td>
<td class="supplier-label-cell" rowspan="5">
@@ -1060,7 +1100,7 @@ function fn_close() {
</td>
<td class="supplier-field-label">전화 번호</td>
<td class="supplier-field-value" colspan="4">
<span contenteditable="true" id="supplierTel">TEL:042-602-3300 / FAX:042-672</span>
<span contenteditable="true" id="supplierTel">TEL:042-602-3300 / FAX:042-672-3399</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,80 @@
package com.pms.common.utils;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
/**
* S/N 동기화 유틸리티
* - 모든 화면에서 S/N 저장 시 CONTRACT_ITEM_SERIAL(마스터)에 동기화
* - 프로젝트 연결이 없는 경우(독립 생산계획 등)는 동기화 skip
*/
public class SerialNoSyncUtil {
/**
* S/N 텍스트를 파싱하여 CONTRACT_ITEM_SERIAL 테이블에 동기화
*
* @param sqlSession 현재 트랜잭션의 SqlSession
* @param projectNo 프로젝트번호 (PROJECT_MGMT.PROJECT_NO)
* @param serialNoText 쉼표 구분 S/N 문자열 (예: "SN001, SN002, SN003")
* @param writer 작성자 ID
*/
public static void syncSerialToContractItemSerial(
SqlSession sqlSession, String projectNo, String serialNoText, String writer) {
if (projectNo == null || projectNo.trim().isEmpty()) {
return;
}
// 1. 프로젝트번호로 CONTRACT_ITEM 조회
Map<String, Object> param = new HashMap<String, Object>();
param.put("projectNo", projectNo);
Map<String, Object> contractItem = sqlSession.selectOne(
"contractMgmt.getContractItemByProject", param);
if (contractItem == null) {
// CONTRACT_ITEM이 없으면 동기화 불가 (프로젝트-계약 연결 없음)
return;
}
String itemObjId = String.valueOf(contractItem.get("item_objid"));
if (itemObjId == null || "null".equals(itemObjId) || itemObjId.trim().isEmpty()) {
itemObjId = String.valueOf(contractItem.get("ITEM_OBJID"));
}
if (itemObjId == null || "null".equals(itemObjId) || itemObjId.trim().isEmpty()) {
return;
}
// 2. 기존 S/N 비활성화
Map<String, Object> deleteParam = new HashMap<String, Object>();
deleteParam.put("itemObjId", itemObjId);
sqlSession.update("contractMgmt.deleteItemSerials", deleteParam);
// 3. 새 S/N 삽입
if (serialNoText == null || serialNoText.trim().isEmpty()) {
return;
}
String[] serialNumbers = serialNoText.split(",");
int seq = 1;
for (String sn : serialNumbers) {
String trimmedSn = sn.trim();
if (trimmedSn.isEmpty()) {
continue;
}
Map<String, Object> insertParam = new HashMap<String, Object>();
insertParam.put("objId", CommonUtils.createObjId());
insertParam.put("itemObjId", itemObjId);
insertParam.put("seq", seq);
insertParam.put("serialNo", trimmedSn);
insertParam.put("writer", writer);
sqlSession.insert("contractMgmt.insertContractItemSerial", insertParam);
seq++;
}
}
}

View File

@@ -4776,7 +4776,17 @@
PP.REQ_DEL_DATE,
PP.PART_NO,
PP.PART_NAME,
PP.SERIAL_NO,
-- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 없으면 PRODUCTION_PLAN 텍스트 fallback
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_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),
PP.SERIAL_NO
) AS SERIAL_NO,
PP.ORDER_QTY,
PP.EXTRA_PROD_QTY,
PP.TOTAL_PROD_QTY,

View File

@@ -352,25 +352,25 @@ public class SalesNcollectMgmtController {
return "/salesmgmt/salesMgmt/projectDetailView";
}
}
// saleNo가 있지만 "detail"이 아니면 프로젝트 기본 정보 조회 모드
// saleNo가 있지만 "detail"이 아니면 판매등록 수정 모드 - getSaleInfo로 S/N 포함 조회
else if(paramMap.get("saleNo") != null && !paramMap.get("saleNo").equals("")) {
System.out.println("=== 프로젝트 기본 정보 조회 모드 ===");
Map<String, Object> orderDataParam = new HashMap<String, Object>();
orderDataParam.put("orderNo", paramMap.get("orderNo"));
saleInfo = salesNcollectMgmtService.getOrderDataByOrderNo(orderDataParam);
System.out.println("=== 판매등록 수정 모드 ===");
Map<String, Object> saleInfoParam = new HashMap<String, Object>();
saleInfoParam.put("orderNo", paramMap.get("orderNo"));
saleInfoParam.put("saleNo", paramMap.get("saleNo"));
saleInfo = salesNcollectMgmtService.getSaleInfo(saleInfoParam);
if(saleInfo != null) {
salesCurrency = CommonUtils.nullToEmpty((String)saleInfo.get("SALES_CURRENCY"));
saleInfo.put("ORDER_QUANTITY", saleInfo.get("SALES_QUANTITY"));
Map<String, Object> shipmentParam = new HashMap<String, Object>();
shipmentParam.put("projectNo", paramMap.get("orderNo"));
Integer totalShipped = salesNcollectMgmtService.getTotalShippedQuantity(shipmentParam);
saleInfo.put("SALES_QUANTITY", totalShipped != null ? totalShipped : 0);
System.out.println("=== 프로젝트 기본 정보 조회 완료 ===");
System.out.println("ORDER_QUANTITY (수주수량): " + saleInfo.get("ORDER_QUANTITY"));
System.out.println("SALES_QUANTITY (총 판매수량): " + saleInfo.get("SALES_QUANTITY"));
System.out.println("=== 판매등록 수정 모드 조회 완료 ===");
System.out.println("SALE_NO: " + saleInfo.get("SALE_NO"));
System.out.println("ORDER_QUANTITY: " + saleInfo.get("ORDER_QUANTITY"));
System.out.println("SALES_QUANTITY: " + saleInfo.get("SALES_QUANTITY"));
System.out.println("SERIAL_NO: " + saleInfo.get("SERIAL_NO"));
System.out.println("MANAGER: " + saleInfo.get("MANAGER"));
System.out.println("INCOTERMS: " + saleInfo.get("INCOTERMS"));
System.out.println("SHIPPING_METHOD: " + saleInfo.get("SHIPPING_METHOD"));
}
}
// saleNo가 없으면 신규 판매 등록 모드 -> 수주 데이터만 조회
@@ -430,6 +430,20 @@ public class SalesNcollectMgmtController {
request.setAttribute("orderInfo", saleInfo);
}
// 수정 모드: saleInfo에서 담당자 선택값 반영
if(saleInfo != null && saleInfo.get("MANAGER") != null) {
String selectedManager = saleInfo.get("MANAGER").toString();
System.out.println("=== 담당자 선택값 반영 ===");
System.out.println("selectedManager: [" + selectedManager + "]");
codeMap.put("managerList", commonService.bizMakeOptionList("", selectedManager, "common.getUserselect"));
} else {
System.out.println("=== 담당자 선택값 없음 ===");
System.out.println("saleInfo null: " + (saleInfo == null));
if(saleInfo != null) {
System.out.println("MANAGER value: " + saleInfo.get("MANAGER"));
}
}
// 환종(통화) - 공통코드 0001533
codeMap.put("salesCurrency",
commonService.bizMakeOptionList("0001533", salesCurrency, "common.getCodeselect"));

View File

@@ -5685,12 +5685,11 @@ WHERE
WHERE OBJID = #{itemObjId}
</update>
<!-- 특정 품목의 S/N 전체 삭제 -->
<update id="deleteItemSerials" parameterType="map">
UPDATE CONTRACT_ITEM_SERIAL
SET STATUS = 'INACTIVE'
<!-- 특정 품목의 S/N 전체 삭제 (hard delete: 유니크 제약조건 충돌 방지) -->
<delete id="deleteItemSerials" parameterType="map">
DELETE FROM CONTRACT_ITEM_SERIAL
WHERE ITEM_OBJID = #{itemObjId}
</update>
</delete>
<!-- 고객사 담당자 정보 조회 -->
<select id="getCustomerContactInfo" parameterType="map" resultType="map">
@@ -5875,4 +5874,16 @@ WHERE
ORDER BY CI.SEQ
</select>
<!-- 프로젝트번호 또는 OBJID로 CONTRACT_ITEM 조회 (S/N 동기화용) -->
<select id="getContractItemByProject" parameterType="map" resultType="map">
SELECT CI.OBJID AS ITEM_OBJID
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'
WHERE (PM.PROJECT_NO = #{projectNo} OR PM.OBJID::VARCHAR = #{projectNo})
LIMIT 1
</select>
</mapper>

View File

@@ -844,19 +844,22 @@
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: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
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
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- 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
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
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL),
SR.serial_no
) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
@@ -893,20 +896,10 @@
COALESCE(SR.sales_vat, 0) AS SALES_VAT,
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT,
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - shipment_log의 split_quantity 합계
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - shipment_log 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - sales_registration.sales_quantity
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(SR.sales_quantity, 0) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: 잔량 * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(SR.sales_quantity, 0)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
COALESCE(SR.sales_currency, T.CONTRACT_CURRENCY) AS SALES_CURRENCY,
CODE_NAME(COALESCE(SR.sales_currency, T.CONTRACT_CURRENCY)) AS SALES_CURRENCY_NAME,
COALESCE(SR.sales_exchange_rate, T.CONTRACT_PRICE_CURRENCY::numeric, 0) AS SALES_EXCHANGE_RATE,
@@ -942,13 +935,14 @@
THEN '분할판매'
ELSE ''
END AS SALES_STATUS,
SR.sale_no AS SALES_REG_NO,
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID,
-- 거래명세서 존재 여부
-- 거래명세서 존재 여부 (LinkedProjectObjids에 해당 프로젝트 OBJID 포함 여부)
CASE WHEN EXISTS(
SELECT 1 FROM NSWOS100_TBL
WHERE OdOrderNo = T.PROJECT_NO
SELECT 1 FROM NSWOS100_TBL
WHERE T.OBJID::VARCHAR = ANY(STRING_TO_ARRAY(LinkedProjectObjids, ','))
) THEN 'Y' ELSE 'N' END AS HAS_TRANSACTION_STATEMENT
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
@@ -1346,7 +1340,18 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="serialNo != null and serialNo != ''">
AND SR.serial_no LIKE '%' || #{serialNo} || '%'
AND (
EXISTS (
SELECT 1 FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND UPPER(CIS.SERIAL_NO) LIKE UPPER('%' || #{serialNo} || '%')
)
OR SR.serial_no LIKE '%' || #{serialNo} || '%'
)
</if>
<if test="poNo != null and poNo != ''">
AND EXISTS (SELECT 1 FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID AND CM.PO_NO LIKE '%' || #{poNo} || '%')
@@ -1550,19 +1555,17 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
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: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
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
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 전체 콤마 리스트 (판매등록 폼 파싱용)
COALESCE(
(SELECT STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SERIAL_NO)
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
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL),
SR.serial_no
) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
@@ -1648,8 +1651,8 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(CM.EXCHANGE_RATE::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 출하일 기본값: 오늘 날짜
TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
@@ -1662,7 +1665,6 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
</select>
@@ -1696,8 +1698,8 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 출하일 기본값: 오늘 날짜
TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
@@ -1711,7 +1713,6 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
</select>
@@ -1920,6 +1921,30 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE target_objid = #{projectNo}
),
shipping_order_status = '출하지시'
<if test="managerUserId != null and managerUserId != ''">
, manager_user_id = #{managerUserId}
</if>
<if test="incoterms != null and incoterms != ''">
, incoterms = #{incoterms}
</if>
<if test="serialNo != null and serialNo != ''">
, serial_no = #{serialNo}
</if>
<if test="shippingMethod != null and shippingMethod != ''">
, shipping_method = #{shippingMethod}
</if>
<if test="shippingDate != null and shippingDate != ''">
, shipping_date = TO_DATE(#{shippingDate}, 'YYYY-MM-DD')
</if>
<if test="salesUnitPrice != null and salesUnitPrice != ''">
, sales_unit_price = #{salesUnitPrice}::numeric
</if>
<if test="salesCurrency != null and salesCurrency != ''">
, sales_currency = #{salesCurrency}
</if>
<if test="salesExchangeRate != null and salesExchangeRate != ''">
, sales_exchange_rate = #{salesExchangeRate}::numeric
</if>
WHERE sale_no = #{saleNo}
</update>
@@ -1976,7 +2001,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 -->
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 (ON CONFLICT DO UPDATE) -->
<insert id="saveTransactionStatement" parameterType="map">
/* salesNcollectMgmt.saveTransactionStatement - 거래명세서 저장 */
INSERT INTO NSWOS100_TBL (
@@ -1995,6 +2020,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,IsQty /* 납품수량 */
,IsPrice /* 납품단가 */
,IsAmount /* 납품금액 */
,LinkedProjectObjids /* 연결된 프로젝트 OBJID 목록 */
) VALUES (
#{suVndCd} /* 업체코드 */
,#{issueDt} /* 작성일자 */
@@ -2011,6 +2037,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,COALESCE(#{isQty}, 0)::integer /* 납품수량 */
,COALESCE(#{isPrice}, 0)::numeric /* 납품단가 */
,COALESCE(#{isAmount}, 0)::numeric /* 납품금액 */
,#{linkedProjectObjids} /* 연결된 프로젝트 OBJID 목록 */
) ON CONFLICT (SuVndCd, IssueDt, IssueNo, IsNo) DO
UPDATE SET
ProdCd = COALESCE(#{prodCd}, '')
@@ -2024,6 +2051,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,IsQty = COALESCE(#{isQty}, 0)::integer
,IsPrice = COALESCE(#{isPrice}, 0)::numeric
,IsAmount = COALESCE(#{isAmount}, 0)::numeric
,LinkedProjectObjids = #{linkedProjectObjids}
</insert>
<!-- 거래명세서 번호 생성 -->
@@ -2051,7 +2079,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
<select id="getSavedTransactionStatement" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSavedTransactionStatement - 저장된 거래명세서 조회 */
SELECT
SELECT
SuVndCd,
IssueDt,
IssueNo,
@@ -2066,9 +2094,10 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
IsDt,
IsQty,
IsPrice,
IsAmount
IsAmount,
LinkedProjectObjids
FROM NSWOS100_TBL
WHERE OdOrderNo = #{projectNo}
WHERE #{projectObjid} = ANY(STRING_TO_ARRAY(LinkedProjectObjids, ','))
ORDER BY IsNo
</select>

View File

@@ -29,6 +29,7 @@ import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
import com.pms.common.utils.CommonUtils;
import com.pms.common.utils.Constants;
import com.pms.common.utils.SerialNoSyncUtil;
import com.pms.api.SalesSlipApiClient;
/**
@@ -367,100 +368,70 @@ public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
}
System.out.println("serialNo: " + paramMap.get("serialNo"));
System.out.println("manager → managerUserId: " + paramMap.get("managerUserId"));
System.out.println("manager: " + paramMap.get("manager"));
// 모든 판매를 shipment_log에 기록 (분할 출하 방식 통일)
// 1. 해당 프로젝트의 sales_registration 레코드 확인
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("orderNo", projectNo);
Map<String, Object> existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
Object saleNoObj = null;
if(existingSale != null) {
saleNoObj = existingSale.get("SALE_NO");
if(saleNoObj == null) saleNoObj = existingSale.get("sale_no");
}
System.out.println("existingSale: " + (existingSale != null ? "있음" : "없음"));
System.out.println("SALE_NO: " + saleNoObj);
// 2. sales_registration 레코드가 없으면 먼저 생성
if(saleNoObj == null) {
System.out.println("sales_registration 레코드 생성 (첫 판매)");
// saleNo가 있으면 수정 모드 → sales_registration 직접 UPDATE
if(saleNo != null && !"".equals(saleNo)) {
System.out.println("=== 수정 모드: updateSaleRegistration ===");
paramMap.put("shippingOrderStatus", "출하지시");
sqlSession.update("salesNcollectMgmt.updateSaleRegistration", paramMap);
} else {
// 신규 등록 모드
System.out.println("=== 신규 등록 모드 ===");
// sales_registration에 기본 레코드 INSERT (수량은 0으로)
Map<String, Object> baseRecord = new HashMap<String, Object>();
baseRecord.put("orderNo", projectNo);
baseRecord.put("salesQuantity", 0); // 기본 레코드는 수량 0
baseRecord.put("salesUnitPrice", paramMap.get("salesUnitPrice"));
baseRecord.put("salesSupplyPrice", 0);
baseRecord.put("salesVat", 0);
baseRecord.put("salesTotalAmount", 0);
baseRecord.put("salesCurrency", paramMap.get("salesCurrency"));
baseRecord.put("salesExchangeRate", paramMap.get("salesExchangeRate"));
baseRecord.put("shippingDate", paramMap.get("shippingDate"));
baseRecord.put("shippingMethod", paramMap.get("shippingMethod"));
baseRecord.put("managerUserId", paramMap.get("managerUserId")); // 위에서 manager → managerUserId 매핑됨
baseRecord.put("incoterms", paramMap.get("incoterms"));
baseRecord.put("serialNo", paramMap.get("serialNo"));
System.out.println("insertSaleRegistration - serialNo: " + paramMap.get("serialNo"));
baseRecord.put("shippingOrderStatus", "출하지시"); // 자동으로 출하지시 상태 설정
baseRecord.put("cretEmpNo", paramMap.get("cretEmpNo"));
// 1. 해당 프로젝트의 sales_registration 레코드 확인
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("orderNo", projectNo);
Map<String, Object> existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", baseRecord);
// 생성된 sale_no 조회
existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
saleNoObj = existingSale.get("SALE_NO");
if(saleNoObj == null) saleNoObj = existingSale.get("sale_no");
System.out.println("생성된 SALE_NO: " + saleNoObj);
}
// 3. shipment_log에 판매 기록 INSERT
System.out.println("shipment_log에 판매 기록 INSERT");
Object orderQtyObj = existingSale.get("ORDER_QUANTITY");
if(orderQtyObj == null) orderQtyObj = existingSale.get("order_quantity");
System.out.println("ORDER_QUANTITY: " + orderQtyObj);
if(orderQtyObj == null || saleNoObj == null) {
System.out.println("=== existingSale 전체 내용 ===");
for(Object key : existingSale.keySet()) {
System.out.println(key + ": " + existingSale.get(key));
Object saleNoObj = null;
if(existingSale != null) {
saleNoObj = existingSale.get("SALE_NO");
if(saleNoObj == null) saleNoObj = existingSale.get("sale_no");
}
System.out.println("existingSale: " + (existingSale != null ? "있음" : "없음"));
System.out.println("SALE_NO: " + saleNoObj);
// 2. sales_registration 레코드가 없으면 생성
if(saleNoObj == null) {
System.out.println("sales_registration 레코드 생성 (첫 판매)");
Map<String, Object> baseRecord = new HashMap<String, Object>();
baseRecord.put("orderNo", projectNo);
baseRecord.put("salesQuantity", paramMap.get("salesQuantity"));
baseRecord.put("salesUnitPrice", paramMap.get("salesUnitPrice"));
baseRecord.put("salesSupplyPrice", paramMap.get("salesSupplyPrice"));
baseRecord.put("salesVat", paramMap.get("salesVat"));
baseRecord.put("salesTotalAmount", paramMap.get("salesTotalAmount"));
baseRecord.put("salesCurrency", paramMap.get("salesCurrency"));
baseRecord.put("salesExchangeRate", paramMap.get("salesExchangeRate"));
baseRecord.put("shippingDate", paramMap.get("shippingDate"));
baseRecord.put("shippingMethod", paramMap.get("shippingMethod"));
baseRecord.put("managerUserId", paramMap.get("manager"));
baseRecord.put("incoterms", paramMap.get("incoterms"));
baseRecord.put("serialNo", paramMap.get("serialNo"));
baseRecord.put("shippingOrderStatus", "출하지시");
baseRecord.put("cretEmpNo", paramMap.get("cretEmpNo"));
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", baseRecord);
System.out.println("sales_registration INSERT 완료");
} else {
// 이미 레코드가 있으면 UPDATE
System.out.println("기존 레코드 존재 → updateSaleRegistration");
paramMap.put("shippingOrderStatus", "출하지시");
sqlSession.update("salesNcollectMgmt.updateSaleRegistration", paramMap);
}
throw new RuntimeException("ORDER_QUANTITY 또는 SALE_NO가 null입니다");
}
paramMap.put("targetObjid", projectNo);
paramMap.put("originalQuantity", orderQtyObj);
// S/N → CONTRACT_ITEM_SERIAL 동기화
String serialNo = CommonUtils.nullToEmpty((String) paramMap.get("serialNo"));
String writer = CommonUtils.nullToEmpty((String) paramMap.get("cretEmpNo"));
// 잔량 계산
int orderQuantity = Integer.parseInt(String.valueOf(orderQtyObj).split("\\.")[0]);
int salesQuantity = Integer.parseInt(String.valueOf(paramMap.get("salesQuantity")));
int remainingQuantity = orderQuantity - salesQuantity;
System.out.println("orderQuantity: " + orderQuantity);
System.out.println("salesQuantity: " + salesQuantity);
System.out.println("remainingQuantity: " + remainingQuantity);
paramMap.put("remainingQuantity", remainingQuantity);
paramMap.put("parentSaleNo", saleNoObj);
System.out.println("shipment_log INSERT 직전 파라미터:");
System.out.println(" targetObjid: " + paramMap.get("targetObjid"));
System.out.println(" parentSaleNo: " + paramMap.get("parentSaleNo"));
System.out.println(" cretEmpNo: " + paramMap.get("cretEmpNo"));
sqlSession.insert("salesNcollectMgmt.insertShipmentLog", paramMap);
// 4. sales_registration의 sales_quantity를 shipment_log 합계로 업데이트
System.out.println("sales_registration 업데이트 - shipment_log 합계 반영");
Map<String, Object> updateParam = new HashMap<String, Object>();
updateParam.put("projectNo", projectNo);
updateParam.put("saleNo", saleNoObj);
sqlSession.update("salesNcollectMgmt.updateSalesQuantityFromShipmentLog", updateParam);
if(projectNo != null && !projectNo.isEmpty()) {
SerialNoSyncUtil.syncSerialToContractItemSerial(
sqlSession, projectNo, serialNo, writer);
}
sqlSession.commit();
resultMap.put("result", true);
@@ -1514,52 +1485,64 @@ public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
System.out.println("=== 거래명세서 저장 시작 ===");
System.out.println("paramMap: " + paramMap);
// 업체코드 (고객 정보에서 추출 필요 - 임시로 '0001' 사용)
String suVndCd = "0001";
// 작성일자 (오늘 날짜)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String issueDt = sdf.format(new Date());
// 납품일자 포맷 변환 (한글 날짜 또는 YYYY-MM-DD → YYYYMMDD)
// 납품일자 포맷 변환
String deliveryDate = "";
String defaultIssueDt = sdf.format(new Date());
if(paramMap.get("deliveryDate") != null && !paramMap.get("deliveryDate").equals("")) {
String deliveryDateStr = String.valueOf(paramMap.get("deliveryDate"));
// 한글 날짜 형식인 경우 (예: "목요일, 11월 13, 2025")
// 오늘 날짜로 대체
if(deliveryDateStr.contains("") || deliveryDateStr.contains("")) {
deliveryDate = issueDt; // 작성일자와 동일하게 설정
System.out.println("납품일자 변환 (한글): " + deliveryDateStr + "" + deliveryDate + " (오늘 날짜 사용)");
}
// YYYY-MM-DD 형식인 경우
else if(deliveryDateStr.contains("-")) {
deliveryDate = deliveryDateStr.replaceAll("-", ""); // "2025-11-13" → "20251113"
System.out.println("납품일자 변환 (하이픈): " + deliveryDateStr + "" + deliveryDate);
}
// 이미 YYYYMMDD 형식인 경우
else if(deliveryDateStr.length() == 8) {
deliveryDate = defaultIssueDt;
} else if(deliveryDateStr.contains("-")) {
deliveryDate = deliveryDateStr.replaceAll("-", "");
} else if(deliveryDateStr.length() == 8) {
deliveryDate = deliveryDateStr;
System.out.println("납품일자 변환 (8자리): " + deliveryDateStr + "" + deliveryDate);
}
// 그 외의 경우 오늘 날짜 사용
else {
deliveryDate = issueDt;
System.out.println("납품일자 변환 (기타): " + deliveryDateStr + "" + deliveryDate + " (오늘 날짜 사용)");
} else {
deliveryDate = defaultIssueDt;
}
}
// 거래명세서 번호 생성
Map<String, Object> noParam = new HashMap<String, Object>();
noParam.put("suVndCd", suVndCd);
noParam.put("issueDt", issueDt);
Integer issueNo = sqlSession.selectOne("salesNcollectMgmt.getNextTransactionStatementNo", noParam);
System.out.println("업체코드: " + suVndCd);
System.out.println("작성일자: " + issueDt);
System.out.println("거래명세서번호: " + issueNo);
// 품목 정보 저장
// 연결된 프로젝트 OBJID 목록 (콤마 구분)
String linkedProjectObjids = paramMap.get("projectObjids") != null
? String.valueOf(paramMap.get("projectObjids")) : "";
// 첫 번째 프로젝트 OBJID로 기존 거래명세서 확인
String firstObjid = linkedProjectObjids.split(",")[0].trim();
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("projectObjid", firstObjid);
List<Map<String, Object>> existingData = sqlSession.selectList("salesNcollectMgmt.getSavedTransactionStatement", checkParam);
String issueDt;
Integer issueNo;
if(existingData != null && !existingData.isEmpty()) {
Map<String, Object> first = existingData.get(0);
issueDt = String.valueOf(first.get("issuedt"));
issueNo = Integer.parseInt(String.valueOf(first.get("issueno")));
// 기존 거래명세서 업데이트 시 DB에 저장된 LinkedProjectObjids 유지
String existingLinked = String.valueOf(first.get("linkedprojectobjids"));
if(existingLinked != null && !existingLinked.isEmpty() && !"null".equals(existingLinked)) {
linkedProjectObjids = existingLinked;
}
System.out.println("기존 거래명세서 업데이트: IssueDt=" + issueDt + ", IssueNo=" + issueNo + ", LinkedObjids=" + linkedProjectObjids);
} else {
issueDt = defaultIssueDt;
Map<String, Object> noParam = new HashMap<String, Object>();
noParam.put("suVndCd", suVndCd);
noParam.put("issueDt", issueDt);
issueNo = sqlSession.selectOne("salesNcollectMgmt.getNextTransactionStatementNo", noParam);
System.out.println("신규 거래명세서 생성: IssueDt=" + issueDt + ", IssueNo=" + issueNo);
}
// OdOrderNo용 첫 번째 프로젝트번호 (기존 호환)
String firstProjectNo = String.valueOf(paramMap.get("projectNos")).split(",")[0].trim();
if(firstProjectNo.length() > 50) {
firstProjectNo = firstProjectNo.substring(0, 50);
}
// 거래명세서 1건만 저장 (모든 품목 포함)
List<Map<String, Object>> items = (List<Map<String, Object>>) paramMap.get("items");
if(items != null && items.size() > 0) {
int seqNo = 1;
@@ -1569,76 +1552,53 @@ public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
insertParam.put("issueDt", issueDt);
insertParam.put("issueNo", issueNo);
insertParam.put("isNo", seqNo);
// 품목 정보
insertParam.put("prodCd", ""); // 기종코드
// 발주번호 (프로젝트번호) - VARCHAR(15) 제한
String projectNos = String.valueOf(paramMap.get("projectNos"));
if(projectNos.length() > 15) {
projectNos = projectNos.substring(0, 15); // 15자로 자르기
String productName = item.get("productName") != null ? String.valueOf(item.get("productName")) : "";
if(productName.length() > 50) {
productName = productName.substring(0, 50);
}
insertParam.put("odOrderNo", projectNos);
// 품번 - VARCHAR(15) 제한
insertParam.put("prodCd", productName);
insertParam.put("odOrderNo", firstProjectNo);
insertParam.put("linkedProjectObjids", linkedProjectObjids);
String spec = String.valueOf(item.get("spec"));
if(spec != null && spec.length() > 15) {
spec = spec.substring(0, 15); // 15자로 자르기
if(spec != null && spec.length() > 50) {
spec = spec.substring(0, 50);
}
insertParam.put("imItemId", spec);
insertParam.put("rmDueDt", ""); // 납기일자
// 수량 정보
insertParam.put("rmDueDt", "");
String quantityStr = String.valueOf(item.get("quantity"));
quantityStr = quantityStr.replaceAll(",", "").replaceAll("[^0-9]", "");
int quantity = 0;
try {
quantity = Integer.parseInt(quantityStr);
} catch(Exception e) {
quantity = 0;
}
insertParam.put("rmOrderQty", quantity); // 발주수량
insertParam.put("rmRcptQty", 0); // 입고처리수량
insertParam.put("rmRemQty", 0); // 잔량
insertParam.put("isDt", deliveryDate); // 납기일자 (포맷 변환된 값)
insertParam.put("isQty", quantity); // 납품수량
// 금액 정보
try { quantity = Integer.parseInt(quantityStr); } catch(Exception e) { quantity = 0; }
insertParam.put("rmOrderQty", quantity);
insertParam.put("rmRcptQty", 0);
insertParam.put("rmRemQty", 0);
insertParam.put("isDt", deliveryDate);
insertParam.put("isQty", quantity);
String unitPriceStr = String.valueOf(item.get("unitPrice"));
unitPriceStr = unitPriceStr.replaceAll(",", "").replaceAll("[^0-9.]", "");
double unitPrice = 0;
try {
unitPrice = Double.parseDouble(unitPriceStr);
} catch(Exception e) {
unitPrice = 0;
}
try { unitPrice = Double.parseDouble(unitPriceStr); } catch(Exception e) { unitPrice = 0; }
String supplyPriceStr = String.valueOf(item.get("supplyPrice"));
supplyPriceStr = supplyPriceStr.replaceAll(",", "").replaceAll("[^0-9.]", "");
double supplyPrice = 0;
try {
supplyPrice = Double.parseDouble(supplyPriceStr);
} catch(Exception e) {
supplyPrice = 0;
}
insertParam.put("isPrice", unitPrice); // 납품단가
insertParam.put("isAmount", supplyPrice); // 납품금액
try { supplyPrice = Double.parseDouble(supplyPriceStr); } catch(Exception e) { supplyPrice = 0; }
insertParam.put("isPrice", unitPrice);
insertParam.put("isAmount", supplyPrice);
System.out.println("품목 " + seqNo + " 저장: " + item.get("productName"));
System.out.println(" - 품번: " + item.get("spec"));
System.out.println(" - 수량: " + quantity);
System.out.println(" - 단가: " + unitPrice);
System.out.println(" - 금액: " + supplyPrice);
sqlSession.insert("salesNcollectMgmt.saveTransactionStatement", insertParam);
seqNo++;
}
}
sqlSession.commit();
resultMap.put("success", true);
resultMap.put("message", "거래명세서가 저장되었습니다.");
resultMap.put("issueNo", issueNo);

View File

@@ -18,6 +18,7 @@ import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
import com.pms.common.utils.CommonUtils;
import com.pms.common.utils.Constants;
import com.pms.common.utils.SerialNoSyncUtil;
@Service
public class ProductionPlanningService {
@@ -1897,20 +1898,28 @@ public class ProductionPlanningService {
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String objid = CommonUtils.nullToEmpty((String)paramMap.get("OBJID"));
String actionType = CommonUtils.nullToEmpty((String)paramMap.get("actionType"));
if("regist".equals(actionType) || "".equals(objid)) {
// 신규 등록
paramMap.put("OBJID", CommonUtils.createObjId());
sqlSession.insert("productionplanning.insertProdPlan", paramMap);
} else {
// 수정
sqlSession.update("productionplanning.updateProdPlan", paramMap);
}
// S/N → CONTRACT_ITEM_SERIAL 동기화 (프로젝트 연결 시)
String projectNo = CommonUtils.nullToEmpty((String)paramMap.get("PROJECT_NO"));
String serialNo = CommonUtils.nullToEmpty((String)paramMap.get("SERIAL_NO"));
String writer = CommonUtils.nullToEmpty((String)paramMap.get("userId"));
if(!projectNo.isEmpty()) {
SerialNoSyncUtil.syncSerialToContractItemSerial(
sqlSession, projectNo, serialNo, writer);
}
sqlSession.commit();
result = true;
} catch(Exception e) {