영업관리_견적관리 결재상신 아마란스 연동(추가 테스트 필요)

This commit is contained in:
2026-02-25 12:38:36 +09:00
parent 5322557d54
commit 6e0a4d155d
6 changed files with 536 additions and 104 deletions

View File

@@ -1837,10 +1837,14 @@ public class ApprovalService {
// API 클라이언트 생성
com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient();
// 첨부파일 처리: ATTACH_FILE_INFO에서 품의서 파일 조회 → 원챔버 업로드
// 첨부파일 처리: targetType별 분기하여 원챔버 업로드
String fileListEncoded = null;
try {
fileListEncoded = uploadProposalFilesToOneChamber(apiClient, empSeq, targetObjId);
if("CONTRACT_ESTIMATE".equals(targetType)) {
fileListEncoded = uploadEstimateFilesToOneChamber(apiClient, empSeq, targetObjId);
} else {
fileListEncoded = uploadProposalFilesToOneChamber(apiClient, empSeq, targetObjId);
}
} catch(Exception fileEx) {
System.err.println("[첨부파일] 원챔버 업로드 중 오류 (결재는 계속 진행): " + fileEx.getMessage());
fileEx.printStackTrace();
@@ -2022,6 +2026,324 @@ public class ApprovalService {
}
}
/**
* 견적서 첨부파일을 원챔버에 업로드하고 fileList (URL 인코딩) 반환
* @return URL 인코딩된 fileList JSON 문자열 (파일 없으면 null)
*/
private String uploadEstimateFilesToOneChamber(
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 {
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;
// 1. 견적서 본문을 HTML 파일로 생성하여 업로드
try {
Map<String, Object> estParam = new HashMap<String, Object>();
estParam.put("targetObjId", targetObjId);
Map<String, Object> estimateInfo = sqlSession.selectOne("approval.getEstimateInfoForApproval", estParam);
if(estimateInfo != null){
estimateInfo = CommonUtils.toUpperCaseMapKey(estimateInfo);
String estimateNo = CommonUtils.checkNull(estimateInfo.get("ESTIMATE_NO"));
Map<String, Object> itemParam = new HashMap<String, Object>();
itemParam.put("targetObjId", targetObjId);
List<Map> itemList = sqlSession.selectList("approval.getEstimateItemsForApproval", itemParam);
String fullHtml = buildEstimateFormFileHtml(estimateInfo, itemList);
String tempFileName = "견적서_" + estimateNo + ".html";
java.io.File tempFile = java.io.File.createTempFile("estimate_", ".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());
}
// 2. 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"));
if(savedFileName.isEmpty()) continue;
String fullPath = filePath + "/" + savedFileName;
java.io.File physicalFile = new java.io.File(fullPath);
if(!physicalFile.exists()){
System.err.println("[첨부파일] 파일 미존재: " + fullPath);
continue;
}
String originalName = realFileName.isEmpty() ? savedFileName : realFileName;
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);
}
}
}
if(uploadCount == 0) return null;
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();
}
}
/**
* 견적서 데이터를 HTML 파일로 구성 (결재 첨부파일용)
*/
private String buildEstimateFormFileHtml(Map estimateInfo, List<Map> itemList){
String estimateNo = CommonUtils.checkNull(estimateInfo.get("ESTIMATE_NO"));
String contractNo = CommonUtils.checkNull(estimateInfo.get("CONTRACT_NO"));
String customerName = CommonUtils.checkNull(estimateInfo.get("CUSTOMER_NAME"));
String writerName = CommonUtils.checkNull(estimateInfo.get("WRITER_NAME"));
String regdate = CommonUtils.checkNull(estimateInfo.get("REGDATE"));
String totalAmount = CommonUtils.checkNull(estimateInfo.get("TOTAL_AMOUNT"));
String totalAmountKrw = CommonUtils.checkNull(estimateInfo.get("TOTAL_AMOUNT_KRW"));
String currencyName = CommonUtils.checkNull(estimateInfo.get("CURRENCY_NAME"));
String exchangeRate = CommonUtils.checkNull(estimateInfo.get("EXCHANGE_RATE"));
String recipient = CommonUtils.checkNull(estimateInfo.get("RECIPIENT"));
String contactPerson = CommonUtils.checkNull(estimateInfo.get("CONTACT_PERSON"));
String modelName = CommonUtils.checkNull(estimateInfo.get("MODEL_NAME"));
String validityPeriod = CommonUtils.checkNull(estimateInfo.get("VALIDITY_PERIOD"));
String noteRemarks = CommonUtils.checkNull(estimateInfo.get("NOTE_REMARKS"));
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html><html><head><meta charset='UTF-8'>");
html.append("<title>견적서 - " + estimateNo + "</title>");
html.append("<style>body{font-family:'맑은 고딕',sans-serif;font-size:10pt;margin:20px;}");
html.append("table{border-collapse:collapse;width:100%;}");
html.append("th,td{border:1px solid #333;padding:6px 8px;font-size:9pt;}");
html.append("th{background-color:#4472C4;color:#fff;text-align:center;}");
html.append(".header{font-size:14pt;font-weight:bold;text-align:center;margin-bottom:15px;}");
html.append(".info-table td{border:1px solid #999;} .info-table th{background-color:#D9E2F3;color:#333;}");
html.append("</style></head><body>");
html.append("<div class='header'>견 적 서</div>");
// 기본 정보 테이블
html.append("<table class='info-table' style='margin-bottom:15px;'>");
html.append("<tr><th style='width:15%'>견적번호</th><td style='width:35%'>" + estimateNo + "</td>");
html.append("<th style='width:15%'>영업번호</th><td style='width:35%'>" + contractNo + "</td></tr>");
html.append("<tr><th>고객사</th><td>" + customerName + "</td>");
html.append("<th>작성일</th><td>" + regdate + "</td></tr>");
html.append("<tr><th>수신</th><td>" + recipient + "</td>");
html.append("<th>담당자</th><td>" + contactPerson + "</td></tr>");
html.append("<tr><th>작성자</th><td>" + writerName + "</td>");
html.append("<th>모델명</th><td>" + modelName + "</td></tr>");
if(!currencyName.isEmpty()){
html.append("<tr><th>통화</th><td>" + currencyName + "</td>");
html.append("<th>환율</th><td>" + exchangeRate + "</td></tr>");
}
html.append("<tr><th>합계금액</th><td colspan='3'>");
if(!totalAmountKrw.isEmpty() && !"0".equals(totalAmountKrw)){
html.append(totalAmountKrw + " (KRW)");
if(!totalAmount.isEmpty()) html.append(" / " + totalAmount + " (" + currencyName + ")");
} else {
html.append(totalAmount);
}
html.append("</td></tr>");
if(!validityPeriod.isEmpty()){
html.append("<tr><th>유효기간</th><td colspan='3'>" + validityPeriod + "</td></tr>");
}
html.append("</table>");
// 품목 리스트
if(itemList != null && !itemList.isEmpty()){
html.append("<table>");
html.append("<thead><tr>");
html.append("<th>No</th><th>품명</th><th>규격</th><th>수량</th><th>단위</th><th>단가</th><th>금액</th><th>비고</th>");
html.append("</tr></thead><tbody>");
int no = 1;
for(Map item : itemList){
item = CommonUtils.toUpperCaseMapKey(item);
html.append("<tr>");
html.append("<td style='text-align:center;'>" + no++ + "</td>");
html.append("<td>" + CommonUtils.checkNull(item.get("DESCRIPTION")) + "</td>");
html.append("<td>" + CommonUtils.checkNull(item.get("SPECIFICATION")) + "</td>");
html.append("<td style='text-align:right;'>" + CommonUtils.checkNull(item.get("QUANTITY")) + "</td>");
html.append("<td style='text-align:center;'>" + CommonUtils.checkNull(item.get("UNIT")) + "</td>");
html.append("<td style='text-align:right;'>" + CommonUtils.checkNull(item.get("UNIT_PRICE")) + "</td>");
html.append("<td style='text-align:right;'>" + CommonUtils.checkNull(item.get("AMOUNT")) + "</td>");
html.append("<td>" + CommonUtils.checkNull(item.get("REMARK")) + "</td>");
html.append("</tr>");
}
html.append("</tbody></table>");
}
// 비고
if(!noteRemarks.isEmpty()){
html.append("<br/><div><strong>비고:</strong><br/>" + noteRemarks.replace("\n", "<br/>") + "</div>");
}
html.append("</body></html>");
return html.toString();
}
/**
* 견적서 데이터를 HTML 본문으로 구성 (아마란스 결재 화면 표시용)
*/
private String buildEstimateContentsHtml(Map estimateInfo, List<Map> itemList){
StringBuilder html = new StringBuilder();
String estimateNo = CommonUtils.checkNull(estimateInfo.get("ESTIMATE_NO"));
String contractNo = CommonUtils.checkNull(estimateInfo.get("CONTRACT_NO"));
String customerName = CommonUtils.checkNull(estimateInfo.get("CUSTOMER_NAME"));
String writerName = CommonUtils.checkNull(estimateInfo.get("WRITER_NAME"));
String regdate = CommonUtils.checkNull(estimateInfo.get("REGDATE"));
String totalAmount = CommonUtils.checkNull(estimateInfo.get("TOTAL_AMOUNT"));
String totalAmountKrw = CommonUtils.checkNull(estimateInfo.get("TOTAL_AMOUNT_KRW"));
String currencyName = CommonUtils.checkNull(estimateInfo.get("CURRENCY_NAME"));
String exchangeRate = CommonUtils.checkNull(estimateInfo.get("EXCHANGE_RATE"));
String recipient = CommonUtils.checkNull(estimateInfo.get("RECIPIENT"));
String contactPerson = CommonUtils.checkNull(estimateInfo.get("CONTACT_PERSON"));
String modelName = CommonUtils.checkNull(estimateInfo.get("MODEL_NAME"));
String validityPeriod = CommonUtils.checkNull(estimateInfo.get("VALIDITY_PERIOD"));
String noteRemarks = CommonUtils.checkNull(estimateInfo.get("NOTE_REMARKS"));
html.append("<div style='font-family:맑은 고딕,sans-serif;font-size:10pt;'>");
html.append("<h3 style='text-align:center;margin-bottom:10px;'>견 적 서</h3>");
html.append("<table cellspacing='0' cellpadding='4' style='border-collapse:collapse;width:100%;margin-bottom:10px;'>");
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;width:15%;font-weight:bold;font-size:9pt;'>견적번호</td>");
html.append("<td style='border:1px solid #333;width:35%;font-size:9pt;'>" + estimateNo + "</td>");
html.append("<td style='border:1px solid #333;background-color:#D9E2F3;width:15%;font-weight:bold;font-size:9pt;'>영업번호</td>");
html.append("<td style='border:1px solid #333;width:35%;font-size:9pt;'>" + contractNo + "</td></tr>");
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>고객사</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + customerName + "</td>");
html.append("<td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>작성일</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + regdate + "</td></tr>");
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>수신</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + recipient + "</td>");
html.append("<td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>담당자</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + contactPerson + "</td></tr>");
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>작성자</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + writerName + "</td>");
html.append("<td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>모델명</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + modelName + "</td></tr>");
if(!currencyName.isEmpty()){
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>통화</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + currencyName + "</td>");
html.append("<td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>환율</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + exchangeRate + "</td></tr>");
}
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>합계금액</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;' colspan='3'>");
if(!totalAmountKrw.isEmpty() && !"0".equals(totalAmountKrw)){
html.append(totalAmountKrw + " (KRW)");
if(!totalAmount.isEmpty()) html.append(" / " + totalAmount + " (" + currencyName + ")");
} else {
html.append(totalAmount);
}
html.append("</td></tr>");
if(!validityPeriod.isEmpty()){
html.append("<tr><td style='border:1px solid #333;background-color:#D9E2F3;font-weight:bold;font-size:9pt;'>유효기간</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;' colspan='3'>" + validityPeriod + "</td></tr>");
}
html.append("</table>");
// 품목 리스트
if(itemList != null && !itemList.isEmpty()){
html.append("<table cellspacing='0' cellpadding='4' style='border-collapse:collapse;width:100%;'>");
html.append("<thead><tr>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>No</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>품명</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>규격</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>수량</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>단위</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>단가</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>금액</th>");
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>비고</th>");
html.append("</tr></thead><tbody>");
int no = 1;
for(Map item : itemList){
item = CommonUtils.toUpperCaseMapKey(item);
html.append("<tr>");
html.append("<td style='border:1px solid #333;text-align:center;font-size:9pt;'>" + no++ + "</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + CommonUtils.checkNull(item.get("DESCRIPTION")) + "</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + CommonUtils.checkNull(item.get("SPECIFICATION")) + "</td>");
html.append("<td style='border:1px solid #333;text-align:right;font-size:9pt;'>" + CommonUtils.checkNull(item.get("QUANTITY")) + "</td>");
html.append("<td style='border:1px solid #333;text-align:center;font-size:9pt;'>" + CommonUtils.checkNull(item.get("UNIT")) + "</td>");
html.append("<td style='border:1px solid #333;text-align:right;font-size:9pt;'>" + CommonUtils.checkNull(item.get("UNIT_PRICE")) + "</td>");
html.append("<td style='border:1px solid #333;text-align:right;font-size:9pt;'>" + CommonUtils.checkNull(item.get("AMOUNT")) + "</td>");
html.append("<td style='border:1px solid #333;font-size:9pt;'>" + CommonUtils.checkNull(item.get("REMARK")) + "</td>");
html.append("</tr>");
}
html.append("</tbody></table>");
}
if(!noteRemarks.isEmpty()){
html.append("<br/><div><strong>비고:</strong><br/>" + noteRemarks.replace("\n", "<br/>") + "</div>");
}
html.append("</div>");
return html.toString();
}
/**
* Amaranth10 전자결재 콜백 처리
* 결재 이벤트(상신/진행/종결/반려/삭제 등) 발생 시 Amaranth10에서 호출
@@ -2091,6 +2413,9 @@ public class ApprovalService {
} else if("SALES_REQUEST".equals(targetType)){
sqlSession.update("approval.salesRequestApprovalStatus", statusParam);
System.out.println("구매요청 상태 변경 - targetObjId: " + targetObjId + "" + proposalStatus);
} else if("CONTRACT_ESTIMATE".equals(targetType)){
// 견적서: AMARANTH_APPROVAL 테이블만으로 상태 관리 (품의서와 동일 패턴)
System.out.println("견적서 결재 상태 변경 - targetObjId: " + targetObjId + "" + internalStatus);
}
}
}
@@ -2195,6 +2520,22 @@ public class ApprovalService {
title = "품의서 결재 - " + proposalNo;
}
}
} else if("CONTRACT_ESTIMATE".equals(targetType) && !targetObjId.isEmpty()){
Map<String, Object> estParam = new HashMap();
estParam.put("targetObjId", targetObjId);
Map<String, Object> estimateInfo = sqlSession.selectOne("approval.getEstimateInfoForApproval", estParam);
if(estimateInfo != null){
estimateInfo = CommonUtils.toUpperCaseMapKey(estimateInfo);
List<Map> itemList = sqlSession.selectList("approval.getEstimateItemsForApproval", estParam);
contentsHtml = buildEstimateContentsHtml(estimateInfo, itemList);
String estimateNo = CommonUtils.checkNull(estimateInfo.get("ESTIMATE_NO"));
if(!estimateNo.isEmpty()){
title = "견적서 결재 - " + estimateNo;
}
}
}
}