Merge pull request 'V20251023001' (#22) from V20251023001 into main

Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
2025-10-28 04:35:48 +00:00
4 changed files with 249 additions and 155 deletions

View File

@@ -9,7 +9,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator.min.js"></script>
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
<style>
::-webkit-scrollbar {
width: 10px;
@@ -103,10 +103,8 @@ $(function(){
// Tabulator 초기화
fn_initRightGrid();
// 초기 로드 시 자동으로 파트 데이터 조회
if($("#bomReportObjId").val()) {
fn_searchPart();
}
// 초기 로드 제거 - 검색 버튼 클릭 시에만 데이터 로드
// 사용자가 검색 조건 입력 후 조회하도록 유도
});
var _rightGrid;
@@ -163,38 +161,74 @@ function fn_initRightGrid(){
height: "600px",
pagination: false,
columns: columns,
placeholder: "조회된 부품이 없습니다.",
data: []
placeholder: "검색 버튼을 클릭하여 품목을 조회하세요"
});
}
function fn_searchPart(){
// 전체 품목 데이터 조회 (검색 조건 적용)
$.ajax({
url: "/partMng/getPartMngList_ajax.do",
method: 'post',
data: $("#form1").serialize(),
dataType: 'json',
beforeSend: function(){
// 로딩 스피너 표시
_startLoading("파트 목록 조회 중...");
},
complete: function(){
// 로딩 스피너 숨김 (성공/실패 관계없이 항상 실행)
_endLoading();
},
success: function(data) {
console.log('조회 성공:', data.length + '건');
// Tabulator에 데이터 설정
if(_rightGrid){
_rightGrid.setData(data);
}
},
error: function(jqxhr, status, error){
console.error('Ajax 에러:', status, error);
Swal.fire('조회 중 오류가 발생했습니다.');
}
});
url: "/partMng/getPartMngList_ajax.do",
type: "POST",
data: $("#form1").serialize(),
dataType: "json",
beforeSend: function(){
// 로딩 스피너 표시
Swal.fire({
title: '조회 중...',
html: '품목 데이터를 불러오는 중입니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
if(_rightGrid){
_rightGrid.clearData();
}
},
success: function(response) {
// 로딩 스피너 닫기
Swal.close();
console.log('조회 완료:', response.data ? response.data.length + '건' : '0건');
// Tabulator에 데이터 설정
if(_rightGrid){
_rightGrid.setData(response.data || []);
}
// 결과 메시지
if(!response.data || response.data.length === 0){
Swal.fire({
icon: 'info',
title: '조회 결과 없음',
text: '검색 조건에 맞는 품목이 없습니다.'
});
} else {
// 성공 토스트 메시지
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: response.data.length + '건 조회 완료',
showConfirmButton: false,
timer: 2000
});
}
},
error: function(jqxhr, status, error){
// 로딩 스피너 닫기
Swal.close();
Swal.fire({
icon: 'error',
title: '조회 실패',
text: '데이터 조회 중 오류가 발생했습니다.'
});
console.error(error);
}
});
}
/**

View File

@@ -609,19 +609,30 @@ public class PartMngController {
* @param paramMap
* @return
*/
/**
* PART 목록 조회 (전체 데이터 조회 - 재귀 CTE 제거로 성능 개선)
* @param request
* @param paramMap
* @return JSON 형태의 전체 데이터
*/
@RequestMapping("/partMng/getPartMngList_ajax.do")
public String getPartList_ajax(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
@ResponseBody
public Map<String, Object> getPartList_ajax(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
paramMap.put("status", "complete");
System.out.println("getPArtList_ajax paramMap : "+paramMap);
paramMap.put("IS_LAST","1");
//paramMap.put("STATUS", "release");
System.out.println("getPartList_ajax paramMap : "+paramMap);
// 전체 데이터 조회 (페이징 제거, 재귀 CTE 제거로 속도 대폭 향상)
List list = partMngService.getToConnectPartMngList(request, paramMap);
// Gson을 사용하여 안전하게 JSON 변환 (특수문자, 이스케이프 처리 자동)
String jsonResult = JsonUtil.ListToJson(list);
request.setAttribute("RESULT", jsonResult);
return "/ajax/ajaxResult";
// 응답 데이터 구성
Map<String, Object> response = new HashMap<>();
response.put("data", list);
System.out.println("데이터 조회 완료 - " + list.size() + "");
return response;
}

View File

@@ -266,8 +266,22 @@
<!-- Part 관리 기본 조회 -->
<sql id="partMngBase">
(
WITH V_FILE AS (
SELECT
-- 파일 개수 집계 (성능 최적화)
WITH V_FILE_COUNTS AS (
SELECT
TARGET_OBJID,
COUNT(CASE WHEN DOC_TYPE = '3D_CAD' THEN 1 END) AS CU01_CNT,
COUNT(CASE WHEN DOC_TYPE = '2D_DRAWING_CAD' THEN 1 END) AS CU02_CNT,
COUNT(CASE WHEN DOC_TYPE = '2D_PDF_CAD' THEN 1 END) AS CU03_CNT,
COUNT(CASE WHEN DOC_TYPE IN ('2D_PDF_CAD', '2D_DRAWING_CAD') THEN 1 END) AS CU_TOTAL_CNT,
COUNT(CASE WHEN DOC_TYPE = 'ECD_DOC' THEN 1 END) AS ECD_CNT
FROM ATTACH_FILE_INFO
WHERE STATUS = 'Active'
AND DOC_TYPE IN ('PART_SHAPE_IMG','ECD_DOC','3D_CAD','2D_DRAWING_CAD','2D_PDF_CAD')
GROUP BY TARGET_OBJID
),
V_FILE AS (
SELECT DISTINCT ON (TARGET_OBJID, DOC_TYPE)
TARGET_OBJID
, SAVED_FILE_NAME
, REAL_FILE_NAME
@@ -276,8 +290,9 @@
, STATUS
FROM ATTACH_FILE_INFO
WHERE 1 = 1
AND DOC_TYPE IN ('PART_SHAPE_IMG','ECD_DOC','3D_CAD','2D_DRAWING_CAD','2D_PDF_CAD')
AND DOC_TYPE IN ('PART_SHAPE_IMG','ECD_DOC')
AND STATUS = 'Active'
ORDER BY TARGET_OBJID, DOC_TYPE, OBJID
)
SELECT DISTINCT
@@ -327,31 +342,17 @@
P.SOURCING_CODE,
CC_SOURCING.CODE_NAME AS SOURCING_NAME,
AF.SAVED_FILE_NAME,
AF.REAL_FILE_NAME,
REPLACE(AF.FILE_PATH, '\', '\\') AS FILE_PATH,
case when CAD.SAVED_FILE_NAME is NOT NULL
then 1
else 0 end as CU01_CNT,
case when DRAWING.SAVED_FILE_NAME is NOT NULL
then 1
else 0 end as CU02_CNT,
case when PDF.SAVED_FILE_NAME is NOT NULL
then 1
else 0 end as CU03_CNT,
case when PDFDRA.SAVED_FILE_NAME is NOT NULL
then 1
else 0 end as CU_TOTAL_CNT,
AF_IMG.SAVED_FILE_NAME,
AF_IMG.REAL_FILE_NAME,
REPLACE(AF_IMG.FILE_PATH, '\', '\\') AS FILE_PATH,
COALESCE(FC.CU01_CNT, 0) AS CU01_CNT,
COALESCE(FC.CU02_CNT, 0) AS CU02_CNT,
COALESCE(FC.CU03_CNT, 0) AS CU03_CNT,
COALESCE(FC.CU_TOTAL_CNT, 0) AS CU_TOTAL_CNT,
AF_ECD.SAVED_FILE_NAME AS ECD_SAVED_FILE_NAME,
AF_ECD.REAL_FILE_NAME AS ECD_REAL_FILE_NAME,
REPLACE(AF_ECD.FILE_PATH, '\', '\\') AS ECD_FILE_PATH,
CASE
WHEN AF_ECD.SAVED_FILE_NAME IS NOT NULL THEN 'Y'
ELSE 'N'
END ECD_FLAG,
CASE WHEN FC.ECD_CNT > 0 THEN 'Y' ELSE 'N' END AS ECD_FLAG,
THICKNESS,
WIDTH,
HEIGHT,
@@ -367,43 +368,15 @@
LEFT JOIN COMM_CODE CC_DESIGN ON CC_DESIGN.CODE_ID = P.DESIGN_APPLY_POINT
LEFT JOIN COMM_CODE CC_SOURCING ON CC_SOURCING.CODE_ID = P.SOURCING_CODE
LEFT JOIN admin_supply_mng SUP ON SUP.objid::varchar = P.SUPPLY_CODE
LEFT OUTER JOIN V_FILE AF
ON P.OBJID = AF.TARGET_OBJID
AND AF.DOC_TYPE IN ('PART_SHAPE_IMG')
<!--
AND AF.STATUS = 'Active'
-->
-- 파일 개수 집계 조인 (한 번만)
LEFT JOIN V_FILE_COUNTS FC ON P.OBJID = FC.TARGET_OBJID
-- 파일 정보 조인 (첫 번째만)
LEFT OUTER JOIN V_FILE AF_IMG
ON P.OBJID = AF_IMG.TARGET_OBJID
AND AF_IMG.DOC_TYPE = 'PART_SHAPE_IMG'
LEFT OUTER JOIN V_FILE AF_ECD
ON P.OBJID = AF_ECD.TARGET_OBJID
AND AF_ECD.DOC_TYPE IN ('ECD_DOC')
<!--
AND AF_ECD.STATUS = 'Active'
-->
LEFT OUTER JOIN V_FILE CAD
ON P.OBJID = CAD.TARGET_OBJID
AND CAD.DOC_TYPE IN ('3D_CAD')
<!--
AND CAD.STATUS = 'Active'
-->
LEFT OUTER JOIN V_FILE DRAWING
ON P.OBJID = DRAWING.TARGET_OBJID
AND DRAWING.DOC_TYPE IN ('2D_DRAWING_CAD')
<!--
AND DRAWING.STATUS = 'Active'
-->
LEFT OUTER JOIN V_FILE PDF
ON P.OBJID = PDF.TARGET_OBJID
AND PDF.DOC_TYPE IN ('2D_PDF_CAD')
<!--
AND PDF.STATUS = 'Active'
-->
LEFT OUTER JOIN V_FILE PDFDRA
ON P.OBJID = PDFDRA.TARGET_OBJID
AND PDFDRA.DOC_TYPE IN ('2D_PDF_CAD', '2D_DRAWING_CAD')
<!--
AND PDFDRA.STATUS = 'Active'
-->
AND AF_ECD.DOC_TYPE = 'ECD_DOC'
<!--
LEFT OUTER JOIN
@@ -2467,69 +2440,17 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
)A WHERE 1=1
</select> -->
<!-- PART 관리 목록 조회 -->
<!-- PART 관리 목록 조회 (전체 데이터, 재귀 CTE 제거로 성능 개선) -->
<select id="getToConnectPartMngList" parameterType="map" resultType="map">
WITH RECURSIVE VIEW_BOM(
OBJID,
PART_NO,
BOM_REPORT_OBJID,
PARENT_PART_NO,
QTY,
LEV,
PATH,
CYCLE
) AS (
SELECT
A.OBJID,
A.PART_NO,
A.BOM_REPORT_OBJID,
A.PARENT_PART_NO,
A.QTY,
1,
ARRAY [A.PART_NO],
FALSE
FROM
PART_BOM_QTY A
WHERE 1=1
AND (A.PARENT_PART_NO IS NULL OR A.PARENT_PART_NO = '')
AND A.BOM_REPORT_OBJID = #{bomReportObjId}::NUMERIC
UNION ALL
SELECT
B.OBJID,
B.PART_NO,
B.BOM_REPORT_OBJID,
B.PARENT_PART_NO,
B.QTY,
LEV + 1,
PATH,
B.PARENT_PART_NO = ANY(PATH)
FROM
PART_BOM_QTY B
JOIN
VIEW_BOM
ON B.PARENT_PART_NO = VIEW_BOM.PART_NO
AND VIEW_BOM.BOM_REPORT_OBJID = B.BOM_REPORT_OBJID
)
SELECT
T.*,
ROW_NUMBER() OVER(ORDER BY EXCEL_UPLOAD_SEQ ASC) RNUM
ROW_NUMBER() OVER(ORDER BY EXCEL_UPLOAD_SEQ ASC, PART_NO ASC) RNUM
FROM(
SELECT
T.*
FROM
<include refid="partMngBase"/> T
WHERE 1=1
<!-- AND IS_LAST = '1' -->
<!-- AND NOT EXISTS
(
SELECT
1
FROM VIEW_BOM V
WHERE 1=1
AND V.PART_NO = T.PART_NO
) -->
<!-- EO 기능 말들고 추가 필요 -->
<if test="search_eo != null and search_eo != ''">
AND UPPER(EO_NO) LIKE UPPER('%${search_eo}%')
@@ -2582,7 +2503,113 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
AND UPG_NO = #{search_product_mgmt_upg}
</if>
ORDER BY PART_NO
ORDER BY EXCEL_UPLOAD_SEQ ASC, PART_NO ASC
) T
</select>
<!-- PART 관리 목록 총 개수 조회 (페이징용) -->
<select id="getToConnectPartMngListCount" parameterType="map" resultType="int">
WITH RECURSIVE VIEW_BOM(
OBJID,
PART_NO,
BOM_REPORT_OBJID,
PARENT_PART_NO,
QTY,
LEV,
PATH,
CYCLE
) AS (
SELECT
A.OBJID,
A.PART_NO,
A.BOM_REPORT_OBJID,
A.PARENT_PART_NO,
A.QTY,
1,
ARRAY [A.PART_NO],
FALSE
FROM
PART_BOM_QTY A
WHERE 1=1
AND (A.PARENT_PART_NO IS NULL OR A.PARENT_PART_NO = '')
AND A.BOM_REPORT_OBJID = #{bomReportObjId}::NUMERIC
UNION ALL
SELECT
B.OBJID,
B.PART_NO,
B.BOM_REPORT_OBJID,
B.PARENT_PART_NO,
B.QTY,
LEV + 1,
PATH,
B.PARENT_PART_NO = ANY(PATH)
FROM
PART_BOM_QTY B
JOIN
VIEW_BOM
ON B.PARENT_PART_NO = VIEW_BOM.PART_NO
AND VIEW_BOM.BOM_REPORT_OBJID = B.BOM_REPORT_OBJID
)
SELECT
COUNT(*)
FROM(
SELECT
T.OBJID
FROM
<include refid="partMngBase"/> T
WHERE 1=1
<if test="search_eo != null and search_eo != ''">
AND UPPER(EO_NO) LIKE UPPER('%${search_eo}%')
</if>
<if test="search_except_eo != null and search_except_eo != ''">
AND EO != #{search_except_eo}
AND (EO IS NULL OR EO = '' OR EO = '0')
</if>
<if test="search_eo_date_from != null and search_eo_date_from != ''">
AND EO_DATE <![CDATA[ >= ]]> #{search_eo_date_from}::TIMESTAMP
</if>
<if test="search_eo_date_to != null and search_eo_date_to != ''">
AND EO_DATE <![CDATA[ <= ]]> #{search_eo_date_to}::TIMESTAMP
</if>
<if test="search_product_mgmt_objid != null and search_product_mgmt_objid != ''">
AND PRODUCT_MGMT_OBJID = #{search_product_mgmt_objid}
</if>
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(PART_NO) LIKE UPPER('%${search_part_no}%')
</if>
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(PART_NAME) LIKE UPPER('%${search_part_name}%')
</if>
<if test="search_spec != null and search_spec != ''">
AND UPPER(SPEC) LIKE UPPER('%${search_spec}%')
</if>
<if test="search_maker != null and search_maker != ''">
AND UPPER(MAKER) LIKE UPPER('%${search_maker}%')
</if>
<if test="search_writer != null and search_writer != ''">
AND WRITER = #{search_writer}
</if>
<if test="IS_LAST != null and IS_LAST != ''">
AND IS_LAST = #{IS_LAST}
</if>
<if test="STATUS != null and STATUS != ''">
AND STATUS = #{STATUS}
</if>
<if test="searchTargetStatus != null and searchTargetStatus != '' and 'create'.equals(searchTargetStatus)">
AND STATUS = 'create'
</if>
<if test="searchTargetStatus != null and searchTargetStatus != '' and 'deploy'.equals(searchTargetStatus)">
AND STATUS = 'release'
</if>
<if test="searchTargetStatus != null and searchTargetStatus != '' and 'changeDesign'.equals(searchTargetStatus)">
AND STATUS = 'release'
</if>
<if test="search_product_mgmt_upg != null and !''.equals(search_product_mgmt_upg)">
AND UPG_NO = #{search_product_mgmt_upg}
</if>
) T
</select>

View File

@@ -1538,6 +1538,28 @@ public class PartMngService extends BaseService {
return CommonUtils.toUpperCaseMapKey(resultList);
}
/**
* PART 목록 총 개수 조회 (페이징용)
*/
public int getToConnectPartMngListCount(HttpServletRequest request, Map paramMap){
int totalCount = 0;
SqlSession sqlSession = null;
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession();
setPartMngCommonCD(paramMap);
totalCount = (Integer)sqlSession.selectOne("partMng.getToConnectPartMngListCount", paramMap);
}catch(Exception e){
e.printStackTrace();
}finally{
sqlSession.close();
}
return totalCount;
}
/**