ebom, mbom

E-BOM & M-BOM 파트 추가 삭제시 반제품 추가 삭제하면 하위 품목 같이 추가 삭제 되는 기능
E-BOM & M-BOM 파트 추가시 원하는 위치로 집어 넣는 기능(현재는 왼쪽에서 선택한 하위레벨의 제일 밑으로만 들어감)
E-BOM & M-BOM 파트 추가 삭제시 자동저장이 아니라, 저장/닫기 버튼이 있어서 저장버튼 누를때만 저장되도록 변경 필요
신규 프로젝트 생성하고, M-BOM 처음 만들때 E-BOM 을 가져와서 할당할때
최상위 제품을 변경하는 로직이 필요(최상위 제품을 삭제하고 반제품을 최상위 품으로 만드는 로직)
최상위 제품으로 만들려고 하는 반제품의 하위 부품도 딸려와서 구성이 되어야 하며,
M-BOM 의 이름도 변경된 최상위 제품의 품번으로 변경되어 저장되어야 함
This commit is contained in:
2026-02-27 18:50:47 +09:00
parent ac189cd114
commit 3e27278599
10 changed files with 1191 additions and 461 deletions

View File

@@ -1223,6 +1223,58 @@ public class PartMngController {
return bomTreeList != null ? bomTreeList : new ArrayList<Map>();
}
/**
* 품번 기준 E-BOM 하위 구조 조회 (반제품 추가 시 하위 품목 연동용)
* 품번에 해당하는 E-BOM이 있으면 하위 트리를 반환, 없으면 빈 리스트 반환
*/
@RequestMapping("/partMng/getEbomSubTreeByPartNo.do")
@ResponseBody
public Map<String, Object> getEbomSubTreeByPartNo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> result = new HashMap<>();
try {
String partNo = CommonUtils.checkNull((String)paramMap.get("partNo"));
if(partNo.isEmpty()) {
result.put("hasEbom", false);
result.put("subTree", new ArrayList<>());
return result;
}
// 1차: 품번으로 단독 E-BOM 존재 여부 확인 (PART_BOM_REPORT)
Map bomReport = partMngService.getBomObjIdByPartNo(partNo);
if(bomReport != null && !bomReport.isEmpty()) {
String bomReportObjId = CommonUtils.checkNull(bomReport.get("OBJID"));
if(bomReportObjId.isEmpty()) {
bomReportObjId = CommonUtils.checkNull(bomReport.get("objid"));
}
Map<String, Object> treeParam = new HashMap<>();
treeParam.put("bomReportObjId", bomReportObjId);
treeParam.put("search_type", "working");
List subTree = partMngService.getBOMPartTreeListSimple(treeParam);
result.put("hasEbom", true);
result.put("bomReportObjId", bomReportObjId);
result.put("subTree", subTree != null ? subTree : new ArrayList<>());
return result;
}
// 2차 fallback: 다른 E-BOM 안에서 해당 품번이 하위 구조를 가진 경우 조회
List subTreeFallback = partMngService.findEbomSubTreeForPart(partNo);
if(subTreeFallback != null && !subTreeFallback.isEmpty()) {
result.put("hasEbom", true);
result.put("subTree", subTreeFallback);
return result;
}
result.put("hasEbom", false);
result.put("subTree", new ArrayList<>());
} catch(Exception e) {
e.printStackTrace();
result.put("hasEbom", false);
result.put("subTree", new ArrayList<>());
}
return result;
}
@RequestMapping("/partMng/structurePopupCenter.do")
public String structurePopupCenter(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
@@ -1250,6 +1302,44 @@ public class PartMngController {
return "/partMng/structurePopupCenter";
}
/**
* E-BOM 일괄 저장 (클라이언트 편집 후 저장)
*/
@RequestMapping("/partMng/saveEbom.do")
@ResponseBody
public Map<String, Object> saveEbom(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
Map<String, Object> result = new HashMap<>();
try {
String bomReportObjId = CommonUtils.checkNull((String)paramMap.get("bomReportObjId"));
List<Map<String, Object>> ebomData = (List<Map<String, Object>>) paramMap.get("ebomData");
if(bomReportObjId.isEmpty() || ebomData == null || ebomData.isEmpty()) {
result.put("result", "fail");
result.put("message", "저장할 데이터가 없습니다.");
return result;
}
HttpSession session = request.getSession();
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
Map info = person.getLoginInfo();
String userId = CommonUtils.checkNull(info.get("userId"));
boolean saved = partMngService.saveEbomBatch(bomReportObjId, ebomData, userId);
if(saved) {
result.put("result", "success");
} else {
result.put("result", "fail");
result.put("message", "저장에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
result.put("result", "fail");
result.put("message", e.getMessage());
}
return result;
}
/**
* 구조등록 우측 프레임
* @param request

View File

@@ -19,7 +19,7 @@
A.SEQ,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE
FROM
BOM_PART_QTY A
@@ -53,7 +53,7 @@
B.SEQ,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH)
FROM
BOM_PART_QTY B
@@ -3322,7 +3322,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
(SELECT PART_NO FROM PART_MNG P WHERE 1=1 AND P.OBJID::varchar = A.PARENT_PART_NO) AS PARENT_PART_MNG_NO,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE
FROM
BOM_PART_QTY A
@@ -3361,7 +3361,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
(SELECT PART_NO FROM PART_MNG P WHERE 1=1 AND P.OBJID::varchar = B.PARENT_PART_NO) AS PARENT_PART_MNG_NO,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH)
FROM
BOM_PART_QTY B
@@ -3949,6 +3949,122 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
</insert>
<!-- E-BOM 내에서 특정 품번이 하위 품목을 가진 채 존재하는 BOM 찾기 (fallback용) -->
<select id="findPartInEbomWithChildren" parameterType="map" resultType="map">
SELECT BPQ.BOM_REPORT_OBJID, BPQ.CHILD_OBJID, BPQ.PART_NO AS PART_OBJID
FROM BOM_PART_QTY BPQ
INNER JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO AND PM.IS_LAST = '1'
WHERE PM.PART_NO = #{partNo}
AND (BPQ.STATUS NOT IN ('deleting', 'deleted') OR BPQ.STATUS IS NULL)
AND EXISTS (
SELECT 1 FROM BOM_PART_QTY SUB
WHERE SUB.PARENT_OBJID = BPQ.CHILD_OBJID
AND SUB.BOM_REPORT_OBJID = BPQ.BOM_REPORT_OBJID
AND (SUB.STATUS NOT IN ('deleting', 'deleted') OR SUB.STATUS IS NULL)
)
ORDER BY BPQ.REGDATE DESC
LIMIT 1
</select>
<!-- 특정 CHILD_OBJID 아래의 하위 트리 조회 (반제품 하위 구조 fallback용) -->
<select id="getSubTreeByParentChildObjid" parameterType="map" resultType="map">
WITH RECURSIVE sub_tree(
BOM_REPORT_OBJID, OBJID, PARENT_OBJID, CHILD_OBJID,
PARENT_PART_NO, PART_NO, LAST_PART_OBJID,
QTY, ITEM_QTY, QTY_TEMP, SEQ, STATUS, LEV
) AS (
SELECT A.BOM_REPORT_OBJID, A.OBJID, A.PARENT_OBJID, A.CHILD_OBJID,
A.PARENT_PART_NO, A.PART_NO, A.LAST_PART_OBJID,
A.QTY, A.ITEM_QTY, A.QTY_TEMP, A.SEQ, A.STATUS, 1
FROM BOM_PART_QTY A
WHERE A.PARENT_OBJID = #{parentChildObjid}
AND A.BOM_REPORT_OBJID = #{bomReportObjId}
AND (A.STATUS NOT IN ('deleting', 'deleted') OR A.STATUS IS NULL)
UNION ALL
SELECT B.BOM_REPORT_OBJID, B.OBJID, B.PARENT_OBJID, B.CHILD_OBJID,
B.PARENT_PART_NO, B.PART_NO, B.LAST_PART_OBJID,
B.QTY, B.ITEM_QTY, B.QTY_TEMP, B.SEQ, B.STATUS, S.LEV + 1
FROM BOM_PART_QTY B
JOIN sub_tree S ON B.PARENT_OBJID = S.CHILD_OBJID
AND B.BOM_REPORT_OBJID = S.BOM_REPORT_OBJID
WHERE (B.STATUS NOT IN ('deleting', 'deleted') OR B.STATUS IS NULL)
)
SELECT
S.BOM_REPORT_OBJID
,S.OBJID
,S.PARENT_OBJID
,S.CHILD_OBJID
,S.PARENT_PART_NO
,S.PART_NO AS PART_OBJID
,S.LAST_PART_OBJID AS BOM_LAST_PART_OBJID
,P.OBJID AS LAST_PART_OBJID
,P.PART_NO
,P.PART_NAME
,S.QTY
,S.ITEM_QTY
,(CASE WHEN S.STATUS = 'deploy' THEN S.QTY
WHEN S.STATUS = 'beforeEdit' THEN S.QTY
WHEN S.STATUS != 'editing' AND (S.QTY_TEMP IS NULL OR S.QTY_TEMP = '') THEN S.QTY
ELSE COALESCE(S.QTY_TEMP, S.QTY) END) AS QTY_TEMP
,S.LEV AS LEVEL
,(SELECT COUNT(*) FROM BOM_PART_QTY WHERE PARENT_OBJID = S.CHILD_OBJID) AS SUB_PART_CNT
,S.SEQ
,S.STATUS
,P.SPEC
,P.MATERIAL
,P.REVISION
,(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = P.PART_TYPE) AS PART_TYPE_TITLE
,(SELECT CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = P.UNIT) AS UNIT_TITLE
FROM sub_tree S
INNER JOIN PART_MNG P ON P.OBJID = COALESCE(NULLIF(S.LAST_PART_OBJID, ''), S.PART_NO)
ORDER BY S.LEV, S.SEQ
</select>
<!-- E-BOM 차분 저장: 기존 데이터 조회 -->
<select id="getExistingBomPartQty" parameterType="map" resultType="map">
SELECT CHILD_OBJID, PARENT_OBJID, PART_NO, PARENT_PART_NO, QTY, ITEM_QTY, QTY_TEMP, SEQ, STATUS
FROM BOM_PART_QTY
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
</select>
<!-- E-BOM 차분 저장: CHILD_OBJID 기준 개별 삭제 -->
<delete id="deleteBomPartQtyByChildObjid" parameterType="map">
DELETE FROM BOM_PART_QTY
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
AND CHILD_OBJID = #{childObjid}
</delete>
<!-- E-BOM 차분 저장: CHILD_OBJID 기준 수정 (수량, 순서, 부모 등) -->
<update id="updateBomPartQtyByChildObjid" parameterType="map">
UPDATE BOM_PART_QTY
SET PARENT_OBJID = #{PARENT_OBJID},
QTY = COALESCE(NULLIF(#{QTY}, ''), '0')::NUMERIC,
ITEM_QTY = COALESCE(NULLIF(#{ITEM_QTY}, ''), '0')::NUMERIC,
QTY_TEMP = COALESCE(NULLIF(#{QTY_TEMP}, ''), '0')::NUMERIC,
SEQ = #{SEQ}
WHERE BOM_REPORT_OBJID = #{bomReportObjId}
AND CHILD_OBJID = #{childObjid}
</update>
<!-- E-BOM 차분 저장: 신규 행 INSERT -->
<insert id="insertBomPartQtyBatch" parameterType="map">
INSERT INTO BOM_PART_QTY (
BOM_REPORT_OBJID, OBJID, PARENT_OBJID, CHILD_OBJID,
PARENT_PART_NO, PART_NO, LAST_PART_OBJID,
QTY, ITEM_QTY, QTY_TEMP,
REGDATE, WRITER, SEQ, STATUS
) VALUES (
#{BOM_REPORT_OBJID}, #{OBJID}, #{PARENT_OBJID}, #{CHILD_OBJID},
#{PARENT_PART_NO}, #{PART_NO}, #{LAST_PART_OBJID},
COALESCE(NULLIF(#{QTY}, ''), '0')::NUMERIC,
COALESCE(NULLIF(#{ITEM_QTY}, ''), '0')::NUMERIC,
COALESCE(NULLIF(#{QTY_TEMP}, ''), '0')::NUMERIC,
NOW(), #{WRITER}, #{SEQ}, #{STATUS}
)
</insert>
<!-- //BOM 구조등록 -->
<insert id="relatePartInfo" parameterType="map">
INSERT INTO BOM_PART_QTY
@@ -5778,7 +5894,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
B.LENGTH,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.SEQ,
B.MAKER,
@@ -5899,7 +6015,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
B.LENGTH,
LEV + 1,
PATH||A.CHILD_OBJID::TEXT,
PATH2||A.SEQ::TEXT,
PATH2||LPAD(A.SEQ::TEXT, 10, '0'),
A.PARENT_OBJID = ANY(PATH),
A.SEQ,
B.MAKER,
@@ -6058,7 +6174,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
COALESCE(BPQ.LAST_PART_OBJID, BPQ.PART_NO) AS LAST_PART_OBJID,
1 AS LEV,
ARRAY[BPQ.CHILD_OBJID::TEXT] AS PATH,
ARRAY[BPQ.SEQ::TEXT] AS PATH2,
ARRAY[LPAD(BPQ.SEQ::TEXT, 10, '0')] AS PATH2,
0 AS LEAF
FROM BOM_PART_QTY BPQ
LEFT JOIN PART_BOM_REPORT PBR ON BPQ.BOM_REPORT_OBJID = PBR.OBJID
@@ -6109,7 +6225,7 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
COALESCE(BPQ.LAST_PART_OBJID, BPQ.PART_NO) AS LAST_PART_OBJID,
BT.LEV + 1,
BT.PATH || BPQ.CHILD_OBJID::TEXT,
BT.PATH2 || BPQ.SEQ::TEXT,
BT.PATH2 || LPAD(BPQ.SEQ::TEXT, 10, '0'),
0 AS LEAF
FROM BOM_PART_QTY BPQ
INNER JOIN BOM_TREE BT ON BPQ.PARENT_OBJID = BT.CHILD_OBJID

View File

@@ -1238,7 +1238,7 @@
-->
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.SEQ,
A.LAST_PART_OBJID
@@ -1337,7 +1337,7 @@
-->
LEV + 1,
PATH||A.CHILD_OBJID::TEXT,
PATH2||A.SEQ::TEXT,
PATH2||LPAD(A.SEQ::TEXT, 10, '0'),
A.PARENT_OBJID = ANY(PATH),
A.SEQ,
A.LAST_PART_OBJID
@@ -3626,7 +3626,7 @@
A.STATUS,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.UNIT,
A.SUPPLY_TYPE,
@@ -3668,7 +3668,7 @@
B.STATUS,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH),
B.UNIT,
B.SUPPLY_TYPE,
@@ -4155,7 +4155,7 @@
A.STATUS,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.UNIT,
A.SUPPLY_TYPE,
@@ -4208,7 +4208,7 @@
B.STATUS,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH),
B.UNIT,
B.SUPPLY_TYPE,
@@ -4381,7 +4381,7 @@
A.STATUS,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.UNIT,
A.WRITER,
@@ -4411,7 +4411,7 @@
B.STATUS,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH),
B.UNIT,
B.WRITER,

View File

@@ -2235,7 +2235,7 @@ WITH RECURSIVE VIEW_BOM(
A.SEQ,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE
FROM
BOM_PART_QTY A
@@ -2268,8 +2268,8 @@ WITH RECURSIVE VIEW_BOM(
B.SEQ,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
B.PARENT_OBJID = ANY(PATH)
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH)
FROM
BOM_PART_QTY B
JOIN
@@ -3312,7 +3312,7 @@ WITH RECURSIVE VIEW_BOM(
A.STATUS,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.UNIT,
A.SUPPLY_TYPE,
@@ -3375,7 +3375,7 @@ WITH RECURSIVE VIEW_BOM(
B.STATUS,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH),
B.UNIT,
B.SUPPLY_TYPE,
@@ -3691,7 +3691,7 @@ WITH RECURSIVE VIEW_BOM(
A.STATUS,
1,
ARRAY [A.CHILD_OBJID::TEXT],
ARRAY [A.SEQ::TEXT],
ARRAY [LPAD(A.SEQ::TEXT, 10, '0')],
FALSE,
A.UNIT,
A.SUPPLY_TYPE,
@@ -3755,7 +3755,7 @@ WITH RECURSIVE VIEW_BOM(
B.STATUS,
LEV + 1,
PATH||B.CHILD_OBJID::TEXT,
PATH2||B.SEQ::TEXT,
PATH2||LPAD(B.SEQ::TEXT, 10, '0'),
B.PARENT_OBJID = ANY(PATH),
B.UNIT,
B.SUPPLY_TYPE,

View File

@@ -1475,8 +1475,8 @@ public class PartMngService extends BaseService {
sqlParamMap.put("PARENT_PART_OBJID", CommonUtils.checkNull(paramMap.get("leftObjId")));
sqlParamMap.put("PARENT_PART_NO", CommonUtils.checkNull(paramMap.get("leftPartNoQty")));
sqlParamMap.put("PARENT_QTY_CHILD_OBJID", CommonUtils.checkNull(paramMap.get("leftPartChildObjId")));
sqlParamMap.put("QTY", 1);
sqlParamMap.put("QTY_TEMP", 1);
sqlParamMap.put("QTY", "1");
sqlParamMap.put("QTY_TEMP", "1");
sqlParamMap.put("STATUS", "adding");
sqlParamMap.put("WRITER", userId);
//sqlParamMap.put("bomReportObjId", CommonUtils.checkNull(paramMap.get("OBJID")));
@@ -3840,6 +3840,195 @@ public class PartMngService extends BaseService {
return resultMap;
}
/**
* 품번(String)으로 E-BOM 존재 여부 조회
*/
public Map<String, Object> getBomObjIdByPartNo(String partNo) throws Exception {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("partNo", partNo);
return getBomObjIdByPartNo(paramMap);
}
/**
* E-BOM 하위 트리 조회 (반제품 추가 시 하위 품목 연동용)
* getBOMPartTreeList와 동일하지만 HttpServletRequest 없이 직접 bomReportObjId로 조회
*/
public List getBOMPartTreeListSimple(Map<String, Object> paramMap) {
List<Map<String,Object>> resultList = new ArrayList();
List<Map<String,Object>> finalList = new ArrayList();
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
try {
resultList = sqlSession.selectList("partMng.getBOMTreeList", paramMap);
int maxLevel = 0;
if(null != resultList && 0 < resultList.size()) {
for(int i = 0; i < resultList.size(); i++) {
Map resultMap = (HashMap) resultList.get(i);
int resultLevel = Integer.parseInt(CommonUtils.checkNull(resultMap.get("level"), "0"));
if(maxLevel < resultLevel) {
maxLevel = resultLevel;
}
}
for(int i = 0; i < resultList.size(); i++) {
Map resultMap = (HashMap) resultList.get(i);
resultMap.put("MAX_LEVEL", maxLevel);
finalList.add(resultMap);
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return finalList;
}
/**
* E-BOM 내에서 특정 품번이 하위 구조를 가진 채 존재하는 경우 그 하위 트리를 조회
* 단독 E-BOM(PART_BOM_REPORT)이 없는 반제품도 다른 E-BOM 안에서 하위 구조를 찾아 반환
*/
public List findEbomSubTreeForPart(String partNo) {
List resultList = new ArrayList();
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
try {
Map<String, Object> findParam = new HashMap<>();
findParam.put("partNo", partNo);
Map<String, Object> found = (Map<String, Object>) sqlSession.selectOne("partMng.findPartInEbomWithChildren", findParam);
if(found == null || found.isEmpty()) {
return resultList;
}
String bomReportObjId = CommonUtils.checkNull(found.get("bom_report_objid"), CommonUtils.checkNull(found.get("BOM_REPORT_OBJID")));
String parentChildObjid = CommonUtils.checkNull(found.get("child_objid"), CommonUtils.checkNull(found.get("CHILD_OBJID")));
if(bomReportObjId.isEmpty() || parentChildObjid.isEmpty()) {
return resultList;
}
Map<String, Object> treeParam = new HashMap<>();
treeParam.put("bomReportObjId", bomReportObjId);
treeParam.put("parentChildObjid", parentChildObjid);
resultList = sqlSession.selectList("partMng.getSubTreeByParentChildObjid", treeParam);
if(resultList != null && resultList.size() > 0) {
int maxLevel = 0;
for(int i = 0; i < resultList.size(); i++) {
Map row = (HashMap) resultList.get(i);
int lev = Integer.parseInt(CommonUtils.checkNull(row.get("level"), "0"));
if(maxLevel < lev) maxLevel = lev;
}
for(int i = 0; i < resultList.size(); i++) {
Map row = (HashMap) resultList.get(i);
row.put("MAX_LEVEL", maxLevel);
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return resultList != null ? resultList : new ArrayList();
}
/**
* E-BOM 일괄 저장 (차분 방식: 기존 CHILD_OBJID 보존, 추가/삭제/수정 분리)
* ASSEMBLY_WBS_TASK, SALES_BOM_REPORT_PART 등이 CHILD_OBJID를 참조하므로 전체 삭제 불가
*/
public boolean saveEbomBatch(String bomReportObjId, List<Map<String, Object>> ebomData, String userId) {
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
try {
// 1. 기존 데이터 조회
Map<String, Object> queryParam = new HashMap<>();
queryParam.put("bomReportObjId", bomReportObjId);
List<Map<String, Object>> existingList = sqlSession.selectList("partMng.getExistingBomPartQty", queryParam);
// 기존 CHILD_OBJID 맵 (CHILD_OBJID -> 행 데이터)
Map<String, Map<String, Object>> existingMap = new HashMap<>();
for(Map<String, Object> row : existingList) {
String childObjid = CommonUtils.checkNull(row.get("child_objid"), CommonUtils.checkNull(row.get("CHILD_OBJID")));
if(!childObjid.isEmpty()) {
existingMap.put(childObjid, row);
}
}
// 새 데이터의 CHILD_OBJID 셋
Set<String> newChildObjids = new HashSet<>();
for(Map<String, Object> item : ebomData) {
String childObjid = CommonUtils.checkNull(item.get("childObjid"));
if(!childObjid.isEmpty()) {
newChildObjids.add(childObjid);
}
}
// 새 데이터에서 참조하는 PARENT_OBJID 셋 (삭제 보호용)
Set<String> referencedParentObjids = new HashSet<>();
for(Map<String, Object> item : ebomData) {
String parentObjid = CommonUtils.checkNull(item.get("parentObjid"));
if(!parentObjid.isEmpty()) {
referencedParentObjids.add(parentObjid);
}
}
// 2. 삭제: 기존에 있지만 새 데이터에 없는 항목 (다른 행이 참조하는 부모는 보호)
for(String existingChildObjid : existingMap.keySet()) {
if(!newChildObjids.contains(existingChildObjid)) {
if(referencedParentObjids.contains(existingChildObjid)) {
continue;
}
Map<String, Object> deleteParam = new HashMap<>();
deleteParam.put("bomReportObjId", bomReportObjId);
deleteParam.put("childObjid", existingChildObjid);
sqlSession.delete("partMng.deleteBomPartQtyByChildObjid", deleteParam);
}
}
// 3. 추가/수정
for(int i = 0; i < ebomData.size(); i++) {
Map<String, Object> item = ebomData.get(i);
String childObjid = CommonUtils.checkNull(item.get("childObjid"));
if(existingMap.containsKey(childObjid)) {
// 기존 항목 → 수정 (수량, 순서 등만 업데이트)
Map<String, Object> updateParam = new HashMap<>();
updateParam.put("bomReportObjId", bomReportObjId);
updateParam.put("childObjid", childObjid);
updateParam.put("PARENT_OBJID", CommonUtils.checkNull(item.get("parentObjid")));
updateParam.put("QTY", CommonUtils.checkNull(item.get("qty"), "1"));
updateParam.put("ITEM_QTY", CommonUtils.checkNull(item.get("itemQty"), "1"));
updateParam.put("QTY_TEMP", CommonUtils.checkNull(item.get("qtyTemp"), "1"));
updateParam.put("SEQ", i + 1);
sqlSession.update("partMng.updateBomPartQtyByChildObjid", updateParam);
} else {
// 신규 항목 → INSERT
Map<String, Object> insertParam = new HashMap<>();
insertParam.put("BOM_REPORT_OBJID", bomReportObjId);
insertParam.put("OBJID", CommonUtils.createObjId());
insertParam.put("PARENT_OBJID", CommonUtils.checkNull(item.get("parentObjid")));
insertParam.put("CHILD_OBJID", childObjid.startsWith("-") ? CommonUtils.createObjId() : childObjid);
insertParam.put("PARENT_PART_NO", CommonUtils.checkNull(item.get("parentPartNo")));
insertParam.put("PART_NO", CommonUtils.checkNull(item.get("partNo")));
insertParam.put("LAST_PART_OBJID", CommonUtils.checkNull(item.get("lastPartObjid")));
insertParam.put("QTY", CommonUtils.checkNull(item.get("qty"), "1"));
insertParam.put("ITEM_QTY", CommonUtils.checkNull(item.get("itemQty"), "1"));
insertParam.put("QTY_TEMP", CommonUtils.checkNull(item.get("qtyTemp"), "1"));
insertParam.put("SEQ", i + 1);
insertParam.put("STATUS", "editing");
insertParam.put("WRITER", userId);
sqlSession.insert("partMng.insertBomPartQtyBatch", insertParam);
}
}
sqlSession.commit();
return true;
} catch(Exception e) {
sqlSession.rollback();
e.printStackTrace();
return false;
} finally {
sqlSession.close();
}
}
/**
* BOM 복사를 위한 데이터 조회 (엑셀 파싱 형식과 동일하게 반환)
*/