Merge pull request 'V2025121901' (#110) from V2025121901 into main

Reviewed-on: #110
This commit was merged in pull request #110.
This commit is contained in:
2025-12-22 02:30:56 +00:00
7 changed files with 1271 additions and 22 deletions

View File

@@ -0,0 +1,391 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<%
// DB에서 메뉴명 조회 (공통 유틸 사용)
String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "원자재소요량");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
<style>
body, html {
overflow-x: hidden;
width: 100%;
margin: 0;
padding: 0;
}
.section-title {
font-weight: bold;
font-size: 13px;
margin: 10px 0 8px 0;
color: #333;
border-left: 3px solid #3498db;
padding-left: 8px;
}
#inputGrid {
margin-bottom: 10px;
}
</style>
</head>
<body>
<script type="text/javascript">
// 입력 그리드 (M-BOM 선택 + 수량 입력)
var inputGrid;
// 결과 그리드 (소요량 목록)
var resultGrid;
// 입력 행 번호
var rowSeq = 0;
// M-BOM 옵션 데이터
var mbomOptionsData = {};
$(document).ready(function(){
$('.select2').select2();
// M-BOM 옵션 데이터 초기화
fn_initMbomOptions();
// 입력 그리드 초기화
fn_initInputGrid();
// 결과 그리드 초기화
fn_initResultGrid();
// 조회 버튼
$("#btnSearch").click(function(){
fn_search();
});
// 행 추가 버튼
$("#btnAddRow").click(function(){
fn_addInputRow();
});
// 행 삭제 버튼
$("#btnDeleteRow").click(function(){
fn_deleteInputRow();
});
});
// M-BOM 옵션 데이터 초기화
function fn_initMbomOptions() {
mbomOptionsData = {"": "선택"};
$("#MBOM_SELECT_HIDDEN option").each(function(){
var val = $(this).val();
var text = $(this).text();
if(val !== '') {
mbomOptionsData[val] = text;
}
});
console.log("M-BOM 옵션:", mbomOptionsData);
}
// 입력 그리드 초기화
function fn_initInputGrid() {
// 초기 데이터 1행 포함
var initialData = [{
ROW_ID: 1,
MBOM_OBJID: "",
PART_NAME: "",
QTY: 1
}];
rowSeq = 1;
inputGrid = new Tabulator("#inputGrid", {
data: initialData,
layout: "fitColumns",
height: "180px",
columns: [
{
title: '',
field: 'CHK',
width: 40,
headerSort: false,
hozAlign: 'center',
formatter: function(cell) {
return '<input type="checkbox" class="row-check">';
}
},
{title: 'ROW_ID', field: 'ROW_ID', visible: false},
{
title: 'M-BOM 선택',
field: 'MBOM_OBJID',
headerHozAlign: 'center',
headerSort: false,
editor: "list",
editorParams: function(cell) {
return {
values: mbomOptionsData,
autocomplete: true,
clearable: true,
listOnEmpty: true
};
},
formatter: function(cell) {
var value = cell.getValue();
return mbomOptionsData[value] || "";
},
cellEdited: function(cell) {
// M-BOM 선택 시 품명 자동 입력
var mbomObjid = cell.getValue();
var row = cell.getRow();
if(mbomObjid) {
var mbomName = mbomOptionsData[mbomObjid] || "";
row.update({PART_NAME: mbomName});
} else {
row.update({PART_NAME: ""});
}
}
},
{
title: '품명',
field: 'PART_NAME',
headerHozAlign: 'center',
headerSort: false,
editor: false
},
{
title: '수량',
field: 'QTY',
width: 200,
headerHozAlign: 'center',
headerSort: false,
hozAlign: 'right',
editor: "number",
editorParams: {
min: 1,
step: 1
}
}
],
placeholder: "M-BOM을 선택하고 수량을 입력하세요."
});
}
// 결과 그리드 초기화
function fn_initResultGrid() {
resultGrid = new Tabulator("#resultGrid", {
data: [],
layout: "fitColumns",
height: "calc(100vh - 450px)",
pagination: false, // 페이지네이션 비활성화 (전체 데이터 표시)
columns: [
{
title: '구분',
field: 'CATEGORY_NAME',
width: 150,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'center'
},
{
title: '품번',
field: 'PART_NO',
width: 320,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'left'
},
{
title: '품명',
field: 'PART_NAME',
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'left'
},
{
title: '소요량',
field: 'REQUIRED_QTY',
width: 150,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'right',
formatter: function(cell) {
var val = cell.getValue();
if(val != null && val !== '') {
return Number(val).toLocaleString();
}
return '';
}
},
{
title: '소재',
field: 'MATERIAL',
width: 150,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'center'
},
{
title: '사이즈',
field: 'SPEC',
width: 150,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'center'
},
{
title: '소재품번',
field: 'MATERIAL_PART_NO',
width: 250,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'left'
},
{
title: '소재소요량',
field: 'MATERIAL_REQUIRED_QTY',
width: 150,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'right',
formatter: function(cell) {
var val = cell.getValue();
if(val != null && val !== '') {
return Number(val).toLocaleString();
}
return '';
}
}
],
placeholder: "조회 결과가 없습니다."
});
}
// 입력 행 추가
function fn_addInputRow() {
rowSeq++;
inputGrid.addRow({
ROW_ID: rowSeq,
MBOM_OBJID: "",
PART_NAME: "",
QTY: 1
});
}
// 입력 행 삭제
function fn_deleteInputRow() {
var rows = inputGrid.getRows();
var deleteRows = [];
rows.forEach(function(row) {
var el = row.getElement();
var checkbox = el.querySelector('.row-check');
if(checkbox && checkbox.checked) {
deleteRows.push(row);
}
});
if(deleteRows.length === 0) {
Swal.fire('삭제할 행을 선택해주세요.');
return;
}
deleteRows.forEach(function(row) {
row.delete();
});
}
// 조회
function fn_search() {
var inputData = inputGrid.getData();
// 유효한 입력만 필터링
var mbomItems = [];
inputData.forEach(function(row) {
if(row.MBOM_OBJID && row.QTY > 0) {
mbomItems.push({
mbomObjid: row.MBOM_OBJID,
qty: row.QTY
});
}
});
if(mbomItems.length === 0) {
Swal.fire('M-BOM을 선택하고 수량을 입력해주세요.');
return;
}
// AJAX 조회
$.ajax({
url: "/productionplanning/getRawMaterialRequirementList.do",
type: "POST",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify({mbomItems: mbomItems}),
dataType: "json",
success: function(data) {
console.log("=== 서버 응답 데이터 ===");
console.log("전체 응답:", data);
console.log("리스트 개수:", data.list ? data.list.length : 0);
console.log("리스트 데이터:", data.list);
if(data.result === "success") {
resultGrid.setData(data.list || []);
var cnt = data.list ? data.list.length : 0;
$("#resultCnt").text(cnt);
console.log("그리드 데이터 개수:", resultGrid.getData().length);
if(cnt === 0) {
Swal.fire({
title: '조회 결과 없음',
text: '구매품 항목이 없습니다.',
icon: 'info'
});
}
} else {
Swal.fire('조회 실패: ' + (data.msg || ''));
resultGrid.setData([]);
$("#resultCnt").text("0");
}
},
error: function(xhr, status, error) {
console.error('조회 오류:', error);
Swal.fire('조회 중 오류가 발생했습니다.');
resultGrid.setData([]);
}
});
}
</script>
<form name="form1" id="form1" method="post">
<!-- M-BOM 옵션을 위한 숨김 select -->
<select id="MBOM_SELECT_HIDDEN" style="display:none;">
<option value="">선택</option>
${code_map.mbom_list}
</select>
<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="btnAddRow">
<input type="button" class="plm_btns" value="행 삭제" id="btnDeleteRow">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>
</div>
<!-- 입력 영역 -->
<div id="plmSearchZon">
<div class="section-title">M-BOM 선택 및 수량 입력</div>
<div id="inputGrid" style="margin-right: 16px;"></div>
</div>
<!-- 결과 그리드 영역 -->
<div class="section-title" style="margin-left: 16px;">원자재 소요량 (구매품, 원소재) - 조회결과: <span id="resultCnt">0</span>건</div>
<div id="resultGrid" style="margin: 0 16px;"></div>
</div>
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,344 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<%
// DB에서 메뉴명 조회 (공통 유틸 사용)
String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "반제품소요량");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
<style>
body, html {
overflow-x: hidden;
width: 100%;
margin: 0;
padding: 0;
}
.section-title {
font-weight: bold;
font-size: 13px;
margin: 10px 0 8px 0;
color: #333;
border-left: 3px solid #3498db;
padding-left: 8px;
}
#inputGrid {
margin-bottom: 10px;
}
</style>
</head>
<body>
<script type="text/javascript">
// 입력 그리드 (M-BOM 선택 + 수량 입력)
var inputGrid;
// 결과 그리드 (소요량 목록)
var resultGrid;
// 입력 행 번호
var rowSeq = 0;
// M-BOM 옵션 데이터
var mbomOptionsData = {};
$(document).ready(function(){
$('.select2').select2();
// M-BOM 옵션 데이터 초기화
fn_initMbomOptions();
// 입력 그리드 초기화
fn_initInputGrid();
// 결과 그리드 초기화
fn_initResultGrid();
// 조회 버튼
$("#btnSearch").click(function(){
fn_search();
});
// 행 추가 버튼
$("#btnAddRow").click(function(){
fn_addInputRow();
});
// 행 삭제 버튼
$("#btnDeleteRow").click(function(){
fn_deleteInputRow();
});
});
// M-BOM 옵션 데이터 초기화
function fn_initMbomOptions() {
mbomOptionsData = {"": "선택"};
$("#MBOM_SELECT_HIDDEN option").each(function(){
var val = $(this).val();
var text = $(this).text();
if(val !== '') {
mbomOptionsData[val] = text;
}
});
console.log("M-BOM 옵션:", mbomOptionsData);
}
// 입력 그리드 초기화
function fn_initInputGrid() {
// 초기 데이터 1행 포함
var initialData = [{
ROW_ID: 1,
MBOM_OBJID: "",
PART_NAME: "",
QTY: 1
}];
rowSeq = 1;
inputGrid = new Tabulator("#inputGrid", {
data: initialData,
layout: "fitColumns",
height: "180px",
columns: [
{
title: '',
field: 'CHK',
width: 40,
headerSort: false,
hozAlign: 'center',
formatter: function(cell) {
return '<input type="checkbox" class="row-check">';
}
},
{title: 'ROW_ID', field: 'ROW_ID', visible: false},
{
title: 'M-BOM 선택',
field: 'MBOM_OBJID',
headerHozAlign: 'center',
headerSort: false,
editor: "list",
editorParams: function(cell) {
return {
values: mbomOptionsData,
autocomplete: true,
clearable: true,
listOnEmpty: true
};
},
formatter: function(cell) {
var value = cell.getValue();
return mbomOptionsData[value] || "";
},
cellEdited: function(cell) {
// M-BOM 선택 시 품명 자동 입력
var mbomObjid = cell.getValue();
var row = cell.getRow();
if(mbomObjid) {
var mbomName = mbomOptionsData[mbomObjid] || "";
row.update({PART_NAME: mbomName});
} else {
row.update({PART_NAME: ""});
}
}
},
{
title: '품명',
field: 'PART_NAME',
headerHozAlign: 'center',
headerSort: false,
editor: false
},
{
title: '수량',
field: 'QTY',
width: 200,
headerHozAlign: 'center',
headerSort: false,
hozAlign: 'right',
editor: "number",
editorParams: {
min: 1,
step: 1
}
}
],
placeholder: "M-BOM을 선택하고 수량을 입력하세요."
});
}
// 결과 그리드 초기화
function fn_initResultGrid() {
resultGrid = new Tabulator("#resultGrid", {
data: [],
layout: "fitColumns",
height: "calc(100vh - 450px)",
columns: [
{
title: '구분',
field: 'CATEGORY_NAME',
width: 200,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'center'
},
{
title: '품번',
field: 'PART_NO',
width: 320,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'left'
},
{
title: '품명',
field: 'PART_NAME',
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'left'
},
{
title: '소요량',
field: 'REQUIRED_QTY',
width: 200,
headerHozAlign: 'center',
headerSort: true,
hozAlign: 'right',
formatter: function(cell) {
var val = cell.getValue();
if(val != null && val !== '') {
return Number(val).toLocaleString();
}
return '';
}
}
],
placeholder: "조회 결과가 없습니다."
});
}
// 입력 행 추가
function fn_addInputRow() {
rowSeq++;
inputGrid.addRow({
ROW_ID: rowSeq,
MBOM_OBJID: "",
PART_NAME: "",
QTY: 1
});
}
// 입력 행 삭제
function fn_deleteInputRow() {
var rows = inputGrid.getRows();
var deleteRows = [];
rows.forEach(function(row) {
var el = row.getElement();
var checkbox = el.querySelector('.row-check');
if(checkbox && checkbox.checked) {
deleteRows.push(row);
}
});
if(deleteRows.length === 0) {
Swal.fire('삭제할 행을 선택해주세요.');
return;
}
deleteRows.forEach(function(row) {
row.delete();
});
}
// 조회
function fn_search() {
var inputData = inputGrid.getData();
// 유효한 입력만 필터링
var mbomItems = [];
inputData.forEach(function(row) {
if(row.MBOM_OBJID && row.QTY > 0) {
mbomItems.push({
mbomObjid: row.MBOM_OBJID,
qty: row.QTY
});
}
});
if(mbomItems.length === 0) {
Swal.fire('M-BOM을 선택하고 수량을 입력해주세요.');
return;
}
// AJAX 조회
$.ajax({
url: "/productionplanning/getSemiProductRequirementList.do",
type: "POST",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify({mbomItems: mbomItems}),
dataType: "json",
success: function(data) {
if(data.result === "success") {
resultGrid.setData(data.list || []);
var cnt = data.list ? data.list.length : 0;
$("#resultCnt").text(cnt);
if(cnt === 0) {
Swal.fire({
title: '조회 결과 없음',
text: '부품 또는 조립품 항목이 없습니다.',
icon: 'info'
});
}
} else {
Swal.fire('조회 실패: ' + (data.msg || ''));
resultGrid.setData([]);
$("#resultCnt").text("0");
}
},
error: function(xhr, status, error) {
console.error('조회 오류:', error);
Swal.fire('조회 중 오류가 발생했습니다.');
resultGrid.setData([]);
}
});
}
</script>
<form name="form1" id="form1" method="post">
<!-- M-BOM 옵션을 위한 숨김 select -->
<select id="MBOM_SELECT_HIDDEN" style="display:none;">
<option value="">선택</option>
${code_map.mbom_list}
</select>
<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="btnAddRow">
<input type="button" class="plm_btns" value="행 삭제" id="btnDeleteRow">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>
</div>
<!-- 입력 영역 -->
<div id="plmSearchZon">
<div class="section-title">M-BOM 선택 및 수량 입력</div>
<div id="inputGrid" style="margin-right: 16px;"></div>
</div>
<!-- 결과 그리드 영역 -->
<div class="section-title" style="margin-left: 16px;">반제품 소요량 (부품/조립품) - 조회결과: <span id="resultCnt">0</span>건</div>
<div id="resultGrid" style="margin: 0 16px;"></div>
</div>
</div>
</form>
</body>
</html>

