SerialNo 동기화 (영업, 생산, 판매)

This commit is contained in:
2026-03-06 15:57:34 +09:00
parent 1912bb0547
commit bb544393f9
7 changed files with 167 additions and 27 deletions

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

@@ -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,20 +844,21 @@
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 우선, 없으면 수주 아이템 S/N 표시
COALESCE(SR.serial_no,
-- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 없으면 판매등록 텍스트 fallback (그리드 요약용)
COALESCE(
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
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
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)
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
@@ -1339,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} || '%')
@@ -1543,20 +1555,16 @@ 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 우선, 없으면 수주 아이템 S/N 표시
COALESCE(SR.serial_no,
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
-- 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
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)
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

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;
/**
@@ -422,6 +423,15 @@ public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
sqlSession.update("salesNcollectMgmt.updateSaleRegistration", paramMap);
}
}
// S/N → CONTRACT_ITEM_SERIAL 동기화
String serialNo = CommonUtils.nullToEmpty((String) paramMap.get("serialNo"));
String writer = CommonUtils.nullToEmpty((String) paramMap.get("cretEmpNo"));
if(projectNo != null && !projectNo.isEmpty()) {
SerialNoSyncUtil.syncSerialToContractItemSerial(
sqlSession, projectNo, serialNo, writer);
}
sqlSession.commit();
resultMap.put("result", true);

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) {