V20260210 #169

Merged
hjjeong merged 2 commits from V20260210 into main 2026-03-12 01:23:19 +00:00
3 changed files with 165 additions and 119 deletions

View File

@@ -79,13 +79,14 @@ var columns = [
{title:'PURCHASE_ORDER_PART_OBJID', field:'PURCHASE_ORDER_PART_OBJID', visible:false, frozen:true},
{title:'FORM_TYPE', field:'FORM_TYPE', visible:false, frozen:true},
{title:'ARRIVAL_PLAN_OBJID', field:'ARRIVAL_PLAN_OBJID', visible:false, frozen:true},
{headerHozAlign:'center', hozAlign:'center', minWidth:110, widthGrow:0.9, title:'품의서 No', field:'PROPOSAL_NO',
{title:'SUB_LOCATION', field:'SUB_LOCATION', visible:false, frozen:true},
{headerHozAlign:'center', hozAlign:'center', minWidth:110, widthGrow:0.9, title:'품의서 No', field:'PROPOSAL_NO', frozen:true,
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell){
fn_openProposalFormPopUp(cell.getData().SALES_REQUEST_OBJID);
}
},
{headerHozAlign:'center', hozAlign:'center', minWidth:110, widthGrow:0.9, title:'발주서 No', field:'PURCHASE_ORDER_NO',
{headerHozAlign:'center', hozAlign:'center', minWidth:110, widthGrow:0.9, title:'발주서 No', field:'PURCHASE_ORDER_NO', frozen:true,
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell){
var objId = fnc_checkNull(cell.getData().PURCHASE_ORDER_MASTER_OBJID);
@@ -93,12 +94,12 @@ var columns = [
fn_formPopUp(objId, formType);
}
},
{headerHozAlign:'center', hozAlign:'center', minWidth:120, widthGrow:0.9, title:'프로젝트번호', field:'PROJECT_NO'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:1.5, title:'부품품번', field:'COMPONENT_PART_NO'},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:1.5, title:'품번', field:'PART_NO'},
{headerHozAlign:'center', hozAlign:'left', minWidth:150, widthGrow:2, title:'품명', field:'PART_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:120, widthGrow:0.9, title:'프로젝트번호', field:'PROJECT_NO', frozen:true},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:1.5, title:'부품품번', field:'COMPONENT_PART_NO', frozen:true},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:1.5, title:'품번', field:'PART_NO', frozen:true},
{headerHozAlign:'center', hozAlign:'left', minWidth:150, widthGrow:2, title:'품명', field:'PART_NAME', frozen:true},
{headerHozAlign:'center', hozAlign:'left', minWidth:120, widthGrow:1.2, title:'공급업체', field:'PARTNER_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:0.7, title:'입고일', field:'RECEIPT_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:0.7, title:'입고일', field:'RECEIPT_DATE'},
//{headerHozAlign:'center', hozAlign:'center', minWidth:100, widthGrow:0.8, title:'입고요청일', field:'DELIVERY_REQUEST_DATE'},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:1, title:'구매담당자', field:'WRITER_NAME'},
// {headerHozAlign:'center', hozAlign:'right', minWidth:80, widthGrow:0.8, title:'발주수량', field:'ORDER_QTY',
@@ -126,7 +127,8 @@ var columns = [
{headerHozAlign:'center', hozAlign:'right', minWidth:100, widthGrow:0.9, title:'확정입고수량', field:'CONFIRMED_QTY',
formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false}
},
{headerHozAlign:'center', hozAlign:'center', minWidth:90, widthGrow:0.6, title:'매입마감', field:'PURCHASE_CLOSE_DATE'}
{headerHozAlign:'center', hozAlign:'left', minWidth:100, widthGrow:1.2, title:'계정과목', field:'SUB_LOCATION_NAME'},
{headerHozAlign:'center', hozAlign:'center', minWidth:85, widthGrow:0.6, title:'매입마감', field:'PURCHASE_CLOSE_DATE'}
];
function fn_search(){
@@ -145,7 +147,7 @@ function fn_search(){
value: selectedIdValues
}).appendTo('#form1');
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/purchaseOrder/purchaseCloseGridList.do", columns, true);
_tabulGrid = fnc_tabul_search(_tabul_layout_fitDataStretch, _tabulGrid, "/purchaseOrder/purchaseCloseGridList.do", columns, true);
if(_tabulGrid) {
_tabulGrid.off("renderComplete");

View File

@@ -6555,6 +6555,10 @@ FROM(
<!-- 확정입고수량 = 전체입고수량 - 폐기수량 -->
,(COALESCE(AP_AGG.TOTAL_DELIVERY_QTY, 0) - COALESCE(DEFECT_AGG.DEFECT_QTY, 0)) AS CONFIRMED_QTY
<!-- 계정과목 -->
,AP.SUB_LOCATION
,COALESCE((SELECT ACCT_NM FROM ERP_ACCT_CODE WHERE ACCT_CD = AP.SUB_LOCATION), AP.SUB_LOCATION) AS SUB_LOCATION_NAME
<!-- 매입마감일 (입고건 단위) -->
,AP.PURCHASE_CLOSE_DATE

View File

@@ -3286,108 +3286,127 @@ public class ApprovalService {
StringBuilder html = new StringBuilder();
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
String projectNumber = CommonUtils.checkNull(proposalInfo.get("PROJECT_NUMBER"));
String projectName = CommonUtils.checkNull(proposalInfo.get("PROJECT_NAME"));
String purchaseTypeName = CommonUtils.checkNull(proposalInfo.get("PURCHASE_TYPE_NAME"));
String orderTypeName = CommonUtils.checkNull(proposalInfo.get("ORDER_TYPE_NAME"));
String productName = CommonUtils.checkNull(proposalInfo.get("PRODUCT_NAME_TITLE"));
String customerName = CommonUtils.checkNull(proposalInfo.get("PROJECT_CUSTOMER_NAME"));
String writerName = CommonUtils.checkNull(proposalInfo.get("WRITER_NAME"));
String regdate = CommonUtils.checkNull(proposalInfo.get("REGDATE_TITLE"));
String remark = CommonUtils.checkNull(proposalInfo.get("REMARK"));
String totalAmount = CommonUtils.checkNull(proposalInfo.get("TOTAL_AMOUNT"));
html.append("<div style='font-family:맑은 고딕,Malgun Gothic,sans-serif;font-size:10pt;'>");
html.append("<table cellspacing='0' cellpadding='5' style='border-collapse:collapse;width:100%;'>");
html.append("<colgroup><col style='width:120px;'/><col style='width:200px;'/><col style='width:120px;'/><col style='width:200px;'/></colgroup>");
html.append("<tbody>");
html.append("<tr>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>품의서 No</th>");
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(proposalNo)).append("</td>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>작성일</th>");
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(regdate)).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>프로젝트번호</th>");
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(projectNumber)).append("</td>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>프로젝트명</th>");
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(projectName)).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>구매유형</th>");
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(purchaseTypeName)).append("</td>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>주문유형</th>");
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(orderTypeName)).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>제품구분</th>");
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(productName)).append("</td>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>고객사</th>");
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(customerName)).append("</td>");
html.append("</tr>");
html.append("<tr>");
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>작성자</th>");
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(writerName)).append("</td>");
if(!totalAmount.isEmpty()){
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>합계금액</th>");
html.append("<td style='border:1px solid #333;text-align:right;padding-right:8px;'>").append(escapeHtml(totalAmount)).append("</td>");
} else {
html.append("<td style='border:1px solid #333;' colspan='2'></td>");
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;
}
}
html.append("</tr>");
html.append("</tbody></table>");
// 품의서 품목 리스트 조회
List<Map> partList = null;
try {
Map<String, Object> partParam = new HashMap();
partParam.put("PROPOSAL_OBJID", targetObjId);
List<Map> partList = sqlSession.selectList("salesMng.getProposalPartList", partParam);
if(partList != null && !partList.isEmpty()){
html.append("<br/>");
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 idx = 1;
for(Map partInfo : partList){
html.append("<tr>");
html.append("<td style='border:1px solid #ccc;text-align:center;font-size:9pt;'>").append(idx++).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NO")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NAME")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("SPEC")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("QTY")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("UNIT_PRICE")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("TOTAL_PRICE")))).append("</td>");
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("VENDOR_NAME")))).append("</td>");
html.append("</tr>");
}
html.append("</tbody></table>");
}
partList = sqlSession.selectList("salesMng.getProposalPartList", partParam);
} catch(Exception e){
System.err.println("품의서 품목 리스트 조회 오류: " + e.getMessage());
}
// 비고
if(!remark.isEmpty()){
html.append("<br/><p><strong>비고:</strong> ").append(escapeHtml(remark)).append("</p>");
long totalAmount = 0;
if(partList != null){
for(Map part : partList){
try {
Object tp = part.get("TOTAL_PRICE");
if(tp != null) totalAmount += Math.round(Double.parseDouble(tp.toString()));
} catch(Exception ignore){}
}
}
// 인라인 스타일 상수
String LBL = "border:1px solid #999;background-color:#f5f5f5;font-weight:bold;text-align:center;padding:2px 4px;height:26px;vertical-align:middle;width:90px;";
String VAL = "border:1px solid #999;padding:2px 8px;height:26px;vertical-align:middle;text-align:left;";
String HDR = "border:1px solid #999;background-color:#ebf1de;font-weight:bold;padding:5px 8px;text-align:center;vertical-align:middle;";
String CELL = "border:1px solid #999;padding:5px 8px;text-align:center;vertical-align:middle;";
String TOT_HDR = "border:1px solid #999;background-color:#dce6f1;font-weight:bold;padding:5px 8px;text-align:center;vertical-align:middle;";
String TOT_VAL = "border:1px solid #999;background-color:#ffffcc;font-weight:bold;text-align:right;font-size:14px;padding:5px 8px;vertical-align:middle;";
String ITH = "border:1px solid #999;background-color:#f5f5f5;font-weight:bold;text-align:center;padding:1px 2px;height:30px;vertical-align:middle;font-size:11px;";
String ITC = "border:1px solid #999;text-align:center;padding:1px 2px;height:35px;vertical-align:middle;font-size:11px;";
String ITL = "border:1px solid #999;text-align:left;padding:1px 4px;height:35px;vertical-align:middle;font-size:11px;";
String ITR = "border:1px solid #999;text-align:right;padding:1px 4px;height:35px;vertical-align:middle;font-size:11px;";
String BORDER_LR = "border-left:2px solid #000;border-right:2px solid #000;";
html.append("<div style='font-family:Malgun Gothic,맑은 고딕,sans-serif;font-size:12px;'>");
// 제목
html.append("<div style='text-align:center;font-size:24px;font-weight:bold;letter-spacing:20px;padding:12px 0;border-bottom:2px solid #000;'>구 매 품 의 서</div>");
// 상단 기본정보
html.append("<table style='width:100%;border-collapse:collapse;").append(BORDER_LR).append("border-bottom:1px solid #000;'>");
html.append("<tr><td style='").append(LBL).append("'>품의번호</td><td style='").append(VAL).append("' colspan='3'>").append(escapeHtml(proposalNo)).append("</td></tr>");
html.append("<tr><td style='").append(LBL).append("'>작성일자</td><td style='").append(VAL).append("' colspan='3'>").append(escapeHtml(regdate)).append("</td></tr>");
html.append("<tr><td style='").append(LBL).append("'>기안부서</td><td style='").append(VAL).append("' colspan='3'>").append(escapeHtml(deptName)).append("</td></tr>");
html.append("<tr><td style='").append(LBL).append("'>기 안 자</td><td style='").append(VAL).append("' colspan='3'>").append(escapeHtml(writerOnly)).append("</td></tr>");
html.append("</table>");
// 개정 정보
html.append("<div style='text-align:right;font-size:11px;color:#ff0000;padding:5px 10px;border-bottom:1px solid #000;").append(BORDER_LR).append("'>[구매품의서 개정 : 22.05.17]</div>");
// 중간 정보 섹션
html.append("<table style='width:100%;border-collapse:collapse;").append(BORDER_LR).append("border-bottom:1px solid #000;'>");
html.append("<tr>");
html.append("<th rowspan='2' style='").append(HDR).append("width:50px;'>구<br/>분</th>");
html.append("<th style='").append(HDR).append("'>부 서</th>");
html.append("<th style='").append(HDR).append("'>소속팀</th>");
html.append("<th style='").append(HDR).append("'>날 짜</th>");
html.append("<td style='").append(CELL).append("'>").append(escapeHtml(regdate)).append("</td>");
html.append("<th style='").append(TOT_HDR).append("'>총 합 계</th>");
html.append("</tr>");
html.append("<tr style='height:50px;'>");
html.append("<td style='").append(CELL).append("'>").append(escapeHtml(deptName)).append("</td>");
html.append("<td style='").append(CELL).append("'>").append(escapeHtml(deptName)).append("</td>");
html.append("<td style='").append(HDR).append("'>기 안 자</td>");
html.append("<td style='").append(CELL).append("'>").append(escapeHtml(writerOnly)).append("</td>");
html.append("<td style='").append(TOT_VAL).append("'>").append(formatNumber(totalAmount)).append("</td>");
html.append("</tr></table>");
// 품목 테이블 (12컬럼 - JSP와 동일)
html.append("<table style='width:100%;border-collapse:collapse;").append(BORDER_LR).append("border-bottom:1px solid #000;'>");
html.append("<thead><tr>");
html.append("<th style='").append(ITH).append("width:30px;'>No.</th>");
html.append("<th style='").append(ITH).append("width:60px;'>목 적</th>");
html.append("<th style='").append(ITH).append("width:85px;'>제 품 명</th>");
html.append("<th style='").append(ITH).append("width:85px;'>부 품 명</th>");
html.append("<th style='").append(ITH).append("width:85px;'>품 명</th>");
html.append("<th style='").append(ITH).append("width:65px;'>규 격</th>");
html.append("<th style='").append(ITH).append("width:65px;'>업 체 명</th>");
html.append("<th style='").append(ITH).append("width:75px;'>입고요청일</th>");
html.append("<th style='").append(ITH).append("width:45px;'>수량</th>");
html.append("<th style='").append(ITH).append("width:40px;'>단위</th>");
html.append("<th style='").append(ITH).append("width:65px;'>단가</th>");
html.append("<th style='").append(ITH).append("width:80px;'>합 계</th>");
html.append("</tr></thead><tbody>");
if(partList != null && !partList.isEmpty()){
int idx = 1;
for(Map partInfo : partList){
html.append("<tr>");
html.append("<td style='").append(ITC).append("'>").append(idx++).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("REMARK")))).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PROJECT_PRODUCT_NAME")))).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("COMPONENT_PART_NAME")))).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("DISPLAY_PART_NAME")))).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("DISPLAY_SPEC")))).append("</td>");
html.append("<td style='").append(ITL).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("VENDOR_NAME")))).append("</td>");
html.append("<td style='").append(ITC).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("DELIVERY_REQUEST_DATE_TITLE")))).append("</td>");
html.append("<td style='").append(ITR).append("'>").append(formatNumber(CommonUtils.checkNull(partInfo.get("QTY")))).append("</td>");
html.append("<td style='").append(ITC).append("'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("UNIT_TITLE")))).append("</td>");
html.append("<td style='").append(ITR).append("'>").append(formatDecimalNumber(CommonUtils.checkNull(partInfo.get("UNIT_PRICE")))).append("</td>");
html.append("<td style='").append(ITR).append("'>").append(formatDecimalNumber(CommonUtils.checkNull(partInfo.get("TOTAL_PRICE")))).append("</td>");
html.append("</tr>");
}
} else {
html.append("<tr><td style='").append(ITC).append("height:100px;' colspan='12'>등록된 품목이 없습니다.</td></tr>");
}
html.append("</tbody></table>");
html.append("</div>");
return html.toString();
}
@@ -3503,48 +3522,54 @@ public class ApprovalService {
h.append("<td class='total-value'>").append(formatNumber(totalAmount)).append("</td>");
h.append("</tr></table></div>");
// 품목 테이블
// 품목 테이블 (12컬럼 - JSP와 동일)
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("<th style='width:30px;'>No.</th>");
h.append("<th style='width:80px;'>목 적</th>");
h.append("<th style='width:100px;'>제 품 명</th>");
h.append("<th style='width:110px;'>부 품 명</th>");
h.append("<th style='width:110px;'> 명</th>");
h.append("<th style='width:80px;'>규 격</th>");
h.append("<th style='width:80px;'>업 체 명</th>");
h.append("<th style='width:90px;'>입고요청일</th>");
h.append("<th style='width:50px;'>수량</th>");
h.append("<th style='width:45px;'>단위</th>");
h.append("<th style='width:70px;'>단가</th>");
h.append("<th style='width:85px;'>합 계</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 projectProductName = CommonUtils.checkNull(part.get("PROJECT_PRODUCT_NAME"));
String componentPartName = CommonUtils.checkNull(part.get("COMPONENT_PART_NAME"));
String displayPartName = CommonUtils.checkNull(part.get("DISPLAY_PART_NAME"));
String displaySpec = CommonUtils.checkNull(part.get("DISPLAY_SPEC"));
String vendorName = CommonUtils.checkNull(part.get("VENDOR_NAME"));
String deliveryDate = CommonUtils.checkNull(part.get("DELIVERY_REQUEST_DATE_TITLE"));
String qty = CommonUtils.checkNull(part.get("QTY"));
String unit = CommonUtils.checkNull(part.get("UNIT_NAME"));
String unitTitle = CommonUtils.checkNull(part.get("UNIT_TITLE"));
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(projectProductName)).append("</td>");
h.append("<td class='text-left'>").append(escapeHtml(componentPartName)).append("</td>");
h.append("<td class='text-left'>").append(escapeHtml(displayPartName)).append("</td>");
h.append("<td class='text-left'>").append(escapeHtml(displaySpec)).append("</td>");
h.append("<td class='text-left'>").append(escapeHtml(vendorName)).append("</td>");
h.append("<td>").append(escapeHtml(deliveryDate)).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("<td>").append(escapeHtml(unitTitle)).append("</td>");
h.append("<td class='text-right'>").append(formatDecimalNumber(unitPrice)).append("</td>");
h.append("<td class='text-right'>").append(formatDecimalNumber(totalPrice)).append("</td>");
h.append("</tr>");
}
} else {
h.append("<tr><td colspan='9' style='height:100px;'>등록된 품목이 없습니다.</td></tr>");
h.append("<tr><td colspan='12' style='height:100px;'>등록된 품목이 없습니다.</td></tr>");
}
h.append("</tbody></table></div>");
@@ -3571,6 +3596,21 @@ public class ApprovalService {
}
}
/**
* 숫자 천단위 콤마 + 소수점 2자리 포맷 (단가, 합계용)
*/
private String formatDecimalNumber(Object value){
if(value == null) return "0.00";
try {
String strVal = value.toString().replaceAll("[^0-9.\\-]", "");
if(strVal.isEmpty()) return "0.00";
double num = Double.parseDouble(strVal);
return String.format("%,.2f", num);
} catch(Exception e){
return value.toString();
}
}
/**
* HTML 이스케이프 (XSS 방지)
*/