View File

@@ -85,7 +85,7 @@ body, html {
<tr>
<td>
<label>프로젝트번호:</label>
<span id="infoProjectNo">${resultMap.PROJECT_NO}</span>
<span id="infoProjectNo">${resultMap.PROJECT_NUMBER}</span>
</td>
<td>
<label>고객사:</label>
@@ -115,7 +115,7 @@ body, html {
</td>
<td>
<label>입고요청일:</label>
<span id="infoDeliveryDate">${resultMap.DELIVERY_REQUEST_DATE}</span>
<span id="infoDeliveryDate">${resultMap.DUE_DATE}</span>
</td>
</tr>
</table>
@@ -134,6 +134,7 @@ var projectMgmtObjid = "${resolvedProjectId}";
var bomReportObjid = "${resolvedBomReportObjid}";
var mbomHeaderObjid = "${resolvedMbomHeaderObjid}"; // MBOM_HEADER.OBJID (M-BOM 관리 화면에서 사용하는 ID)
var vendorList = []; // 공급업체 목록
var processingVendorList = []; // 가공업체 목록 (Select2용 배열)
// 디버그: resultMap 내용 확인 (주석처리)
// console.log("=== JSP resultMap 디버그 ===");
@@ -156,8 +157,17 @@ function logError(){
}
$(document).ready(function(){
// 공급업체 목록 먼저 로드
// 공급업체 목록 로드 (가공업체도 동일 테이블 사용)
fn_loadVendorList(function(){
// vendorList에서 processingVendorList 변환 (Select2용 배열 형태)
processingVendorList = [];
for(var key in vendorList) {
if(key !== '') { // 빈 값 제외
processingVendorList.push({id: key, text: vendorList[key]});
}
}
console.log("가공업체 목록 변환 완료:", processingVendorList.length + "개");
fn_initGrid();
logDebug("purchaseListFormPopUp :: grid initialized");
fn_loadInitialData();
@@ -196,6 +206,7 @@ function fn_loadVendorList(callback) {
});
}
function fn_loadInitialData(){
logDebug("purchaseListFormPopUp :: fn_loadInitialData start",
"projectMgmtObjid=", projectMgmtObjid,
@@ -224,6 +235,62 @@ function fn_loadInitialData(){
}
}
// Select2 커스텀 에디터 생성 함수
function createSelect2Editor(options) {
return function(cell, onRendered, success, cancel, editorParams) {
var cellValue = cell.getValue();
var container = document.createElement("span");
var select = document.createElement("select");
select.style.width = "100%";
// 빈 옵션 추가
var emptyOption = document.createElement("option");
emptyOption.value = "";
emptyOption.text = "선택";
select.appendChild(emptyOption);
// 옵션 추가
options.forEach(function(opt) {
var option = document.createElement("option");
if(typeof opt === 'object') {
option.value = opt.id || opt.value || '';
option.text = opt.text || opt.label || '';
} else {
option.value = opt;
option.text = opt;
}
if(option.value == cellValue) {
option.selected = true;
}
select.appendChild(option);
});
container.appendChild(select);
onRendered(function() {
$(select).select2({
width: '100%',
dropdownAutoWidth: true,
allowClear: true,
placeholder: '선택'
});
$(select).select2('open');
});
$(select).on('select2:select select2:clear', function(e) {
success($(select).val());
});
$(select).on('select2:close', function() {
setTimeout(function() {
success($(select).val());
}, 100);
});
return container;
};
}
// Tabulator 그리드 초기화
function fn_initGrid() {
var columns = [
@@ -462,13 +529,34 @@ function fn_initGrid() {
return value ? Number(value).toLocaleString() : '0';
}
},
// 24. 가공업체
// 24. 가공업체 (수정가능 - Select2 에디터)
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '가공업체',
field: 'PROCESSING_VENDOR'
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">가공업체</span>',
field: 'PROCESSING_VENDOR',
editor: function(cell, onRendered, success, cancel, editorParams) {
// Select2 에디터
return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams);
},
formatter: function(cell) {
var value = cell.getValue();
// 저장된 값이 없으면 기본값 '5001'(RPS) 설정
if(value === undefined || value === null || value === '') {
value = '5001';
cell.getRow().update({PROCESSING_VENDOR: value}, false);
}
// OBJID로 업체명 조회하여 표시
for(var i = 0; i < processingVendorList.length; i++) {
if(processingVendorList[i].id == value) {
return processingVendorList[i].text;
}
}
return value;
}
},
/* // 25. 가공납기 - 주석처리
{
@@ -550,28 +638,27 @@ function fn_initGrid() {
return value ? Number(value).toLocaleString() : '0';
}
},
// 30. 공급업체 (수정가능 - 기준정보에서 선택)
// 30. 공급업체 (수정가능 - Select2 에디터)
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">공급업체</span>',
field: 'VENDOR_PM',
editor: 'list',
editorParams: function(cell) {
return {
values: vendorList,
autocomplete: true,
listOnEmpty: true,
freetext: true,
allowEmpty: true
};
editor: function(cell, onRendered, success, cancel, editorParams) {
// Select2 에디터 (가공업체와 동일한 목록 사용)
return createSelect2Editor(processingVendorList)(cell, onRendered, success, cancel, editorParams);
},
formatter: function(cell) {
var value = cell.getValue();
if(!value) return '';
// vendorList에서 해당 값의 이름 찾기
return vendorList[value] || value;
// processingVendorList에서 해당 값의 이름 찾기
for(var i = 0; i < processingVendorList.length; i++) {
if(processingVendorList[i].id == value) {
return processingVendorList[i].text;
}
}
return value;
}
},
// 31. 단가 (수정가능)