결재 건수 및 결재 상세 화면 아마란스 연동

This commit is contained in:
2026-02-10 17:01:35 +09:00
parent 741f3737d4
commit 97f6cd77be
6 changed files with 1118 additions and 146 deletions

View File

@@ -0,0 +1,247 @@
<%@ 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>
/* 상세 팝업 전용 스타일 */
.doc-detail-wrap {
padding: 10px;
}
.doc-info-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
}
.doc-info-table td {
padding: 5px 8px;
border: 1px solid #ccc;
font-size: 12px;
}
.doc-info-table td.label {
background-color: #f5f5f5;
font-weight: bold;
width: 80px;
text-align: center;
white-space: nowrap;
}
.doc-contents-wrap {
border: 1px solid #ccc;
margin-top: 5px;
background: #fff;
}
.doc-contents-wrap iframe {
width: 100%;
min-height: 500px;
border: none;
}
.file-list-wrap {
margin-top: 8px;
padding: 5px 8px;
border: 1px solid #ccc;
background: #fafafa;
}
.file-list-wrap .file-title {
font-weight: bold;
font-size: 12px;
margin-bottom: 4px;
}
.file-list-wrap .file-item {
font-size: 11px;
padding: 2px 0;
}
.loading-msg {
text-align: center;
padding: 50px;
font-size: 14px;
color: #666;
}
.error-msg {
text-align: center;
padding: 50px;
font-size: 14px;
color: #e74c3c;
}
</style>
<script type="text/javascript">
$(function(){
$(document).ready(function(){
fn_loadDetail();
});
$('#btn_close').click(function(){
self.close();
});
});
// 결재 문서 상세 조회
function fn_loadDetail(){
var docId = fnc_getUrlParam("docId");
var deptSeq = fnc_getUrlParam("deptSeq");
if(!docId){
$("#detailContent").html('<div class="error-msg">문서 ID가 없습니다.</div>');
return;
}
$.ajax({
url: "/approval/getAmaranthApprovalDocDetail.do",
type: "POST",
data: {
"docId": docId,
"deptSeq": deptSeq || ""
},
dataType: "json",
beforeSend: function(){
$("#detailContent").html('<div class="loading-msg">문서 정보를 조회중입니다...</div>');
},
success: function(data){
if(data.resultCode == 0 && data.resultData && data.resultData.result){
fn_renderDetail(data.resultData.result);
} else {
var msg = data.resultMsg || "문서 조회에 실패했습니다.";
$("#detailContent").html('<div class="error-msg">' + msg + '</div>');
}
},
error: function(xhr, status, error){
console.error("상세 조회 오류:", error);
$("#detailContent").html('<div class="error-msg">문서 조회 중 오류가 발생했습니다.</div>');
}
});
}
// 상세 정보 렌더링
function fn_renderDetail(result){
var html = '<div class="doc-detail-wrap">';
// 문서 기본 정보 테이블
html += '<table class="doc-info-table">';
html += '<tr>';
html += '<td class="label">문서번호</td><td>' + (result.docId || '') + '</td>';
html += '<td class="label">양식명</td><td>' + (result.formName || '') + '</td>';
html += '</tr>';
html += '<tr>';
html += '<td class="label">제목</td><td colspan="3">' + (result.docTitle || '') + '</td>';
html += '</tr>';
html += '<tr>';
html += '<td class="label">기안부서</td><td>' + (result.deptName || '') + '</td>';
html += '<td class="label">기안자</td><td>' + (result.empName || '') + '</td>';
html += '</tr>';
html += '<tr>';
html += '<td class="label">직급</td><td>' + (result.positionName || '') + '</td>';
html += '<td class="label">직책</td><td>' + (result.dutyName || '') + '</td>';
html += '</tr>';
html += '<tr>';
html += '<td class="label">기안일</td><td>' + fn_formatDate(result.repDt) + '</td>';
html += '<td class="label">상태</td><td>' + (result.docStsName || '') + '</td>';
html += '</tr>';
html += '<tr>';
html += '<td class="label">최종결재자</td><td>' + (result.lineName || '') + '</td>';
html += '<td class="label">첨부파일</td><td>' + (result.attachCnt || '0') + '건</td>';
html += '</tr>';
html += '</table>';
// 첨부파일 목록
if(result.fileList && result.fileList.length > 0){
html += '<div class="file-list-wrap">';
html += '<div class="file-title">첨부파일 (' + result.fileList.length + '건)</div>';
for(var i = 0; i < result.fileList.length; i++){
var file = result.fileList[i];
var fileName = (file.originalFileName || '파일') + '.' + (file.fileExtsn || '');
var fileSize = fn_formatFileSize(file.fileSize);
html += '<div class="file-item">📎 ' + fileName + ' (' + fileSize + ')</div>';
}
html += '</div>';
}
// 문서 내용 (HTML)
if(result.contents){
html += '<div class="doc-contents-wrap">';
html += '<iframe id="docContentsFrame" sandbox="allow-same-origin"></iframe>';
html += '</div>';
}
html += '</div>';
$("#detailContent").html(html);
// iframe에 문서 HTML 삽입
if(result.contents){
setTimeout(function(){
var iframe = document.getElementById("docContentsFrame");
if(iframe){
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write(result.contents);
iframeDoc.close();
// iframe 높이 자동 조절
setTimeout(function(){
try {
var bodyHeight = iframeDoc.body.scrollHeight;
if(bodyHeight > 0){
iframe.style.height = Math.min(bodyHeight + 30, 800) + "px";
}
} catch(e){}
}, 500);
}
}, 100);
}
}
// 날짜 포맷팅 (YYYYMMDDHHMISS → YYYY-MM-DD HH:MI)
function fn_formatDate(dt){
if(!dt || dt.length < 8) return dt || '';
var formatted = dt.substring(0, 4) + '-' + dt.substring(4, 6) + '-' + dt.substring(6, 8);
if(dt.length >= 12){
formatted += ' ' + dt.substring(8, 10) + ':' + dt.substring(10, 12);
}
return formatted;
}
// 파일 크기 포맷팅
function fn_formatFileSize(size){
if(!size) return '0 B';
var s = parseInt(size);
if(s < 1024) return s + ' B';
if(s < 1024 * 1024) return (s / 1024).toFixed(1) + ' KB';
return (s / (1024 * 1024)).toFixed(1) + ' MB';
}
// URL 파라미터 가져오기
function fnc_getUrlParam(name){
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
if(results == null) return '';
return decodeURIComponent(results[1]) || '';
}
</script>
</head>
<body class="backcolor_light_blue">
<form name="form1" action="" method="post">
<section>
<div class="plm_menu_name">
<h2>
<span>결재 문서 상세</span>
</h2>
</div>
<div id="businessPopupFormWrap">
<div id="detailContent">
<div class="loading-msg">문서 정보를 조회중입니다...</div>
</div>
<div class="btn_wrap">
<div class="plm_btn_wrap_center">
<br>
<input type="button" value="닫기" id="btn_close" class="plm_btns">
</div>
</div>
</div>
</section>
</form>
</body>
</html>

