Merge remote-tracking branch 'origin/main' into V20260210

This commit is contained in:
2026-02-25 14:12:08 +09:00
5 changed files with 354 additions and 56 deletions

View File

@@ -901,6 +901,7 @@ public class QualityController {
e.printStackTrace();
}
request.setAttribute("code_map", code_map);
request.setAttribute("AMARANTH_OUT_PROCESS_CODE", Constants.AMARANTH_OUT_PROCESS_CODE);
return "/quality/ecrList";
}

View File

@@ -694,6 +694,13 @@
STATUS = #{status}
WHERE OBJID::VARCHAR = #{targetObjId}::VARCHAR
</update>
<!-- ECR(ECR_MNG) 결재 상태 변경 -->
<update id="changeEcrApprovalStatus" parameterType="map">
UPDATE ECR_MNG SET
STATUS_CD = #{statusCd}
WHERE OBJID::VARCHAR = #{targetObjId}::VARCHAR
</update>
<!-- 견적서 정보 조회 (결재 본문용) -->
<select id="getEstimateInfoForApproval" parameterType="map" resultType="map">

View File

@@ -2281,7 +2281,17 @@
, CASE WHEN (ECR.ECR_DOC_SUMMARY IS NOT NULL AND ECR.ECR_DOC_SUMMARY != '') OR (ECR.ECR_DOC_REASON IS NOT NULL AND ECR.ECR_DOC_REASON != '') THEN 1 ELSE 0 END AS ECR_DOC_CNT
, ECR.CHANGE_TYPE
, CODE_NAME(ECR.CHANGE_TYPE) AS CHANGE_TYPE_NAME
, COALESCE(AMR.STATUS, '') AS AMARANTH_STATUS
, CASE
WHEN AMR.STATUS = 'complete' THEN '결재완료'
WHEN AMR.STATUS = 'inProcess' THEN '결재 상신중'
WHEN AMR.STATUS = 'reject' THEN '반려'
ELSE ''
END AS AMARANTH_STATUS_TITLE
FROM PMS_QUALITY_ECR ECR
LEFT OUTER JOIN AMARANTH_APPROVAL AMR
ON ECR.OBJID::VARCHAR = AMR.TARGET_OBJID
AND AMR.TARGET_TYPE = 'ECR'
WHERE 1=1
<if test="search_request_date_from != null and search_request_date_from != ''">
AND ECR.REQUEST_DATE >= #{search_request_date_from}

View File

