Files
wace_plm/WebContent/WEB-INF/view/approval/amaranthApprovalSubmit.jsp
2026-02-19 11:33:04 +09:00

657 lines
20 KiB
Plaintext

<%@ 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%> - 결재 상신</title>
<style>
/* 결재 상신 팝업 전용 스타일 */
body { font-family: "맑은 고딕", "Malgun Gothic", sans-serif; font-size: 12px; margin: 0; padding: 0; background: #f5f5f5; }
.submit-wrap { padding: 15px; }
.section-title { font-size: 13px; font-weight: bold; color: #333; margin: 10px 0 5px 0; padding-bottom: 5px; border-bottom: 2px solid #4a90d9; }
.section-title:first-child { margin-top: 0; }
/* 문서 정보 영역 */
.doc-info-area { margin-bottom: 10px; }
.doc-info-area table { width: 100%; border-collapse: collapse; }
.doc-info-area td { padding: 5px 8px; border: 1px solid #ddd; font-size: 12px; }
.doc-info-area td.label { background: #f0f4f8; font-weight: bold; width: 80px; text-align: center; white-space: nowrap; }
.doc-info-area input[type=text] { width: 95%; padding: 3px 5px; border: 1px solid #ccc; font-size: 12px; }
/* 사원 검색 영역 */
.search-area { margin-bottom: 10px; background: #fff; border: 1px solid #ddd; padding: 10px; border-radius: 3px; }
.search-row { display: flex; align-items: center; gap: 5px; margin-bottom: 8px; }
.search-row input[type=text] { flex: 1; padding: 5px 8px; border: 1px solid #ccc; font-size: 12px; border-radius: 3px; }
.search-row select { padding: 5px; border: 1px solid #ccc; font-size: 12px; border-radius: 3px; }
.btn-search { padding: 5px 15px; background: #4a90d9; color: #fff; border: none; cursor: pointer; font-size: 12px; border-radius: 3px; }
.btn-search:hover { background: #357abd; }
/* 검색 결과 목록 */
.emp-list-area { max-height: 180px; overflow-y: auto; border: 1px solid #ddd; background: #fff; }
.emp-list-area table { width: 100%; border-collapse: collapse; }
.emp-list-area th { background: #f0f4f8; padding: 4px 6px; border: 1px solid #ddd; font-size: 11px; position: sticky; top: 0; z-index: 1; }
.emp-list-area td { padding: 4px 6px; border: 1px solid #eee; font-size: 11px; cursor: pointer; }
.emp-list-area tr:hover { background: #e8f0fe; }
.emp-list-area tr.selected { background: #cde4f7; }
/* 결재라인 영역 */
.appline-area { margin-bottom: 10px; }
.appline-controls { display: flex; gap: 5px; margin-bottom: 5px; }
.btn-appline { padding: 4px 10px; border: 1px solid #ccc; background: #fff; cursor: pointer; font-size: 11px; border-radius: 3px; }
.btn-appline:hover { background: #e8e8e8; }
.btn-appline.add { background: #4a90d9; color: #fff; border-color: #4a90d9; }
.btn-appline.add:hover { background: #357abd; }
.btn-appline.remove { background: #e74c3c; color: #fff; border-color: #e74c3c; }
.btn-appline.remove:hover { background: #c0392b; }
.appline-table-area { border: 1px solid #ddd; background: #fff; max-height: 200px; overflow-y: auto; }
.appline-table-area table { width: 100%; border-collapse: collapse; }
.appline-table-area th { background: #f0f4f8; padding: 5px 6px; border: 1px solid #ddd; font-size: 11px; position: sticky; top: 0; z-index: 1; }
.appline-table-area td { padding: 4px 6px; border: 1px solid #eee; font-size: 11px; text-align: center; }
.appline-table-area tr.drafter { background: #fff9e6; }
.appline-table-area select { padding: 2px; font-size: 11px; border: 1px solid #ccc; }
/* 버튼 영역 */
.btn-area { text-align: center; padding: 10px 0; border-top: 1px solid #ddd; margin-top: 10px; }
.btn-submit { padding: 8px 30px; background: #4a90d9; color: #fff; border: none; cursor: pointer; font-size: 13px; font-weight: bold; border-radius: 3px; }
.btn-submit:hover { background: #357abd; }
.btn-cancel { padding: 8px 20px; background: #999; color: #fff; border: none; cursor: pointer; font-size: 13px; border-radius: 3px; margin-left: 10px; }
.btn-cancel:hover { background: #777; }
/* 순서 이동 버튼 */
.btn-move { padding: 2px 6px; font-size: 10px; border: 1px solid #ccc; background: #f9f9f9; cursor: pointer; }
.btn-move:hover { background: #ddd; }
/* 로딩 오버레이 */
.loading-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.3); z-index: 9999; justify-content: center; align-items: center; }
.loading-overlay.active { display: flex; }
.loading-spinner { background: #fff; padding: 20px 30px; border-radius: 5px; font-size: 13px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); }
</style>
</head>
<body>
<%
// URL 파라미터
String targetType = CommonUtils.checkNull(request.getParameter("targetType"));
String targetObjId = CommonUtils.checkNull(request.getParameter("targetObjId"));
String approvalTitle = CommonUtils.checkNull(request.getParameter("approvalTitle"));
// 양식코드/연동코드/기록물철 (추후 관리화면에서 설정 가능하도록)
String tiKeyCode = CommonUtils.checkNull(request.getParameter("tiKeyCode"), "1576");
String approKey = CommonUtils.checkNull(request.getParameter("approKey"), "");
String aiKeyCode = CommonUtils.checkNull(request.getParameter("aiKeyCode"), "102433");
%>
<div class="submit-wrap">
<!-- 문서 정보 -->
<div class="section-title">문서 정보</div>
<div class="doc-info-area">
<table>
<tr>
<td class="label">문서 제목</td>
<td><input type="text" id="txtDocTitle" value="<%=approvalTitle%>" placeholder="결재 문서 제목을 입력하세요" /></td>
</tr>
</table>
</div>
<!-- 사원 검색 -->
<div class="section-title">사원 검색</div>
<div class="search-area">
<div class="search-row">
<input type="text" id="txtEmpSearch" placeholder="사원명을 입력하세요" />
<button type="button" class="btn-search" id="btnSearchEmp">검색</button>
</div>
<div class="emp-list-area">
<table>
<thead>
<tr>
<th style="width:30px">선택</th>
<th style="width:150px">사원명</th>
<th>부서</th>
<th style="width:70px">직급</th>
<th style="width:70px">직책</th>
</tr>
</thead>
<tbody id="tbEmpList">
<tr><td colspan="5" style="text-align:center;padding:20px;color:#999;">사원명을 입력하고 검색하세요</td></tr>
</tbody>
</table>
</div>
</div>
<!-- 결재라인 -->
<div class="section-title">결재라인</div>
<div class="appline-area">
<div class="appline-controls">
<select id="selApprovalType">
<option value="002">검토</option>
<option value="003" selected>결재</option>
<option value="004">협조</option>
<option value="007">합의</option>
</select>
<button type="button" class="btn-appline add" id="btnAddAppLine">추가 ▶</button>
<button type="button" class="btn-appline remove" id="btnRemoveAppLine">삭제</button>
<button type="button" class="btn-appline" id="btnMoveUp">▲ 위로</button>
<button type="button" class="btn-appline" id="btnMoveDown">▼ 아래로</button>
</div>
<div class="appline-table-area">
<table>
<thead>
<tr>
<th style="width:30px">선택</th>
<th style="width:40px">순번</th>
<th style="width:70px">결재유형</th>
<th style="width:130px">사원명</th>
<th>부서</th>
<th style="width:70px">직급</th>
<th style="width:70px">직책</th>
</tr>
</thead>
<tbody id="tbAppLine">
<!-- 기안자 행은 자동 추가됨 -->
</tbody>
</table>
</div>
</div>
<!-- 버튼 -->
<div class="btn-area">
<button type="button" class="btn-submit" id="btnSubmitApproval">결재 상신</button>
<button type="button" class="btn-cancel" id="btnClosePopup">취소</button>
</div>
</div>
<!-- 로딩 오버레이 -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner">결재 상신 중...</div>
</div>
<script>
// 전역 변수
var g_empList = []; // 전체 사원 목록 (API에서 가져온 데이터)
var g_appLineList = []; // 결재라인 배열
var g_selectedEmpIdx = -1; // 사원 검색 목록에서 선택된 인덱스
var g_selectedLineIdx = -1; // 결재라인에서 선택된 인덱스
var g_empListLoaded = false; // 사원 목록 로드 여부
var g_currentUserEmpSeq = "<%=empseq%>";
var g_currentUserName = "<%=connectUserName%>";
// 파라미터
var g_targetType = "<%=targetType%>";
var g_targetObjId = "<%=targetObjId%>";
var g_tiKeyCode = "<%=tiKeyCode%>";
var g_approKey = "<%=approKey%>";
var g_aiKeyCode = "<%=aiKeyCode%>";
$(document).ready(function(){
// 사원 목록 최초 로드
fn_loadEmployeeList();
// 사원 검색 버튼
$("#btnSearchEmp").click(function(){
fn_filterEmployeeList();
});
// 사원 검색 엔터키
$("#txtEmpSearch").keypress(function(e){
if(e.which == 13){
fn_filterEmployeeList();
}
});
// 결재라인에 추가
$("#btnAddAppLine").click(function(){
fn_addToAppLine();
});
// 결재라인에서 삭제
$("#btnRemoveAppLine").click(function(){
fn_removeFromAppLine();
});
// 위로 이동
$("#btnMoveUp").click(function(){
fn_moveAppLine(-1);
});
// 아래로 이동
$("#btnMoveDown").click(function(){
fn_moveAppLine(1);
});
// 결재 상신
$("#btnSubmitApproval").click(function(){
fn_submitApproval();
});
// 취소
$("#btnClosePopup").click(function(){
window.close();
});
});
/**
* Amaranth 사원 목록 로드 (전체)
*/
function fn_loadEmployeeList(){
$.ajax({
url: "/approval/searchAmaranthEmployees.do",
type: "POST",
dataType: "json",
success: function(data){
if(data && data.resultCode == 0 && data.resultData){
g_empList = [];
var rawList = data.resultData;
// 사원 목록 파싱
for(var i = 0; i < rawList.length; i++){
var emp = rawList[i];
var empSeq = emp.empSeq || "";
var empName = emp.empName || emp.empNameKr || "";
var loginId = emp.loginId || "";
var useYn = emp.useYn || "";
// 사용 중인 사원만
if(useYn != "Y") continue;
// empDeptList에서 부서 정보 추출
var deptList = emp.empDeptList || [];
if(deptList.length > 0){
// 주 부서 우선, 없으면 첫번째 부서
var mainDept = null;
for(var d = 0; d < deptList.length; d++){
if(deptList[d].mainDeptYn == "Y"){
mainDept = deptList[d];
break;
}
}
if(!mainDept) mainDept = deptList[0];
// 재직자만 (J01 = 재직)
if(mainDept.workStatus && mainDept.workStatus != "J01") continue;
g_empList.push({
empSeq: empSeq,
empName: empName,
loginId: loginId,
deptSeq: mainDept.deptSeq || "",
deptName: mainDept.deptName || "",
compSeq: mainDept.compSeq || "",
positionName: mainDept.positionCodeName || "",
dutyName: mainDept.dutyCodeName || ""
});
}
}
g_empListLoaded = true;
console.log("사원 목록 로드 완료: " + g_empList.length + "명");
// 기안자 자동 추가
fn_addDrafter();
} else {
console.error("사원 목록 로드 실패:", data);
}
},
error: function(xhr, status, error){
console.error("사원 목록 로드 AJAX 오류:", error);
}
});
}
/**
* 기안자 자동 추가 (결재라인 첫번째)
*/
function fn_addDrafter(){
if(!g_currentUserEmpSeq){
alert("현재 사용자의 empSeq 정보가 없습니다.");
return;
}
// 현재 사용자 정보 찾기
var drafterInfo = null;
for(var i = 0; i < g_empList.length; i++){
if(g_empList[i].empSeq == g_currentUserEmpSeq){
drafterInfo = g_empList[i];
break;
}
}
if(drafterInfo){
g_appLineList = [{
empSeq: drafterInfo.empSeq,
empName: drafterInfo.empName,
deptSeq: drafterInfo.deptSeq,
deptName: drafterInfo.deptName,
compSeq: drafterInfo.compSeq,
positionName: drafterInfo.positionName,
dutyName: drafterInfo.dutyName,
klUserType: "001", // 기안
klUserTypeName: "기안",
isDrafter: true
}];
} else {
// 사원 목록에서 찾을 수 없는 경우 세션 정보로 기안자 추가
g_appLineList = [{
empSeq: g_currentUserEmpSeq,
empName: g_currentUserName,
deptSeq: "",
deptName: "",
compSeq: "",
positionName: "",
dutyName: "",
klUserType: "001",
klUserTypeName: "기안",
isDrafter: true
}];
}
fn_renderAppLine();
}
/**
* 사원 검색 필터링 (클라이언트 필터)
*/
function fn_filterEmployeeList(){
var keyword = $.trim($("#txtEmpSearch").val());
if(!g_empListLoaded){
alert("사원 목록을 로드 중입니다. 잠시 후 다시 시도하세요.");
return;
}
if(keyword == ""){
alert("검색어를 입력하세요.");
return;
}
var filtered = [];
for(var i = 0; i < g_empList.length; i++){
var emp = g_empList[i];
if(emp.empName.indexOf(keyword) >= 0 || emp.deptName.indexOf(keyword) >= 0){
filtered.push(emp);
}
}
fn_renderEmpList(filtered);
}
/**
* 사원 검색 결과 렌더링
*/
function fn_renderEmpList(list){
var html = "";
g_selectedEmpIdx = -1;
if(list.length == 0){
html = '<tr><td colspan="5" style="text-align:center;padding:20px;color:#999;">검색 결과가 없습니다</td></tr>';
} else {
for(var i = 0; i < list.length; i++){
var emp = list[i];
html += '<tr data-idx="' + i + '" onclick="fn_selectEmp(this, ' + i + ')"';
html += ' data-empseq="' + emp.empSeq + '"';
html += ' data-empname="' + fn_escHtml(emp.empName) + '"';
html += ' data-deptseq="' + emp.deptSeq + '"';
html += ' data-deptname="' + fn_escHtml(emp.deptName) + '"';
html += ' data-compseq="' + emp.compSeq + '"';
html += ' data-positionname="' + fn_escHtml(emp.positionName) + '"';
html += ' data-dutyname="' + fn_escHtml(emp.dutyName) + '"';
html += '>';
html += '<td style="text-align:center"><input type="radio" name="empRadio" /></td>';
html += '<td>' + fn_escHtml(emp.empName) + '</td>';
html += '<td>' + fn_escHtml(emp.deptName) + '</td>';
html += '<td>' + fn_escHtml(emp.positionName) + '</td>';
html += '<td>' + fn_escHtml(emp.dutyName) + '</td>';
html += '</tr>';
}
}
// 검색 결과를 별도 배열로 보관
window._filteredList = list;
$("#tbEmpList").html(html);
}
/**
* 사원 목록에서 행 선택
*/
function fn_selectEmp(row, idx){
g_selectedEmpIdx = idx;
$("#tbEmpList tr").removeClass("selected");
$(row).addClass("selected");
$(row).find("input[type=radio]").prop("checked", true);
}
/**
* 결재라인에 추가
*/
function fn_addToAppLine(){
if(g_selectedEmpIdx < 0 || !window._filteredList || !window._filteredList[g_selectedEmpIdx]){
alert("추가할 사원을 선택해주세요.");
return;
}
var emp = window._filteredList[g_selectedEmpIdx];
var approvalType = $("#selApprovalType").val();
var approvalTypeName = $("#selApprovalType option:selected").text();
// 중복 체크
for(var i = 0; i < g_appLineList.length; i++){
if(g_appLineList[i].empSeq == emp.empSeq){
alert("이미 결재라인에 추가된 사원입니다.");
return;
}
}
g_appLineList.push({
empSeq: emp.empSeq,
empName: emp.empName,
deptSeq: emp.deptSeq,
deptName: emp.deptName,
compSeq: emp.compSeq,
positionName: emp.positionName,
dutyName: emp.dutyName,
klUserType: approvalType,
klUserTypeName: approvalTypeName,
isDrafter: false
});
fn_renderAppLine();
}
/**
* 결재라인에서 삭제 (기안자는 삭제 불가)
*/
function fn_removeFromAppLine(){
if(g_selectedLineIdx < 0){
alert("삭제할 결재자를 선택해주세요.");
return;
}
if(g_appLineList[g_selectedLineIdx].isDrafter){
alert("기안자는 삭제할 수 없습니다.");
return;
}
g_appLineList.splice(g_selectedLineIdx, 1);
g_selectedLineIdx = -1;
fn_renderAppLine();
}
/**
* 결재라인 순서 이동 (기안자는 이동 불가)
*/
function fn_moveAppLine(direction){
if(g_selectedLineIdx < 0){
alert("이동할 결재자를 선택해주세요.");
return;
}
// 기안자(0번)는 이동 불가
if(g_selectedLineIdx == 0){
alert("기안자는 이동할 수 없습니다.");
return;
}
var newIdx = g_selectedLineIdx + direction;
// 기안자 위치(0)로는 이동 불가
if(newIdx <= 0 || newIdx >= g_appLineList.length){
return;
}
// swap
var temp = g_appLineList[g_selectedLineIdx];
g_appLineList[g_selectedLineIdx] = g_appLineList[newIdx];
g_appLineList[newIdx] = temp;
g_selectedLineIdx = newIdx;
fn_renderAppLine();
}
/**
* 결재라인 렌더링
*/
function fn_renderAppLine(){
var html = "";
if(g_appLineList.length == 0){
html = '<tr><td colspan="7" style="text-align:center;padding:15px;color:#999;">결재라인이 비어있습니다</td></tr>';
} else {
for(var i = 0; i < g_appLineList.length; i++){
var line = g_appLineList[i];
var rowClass = line.isDrafter ? ' class="drafter"' : '';
var isSelected = (i == g_selectedLineIdx);
html += '<tr' + rowClass + (isSelected ? ' style="background:#cde4f7;"' : '') + ' onclick="fn_selectLine(' + i + ')">';
html += '<td><input type="radio" name="lineRadio"' + (isSelected ? ' checked' : '') + ' /></td>';
html += '<td>' + (i + 1) + '</td>';
html += '<td>' + fn_escHtml(line.klUserTypeName) + '</td>';
html += '<td>' + fn_escHtml(line.empName) + '</td>';
html += '<td style="text-align:left">' + fn_escHtml(line.deptName) + '</td>';
html += '<td>' + fn_escHtml(line.positionName) + '</td>';
html += '<td>' + fn_escHtml(line.dutyName) + '</td>';
html += '</tr>';
}
}
$("#tbAppLine").html(html);
}
/**
* 결재라인 행 선택
*/
function fn_selectLine(idx){
g_selectedLineIdx = idx;
fn_renderAppLine();
}
/**
* 결재 상신 처리
*/
function fn_submitApproval(){
// 유효성 검사
var title = $.trim($("#txtDocTitle").val());
if(title == ""){
alert("문서 제목을 입력해주세요.");
$("#txtDocTitle").focus();
return;
}
if(g_appLineList.length < 2){
alert("결재자를 1명 이상 추가해주세요.\n(기안자 + 결재자)");
return;
}
// 결재라인 JSON 구성
var appLineJson = [];
for(var i = 0; i < g_appLineList.length; i++){
var line = g_appLineList[i];
var seqNum = fn_padSeqNum(i + 1);
appLineJson.push({
positionName: line.positionName || "",
deptSeq: line.deptSeq || "",
klASeqNum: "1",
compSeq: line.compSeq || "",
empName: line.empName || "",
empSeq: line.empSeq || "",
klSeqNum: seqNum,
klUserTypeName: line.klUserTypeName || "",
klUserType: line.klUserType || "",
dutyName: line.dutyName || ""
});
}
var appLineListStr = JSON.stringify(appLineJson);
// 기안자의 deptSeq (companyInfo 용)
var drafterDeptSeq = g_appLineList[0].deptSeq || "";
if(!confirm("결재 상신 하시겠습니까?")){
return;
}
// 로딩 표시
$("#loadingOverlay").addClass("active");
$.ajax({
url: "/approval/submitAmaranthApproval.do",
type: "POST",
data: {
"title": title,
"appLineListJson": appLineListStr,
"deptSeq": drafterDeptSeq,
"tiKeyCode": g_tiKeyCode,
"approKey": g_approKey,
"aiKeyCode": g_aiKeyCode,
"targetType": g_targetType,
"targetObjId": g_targetObjId
},
dataType: "json",
success: function(data){
$("#loadingOverlay").removeClass("active");
if(data && data.resultCode == 0){
alert("결재 상신이 완료되었습니다.");
// 부모 창 새로고침
if(window.opener && window.opener.fn_search){
window.opener.fn_search();
}
window.close();
} else {
var msg = "결재 상신에 실패했습니다.";
if(data && data.resultMsg){
msg += "\n\n" + data.resultMsg;
}
alert(msg);
}
},
error: function(xhr, status, error){
$("#loadingOverlay").removeClass("active");
alert("결재 상신 중 오류가 발생했습니다.\n" + error);
}
});
}
/**
* 순번 패딩 (10자리, 예: 0000000001)
*/
function fn_padSeqNum(num){
var s = "0000000000" + num;
return s.substring(s.length - 10);
}
/**
* HTML 이스케이프
*/
function fn_escHtml(str){
if(!str) return "";
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
</script>
</body>
</html>