View File

@@ -8,58 +8,159 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<!-- //JSTL 변수선언 -->
<%-- [주석 처리] 기존 JSTL 페이징 변수 - Amaranth API AJAX 방식으로 변경
<c:set var="totalCount" value="${empty TOTAL_COUNT?0:TOTAL_COUNT}" />
<c:set var="maxPage" value="${empty MAX_PAGE_SIZE?1:MAX_PAGE_SIZE}" />
<c:set var="nPage" value="${empty param.page?1:param.page}" />
<c:set var="pageIndex" value="${(nPage-1)/10}" />
<c:set var="nextPage" value="${empty NEXT_PAGE?1:NEXT_PAGE}" />
<c:set var="prevPage" value="${empty PREV_PAGE?1:PREV_PAGE}" />
--%>
<script type="text/javascript">
// 현재 페이지 및 페이징 상태
var currentPage = 1;
var maxPage = 1;
var totalCount = 0;
$(function(){
$(document).ready(function(){
fnc_datepick();
// 페이지 로드 시 Amaranth API로 결재 목록 조회
fn_loadApprovalList(1);
});
$(".searchInput").keyup(function(e){
var keyCode = e.keyCode;
if(keyCode == 13){
fn_search();
if(e.keyCode == 13){
fn_searchAmaranth();
}
});
$("#btnSearch").click(function(){
fn_search();
fn_searchAmaranth();
});
$(".btnApprovalDetail").click(function(){
var approvalObjId = $(this).attr("data-APPROVAL_OBJID");
var routeObjId = $(this).attr("data-ROUTE_OBJID");
//Swal.fire("approvalObjId : "+approvalObjId+", routeObjId : "+routeObjId);
var params = "?approvalObjId="+approvalObjId;
params += "&routeObjId="+routeObjId;
//Swal.fire("params : "+params);
window.open("/approval/approvalDetail.do"+params,"approvalDetailPopup","width=650 height=620 menubar=no status=no");
});
//결재 유형에 따른 대상구분명을 가져와 보여준다.
$("X.targetTypeA").each( function() {
var code = $(this).text();
var codeName = fnc_getApprovalTargetName(code);
$(this).text(codeName);
} );
});
// Amaranth API 결재 문서 목록 조회
function fn_loadApprovalList(page){
currentPage = page;
var keyWord = $("#search_approvalTitle").val() || "";
var fromDt = $("#search_fromDate").val() || "";
var toDt = $("#search_toDate").val() || "";
$.ajax({
url: "/approval/getAmaranthApprovalDocList.do",
type: "POST",
data: {
"page": page,
"search_approvalTitle": keyWord,
"search_fromDate": fromDt,
"search_toDate": toDt
},
dataType: "json",
beforeSend: function(){
$("#approvalListBody").html('<tr style="text-align:center;"><td colspan="7">조회중...</td></tr>');
},
success: function(data){
totalCount = data.totalCount || 0;
maxPage = data.maxPage || 1;
var list = data.list || [];
fn_renderList(list);
fn_renderPaging(currentPage, maxPage, totalCount);
},
error: function(xhr, status, error){
console.error("결재 목록 조회 오류:", error);
$("#approvalListBody").html('<tr style="text-align:center;"><td colspan="7">조회 중 오류가 발생했습니다.</td></tr>');
fn_renderPaging(1, 1, 0);
}
});
}
// 검색
function fn_searchAmaranth(){
fn_loadApprovalList(1);
}
// 테이블 목록 렌더링
function fn_renderList(list){
var html = "";
if(list.length == 0){
html = '<tr style="text-align:center;"><td colspan="7">조회된 정보가 없습니다.</td></tr>';
} else {
for(var i = 0; i < list.length; i++){
var item = list[i];
html += '<tr style="text-align:center;">';
html += '<td>' + (item.RNUM || '') + '</td>';
html += '<td><a href="javascript:fn_openApprovalDetail(\'' + (item.DOC_ID || '') + '\',\'' + (item.DEPT_SEQ || '') + '\');" style="color:#0066cc;text-decoration:underline;">' + (item.DOC_ID || '') + '</a></td>';
html += '<td>' + (item.FORM_NAME || '') + '</td>';
html += '<td class="align_l4"><a href="javascript:fn_openApprovalDetail(\'' + (item.DOC_ID || '') + '\',\'' + (item.DEPT_SEQ || '') + '\');">' + (item.DOC_TITLE || '') + '</a></td>';
html += '<td>' + (item.REP_DT || '') + '</td>';
html += '<td>' + (item.DEPT_NAME || '') + ' ' + (item.EMP_NAME || '') + '</td>';
html += '<td>' + (item.DOC_STS_NAME || '') + '</td>';
html += '</tr>';
}
}
$("#approvalListBody").html(html);
}
// Amaranth 결재 문서 상세 팝업 열기
function fn_openApprovalDetail(docId, deptSeq){
if(!docId) return;
var params = "?docId=" + docId + "&deptSeq=" + (deptSeq || "");
window.open("/approval/amaranthApprovalDetail.do" + params, "amaranthApprovalDetail", "width=1000,height=800,menubar=no,status=no,scrollbars=yes,resizable=yes");
}
// 페이징 렌더링
function fn_renderPaging(nPage, maxPage, totalCount){
var html = "";
if(totalCount > 0){
html += '<table><tr>';
// prev
if(nPage > 1){
html += '<td><a href="javascript:fn_loadApprovalList(' + (nPage - 1) + ');">prev</a></td>';
} else {
html += '<td class="no_more_page">prev</td>';
}
// 페이지 번호
var startPage = nPage > 5 ? nPage - 5 : 1;
var endPage = nPage > 5 ? nPage + 4 : 10;
if(endPage > maxPage) endPage = maxPage;
for(var i = startPage; i <= endPage; i++){
if(i == nPage){
html += '<td><a href="#" class="now_page">' + i + '</a></td>';
} else {
html += '<td><a href="javascript:fn_loadApprovalList(' + i + ');">' + i + '</a></td>';
}
}
// next
if(nPage < maxPage){
html += '<td><a href="javascript:fn_loadApprovalList(' + (nPage + 1) + ');">next</a></td>';
} else {
html += '<td class="no_more_page">next</td>';
}
html += '</tr></table>';
html += '<p id="adminPageCount">총 ' + totalCount + '건</p>';
}
$("#pagingArea").html(html);
}
<%-- [주석 처리] 기존 form submit 방식 검색 - AJAX 방식으로 변경
function fn_search(){
document.form1.action = "/approval/approvalList.do";
document.form1.submit();
}
function openApprovalPop(){
window.open("/approval/approvalDetail.do","specDataPopUp","width=650 height=730 menubar=no status=no");
}
--%>
</script>
</head>
@@ -83,33 +184,17 @@ function openApprovalPop(){
<table>
<tr>
<td class="">
<label for="" class="">제목</label>
<label for="" class="">검색어</label>
</td>
<td>
<input type="text" name="search_approvalTitle" id="search_approvalTitle" class="searchInput" value="${param.search_approvalTitle}" style="width:250px;"/>
<input type="text" name="search_approvalTitle" id="search_approvalTitle" class="searchInput" value="" style="width:250px;" placeholder="제목/내용/작성자"/>
</td>
<td class="">
<label for="" class="">상신일</label>
<label for="" class="">기안일</label>
</td>
<td>
<input type="text" name="search_fromDate" id="search_fromDate" value="${param.search_fromDate}" class="input_date_solo searchInput"> ~ <input type="text" name="search_toDate" id="search_toDate" value="${param.search_toDate}" class="input_date_solo searchInput">
<input type="text" name="search_fromDate" id="search_fromDate" value="" class="input_date_solo searchInput"> ~ <input type="text" name="search_toDate" id="search_toDate" value="" class="input_date_solo searchInput">
</td>
<td class="">
<label for="" class="">상신자</label>
</td>
<td>
<input type="text" name="search_writerUserName" id="search_writerUserName" value="${param.search_writerUserName}" class="searchInput">
</td>
<%-- <td class="">
<label for="" class="">상태</label>
</td>
<td>
<select name="search_approvalStatus" id="search_approvalStatus">
<option value="">전체</option>
<option value="inProcess" ${param.search_approvalStatus eq 'inProcess'?'selected':''}>결재중</option>
<option value="complete" ${param.search_approvalStatus eq 'complete'?'selected':''}>결재완료</option>
</select>
</td> --%>
</tr>
</table>
</div>
@@ -119,20 +204,20 @@ function openApprovalPop(){
<colgroup>
<col width="5%">
<col width="10%">
<col width="10%">
<col width="12%">
<col width="*">
<col width="10%">
<col width="20%">
<col width="10%">
<col width="15%">
<col width="8%">
</colgroup>
<thead>
<tr class="plm_thead">
<td>No</td>
<td>결재번호</td>
<td>대상구분</td>
<td>문서번호</td>
<td>양식명</td>
<td>제목</td>
<td>상신일</td>
<td>상신자</td>
<td>기안일</td>
<td>기안자</td>
<td>상태</td>
</tr>
</table>
@@ -142,101 +227,23 @@ function openApprovalPop(){
<colgroup>
<col width="5%">
<col width="10%">
<col width="10%">
<col width="12%">
<col width="*">
<col width="10%">
<col width="20%">
<col width="10%">
<col width="15%">
<col width="8%">
</colgroup>
<c:choose>
<c:when test="${!empty LIST}">
<c:forEach var="item" items="${LIST}" varStatus="status">
<tr style="text-align:center;">
<td>${item.RNUM}</td>
<td>${item.ROUTE_NO}</td>
<td class="targetType">${item.TARGET_NAME}</td>
<td class="align_l4"><a href="#" class="btnApprovalDetail" data-APPROVAL_OBJID="${item.APPROVAL_OBJID}" data-ROUTE_OBJID="${item.ROUTE_OBJID}">${item.APPROVAL_TITLE}</a></td>
<td>${item.ROUTE_REGDATE}</td>
<td>${item.WRITER_DEPT_NAME} ${item.WRITER_USER_NAME}</td>
<td>
<c:choose>
<c:when test="${item.STATUS eq 'inProcess'}">
결재중
</c:when>
<c:when test="${item.STATUS eq 'reject'}">
반려
</c:when>
<c:when test="${item.STATUS eq 'complete'}">
결재완료
</c:when>
<c:when test="${item.STATUS eq 'cancel'}">
결재취소
</c:when>
<c:otherwise>
${item.STATUS}
</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</c:when>
<c:otherwise>
<tr style="text-align:center;">
<td colspan="7">조회된 정보가 없습니다.</td>
</tr>
</c:otherwise>
</c:choose>
<tbody id="approvalListBody">
<tr style="text-align:center;">
<td colspan="7">조회중...</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="pdm_page">
<%--
nPage ${nPage}
prevPage ${prevPage}
maxPage ${maxPage}
nextPage ${nextPage}
totalCount ${totalCount}
--%>
<input type="hidden" name="page" id="page" value="${nPage}">
<c:if test="${!empty LIST}">
<div class="page_pro">
<table>
<tr>
<c:choose>
<c:when test="${nPage > 1}">
<td><a href="javascript:fnc_goPrev('${prevPage}');">prev</a></td>
</c:when>
<c:otherwise>
<td class="no_more_page">prev</td>
</c:otherwise>
</c:choose>
<c:forEach var="v" begin="${nPage>5?nPage-5:1}" end="${nPage>5?nPage+4:10}" step="1" varStatus="status">
<c:if test="${status.index -1 < maxPage}">
<c:choose>
<c:when test="${status.index eq nPage}">
<td><a href="#" class="now_page">${nPage}</a></td>
</c:when>
<c:otherwise>
<td><a href="javascript:fnc_goPage('${status.index}');">${status.index}</a></td>
</c:otherwise>
</c:choose>
</c:if>
</c:forEach>
<c:choose>
<c:when test="${nPage < maxPage}">
<td><a href="javascript:fnc_goNext('${nextPage}');">next</a></td>
</c:when>
<c:otherwise>
<td class="no_more_page">next</td>
</c:otherwise>
</c:choose>
</tr>
</table>
<p id="adminPageCount">총 ${totalCount}건</p>
<div class="page_pro" id="pagingArea">
</div>
</c:if>
</div>
</div>
</div>

View File

@@ -142,9 +142,9 @@ $(function(){
fn_goMyTaskMyApproval();
});
//결재건수 세팅
fn_setApprovalCnt();
setInterval(fn_setApprovalCnt, 60000); //refresh
//결재건수 세팅 (주석 해제 시 Amaranth10 결재 건수 조회 활성화)
//fn_setApprovalCnt();
//setInterval(fn_setApprovalCnt, 60000); //refresh
//setTimeout(() => fn_setApprovalCnt(), 10000);
$(".blink_none").children("span").children("img").attr({src:"/images/bell.png"});
@@ -411,8 +411,8 @@ function fn_setApprovalCnt(){
<!-- 우측 버튼 영역 -->
<td style="text-align: right; padding-right: 10px;">
<span id="blink" style="padding: 3px 6px; border-radius:3px; font-size:10px; background-color:#ff6b35; color:#fff; cursor:pointer; margin-right: 5px;" class="btnApprovalList blink_none">
결재 <label class="notice_no">0</label>건</span>
<!-- <span id="blink" style="padding: 3px 6px; border-radius:3px; font-size:10px; background-color:#ff6b35; color:#fff; cursor:pointer; margin-right: 5px;" class="btnApprovalList blink_none">
결재 <label class="notice_no">0</label>건</span> -->
<a href="#" onclick="javascript:fn_openBoardList('qna');" style="color:#fff;background-color:#676868; border-radius:2px; padding:2px 4px; text-decoration: none; display: inline-block; font-size:9px; margin-right: 5px;">Q&A</a>

View File

@@ -403,6 +403,285 @@ public class AmaranthApprovalApiClient {
}
}
/**
* 결재 문서 목록 조회 - 서버 인증 방식
* @param baseUrl API 서버의 기본 URL
* @param empSeq 사원 시퀀스
* @param compSeq 회사 시퀀스
* @param menuId 결재함 메뉴 ID (미결: 1001000 등)
* @param fromDt 조회 시작일 (YYYY-MM-DD)
* @param toDt 조회 종료일 (YYYY-MM-DD)
* @param keyWord 검색어
* @param page 페이지 번호
* @param pageSize 페이지 사이즈
* @return API 응답 결과 (JSON 문자열)
*/
public String getApprovalDocList(String baseUrl, String empSeq, String compSeq,
String menuId, String fromDt, String toDt, String keyWord,
String page, String pageSize) throws Exception {
System.setProperty("https.protocols", "TLSv1.2");
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
public boolean verify(String hostname, javax.net.ssl.SSLSession session) { return true; }
});
String urlPath = "/apiproxy/api99u02A02";
String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
String fullUrl = cleanBaseUrl + urlPath;
URL url = new URL(fullUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
connection.setInstanceFollowRedirects(false);
try {
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("callerName", CALLER_NAME);
connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
String transactionId = generateTransactionId();
connection.setRequestProperty("transaction-id", transactionId);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
connection.setRequestProperty("timestamp", timestamp);
connection.setRequestProperty("groupSeq", GROUP_SEQ);
String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath, HASH_KEY);
connection.setRequestProperty("wehago-sign", wehagoSign);
// 요청 본문 작성
StringBuilder body = new StringBuilder();
body.append("{");
body.append("\"header\":{");
body.append("\"empSeq\":\"").append(escapeJson(empSeq)).append("\",");
body.append("\"groupSeq\":\"").append(escapeJson(GROUP_SEQ)).append("\"");
body.append("},");
body.append("\"body\":{");
body.append("\"menuId\":\"").append(escapeJson(menuId)).append("\",");
if (fromDt != null && !fromDt.isEmpty()) {
body.append("\"fromDt\":\"").append(escapeJson(fromDt)).append("\",");
}
if (toDt != null && !toDt.isEmpty()) {
body.append("\"toDt\":\"").append(escapeJson(toDt)).append("\",");
}
body.append("\"pageSize\":\"").append(escapeJson(pageSize != null ? pageSize : "30")).append("\",");
body.append("\"keyWord\":\"").append(escapeJson(keyWord != null ? keyWord : "")).append("\",");
body.append("\"page\":\"").append(escapeJson(page != null ? page : "1")).append("\",");
body.append("\"sort\":\"10\",");
body.append("\"langCode\":\"kr\",");
body.append("\"searchKind\":\"1\",");
body.append("\"companyInfo\":{");
body.append("\"compSeq\":\"").append(escapeJson(compSeq)).append("\"");
body.append("}");
body.append("}");
body.append("}");
String requestBody = body.toString();
System.out.println("=== Amaranth 결재 문서 목록 조회 ===");
System.out.println("URL: " + fullUrl);
System.out.println("Request Body: " + requestBody);
connection.setDoOutput(true);
connection.setDoInput(true);
OutputStreamWriter writer = new OutputStreamWriter(
connection.getOutputStream(), StandardCharsets.UTF_8);
writer.write(requestBody);
writer.flush();
writer.close();
int responseCode = connection.getResponseCode();
BufferedReader reader = null;
StringBuilder response = new StringBuilder();
try {
if (responseCode >= 200 && responseCode < 300) {
reader = new BufferedReader(new InputStreamReader(
connection.getInputStream(), StandardCharsets.UTF_8));
} else {
java.io.InputStream errorStream = connection.getErrorStream();
if (errorStream != null) {
reader = new BufferedReader(new InputStreamReader(
errorStream, StandardCharsets.UTF_8));
} else {
throw new Exception("결재 문서 목록 조회 실패: HTTP " + responseCode);
}
}
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
} finally {
if (reader != null) reader.close();
}
System.out.println("Response Code: " + responseCode);
if (responseCode >= 200 && responseCode < 300) {
return response.toString();
} else {
throw new Exception("결재 문서 목록 조회 실패: HTTP " + responseCode + " - " + response.toString());
}
} finally {
connection.disconnect();
}
}
/**
* 결재 문서 상세 조회 - 서버 인증 방식
* @param baseUrl API 서버 기본 URL
* @param empSeq 사원 시퀀스
* @param compSeq 회사 시퀀스
* @param deptSeq 부서 시퀀스
* @param docId 문서 ID
* @return API 응답 결과 (JSON 문자열)
*/
public String getApprovalDocDetail(String baseUrl, String empSeq, String compSeq,
String deptSeq, String docId) throws Exception {
System.setProperty("https.protocols", "TLSv1.2");
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
public boolean verify(String hostname, javax.net.ssl.SSLSession session) { return true; }
});
String urlPath = "/apiproxy/api99u02A04";
String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
String fullUrl = cleanBaseUrl + urlPath;
URL url = new URL(fullUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
connection.setInstanceFollowRedirects(false);
try {
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("callerName", CALLER_NAME);
connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
String transactionId = generateTransactionId();
connection.setRequestProperty("transaction-id", transactionId);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
connection.setRequestProperty("timestamp", timestamp);
connection.setRequestProperty("groupSeq", GROUP_SEQ);
String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath, HASH_KEY);
connection.setRequestProperty("wehago-sign", wehagoSign);
// 요청 본문 작성
StringBuilder body = new StringBuilder();
body.append("{");
body.append("\"header\":{");
body.append("\"pId\":\"\",");
body.append("\"tId\":\"\",");
body.append("\"groupSeq\":\"").append(escapeJson(GROUP_SEQ)).append("\",");
body.append("\"empSeq\":\"").append(escapeJson(empSeq)).append("\"");
body.append("},");
body.append("\"body\":{");
body.append("\"migYn\":\"0\",");
body.append("\"langCode\":\"kr\",");
body.append("\"spMigYn\":\"0\",");
body.append("\"docId\":\"").append(escapeJson(docId)).append("\",");
body.append("\"spDocId\":\"\",");
body.append("\"companyInfo\":{");
body.append("\"compSeq\":\"").append(escapeJson(compSeq)).append("\",");
body.append("\"deptSeq\":\"").append(escapeJson(deptSeq)).append("\"");
body.append("}");
body.append("}");
body.append("}");
String requestBody = body.toString();
System.out.println("=== Amaranth 결재 문서 상세 조회 ===");
System.out.println("URL: " + fullUrl);
System.out.println("docId: " + docId);
System.out.println("Request Body: " + requestBody);
connection.setDoOutput(true);
connection.setDoInput(true);
OutputStreamWriter writer = new OutputStreamWriter(
connection.getOutputStream(), StandardCharsets.UTF_8);
writer.write(requestBody);
writer.flush();
writer.close();
int responseCode = connection.getResponseCode();
BufferedReader reader = null;
StringBuilder response = new StringBuilder();
try {
if (responseCode >= 200 && responseCode < 300) {
reader = new BufferedReader(new InputStreamReader(
connection.getInputStream(), StandardCharsets.UTF_8));
} else {
java.io.InputStream errorStream = connection.getErrorStream();
if (errorStream != null) {
reader = new BufferedReader(new InputStreamReader(
errorStream, StandardCharsets.UTF_8));
} else {
throw new Exception("결재 문서 상세 조회 실패: HTTP " + responseCode);
}
}
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
} finally {
if (reader != null) reader.close();
}
System.out.println("Response Code: " + responseCode);
if (responseCode >= 200 && responseCode < 300) {
return response.toString();
} else {
throw new Exception("결재 문서 상세 조회 실패: HTTP " + responseCode + " - " + response.toString());
}
} finally {
connection.disconnect();
}
}
/**
* loginId 암호화 (AES128 CBC PKCS5Padding)
*/