@@ -1843,7 +1843,7 @@ public class ApprovalService {
if("CONTRACT_ESTIMATE".equals(targetType)) {
fileListEncoded = uploadEstimateFilesToOneChamber(apiClient, empSeq, targetObjId);
} else {
fileListEncoded = uploadProposalFilesToOneChamber(apiClient, empSeq, targetObjId);
fileListEncoded = uploadFilesToOneChamber(apiClient, empSeq, targetObjId, targetType);
}
} catch(Exception fileEx) {
System.err.println("[첨부파일] 원챔버 업로드 중 오류 (결재는 계속 진행): " + fileEx.getMessage());
@@ -1901,12 +1901,13 @@ public class ApprovalService {
}
/**
* 품의서 첨부파일을 원챔버에 업로드하고 fileList (URL 인코딩) 반환
* 결재 대상 첨부파일을 원챔버에 업로드하고 fileList (URL 인코딩) 반환
* @param targetType PROPOSAL, ECR 등
* @return URL 인코딩된 fileList JSON 문자열 (파일 없으면 null)
*/
private String uploadProposalFilesToOneChamber(
private String uploadFilesToOneChamber(
com.pms.api.AmaranthApprovalApiClient apiClient,
String empSeq, String targetObjId) throws Exception {
String empSeq, String targetObjId, String targetType) throws Exception {
if(targetObjId == null || targetObjId.isEmpty()) return null;
@@ -1923,8 +1924,12 @@ public class ApprovalService {
StringBuilder listItems = new StringBuilder();
int uploadCount = 0;
// 2. 품의서 본문을 JSP와 동일한 레이아웃의 HTML 파일로 생성하여 업로드
// 2. 본문을 HTML 파일로 생성하여 업로드
try {
String fullHtml = null;
String tempFileName = null;
if("PROPOSAL".equals(targetType)){
Map<String, Object> proposalParam = new HashMap<String, Object>();
proposalParam.put("PROPOSAL_OBJID", targetObjId);
Map<String, Object> proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam);
@@ -1933,21 +1938,38 @@ public class ApprovalService {
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");
fullHtml = buildProposalFormFileHtml(proposalInfo, partList);
tempFileName = "구매품의서_" + proposalNo + ".html";
}
} else if("ECR".equals(targetType)){
Map<String, Object> ecrParam = new HashMap<String, Object>();
try {
ecrParam.put("OBJID", Long.parseLong(targetObjId));
} catch(NumberFormatException nfe){
ecrParam.put("OBJID", targetObjId);
}
Map<String, Object> ecrInfo = sqlSession.selectOne("quality.getEcrInfo", ecrParam);
if(ecrInfo != null){
ecrInfo = CommonUtils.toUpperCaseMapKey(ecrInfo);
String ecrNo = CommonUtils.checkNull(ecrInfo.get("ECR_NO"));
fullHtml = buildEcrFormFileHtml(ecrInfo);
tempFileName = "설계변경요청서_" + ecrNo + ".html";
}
}
if(fullHtml != null && tempFileName != null){
java.io.File tempFile = java.io.File.createTempFile("approval_", ".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)");
System.out.println("[첨부파일] HTML 생성: " + tempFileName + " (" + tempFile.length() + " bytes)");
String uploadResponse = apiClient.uploadFileToOneChamber(
AMARANTH_BASE_URL, authToken, userHashKey, empSeq, tempFile, tempFileName
@@ -1957,13 +1979,12 @@ public class ApprovalService {
if(listItem != null){
listItems.append(listItem);
uploadCount++;
System.out.println("[첨부파일] 품의서 HTML 업로드 성공");
System.out.println("[첨부파일] HTML 업로드 성공");
}
tempFile.delete();
}
} catch(Exception htmlEx){
System.err.println("[첨부파일] 품의서 HTML 생성/업로드 오류: " + htmlEx.getMessage());
System.err.println("[첨부파일] HTML 생성/업로드 오류: " + htmlEx.getMessage());
}
// 3. ATTACH_FILE_INFO에서 기존 첨부파일도 업로드
@@ -2474,16 +2495,17 @@ public class ApprovalService {
statusParam.put("targetObjId", targetObjId);
statusParam.put("status", proposalStatus);
if("PROPOSAL".equals(targetType)){
sqlSession.update("approval.changeProposalApprovalStatus", statusParam);
System.out.println("품의서 상태 변경 - targetObjId: " + targetObjId + "" + proposalStatus);
} 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);
}
if("PROPOSAL".equals(targetType)){
sqlSession.update("approval.changeProposalApprovalStatus", statusParam);
System.out.println("품의서 상태 변경 - targetObjId: " + targetObjId + "" + proposalStatus);
} else if("SALES_REQUEST".equals(targetType)){
sqlSession.update("approval.salesRequestApprovalStatus", statusParam);
System.out.println("구매요청 상태 변경 - targetObjId: " + targetObjId + "" + proposalStatus);
} else if("CONTRACT_ESTIMATE".equals(targetType)){
System.out.println("견적서 결재 상태 변경 - targetObjId: " + targetObjId + "" + internalStatus);
} else if("ECR".equals(targetType)){
System.out.println("ECR 콜백 처리 - targetObjId: " + targetObjId + " (AMARANTH_APPROVAL 상태만 관리)");
}
}
}
@@ -2529,6 +2551,8 @@ public class ApprovalService {
return "";
}
// ECR(quality)은 PMS_QUALITY_ECR 테이블에 STATUS_CD가 없으므로 AMARANTH_APPROVAL만으로 상태 관리
/**
* Amaranth10 결재작성 시 본문 내용 조회 (Binding WebAPI / WebAPI)
* 결재 작성 화면이 열릴 때 Amaranth10에서 호출
@@ -2573,37 +2597,57 @@ public class ApprovalService {
System.out.println("매핑 정보 - targetType: " + targetType + ", targetObjId: " + targetObjId);
if("PROPOSAL".equals(targetType) && !targetObjId.isEmpty()){
Map<String, Object> proposalParam = new HashMap();
proposalParam.put("PROPOSAL_OBJID", targetObjId);
Map<String, Object> proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam);
if("PROPOSAL".equals(targetType) && !targetObjId.isEmpty()){
Map<String, Object> proposalParam = new HashMap();
proposalParam.put("PROPOSAL_OBJID", targetObjId);
Map<String, Object> proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam);
if(proposalInfo != null){
proposalInfo = CommonUtils.toUpperCaseMapKey(proposalInfo);
contentsHtml = buildProposalContentsHtml(proposalInfo, sqlSession, targetObjId);
if(proposalInfo != null){
proposalInfo = CommonUtils.toUpperCaseMapKey(proposalInfo);
contentsHtml = buildProposalContentsHtml(proposalInfo, sqlSession, targetObjId);
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
if(!proposalNo.isEmpty()){
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;
}
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
if(!proposalNo.isEmpty()){
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;
}
}
} else if("ECR".equals(targetType) && !targetObjId.isEmpty()){
Map<String, Object> ecrParam = new HashMap();
try {
ecrParam.put("OBJID", Long.parseLong(targetObjId));
} catch(NumberFormatException nfe){
ecrParam.put("OBJID", targetObjId);
}
System.out.println("ECR 본문 조회 - OBJID: " + targetObjId);
Map<String, Object> ecrInfo = sqlSession.selectOne("quality.getEcrInfo", ecrParam);
System.out.println("ECR 조회 결과: " + (ecrInfo != null ? "데이터 있음" : "null"));
if(ecrInfo != null){
ecrInfo = CommonUtils.toUpperCaseMapKey(ecrInfo);
contentsHtml = buildEcrContentsHtml(ecrInfo);
String ecrNo = CommonUtils.checkNull(ecrInfo.get("ECR_NO"));
if(!ecrNo.isEmpty()){
title = "ECR 결재 - " + ecrNo;
}
}
}
}
// JSONObject로 안전한 JSON 응답 생성 (이스케이프 자동 처리)
@@ -2642,6 +2686,195 @@ public class ApprovalService {
/**
* 품의서 데이터를 HTML 본문으로 구성
*/
/**
* ECR 데이터를 HTML 본문으로 구성
*/
private String buildEcrContentsHtml(Map ecrInfo){
StringBuilder html = new StringBuilder();
String ecrNo = CommonUtils.checkNull(ecrInfo.get("ECR_NO"));
String requestDate = CommonUtils.checkNull(ecrInfo.get("REQUEST_DATE"));
String requesterName = CommonUtils.checkNull(ecrInfo.get("REQUESTER_NAME"));
String partNo = CommonUtils.checkNull(ecrInfo.get("PART_NO"));
String partName = CommonUtils.checkNull(ecrInfo.get("PART_NAME"));
String issueContent = CommonUtils.checkNull(ecrInfo.get("ISSUE_CONTENT"));
String dueDate = CommonUtils.checkNull(ecrInfo.get("DUE_DATE"));
String actionDeptName = CommonUtils.checkNull(ecrInfo.get("ACTION_DEPT_NAME"));
String actionUserName = CommonUtils.checkNull(ecrInfo.get("ACTION_USER_NAME"));
String actionContent = CommonUtils.checkNull(ecrInfo.get("ACTION_CONTENT"));
String completeDate = CommonUtils.checkNull(ecrInfo.get("COMPLETE_DATE"));
String changeTypeName = CommonUtils.checkNull(ecrInfo.get("CHANGE_TYPE_NAME"));
String ecrDocSummary = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_SUMMARY"));
String ecrDocReason = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_REASON"));
html.append("<div style='font-family:맑은 고딕,Malgun Gothic,sans-serif; padding:10px;'>");
html.append("<h2 style='text-align:center; margin-bottom:15px;'>ECR (Engineering Change Request)</h2>");
html.append("<table style='width:100%; border-collapse:collapse; border:1px solid #333; margin-bottom:15px;'>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold; width:15%;'>ECR No</td>");
html.append("<td style='border:1px solid #333; padding:6px; width:35%;'>").append(ecrNo).append("</td>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold; width:15%;'>요청일</td>");
html.append("<td style='border:1px solid #333; padding:6px; width:35%;'>").append(requestDate).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>요청자</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(requesterName).append("</td>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>설변유형</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(changeTypeName).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>품번</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(partNo).append("</td>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>품명</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(partName).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>이슈사항</td>");
html.append("<td style='border:1px solid #333; padding:6px;' colspan='3' style='white-space:pre-wrap;'>").append(issueContent).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>완료요청일</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(dueDate).append("</td>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>조치부서</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(actionDeptName).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>조치자</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(actionUserName).append("</td>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>완료일</td>");
html.append("<td style='border:1px solid #333; padding:6px;'>").append(completeDate).append("</td>");
html.append("</tr>");
if(!actionContent.isEmpty()){
html.append("<tr>");
html.append("<td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>조치내용</td>");
html.append("<td style='border:1px solid #333; padding:6px;' colspan='3' style='white-space:pre-wrap;'>").append(actionContent).append("</td>");
html.append("</tr>");
}
html.append("</table>");
if(!ecrDocSummary.isEmpty() || !ecrDocReason.isEmpty()){
html.append("<h3 style='margin:15px 0 8px;'>설계변경요청서</h3>");
html.append("<table style='width:100%; border-collapse:collapse; border:1px solid #333;'>");
if(!ecrDocSummary.isEmpty()){
html.append("<tr><td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold; width:15%;'>요약</td>");
html.append("<td style='border:1px solid #333; padding:8px; white-space:pre-wrap;'>").append(ecrDocSummary).append("</td></tr>");
}
if(!ecrDocReason.isEmpty()){
html.append("<tr><td style='border:1px solid #333; padding:6px; background:#f0f0f0; font-weight:bold;'>변경사유</td>");
html.append("<td style='border:1px solid #333; padding:8px; white-space:pre-wrap;'>").append(ecrDocReason).append("</td></tr>");
}
html.append("</table>");
}
html.append("</div>");
return html.toString();
}
/**
* ECR 데이터를 첨부파일용 완전한 HTML 파일로 구성
*/
private String buildEcrFormFileHtml(Map ecrInfo){
StringBuilder html = new StringBuilder();
String ecrNo = CommonUtils.checkNull(ecrInfo.get("ECR_NO"));
String requestDate = CommonUtils.checkNull(ecrInfo.get("REQUEST_DATE"));
String requesterName = CommonUtils.checkNull(ecrInfo.get("REQUESTER_NAME"));
String partNo = CommonUtils.checkNull(ecrInfo.get("PART_NO"));
String partName = CommonUtils.checkNull(ecrInfo.get("PART_NAME"));
String issueContent = CommonUtils.checkNull(ecrInfo.get("ISSUE_CONTENT"));
String dueDate = CommonUtils.checkNull(ecrInfo.get("DUE_DATE"));
String actionDeptName = CommonUtils.checkNull(ecrInfo.get("ACTION_DEPT_NAME"));
String actionUserName = CommonUtils.checkNull(ecrInfo.get("ACTION_USER_NAME"));
String actionContent = CommonUtils.checkNull(ecrInfo.get("ACTION_CONTENT"));
String completeDate = CommonUtils.checkNull(ecrInfo.get("COMPLETE_DATE"));
String changeTypeName = CommonUtils.checkNull(ecrInfo.get("CHANGE_TYPE_NAME"));
String ecrDocFormNo = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_FORM_NO"));
String ecrRevNo = CommonUtils.checkNull(ecrInfo.get("ECR_REV_NO"));
String ecrRevDate = CommonUtils.checkNull(ecrInfo.get("ECR_REV_DATE"));
String ecrDocAuthor = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_AUTHOR"));
String ecrDocSummary = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_SUMMARY"));
String ecrDocReason = CommonUtils.checkNull(ecrInfo.get("ECR_DOC_REASON"));
html.append("<!DOCTYPE html><html><head><meta charset='UTF-8'>");
html.append("<title>ECR - ").append(ecrNo).append("</title>");
html.append("<style>");
html.append("body{font-family:'Malgun Gothic','Arial',sans-serif;padding:0;background:#fff;}");
html.append(".ecr_container{width:100%;max-width:800px;margin:0 auto;border:1px solid #333;}");
html.append(".ecr_header{display:table;width:100%;border-bottom:1px solid #333;}");
html.append(".ecr_header_center{display:table-cell;vertical-align:middle;text-align:center;background:#1565a0;color:white;padding:15px;}");
html.append(".ecr_header_center h1{font-size:28px;margin:0 0 5px;}");
html.append(".ecr_header_center p{font-size:14px;margin:0;}");
html.append(".ecr_header_right{display:table-cell;width:180px;vertical-align:top;border-left:1px solid #333;}");
html.append(".ecr_header_right table{width:100%;border-collapse:collapse;}");
html.append(".ecr_header_right td{padding:5px 8px;font-size:12px;border-bottom:1px solid #ccc;}");
html.append(".ecr_header_right td:first-child{font-weight:bold;width:70px;}");
html.append(".ecr_body{padding:20px;}");
html.append("table.info{width:100%;border-collapse:collapse;margin-bottom:15px;}");
html.append("table.info td,table.info th{border:1px solid #333;padding:8px;}");
html.append(".lbl{background:#f0f0f0;font-weight:bold;width:15%;}");
html.append(".section_title{font-weight:bold;font-size:13px;text-decoration:underline;margin:15px 0 5px;}");
html.append(".content_box{border:1px solid #ccc;padding:10px;min-height:100px;white-space:pre-wrap;font-size:13px;}");
html.append(".ecr_footer{padding:10px 15px;font-size:11px;border-top:1px solid #ccc;}");
html.append("</style></head><body>");
html.append("<div class='ecr_container'>");
html.append("<div class='ecr_header'>");
html.append("<div class='ecr_header_center'><h1>ECR</h1><p>(Engineering Change Request)</p></div>");
html.append("<div class='ecr_header_right'><table>");
html.append("<tr><td>Form no.</td><td>").append(ecrDocFormNo).append("</td></tr>");
html.append("<tr><td>Rev.no</td><td>").append(ecrRevNo).append("</td></tr>");
html.append("<tr><td>Rev. date</td><td>").append(ecrRevDate).append("</td></tr>");
html.append("<tr><td>Author</td><td>").append(ecrDocAuthor).append("</td></tr>");
html.append("</table></div></div>");
html.append("<div class='ecr_body'>");
html.append("<table class='info'>");
html.append("<tr><td class='lbl'>ECR No</td><td>").append(ecrNo).append("</td>");
html.append("<td class='lbl'>요청일</td><td>").append(requestDate).append("</td></tr>");
html.append("<tr><td class='lbl'>요청자</td><td>").append(requesterName).append("</td>");
html.append("<td class='lbl'>설변유형</td><td>").append(changeTypeName).append("</td></tr>");
html.append("<tr><td class='lbl'>품번</td><td>").append(partNo).append("</td>");
html.append("<td class='lbl'>품명</td><td>").append(partName).append("</td></tr>");
html.append("<tr><td class='lbl'>이슈사항</td><td colspan='3' style='white-space:pre-wrap;'>").append(issueContent).append("</td></tr>");
html.append("<tr><td class='lbl'>완료요청일</td><td>").append(dueDate).append("</td>");
html.append("<td class='lbl'>조치부서</td><td>").append(actionDeptName).append("</td></tr>");
html.append("<tr><td class='lbl'>조치자</td><td>").append(actionUserName).append("</td>");
html.append("<td class='lbl'>완료일</td><td>").append(completeDate).append("</td></tr>");
if(!actionContent.isEmpty()){
html.append("<tr><td class='lbl'>조치내용</td><td colspan='3' style='white-space:pre-wrap;'>").append(actionContent).append("</td></tr>");
}
html.append("</table>");
if(!ecrDocSummary.isEmpty()){
html.append("<div class='section_title'>요약 (Summary)</div>");
html.append("<div class='content_box'>").append(ecrDocSummary).append("</div>");
}
if(!ecrDocReason.isEmpty()){
html.append("<div class='section_title'>변경사항 설명 (이유 / 배경)</div>");
html.append("<div class='content_box'>").append(ecrDocReason).append("</div>");
}
html.append("</div>");
html.append("<div class='ecr_footer'>");
html.append("<p>Confidential! &copy; RPS reserves all rights even in the event of industrial property rights.</p>");
html.append("<p>We reserve all rights of disposal such as copying and passing on to third parties!</p>");
html.append("</div>");
html.append("</div>");
html.append("</body></html>");
return html.toString();
}
private String buildProposalContentsHtml(Map proposalInfo, SqlSession sqlSession, String targetObjId){
StringBuilder html = new StringBuilder();