결재시 첨부파일 업로드 반영영
This commit is contained in:
@@ -834,6 +834,18 @@ public class AmaranthApprovalApiClient {
|
||||
String formId, String approKey, String subjectStr,
|
||||
String mod, String compSeq, String deptSeq,
|
||||
String loginId) throws Exception {
|
||||
return getSsoUrl(baseUrl, empSeq, outProcessCode, formId, approKey,
|
||||
subjectStr, mod, compSeq, deptSeq, loginId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth10 전자결재 SSO URL 생성 (첨부파일 포함)
|
||||
* @param fileList URL 인코딩된 파일 리스트 JSON (null이면 첨부파일 없음)
|
||||
*/
|
||||
public String getSsoUrl(String baseUrl, String empSeq, String outProcessCode,
|
||||
String formId, String approKey, String subjectStr,
|
||||
String mod, String compSeq, String deptSeq,
|
||||
String loginId, String fileList) throws Exception {
|
||||
|
||||
System.out.println("=== Amaranth SSO URL 생성 시작 ===");
|
||||
System.out.println("empSeq: " + empSeq + ", outProcessCode: " + outProcessCode + ", formId: " + formId);
|
||||
@@ -887,7 +899,7 @@ public class AmaranthApprovalApiClient {
|
||||
|
||||
// 요청 본문 구성
|
||||
String requestBody = buildSsoRequestBody(empSeqEnc, outProcessCode, formId,
|
||||
approKey, subjectStr, mod, compSeq, deptSeq, loginId);
|
||||
approKey, subjectStr, mod, compSeq, deptSeq, loginId, fileList);
|
||||
|
||||
System.out.println("[2단계] SSO API 호출 - URL: " + fullUrl);
|
||||
System.out.println("[2단계] Request Body: " + requestBody);
|
||||
@@ -943,7 +955,7 @@ public class AmaranthApprovalApiClient {
|
||||
String formId, String approKey,
|
||||
String subjectStr, String mod,
|
||||
String compSeq, String deptSeq,
|
||||
String loginId) {
|
||||
String loginId, String fileList) {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{");
|
||||
json.append("\"header\":{},");
|
||||
@@ -986,6 +998,11 @@ public class AmaranthApprovalApiClient {
|
||||
json.append(",\"subjectStr\":\"").append(escapeJson(subjectStr)).append("\"");
|
||||
}
|
||||
|
||||
// 첨부파일 (URL 인코딩된 JSON 문자열)
|
||||
if (fileList != null && !fileList.isEmpty()) {
|
||||
json.append(",\"fileList\":\"").append(escapeJson(fileList)).append("\"");
|
||||
}
|
||||
|
||||
// 본문 인코딩 (UTF-8)
|
||||
json.append(",\"contentsEnc\":\"U\"");
|
||||
|
||||
@@ -995,6 +1012,159 @@ public class AmaranthApprovalApiClient {
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 원챔버 첨부파일 업로드 (전자결재용)
|
||||
* API: /apiproxy/authUser/api99u04A12
|
||||
* moduleGbn="EAP" (전자결재 영리)
|
||||
* 단일 파일만 업로드 가능 → 다중 파일은 반복 호출
|
||||
*
|
||||
* @return API 응답 JSON 전체
|
||||
*/
|
||||
public String uploadFileToOneChamber(String baseUrl, String authToken, String userHashKey,
|
||||
String empSeq, java.io.File file, String originalFileName) throws Exception {
|
||||
System.setProperty("https.protocols", "TLSv1.2");
|
||||
setupSslTrustAll();
|
||||
|
||||
String urlPath = "/apiproxy/authUser/api99u04A12";
|
||||
String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
String fullUrl = cleanBaseUrl + urlPath;
|
||||
|
||||
String boundary = "----Boundary" + System.currentTimeMillis();
|
||||
String CRLF = "\r\n";
|
||||
|
||||
URL url = new URL(fullUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(60000);
|
||||
connection.setReadTimeout(60000);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
|
||||
try {
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setRequestProperty("callerName", CALLER_NAME);
|
||||
connection.setRequestProperty("Authorization", "Bearer " + authToken);
|
||||
|
||||
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(authToken, transactionId, timestamp, urlPath, userHashKey);
|
||||
connection.setRequestProperty("wehago-sign", wehagoSign);
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
|
||||
java.io.DataOutputStream dos = new java.io.DataOutputStream(connection.getOutputStream());
|
||||
|
||||
// callerName
|
||||
dos.writeBytes("--" + boundary + CRLF);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"callerName\"" + CRLF + CRLF);
|
||||
dos.writeBytes(CALLER_NAME + CRLF);
|
||||
|
||||
// groupSeq
|
||||
dos.writeBytes("--" + boundary + CRLF);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"groupSeq\"" + CRLF + CRLF);
|
||||
dos.writeBytes(GROUP_SEQ + CRLF);
|
||||
|
||||
// empSeq
|
||||
dos.writeBytes("--" + boundary + CRLF);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"empSeq\"" + CRLF + CRLF);
|
||||
dos.writeBytes(empSeq + CRLF);
|
||||
|
||||
// moduleGbn = EAP (전자결재 영리)
|
||||
dos.writeBytes("--" + boundary + CRLF);
|
||||
dos.writeBytes("Content-Disposition: form-data; name=\"moduleGbn\"" + CRLF + CRLF);
|
||||
dos.writeBytes("EAP" + CRLF);
|
||||
|
||||
// file[] - 파일명을 UTF-8 바이트로 전송
|
||||
dos.writeBytes("--" + boundary + CRLF);
|
||||
byte[] contentDisp = ("Content-Disposition: form-data; name=\"file[]\"; filename=\""
|
||||
+ originalFileName + "\"" + CRLF).getBytes(StandardCharsets.UTF_8);
|
||||
dos.write(contentDisp);
|
||||
dos.writeBytes("Content-Type: application/octet-stream" + CRLF + CRLF);
|
||||
|
||||
java.io.FileInputStream fis = new java.io.FileInputStream(file);
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
dos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
fis.close();
|
||||
|
||||
dos.writeBytes(CRLF);
|
||||
dos.writeBytes("--" + boundary + "--" + CRLF);
|
||||
dos.flush();
|
||||
dos.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("[원챔버 업로드] 파일: " + originalFileName + ", Response Code: " + responseCode);
|
||||
System.out.println("[원챔버 업로드] Response: " + response.toString());
|
||||
|
||||
return response.toString();
|
||||
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 원챔버 업로드 응답에서 list 배열의 첫 번째 항목 JSON 추출
|
||||
*/
|
||||
public String extractListItemFromUploadResponse(String jsonResponse) {
|
||||
int listIdx = jsonResponse.indexOf("\"list\":[");
|
||||
if (listIdx == -1) return null;
|
||||
|
||||
int arrStart = jsonResponse.indexOf("[", listIdx);
|
||||
int objStart = jsonResponse.indexOf("{", arrStart);
|
||||
if (objStart == -1) return null;
|
||||
|
||||
// 중첩 중괄호를 고려하여 닫는 중괄호 찾기
|
||||
int depth = 0;
|
||||
int objEnd = -1;
|
||||
for (int i = objStart; i < jsonResponse.length(); i++) {
|
||||
char c = jsonResponse.charAt(i);
|
||||
if (c == '{') depth++;
|
||||
else if (c == '}') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
objEnd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (objEnd == -1) return null;
|
||||
return jsonResponse.substring(objStart, objEnd + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL 인증서 검증 우회 설정 (개발 환경용)
|
||||
*/
|
||||
|
||||
@@ -1817,11 +1817,22 @@ public class ApprovalService {
|
||||
System.out.println("outProcessCode: " + outProcessCode + ", formId: " + formId);
|
||||
System.out.println("approKey: " + approKey);
|
||||
|
||||
// API 호출 (loginId 파라미터 추가)
|
||||
// API 클라이언트 생성
|
||||
com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient();
|
||||
|
||||
// 첨부파일 처리: ATTACH_FILE_INFO에서 품의서 파일 조회 → 원챔버 업로드
|
||||
String fileListEncoded = null;
|
||||
try {
|
||||
fileListEncoded = uploadProposalFilesToOneChamber(apiClient, empSeq, targetObjId);
|
||||
} catch(Exception fileEx) {
|
||||
System.err.println("[첨부파일] 원챔버 업로드 중 오류 (결재는 계속 진행): " + fileEx.getMessage());
|
||||
fileEx.printStackTrace();
|
||||
}
|
||||
|
||||
// SSO URL 생성 API 호출
|
||||
String apiResponse = apiClient.getSsoUrl(
|
||||
AMARANTH_BASE_URL, empSeq, outProcessCode, formId,
|
||||
approKey, approvalTitle, "W", compSeq, deptSeq, loginId
|
||||
approKey, approvalTitle, "W", compSeq, deptSeq, loginId, fileListEncoded
|
||||
);
|
||||
|
||||
System.out.println("SSO API 응답: " + apiResponse);
|
||||
@@ -1867,6 +1878,132 @@ public class ApprovalService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 품의서 첨부파일을 원챔버에 업로드하고 fileList (URL 인코딩) 반환
|
||||
* @return URL 인코딩된 fileList JSON 문자열 (파일 없으면 null)
|
||||
*/
|
||||
private String uploadProposalFilesToOneChamber(
|
||||
com.pms.api.AmaranthApprovalApiClient apiClient,
|
||||
String empSeq, String targetObjId) throws Exception {
|
||||
|
||||
if(targetObjId == null || targetObjId.isEmpty()) return null;
|
||||
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(true);
|
||||
try {
|
||||
// 1. 인증 토큰 발급 (파일 업로드는 사용자 인증 필요)
|
||||
Map<String, String> authResult = apiClient.getAuthToken(AMARANTH_BASE_URL, empSeq);
|
||||
if(!"true".equals(authResult.get("success"))){
|
||||
throw new Exception("파일 업로드용 인증 토큰 발급 실패: " + authResult.get("resultMsg"));
|
||||
}
|
||||
String authToken = authResult.get("authToken");
|
||||
String userHashKey = authResult.get("hashKey");
|
||||
|
||||
StringBuilder listItems = new StringBuilder();
|
||||
int uploadCount = 0;
|
||||
|
||||
// 2. 품의서 본문을 JSP와 동일한 레이아웃의 HTML 파일로 생성하여 업로드
|
||||
try {
|
||||
Map<String, Object> proposalParam = new HashMap<String, Object>();
|
||||
proposalParam.put("PROPOSAL_OBJID", targetObjId);
|
||||
Map<String, Object> proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam);
|
||||
|
||||
if(proposalInfo != null){
|
||||
proposalInfo = CommonUtils.toUpperCaseMapKey(proposalInfo);
|
||||
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
|
||||
|
||||
// 품목 리스트 조회
|
||||
Map<String, Object> partParam = new HashMap<String, Object>();
|
||||
partParam.put("PROPOSAL_OBJID", targetObjId);
|
||||
List<Map> partList = sqlSession.selectList("salesMng.getProposalPartList", partParam);
|
||||
|
||||
String fullHtml = buildProposalFormFileHtml(proposalInfo, partList);
|
||||
|
||||
String tempFileName = "구매품의서_" + proposalNo + ".html";
|
||||
java.io.File tempFile = java.io.File.createTempFile("proposal_", ".html");
|
||||
java.io.OutputStreamWriter writer = new java.io.OutputStreamWriter(
|
||||
new java.io.FileOutputStream(tempFile), "UTF-8");
|
||||
writer.write(fullHtml);
|
||||
writer.close();
|
||||
|
||||
System.out.println("[첨부파일] 품의서 HTML 생성: " + tempFileName + " (" + tempFile.length() + " bytes)");
|
||||
|
||||
String uploadResponse = apiClient.uploadFileToOneChamber(
|
||||
AMARANTH_BASE_URL, authToken, userHashKey, empSeq, tempFile, tempFileName
|
||||
);
|
||||
|
||||
String listItem = apiClient.extractListItemFromUploadResponse(uploadResponse);
|
||||
if(listItem != null){
|
||||
listItems.append(listItem);
|
||||
uploadCount++;
|
||||
System.out.println("[첨부파일] 품의서 HTML 업로드 성공");
|
||||
}
|
||||
|
||||
tempFile.delete();
|
||||
}
|
||||
} catch(Exception htmlEx){
|
||||
System.err.println("[첨부파일] 품의서 HTML 생성/업로드 오류: " + htmlEx.getMessage());
|
||||
}
|
||||
|
||||
// 3. ATTACH_FILE_INFO에서 기존 첨부파일도 업로드
|
||||
Map<String, Object> fileParam = new HashMap<String, Object>();
|
||||
fileParam.put("targetObjId", targetObjId);
|
||||
List<Map<String, Object>> fileList = sqlSession.selectList("common.getFileList", fileParam);
|
||||
|
||||
if(fileList != null && !fileList.isEmpty()){
|
||||
System.out.println("[첨부파일] 품의서(" + targetObjId + ") 기존 첨부파일 " + fileList.size() + "건 발견");
|
||||
|
||||
for(Map<String, Object> fileInfo : fileList){
|
||||
String savedFileName = CommonUtils.checkNull(fileInfo.get("saved_file_name"));
|
||||
String realFileName = CommonUtils.checkNull(fileInfo.get("real_file_name"));
|
||||
String filePath = CommonUtils.checkNull(fileInfo.get("file_path"));
|
||||
String fileExt = CommonUtils.checkNull(fileInfo.get("file_ext"));
|
||||
|
||||
if(savedFileName.isEmpty()) continue;
|
||||
|
||||
java.io.File physicalFile = new java.io.File(filePath, savedFileName);
|
||||
if(!physicalFile.exists()){
|
||||
System.err.println("[첨부파일] 물리 파일 없음: " + physicalFile.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
|
||||
String originalName = realFileName;
|
||||
if(!originalName.contains(".") && !fileExt.isEmpty()){
|
||||
originalName = realFileName + "." + fileExt;
|
||||
}
|
||||
|
||||
System.out.println("[첨부파일] 업로드 시작: " + originalName + " (" + physicalFile.length() + " bytes)");
|
||||
|
||||
String uploadResponse = apiClient.uploadFileToOneChamber(
|
||||
AMARANTH_BASE_URL, authToken, userHashKey, empSeq, physicalFile, originalName
|
||||
);
|
||||
|
||||
String listItem = apiClient.extractListItemFromUploadResponse(uploadResponse);
|
||||
if(listItem != null){
|
||||
if(uploadCount > 0) listItems.append(",");
|
||||
listItems.append(listItem);
|
||||
uploadCount++;
|
||||
System.out.println("[첨부파일] 업로드 성공: " + originalName);
|
||||
} else {
|
||||
System.err.println("[첨부파일] 업로드 응답에서 list 항목 추출 실패: " + originalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(uploadCount == 0) return null;
|
||||
|
||||
// 4. JSON 배열 구성 → URL 인코딩
|
||||
String fileListJson = "[" + listItems.toString() + "]";
|
||||
String fileListEncoded = java.net.URLEncoder.encode(fileListJson, "UTF-8");
|
||||
|
||||
System.out.println("[첨부파일] 총 " + uploadCount + "건 업로드 완료");
|
||||
|
||||
return fileListEncoded;
|
||||
|
||||
} finally {
|
||||
if(sqlSession != null) sqlSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth10 전자결재 콜백 처리
|
||||
* 결재 이벤트(상신/진행/종결/반려/삭제 등) 발생 시 Amaranth10에서 호출
|
||||
@@ -2172,6 +2309,185 @@ public class ApprovalService {
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 품의서 JSP(proposalFormPopUp.jsp)와 동일한 레이아웃의 HTML 파일 생성
|
||||
* 결재 첨부파일용 (입력 필드 없이 값만 표시)
|
||||
*/
|
||||
private String buildProposalFormFileHtml(Map proposalInfo, List<Map> partList){
|
||||
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
|
||||
String regdate = CommonUtils.checkNull(proposalInfo.get("REGDATE_TITLE"));
|
||||
String writerName = CommonUtils.checkNull(proposalInfo.get("WRITER_NAME"));
|
||||
String recipientRef = CommonUtils.checkNull(proposalInfo.get("RECIPIENT_REF"));
|
||||
String executor = CommonUtils.checkNull(proposalInfo.get("EXECUTOR"));
|
||||
String executionDate = CommonUtils.checkNull(proposalInfo.get("EXECUTION_DATE_TITLE"));
|
||||
String title = CommonUtils.checkNull(proposalInfo.get("TITLE"));
|
||||
String remark = CommonUtils.checkNull(proposalInfo.get("REMARK"));
|
||||
|
||||
// 부서명/기안자명 분리 (WRITER_NAME = "경영지원팀 관리자" 형식)
|
||||
String deptName = "-";
|
||||
String writerOnly = "-";
|
||||
if(!writerName.isEmpty()){
|
||||
if(writerName.contains(" ")){
|
||||
deptName = writerName.substring(0, writerName.indexOf(" "));
|
||||
writerOnly = writerName.substring(writerName.lastIndexOf(" ") + 1);
|
||||
} else {
|
||||
deptName = writerName;
|
||||
writerOnly = writerName;
|
||||
}
|
||||
}
|
||||
|
||||
// 합계 계산
|
||||
long totalAmount = 0;
|
||||
if(partList != null){
|
||||
for(Map part : partList){
|
||||
try {
|
||||
Object tp = part.get("TOTAL_PRICE");
|
||||
if(tp != null) totalAmount += Long.parseLong(tp.toString());
|
||||
} catch(Exception ignore){}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder h = new StringBuilder();
|
||||
h.append("<!DOCTYPE html><html><head><meta charset='UTF-8'/>");
|
||||
h.append("<title>구매품의서 - ").append(escapeHtml(proposalNo)).append("</title>");
|
||||
h.append("<style>");
|
||||
h.append("body{font-family:'Malgun Gothic','맑은 고딕',sans-serif;font-size:12px;margin:0;padding:10px;background:#fff;}");
|
||||
h.append(".proposal-container{width:100%;max-width:800px;margin:0 auto;}");
|
||||
h.append(".proposal-title{text-align:center;font-size:28px;font-weight:bold;letter-spacing:20px;padding:15px 0;border-bottom:2px solid #000;}");
|
||||
h.append(".header-section{border-bottom:1px solid #000;border-left:2px solid #000;border-right:2px solid #000;}");
|
||||
h.append(".sub-info-section{border-bottom:1px solid #000;border-left:2px solid #000;border-right:2px solid #000;}");
|
||||
h.append(".info-table{width:100%;border-collapse:collapse;}");
|
||||
h.append(".info-table td{border:1px solid #999;padding:2px 4px;height:26px;vertical-align:middle;}");
|
||||
h.append(".info-table .label{background-color:#f5f5f5;font-weight:bold;width:90px;min-width:90px;text-align:center;}");
|
||||
h.append(".revision-info{text-align:right;font-size:11px;color:#ff0000;padding:5px 10px;border-bottom:1px solid #000;}");
|
||||
h.append(".middle-section{border-bottom:1px solid #000;border-left:2px solid #000;border-right:2px solid #000;}");
|
||||
h.append(".middle-table{width:100%;border-collapse:collapse;}");
|
||||
h.append(".middle-table td,.middle-table th{border:1px solid #999;padding:5px 8px;text-align:center;vertical-align:middle;}");
|
||||
h.append(".middle-table .header-cell{background-color:#ebf1de;font-weight:bold;}");
|
||||
h.append(".middle-table .total-header{background-color:#dce6f1;}");
|
||||
h.append(".middle-table .total-value{background-color:#ffffcc;font-weight:bold;text-align:right;font-size:14px;}");
|
||||
h.append(".item-section{border-bottom:1px solid #000;border-left:2px solid #000;border-right:2px solid #000;}");
|
||||
h.append(".item-table{width:100%;border-collapse:collapse;}");
|
||||
h.append(".item-table th,.item-table td{border:1px solid #999;padding:1px 2px;text-align:center;vertical-align:middle;}");
|
||||
h.append(".item-table th{background-color:#f5f5f5;font-weight:bold;height:30px;}");
|
||||
h.append(".item-table td{height:35px;}");
|
||||
h.append(".text-left{text-align:left;padding-left:4px;}");
|
||||
h.append(".text-right{text-align:right;padding-right:4px;}");
|
||||
h.append(".reference-table{width:100%;border-collapse:collapse;}");
|
||||
h.append(".reference-table td{border:1px solid #999;padding:8px;}");
|
||||
h.append(".reference-table .label{background-color:#f5f5f5;font-weight:bold;width:80px;text-align:center;}");
|
||||
h.append("</style></head><body>");
|
||||
|
||||
h.append("<div class='proposal-container'>");
|
||||
|
||||
// 제목
|
||||
h.append("<div class='proposal-title'>구 매 품 의 서</div>");
|
||||
|
||||
// 상단 기본정보
|
||||
h.append("<div class='header-section'>");
|
||||
h.append("<table class='info-table'>");
|
||||
h.append("<tr><td class='label'>품 의 번 호</td><td colspan='3'>").append(escapeHtml(proposalNo)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>작 성 일 자</td><td colspan='3'>").append(escapeHtml(regdate)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>기 안 부 서</td><td colspan='3'>").append(escapeHtml(deptName)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>기 안 자</td><td colspan='3'>").append(escapeHtml(writerOnly)).append("</td></tr>");
|
||||
h.append("</table></div>");
|
||||
|
||||
// 하단 기본정보 (수신및참조, 시행자, 시행일자, 제목)
|
||||
h.append("<div class='sub-info-section'>");
|
||||
h.append("<table class='info-table'>");
|
||||
h.append("<tr><td class='label'>수신및참조</td><td colspan='3'>").append(escapeHtml(recipientRef)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>시 행 자</td><td colspan='3'>").append(escapeHtml(executor)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>시행일자</td><td colspan='3'>").append(escapeHtml(executionDate)).append("</td></tr>");
|
||||
h.append("<tr><td class='label'>제 목</td><td colspan='3'>").append(escapeHtml(title)).append("</td></tr>");
|
||||
h.append("</table></div>");
|
||||
|
||||
// 개정 정보
|
||||
h.append("<div class='revision-info'>[구매품의서 개정 : 22.05.17]</div>");
|
||||
|
||||
// 중간 정보 섹션
|
||||
h.append("<div class='middle-section'><table class='middle-table'>");
|
||||
h.append("<tr>");
|
||||
h.append("<th rowspan='2' class='header-cell' style='width:50px;'>구<br/>분</th>");
|
||||
h.append("<th class='header-cell'>부 서</th><th class='header-cell'>소속팀</th>");
|
||||
h.append("<th class='header-cell'>날 짜</th><th>").append(escapeHtml(regdate)).append("</th>");
|
||||
h.append("<th class='total-header'>총 합 계</th>");
|
||||
h.append("</tr>");
|
||||
h.append("<tr style='height:50px;'>");
|
||||
h.append("<td>").append(escapeHtml(deptName)).append("</td>");
|
||||
h.append("<td>").append(escapeHtml(deptName)).append("</td>");
|
||||
h.append("<td class='header-cell'>기 안 자</td>");
|
||||
h.append("<td>").append(escapeHtml(writerOnly)).append("</td>");
|
||||
h.append("<td class='total-value'>").append(formatNumber(totalAmount)).append("</td>");
|
||||
h.append("</tr></table></div>");
|
||||
|
||||
// 품목 테이블
|
||||
h.append("<div class='item-section'><table class='item-table'><thead><tr>");
|
||||
h.append("<th style='width:40px;'>No.</th>");
|
||||
h.append("<th style='width:150px;'>목 적</th>");
|
||||
h.append("<th style='width:250px;'>품명 / 규격</th>");
|
||||
h.append("<th style='width:100px;'>납 기 일</th>");
|
||||
h.append("<th style='width:100px;'>업 체 명</th>");
|
||||
h.append("<th style='width:60px;'>수량</th>");
|
||||
h.append("<th style='width:50px;'>단위</th>");
|
||||
h.append("<th style='width:80px;'>단가</th>");
|
||||
h.append("<th style='width:100px;'>합 계</th>");
|
||||
h.append("</tr></thead><tbody>");
|
||||
|
||||
if(partList != null && !partList.isEmpty()){
|
||||
int idx = 1;
|
||||
for(Map part : partList){
|
||||
String partName = CommonUtils.checkNull(part.get("PART_NAME"));
|
||||
String spec = CommonUtils.checkNull(part.get("SPEC"));
|
||||
String partRemark = CommonUtils.checkNull(part.get("REMARK"));
|
||||
String deliveryDate = CommonUtils.checkNull(part.get("DELIVERY_REQUEST_DATE_TITLE"));
|
||||
String vendorName = CommonUtils.checkNull(part.get("VENDOR_NAME"));
|
||||
String qty = CommonUtils.checkNull(part.get("QTY"));
|
||||
String unit = CommonUtils.checkNull(part.get("UNIT_NAME"));
|
||||
String unitPrice = CommonUtils.checkNull(part.get("UNIT_PRICE"));
|
||||
String totalPrice = CommonUtils.checkNull(part.get("TOTAL_PRICE"));
|
||||
|
||||
h.append("<tr>");
|
||||
h.append("<td>").append(idx++).append("</td>");
|
||||
h.append("<td class='text-left'>").append(escapeHtml(partRemark)).append("</td>");
|
||||
h.append("<td class='text-left'>").append(escapeHtml(partName));
|
||||
if(!spec.isEmpty()) h.append("<br/>(").append(escapeHtml(spec)).append(")");
|
||||
h.append("</td>");
|
||||
h.append("<td>").append(escapeHtml(deliveryDate)).append("</td>");
|
||||
h.append("<td class='text-left'>").append(escapeHtml(vendorName)).append("</td>");
|
||||
h.append("<td class='text-right'>").append(formatNumber(qty)).append("</td>");
|
||||
h.append("<td>").append(escapeHtml(unit)).append("</td>");
|
||||
h.append("<td class='text-right'>").append(formatNumber(unitPrice)).append("</td>");
|
||||
h.append("<td class='text-right'>").append(formatNumber(totalPrice)).append("</td>");
|
||||
h.append("</tr>");
|
||||
}
|
||||
} else {
|
||||
h.append("<tr><td colspan='9' style='height:100px;'>등록된 품목이 없습니다.</td></tr>");
|
||||
}
|
||||
|
||||
h.append("</tbody></table></div>");
|
||||
|
||||
// 참조문서
|
||||
h.append("<table class='reference-table'>");
|
||||
h.append("<tr style='height:50px;'><td class='label' style='width:150px;'>참 조 문 서</td>");
|
||||
h.append("<td>선택된 문서가 없습니다.</td></tr></table>");
|
||||
|
||||
h.append("</div></body></html>");
|
||||
return h.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 숫자 천단위 콤마 포맷
|
||||
*/
|
||||
private String formatNumber(Object value){
|
||||
if(value == null) return "0";
|
||||
try {
|
||||
long num = Long.parseLong(value.toString().replaceAll("[^0-9\\-]", ""));
|
||||
return String.format("%,d", num);
|
||||
} catch(Exception e){
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 이스케이프 (XSS 방지)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user