View File

@@ -47,6 +47,45 @@ public class ApprovalController {
return "/approval/approvalList";
}
/**
* Amaranth10 전자결재 문서 목록 조회 (AJAX 전용)
* approvalList.jsp에서만 사용하는 전용 엔드포인트
* @param request
* @param paramMap
* @return JSON 결과
*/
@RequestMapping("/approval/getAmaranthApprovalDocList.do")
public String getAmaranthApprovalDocList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap)throws Exception{
String jsonResult = approvalService.getAmaranthApprovalDocListJson(request, paramMap);
request.setAttribute("RESULT", jsonResult);
return "/ajax/ajaxResult";
}
/**
* Amaranth10 결재 문서 상세 팝업 페이지
* approvalList.jsp에서 문서번호 클릭 시 호출
* @param request
* @param paramMap docId, deptSeq 필수
* @return 상세 팝업 JSP
*/
@RequestMapping("/approval/amaranthApprovalDetail.do")
public String amaranthApprovalDetail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap)throws Exception{
return "/approval/amaranthApprovalDetail";
}
/**
* Amaranth10 결재 문서 상세 데이터 조회 (AJAX 전용)
* @param request
* @param paramMap docId, deptSeq 필수
* @return JSON 결과
*/
@RequestMapping("/approval/getAmaranthApprovalDocDetail.do")
public String getAmaranthApprovalDocDetail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap)throws Exception{
String jsonResult = approvalService.getAmaranthApprovalDocDetail(request, paramMap);
request.setAttribute("RESULT", jsonResult);
return "/ajax/ajaxResult";
}
/**
* 결재 상신 Form
* @param request

View File

@@ -1228,6 +1228,406 @@ public class ApprovalService {
return count;
}
/**
* Amaranth10 전자결재 문서 목록 조회 - JSON 문자열 반환 (AJAX 전용)
* approvalList.jsp 전용 엔드포인트에서 호출
* @param request HttpServletRequest
* @param paramMap 검색 파라미터
* @return JSON 문자열
*/
public String getAmaranthApprovalDocListJson(HttpServletRequest request, Map paramMap){
Map<String, Object> resultMap = getAmaranthApprovalDocList(request, paramMap);
return buildApprovalDocListJson(resultMap);
}
/**
* Amaranth10 결재 문서 상세 조회
* @param request HttpServletRequest
* @param paramMap docId, deptSeq 필수
* @return API 응답 JSON 원본 (contents HTML 포함)
*/
public String getAmaranthApprovalDocDetail(HttpServletRequest request, Map paramMap){
try {
// 세션에서 사원 정보 가져오기
HttpSession session = request.getSession();
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String empSeq = CommonUtils.checkNull(person.getEmpseq());
String compSeq = "1000";
String docId = CommonUtils.checkNull(paramMap.get("docId"));
String deptSeq = CommonUtils.checkNull(paramMap.get("deptSeq"));
System.out.println("=== Amaranth 결재 문서 상세 조회 ===");
System.out.println("empSeq: " + empSeq + ", docId: " + docId + ", deptSeq: " + deptSeq);
if(empSeq == null || empSeq.isEmpty()){
System.err.println("empSeq가 비어있습니다.");
return "{\"resultCode\":-1,\"resultMsg\":\"empSeq가 비어있습니다.\"}";
}
if(docId == null || docId.isEmpty()){
System.err.println("docId가 비어있습니다.");
return "{\"resultCode\":-1,\"resultMsg\":\"docId가 비어있습니다.\"}";
}
// API 호출
com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient();
String baseUrl = "https://erp.rps-korea.com";
String apiResponse = apiClient.getApprovalDocDetail(baseUrl, empSeq, compSeq, deptSeq, docId);
System.out.println("상세 조회 API 응답 길이: " + (apiResponse != null ? apiResponse.length() : 0) + " bytes");
return apiResponse;
} catch(Exception e){
System.err.println("Amaranth 결재 문서 상세 조회 오류: " + e.getMessage());
e.printStackTrace();
return "{\"resultCode\":-1,\"resultMsg\":\"" + escapeJsonValue(e.getMessage()) + "\"}";
}
}
/**
* 결재 문서 목록 결과를 JSON 문자열로 변환
*/
private String buildApprovalDocListJson(Map<String, Object> resultMap){
StringBuilder json = new StringBuilder();
json.append("{");
// 페이징 정보
json.append("\"totalCount\":").append(resultMap.get("TOTAL_COUNT")).append(",");
json.append("\"maxPage\":").append(resultMap.get("MAX_PAGE_SIZE")).append(",");
json.append("\"nextPage\":").append(resultMap.get("NEXT_PAGE")).append(",");
json.append("\"prevPage\":").append(resultMap.get("PREV_PAGE")).append(",");
// 문서 리스트
json.append("\"list\":[");
ArrayList<HashMap<String, Object>> list = (ArrayList<HashMap<String, Object>>) resultMap.get("LIST");
if(list != null){
for(int i = 0; i < list.size(); i++){
if(i > 0) json.append(",");
HashMap<String, Object> doc = list.get(i);
json.append("{");
json.append("\"RNUM\":\"").append(escapeJsonValue(String.valueOf(doc.get("RNUM")))).append("\",");
json.append("\"DOC_ID\":\"").append(escapeJsonValue(String.valueOf(doc.get("DOC_ID")))).append("\",");
json.append("\"DOC_TITLE\":\"").append(escapeJsonValue(String.valueOf(doc.get("DOC_TITLE")))).append("\",");
json.append("\"FORM_NAME\":\"").append(escapeJsonValue(String.valueOf(doc.get("FORM_NAME")))).append("\",");
json.append("\"EMP_NAME\":\"").append(escapeJsonValue(String.valueOf(doc.get("EMP_NAME")))).append("\",");
json.append("\"DEPT_NAME\":\"").append(escapeJsonValue(String.valueOf(doc.get("DEPT_NAME")))).append("\",");
json.append("\"DOC_STS_NAME\":\"").append(escapeJsonValue(String.valueOf(doc.get("DOC_STS_NAME")))).append("\",");
json.append("\"REP_DT\":\"").append(escapeJsonValue(String.valueOf(doc.get("REP_DT")))).append("\",");
json.append("\"DOC_STS\":\"").append(escapeJsonValue(String.valueOf(doc.get("DOC_STS")))).append("\",");
json.append("\"DEPT_SEQ\":\"").append(escapeJsonValue(String.valueOf(doc.get("DEPT_SEQ")))).append("\"");
json.append("}");
}
}
json.append("]");
json.append("}");
return json.toString();
}
/**
* JSON 값 이스케이프 (서비스 내부용)
*/
private String escapeJsonValue(String value){
if(value == null || "null".equals(value)) return "";
return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}
/**
* Amaranth10 전자결재 문서 목록 조회 (서버 인증 방식)
* @param request HttpServletRequest
* @param paramMap 검색 파라미터 (search_approvalTitle, search_fromDate, search_toDate, page 등)
* @return 결재 문서 목록 및 페이징 정보
*/
public Map<String, Object> getAmaranthApprovalDocList(HttpServletRequest request, Map paramMap){
Map<String, Object> resultMap = new HashMap();
ArrayList<HashMap<String, Object>> resultList = new ArrayList();
try{
// 세션에서 사원 정보 가져오기
HttpSession session = request.getSession();
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String empSeq = CommonUtils.checkNull(person.getEmpseq());
String compSeq = "1000"; // 회사 시퀀스
System.out.println("=== Amaranth 결재 문서 목록 조회 ===");
System.out.println("empSeq: " + empSeq);
if(empSeq == null || empSeq.isEmpty()){
System.err.println("empSeq가 비어있습니다. LoginId 업데이트 배치를 먼저 실행하세요.");
resultMap.put("LIST", resultList);
resultMap.put("TOTAL_COUNT", 0);
return resultMap;
}
// 검색 조건 매핑
String keyWord = CommonUtils.checkNull(paramMap.get("search_approvalTitle"));
String fromDt = CommonUtils.checkNull(paramMap.get("search_fromDate"));
String toDt = CommonUtils.checkNull(paramMap.get("search_toDate"));
String page = CommonUtils.checkNull(paramMap.get("page"), "1");
String pageSize = "20";
// menuId: 기안함(1000400) - 결재 전체 목록 조회용
String menuId = CommonUtils.checkNull(paramMap.get("menuId"), "1000400");
// API 호출
com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient();
String baseUrl = "https://erp.rps-korea.com";
String apiResponse = apiClient.getApprovalDocList(
baseUrl, empSeq, compSeq, menuId, fromDt, toDt, keyWord, page, pageSize);
System.out.println("API 응답 길이: " + (apiResponse != null ? apiResponse.length() : 0) + " bytes");
// JSON 파싱
resultList = parseApprovalDocList(apiResponse);
int totalCnt = extractTotalCnt(apiResponse);
// 번호 매기기 (RNUM)
int startNum = (Integer.parseInt(page) - 1) * Integer.parseInt(pageSize);
for(int i = 0; i < resultList.size(); i++){
resultList.get(i).put("RNUM", String.valueOf(startNum + i + 1));
}
// 페이징 정보 설정
int currentPage = Integer.parseInt(page);
int pageSizeInt = Integer.parseInt(pageSize);
int maxPage = (int) Math.ceil((double) totalCnt / pageSizeInt);
if(maxPage < 1) maxPage = 1;
resultMap.put("LIST", resultList);
resultMap.put("TOTAL_COUNT", totalCnt);
resultMap.put("MAX_PAGE_SIZE", maxPage);
resultMap.put("NEXT_PAGE", Math.min(currentPage + 1, maxPage));
resultMap.put("PREV_PAGE", Math.max(currentPage - 1, 1));
System.out.println("결재 문서 수: " + resultList.size() + ", 전체: " + totalCnt);
}catch(Exception e){
System.err.println("Amaranth 결재 문서 목록 조회 오류: " + e.getMessage());
e.printStackTrace();
resultMap.put("LIST", resultList);
resultMap.put("TOTAL_COUNT", 0);
resultMap.put("MAX_PAGE_SIZE", 1);
resultMap.put("NEXT_PAGE", 1);
resultMap.put("PREV_PAGE", 1);
}
return resultMap;
}
/**
* API 응답에서 eaDocList 배열 파싱
* 응답 구조: {"resultData":{"eaDocList":[{...},{...}],"totalCnt":94}}
*/
private ArrayList<HashMap<String, Object>> parseApprovalDocList(String jsonResponse){
ArrayList<HashMap<String, Object>> list = new ArrayList();
try {
// eaDocList 배열 찾기
int eaDocListIndex = jsonResponse.indexOf("\"eaDocList\"");
if(eaDocListIndex == -1){
System.err.println("eaDocList를 찾을 수 없습니다.");
return list;
}
// eaDocList 다음의 [ 찾기
int arrayStart = jsonResponse.indexOf("[", eaDocListIndex);
if(arrayStart == -1){
System.err.println("eaDocList 배열 시작을 찾을 수 없습니다.");
return list;
}
// 대응하는 ] 찾기
int bracketCount = 0;
int arrayEnd = -1;
for(int i = arrayStart; i < jsonResponse.length(); i++){
char c = jsonResponse.charAt(i);
if(c == '[') bracketCount++;
else if(c == ']') bracketCount--;
if(bracketCount == 0){
arrayEnd = i;
break;
}
}
if(arrayEnd == -1){
System.err.println("eaDocList 배열 끝을 찾을 수 없습니다.");
return list;
}
String arrayStr = jsonResponse.substring(arrayStart + 1, arrayEnd);
// 각 문서 객체 파싱
int searchFrom = 0;
while(searchFrom < arrayStr.length()){
int objStart = arrayStr.indexOf("{", searchFrom);
if(objStart == -1) break;
// 중첩 { } 처리
int braceCount = 0;
int objEnd = -1;
for(int i = objStart; i < arrayStr.length(); i++){
char c = arrayStr.charAt(i);
if(c == '{') braceCount++;
else if(c == '}') braceCount--;
if(braceCount == 0){
objEnd = i;
break;
}
}
if(objEnd == -1) break;
String objStr = arrayStr.substring(objStart, objEnd + 1);
HashMap<String, Object> doc = parseDocObject(objStr);
if(!doc.isEmpty()){
list.add(doc);
}
searchFrom = objEnd + 1;
}
System.out.println("파싱된 문서 수: " + list.size());
} catch (Exception e) {
System.err.println("eaDocList 파싱 오류: " + e.getMessage());
e.printStackTrace();
}
return list;
}
/**
* 개별 문서 JSON 객체 파싱
*/
private HashMap<String, Object> parseDocObject(String objStr){
HashMap<String, Object> doc = new HashMap();
try {
doc.put("DOC_ID", extractJsonStringValue(objStr, "docId"));
doc.put("DOC_TITLE", extractJsonStringValue(objStr, "docTitle"));
doc.put("FORM_NAME", extractJsonStringValue(objStr, "formName"));
doc.put("EMP_NAME", extractJsonStringValue(objStr, "empName"));
doc.put("DEPT_NAME", extractJsonStringValue(objStr, "deptName"));
doc.put("DOC_STS", extractJsonStringValue(objStr, "docSts"));
doc.put("DOC_STS_NAME", extractJsonStringValue(objStr, "docStsName"));
doc.put("POSITION_NAME", extractJsonStringValue(objStr, "positionName"));
doc.put("DUTY_NAME", extractJsonStringValue(objStr, "dutyName"));
doc.put("LINE_NAME", extractJsonStringValue(objStr, "lineName"));
doc.put("ATTACH_CNT", extractJsonStringValue(objStr, "attachCnt"));
doc.put("COMMENT_CNT", extractJsonStringValue(objStr, "commentCnt"));
doc.put("EMERGENCY_FLAG", extractJsonStringValue(objStr, "emergencyFlag"));
doc.put("DELAY_FLAG", extractJsonStringValue(objStr, "delayFlag"));
doc.put("READ_YN", extractJsonStringValue(objStr, "readYN"));
doc.put("APP_YN", extractJsonStringValue(objStr, "appYN"));
doc.put("COMP_SEQ", extractJsonStringValue(objStr, "compSeq"));
doc.put("EMP_SEQ", extractJsonStringValue(objStr, "empSeq"));
doc.put("DEPT_SEQ", extractJsonStringValue(objStr, "deptSeq"));
// 상신일(repDt) 포맷팅: YYYYMMDDHHMISS → YYYY-MM-DD
String repDt = extractJsonStringValue(objStr, "repDt");
if(repDt != null && repDt.length() >= 8){
doc.put("REP_DT", repDt.substring(0, 4) + "-" + repDt.substring(4, 6) + "-" + repDt.substring(6, 8));
} else {
doc.put("REP_DT", repDt);
}
// 생성일(createdDt) 포맷팅
String createdDt = extractJsonStringValue(objStr, "createdDt");
if(createdDt != null && createdDt.length() >= 8){
doc.put("CREATED_DT", createdDt.substring(0, 4) + "-" + createdDt.substring(4, 6) + "-" + createdDt.substring(6, 8));
} else {
doc.put("CREATED_DT", createdDt);
}
} catch (Exception e) {
System.err.println("문서 객체 파싱 오류: " + e.getMessage());
}
return doc;
}
/**
* JSON 문자열에서 특정 키의 문자열 값 추출
*/
private String extractJsonStringValue(String json, String key){
String searchKey = "\"" + key + "\"";
int keyIndex = json.indexOf(searchKey);
if(keyIndex == -1) return "";
int colonIndex = json.indexOf(":", keyIndex + searchKey.length());
if(colonIndex == -1) return "";
// 콜론 뒤의 공백 건너뛰기
int valueStart = colonIndex + 1;
while(valueStart < json.length() && json.charAt(valueStart) == ' '){
valueStart++;
}
if(valueStart >= json.length()) return "";
// null 값 체크
if(json.substring(valueStart).startsWith("null")){
return "";
}
// 숫자 값 체크 (따옴표 없는 경우)
char firstChar = json.charAt(valueStart);
if(firstChar != '"'){
// 숫자나 boolean 값
int valueEnd = valueStart;
while(valueEnd < json.length()){
char c = json.charAt(valueEnd);
if(c == ',' || c == '}' || c == ']') break;
valueEnd++;
}
return json.substring(valueStart, valueEnd).trim();
}
// 문자열 값 (따옴표 있는 경우)
int quoteStart = valueStart; // 이미 '"' 위치
int quoteEnd = json.indexOf("\"", quoteStart + 1);
if(quoteEnd == -1) return "";
return json.substring(quoteStart + 1, quoteEnd);
}
/**
* API 응답에서 totalCnt 추출
*/
private int extractTotalCnt(String jsonResponse){
try {
String searchKey = "\"totalCnt\"";
int keyIndex = jsonResponse.indexOf(searchKey);
if(keyIndex == -1) return 0;
int colonIndex = jsonResponse.indexOf(":", keyIndex + searchKey.length());
if(colonIndex == -1) return 0;
int valueStart = colonIndex + 1;
while(valueStart < jsonResponse.length() && jsonResponse.charAt(valueStart) == ' '){
valueStart++;
}
int valueEnd = valueStart;
while(valueEnd < jsonResponse.length()){
char c = jsonResponse.charAt(valueEnd);
if(c == ',' || c == '}' || c == ']') break;
valueEnd++;
}
String cntStr = jsonResponse.substring(valueStart, valueEnd).trim();
return Integer.parseInt(cntStr);
} catch (Exception e) {
System.err.println("totalCnt 추출 오류: " + e.getMessage());
return 0;
}
}
public ArrayList getApprovalLine(HttpServletRequest request, Map paramMap){
ArrayList<HashMap<String,Object>> resultList = new ArrayList();
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();