생산계획&실적관리(장비) 메뉴 추가, wbs 할당 기능 추가

This commit is contained in:
2026-03-16 15:25:07 +09:00
parent e017520b60
commit b5045b45c2
6 changed files with 1734 additions and 2 deletions

View File

@@ -0,0 +1,587 @@
<%@ 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" %>
<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, "생산계획&실적 관리(장비)");
%>
<!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;
}
.select2-selection__choice {
font-size: 11px;
background-color: #fff !important;
border: none !important;
margin-right: 0px !important;
}
.select2-selection__choice__remove {
display: contents !important;
}
.select2-container .select2-selection--multiple {
min-height: 20px !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
margin-top: 3.5px !important;
}
.select2-selection__rendered {
height: 18px !important;
}
.select2-container .select2-selection--multiple .select2-selection__rendered {
overflow: auto !important;
}
/* WBS할당 모달 */
.wbs-modal-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.4);
z-index: 9998;
}
.wbs-modal {
display: none;
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background: #fff;
border: 2px solid #FFA500;
border-radius: 4px;
width: 420px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
.wbs-modal-header {
background-color: #FFA500;
color: #fff;
font-weight: bold;
padding: 8px 12px;
font-size: 13px;
}
.wbs-modal-body {
padding: 15px 20px;
}
.wbs-modal-body .wbs-desc {
font-size: 12px;
color: #666;
margin-bottom: 12px;
}
.wbs-modal-body table td {
padding: 5px 5px;
}
.wbs-modal-body select {
width: 220px;
}
.wbs-modal-footer {
text-align: center;
padding: 10px;
border-top: 1px solid #eee;
}
</style>
</head>
<body>
<script type="text/javascript">
// Tabulator 그리드 전역 변수
var _tabulGrid;
$(document).ready(function(){
_fnc_datepick();
$('.select2').select2();
// 품번/품명 Select2 AJAX 초기화
initPartSelect2Ajax("#search_part_no", "#search_part_name", "#search_part_objid");
// Enter 키로 검색
$("#plmSearchZon input").keyup(function(e) {
if (e.keyCode == 13) {
fn_search();
}
});
// 조회 버튼
$("#btnSearch").click(function(){
fn_search();
});
// WBS할당 버튼
$("#btnWbsAssign").click(function(){
fn_toggleWbsAssign();
});
// 초기 조회
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
});
}
}
// 그리드 컬럼 정의
var columns = [
{title:'OBJID', field:'OBJID', visible: false, frozen: true},
// 프로젝트번호
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 130,
title: '프로젝트번호',
field: 'PROJECT_NO',
frozen: true,
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell){
var orderNo = cell.getData().PROJECT_NO;
fn_openSaleRegPopup(orderNo, "detail");
}
},
// 제품구분
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '제품구분',
field: 'PRODUCT_NAME'
},
// 주문유형
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '주문유형',
field: 'CATEGORY_CODE_NAME'
},
// 생산유형
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '생산유형',
field: 'PRODUCTION_TYPE_NAME'
},
// 고객사
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '고객사',
field: 'CUSTOMER_NAME'
},
// 요청납기
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 90,
title: '요청납기',
field: 'REQ_DEL_DATE'
},
// 고객사요청사항
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 230,
title: '고객사요청사항',
field: 'CUSTOMER_REQUEST'
},
// 품번
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO'
},
// 품명
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 180,
title: '품명',
field: 'PART_NAME'
},
// S/N
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: 'S/N',
field: 'SERIAL_NO'
},
// 생산WBS
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '생산WBS',
field: 'PROD_WBS_CNT',
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
var cnt = cell.getValue();
if(!objid) return;
if(!cnt || cnt == 0) { Swal.fire({title:'알림', text:'WBS할당을 먼저 진행해주세요.', icon:'info'}); return; }
wbs_popup(objid, 'PRODUCE');
}
},
// 생산진척율(%)
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 110,
title: '생산진척율(%)',
field: 'PROD_PROGRESS_RATE',
formatter: function(cell) {
var val = cell.getValue();
if(val == null || val === '') return '';
return val + '%';
}
},
// 납품WBS
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '납품WBS',
field: 'DELV_WBS_CNT',
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
var cnt = cell.getValue();
if(!objid) return;
if(!cnt || cnt == 0) { Swal.fire({title:'알림', text:'WBS할당을 먼저 진행해주세요.', icon:'info'}); return; }
wbs_popup(objid, 'SHIP');
}
},
// 납품진척율(%)
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 110,
title: '납품진척율(%)',
field: 'DELV_PROGRESS_RATE',
formatter: function(cell) {
var val = cell.getValue();
if(val == null || val === '') return '';
return val + '%';
}
}
];
// 검색 함수
function fn_search(){
var projectNos = $("#search_project_no").val();
if(projectNos && projectNos.length > 0) {
$("#search_project_nos_hidden").val(projectNos.join(","));
} else {
$("#search_project_nos_hidden").val("");
}
_tabulGrid = fnc_tabul_search(_tabul_layout_fitDataStretch, _tabulGrid, "/productionplanning/prodPlanResultMgmtEquipGridList.do", columns, true);
}
// WBS할당 모달 열기
function fn_toggleWbsAssign() {
var checkedRows = getCheckedRows();
if(checkedRows.length === 0) {
Swal.fire({title: '선택 필요', text: 'WBS를 할당할 프로젝트를 선택해주세요.', icon: 'warning'});
return;
}
if(checkedRows.length > 1) {
Swal.fire({title: '알림', text: '한 번에 하나의 프로젝트만 선택해주세요.', icon: 'info'});
return;
}
var row = checkedRows[0];
var hasProd = row.PROD_WBS_CNT && row.PROD_WBS_CNT > 0;
var hasDelv = row.DELV_WBS_CNT && row.DELV_WBS_CNT > 0;
if(hasProd || hasDelv) {
var msg = '이미 WBS가 할당되어 있습니다.\n재할당 시 기존 데이터(담당자, 일정, 진척율 등)가 모두 삭제됩니다.\n계속하시겠습니까?';
Swal.fire({title:'재할당 경고', text: msg, icon:'warning', showCancelButton:true, confirmButtonText:'재할당', cancelButtonText:'취소'
}).then(function(result){
if(result.isConfirmed) fn_openWbsModal(row.OBJID);
});
} else {
fn_openWbsModal(row.OBJID);
}
}
function fn_openWbsModal(objid) {
$("#wbsModal").data("projectObjid", objid);
$("#wbs_produce").val("");
$("#wbs_delivery").val("");
$("#wbsModalOverlay").fadeIn(200);
$("#wbsModal").fadeIn(200);
}
// WBS할당 모달 닫기
function fn_closeWbsModal() {
$("#wbsModal").fadeOut(200);
$("#wbsModalOverlay").fadeOut(200);
}
// WBS할당 저장 (템플릿 할당)
function fn_saveWbsAssign() {
var projectObjid = $("#wbsModal").data("projectObjid");
var prodTemplate = $("#wbs_produce").val();
var delvTemplate = $("#wbs_delivery").val();
if(!prodTemplate && !delvTemplate) {
Swal.fire({title: '알림', text: '생산WBS 또는 납품WBS를 선택해주세요.', icon: 'warning'});
return;
}
var requests = [];
if(prodTemplate) {
requests.push({ projectObjid: projectObjid, wbsType: 'PRODUCE', templateObjid: prodTemplate });
}
if(delvTemplate) {
requests.push({ projectObjid: projectObjid, wbsType: 'SHIP', templateObjid: delvTemplate });
}
var completed = 0;
var hasError = false;
for(var i=0; i<requests.length; i++) {
(function(req){
// 템플릿 태스크 조회 후 저장
$.ajax({
url: "/productionplanning/getWbsTemplateTasks.do",
type: "POST", data: { templateObjid: req.templateObjid },
dataType: "json",
success: function(result){
if(result.list && result.list.length > 0){
var tasks = [];
for(var j=0; j<result.list.length; j++){
var d = result.list[j];
tasks.push({
objid: '', taskName: d.TASK_NAME || d.task_name || '',
taskSeq: j+1, taskLevel: d.TASK_LEVEL || d.task_level || '',
unitNo: d.UNIT_NO || d.unit_no || '',
upperTaskObjid: d.UPPER_TASK_OBJID || d.upper_task_objid || '',
templateTaskObjid: d.OBJID || d.objid || '',
userId: '', planStart: '', planEnd: '', actStart: '', actEnd: '', remark: ''
});
}
$.ajax({
url: "/productionplanning/saveEquipWbsAssign.do",
type: "POST", contentType: "application/json",
data: JSON.stringify({ projectObjid: req.projectObjid, wbsType: req.wbsType, templateObjid: req.templateObjid, tasks: tasks }),
dataType: "json",
success: function(res){
if(!res.success) hasError = true;
completed++;
if(completed === requests.length) fn_wbsAssignDone(hasError);
},
error: function(){ hasError = true; completed++; if(completed === requests.length) fn_wbsAssignDone(hasError); }
});
}
}
});
})(requests[i]);
}
}
function fn_wbsAssignDone(hasError) {
fn_closeWbsModal();
if(hasError) {
Swal.fire({title:'오류', text:'일부 할당에 실패했습니다.', icon:'error'});
} else {
Swal.fire({title:'완료', text:'WBS 할당이 완료되었습니다.', icon:'success'});
}
fn_search();
}
// 폴더 아이콘 클릭 → 상세 편집 팝업
function wbs_popup(objId, wbsType){
var popup_width = 1200;
var popup_height = 600;
var url = "/productionplanning/prodPlanWbsAssignPopup.do?projectObjid=" + objId + "&wbsType=" + wbsType;
fn_centerPopup(popup_width, popup_height, url, 'wbsAssignPopup');
}
// 프로젝트 상세 팝업
function fn_openProjectDetailPopup(objid) {
var popup_width = 1000;
var popup_height = 550;
var url = "/salesMgmt/salesRegForm.do?orderNo=&saleNo=detail&objid=" + objid;
fn_centerPopup(popup_width, popup_height, url);
}
// 선택된 행 가져오기
function getCheckedRows() {
if(_tabulGrid) {
return _tabulGrid.getSelectedData();
}
return [];
}
</script>
<!-- hiddenForm -->
<form name="hiddenForm" id="hiddenForm" method="post">
<input type="hidden" name="OBJID" id="OBJID">
<input type="hidden" name="actionType" id="actionType">
</form>
<form name="form1" id="form1" method="post">
<input type="hidden" name="actionType" id="actionType">
<input type="hidden" name="search_project_nos" id="search_project_nos_hidden">
<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">
<input type="button" class="plm_btns" value="WBS할당" id="btnWbsAssign">
</div>
</div>
<!-- 검색 영역 -->
<div id="plmSearchZon">
<table>
<tr>
<td><label>프로젝트번호</label></td>
<td>
<select name="search_project_no" id="search_project_no" class="select2" style="width:235px;" multiple="multiple">
<option value="">선택</option>
${code_map.project_no}
</select>
</td>
<td><label>제품구분</label></td>
<td>
<select name="search_product_code" id="search_product_code" class="select2" style="">
<option value="">전체</option>
${code_map.product_cd}
</select>
</td>
<td><label>주문유형</label></td>
<td>
<select name="search_category_code" id="search_category_code" class="select2" style="">
<option value="">전체</option>
${code_map.category_cd}
</select>
</td>
<td><label>생산유형</label></td>
<td>
<select name="search_production_type" id="search_production_type" class="select2" style="">
<option value="">전체</option>
${code_map.production_type_cd}
</select>
</td>
<td><label>고객사</label></td>
<td>
<select name="search_customer_objid" id="search_customer_objid" class="select2" style="width:250px;">
<option value="">전체</option>
${code_map.customer_cd}
</select>
</td>
</tr>
<tr>
<td><label>요청납기</label></td>
<td>
<input type="text" name="search_req_del_date_from" id="search_req_del_date_from" class="date_icon" style="width:110px;" autocomplete="off">~
<input type="text" name="search_req_del_date_to" id="search_req_del_date_to" class="date_icon" style="width:110px;" autocomplete="off">
</td>
<td><label>품번</label></td>
<td>
<select name="search_part_no" id="search_part_no" class="select2-part" style="width:130px;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_part_objid" id="search_part_objid" value="">
</td>
<td><label>품명</label></td>
<td>
<select name="search_part_name" id="search_part_name" class="select2-part" style="width:130px;">
<option value="">품명 선택</option>
</select>
</td>
<td><label>S/N</label></td>
<td><input type="text" name="search_serial_no" id="search_serial_no" style="" autocomplete="off"></td>
</tr>
</table>
</div>
<!-- 그리드 영역 -->
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</form>
<!-- WBS할당 모달 -->
<div id="wbsModalOverlay" class="wbs-modal-overlay" onclick="fn_closeWbsModal()"></div>
<div id="wbsModal" class="wbs-modal">
<div class="wbs-modal-header">WBS할당</div>
<div class="wbs-modal-body">
<div class="wbs-desc">└ 선택후 WBS 할당 버튼 클릭하여 WBS 할당</div>
<table>
<tr>
<td>생산WBS</td>
<td>
<select id="wbs_produce">
<option value="">선택</option>
${code_map.wbs_template}
</select>
</td>
</tr>
<tr>
<td>납품WBS</td>
<td>
<select id="wbs_delivery">
<option value="">선택</option>
${code_map.wbs_template}
</select>
</td>
</tr>
</table>
</div>
<div class="wbs-modal-footer">
<input type="button" class="plm_btns" value="저장" onclick="fn_saveWbsAssign()">
<input type="button" class="plm_btns" value="닫기" onclick="fn_closeWbsModal()">
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,680 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ 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"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
.info-area { padding: 8px 10px; }
.info-area table td { padding: 2px 8px; }
.info-area label { font-weight: bold; }
#wbsTaskList td { text-align: center; padding: 2px 3px; vertical-align: middle; }
#wbsTaskList input[type="text"] { width: 95%; font-size: 11px; }
#wbsTaskList select { font-size: 11px; }
#wbsTaskList input.date_input { width: 85px; font-size: 11px; text-align: center; }
.row-total td { font-weight: bold; background-color: #FFF9C4 !important; }
.row-level1 td { font-weight: bold; background-color: #FFF3E0 !important; }
.row-level2 td { background-color: #F3E5F5 !important; }
.calc-field { background: transparent; border: none; text-align: center; font-size: 11px; }
.delay-plus { color: red; font-weight: bold; }
.delay-minus { color: blue; }
/* select2 컴팩트 스타일 */
#wbsTaskList .select2-container { width: 100% !important; }
#wbsTaskList .select2-container .select2-selection--single { height: 24px; min-height: 24px; }
#wbsTaskList .select2-container .select2-selection--single .select2-selection__rendered { line-height: 24px; font-size: 11px; padding-left: 4px; }
#wbsTaskList .select2-container .select2-selection--single .select2-selection__arrow { height: 22px; }
</style>
<script>
var rowSeq = 0;
var projectObjid = "${param.projectObjid}";
var wbsType = "${param.wbsType}";
var wbsTypeLabel = (wbsType === 'PRODUCE') ? '생산' : '납품';
// 담당자 옵션 HTML (서버에서 생성)
var userOptionsHtml = '<option value=""></option>';
<c:forEach var="user" items="${userList}">
userOptionsHtml += '<option value="${user.CODE_ID}">${user.CODE_NAME}</option>';
</c:forEach>
$(document).ready(function(){
document.getElementById('wbsTypeLabel').innerText = wbsTypeLabel + ' WBS';
fn_loadExistingWbs();
$("#btnSave").click(function(){ fn_save(); });
$("#btnClose").click(function(){ window.close(); });
$("#wbsTemplateSelect").change(function(){
if($(this).val()) fn_loadTemplateTasks($(this).val());
});
});
function fn_loadExistingWbs(){
$.ajax({
url: "/productionplanning/getEquipWbsTaskList.do",
type: "POST", data: { projectObjid: projectObjid, wbsType: wbsType },
dataType: "json",
success: function(result){
if(result.list && result.list.length > 0){
$("#templateArea").hide();
fn_renderRows(result.list, false);
} else {
$("#templateArea").show();
addTotalRow();
}
}
});
}
function fn_loadTemplateTasks(templateObjid){
$.ajax({
url: "/productionplanning/getWbsTemplateTasks.do",
type: "POST", data: { templateObjid: templateObjid },
dataType: "json",
success: function(result){
if(result.list && result.list.length > 0){
// 기존 행 제거 후 템플릿 데이터로 재구성
$("#wbsTaskList").empty();
fn_renderRows(result.list, true);
$("#templateArea").hide();
} else {
Swal.fire({title:'알림', text:'템플릿에 등록된 태스크가 없습니다.', icon:'info'});
}
}
});
}
function fn_renderRows(list, isTemplate){
var hasTotalRow = false;
for(var i=0; i<list.length; i++){
var d = list[i];
var level = String(d.TASK_LEVEL || d.task_level || '');
var depth = parseInt(level) || 0;
if(depth === 0){
hasTotalRow = true;
var totalUserId = d.USER_ID || d.user_id || '';
addTotalRow(totalUserId);
continue;
}
var objId = isTemplate ? generateObjId() : (d.OBJID || d.objid || generateObjId());
var unitNo = d.UNIT_NO || d.unit_no || '';
var taskName = d.TASK_NAME || d.task_name || '';
var userId = d.USER_ID || d.user_id || '';
var planStart = d.PLAN_START || d.plan_start || '';
var planEnd = d.PLAN_END || d.plan_end || '';
var actStart = d.ACT_START || d.act_start || '';
var actEnd = d.ACT_END || d.act_end || '';
var remark = d.REMARK || d.remark || '';
var upperTask = d.UPPER_TASK_OBJID || d.upper_task_objid || '';
var tmplTask = isTemplate ? (d.OBJID || d.objid || '') : (d.TEMPLATE_TASK_OBJID || d.template_task_objid || '');
var progress = d.PROGRESS || d.progress || '';
appendTaskRow(objId, depth, unitNo, taskName, userId, planStart, planEnd, actStart, actEnd, remark, upperTask, tmplTask, progress);
}
if(!hasTotalRow) addTotalRow();
renumberAllRows();
recalcAllParents();
}
// TOTAL 행 추가
function addTotalRow(userId){
if($("#row_total").length > 0) return;
var objId = generateObjId();
var tr = '<tr id="row_total" class="row-total" data-depth="0">';
tr += '<td></td>';
tr += hiddens(objId, '0', '', '0', '');
tr += '<td></td><td></td><td></td>';
tr += '<td style="font-weight:bold;">TOTAL<input type="hidden" name="TASK_NAME_' + objId + '" value="TOTAL"></td>';
tr += '<td><select name="USER_ID_' + objId + '" id="USER_ID_' + objId + '" class="user-select">' + userOptionsHtml + '</select></td>';
tr += '<td class="calc-field" id="ps_' + objId + '"></td>';
tr += '<td class="calc-field" id="pe_' + objId + '"></td>';
tr += '<td class="calc-field" id="as_' + objId + '"></td>';
tr += '<td class="calc-field" id="ae_' + objId + '"></td>';
tr += '<td class="calc-field" id="pr_' + objId + '"></td>';
tr += '<td class="calc-field" id="dd_' + objId + '"></td>';
tr += '<td><input type="text" name="REMARK_' + objId + '" id="REMARK_' + objId + '" value="" style="width:95%;"></td>';
tr += '</tr>';
$("#wbsTaskList").prepend(tr);
if(userId) $("#USER_ID_" + objId).val(userId);
$("#USER_ID_" + objId).select2({ width: '100%', placeholder: '', allowClear: true });
}
function hiddens(objId, unitNo, upperTask, level, tmplTask){
var h = '';
h += '<input type="hidden" name="WBS_TASK_OBJID" value="' + objId + '">';
h += '<input type="hidden" name="UNIT_NO_' + objId + '" id="UNIT_NO_' + objId + '" value="' + unitNo + '">';
h += '<input type="hidden" name="UPPER_TASK_OBJID_' + objId + '" id="UPPER_TASK_OBJID_' + objId + '" value="' + upperTask + '">';
h += '<input type="hidden" name="TASK_LEVEL_' + objId + '" id="TASK_LEVEL_' + objId + '" value="' + level + '">';
h += '<input type="hidden" name="TEMPLATE_TASK_OBJID_' + objId + '" id="TEMPLATE_TASK_OBJID_' + objId + '" value="' + tmplTask + '">';
return h;
}
function appendTaskRow(objId, depth, unitNo, taskName, userId, planStart, planEnd, actStart, actEnd, remark, upperTask, tmplTask, progress){
var isLevel3 = (depth === 3);
var rowClass = (depth === 1) ? 'row-level1' : (depth === 2) ? 'row-level2' : '';
var tr = '<tr id="row_' + objId + '" class="' + rowClass + '">';
tr += '<td><input type="checkbox" name="rowCheck" value="' + objId + '"></td>';
tr += hiddens(objId, unitNo, upperTask, depth, tmplTask);
tr += '<td><input type="text" class="lvl_input" data-objid="' + objId + '" data-level="1" style="width:80%;text-align:center;"' + (depth==1 ? ' value="'+unitNo+'"' : '') + '></td>';
tr += '<td><input type="text" class="lvl_input" data-objid="' + objId + '" data-level="2" style="width:80%;text-align:center;"' + (depth==2 ? ' value="'+unitNo+'"' : '') + '></td>';
tr += '<td><input type="text" class="lvl_input" data-objid="' + objId + '" data-level="3" style="width:80%;text-align:center;"' + (depth==3 ? ' value="'+unitNo+'"' : '') + '></td>';
tr += '<td><input type="text" name="TASK_NAME_' + objId + '" id="TASK_NAME_' + objId + '" value="' + escapeHtml(taskName) + '"></td>';
tr += '<td><select name="USER_ID_' + objId + '" id="USER_ID_' + objId + '" class="user-select">' + userOptionsHtml + '</select></td>';
if(isLevel3){
tr += '<td><input type="text" class="date_input" name="PLAN_START_' + objId + '" id="PLAN_START_' + objId + '" value="' + planStart + '" autocomplete="off"></td>';
tr += '<td><input type="text" class="date_input" name="PLAN_END_' + objId + '" id="PLAN_END_' + objId + '" value="' + planEnd + '" autocomplete="off"></td>';
tr += '<td><input type="text" class="date_input" name="ACT_START_' + objId + '" id="ACT_START_' + objId + '" value="' + actStart + '" autocomplete="off"></td>';
tr += '<td><input type="text" class="date_input" name="ACT_END_' + objId + '" id="ACT_END_' + objId + '" value="' + actEnd + '" autocomplete="off"></td>';
} else {
tr += '<td class="calc-field" id="ps_' + objId + '"></td>';
tr += '<td class="calc-field" id="pe_' + objId + '"></td>';
tr += '<td class="calc-field" id="as_' + objId + '"></td>';
tr += '<td class="calc-field" id="ae_' + objId + '"></td>';
}
// 진척율: Level 3은 직접 입력, 나머지는 하위 평균 자동 계산
if(isLevel3){
var prVal = (progress != null && progress !== '') ? progress : '0';
tr += '<td><input type="text" name="PROGRESS_' + objId + '" id="PROGRESS_' + objId + '" value="' + prVal + '" maxlength="3" style="width:35px;text-align:center;font-size:11px;" oninput="this.value=this.value.replace(/[^0-9]/g,\'\')" onkeydown="if(event.keyCode===13){this.blur();}" onblur="validateProgress(this);recalcAllParents()"></td>';
} else {
tr += '<td class="calc-field" id="pr_' + objId + '"></td>';
}
tr += '<td class="calc-field" id="dd_' + objId + '"></td>';
tr += '<td><input type="text" name="REMARK_' + objId + '" id="REMARK_' + objId + '" value="' + escapeHtml(remark) + '" style="width:95%;"></td>';
tr += '</tr>';
$("#wbsTaskList").append(tr);
if(userId) $("#USER_ID_" + objId).val(userId);
$("#USER_ID_" + objId).select2({ width: '100%', placeholder: '', allowClear: true });
bindLevelInput(objId);
if(isLevel3) bindDatePicker(objId);
}
function bindDatePicker(objId){
var opts = { changeMonth:true, changeYear:true, dateFormat:'yy-mm-dd',
onSelect: function(){ recalcAllParents(); }
};
$("#PLAN_START_" + objId).datepicker(opts);
$("#PLAN_END_" + objId).datepicker(opts);
$("#ACT_START_" + objId).datepicker(opts);
$("#ACT_END_" + objId).datepicker(opts);
}
function bindLevelInput(objId){
$("#row_" + objId + " .lvl_input").on("input", function(){
var curObjId = $(this).data("objid");
var level = $(this).data("level");
$("#row_" + curObjId + " .lvl_input").not(this).val("");
$("#UNIT_NO_" + curObjId).val($(this).val());
$("#TASK_LEVEL_" + curObjId).val($(this).val() ? level : "");
var tr = $("#row_" + curObjId);
tr.removeClass('row-level1 row-level2');
if(level == 1) tr.addClass('row-level1');
else if(level == 2) tr.addClass('row-level2');
});
}
function generateObjId(){
return String(Math.abs(Math.floor(Math.random() * 2147483647))) + String(++rowSeq);
}
function getRowDepth(tr){
var objId = $(tr).find("input[name='WBS_TASK_OBJID']").val();
var taskLevel = $.trim($("#TASK_LEVEL_" + objId).val());
if(taskLevel !== "") return parseInt(taskLevel);
var unitNo = $.trim($("#UNIT_NO_" + objId).val());
if(unitNo === "0") return 0;
if(unitNo === "") return -1;
return (unitNo.match(/\./g) || []).length + 1;
}
function findLastDescendant(parentTr){
var parentDepth = getRowDepth(parentTr[0]);
var last = parentTr;
parentTr.nextAll("tr").each(function(){
if(getRowDepth(this) > parentDepth) last = $(this);
else return false;
});
return last;
}
function renumberAllRows(){
var rows = $("#wbsTaskList tr:not(#row_total)");
var counters = [0, 0, 0];
rows.each(function(){
var objId = $(this).find("input[name='WBS_TASK_OBJID']").val();
var depth = parseInt($("#TASK_LEVEL_" + objId).val()) || 0;
if(depth < 1 || depth > 3) return true;
if(depth === 1){ counters[0]++; counters[1]=0; counters[2]=0; }
else if(depth === 2){ counters[1]++; counters[2]=0; }
else { counters[2]++; }
var unitNo;
if(depth === 1) unitNo = String(counters[0]);
else if(depth === 2) unitNo = counters[0] + "." + counters[1];
else unitNo = counters[0] + "." + counters[1] + "." + counters[2];
$("#UNIT_NO_" + objId).val(unitNo);
$(this).find(".lvl_input").val("");
$(this).find(".lvl_input[data-level='" + depth + "']").val(unitNo);
});
}
// --- 행 추가/삭제 ---
function addRow(){
var objId = generateObjId();
var checked = $("input[name='rowCheck']:checked");
var autoLevel = "";
var insertAfterTr = null;
if(checked.length > 0){
var selectedTr = checked.last().closest("tr");
autoLevel = getRowDepth(selectedTr[0]);
insertAfterTr = findLastDescendant(selectedTr);
checked.prop("checked", false);
}
var depth = parseInt(autoLevel) || 3;
appendTaskRow(objId, depth, '', '', '', '', '', '', '', '', '', '', '');
if(insertAfterTr){
// detach 전 select2 제거 후 재초기화
$("#USER_ID_" + objId).select2('destroy');
var newRow = $("#row_" + objId).detach();
insertAfterTr.after(newRow);
$("#USER_ID_" + objId).select2({ width: '100%', placeholder: '', allowClear: true });
bindLevelInput(objId);
if(depth === 3) bindDatePicker(objId);
}
$("#TASK_LEVEL_" + objId).val(depth);
var tr = $("#row_" + objId);
tr.removeClass('row-level1 row-level2');
if(depth === 1) tr.addClass('row-level1');
else if(depth === 2) tr.addClass('row-level2');
renumberAllRows();
}
function addChildRow(){
var checked = $("input[name='rowCheck']:checked");
if(checked.length == 0){ Swal.fire('부모 행을 선택해 주세요'); return; }
var selectedTr = checked.last().closest("tr");
var selectedDepth = getRowDepth(selectedTr[0]);
if(selectedDepth >= 3){ Swal.fire('수준 3 이하로는 추가할 수 없습니다'); return; }
var objId = generateObjId();
var childDepth = selectedDepth + 1;
var insertAfterTr = findLastDescendant(selectedTr);
appendTaskRow(objId, childDepth, '', '', '', '', '', '', '', '', '', '', '');
$("#USER_ID_" + objId).select2('destroy');
var newRow = $("#row_" + objId).detach();
insertAfterTr.after(newRow);
$("#USER_ID_" + objId).select2({ width: '100%', placeholder: '', allowClear: true });
bindLevelInput(objId);
if(childDepth === 3) bindDatePicker(objId);
$("#TASK_LEVEL_" + objId).val(childDepth);
var tr = $("#row_" + objId);
tr.removeClass('row-level1 row-level2');
if(childDepth === 1) tr.addClass('row-level1');
else if(childDepth === 2) tr.addClass('row-level2');
checked.prop("checked", false);
renumberAllRows();
}
function deleteRow(){
var checked = $("input[name='rowCheck']:checked");
if(checked.length == 0){ Swal.fire('삭제할 행을 선택해 주세요'); return; }
var removeTargets = [];
var hasChildren = false;
checked.each(function(){
var objId = $(this).val();
var tr = $("#row_" + objId);
if(tr.attr("id") === "row_total") return true;
removeTargets.push(tr);
var parentDepth = getRowDepth(tr[0]);
tr.nextAll("tr").each(function(){
if(getRowDepth(this) > parentDepth){ removeTargets.push($(this)); hasChildren = true; }
else return false;
});
});
var msg = hasChildren ? '하위 항목도 함께 삭제됩니다. 삭제하시겠습니까?' : '삭제하시겠습니까?';
Swal.fire({ title: msg, icon:'warning', showCancelButton:true, confirmButtonText:'삭제', cancelButtonText:'취소'
}).then(function(result){
if(result.isConfirmed){
$.each(removeTargets, function(i, tr){ tr.remove(); });
renumberAllRows();
recalcAllParents();
}
});
}
// --- 자동 계산 ---
function recalcAllParents(){
var rows = $("#wbsTaskList tr");
var allItems = [];
rows.each(function(){
var objId = $(this).find("input[name='WBS_TASK_OBJID']").val();
if(!objId) return true;
var depth = getRowDepth(this);
var item = { objId: objId, depth: depth, tr: $(this) };
if(depth === 3){
item.planStart = $.trim($("#PLAN_START_" + objId).val());
item.planEnd = $.trim($("#PLAN_END_" + objId).val());
item.actStart = $.trim($("#ACT_START_" + objId).val());
item.actEnd = $.trim($("#ACT_END_" + objId).val());
var prVal = $.trim($("#PROGRESS_" + objId).val());
item.progress = (prVal !== '') ? Number(prVal) : 0;
item.delay = calcDelay(item);
}
allItems.push(item);
});
// Level 2: 하위 Level 3 집계
for(var i=0; i<allItems.length; i++){
if(allItems[i].depth === 2) calcParentFromChildren(allItems, i, 3);
}
// Level 1: 하위 Level 2 집계
for(var i=0; i<allItems.length; i++){
if(allItems[i].depth === 1) calcParentFromChildren(allItems, i, 2);
}
// TOTAL: 하위 Level 1 집계
for(var i=0; i<allItems.length; i++){
if(allItems[i].depth === 0){
var children = allItems.filter(function(x){ return x.depth === 1; });
calcParentAggregate(allItems[i], children);
}
}
// DOM에 반영
for(var i=0; i<allItems.length; i++){
var it = allItems[i];
if(it.depth < 3){
$("#ps_" + it.objId).text(it.planStart || '');
$("#pe_" + it.objId).text(it.planEnd || '');
$("#as_" + it.objId).text(it.actStart || '');
$("#ae_" + it.objId).text(it.actEnd || '');
}
if(it.depth < 3){
var prVal = (it.progress != null && it.progress !== '') ? it.progress : 0;
$("#pr_" + it.objId).text(Number(prVal).toFixed(1));
}
var ddText = '';
if(it.delay != null && it.delay !== ''){
if(it.delay > 0) ddText = '<span class="delay-plus">+' + it.delay + '</span>';
else if(it.delay < 0) ddText = '<span class="delay-minus">' + it.delay + '</span>';
else ddText = '0';
}
$("#dd_" + it.objId).html(ddText);
}
}
function calcParentFromChildren(allItems, parentIdx, childDepth){
var parent = allItems[parentIdx];
var children = [];
for(var j=parentIdx+1; j<allItems.length; j++){
if(allItems[j].depth <= parent.depth) break;
if(allItems[j].depth === childDepth) children.push(allItems[j]);
}
calcParentAggregate(parent, children);
}
function calcParentAggregate(parent, children){
if(!children || children.length === 0){
parent.planStart=''; parent.planEnd=''; parent.actStart=''; parent.actEnd=''; parent.progress=0; parent.delay='';
return;
}
var ps=[], pe=[], as2=[], ae=[];
var rateSum = 0;
for(var i=0; i<children.length; i++){
var c = children[i];
if(c.planStart) ps.push(c.planStart);
if(c.planEnd) pe.push(c.planEnd);
if(c.actStart) as2.push(c.actStart);
if(c.actEnd) ae.push(c.actEnd);
rateSum += (c.progress != null && c.progress !== '') ? Number(c.progress) : 0;
}
parent.planStart = ps.length > 0 ? ps.sort()[0] : '';
parent.planEnd = pe.length > 0 ? pe.sort().reverse()[0] : '';
parent.actStart = as2.length > 0 ? as2.sort()[0] : '';
parent.actEnd = ae.length > 0 ? ae.sort().reverse()[0] : '';
parent.progress = rateSum / children.length;
parent.delay = calcDelay(parent);
}
function calcProgress3(item){
if(item.actEnd) return 100;
if(!item.actStart) return 0;
if(!item.planStart || !item.planEnd) return 0;
var today = new Date(); today.setHours(0,0,0,0);
var s = new Date(item.planStart), e = new Date(item.planEnd), a = new Date(item.actStart);
var total = (e - s) / 86400000;
if(total <= 0) return 0;
var elapsed = (today - a) / 86400000;
return Math.max(0, Math.min(Math.round(elapsed / total * 100), 99));
}
function calcDelay(item){
if(!item.planEnd || item.progress === '' || item.progress == null) return '';
if(Number(item.progress) === 100){
if(!item.actEnd) return '';
return Math.round((new Date(item.actEnd) - new Date(item.planEnd)) / 86400000);
}
return '';
}
function validateProgress(el){
el.value = el.value.replace(/[^0-9]/g, '');
var v = parseInt(el.value);
if(isNaN(v) || v < 0) el.value = '0';
else if(v > 100) el.value = '100';
}
function escapeHtml(str){
if(!str) return '';
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function calculateParentRelations(){
var rows = $("#wbsTaskList tr");
var totalObjId = $("#row_total").find("input[name='WBS_TASK_OBJID']").val();
rows.each(function(idx){
if($(this).attr("id") === "row_total") return true;
var objId = $(this).find("input[name='WBS_TASK_OBJID']").val();
var depth = getRowDepth(this);
var parentObjId = "";
if(depth === 1){ parentObjId = totalObjId; }
else if(depth > 1){
$(this).prevAll("tr").each(function(){
if(getRowDepth(this) === depth - 1){
parentObjId = $(this).find("input[name='WBS_TASK_OBJID']").val();
return false;
}
});
}
$("#UPPER_TASK_OBJID_" + objId).val(parentObjId);
});
}
// --- 저장 ---
function fn_save(){
var taskCount = $("input[name='WBS_TASK_OBJID']").length;
if(taskCount <= 1){ Swal.fire({title:'알림', text:'등록할 항목을 추가해 주세요.', icon:'warning'}); return; }
var isValid = true;
$("input[name='WBS_TASK_OBJID']").each(function(){
var objId = $(this).val();
if($(this).closest("tr").attr("id") === "row_total") return true;
var level = $.trim($("#UNIT_NO_" + objId).val());
var taskName = $.trim($("#TASK_NAME_" + objId).val());
if(level == "" || taskName == ""){ isValid = false; return false; }
});
if(!isValid){ Swal.fire('수준과 Unit Name / 공정을 모두 입력해 주세요'); return; }
calculateParentRelations();
recalcAllParents();
var saveData = [];
var totalUserId = '';
$("#wbsTaskList tr").each(function(){
var objId = $(this).find("input[name='WBS_TASK_OBJID']").val();
if(!objId) return true;
var depth = getRowDepth(this);
// TOTAL 행은 담당자만 별도 저장
if(depth === 0){
totalUserId = $("#USER_ID_" + objId).val() || '';
return true;
}
var row = {
objid: objId,
taskName: $.trim($("#TASK_NAME_" + objId).val()),
taskSeq: saveData.length + 1,
taskLevel: String(depth),
unitNo: $.trim($("#UNIT_NO_" + objId).val()),
upperTaskObjid: $.trim($("#UPPER_TASK_OBJID_" + objId).val()),
templateTaskObjid: $.trim($("#TEMPLATE_TASK_OBJID_" + objId).val()),
remark: $.trim($("#REMARK_" + objId).val())
};
row.userId = $("#USER_ID_" + objId).val() || '';
if(depth === 3){
row.planStart = $.trim($("#PLAN_START_" + objId).val());
row.planEnd = $.trim($("#PLAN_END_" + objId).val());
row.actStart = $.trim($("#ACT_START_" + objId).val());
row.actEnd = $.trim($("#ACT_END_" + objId).val());
row.progress = $.trim($("#PROGRESS_" + objId).val());
} else {
row.progress = $.trim($("#pr_" + objId).text()).replace('%', '');
row.planStart = $.trim($("#ps_" + objId).text());
row.planEnd = $.trim($("#pe_" + objId).text());
row.actStart = $.trim($("#as_" + objId).text());
row.actEnd = $.trim($("#ae_" + objId).text());
}
saveData.push(row);
});
Swal.fire({ title:'저장하시겠습니까?', icon:'question', showCancelButton:true, confirmButtonText:'확인', cancelButtonText:'취소'
}).then(function(result){
if(result.isConfirmed){
$.ajax({
url: "/productionplanning/saveEquipWbsAssign.do",
type: "POST", contentType: "application/json",
data: JSON.stringify({ projectObjid: projectObjid, wbsType: wbsType, templateObjid: $("#wbsTemplateSelect").val() || '', totalUserId: totalUserId, tasks: saveData }),
dataType: "json",
success: function(res){
if(res.success){
Swal.fire({title:'완료', text:'저장되었습니다.', icon:'success'}).then(function(){
if(opener && opener.fn_search) opener.fn_search();
window.close();
});
} else {
Swal.fire({title:'오류', text: res.message || '저장 실패', icon:'error'});
}
},
error: function(){ Swal.fire({title:'오류', text:'서버 오류', icon:'error'}); }
});
}
});
}
</script>
</head>
<body>
<div class="info-area">
<table>
<tr>
<td><label>프로젝트번호:</label></td>
<td>${projectInfo.PROJECT_NO}</td>
<td style="padding-left:20px;"><label>WBS유형:</label></td>
<td><span style="font-weight:bold; color:#1976D2;" id="wbsTypeLabel"></span></td>
<td style="padding-left:20px;"><label>품번:</label></td>
<td>${projectInfo.PART_NO}</td>
<td style="padding-left:20px;"><label>품명:</label></td>
<td>${projectInfo.PART_NAME}</td>
</tr>
</table>
</div>
<div id="templateArea" style="padding:0 10px 5px; display:none;">
<label>WBS 템플릿:</label>
<select id="wbsTemplateSelect" style="width:250px;">
<option value="">-- 선택 --</option>
${wbsTemplateOptions}
</select>
</div>
<div style="padding:0 10px;">
<div class="plm_btn_wrap" style="margin-bottom:5px;">
<input type="button" value="추가" class="plm_btns" onclick="addRow();">
<input type="button" value="하위추가" class="plm_btns" onclick="addChildRow();">
<input type="button" value="삭제" class="plm_btns" onclick="deleteRow();">
<input type="button" value="저장" class="plm_btns" id="btnSave">
<input type="button" value="닫기" class="plm_btns" id="btnClose">
</div>
</div>
<div style="width:100%; padding:0 10px;">
<table class="plm_table" style="width:100%;border-collapse:collapse;">
<colgroup>
<col width="3%"/><col width="4%"/><col width="4%"/><col width="5%"/>
<col width="11%"/><col width="10%"/>
<col width="8%"/><col width="8%"/><col width="8%"/><col width="8%"/>
<col width="5%"/><col width="5%"/><col width="*"/>
</colgroup>
<thead>
<tr class="plm_thead">
<td rowspan="2">선택</td>
<td colspan="3">수준</td>
<td rowspan="2">Unit Name / 공정</td>
<td rowspan="2">담당자</td>
<td colspan="2" id="thPlanGroup">계획</td>
<td colspan="2" id="thActGroup">실적</td>
<td rowspan="2">진척율<br/>(%)</td>
<td rowspan="2">지연일수</td>
<td rowspan="2">비고</td>
</tr>
<tr class="plm_thead">
<td>1</td><td>2</td><td>3</td>
<td>시작일</td><td>완료일</td>
<td>시작일</td><td>완료일</td>
</tr>
</thead>
</table>
</div>
<div style="width:100%;height:calc(100vh - 180px);overflow-y:auto;padding:0 10px;">
<table class="plm_table" style="width:100%;border-collapse:collapse;">
<colgroup>
<col width="3%"/><col width="4%"/><col width="4%"/><col width="5%"/>
<col width="11%"/><col width="10%"/>
<col width="8%"/><col width="8%"/><col width="8%"/><col width="8%"/>
<col width="5%"/><col width="5%"/><col width="*"/>
</colgroup>
<tbody id="wbsTaskList"></tbody>
</table>
</div>
<script>
// 헤더에 WBS유형 표시
var planLabel = wbsTypeLabel;
$("#thPlanGroup").text(planLabel + ' 계획');
$("#thActGroup").text(planLabel + ' 실적');
</script>
</body>
</html>

View File

@@ -24,6 +24,11 @@ String connector = person.getUserId();
<c:set var="sysYear"><fmt:formatDate value="${now}" pattern="yyyy" /></c:set>
<c:set var="connector" value="<%=connector %>" />
<%
// DB에서 메뉴명 조회 (공통 유틸 사용)
String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "품목별 입고 관리");
%>
<style>
::-webkit-scrollbar {
display: none;
@@ -100,7 +105,7 @@ var columns = [
// fn_openTemplateMasterPopUp(objid);
// }
// },
{headerHozAlign : 'center', hozAlign : 'center', width : '250', title : 'UNIT', field : 'WBS_TASK_CNT0',
{headerHozAlign : 'center', hozAlign : 'center', width : '250', title : 'WBS', field : 'WBS_TASK_CNT0',
formatter:fnc_getFolderIcon,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
@@ -342,7 +347,7 @@ function openProjectFormPopUp(objId){
<div class="content-box-s">
<div class="plm_menu_name_gdnsi">
<h2>
<span>프로젝트관리_제품구분_UNIT관리</span>
<span><%=menuName%></span>
</h2>
<div class="btnArea">
<input type="button" value="삭제" class="plm_btns delete" id="btnDelete">

View File

@@ -1719,6 +1719,154 @@ public class ProductionPlanningController extends BaseService {
return paramMap;
}
/**
* 생산관리 -> 생산계획&실적관리(장비) 목록
*/
@RequestMapping("/productionplanning/prodPlanResultMgmtEquipList.do")
public String prodPlanResultMgmtEquipList(HttpServletRequest request, @RequestParam Map paramMap){
Map code_map = new HashMap();
try{
code_map.put("project_no", commonService.bizMakeOptionList("", CommonUtils.nullToEmpty((String)paramMap.get("project_no")), "common.getCusProjectNoList"));
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)paramMap.get("product_code")), "common.getCodeselect"));
code_map.put("category_cd", commonService.bizMakeOptionList("0000167", CommonUtils.nullToEmpty((String)paramMap.get("category_code")), "common.getCodeselect"));
code_map.put("production_type_cd", commonService.bizMakeOptionList("0001832", CommonUtils.nullToEmpty((String)paramMap.get("production_type")), "common.getCodeselect"));
code_map.put("customer_cd", commonService.bizMakeOptionList("", CommonUtils.nullToEmpty((String)paramMap.get("customer_objid")), "common.getsupplyselect"));
// WBS 템플릿 목록 (Machine 제품용)
List wbsTemplateList = commonService.selectList("productionplanning.getWbsTemplateOptionList", request, paramMap);
StringBuilder wbsTemplateSb = new StringBuilder();
if(wbsTemplateList != null) {
for(int i=0; i<wbsTemplateList.size(); i++) {
Map row = (Map)wbsTemplateList.get(i);
wbsTemplateSb.append("<option value=\"").append(row.get("OBJID")).append("\">").append(row.get("TITLE")).append("</option>");
}
}
code_map.put("wbs_template", wbsTemplateSb.toString());
request.setAttribute("code_map", code_map);
}catch(Exception e){
e.printStackTrace();
}
return "/productionplanning/prodPlanResultMgmtEquipList";
}
/**
* 생산관리 -> 생산계획&실적관리(장비) 그리드 목록
*/
@ResponseBody
@RequestMapping("/productionplanning/prodPlanResultMgmtEquipGridList.do")
public Map prodPlanResultMgmtEquipGridList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
String[] projectNos = request.getParameterValues("search_project_no");
if(projectNos != null && projectNos.length > 0) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < projectNos.length; i++) {
if(i > 0) sb.append(",");
sb.append(projectNos[i]);
}
paramMap.put("search_project_nos", sb.toString());
}
commonService.selectListPagingNew("productionplanning.prodPlanResultMgmtEquipGridList", request, paramMap);
return paramMap;
}
/**
* 장비 WBS할당 팝업
*/
@RequestMapping("/productionplanning/prodPlanWbsAssignPopup.do")
public String prodPlanWbsAssignPopup(HttpServletRequest request, @RequestParam Map paramMap){
try{
// 프로젝트 정보 조회
Map projectInfo = commonService.selectOne("productionplanning.getEquipProjectInfo", request, paramMap);
request.setAttribute("projectInfo", projectInfo);
// WBS 템플릿 목록
List templateList = commonService.selectList("productionplanning.getWbsTemplateOptionList", request, paramMap);
StringBuilder sb = new StringBuilder();
if(templateList != null){
for(int i=0; i<templateList.size(); i++){
Map row = (Map)templateList.get(i);
sb.append("<option value=\"").append(row.get("OBJID")).append("\">").append(row.get("TITLE")).append("</option>");
}
}
request.setAttribute("wbsTemplateOptions", sb.toString());
// 사용자 목록
List userList = commonService.selectList("common.getUserselect6", request, new HashMap());
request.setAttribute("userList", userList);
}catch(Exception e){
e.printStackTrace();
}
return "/productionplanning/prodPlanWbsAssignPopup";
}
/**
* 장비 WBS 템플릿 태스크 목록 조회
*/
@ResponseBody
@RequestMapping("/productionplanning/getWbsTemplateTasks.do")
public Map getWbsTemplateTasks(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map result = new HashMap();
try{
List list = commonService.selectList("productionplanning.getWbsTemplateTasks", request, paramMap);
result.put("list", list);
}catch(Exception e){
e.printStackTrace();
result.put("list", new ArrayList());
}
return result;
}
/**
* 장비 프로젝트의 기존 WBS 태스크 목록 조회
*/
@ResponseBody
@RequestMapping("/productionplanning/getEquipWbsTaskList.do")
public Map getEquipWbsTaskList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map result = new HashMap();
try{
List list = commonService.selectList("productionplanning.getEquipWbsTaskList", request, paramMap);
result.put("list", list);
}catch(Exception e){
e.printStackTrace();
result.put("list", new ArrayList());
}
return result;
}
/**
* 장비 WBS 할당 저장
*/
@ResponseBody
@RequestMapping("/productionplanning/saveEquipWbsAssign.do")
public Map saveEquipWbsAssign(HttpServletRequest request, @RequestBody Map<String, Object> paramMap){
try{
HttpSession session = request.getSession();
PersonBean loginUser = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
if(loginUser != null){
paramMap.put("writer", loginUser.getUserId());
} else {
paramMap.put("writer", "");
}
List<Map<String, Object>> tasks = (List<Map<String, Object>>)paramMap.get("tasks");
if(tasks == null || tasks.isEmpty()){
Map result = new HashMap();
result.put("success", false);
result.put("message", "저장할 태스크가 없습니다.");
return result;
}
return productionPlanningService.saveEquipWbsAssign(paramMap);
}catch(Exception e){
e.printStackTrace();
Map result = new HashMap();
result.put("success", false);
result.put("message", e.getMessage());
return result;
}
}
/**
* 생산계획 생성 팝업
* @param request

View File

@@ -4603,6 +4603,7 @@
LEFT OUTER JOIN PRODUCTION_PLAN PP ON PP.PROJECT_OBJID = PM.OBJID
AND PP.STATUS = 'active'
WHERE PM.PROJECT_NO IS NOT NULL AND PM.PROJECT_NO != ''
AND PM.PRODUCT != '0000928'
UNION ALL
@@ -4712,6 +4713,276 @@
</if>
ORDER BY T.SORT_DATE DESC, T.PROJECT_NO DESC
</select>
<!-- 장비 WBS 템플릿 옵션 목록 -->
<select id="getWbsTemplateOptionList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT OBJID, TITLE
FROM PMS_WBS_TEMPLATE
WHERE 1=1
ORDER BY TITLE
</select>
<!-- 장비 프로젝트 기본정보 조회 -->
<select id="getEquipProjectInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.PROJECT_NO,
COALESCE(CI.PART_NO, PM.PART_NO, '') AS PART_NO,
COALESCE(CI.PART_NAME, PM.PART_NAME, '') AS PART_NAME
FROM PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID
AND PM.PART_OBJID = CI.PART_OBJID AND CI.STATUS = 'ACTIVE'
WHERE PM.OBJID::VARCHAR = #{projectObjid}::VARCHAR
</select>
<!-- 장비 WBS 템플릿 태스크 목록 조회 -->
<select id="getWbsTemplateTasks" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.PARENT_OBJID,
T.TASK_NAME,
T.TASK_SEQ,
T.TASK_LEVEL,
T.USER_ID,
T.UNIT_NO,
T.UPPER_TASK_OBJID
FROM PMS_WBS_TASK_STANDARD T
WHERE T.PARENT_OBJID = #{templateObjid}
ORDER BY CAST(T.TASK_SEQ AS INTEGER)
</select>
<!-- 장비 프로젝트의 기존 WBS 태스크 목록 조회 -->
<select id="getEquipWbsTaskList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.CONTRACT_OBJID,
T.PARENT_OBJID,
T.TASK_NAME,
T.TASK_SEQ,
T.UNIT_NO,
COALESCE(T.TASK_LEVEL, '') AS TASK_LEVEL,
COALESCE(T.UPPER_TASK_OBJID, '') AS UPPER_TASK_OBJID,
COALESCE(T.TEMPLATE_TASK_OBJID, '') AS TEMPLATE_TASK_OBJID,
CASE WHEN #{wbsType} = 'PRODUCE' THEN COALESCE(T.PRODUCE_USER_ID, '')
ELSE COALESCE(T.SHIP_USER_ID, '') END AS USER_ID,
CASE WHEN #{wbsType} = 'PRODUCE' THEN COALESCE(T.PRODUCE_PLAN_START, '')
ELSE COALESCE(T.SHIP_PLAN_START, '') END AS PLAN_START,
CASE WHEN #{wbsType} = 'PRODUCE' THEN COALESCE(T.PRODUCE_PLAN_END, '')
ELSE COALESCE(T.SHIP_PLAN_END, '') END AS PLAN_END,
CASE WHEN #{wbsType} = 'PRODUCE' THEN COALESCE(T.PRODUCE_ACT_START, '')
ELSE COALESCE(T.SHIP_ACT_START, '') END AS ACT_START,
CASE WHEN #{wbsType} = 'PRODUCE' THEN COALESCE(T.PRODUCE_ACT_END, '')
ELSE COALESCE(T.SHIP_ACT_END, '') END AS ACT_END,
COALESCE(T.REMARK, '') AS REMARK,
COALESCE(T.PROGRESS, '') AS PROGRESS
FROM PMS_WBS_TASK T
WHERE T.CONTRACT_OBJID = #{projectObjid}
AND COALESCE(T.WBS_TYPE, '') = #{wbsType}
ORDER BY CAST(COALESCE(NULLIF(T.TASK_SEQ,''), '0') AS INTEGER)
</select>
<!-- 장비 WBS 태스크 삭제 -->
<delete id="deleteEquipWbsTasks" parameterType="map">
DELETE FROM PMS_WBS_TASK
WHERE CONTRACT_OBJID = #{projectObjid}
AND COALESCE(WBS_TYPE, '') = #{wbsType}
</delete>
<!-- 장비 WBS 태스크 INSERT -->
<insert id="insertEquipWbsTask" parameterType="map">
INSERT INTO PMS_WBS_TASK (
OBJID,
CONTRACT_OBJID,
PARENT_OBJID,
TASK_NAME,
TASK_SEQ,
TASK_LEVEL,
UNIT_NO,
UPPER_TASK_OBJID,
TEMPLATE_TASK_OBJID,
WBS_TYPE,
<if test="wbsType == 'PRODUCE'">
PRODUCE_USER_ID,
PRODUCE_PLAN_START,
PRODUCE_PLAN_END,
PRODUCE_ACT_START,
PRODUCE_ACT_END,
</if>
<if test="wbsType == 'SHIP'">
SHIP_USER_ID,
SHIP_PLAN_START,
SHIP_PLAN_END,
SHIP_ACT_START,
SHIP_ACT_END,
</if>
REMARK,
PROGRESS,
WRITER
) VALUES (
#{newObjid},
#{projectObjid},
'',
#{taskName},
#{taskSeq},
#{taskLevel},
#{unitNo},
#{upperTaskObjid},
#{templateTaskObjid},
#{wbsType},
<if test="wbsType == 'PRODUCE'">
#{userId},
#{planStart},
#{planEnd},
#{actStart},
#{actEnd},
</if>
<if test="wbsType == 'SHIP'">
#{userId},
#{planStart},
#{planEnd},
#{actStart},
#{actEnd},
</if>
#{remark},
#{progress},
#{writer}
)
</insert>
<!-- 생산계획&실적관리(장비) 그리드 목록 조회 - Machine 제품만 -->
<select id="prodPlanResultMgmtEquipGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT * FROM (
SELECT
PM.OBJID,
PM.PROJECT_NO,
CM.PRODUCT,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1),
''
) AS PRODUCT_NAME,
CM.CATEGORY_CD AS CATEGORY_CODE,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.CATEGORY_CD LIMIT 1),
CM.CATEGORY_CD
) AS CATEGORY_CODE_NAME,
COALESCE(PP.PRODUCTION_TYPE, '') AS PRODUCTION_TYPE,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = PP.PRODUCTION_TYPE LIMIT 1),
''
) AS PRODUCTION_TYPE_NAME,
CM.CUSTOMER_OBJID,
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(CI.DUE_DATE, PM.DUE_DATE, CM.REQ_DEL_DATE) AS REQ_DEL_DATE,
COALESCE(CI.CUSTOMER_REQUEST, '') AS CUSTOMER_REQUEST,
COALESCE(CI.PART_NO, PM.PART_NO, '') AS PART_NO,
COALESCE(CI.PART_NAME, PM.PART_NAME, '') AS PART_NAME,
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM_SERIAL CIS
WHERE CIS.ITEM_OBJID = CI.OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- 생산WBS: WBS_TYPE='PRODUCE'인 태스크 존재 여부
(SELECT COUNT(1) FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'PRODUCE'
) AS PROD_WBS_CNT,
-- 생산진척율: Level1 PROGRESS 평균 (계층별 집계 결과)
CASE
WHEN (SELECT COUNT(1) FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'PRODUCE' AND O.TASK_LEVEL = '1') = 0 THEN 0
ELSE ROUND(
(SELECT COALESCE(AVG(CASE WHEN O.PROGRESS IS NOT NULL AND O.PROGRESS != ''
THEN CAST(O.PROGRESS AS numeric) ELSE 0 END), 0)
FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'PRODUCE' AND O.TASK_LEVEL = '1')
, 1)
END AS PROD_PROGRESS_RATE,
-- 납품WBS: WBS_TYPE='SHIP'인 태스크 존재 여부
(SELECT COUNT(1) FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'SHIP'
) AS DELV_WBS_CNT,
-- 납품진척율: Level1 PROGRESS 평균 (계층별 집계 결과)
CASE
WHEN (SELECT COUNT(1) FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'SHIP' AND O.TASK_LEVEL = '1') = 0 THEN 0
ELSE ROUND(
(SELECT COALESCE(AVG(CASE WHEN O.PROGRESS IS NOT NULL AND O.PROGRESS != ''
THEN CAST(O.PROGRESS AS numeric) ELSE 0 END), 0)
FROM PMS_WBS_TASK AS O
WHERE O.CONTRACT_OBJID = PM.OBJID
AND O.WBS_TYPE = 'SHIP' AND O.TASK_LEVEL = '1')
, 1)
END AS DELV_PROGRESS_RATE,
PM.REGDATE AS SORT_DATE
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT OUTER JOIN CONTRACT_ITEM CI ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID
AND PM.PART_OBJID = CI.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LEFT OUTER JOIN PRODUCTION_PLAN PP ON PP.PROJECT_OBJID = PM.OBJID
AND PP.STATUS = 'active'
WHERE PM.PROJECT_NO IS NOT NULL AND PM.PROJECT_NO != ''
AND PM.PRODUCT = '0000928'
) T
WHERE 1=1
<if test="search_project_nos != null and search_project_nos != ''">
AND T.OBJID::VARCHAR IN
<foreach item="projNo" collection="search_project_nos.split(',')" open="(" separator="," close=")">
#{projNo}
</foreach>
</if>
<if test="search_product_code != null and search_product_code != ''">
AND T.PRODUCT = #{search_product_code}
</if>
<if test="search_category_code != null and search_category_code != ''">
AND T.CATEGORY_CODE = #{search_category_code}
</if>
<if test="search_production_type != null and search_production_type != ''">
AND T.PRODUCTION_TYPE = #{search_production_type}
</if>
<if test="search_customer_objid != null and search_customer_objid != ''">
AND (
T.CUSTOMER_OBJID = #{search_customer_objid}
OR T.CUSTOMER_OBJID = REPLACE(#{search_customer_objid}, 'C_', '')
OR REPLACE(T.CUSTOMER_OBJID, 'C_', '') = REPLACE(#{search_customer_objid}, 'C_', '')
)
</if>
<if test="search_req_del_date_from != null and search_req_del_date_from != ''">
AND T.REQ_DEL_DATE >= #{search_req_del_date_from}
</if>
<if test="search_req_del_date_to != null and search_req_del_date_to != ''">
AND T.REQ_DEL_DATE &lt;= #{search_req_del_date_to}
</if>
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(T.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
</if>
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(T.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
</if>
<if test="search_serial_no != null and search_serial_no != ''">
AND UPPER(T.SERIAL_NO) LIKE '%' || UPPER(#{search_serial_no}) || '%'
</if>
ORDER BY T.SORT_DATE DESC, T.PROJECT_NO DESC
</select>
<!-- 프로젝트 정보 조회 (생산계획 폼용) - PROJECT_MGMT 또는 PRODUCTION_PLAN에서 조회 -->
<select id="getProdPlanProjectInfo" parameterType="map" resultType="map">

View File

@@ -2415,4 +2415,45 @@ public class ProductionPlanningService {
return result;
}
/**
* 장비 WBS 할당 저장
*/
public Map saveEquipWbsAssign(Map<String, Object> paramMap){
Map resultMap = new HashMap();
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
try{
String projectObjid = (String)paramMap.get("projectObjid");
String wbsType = (String)paramMap.get("wbsType");
String writer = (String)paramMap.get("writer");
List<Map<String, Object>> tasks = (List<Map<String, Object>>)paramMap.get("tasks");
// 기존 WBS 태스크 삭제
Map deleteParam = new HashMap();
deleteParam.put("projectObjid", projectObjid);
deleteParam.put("wbsType", wbsType);
sqlSession.delete("productionplanning.deleteEquipWbsTasks", deleteParam);
// 태스크 INSERT
for(int i=0; i<tasks.size(); i++){
Map<String, Object> task = tasks.get(i);
task.put("projectObjid", projectObjid);
task.put("wbsType", wbsType);
task.put("writer", writer);
task.put("newObjid", CommonUtils.createObjId());
sqlSession.insert("productionplanning.insertEquipWbsTask", task);
}
sqlSession.commit();
resultMap.put("success", true);
}catch(Exception e){
sqlSession.rollback();
resultMap.put("success", false);
resultMap.put("message", e.getMessage());
e.printStackTrace();
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
}