Compare commits

...

16 Commits

Author SHA1 Message Date
d9b53e6c96 Merge pull request '부품품명, 부품품번, 생산일, 판매일 주석처리' (#118) from V2025121901 into main
Reviewed-on: #118
2026-01-07 09:28:27 +00:00
1265bcde17 부품품명, 부품품번, 생산일, 판매일 주석처리 2026-01-07 18:28:14 +09:00
036de8767e Merge pull request '품질관리_260107_CS업무플로우(최신)수정사항2' (#117) from V2025121901 into main
Reviewed-on: #117
2026-01-07 09:23:44 +00:00
ef4f617f66 품질관리_260107_CS업무플로우(최신)수정사항2 2026-01-07 18:23:27 +09:00
4f08ba1c01 Merge pull request 'V2025121901' (#116) from V2025121901 into main
Reviewed-on: #116
2026-01-07 06:07:07 +00:00
799ea4faf7 개발관리_설계변경리스트 2026-01-07 14:49:27 +09:00
2d836456c6 mbom변경이력 수정한 사람 id -> name으로 변경 2026-01-07 10:40:26 +09:00
5836f8d571 Merge pull request '검색영역 수정' (#115) from V2025121901 into main
Reviewed-on: #115
2025-12-30 09:21:36 +00:00
4bbd0c1e4e 검색영역 수정 2025-12-30 18:20:51 +09:00
5df50b7247 Merge pull request 'V2025121901' (#114) from V2025121901 into main
Reviewed-on: #114
2025-12-30 02:51:24 +00:00
5959604464 Merge remote-tracking branch 'origin/main' into V2025121901 2025-12-30 11:50:20 +09:00
b53b20945b mbom 복사 시 프로젝트의 파트를 1레벨로 2025-12-30 11:48:15 +09:00
leeheejin
001f38d0f3 1번 2번 수정 완료 2025-12-30 09:38:18 +09:00
ba09a208cf ebom 등록 시 품번, 품명 입력 -> 1레벨 자동 입력으로 변경 2025-12-29 18:14:04 +09:00
leeheejin
451b16f051 시리얼넘버 생성되도록 수정 2025-12-29 15:31:44 +09:00
leeheejin
122ba638a6 엑셀다운로드 수정 완료 2025-12-29 11:06:39 +09:00
23 changed files with 1638 additions and 496 deletions

View File

@@ -3958,12 +3958,11 @@
ORDER BY REGDATE DESC, MBOM_NO
</select>
<!-- M-BOM 목록 (bizMakeOptionList용) -->
<!-- M-BOM 목록 (bizMakeOptionList용) - (E-BOM) 텍스트 제거 -->
<select id="getMbomListForSelect2" parameterType="map" resultType="map">
SELECT
OBJID AS CODE,
COALESCE(MBOM_NO, '') ||
CASE WHEN SOURCE_BOM_TYPE IS NOT NULL AND SOURCE_BOM_TYPE != '' THEN ' (' || SOURCE_BOM_TYPE || ')' ELSE '' END AS NAME
COALESCE(MBOM_NO, '') AS NAME
FROM MBOM_HEADER
WHERE STATUS = 'Y'
ORDER BY REGDATE DESC, MBOM_NO

View File

@@ -1358,30 +1358,32 @@
DELETE FROM PROCESS_INSPECTION_DETAIL WHERE MASTER_OBJID = #{MASTER_OBJID}
</delete>
<!-- 공정검사 목록 조회 (엑셀용) - 상세 정보 포함 -->
<!-- 공정검사 목록 조회 (엑셀용) - 상세 정보 포함 (디테일 기준) -->
<select id="getProcessInspectionListForExcel" parameterType="map" resultType="map">
SELECT
/* 공정검사 마스터 정보 */
PIM.INSPECTION_DATE
, (SELECT U.USER_NAME FROM USER_INFO U WHERE U.USER_ID = PIM.INSPECTOR_ID) AS INSPECTOR_NAME
, PIM.REMARK AS MASTER_REMARK
/* 검사일, 검사자는 디테일에서 가져옴 */
COALESCE(PID.INSPECTION_DATE, '') AS INSPECTION_DATE
, COALESCE((SELECT U.USER_NAME FROM USER_INFO U WHERE U.USER_ID = PID.INSPECTOR_ID), '') AS INSPECTOR_NAME
, COALESCE((SELECT CODE_NAME(PM.PRODUCT) FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID), '') AS PRODUCT_TYPE
/* 공정검사 디테일 정보 */
, (SELECT CC.CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PID.PROCESS_CD) AS PROCESS_NAME
, (SELECT PM.PROJECT_NO FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID) AS PROJECT_NO
, PID.PART_NO
, PID.PART_NAME
, PID.INSPECTION_QTY
, PID.DEFECT_QTY
, COALESCE((SELECT CC.CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PID.PROCESS_CD), '') AS PROCESS_NAME
, COALESCE((SELECT PM.PROJECT_NO FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID), '') AS PROJECT_NO
, COALESCE(PID.PART_NO, '') AS PART_NO
, COALESCE(PID.PART_NAME, '') AS PART_NAME
, COALESCE(PID.INSPECTION_QTY, 0) AS INSPECTION_QTY
, COALESCE(PID.DEFECT_QTY, 0) AS DEFECT_QTY
, CASE WHEN COALESCE(PID.INSPECTION_QTY, 0) > 0
THEN ROUND((COALESCE(PID.DEFECT_QTY, 0)::NUMERIC / PID.INSPECTION_QTY::NUMERIC) * 100, 2)
ELSE NULL END AS DEFECT_RATE
, PID.WORK_ENV_STATUS
, PID.MEASURING_DEVICE
, (SELECT DEPT_NAME FROM DEPT_INFO WHERE DEPT_CODE = PID.DEPT_CD) AS DEPT_NAME
, (SELECT U2.USER_NAME FROM USER_INFO U2 WHERE U2.USER_ID = PID.USER_ID) AS USER_NAME
, PID.REMARK AS DETAIL_REMARK
, PID.ACTION_STATUS
, PID.INSPECTION_RESULT
ELSE 0 END AS DEFECT_RATE
, COALESCE(PID.WORK_ENV_STATUS, '') AS WORK_ENV_STATUS
, COALESCE(PID.MEASURING_DEVICE, '') AS MEASURING_DEVICE
, COALESCE((SELECT DI.DEPT_NAME FROM DEPT_INFO DI WHERE DI.DEPT_CODE = PID.DEPT_CD), '') AS DEPT_NAME
, COALESCE((SELECT U2.USER_NAME FROM USER_INFO U2 WHERE U2.USER_ID = PID.USER_ID), '') AS USER_NAME
, COALESCE(PID.REMARK, '') AS DETAIL_REMARK
, COALESCE(PID.ACTION_STATUS, '') AS ACTION_STATUS
, COALESCE(NULLIF(PID.INSPECTION_RESULT, ''),
(SELECT CASE WHEN COUNT(CASE WHEN PID2.INSPECTION_RESULT = 'NG' THEN 1 END) > 0 THEN 'NG' ELSE 'OK' END
FROM PROCESS_INSPECTION_DETAIL PID2 WHERE PID2.MASTER_OBJID = PIM.OBJID), '') AS INSPECTION_RESULT
FROM PROCESS_INSPECTION_MASTER PIM
INNER JOIN PROCESS_INSPECTION_DETAIL PID ON PID.MASTER_OBJID = PIM.OBJID
WHERE 1=1
@@ -1396,15 +1398,15 @@
</if>
<if test="search_inspector != null and search_inspector != ''">
AND (
UPPER(PIM.INSPECTOR_ID) LIKE UPPER('%' || #{search_inspector} || '%')
OR UPPER((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = PIM.INSPECTOR_ID)) LIKE UPPER('%' || #{search_inspector} || '%')
UPPER(PID.INSPECTOR_ID) LIKE UPPER('%' || #{search_inspector} || '%')
OR UPPER((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = PID.INSPECTOR_ID)) LIKE UPPER('%' || #{search_inspector} || '%')
)
</if>
<if test="search_inspection_date_from != null and search_inspection_date_from != ''">
AND PIM.INSPECTION_DATE <![CDATA[>=]]> #{search_inspection_date_from}
AND PID.INSPECTION_DATE <![CDATA[>=]]> #{search_inspection_date_from}
</if>
<if test="search_inspection_date_to != null and search_inspection_date_to != ''">
AND PIM.INSPECTION_DATE <![CDATA[<=]]> #{search_inspection_date_to}
AND PID.INSPECTION_DATE <![CDATA[<=]]> #{search_inspection_date_to}
</if>
<if test="search_inspection_result != null and search_inspection_result != ''">
AND PID.INSPECTION_RESULT = #{search_inspection_result}
@@ -1540,96 +1542,62 @@
ORDER BY MIN(T.INSPECTION_DATE) DESC, T.INSPECTION_GROUP_ID
</select>
<!-- 반제품검사 엑셀 다운로드용 데이터 조회 (그룹화 후 쉼표로 나열) -->
<!-- 반제품검사 엑셀 다운로드용 데이터 조회 (전체 풀어서 개별 행으로) -->
<select id="getSemiProductInspectionListForExcel" parameterType="map" resultType="map">
SELECT T.INSPECTION_GROUP_ID
, MIN(T.OBJID) AS OBJID
<!-- 품명(모델명): 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.MODEL_NAME, ''), ', ' ORDER BY NULLIF(T.MODEL_NAME, '')) AS MODEL_NAME
<!-- 제품구분: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PRODUCT_TYPE, ''), ', ' ORDER BY NULLIF(T.PRODUCT_TYPE, '')) AS PRODUCT_TYPE
<!-- 작업지시번호: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.WORK_ORDER_NO, ''), ', ' ORDER BY NULLIF(T.WORK_ORDER_NO, '')) AS WORK_ORDER_NO
<!-- 부품품번: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PART_NO, ''), ', ' ORDER BY NULLIF(T.PART_NO, '')) AS PART_NO
<!-- 부품명: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PART_NAME, ''), ', ' ORDER BY NULLIF(T.PART_NAME, '')) AS PART_NAME
<!-- 수량 집계 -->
, SUM(T.RECEIPT_QTY) AS RECEIPT_QTY
, SUM(T.GOOD_QTY) AS GOOD_QTY
, SUM(T.DEFECT_QTY) AS DEFECT_QTY
SELECT SPI.OBJID
, COALESCE(TO_CHAR(SPI.INSPECTION_DATE, 'YYYY-MM-DD'), TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD')) AS INSPECTION_DATE
, COALESCE((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.INSPECTOR),
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.WRITER), '') AS WRITER_NAME
, COALESCE(SPI.MODEL_NAME, '') AS MODEL_NAME
, COALESCE(SPI.PRODUCT_TYPE, '') AS PRODUCT_TYPE
, COALESCE(SPI.WORK_ORDER_NO, '') AS WORK_ORDER_NO
, COALESCE(SPI.PART_NO, '') AS PART_NO
, COALESCE(SPI.PART_NAME, '') AS PART_NAME
, COALESCE(SPI.RECEIPT_QTY, 0) AS RECEIPT_QTY
, COALESCE(SPI.GOOD_QTY, 0) AS GOOD_QTY
, COALESCE(SPI.DEFECT_QTY, 0) AS DEFECT_QTY
, CASE
WHEN SUM(T.RECEIPT_QTY) > 0
THEN ROUND(SUM(T.DEFECT_QTY) * 100.0 / SUM(T.RECEIPT_QTY), 2)
WHEN COALESCE(SPI.RECEIPT_QTY, 0) > 0
THEN ROUND(COALESCE(SPI.DEFECT_QTY, 0) * 100.0 / SPI.RECEIPT_QTY, 2)
ELSE 0
END AS DEFECT_RATE
, SUM(CASE WHEN T.DISPOSITION_TYPE = '수정' THEN T.DEFECT_QTY ELSE 0 END) AS REGENERATION_QTY
, SUM(T.GOOD_QTY) + SUM(CASE WHEN T.DISPOSITION_TYPE = '수정' THEN T.DEFECT_QTY ELSE 0 END) AS FINAL_GOOD_QTY
<!-- 검사일: 쉼표로 나열 -->
, STRING_AGG(DISTINCT T.INSPECTION_DATE_STR, ', ' ORDER BY T.INSPECTION_DATE_STR) AS INSPECTION_DATE
<!-- 검사자: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.WRITER_NAME, ''), ', ' ORDER BY NULLIF(T.WRITER_NAME, '')) AS WRITER_NAME
<!-- 불량유형: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DEFECT_TYPE, ''), ', ' ORDER BY NULLIF(T.DEFECT_TYPE, '')) AS DEFECT_TYPE
<!-- 불량원인: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DEFECT_CAUSE, ''), ', ' ORDER BY NULLIF(T.DEFECT_CAUSE, '')) AS DEFECT_CAUSE
<!-- 귀책부서: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.RESPONSIBLE_DEPT, ''), ', ' ORDER BY NULLIF(T.RESPONSIBLE_DEPT, '')) AS RESPONSIBLE_DEPT
<!-- 처리현황: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PROCESS_STATUS, ''), ', ' ORDER BY NULLIF(T.PROCESS_STATUS, '')) AS PROCESS_STATUS
<!-- 처리결과: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DISPOSITION_TYPE, ''), ', ' ORDER BY NULLIF(T.DISPOSITION_TYPE, '')) AS DISPOSITION_TYPE
<!-- 비고: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.REMARK, ''), ', ' ORDER BY NULLIF(T.REMARK, '')) AS REMARK
FROM (
SELECT SPI.OBJID
, COALESCE(SPI.INSPECTION_GROUP_ID, SPI.OBJID::VARCHAR) AS INSPECTION_GROUP_ID
, COALESCE(SPI.MODEL_NAME, '') AS MODEL_NAME
, COALESCE(SPI.PRODUCT_TYPE, '') AS PRODUCT_TYPE
, COALESCE(SPI.WORK_ORDER_NO, '') AS WORK_ORDER_NO
, COALESCE(SPI.PART_NO, '') AS PART_NO
, COALESCE(SPI.PART_NAME, '') AS PART_NAME
, COALESCE(SPI.RECEIPT_QTY, 0) AS RECEIPT_QTY
, COALESCE(SPI.GOOD_QTY, 0) AS GOOD_QTY
, COALESCE(SPI.DEFECT_QTY, 0) AS DEFECT_QTY
, COALESCE(TO_CHAR(SPI.INSPECTION_DATE, 'YYYY-MM-DD'), TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD')) AS INSPECTION_DATE_STR
, COALESCE((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.INSPECTOR),
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.WRITER), '') AS WRITER_NAME
, COALESCE(SPI.DEFECT_TYPE, '') AS DEFECT_TYPE
, COALESCE(SPI.DEFECT_CAUSE, '') AS DEFECT_CAUSE
, COALESCE(SPI.RESPONSIBLE_DEPT, '') AS RESPONSIBLE_DEPT
, COALESCE(SPI.PROCESS_STATUS, '') AS PROCESS_STATUS
, COALESCE(SPI.DISPOSITION_TYPE, '') AS DISPOSITION_TYPE
, COALESCE(SPI.REMARK, '') AS REMARK
FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION SPI
WHERE 1=1
<!-- 품명(모델명) 검색 -->
<if test="search_model_name != null and search_model_name != ''">
AND UPPER(SPI.MODEL_NAME) LIKE UPPER('%' || #{search_model_name} || '%')
</if>
<!-- 작업지시번호 검색 -->
<if test="search_work_order_no != null and search_work_order_no != ''">
AND UPPER(SPI.WORK_ORDER_NO) LIKE UPPER('%' || #{search_work_order_no} || '%')
</if>
<!-- 부품품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(SPI.PART_NO) LIKE UPPER('%' || #{search_part_no} || '%')
</if>
<!-- 부품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(SPI.PART_NAME) LIKE UPPER('%' || #{search_part_name} || '%')
</if>
<!-- 검사일 검색 -->
<if test="search_inspection_date != null and search_inspection_date != ''">
AND TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD') = #{search_inspection_date}
</if>
<!-- 검사자 검색 -->
<if test="search_writer != null and search_writer != ''">
AND SPI.WRITER = #{search_writer}
</if>
) T
GROUP BY T.INSPECTION_GROUP_ID
ORDER BY MIN(T.INSPECTION_DATE_STR) DESC, T.INSPECTION_GROUP_ID
, CASE WHEN SPI.DISPOSITION_TYPE = '수정' THEN COALESCE(SPI.DEFECT_QTY, 0) ELSE 0 END AS REGENERATION_QTY
, COALESCE(SPI.GOOD_QTY, 0) + CASE WHEN SPI.DISPOSITION_TYPE = '수정' THEN COALESCE(SPI.DEFECT_QTY, 0) ELSE 0 END AS FINAL_GOOD_QTY
, COALESCE(SPI.DEFECT_TYPE, '') AS DEFECT_TYPE
, COALESCE(SPI.DEFECT_CAUSE, '') AS DEFECT_CAUSE
, COALESCE(SPI.RESPONSIBLE_DEPT, '') AS RESPONSIBLE_DEPT
, COALESCE(SPI.PROCESS_STATUS, '') AS PROCESS_STATUS
, COALESCE(SPI.DISPOSITION_TYPE, '') AS DISPOSITION_TYPE
, COALESCE(SPI.REMARK, '') AS REMARK
, COALESCE(SPI.DATA_TYPE, '') AS DATA_TYPE
, COALESCE(SPI.INSPECTION_GROUP_ID::VARCHAR, '') AS INSPECTION_GROUP_ID
FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION SPI
WHERE 1=1
<!-- 품명(모델명) 검색 -->
<if test="search_model_name != null and search_model_name != ''">
AND UPPER(SPI.MODEL_NAME) LIKE UPPER('%' || #{search_model_name} || '%')
</if>
<!-- 작업지시번호 검색 -->
<if test="search_work_order_no != null and search_work_order_no != ''">
AND UPPER(SPI.WORK_ORDER_NO) LIKE UPPER('%' || #{search_work_order_no} || '%')
</if>
<!-- 부품품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(SPI.PART_NO) LIKE UPPER('%' || #{search_part_no} || '%')
</if>
<!-- 부품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(SPI.PART_NAME) LIKE UPPER('%' || #{search_part_name} || '%')
</if>
<!-- 검사일 검색 -->
<if test="search_inspection_date != null and search_inspection_date != ''">
AND TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD') = #{search_inspection_date}
</if>
<!-- 검사자 검색 -->
<if test="search_writer != null and search_writer != ''">
AND SPI.WRITER = #{search_writer}
</if>
ORDER BY SPI.INSPECTION_DATE DESC, SPI.REG_DATE DESC
</select>
<!-- 반제품검사 품명(모델명) 드롭박스 목록 (CODE, NAME 형태) -->

View File

@@ -433,30 +433,41 @@ var gridFn = {
url: "/partMng/parsingExcelFile.do"
,datatype : "json"
,postData:{"targetObjId":"${objid}","docType":"PART_EXCEL_IMPORT","OBJID":"${CONTRACT_OBJID}"}
,loadComplete : function(data) {
// CSV 파일에서만 LEVEL 값을 PARENT_PART_NO 컬럼에 표시
if(data && data.rows) {
for(var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
// CSV 파일이고 LEVEL 값이 있는 경우
if(row.IS_CSV === 'Y' && row.LEVEL && row.LEVEL !== '') {
// LEVEL 값을 PARENT_PART_NO 컬럼에 표시 (화면용)
grid.jqGrid('setCell', row.id, 'PARENT_PART_NO', row.LEVEL);
}
,loadComplete : function(data) {
gridFn.footerSummary();
}
}
gridFn.footerSummary();
}
,gridComplete : function() {
//gridFn.opennEdit(); //수정가능
var valid = true;
gridFn.closeEdit();
//var tempGrid = $('#expenseDetailGrid');
// 1번 레벨(PARENT_PART_NO가 비어있는 행)의 품번/품명을 헤더에 자동 설정
var isFirstLevelSet = false;
$.each(grid.getRowData(), function(i, d){
console.log(d);
console.log("Row data:", d);
// NOTE 검증
if(!fnc_isEmpty(d["NOTE"])){
valid = false;
}
// PARENT_PART_NO가 비어있으면 1번 레벨 (최상위)
var parentPartNo = d["PARENT_PART_NO"] || '';
if(!isFirstLevelSet && parentPartNo === '') {
var partNo = d["PART_NO"] || '';
var partName = d["PART_NAME"] || '';
console.log("Level 1 found - PART_NO:", partNo, ", PART_NAME:", partName);
// 새로 CSV 업로드 시 항상 덮어쓰기
if(partNo !== '') {
$('#bom_part_no').val(partNo);
}
if(partName !== '') {
$('#bom_part_name').val(partName);
}
isFirstLevelSet = true;
}
});
gridFn.opennEdit();
@@ -967,11 +978,11 @@ function fn_save(){
</td>
<td style="font-size:12px;" class="align_c"><label for="bom_part_no">품번</label></td>
<td>
<input type="text" name="bom_part_no" id="bom_part_no" required reqTitle="품번" value="<%= bomPartNo %>" style="width: 100%;"/>
<input type="text" name="bom_part_no" id="bom_part_no" required reqTitle="품번" value="<%= bomPartNo %>" style="width: 100%;" readonly/>
</td>
<td style="font-size:12px;" class="align_c"><label for="bom_part_name">품명</label></td>
<td>
<input type="text" name="bom_part_name" id="bom_part_name" required reqTitle="품명" value="<%= bomPartName %>" style="width: 200px;"/>
<input type="text" name="bom_part_name" id="bom_part_name" required reqTitle="품명" value="<%= bomPartName %>" style="width: 200px;" readonly/>
</td>
</tr>
<tr>

View File

@@ -524,11 +524,12 @@ function generateHistoryHtml(historyList) {
var changeType = history.CHANGE_TYPE || "UNKNOWN";
var changeDate = history.CHANGE_DATE || history.REGDATE || "";
var changeUser = history.CHANGE_USER || "";
var changeUserName = history.CHANGE_USER_NAME || "";
// 이력 헤더
html += "<div style='margin-bottom: 40px;'>";
html += "<h3 style='background-color: #333; color: white; padding: 10px;'>";
html += "이력 #" + (i + 1) + " - " + changeType + " (" + changeDate + " / " + changeUser + ")";
html += "이력 #" + (i + 1) + " - " + changeType + " (" + changeDate + " / " + changeUserName + ")";
html += "</h3>";
try {
@@ -949,8 +950,7 @@ function compareItemFields(before, after) {
<td>
<input type="text" name="search_part_name" id="search_part_name" style="width: 300px;" value="" readonly>
</td>
</tr>
<tr>
<td><label for="search_mbom_part_no">M-BOM 품번</label></td>
<td>
<input type="text" name="search_mbom_part_no" id="search_mbom_part_no" style="width: 300px;" value="" readonly>

View File

@@ -0,0 +1,294 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%> - M-BOM 변경 이력 상세</title>
<style>
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
.history-container {
padding: 20px;
}
h2 {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.history-header {
background-color: #333;
color: white;
padding: 10px 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.history-header span {
margin-right: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #4CAF50;
color: white;
font-weight: bold;
}
.section-title {
background-color: #5bc0de;
color: white;
font-weight: bold;
text-align: center;
}
.change-add {
background-color: #e7f4e7;
}
.change-delete {
background-color: #ffe7e7;
}
.change-modify {
background-color: #fff4e6;
}
.before-value {
color: #999;
text-decoration: line-through;
}
.after-value {
color: #d9534f;
font-weight: bold;
}
.btn-close {
display: block;
width: 100px;
margin: 20px auto;
padding: 10px 20px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-close:hover {
background-color: #5a6268;
}
</style>
</head>
<body>
<div class="history-container">
<h2>M-BOM 변경 이력 상세</h2>
<div class="history-header">
<span><strong>변경유형:</strong> ${historyInfo.CHANGE_TYPE}</span>
<span><strong>변경일:</strong> ${historyInfo.CHANGE_DATE}</span>
<span><strong>작성자:</strong> ${historyInfo.CHANGE_USER_NAME}</span>
<span><strong>프로젝트:</strong> ${historyInfo.PROJECT_NO}</span>
</div>
<div id="historyContent">
<!-- JavaScript로 동적 생성 -->
</div>
<button class="btn-close" onclick="window.close();">닫기</button>
</div>
<script>
// 가공업체 목록 (OBJID -> 업체명 변환용)
var vendorMap = {};
// 가공업체 목록 로드
function loadVendorList() {
$.ajax({
url: '/common/getClientMngList.do',
method: 'POST',
async: false,
dataType: 'json',
success: function(data) {
if(data && data.length > 0) {
data.forEach(function(item) {
vendorMap[item.CODE_ID] = item.NAME;
});
}
}
});
}
// OBJID를 업체명으로 변환
function getVendorName(objid) {
if(!objid) return '';
return vendorMap[objid] || objid;
}
// 필드명 한글 변환
function getFieldNameKorean(field) {
var fieldMap = {
"QTY": "수량",
"SUPPLY_TYPE": "자급/사급",
"RAW_MATERIAL": "소재",
"RAW_MATERIAL_SIZE": "사이즈",
"RAW_MATERIAL_PART_NO": "소재품번",
"PROCESSING_VENDOR": "가공업체",
"PROCESSING_DEADLINE": "가공납기",
"GRINDING_DEADLINE": "연삭납기",
"REQUIRED_QTY": "소재소요량",
"ORDER_QTY": "소재발주수량",
"PRODUCTION_QTY": "제작수량",
"REMARK": "비고"
};
return fieldMap[field] || field;
}
$(function(){
// 가공업체 목록 로드
loadVendorList();
// 이력 데이터 파싱 및 표시
var changeType = '${historyInfo.CHANGE_TYPE}';
var beforeDataStr = '${historyInfo.BEFORE_DATA_ESCAPED}';
var afterDataStr = '${historyInfo.AFTER_DATA_ESCAPED}';
try {
var beforeData = beforeDataStr ? JSON.parse(beforeDataStr) : null;
var afterData = afterDataStr ? JSON.parse(afterDataStr) : null;
var html = "";
if(changeType === "CREATE") {
// 생성 이력
html = generateCreateHistoryTable(afterData);
} else if(changeType === "UPDATE") {
// 수정 이력
html = generateUpdateHistoryTable(beforeData, afterData);
}
$("#historyContent").html(html);
} catch(e) {
console.error("JSON 파싱 오류:", e);
$("#historyContent").html("<p style='color: red;'>데이터 파싱 오류가 발생했습니다.</p>");
}
});
// 생성 이력 테이블 생성
function generateCreateHistoryTable(afterData) {
var html = "<table>";
html += "<tr class='section-title'><td colspan='4'>생성된 M-BOM 정보</td></tr>";
html += "<tr><th>항목</th><th>값</th><th>항목</th><th>값</th></tr>";
// 헤더 정보
html += "<tr>";
html += "<td>M-BOM 품번</td><td>" + (afterData.mbomNo || afterData.MBOM_NO || "") + "</td>";
html += "<td>품번</td><td>" + (afterData.partNo || afterData.PART_NO || "") + "</td>";
html += "</tr>";
html += "<tr>";
html += "<td>품명</td><td>" + (afterData.partName || afterData.PART_NAME || "") + "</td>";
html += "<td>기준 BOM 유형</td><td>" + (afterData.sourceBomType || afterData.SOURCE_BOM_TYPE || "") + "</td>";
html += "</tr>";
// BOM 상세 항목
var mbomData = afterData.mbomData || [];
if(mbomData.length > 0) {
html += "<tr class='section-title'><td colspan='4'>생성된 BOM 항목 (" + mbomData.length + "개)</td></tr>";
html += "<tr class='change-add'><th>품번</th><th>품명</th><th>수량</th><th>레벨</th></tr>";
for(var i = 0; i < mbomData.length; i++) {
var item = mbomData[i];
html += "<tr class='change-add'>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>" + (item.qty || "") + "</td>";
html += "<td>" + (item.level || "") + "</td>";
html += "</tr>";
}
}
html += "</table>";
return html;
}
// 수정 이력 테이블 생성
function generateUpdateHistoryTable(beforeData, afterData) {
var html = "";
// beforeData는 변경된 항목 배열
var changedItems = beforeData;
if(!Array.isArray(changedItems) || changedItems.length === 0) {
return "<p>변경 사항이 없습니다.</p>";
}
html += "<table>";
html += "<tr class='section-title'><td colspan='4'>변경된 항목 (" + changedItems.length + "개)</td></tr>";
html += "<tr><th>변경유형</th><th>품번</th><th>품명</th><th>변경 내용</th></tr>";
for(var i = 0; i < changedItems.length; i++) {
var item = changedItems[i];
var changeType = item.changeType;
if(changeType === "ADD") {
// 추가된 항목
var afterDataItem = item.afterData || {};
html += "<tr class='change-add'>";
html += "<td>추가</td>";
html += "<td>" + (afterDataItem.partNo || "") + "</td>";
html += "<td>" + (afterDataItem.partName || "") + "</td>";
html += "<td>새 항목 추가</td>";
html += "</tr>";
} else if(changeType === "DELETE") {
// 삭제된 항목
var beforeDataItem = item.beforeData || {};
html += "<tr class='change-delete'>";
html += "<td>삭제</td>";
html += "<td>" + (beforeDataItem.PART_NO || "") + "</td>";
html += "<td>" + (beforeDataItem.PART_NAME || "") + "</td>";
html += "<td>항목 삭제</td>";
html += "</tr>";
} else if(changeType === "MODIFY") {
// 수정된 항목
html += "<tr class='change-modify'>";
html += "<td>수정</td>";
html += "<td>" + (item.partNo || "") + "</td>";
html += "<td>" + (item.partName || "") + "</td>";
html += "<td>";
var fieldChanges = item.fieldChanges || [];
for(var j = 0; j < fieldChanges.length; j++) {
var fc = fieldChanges[j];
var fieldNameKr = getFieldNameKorean(fc.field);
// 가공업체 필드는 OBJID를 업체명으로 변환
var beforeVal = fc.before;
var afterVal = fc.after;
if(fc.field === 'PROCESSING_VENDOR' || fc.field === 'processingVendor') {
beforeVal = beforeVal ? getVendorName(beforeVal) : null;
afterVal = afterVal ? getVendorName(afterVal) : null;
}
html += "<strong>" + fieldNameKr + ":</strong> ";
html += "<span class='before-value'>" + (beforeVal || "(없음)") + "</span> → ";
html += "<span class='after-value'>" + (afterVal || "(없음)") + "</span>";
if(j < fieldChanges.length - 1) html += "<br>";
}
html += "</td>";
html += "</tr>";
}
}
html += "</table>";
return html;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,207 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<c:set var="now" value="<%=new java.util.Date() %>"/>
<c:set var="sysYear"><fmt:formatDate value="${now}" pattern="yyyy" /></c:set>
<%
// DB에서 메뉴명 조회 (공통 유틸 사용)
String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "생산관리_M-BOM 변경이력");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
overflow-x: hidden;
width: 100%;
margin: 0;
padding: 0;
}
</style>
<script type="text/javascript" src="/js/tabulator/tabulator_custom.js"></script>
</head>
<body>
<script type="text/javascript">
// Tabulator 그리드 전역 변수
var _tabulGrid;
$(document).ready(function(){
_fnc_datepick(); // 날짜 선택기 초기화
$('.select2').select2(); // select2 초기화
// Enter 키로 검색
$("input").keyup(function(e) {
if (e.keyCode == 13) {
fn_search();
}
});
// 조회 버튼
$("#btnSearch").click(function(){
fn_search();
});
// 초기 조회
fn_search();
});
// 날짜 선택기 초기화 함수
function _fnc_datepick(){
var $dateinput = $("input.date_icon");
for(var i=0; i<$dateinput.length; i++){
$dateinput.eq(i).attr("size","10");
$dateinput.eq(i).datepicker({
changeMonth:true,
changeYear:true
});
}
}
// 그리드 컬럼 정의 - M-BOM 변경 이력 목록 (MBOM_HISTORY 기준)
var columns = [
{title:'OBJID', field:'OBJID', visible: false},
{title:'PROJECT_OBJID', field:'PROJECT_OBJID', visible: false},
{title:'MBOM_HEADER_OBJID', field:'MBOM_HEADER_OBJID', visible: false},
{title:'HISTORY_OBJID', field:'HISTORY_OBJID', visible: false},
// 프로젝트번호
{headerHozAlign: 'center', hozAlign: 'left', title: '프로젝트번호', field: 'PROJECT_NO', width: 120,
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell) {
fn_openSaleRegPopup(cell.getData().PROJECT_NO, "detail");
}
},
// 주문유형
{headerHozAlign: 'center', hozAlign: 'center', title: '주문유형', field: 'CATEGORY_NAME', width: 80},
// 제품구분
{headerHozAlign: 'center', hozAlign: 'center', title: '제품구분', field: 'PRODUCT_NAME', width: 80},
// 국내/해외
{headerHozAlign: 'center', hozAlign: 'center', title: '국내/해외', field: 'AREA_NAME', width: 90},
// 접수일
{headerHozAlign: 'center', hozAlign: 'center', title: '접수일', field: 'RECEIPT_DATE', width: 100},
// 고객사
{headerHozAlign: 'center', hozAlign: 'left', title: '고객사', field: 'CUSTOMER_NAME', width: 120},
// 유/무상
{headerHozAlign: 'center', hozAlign: 'center', title: '유/무상', field: 'PAID_TYPE_NAME', width: 80},
// 품번
{headerHozAlign: 'center', hozAlign: 'left', title: '품번', field: 'PART_NO', width: 150},
// 품명
{headerHozAlign: 'center', hozAlign: 'left', title: '품명', field: 'PART_NAME', width: 180},
// 수주수량
{headerHozAlign: 'center', hozAlign: 'right', title: '수주수량', field: 'QUANTITY', width: 80},
// 요청납기
{headerHozAlign: 'center', hozAlign: 'center', title: '요청납기', field: 'REQ_DEL_DATE', width: 90},
// 고객사요청사항
{headerHozAlign: 'center', hozAlign: 'left', title: '고객사요청사항', field: 'CUSTOMER_REQUEST'},
// 변경일
{headerHozAlign: 'center', hozAlign: 'center', title: '변경일', field: 'CHANGE_DATE', width: 140},
// 작성자
{headerHozAlign: 'center', hozAlign: 'center', title: '작성자', field: 'CHANGE_USER_NAME', width: 80},
// 이력보기
{headerHozAlign: 'center', hozAlign: 'center', title: '이력보기', field: 'CHANGE_TYPE', width: 80,
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell) {
var rowData = cell.getData();
var historyObjId = fnc_checkNull(rowData.HISTORY_OBJID);
if(historyObjId) {
fn_openMbomHistoryDetailPopup(historyObjId);
}
}
}
];
// 검색 함수
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/productionplanning/mBomHistoryGridList.do", columns, false);
}
// 프로젝트 상세 팝업
function fn_openSaleRegPopup(orderNo, saleNo){
var popup_width = 1000;
var popup_height = 550;
var url = "/salesMgmt/salesRegForm.do?orderNo=" + encodeURIComponent(orderNo) + "&saleNo=" + (saleNo ? encodeURIComponent(saleNo) : "");
fn_centerPopup(popup_width, popup_height, url);
}
// M-BOM 이력 상세 팝업 (개별 히스토리 건)
function fn_openMbomHistoryDetailPopup(historyObjId) {
var popup_width = 1400;
var popup_height = 800;
var url = "/productionplanning/mBomHistoryDetailPopup.do?historyObjId=" + historyObjId;
fn_centerPopup(popup_width, popup_height, url, 'mbomHistoryDetailPopup');
}
</script>
<form name="form1" id="form1" method="post">
<input type="hidden" name="actionType" id="actionType">
<div class="content-box">
<div class="content-box-s">
<div class="plm_menu_name_gdnsi">
<h2>
<span><%=menuName%></span>
</h2>
<div class="btnArea">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>
</div>
<!-- 검색 영역 -->
<div id="plmSearchZon">
<table>
<tr>
<td><label for="search_category_cd">주문유형</label></td>
<td>
<select name="search_category_cd" id="search_category_cd" style="" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.category_cd}
</select>
</td>
<td><label for="search_product_cd">제품구분</label></td>
<td>
<select name="search_product_cd" id="search_product_cd" style="" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td>
<td><label for="search_area_cd">국내/해외</label></td>
<td>
<select name="search_area_cd" id="search_area_cd" style="" class="select2" autocomplete="off">
<option value="">선택</option>
<option value="0001220">국내</option>
<option value="0001221">해외</option>
</select>
</td>
<td><label for="search_customer_objid">고객사</label></td>
<td>
<select name="search_customer_objid" id="search_customer_objid" style="width:253px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</td>
<td><label>변경일</label></td>
<td>
<input type="text" name="search_change_date_from" id="search_change_date_from" autocomplete="off" value="" class="date_icon" style="width:100px;">~
<input type="text" name="search_change_date_to" id="search_change_date_to" autocomplete="off" value="" class="date_icon" style="width:100px;">
</td>
</tr>
</table>
</div>
<!-- 그리드 영역 -->
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</form>
</body>
</html>

View File

@@ -663,91 +663,84 @@ function fn_openPurchaseListPopup() {
<!-- 검색 영역 -->
<div id="plmSearchZon">
<div style="display: flex; flex-wrap: wrap; gap: 10px 15px; align-items: center;">
<!-- 주문유형 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_category_cd">주문유형</label>
<select name="search_category_cd" id="search_category_cd" style="width:120px" class="select2">
<option value="">선택</option>
${code_map.category_cd}
</select>
</div>
<!-- 제품구분 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_product_cd">제품구분</label>
<select name="search_product_cd" id="search_product_cd" style="width:120px" class="select2">
<option value="">선택</option>
${code_map.product_cd}
</select>
</div>
<!-- 국내/해외 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_area_cd">국내/해외</label>
<select name="search_area_cd" id="search_area_cd" style="width:100px" class="select2">
<option value="">선택</option>
<option value="국내">국내</option>
<option value="해외">해외</option>
</select>
</div>
<!-- 고객사 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_customer_objid">고객사</label>
<select name="search_customer_objid" id="search_customer_objid" style="width:150px" class="select2">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</div>
<!-- 유/무상 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_paid_type">유/무상</label>
<select name="search_paid_type" id="search_paid_type" style="width:80px" class="select2">
<option value="">선택</option>
<option value="paid">유상</option>
<option value="free">무상</option>
</select>
</div>
<!-- S/N -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_serial_no">S/N</label>
<input type="text" name="search_serial_no" id="search_serial_no" value="" style="width:100px;">
</div>
<!-- 품번 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_part_no">품번</label>
<select name="search_part_no" id="search_part_no" class="select2-part" style="width:150px;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_part_objid" id="search_part_objid" value="">
</div>
<!-- 품명 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_part_name">품명</label>
<select name="search_part_name" id="search_part_name" class="select2-part" style="width:150px;">
<option value="">품명 선택</option>
</select>
</div>
<!-- 접수일 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label>접수일</label>
<input type="text" name="search_receipt_date_from" id="search_receipt_date_from" autocomplete="off" value="" class="date_icon" style="width:90px;"> ~
<input type="text" name="search_receipt_date_to" id="search_receipt_date_to" autocomplete="off" value="" class="date_icon" style="width:90px;">
</div>
<!-- 요청납기 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label>요청납기</label>
<input type="text" name="search_req_del_date_from" id="search_req_del_date_from" autocomplete="off" value="" class="date_icon" style="width:90px;"> ~
<input type="text" name="search_req_del_date_to" id="search_req_del_date_to" autocomplete="off" value="" class="date_icon" style="width:90px;">
</div>
</div>
<table>
<tr>
<td><label for="search_category_cd">주문유형</label></td>
<td>
<select name="search_category_cd" id="search_category_cd" style="width:210px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.category_cd}
</select>
</td>
<td><label for="search_product_cd">제품구분</label></td>
<td>
<select name="search_product_cd" id="search_product_cd" style="width:210px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td>
<td><label for="search_area_cd">국내/해외</label></td>
<td>
<select name="search_area_cd" id="search_area_cd" style="width:213px" class="select2" autocomplete="off">
<option value="">선택</option>
<option value="국내">국내</option>
<option value="해외">해외</option>
</select>
</td>
<td><label for="search_customer_objid">고객사</label></td>
<td>
<select name="search_customer_objid" id="search_customer_objid" style="width:253px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</td>
<td><label for="search_paid_type">유/무상</label></td>
<td>
<select name="search_paid_type" id="search_paid_type" style="" class="select2" autocomplete="off">
<option value="">선택</option>
<option value="paid">유상</option>
<option value="free">무상</option>
</select>
</td>
<td><label for="search_serial_no">S/N</label></td>
<td>
<input type="text" name="search_serial_no" id="search_serial_no" value="" style="">
</td>
</tr>
<tr>
<td><label for="search_part_no">품번</label></td>
<td>
<select name="search_part_no" id="search_part_no" class="select2-part" style="width:150px;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_part_objid" id="search_part_objid" value="">
</td>
<td><label for="search_part_name">품명</label></td>
<td>
<select name="search_part_name" id="search_part_name" class="select2-part" style="width:150px;">
<option value="">품명 선택</option>
</select>
</td>
<td><label>접수일</label></td>
<td>
<input type="text" name="search_receipt_date_from" id="search_receipt_date_from" autocomplete="off" value="" class="date_icon" style="width:100px;">~
<input type="text" name="search_receipt_date_to" id="search_receipt_date_to" autocomplete="off" value="" class="date_icon" style="width:100px;">
</td>
<td><label>요청납기</label></td>
<td>
<input type="text" name="search_req_del_date_from" id="search_req_del_date_from" autocomplete="off" value="" class="date_icon" style="width:120px;">~
<input type="text" name="search_req_del_date_to" id="search_req_del_date_to" autocomplete="off" value="" class="date_icon" style="width:120px;">
</td>
</tr>
</table>
</div>
<!-- 그리드 영역 -->

View File

@@ -28,9 +28,32 @@
.readonly-field {
background-color: #eee !important;
}
/* S/N 입력 필드 placeholder 색상 */
#SERIAL_NO::placeholder {
color: #999 !important;
opacity: 1;
}
#SERIAL_NO::-webkit-input-placeholder {
color: #999 !important;
}
#SERIAL_NO::-moz-placeholder {
color: #999 !important;
opacity: 1;
}
#SERIAL_NO:-ms-input-placeholder {
color: #999 !important;
}
#SERIAL_NO {
cursor: pointer;
}
</style>
</head>
<script>
// S/N 관리 변수
var snList = [];
var snCounter = 1;
$(function(){
fnc_datepick();
$(".select2").select2();
@@ -43,6 +66,30 @@ $(function(){
fn_save();
});
// S/N 클릭 시 팝업 오픈
$("#SERIAL_NO").click(function() {
fn_openSnManagePopup();
}).attr("placeholder", "클릭하여 S/N 추가").attr("readonly", true);
// 페이지 로드 시 기존 S/N 데이터가 있으면 파싱
var initialSnValue = $("#SERIAL_NO").val();
if(initialSnValue && initialSnValue.trim() != '') {
var snArray = initialSnValue.split(',');
var initialSnList = [];
for(var i = 0; i < snArray.length; i++) {
if(snArray[i].trim() != '') {
initialSnList.push({
id: i + 1,
value: snArray[i].trim()
});
}
}
if(initialSnList.length > 0) {
snList = initialSnList;
snCounter = initialSnList.length + 1;
}
}
// 프로젝트번호 변경 시 관련 정보 자동 로드 (Select2 이벤트)
$("#PROJECT_NO").on("change select2:select", function(){
var projectObjid = $(this).val();
@@ -377,6 +424,216 @@ function fn_save(){
});
}
}
// ========== S/N 관리 함수 ==========
// S/N 관리 팝업 열기
function fn_openSnManagePopup() {
// 기존 데이터 로드
var serialNoValue = $("#SERIAL_NO").val();
if(serialNoValue && serialNoValue.trim() != '' && snList.length == 0) {
var snArray = serialNoValue.split(',');
for(var i = 0; i < snArray.length; i++) {
if(snArray[i].trim() != '') {
snList.push({
id: snCounter++,
value: snArray[i].trim()
});
}
}
}
// 팝업 HTML 생성
var popupHtml = '<div style="padding:10px; color:#333;">';
popupHtml += ' <h3 style="margin:0 0 15px 0; text-align:center; color:#333;">S/N 관리</h3>';
popupHtml += ' <div id="snListContainer" style="margin-bottom:15px; max-height:300px; overflow-y:auto; color:#333;"></div>';
popupHtml += ' <div style="margin-bottom:15px; display:flex; gap:5px;">';
popupHtml += ' <input type="text" id="newSnInput" placeholder="S/N 입력" style="flex:1; padding:8px; border:1px solid #ddd; border-radius:4px; color:#333;">';
popupHtml += ' <button type="button" onclick="fn_addSn()" class="plm_btns">추가</button>';
popupHtml += ' </div>';
popupHtml += ' <div style="text-align:center; margin-top:20px; display:flex; gap:10px; justify-content:center;">';
popupHtml += ' <button type="button" onclick="fn_openSequentialSnPopup()" class="plm_btns">연속번호생성</button>';
popupHtml += ' <button type="button" onclick="fn_confirmSnList()" class="plm_btns">확인</button>';
popupHtml += ' <button type="button" onclick="fn_closeSnPopup()" class="plm_btns">취소</button>';
popupHtml += ' </div>';
popupHtml += '</div>';
// 팝업 오픈
Swal.fire({
html: popupHtml,
width: '700px',
showConfirmButton: false,
showCloseButton: true,
didOpen: function() {
setTimeout(function() {
fn_renderSnList();
// 엔터키로 추가
$("#newSnInput").keypress(function(e) {
if(e.which == 13) {
fn_addSn();
return false;
}
});
}, 50);
}
});
}
// S/N 목록 렌더링
function fn_renderSnList() {
var html = '<table style="width:100%; margin-bottom:10px; border-collapse:collapse; border:1px solid #ddd; color:#333;">';
html += '<colgroup><col width="15%"><col width="65%"><col width="20%"></colgroup>';
html += '<thead><tr style="background:#f5f5f5; color:#333;">';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">번호</th>';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">S/N</th>';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">삭제</th>';
html += '</tr></thead>';
html += '<tbody>';
if(snList.length == 0) {
html += '<tr><td colspan="3" style="text-align:center; padding:30px; color:#999; border:1px solid #ddd;">등록된 S/N이 없습니다.</td></tr>';
} else {
for(var i = 0; i < snList.length; i++) {
html += '<tr>';
html += '<td style="text-align:center; padding:8px; border:1px solid #ddd; color:#333;">' + (i+1) + '</td>';
html += '<td style="padding:8px; border:1px solid #ddd; color:#333;">' + snList[i].value + '</td>';
html += '<td style="text-align:center; padding:8px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteSn(' + snList[i].id + ')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
}
}
html += '</tbody></table>';
$("#snListContainer").html(html);
}
// S/N 추가
function fn_addSn() {
var newSn = $("#newSnInput").val().trim();
if(newSn == '') {
Swal.fire("S/N을 입력해주세요.");
return;
}
// 중복 체크
for(var i = 0; i < snList.length; i++) {
if(snList[i].value == newSn) {
Swal.fire("이미 등록된 S/N입니다.");
return;
}
}
snList.push({
id: snCounter++,
value: newSn
});
$("#newSnInput").val('');
fn_renderSnList();
}
// S/N 삭제
function fn_deleteSn(id) {
for(var i = 0; i < snList.length; i++) {
if(snList[i].id == id) {
snList.splice(i, 1);
break;
}
}
fn_renderSnList();
}
// S/N 목록 확인 및 적용
function fn_confirmSnList() {
var snValues = [];
for(var i = 0; i < snList.length; i++) {
snValues.push(snList[i].value);
}
$("#SERIAL_NO").val(snValues.join(', '));
Swal.close();
}
// S/N 팝업 닫기
function fn_closeSnPopup() {
Swal.close();
}
// 연속번호 생성 팝업
function fn_openSequentialSnPopup() {
var seqHtml = '<div style="padding:10px; color:#333;">';
seqHtml += '<h3 style="margin:0 0 15px 0; text-align:center; color:#333;">연속번호 생성</h3>';
seqHtml += '<table style="width:100%; border-collapse:collapse;">';
seqHtml += '<tr><td style="padding:8px; text-align:right; width:30%;">접두어:</td>';
seqHtml += '<td style="padding:8px;"><input type="text" id="seqPrefix" style="width:100%; padding:5px;" placeholder="예: SN-"></td></tr>';
seqHtml += '<tr><td style="padding:8px; text-align:right;">시작번호:</td>';
seqHtml += '<td style="padding:8px;"><input type="number" id="seqStart" style="width:100%; padding:5px;" value="1"></td></tr>';
seqHtml += '<tr><td style="padding:8px; text-align:right;">종료번호:</td>';
seqHtml += '<td style="padding:8px;"><input type="number" id="seqEnd" style="width:100%; padding:5px;" value="10"></td></tr>';
seqHtml += '<tr><td style="padding:8px; text-align:right;">자릿수:</td>';
seqHtml += '<td style="padding:8px;"><input type="number" id="seqDigits" style="width:100%; padding:5px;" value="3" min="1" max="10"></td></tr>';
seqHtml += '</table>';
seqHtml += '<div style="text-align:center; margin-top:15px;">';
seqHtml += '<button type="button" onclick="fn_generateSequentialSn()" class="plm_btns">생성</button>';
seqHtml += '<button type="button" onclick="fn_closeSeqPopup()" class="plm_btns" style="margin-left:10px;">취소</button>';
seqHtml += '</div></div>';
Swal.fire({
html: seqHtml,
width: '400px',
showConfirmButton: false,
showCloseButton: true
});
}
// 연속번호 생성
function fn_generateSequentialSn() {
var prefix = $("#seqPrefix").val();
var start = parseInt($("#seqStart").val()) || 1;
var end = parseInt($("#seqEnd").val()) || 10;
var digits = parseInt($("#seqDigits").val()) || 3;
if(start > end) {
Swal.fire("시작번호가 종료번호보다 클 수 없습니다.");
return;
}
if(end - start > 999) {
Swal.fire("한 번에 1000개 이상 생성할 수 없습니다.");
return;
}
for(var i = start; i <= end; i++) {
var numStr = String(i).padStart(digits, '0');
var newSn = prefix + numStr;
// 중복 체크
var isDup = false;
for(var j = 0; j < snList.length; j++) {
if(snList[j].value == newSn) {
isDup = true;
break;
}
}
if(!isDup) {
snList.push({
id: snCounter++,
value: newSn
});
}
}
Swal.close();
fn_openSnManagePopup();
}
// 연속번호 팝업 닫기
function fn_closeSeqPopup() {
Swal.close();
fn_openSnManagePopup();
}
</script>
<body>
<form name="form1" id="form1" method="post">

View File

@@ -46,8 +46,9 @@ var resultGrid;
var grid;
// 입력 행 번호
var rowSeq = 0;
// M-BOM 옵션 데이터 (셀렉트박스용)
var mbomOptionsData = {};
// M-BOM 옵션 데이터 (셀렉트박스용) - 배열로 순서 보장
var mbomOptionsData = [];
var mbomOptionsMap = {};
// M-BOM 품명 데이터 (OBJID -> PART_NAME 매핑)
var mbomPartNameData = {};
@@ -81,12 +82,16 @@ $(document).ready(function(){
// M-BOM 옵션 데이터 초기화
function fn_initMbomOptions() {
mbomOptionsData = {"": "선택"};
// 배열로 순서 보장 - "선택"을 맨 위에
mbomOptionsData = [{label: "선택", value: ""}];
mbomOptionsMap = {"": "선택"};
$("#MBOM_SELECT_HIDDEN option").each(function(){
var val = $(this).val();
var text = $(this).text();
if(val !== '') {
mbomOptionsData[val] = text;
mbomOptionsData.push({label: text, value: val});
mbomOptionsMap[val] = text;
}
});
@@ -142,7 +147,7 @@ function fn_initInputGrid() {
},
formatter: function(cell) {
var value = cell.getValue();
return mbomOptionsData[value] || "";
return mbomOptionsMap[value] || "";
},
cellEdited: function(cell) {
// M-BOM 선택 시 품명 자동 입력 (MBOM_HEADER.PART_NAME 사용)

View File

@@ -46,8 +46,9 @@ var resultGrid;
var grid;
// 입력 행 번호
var rowSeq = 0;
// M-BOM 옵션 데이터 (셀렉트박스용)
var mbomOptionsData = {};
// M-BOM 옵션 데이터 (셀렉트박스용) - 배열로 순서 보장
var mbomOptionsData = [];
var mbomOptionsMap = {};
// M-BOM 품명 데이터 (OBJID -> PART_NAME 매핑)
var mbomPartNameData = {};
@@ -81,12 +82,16 @@ $(document).ready(function(){
// M-BOM 옵션 데이터 초기화
function fn_initMbomOptions() {
mbomOptionsData = {"": "선택"};
// 배열로 순서 보장 - "선택"을 맨 위에
mbomOptionsData = [{label: "선택", value: ""}];
mbomOptionsMap = {"": "선택"};
$("#MBOM_SELECT_HIDDEN option").each(function(){
var val = $(this).val();
var text = $(this).text();
if(val !== '') {
mbomOptionsData[val] = text;
mbomOptionsData.push({label: text, value: val});
mbomOptionsMap[val] = text;
}
});
@@ -142,7 +147,7 @@ function fn_initInputGrid() {
},
formatter: function(cell) {
var value = cell.getValue();
return mbomOptionsData[value] || "";
return mbomOptionsMap[value] || "";
},
cellEdited: function(cell) {
// M-BOM 선택 시 품명 자동 입력 (MBOM_HEADER.PART_NAME 사용)

View File

@@ -495,7 +495,7 @@ function fn_purchaseClose(){
<td><label for="project_no">프로젝트번호</label></td>
<td>
<select name="project_no" id="project_no" style="width:260px;" class="select2" autocomplete="off" multiple="multiple">
<select name="project_no" id="project_no" style="width:253px;" class="select2" autocomplete="off" multiple="multiple">
<option value="">선택</option>
${code_map.project_no}
</select>
@@ -509,11 +509,11 @@ function fn_purchaseClose(){
<td><select name="type" id="type" class="select2" autocomplete="off" style="width:130px;"><option value="">선택</option>${code_map.type}</select></td>
--%>
<td><label for="">규격</label></td>
<td><input type="text" name="SEARCH_PART_SPEC" id="SEARCH_PART_SPEC" autocomplete="off" value="${param.SEARCH_PART_SPEC }" style="width:120px;"/></td>
<td><input type="text" name="SEARCH_PART_SPEC" id="SEARCH_PART_SPEC" autocomplete="off" value="${param.SEARCH_PART_SPEC }" style=""/></td>
<td><label for="">품명</label></td>
<td>
<select name="SEARCH_PART_NAME" id="SEARCH_PART_NAME" class="select2-part" style="width:130px;">
<select name="SEARCH_PART_NAME" id="SEARCH_PART_NAME" class="select2-part" style="">
<option value="">품명 선택</option>
</select>
</td>
@@ -527,8 +527,8 @@ function fn_purchaseClose(){
<td class=""><label>입고요청일</label></td>
<td>
<input type="text" name="delivery_start_date" id="delivery_start_date" style="width:90px;" autocomplete="off" value="${param.delivery_start_date}" class="date_icon">~
<input type="text" name="delivery_end_date" id="delivery_end_date" style="width:90px;" autocomplete="off" value="${param.delivery_end_date }" class="date_icon">
<input type="text" name="delivery_start_date" id="delivery_start_date" style="width:120px;" autocomplete="off" value="${param.delivery_start_date}" class="date_icon">~
<input type="text" name="delivery_end_date" id="delivery_end_date" style="width:120px;" autocomplete="off" value="${param.delivery_end_date }" class="date_icon">
</td>
<td class=""><label>발주일</label></td>
@@ -539,7 +539,7 @@ function fn_purchaseClose(){
<td><label for="">입고결과</label></td>
<td>
<select name="delivery_status" id="delivery_status" class="select2" autocomplete="off" style="width:130px;">
<select name="delivery_status" id="delivery_status" class="select2" autocomplete="off" style="">
<option value="">선택</option>
<option value="입고중">입고중</option>
<option value="입고완료">입고완료</option>

View File

@@ -335,12 +335,16 @@ function fn_save(){
<td>${info.receipt_no}</td> -->
<td class="input_title"><label>접수일</label></td>
<td>${info.receipt_date}</td>
<td class="input_title"><label>접수자</label></td>
<td>${info.writer_name}</td>
</tr>
<tr>
<td class="input_title"><label>고객사</label></td>
<td colspan="3">${info.customer_name}</td>
</tr>
<tr>
<td class="input_title"><label>수량</label></td>
<td>${info.qty}</td>
</tr>
<tr>
<td class="input_title"><label>고객사</label></td>
<td>${info.customer_name}</td>
<td class="input_title"><label>제품구분</label></td>
<td>${info.product_type_name}</td>
</tr>
@@ -352,7 +356,7 @@ function fn_save(){
<td class="input_title"><label>품번</label></td>
<td>${info.product_no}</td>
</tr>
<tr>
<!-- <tr>
<td class="input_title"><label>부품품명</label></td>
<td>${info.part_name}</td>
<td class="input_title"><label>부품품번</label></td>
@@ -364,7 +368,7 @@ function fn_save(){
<td>${info.production_date}</td>
<td class="input_title"><label>판매일</label></td>
<td>${info.sales_date}</td>
</tr>
</tr> -->
<tr>
<td class="input_title"><label>S/N</label></td>
<td>${info.serial_no}</td>
@@ -409,6 +413,7 @@ function fn_save(){
<option value="R" ${info.BLAME_DECISION eq 'R' ? 'selected' : ''}>RPS</option>
<option value="C" ${info.BLAME_DECISION eq 'C' ? 'selected' : ''}>고객사</option>
<option value="O" ${info.BLAME_DECISION eq 'O' ? 'selected' : ''}>외주업체</option>
<option value="O" ${info.BLAME_DECISION eq 'E' ? 'selected' : ''}>단순문의</option>
</select>
</td>
<td class="input_title"><label>상태 <span style="color:red;">*</span></label></td>

View File

@@ -9,12 +9,21 @@ String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "고객 이슈 등록");
String actionType = CommonUtils.checkNull(request.getParameter("actionType"));
// info Map 가져오기
Map info = (Map) request.getAttribute("info");
if(info == null) info = new HashMap();
// OBJID가 없으면 신규 생성 (첨부파일 연결용)
String objId = CommonUtils.checkNull(request.getAttribute("info") != null ? ((Map)request.getAttribute("info")).get("objid") : "");
String objId = CommonUtils.checkNull(info.get("objid"));
boolean isNew = objId.isEmpty() || "new".equals(actionType);
if(objId.isEmpty()){
objId = String.valueOf(CommonUtils.createObjId());
}
// 접수자/작성자
String writerName = CommonUtils.checkNull(info.get("writer_name"));
if(writerName.isEmpty()) writerName = connectUserName;
%>
<!DOCTYPE html>
<html>
@@ -138,18 +147,24 @@ function fn_save(){
<td>
<input type="text" name="RECEIPT_DATE" id="RECEIPT_DATE" class="date_icon" required reqTitle="접수일" value="${info.receipt_date}">
</td>
<td class="input_title"><label>수량 <span style="color:red;">*</span></label></td>
<td class="input_title"><label>접수자</label></td>
<td>
<input type="text" name="QTY" id="QTY" value="${info.qty}" required reqTitle="수량">
<input type="text" id="ACTION_USER_NAME" value="<%=writerName%>" readonly>
</td>
</tr>
<tr>
<tr>
<td class="input_title"><label>고객사 <span style="color:red;">*</span></label></td>
<td>
<td colspan="3">
<select name="CUSTOMER_OBJID" id="CUSTOMER_OBJID" class="select2" required reqTitle="고객사" type="select" style="width:100%;">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</td>
</tr>
<tr>
<td class="input_title"><label>수량 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="QTY" id="QTY" value="${info.qty}" required reqTitle="수량">
</td>
<td class="input_title"><label>제품구분 <span style="color:red;">*</span></label></td>
<td>
@@ -175,7 +190,7 @@ function fn_save(){
</td>
</tr>
<tr>
<!-- <tr>
<td class="input_title"><label>부품품명 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="PART_NAME" id="PART_NAME" value="${info.part_name}" required reqTitle="부품품명">
@@ -195,7 +210,7 @@ function fn_save(){
<td>
<input type="text" name="SALES_DATE" id="SALES_DATE" class="date_icon" value="${info.sales_date}">
</td>
</tr>
</tr> -->
<tr>
<td class="input_title"><label>S/N</label></td>
<td>

View File

@@ -89,24 +89,30 @@ var columns = [
}
},
{headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1, title:'접수일', field:'RECEIPT_DATE'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:2, title:'조치구분', field:'ACTION_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:150, widthGrow:1, title:'고객사', field:'CUSTOMER_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'제품구분', field:'PRODUCT_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'품명', field:'PRODUCT_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'품번', field:'PRODUCT_NO'},
{headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1, title:'S/N', field:'SERIAL_NO'},
{headerHozAlign:'center', hozAlign:'right', minWidth:70, widthGrow:1, title:'수량', field:'QTY',
formatter:"money", formatterParams:{thousand:",", precision:false}
},
{headerHozAlign:'center', hozAlign:'left', minWidth:150, widthGrow:1, title:'고객사', field:'CUSTOMER_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'제품구분', field:'PRODUCT_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:80, widthGrow:1, title:'접수자', field:'WRITER_NAME'},
//{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'모델명', field:'MODEL_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'품명', field:'PRODUCT_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'품번', field:'PRODUCT_NO'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'부품품', field:'PART_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'부품품번', field:'PART_NO'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'생산일', field:'PRODUCTION_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'판매일', field:'SALES_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:1, title:'S/N', field:'SERIAL_NO'},
//{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'부품품명', field:'PART_NAME'},
//{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'부품품', field:'PART_NO'},
//{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'생산일', field:'PRODUCTION_DATE'},
//{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'판매일', field:'SALES_DATE'},
{headerHozAlign:'center', hozAlign:'left', minWidth:80, widthGrow:1, title:'제조사', field:'MANUFACTURER'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:2, title:'이슈내용', field:'COMPLAINT_CONTENT'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:2, title:'조치구분', field:'ACTION_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:2, title:'조치내용', field:'ACTION_CONTENT'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'조치일', field:'ACTION_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:80, widthGrow:1, title:'조치자', field:'ACTION_USER_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:80, widthGrow:1, title:'귀책판정', field:'BLAME_DECISION_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'상태(C/O)', field:'STATUS',
formatter: function(cell, formatterParams, onRendered){
@@ -118,8 +124,8 @@ var columns = [
}
},
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1, title:'비고', field:'REMARK'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'조치일', field:'ACTION_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:80, widthGrow:1, title:'조치자', field:'ACTION_USER_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'이미지파일', field:'IMAGE_FILE',
formatter:fnc_subInfoValueFormatter,
cellClick:function(e, cell){
@@ -165,7 +171,7 @@ function fn_registDefectPopUp(){
var selected = _tabulGrid.getSelectedData();
var popup_width = 800;
var popup_height = 450;
var popup_height = 480;
var hiddenForm = document.hiddenForm;
var target = "customerCsDefectPopUp";
var url = "/quality/customerCsDefectFormPopUp.do";
@@ -265,7 +271,7 @@ function fn_FileRegist(objId, docType, docTypeName){
</td>
<td><label>고객사</label></td>
<td>
<select name="search_customer_objid" id="search_customer_objid" class="select2" autocomplete="off" style="width:180px;">
<select name="search_customer_objid" id="search_customer_objid" class="select2" autocomplete="off" style="width:230px;">
<option value="">전체</option>
${code_map.customer_cd}
</select>
@@ -301,6 +307,7 @@ function fn_FileRegist(objId, docType, docTypeName){
<option value="R">RPS</option>
<option value="C">고객사</option>
<option value="O">외주업체</option>
<option value="E">단순문의</option>
</select>
</td>
<td><label>상태</label></td>

View File

@@ -243,7 +243,7 @@ function fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS){
<td><label for="">품번</label></td>
<td>
<select name="search_part_no" id="search_part_no" class="select2-part" style="width:170px;">
<select name="search_part_no" id="search_part_no" class="select2-part" style="width:210px;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_part_objid" id="search_part_objid" value="">
@@ -251,7 +251,7 @@ function fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS){
<td><label for="">품명</label></td>
<td>
<select name="search_part_name" id="search_part_name" class="select2-part" style="width:170px;">
<select name="search_part_name" id="search_part_name" class="select2-part" style="width:210px;">
<option value="">품명 선택</option>
</select>
</td>
@@ -281,7 +281,7 @@ function fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS){
<td><label for="">검사여부</label></td>
<td>
<select name="search_inspection_yn" id="search_inspection_yn" class="select2" autocomplete="off" style="width:120px;">
<select name="search_inspection_yn" id="search_inspection_yn" class="select2" autocomplete="off" style="width:210px;">
<option value="">전체</option>
<option value="검사">검사</option>
<option value="스킵">스킵</option>
@@ -290,7 +290,7 @@ function fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS){
<td><label for="">요청현황</label></td>
<td>
<select name="search_request_status" id="search_request_status" class="select2" autocomplete="off" style="width:120px;">
<select name="search_request_status" id="search_request_status" class="select2" autocomplete="off" style="width:140px;">
<option value="">전체</option>
<option value="미요청">미요청</option>
<option value="요청중">요청중</option>
@@ -299,12 +299,12 @@ function fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS){
</td>
<td><label for="">요청자</label></td>
<td><select name="request_user_id" id="request_user_id" class="select2" autocomplete="off" style="width:120px;"><option value="">선택</option>${code_map.request_user_id}</select></td>
<td><select name="request_user_id" id="request_user_id" class="select2" autocomplete="off" style="width:140px;"><option value="">선택</option>${code_map.request_user_id}</select></td>
<td class=""><label>요청일</label></td>
<td>
<input type="text" name="request_start_date" id="request_start_date" style="width:100px;" autocomplete="off" value="${param.request_start_date}" class="date_icon">~
<input type="text" name="request_end_date" id="request_end_date" style="width:100px;" autocomplete="off" value="${param.request_end_date }" class="date_icon">
<input type="text" name="request_start_date" id="request_start_date" style="width:120px;" autocomplete="off" value="${param.request_start_date}" class="date_icon">~
<input type="text" name="request_end_date" id="request_end_date" style="width:120px;" autocomplete="off" value="${param.request_end_date }" class="date_icon">
</td>
</tr>
</table>

View File

@@ -83,6 +83,11 @@ $(document).ready(function(){
fn_registPopUp();
});
// 엑셀 다운로드
$("#btnExcel").click(function(){
fn_excelDownload();
});
fn_search();
});
@@ -169,6 +174,126 @@ function fn_openAttachFilePopUp(objId) {
fn_watchPopupClose(popup, objId, 'ATTACH_FILE_CNT', 'PROCESS_INSPECTION_FILE');
}
// 엑셀 다운로드 (전체 데이터 풀어서 - ExcelJS)
function fn_excelDownload(){
var params = {
search_project_no: $("#search_project_no").val() || "",
productType: $("#productType").val() || "",
search_part_no: $("#search_part_no").val() || "",
search_part_name: $("#search_part_name").val() || "",
search_inspection_date_from: $("#search_inspection_date_from").val() || "",
search_inspection_date_to: $("#search_inspection_date_to").val() || "",
search_inspector: $("#search_inspector").val() || "",
search_inspection_result: $("#search_inspection_result").val() || ""
};
$.ajax({
url: "/quality/getProcessInspectionListForExcel.do",
type: "POST",
data: params,
dataType: "json",
success: function(result){
console.log("엑셀 데이터:", result);
if(result && result.list && result.list.length > 0){
fn_createExcelFile(result.list);
} else {
Swal.fire("다운로드할 데이터가 없습니다.");
}
},
error: function(xhr, status, error){
console.error("엑셀 데이터 조회 실패:", error);
console.error("상태:", status);
console.error("응답:", xhr.responseText);
Swal.fire("엑셀 데이터 조회 중 오류가 발생했습니다.");
}
});
}
// ExcelJS로 엑셀 파일 생성 (전체 풀어서)
async function fn_createExcelFile(data){
try {
var pageTitle = "공정검사관리";
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet(pageTitle);
// 헤더 설정 (모든 컬럼 포함: 제품구분, 공정명, 부서, 작업자, 디테일비고, 조치현황)
var headers = ["No", "검사일", "검사자", "제품구분", "공정명", "프로젝트번호", "품번", "품명",
"검사수량", "불량수량", "불량율(%)", "작업환경상태", "측정기", "부서", "작업자", "디테일비고", "조치현황", "검사결과"];
// 열 너비 설정
var colWidths = [6, 12, 12, 15, 12, 18, 18, 22, 12, 12, 10, 14, 10, 12, 12, 15, 12, 10];
colWidths.forEach(function(width, idx){
worksheet.getColumn(idx + 1).width = width;
});
// 헤더 행 추가
var headerRow = worksheet.addRow(headers);
// 헤더 스타일 (노란색 배경)
headerRow.eachCell(function(cell){
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFFFF00' } // 노란색
};
cell.font = { bold: true };
cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
// 데이터 추가 (모든 컬럼 포함)
data.forEach(function(item, index){
var row = worksheet.addRow([
index + 1,
item.inspection_date || item.INSPECTION_DATE || "",
item.inspector_name || item.INSPECTOR_NAME || "",
item.product_type || item.PRODUCT_TYPE || "",
item.process_name || item.PROCESS_NAME || "",
item.project_no || item.PROJECT_NO || "",
item.part_no || item.PART_NO || "",
item.part_name || item.PART_NAME || "",
item.inspection_qty || item.INSPECTION_QTY || 0,
item.defect_qty || item.DEFECT_QTY || 0,
item.defect_rate || item.DEFECT_RATE || 0,
item.work_env_status || item.WORK_ENV_STATUS || "",
item.measuring_device || item.MEASURING_DEVICE || "",
item.dept_name || item.DEPT_NAME || "",
item.user_name || item.USER_NAME || "",
item.detail_remark || item.DETAIL_REMARK || "",
item.action_status || item.ACTION_STATUS || "",
item.inspection_result || item.INSPECTION_RESULT || ""
]);
// 데이터 행 스타일
row.eachCell(function(cell){
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
});
// 파일명 생성
var today = new Date();
var dateStr = today.getFullYear() + ("0" + (today.getMonth()+1)).slice(-2) + ("0" + today.getDate()).slice(-2);
var fileName = pageTitle + "_" + dateStr + ".xlsx";
// 다운로드
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), fileName);
} catch(e) {
console.error("엑셀 생성 오류:", e);
Swal.fire("엑셀 파일 생성 중 오류가 발생했습니다.");
}
}
</script>
<body>
@@ -187,6 +312,7 @@ function fn_openAttachFilePopUp(objId) {
<div class="btnArea">
<input type="button" class="plm_btns" value="공정검사 등록" id="btnRegist">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
<input type="button" class="plm_btns" value="Excel Download" id="btnExcel">
</div>
</div>

View File

@@ -84,9 +84,8 @@ $(document).ready(function(){
fn_search();
});
// 엑셀 다운로드 (원본 데이터 - 외 N건 풀어서)
// 엑셀 다운로드 (전체 데이터 풀어서 - ExcelJS)
function fn_excelDownload(){
// 서버에서 원본 데이터 가져오기
var params = {
search_model_name: $("#search_model_name").val() || "",
search_work_order_no: $("#search_work_order_no").val() || "",
@@ -102,132 +101,110 @@ function fn_excelDownload(){
data: params,
dataType: "json",
success: function(result){
console.log("엑셀 데이터:", result);
if(result && result.list && result.list.length > 0){
createExcelFile(result.list);
fn_createExcelFile(result.list);
} else {
Swal.fire("다운로드할 데이터가 없습니다.");
}
},
error: function(xhr, status, error){
console.error("엑셀 데이터 조회 실패:", error);
console.error("상태:", status);
console.error("응답:", xhr.responseText);
Swal.fire("엑셀 데이터 조회 중 오류가 발생했습니다.");
}
});
}
// ExcelJS로 원본 데이터 엑셀 파일 생성 (공통 스타일 적용)
async function createExcelFile(data){
var pageTitle = "반제품검사관리";
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet(pageTitle);
// 헤더 설정 (컬럼 순서 변경: 검사일, 검사자를 앞으로)
var headers = ["No", "검사일", "검사자", "품명(모델명)", "작업지시번호", "부품품번", "부품명",
"입고수량 합계", "양품수량 합계", "불량수량 합계", "불량율(%)", "재생수량 합계", "최종양품수량 합계",
"불량유형", "불량원인", "귀책부서", "처리현황", "처리결과", "비고"];
// 열 너비 설정
var colWidths = [6, 12, 12, 25, 18, 20, 25, 15, 15, 15, 12, 15, 18, 15, 15, 12, 12, 12, 20];
colWidths.forEach(function(width, idx){
worksheet.getColumn(idx + 1).width = width;
});
// 제목 행 추가
worksheet.mergeCells('A1:S1');
var titleCell = worksheet.getCell('A1');
titleCell.value = pageTitle;
titleCell.font = { size: 16, bold: true };
titleCell.alignment = { vertical: 'middle', horizontal: 'center' };
worksheet.getRow(1).height = 30;
// 빈 행 추가
worksheet.addRow([]);
// 헤더 행 추가 (3행)
var headerRow = worksheet.addRow(headers);
// 헤더 스타일 (공통 스타일: 파란색 배경, 흰색 글자)
headerRow.eachCell(function(cell){
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF4472C4' } // 파란색
};
cell.font = {
color: { argb: 'FFFFFFFF' }, // 흰색 글자
bold: true
};
cell.alignment = {
vertical: 'middle',
horizontal: 'center',
wrapText: true
};
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
headerRow.height = 25;
// 데이터 추가 (컬럼 순서 변경: 검사일, 검사자를 앞으로)
data.forEach(function(item, index){
var row = worksheet.addRow([
index + 1,
item.inspection_date || item.INSPECTION_DATE || "",
item.writer_name || item.WRITER_NAME || "",
item.model_name || item.MODEL_NAME || "",
item.work_order_no || item.WORK_ORDER_NO || "",
item.part_no || item.PART_NO || "",
item.part_name || item.PART_NAME || "",
item.receipt_qty || item.RECEIPT_QTY || 0,
item.good_qty || item.GOOD_QTY || 0,
item.defect_qty || item.DEFECT_QTY || 0,
item.defect_rate || item.DEFECT_RATE || 0,
item.regeneration_qty || item.REGENERATION_QTY || 0,
item.final_good_qty || item.FINAL_GOOD_QTY || 0,
item.defect_type || item.DEFECT_TYPE || "",
item.defect_cause || item.DEFECT_CAUSE || "",
item.responsible_dept || item.RESPONSIBLE_DEPT || "",
item.process_status || item.PROCESS_STATUS || "",
item.disposition_type || item.DISPOSITION_TYPE || "",
item.remark || item.REMARK || ""
]);
// ExcelJS로 엑셀 파일 생성 (전체 풀어서)
async function fn_createExcelFile(data){
try {
var pageTitle = "반제품검사관리";
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet(pageTitle);
// 데이터 행 스타일 (테두리)
row.eachCell(function(cell, colNumber){
// 헤더 설정 (DATA_TYPE, PRODUCT_TYPE, INSPECTION_GROUP_ID 추가)
var headers = ["No", "검사일", "검사자", "품명(모델명)", "제품구분", "작업지시번호", "부품품번", "부품명",
"입고수량", "양품수량", "불량수량", "불량율(%)", "재생수량", "최종양품수량",
"불량유형", "불량원인", "귀책부서", "처리현황", "처리결과", "비고", "데이터타입", "검사그룹ID"];
// 열 너비 설정
var colWidths = [6, 12, 12, 20, 12, 15, 18, 20, 12, 12, 12, 10, 12, 14, 12, 12, 10, 10, 10, 15, 10, 15];
colWidths.forEach(function(width, idx){
worksheet.getColumn(idx + 1).width = width;
});
// 헤더 행 추가
var headerRow = worksheet.addRow(headers);
// 헤더 스타일 (노란색 배경)
headerRow.eachCell(function(cell){
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFFFF00' } // 노란색
};
cell.font = { bold: true };
cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
// 숫자 컬럼 오른쪽 정렬 (8~13번 컬럼: 수량, 불량율)
if(colNumber >= 8 && colNumber <= 13){
cell.alignment = { horizontal: 'right' };
// 숫자에 천단위 구분자 (불량율 제외)
if(colNumber !== 11 && typeof cell.value === 'number'){
cell.numFmt = '#,##0';
}
} else if(colNumber === 1 || colNumber === 2 || colNumber === 3){
cell.alignment = { horizontal: 'center' };
}
});
});
// 파일명 생성
var date = new Date();
var currentDate = date.getFullYear() +
("0" + (date.getMonth() + 1)).slice(-2) +
("0" + date.getDate()).slice(-2) + "_" +
("0" + date.getHours()).slice(-2) +
("0" + date.getMinutes()).slice(-2);
var fileName = pageTitle + "_" + currentDate + ".xlsx";
// 다운로드
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), fileName);
// 데이터 추가 (DATA_TYPE, PRODUCT_TYPE, INSPECTION_GROUP_ID 추가)
data.forEach(function(item, index){
var row = worksheet.addRow([
index + 1,
item.inspection_date || item.INSPECTION_DATE || "",
item.writer_name || item.WRITER_NAME || "",
item.model_name || item.MODEL_NAME || "",
item.product_type || item.PRODUCT_TYPE || "",
item.work_order_no || item.WORK_ORDER_NO || "",
item.part_no || item.PART_NO || "",
item.part_name || item.PART_NAME || "",
item.receipt_qty || item.RECEIPT_QTY || 0,
item.good_qty || item.GOOD_QTY || 0,
item.defect_qty || item.DEFECT_QTY || 0,
item.defect_rate || item.DEFECT_RATE || 0,
item.regeneration_qty || item.REGENERATION_QTY || 0,
item.final_good_qty || item.FINAL_GOOD_QTY || 0,
item.defect_type || item.DEFECT_TYPE || "",
item.defect_cause || item.DEFECT_CAUSE || "",
item.responsible_dept || item.RESPONSIBLE_DEPT || "",
item.process_status || item.PROCESS_STATUS || "",
item.disposition_type || item.DISPOSITION_TYPE || "",
item.remark || item.REMARK || "",
item.data_type || item.DATA_TYPE || "",
item.inspection_group_id || item.INSPECTION_GROUP_ID || ""
]);
// 데이터 행 스타일
row.eachCell(function(cell){
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
});
// 파일명 생성
var today = new Date();
var dateStr = today.getFullYear() + ("0" + (today.getMonth()+1)).slice(-2) + ("0" + today.getDate()).slice(-2);
var fileName = pageTitle + "_" + dateStr + ".xlsx";
// 다운로드
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), fileName);
} catch(e) {
console.error("엑셀 생성 오류:", e);
Swal.fire("엑셀 파일 생성 중 오류가 발생했습니다.");
}
}
// 컬럼: 검사일, 검사자, 품명(모델명), 작업지시번호, 부품품번, 부품명, 입고수량 합계, 양품수량 합계, 불량수량 합계, 불량율, 재생수량 합계, 최종양품수량 합계

View File

@@ -1223,10 +1223,122 @@ public class ProductionPlanningController extends BaseService {
}
if(mbomDetailList != null && !mbomDetailList.isEmpty()) {
// 할당된 E-BOM/M-BOM인 경우: 프로젝트의 PART_OBJID로 PART_MNG에서 파트 정보 조회하여 1레벨로 사용
if("ASSIGNED_EBOM".equals(bomDataType) || "ASSIGNED_MBOM".equals(bomDataType)) {
String partObjId = CommonUtils.checkNull(projectInfo.get("PART_OBJID"));
// PART_OBJID가 있으면 PART_MNG 테이블에서 파트 정보 조회
String projectPartNo = "";
String projectPartName = "";
String projectPartObjId = "";
if(!"".equals(partObjId)) {
Map<String, Object> partParam = new HashMap<>();
partParam.put("partObjId", partObjId);
Map<String, Object> partInfo = commonService.selectOne("partMng.getPartInfoByObjId", request, partParam);
if(partInfo != null) {
projectPartNo = CommonUtils.checkNull(partInfo.get("PART_NO"));
projectPartName = CommonUtils.checkNull(partInfo.get("PART_NAME"));
projectPartObjId = partObjId;
System.out.println("PART_MNG에서 파트 정보 조회 - PART_OBJID: " + partObjId + ", PART_NO: " + projectPartNo + ", PART_NAME: " + projectPartName);
}
}
// PART_OBJID로 조회 실패 시 PROJECT_MGMT의 PART_NO, PART_NAME 사용 (fallback)
if("".equals(projectPartNo)) {
projectPartNo = CommonUtils.checkNull(projectInfo.get("PART_NO"));
projectPartName = CommonUtils.checkNull(projectInfo.get("PART_NAME"));
System.out.println("PROJECT_MGMT의 파트 정보 사용 (fallback) - PART_NO: " + projectPartNo + ", PART_NAME: " + projectPartName);
}
// 프로젝트에 파트 정보가 있고, PART_OBJID가 유효한 경우에만 1레벨 교체
// PART_OBJID가 없으면 외래키 제약조건 위반으로 저장 불가
if(!"".equals(projectPartNo) && !"".equals(projectPartName) && !"".equals(projectPartObjId)) {
List<Map<String, Object>> newMbomDetailList = new ArrayList<>();
// 1레벨의 CHILD_OBJID 생성 (부모-자식 관계 연결용)
String newLevel1ChildObjid = CommonUtils.createObjId();
// 1레벨: PART_MNG 테이블의 파트 정보로 생성
Map<String, Object> level1Item = new HashMap<>();
level1Item.put("LEVEL", 1);
level1Item.put("PART_NO", projectPartNo);
level1Item.put("PART_NAME", projectPartName);
level1Item.put("QTY", 1); // 1레벨 수량은 항상 1
level1Item.put("PART_OBJID", projectPartObjId); // 실제 PART_MNG의 OBJID 사용
level1Item.put("CHILD_OBJID", newLevel1ChildObjid); // 부모-자식 연결용
level1Item.put("PARENT_OBJID", ""); // 1레벨은 부모 없음
newMbomDetailList.add(level1Item);
// 기존 BOM의 1레벨 CHILD_OBJID 찾기 (2레벨의 PARENT_OBJID 매핑용)
String originalLevel1ChildObjid = "";
String originalLevel1PartNo = "";
for(Map<String, Object> item : mbomDetailList) {
Integer level = null;
Object levelObj = item.get("LEVEL");
if(levelObj instanceof Integer) {
level = (Integer) levelObj;
} else if(levelObj instanceof Long) {
level = ((Long) levelObj).intValue();
} else if(levelObj instanceof String) {
try { level = Integer.parseInt((String) levelObj); } catch(Exception e) {}
}
if(level != null && level == 1) {
originalLevel1ChildObjid = CommonUtils.checkNull(item.get("CHILD_OBJID"));
originalLevel1PartNo = CommonUtils.checkNull(item.get("PART_NO"));
break;
}
}
// 2레벨 이상 데이터 추가 (레벨 조정 없이 그대로)
// 단, 원래 1레벨의 자식(2레벨)은 프로젝트 파트의 자식이 됨
for(Map<String, Object> item : mbomDetailList) {
Integer level = null;
Object levelObj = item.get("LEVEL");
if(levelObj instanceof Integer) {
level = (Integer) levelObj;
} else if(levelObj instanceof Long) {
level = ((Long) levelObj).intValue();
} else if(levelObj instanceof String) {
try { level = Integer.parseInt((String) levelObj); } catch(Exception e) {}
}
if(level != null && level >= 2) {
Map<String, Object> newItem = new HashMap<>(item);
// 원래 2레벨(1레벨의 직접 자식)은 부모를 프로젝트 파트로 변경
if(level == 2) {
String parentObjid = CommonUtils.checkNull(item.get("PARENT_OBJID"));
String parentPartNo = CommonUtils.checkNull(item.get("PARENT_PART_NO"));
// PARENT_OBJID가 원래 1레벨의 CHILD_OBJID와 같으면 새 1레벨로 변경
if(parentObjid.equals(originalLevel1ChildObjid) || parentPartNo.equals(originalLevel1PartNo)) {
newItem.put("PARENT_OBJID", newLevel1ChildObjid);
newItem.put("PARENT_PART_NO", projectPartNo);
}
}
newMbomDetailList.add(newItem);
}
}
mbomDetailList = newMbomDetailList;
System.out.println("프로젝트 파트 정보로 1레벨 교체 완료 - " + projectPartNo + " / " + projectPartName + " / CHILD_OBJID: " + newLevel1ChildObjid);
}
}
// 저장된 M-BOM 데이터가 있으면 이를 표시
int maxLevel = 1;
for(Map<String, Object> item : mbomDetailList) {
Integer level = (Integer) item.get("LEVEL");
Integer level = null;
Object levelObj = item.get("LEVEL");
if(levelObj instanceof Integer) {
level = (Integer) levelObj;
} else if(levelObj instanceof Long) {
level = ((Long) levelObj).intValue();
} else if(levelObj instanceof String) {
try { level = Integer.parseInt((String) levelObj); } catch(Exception e) {}
}
if(level != null && level > maxLevel) {
maxLevel = level;
}
@@ -1995,4 +2107,68 @@ public class ProductionPlanningController extends BaseService {
return resultMap;
}
/**
* M-BOM 변경이력 목록 페이지
*/
@RequestMapping("/productionplanning/mBomHistoryList.do")
public String mBomHistoryList(HttpServletRequest request, @RequestParam Map paramMap){
Map code_map = new HashMap();
try {
// 주문유형 (0000167)
code_map.put("category_cd", commonService.bizMakeOptionList("0000167", (String)paramMap.get("search_category_cd"), "common.getCodeselect"));
// 제품구분 (0000001)
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", (String)paramMap.get("search_product_cd"), "common.getCodeselect"));
// 고객사 (SUPPLY_MNG + CLIENT_MNG)
code_map.put("customer_cd", commonService.bizMakeOptionList("", (String)paramMap.get("search_customer_objid"), "common.getAllSupplySelect"));
request.setAttribute("code_map", code_map);
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomHistoryList";
}
/**
* M-BOM 변경이력 그리드 목록 조회 (MBOM_HISTORY 기준)
*/
@ResponseBody
@RequestMapping("/productionplanning/mBomHistoryGridList.do")
public Map mBomHistoryGridList(HttpServletRequest request, @RequestParam Map paramMap){
commonService.selectListPagingNew("productionplanning.getMbomHistoryGridList", request, paramMap);
return paramMap;
}
/**
* M-BOM 변경이력 상세 팝업
*/
@RequestMapping("/productionplanning/mBomHistoryDetailPopup.do")
public String mBomHistoryDetailPopup(HttpServletRequest request, @RequestParam Map paramMap){
try {
String historyObjId = CommonUtils.nullToEmpty((String)paramMap.get("historyObjId"));
if(!"".equals(historyObjId)) {
// 이력 상세 정보 조회
Map<String, Object> historyInfo = commonService.selectOne("productionplanning.getMbomHistoryDetail", request, paramMap);
if(historyInfo != null) {
// JSON 데이터 이스케이프 처리 (JSP에서 JavaScript로 전달 시 오류 방지)
String beforeData = CommonUtils.nullToEmpty((String)historyInfo.get("BEFORE_DATA"));
String afterData = CommonUtils.nullToEmpty((String)historyInfo.get("AFTER_DATA"));
// 줄바꿈, 작은따옴표 등 이스케이프
beforeData = beforeData.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "");
afterData = afterData.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "");
historyInfo.put("BEFORE_DATA_ESCAPED", beforeData);
historyInfo.put("AFTER_DATA_ESCAPED", afterData);
request.setAttribute("historyInfo", historyInfo);
}
}
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomHistoryDetailPopup";
}
}

View File

@@ -597,7 +597,7 @@ public class QualityController {
}
/**
* 공정검사 엑셀 다운로드
* 공정검사 엑셀 다운로드 (JSP 방식)
*/
@RequestMapping("/quality/processInspectionExcelDownload.do")
public String processInspectionExcelDownload(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
@@ -606,6 +606,18 @@ public class QualityController {
return "/quality/processInspectionExcel";
}
/**
* 공정검사 엑셀 데이터 조회 (AJAX JSON) - 전체 풀어서 다운로드
*/
@ResponseBody
@RequestMapping("/quality/getProcessInspectionListForExcel.do")
public Map getProcessInspectionListForExcel(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map result = new HashMap();
List list = service.getProcessInspectionListForExcel(paramMap);
result.put("list", list);
return result;
}
// =====================================================
// 반제품검사 관리
// =====================================================
@@ -780,7 +792,7 @@ public class QualityController {
// 고객사
code_map.put("customer_cd", commonService.bizMakeOptionList("", CommonUtils.nullToEmpty((String)info.get("customer_objid")), "common.getClientMngSupplySelect"));
// 제품구분
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)info.get("PRODUCT_TYPE")), "common.getCodeselect"));
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)info.get("product_type")), "common.getCodeselect"));
} catch(Exception e) {
e.printStackTrace();
}

View File

@@ -8115,4 +8115,21 @@ SELECT PM.OBJID
ORDER BY REGDATE DESC, PART_NO
</select>
<!-- PART_OBJID로 PART_MNG 테이블에서 파트 정보 조회 -->
<select id="getPartInfoByObjId" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
OBJID,
PART_NO,
PART_NAME,
PART_TYPE,
UNIT,
MATERIAL,
SPEC,
MAKER,
SUPPLY_CODE
FROM PART_MNG
WHERE OBJID::VARCHAR = #{partObjId}
LIMIT 1
</select>
</mapper>

View File

@@ -3137,6 +3137,7 @@
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
PM.BOM_REPORT_OBJID,
PM.PART_OBJID,
PM.PART_NO,
PM.PART_NAME,
PM.SOURCE_BOM_TYPE,
@@ -3426,6 +3427,7 @@
MH.BEFORE_DATA,
MH.AFTER_DATA,
MH.CHANGE_USER,
USER_NAME(MH.CHANGE_USER) AS CHANGE_USER_NAME,
TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD HH24:MI:SS') AS CHANGE_DATE,
-- MBOM_HEADER 정보 조인
MHD.MBOM_NO AS MBOM_PART_NO,
@@ -3958,12 +3960,11 @@
ORDER BY REGDATE DESC, MBOM_NO
</select>
<!-- M-BOM 목록 (bizMakeOptionList용) -->
<!-- M-BOM 목록 (bizMakeOptionList용) - (E-BOM) 텍스트 제거 -->
<select id="getMbomListForSelect2" parameterType="map" resultType="map">
SELECT
OBJID AS CODE,
COALESCE(MBOM_NO, '') ||
CASE WHEN SOURCE_BOM_TYPE IS NOT NULL AND SOURCE_BOM_TYPE != '' THEN ' (' || SOURCE_BOM_TYPE || ')' ELSE '' END AS NAME
COALESCE(MBOM_NO, '') AS NAME
FROM MBOM_HEADER
WHERE STATUS = 'Y'
ORDER BY REGDATE DESC, MBOM_NO
@@ -4932,4 +4933,93 @@
WHERE PROJECT_OBJID = #{projectObjid}
</delete>
<!-- M-BOM 변경이력 목록 조회 (MBOM_HISTORY 기준) -->
<select id="getMbomHistoryGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MH.OBJID AS HISTORY_OBJID,
MH.MBOM_HEADER_OBJID,
MH.CHANGE_TYPE,
--TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD HH24:MI:SS') AS CHANGE_DATE,
TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD') AS CHANGE_DATE,
MH.CHANGE_USER,
COALESCE(
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = MH.CHANGE_USER LIMIT 1),
MH.CHANGE_USER
) AS CHANGE_USER_NAME,
PM.OBJID AS PROJECT_OBJID,
PM.PROJECT_NO,
PM.PART_NO,
PM.PART_NAME,
COALESCE(PM.QUANTITY::numeric, 0) AS QUANTITY,
COALESCE(PM.DUE_DATE, CM.REQ_DEL_DATE) AS REQ_DEL_DATE,
CM.OBJID AS CONTRACT_OBJID,
COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.CATEGORY_CD LIMIT 1), '') AS CATEGORY_NAME,
COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1), '') AS PRODUCT_NAME,
COALESCE((SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.AREA_CD LIMIT 1), '') AS AREA_NAME,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS RECEIPT_DATE,
COALESCE(
CASE WHEN CM.CUSTOMER_OBJID LIKE 'C_%' THEN (SELECT CLIENT_NM FROM CLIENT_MNG AS C WHERE 'C_' || C.OBJID::VARCHAR = CM.CUSTOMER_OBJID LIMIT 1) ELSE (SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID::VARCHAR = CM.CUSTOMER_OBJID::VARCHAR LIMIT 1) END,
''
) AS CUSTOMER_NAME,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PAID_TYPE LIMIT 1),
CASE
WHEN CM.PAID_TYPE = 'paid' THEN '유상'
WHEN CM.PAID_TYPE = 'free' THEN '무상'
ELSE ''
END
) AS PAID_TYPE_NAME,
COALESCE(CI.CUSTOMER_REQUEST, '') AS CUSTOMER_REQUEST
FROM MBOM_HISTORY MH
INNER JOIN MBOM_HEADER MHD ON MH.MBOM_HEADER_OBJID = MHD.OBJID
INNER JOIN PROJECT_MGMT PM ON MHD.PROJECT_OBJID = PM.OBJID::VARCHAR
LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
WHERE 1=1
<if test="search_category_cd != null and search_category_cd != ''">
AND CM.CATEGORY_CD = #{search_category_cd}
</if>
<if test="search_product_cd != null and search_product_cd != ''">
AND CM.PRODUCT = #{search_product_cd}
</if>
<if test="search_area_cd != null and search_area_cd != ''">
AND CM.AREA_CD = #{search_area_cd}
</if>
<if test="search_customer_objid != null and search_customer_objid != ''">
AND CM.CUSTOMER_OBJID = #{search_customer_objid}
</if>
<if test="search_change_date_from != null and search_change_date_from != ''">
AND MH.CHANGE_DATE >= TO_DATE(#{search_change_date_from}, 'YYYY-MM-DD')
</if>
<if test="search_change_date_to != null and search_change_date_to != ''">
AND MH.CHANGE_DATE &lt;= TO_DATE(#{search_change_date_to}, 'YYYY-MM-DD') + INTERVAL '1 day'
</if>
ORDER BY MH.CHANGE_DATE DESC
</select>
<!-- M-BOM 변경이력 상세 조회 (개별 히스토리 건) -->
<select id="getMbomHistoryDetail" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MH.OBJID AS HISTORY_OBJID,
MH.MBOM_HEADER_OBJID,
MH.CHANGE_TYPE,
MH.CHANGE_DESCRIPTION,
MH.BEFORE_DATA::TEXT AS BEFORE_DATA,
MH.AFTER_DATA::TEXT AS AFTER_DATA,
TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD HH24:MI:SS') AS CHANGE_DATE,
MH.CHANGE_USER,
COALESCE(
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = MH.CHANGE_USER LIMIT 1),
MH.CHANGE_USER
) AS CHANGE_USER_NAME,
-- PROJECT 정보
PM.PROJECT_NO,
PM.PART_NO,
PM.PART_NAME
FROM MBOM_HISTORY MH
INNER JOIN MBOM_HEADER MHD ON MH.MBOM_HEADER_OBJID = MHD.OBJID
INNER JOIN PROJECT_MGMT PM ON MHD.PROJECT_OBJID = PM.OBJID::VARCHAR
WHERE MH.OBJID::VARCHAR = #{historyObjId}
</select>
</mapper>

View File

@@ -1358,30 +1358,32 @@
DELETE FROM PROCESS_INSPECTION_DETAIL WHERE MASTER_OBJID = #{MASTER_OBJID}
</delete>
<!-- 공정검사 목록 조회 (엑셀용) - 상세 정보 포함 -->
<!-- 공정검사 목록 조회 (엑셀용) - 상세 정보 포함 (디테일 기준) -->
<select id="getProcessInspectionListForExcel" parameterType="map" resultType="map">
SELECT
/* 공정검사 마스터 정보 */
PIM.INSPECTION_DATE
, (SELECT U.USER_NAME FROM USER_INFO U WHERE U.USER_ID = PIM.INSPECTOR_ID) AS INSPECTOR_NAME
, PIM.REMARK AS MASTER_REMARK
/* 검사일, 검사자는 디테일에서 가져옴 */
COALESCE(PID.INSPECTION_DATE, '') AS INSPECTION_DATE
, COALESCE((SELECT U.USER_NAME FROM USER_INFO U WHERE U.USER_ID = PID.INSPECTOR_ID), '') AS INSPECTOR_NAME
, COALESCE((SELECT CODE_NAME(PM.PRODUCT) FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID), '') AS PRODUCT_TYPE
/* 공정검사 디테일 정보 */
, (SELECT CC.CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PID.PROCESS_CD) AS PROCESS_NAME
, (SELECT PM.PROJECT_NO FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID) AS PROJECT_NO
, PID.PART_NO
, PID.PART_NAME
, PID.INSPECTION_QTY
, PID.DEFECT_QTY
, COALESCE((SELECT CC.CODE_NAME FROM COMM_CODE CC WHERE CC.CODE_ID = PID.PROCESS_CD), '') AS PROCESS_NAME
, COALESCE((SELECT PM.PROJECT_NO FROM PROJECT_MGMT PM WHERE PM.OBJID = PID.PROJECT_OBJID), '') AS PROJECT_NO
, COALESCE(PID.PART_NO, '') AS PART_NO
, COALESCE(PID.PART_NAME, '') AS PART_NAME
, COALESCE(PID.INSPECTION_QTY, 0) AS INSPECTION_QTY
, COALESCE(PID.DEFECT_QTY, 0) AS DEFECT_QTY
, CASE WHEN COALESCE(PID.INSPECTION_QTY, 0) > 0
THEN ROUND((COALESCE(PID.DEFECT_QTY, 0)::NUMERIC / PID.INSPECTION_QTY::NUMERIC) * 100, 2)
ELSE NULL END AS DEFECT_RATE
, PID.WORK_ENV_STATUS
, PID.MEASURING_DEVICE
, (SELECT DEPT_NAME FROM DEPT_INFO WHERE DEPT_CODE = PID.DEPT_CD) AS DEPT_NAME
, (SELECT U2.USER_NAME FROM USER_INFO U2 WHERE U2.USER_ID = PID.USER_ID) AS USER_NAME
, PID.REMARK AS DETAIL_REMARK
, PID.ACTION_STATUS
, PID.INSPECTION_RESULT
ELSE 0 END AS DEFECT_RATE
, COALESCE(PID.WORK_ENV_STATUS, '') AS WORK_ENV_STATUS
, COALESCE(PID.MEASURING_DEVICE, '') AS MEASURING_DEVICE
, COALESCE((SELECT DI.DEPT_NAME FROM DEPT_INFO DI WHERE DI.DEPT_CODE = PID.DEPT_CD), '') AS DEPT_NAME
, COALESCE((SELECT U2.USER_NAME FROM USER_INFO U2 WHERE U2.USER_ID = PID.USER_ID), '') AS USER_NAME
, COALESCE(PID.REMARK, '') AS DETAIL_REMARK
, COALESCE(PID.ACTION_STATUS, '') AS ACTION_STATUS
, COALESCE(NULLIF(PID.INSPECTION_RESULT, ''),
(SELECT CASE WHEN COUNT(CASE WHEN PID2.INSPECTION_RESULT = 'NG' THEN 1 END) > 0 THEN 'NG' ELSE 'OK' END
FROM PROCESS_INSPECTION_DETAIL PID2 WHERE PID2.MASTER_OBJID = PIM.OBJID), '') AS INSPECTION_RESULT
FROM PROCESS_INSPECTION_MASTER PIM
INNER JOIN PROCESS_INSPECTION_DETAIL PID ON PID.MASTER_OBJID = PIM.OBJID
WHERE 1=1
@@ -1396,15 +1398,15 @@
</if>
<if test="search_inspector != null and search_inspector != ''">
AND (
UPPER(PIM.INSPECTOR_ID) LIKE UPPER('%' || #{search_inspector} || '%')
OR UPPER((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = PIM.INSPECTOR_ID)) LIKE UPPER('%' || #{search_inspector} || '%')
UPPER(PID.INSPECTOR_ID) LIKE UPPER('%' || #{search_inspector} || '%')
OR UPPER((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = PID.INSPECTOR_ID)) LIKE UPPER('%' || #{search_inspector} || '%')
)
</if>
<if test="search_inspection_date_from != null and search_inspection_date_from != ''">
AND PIM.INSPECTION_DATE <![CDATA[>=]]> #{search_inspection_date_from}
AND PID.INSPECTION_DATE <![CDATA[>=]]> #{search_inspection_date_from}
</if>
<if test="search_inspection_date_to != null and search_inspection_date_to != ''">
AND PIM.INSPECTION_DATE <![CDATA[<=]]> #{search_inspection_date_to}
AND PID.INSPECTION_DATE <![CDATA[<=]]> #{search_inspection_date_to}
</if>
<if test="search_inspection_result != null and search_inspection_result != ''">
AND PID.INSPECTION_RESULT = #{search_inspection_result}
@@ -1540,96 +1542,62 @@
ORDER BY MIN(T.INSPECTION_DATE) DESC, T.INSPECTION_GROUP_ID
</select>
<!-- 반제품검사 엑셀 다운로드용 데이터 조회 (그룹화 후 쉼표로 나열) -->
<!-- 반제품검사 엑셀 다운로드용 데이터 조회 (전체 풀어서 개별 행으로) -->
<select id="getSemiProductInspectionListForExcel" parameterType="map" resultType="map">
SELECT T.INSPECTION_GROUP_ID
, MIN(T.OBJID) AS OBJID
<!-- 품명(모델명): 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.MODEL_NAME, ''), ', ' ORDER BY NULLIF(T.MODEL_NAME, '')) AS MODEL_NAME
<!-- 제품구분: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PRODUCT_TYPE, ''), ', ' ORDER BY NULLIF(T.PRODUCT_TYPE, '')) AS PRODUCT_TYPE
<!-- 작업지시번호: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.WORK_ORDER_NO, ''), ', ' ORDER BY NULLIF(T.WORK_ORDER_NO, '')) AS WORK_ORDER_NO
<!-- 부품품번: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PART_NO, ''), ', ' ORDER BY NULLIF(T.PART_NO, '')) AS PART_NO
<!-- 부품명: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PART_NAME, ''), ', ' ORDER BY NULLIF(T.PART_NAME, '')) AS PART_NAME
<!-- 수량 집계 -->
, SUM(T.RECEIPT_QTY) AS RECEIPT_QTY
, SUM(T.GOOD_QTY) AS GOOD_QTY
, SUM(T.DEFECT_QTY) AS DEFECT_QTY
SELECT SPI.OBJID
, COALESCE(TO_CHAR(SPI.INSPECTION_DATE, 'YYYY-MM-DD'), TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD')) AS INSPECTION_DATE
, COALESCE((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.INSPECTOR),
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.WRITER), '') AS WRITER_NAME
, COALESCE(SPI.MODEL_NAME, '') AS MODEL_NAME
, COALESCE(SPI.PRODUCT_TYPE, '') AS PRODUCT_TYPE
, COALESCE(SPI.WORK_ORDER_NO, '') AS WORK_ORDER_NO
, COALESCE(SPI.PART_NO, '') AS PART_NO
, COALESCE(SPI.PART_NAME, '') AS PART_NAME
, COALESCE(SPI.RECEIPT_QTY, 0) AS RECEIPT_QTY
, COALESCE(SPI.GOOD_QTY, 0) AS GOOD_QTY
, COALESCE(SPI.DEFECT_QTY, 0) AS DEFECT_QTY
, CASE
WHEN SUM(T.RECEIPT_QTY) > 0
THEN ROUND(SUM(T.DEFECT_QTY) * 100.0 / SUM(T.RECEIPT_QTY), 2)
WHEN COALESCE(SPI.RECEIPT_QTY, 0) > 0
THEN ROUND(COALESCE(SPI.DEFECT_QTY, 0) * 100.0 / SPI.RECEIPT_QTY, 2)
ELSE 0
END AS DEFECT_RATE
, SUM(CASE WHEN T.DISPOSITION_TYPE = '수정' THEN T.DEFECT_QTY ELSE 0 END) AS REGENERATION_QTY
, SUM(T.GOOD_QTY) + SUM(CASE WHEN T.DISPOSITION_TYPE = '수정' THEN T.DEFECT_QTY ELSE 0 END) AS FINAL_GOOD_QTY
<!-- 검사일: 쉼표로 나열 -->
, STRING_AGG(DISTINCT T.INSPECTION_DATE_STR, ', ' ORDER BY T.INSPECTION_DATE_STR) AS INSPECTION_DATE
<!-- 검사자: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.WRITER_NAME, ''), ', ' ORDER BY NULLIF(T.WRITER_NAME, '')) AS WRITER_NAME
<!-- 불량유형: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DEFECT_TYPE, ''), ', ' ORDER BY NULLIF(T.DEFECT_TYPE, '')) AS DEFECT_TYPE
<!-- 불량원인: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DEFECT_CAUSE, ''), ', ' ORDER BY NULLIF(T.DEFECT_CAUSE, '')) AS DEFECT_CAUSE
<!-- 귀책부서: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.RESPONSIBLE_DEPT, ''), ', ' ORDER BY NULLIF(T.RESPONSIBLE_DEPT, '')) AS RESPONSIBLE_DEPT
<!-- 처리현황: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.PROCESS_STATUS, ''), ', ' ORDER BY NULLIF(T.PROCESS_STATUS, '')) AS PROCESS_STATUS
<!-- 처리결과: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.DISPOSITION_TYPE, ''), ', ' ORDER BY NULLIF(T.DISPOSITION_TYPE, '')) AS DISPOSITION_TYPE
<!-- 비고: 쉼표로 나열 -->
, STRING_AGG(DISTINCT NULLIF(T.REMARK, ''), ', ' ORDER BY NULLIF(T.REMARK, '')) AS REMARK
FROM (
SELECT SPI.OBJID
, COALESCE(SPI.INSPECTION_GROUP_ID, SPI.OBJID::VARCHAR) AS INSPECTION_GROUP_ID
, COALESCE(SPI.MODEL_NAME, '') AS MODEL_NAME
, COALESCE(SPI.PRODUCT_TYPE, '') AS PRODUCT_TYPE
, COALESCE(SPI.WORK_ORDER_NO, '') AS WORK_ORDER_NO
, COALESCE(SPI.PART_NO, '') AS PART_NO
, COALESCE(SPI.PART_NAME, '') AS PART_NAME
, COALESCE(SPI.RECEIPT_QTY, 0) AS RECEIPT_QTY
, COALESCE(SPI.GOOD_QTY, 0) AS GOOD_QTY
, COALESCE(SPI.DEFECT_QTY, 0) AS DEFECT_QTY
, COALESCE(TO_CHAR(SPI.INSPECTION_DATE, 'YYYY-MM-DD'), TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD')) AS INSPECTION_DATE_STR
, COALESCE((SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.INSPECTOR),
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = SPI.WRITER), '') AS WRITER_NAME
, COALESCE(SPI.DEFECT_TYPE, '') AS DEFECT_TYPE
, COALESCE(SPI.DEFECT_CAUSE, '') AS DEFECT_CAUSE
, COALESCE(SPI.RESPONSIBLE_DEPT, '') AS RESPONSIBLE_DEPT
, COALESCE(SPI.PROCESS_STATUS, '') AS PROCESS_STATUS
, COALESCE(SPI.DISPOSITION_TYPE, '') AS DISPOSITION_TYPE
, COALESCE(SPI.REMARK, '') AS REMARK
FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION SPI
WHERE 1=1
<!-- 품명(모델명) 검색 -->
<if test="search_model_name != null and search_model_name != ''">
AND UPPER(SPI.MODEL_NAME) LIKE UPPER('%' || #{search_model_name} || '%')
</if>
<!-- 작업지시번호 검색 -->
<if test="search_work_order_no != null and search_work_order_no != ''">
AND UPPER(SPI.WORK_ORDER_NO) LIKE UPPER('%' || #{search_work_order_no} || '%')
</if>
<!-- 부품품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(SPI.PART_NO) LIKE UPPER('%' || #{search_part_no} || '%')
</if>
<!-- 부품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(SPI.PART_NAME) LIKE UPPER('%' || #{search_part_name} || '%')
</if>
<!-- 검사일 검색 -->
<if test="search_inspection_date != null and search_inspection_date != ''">
AND TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD') = #{search_inspection_date}
</if>
<!-- 검사자 검색 -->
<if test="search_writer != null and search_writer != ''">
AND SPI.WRITER = #{search_writer}
</if>
) T
GROUP BY T.INSPECTION_GROUP_ID
ORDER BY MIN(T.INSPECTION_DATE_STR) DESC, T.INSPECTION_GROUP_ID
, CASE WHEN SPI.DISPOSITION_TYPE = '수정' THEN COALESCE(SPI.DEFECT_QTY, 0) ELSE 0 END AS REGENERATION_QTY
, COALESCE(SPI.GOOD_QTY, 0) + CASE WHEN SPI.DISPOSITION_TYPE = '수정' THEN COALESCE(SPI.DEFECT_QTY, 0) ELSE 0 END AS FINAL_GOOD_QTY
, COALESCE(SPI.DEFECT_TYPE, '') AS DEFECT_TYPE
, COALESCE(SPI.DEFECT_CAUSE, '') AS DEFECT_CAUSE
, COALESCE(SPI.RESPONSIBLE_DEPT, '') AS RESPONSIBLE_DEPT
, COALESCE(SPI.PROCESS_STATUS, '') AS PROCESS_STATUS
, COALESCE(SPI.DISPOSITION_TYPE, '') AS DISPOSITION_TYPE
, COALESCE(SPI.REMARK, '') AS REMARK
, COALESCE(SPI.DATA_TYPE, '') AS DATA_TYPE
, COALESCE(SPI.INSPECTION_GROUP_ID::VARCHAR, '') AS INSPECTION_GROUP_ID
FROM PMS_QUALITY_SEMI_PRODUCT_INSPECTION SPI
WHERE 1=1
<!-- 품명(모델명) 검색 -->
<if test="search_model_name != null and search_model_name != ''">
AND UPPER(SPI.MODEL_NAME) LIKE UPPER('%' || #{search_model_name} || '%')
</if>
<!-- 작업지시번호 검색 -->
<if test="search_work_order_no != null and search_work_order_no != ''">
AND UPPER(SPI.WORK_ORDER_NO) LIKE UPPER('%' || #{search_work_order_no} || '%')
</if>
<!-- 부품품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(SPI.PART_NO) LIKE UPPER('%' || #{search_part_no} || '%')
</if>
<!-- 부품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(SPI.PART_NAME) LIKE UPPER('%' || #{search_part_name} || '%')
</if>
<!-- 검사일 검색 -->
<if test="search_inspection_date != null and search_inspection_date != ''">
AND TO_CHAR(SPI.REG_DATE, 'YYYY-MM-DD') = #{search_inspection_date}
</if>
<!-- 검사자 검색 -->
<if test="search_writer != null and search_writer != ''">
AND SPI.WRITER = #{search_writer}
</if>
ORDER BY SPI.INSPECTION_DATE DESC, SPI.REG_DATE DESC
</select>
<!-- 반제품검사 품명(모델명) 드롭박스 목록 (CODE, NAME 형태) -->
@@ -1939,6 +1907,8 @@
, CCS.REMARK
, CCS.ACTION_DATE
, CCS.ACTION_USER_ID
, CCS.WRITER
, USER_NAME(CCS.WRITER) as WRITER_NAME
, (SELECT USER_NAME FROM USER_INFO WHERE USER_ID = CCS.ACTION_USER_ID) AS ACTION_USER_NAME
, (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = CCS.OBJID AND AFI.DOC_TYPE = 'CUSTOMER_CS_IMAGE' AND STATUS = 'Active') AS IMAGE_FILE
, (SELECT COUNT(*) FROM ATTACH_FILE_INFO AFI WHERE AFI.TARGET_OBJID = CCS.OBJID AND AFI.DOC_TYPE = 'CUSTOMER_CS_ACTION' AND STATUS = 'Active') AS ATTACH_FILE
@@ -2026,14 +1996,19 @@
, (SELECT USER_NAME FROM USER_INFO WHERE USER_ID = CCS.ACTION_USER_ID) AS ACTION_USER_NAME
, CCS.ATTACH_FILE_OBJID
, CCS.WRITER
, USER_NAME(CCS.WRITER) AS WRITER_NAME
, TO_CHAR(CCS.REG_DATE, 'YYYY-MM-DD') AS REG_DATE
FROM CUSTOMER_CS CCS
WHERE CCS.OBJID = #{OBJID}
</select>
<!-- 고객 CS 시퀀스 조회 -->
<select id="getCustomerCsSeq" resultType="int">
SELECT NEXTVAL('SEQ_CUSTOMER_CS_RECEIPT_NO')
<!-- 고객 CS 날짜별 순번 조회 (해당 날짜의 다음 순번 반환) -->
<select id="getCustomerCsSeqByDate" parameterType="map" resultType="int">
SELECT COALESCE(MAX(
CAST(SPLIT_PART(RECEIPT_NO, '-', 3) AS INTEGER)
), 0) + 1
FROM CUSTOMER_CS
WHERE RECEIPT_NO LIKE 'CS-' || #{TARGET_DATE} || '-%'
</select>
<!-- 고객 CS 등록 -->

View File

@@ -1547,9 +1547,12 @@ public class QualityService extends BaseService{
paramMap.put("OBJID", objId);
}
// 접수번호 생성 (CS-YYYYMMDD-SEQ)
// 접수번호 생성 (CS-YYYYMMDD-날짜별순번)
String today = new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date());
String receiptNo = "CS-" + today + "-" + String.format("%03d", sqlSession.selectOne("quality.getCustomerCsSeq"));
Map<String, Object> seqParam = new HashMap<String, Object>();
seqParam.put("TARGET_DATE", today);
int dailySeq = (int) sqlSession.selectOne("quality.getCustomerCsSeqByDate", seqParam);
String receiptNo = "CS-" + today + "-" + String.format("%03d", dailySeq);
paramMap.put("RECEIPT_NO", receiptNo);
sqlSession.insert("quality.insertCustomerCs", paramMap);