diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp index b83babe..389ac82 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp @@ -194,6 +194,13 @@ $(document).ready(function(){ fn_search(); }); +// 콤마 추가 함수 +function addComma(num) { + if(!num) return ''; + var regexp = /\B(?=(\d{3})+(?!\d))/g; + return num.toString().replace(regexp, ','); +} + var columns = [ {title:'EST_OBJID' ,field:'EST_OBJID' ,visible:false}, {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true, @@ -288,11 +295,19 @@ var columns = [ } }, // {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '견적단가', field : 'EST_PRICE' }, - {headerHozAlign : 'center', hozAlign : 'center', width : '130', title : '견적공급가액', field : 'EST_SUPPLY_PRICE', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false,}, + {headerHozAlign : 'center', hozAlign : 'center', width : '130', title : '견적공급가액', field : 'EST_TOTAL_AMOUNT', + formatter: function(cell, formatterParams, onRendered){ + var value = fnc_checkNull(cell.getValue()); + if(value === '' || value === '0') return ''; + return addComma(value); + } }, - {headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '견적원화환산공급가액', field : 'EXC_EST_SUPPLY_PRICE', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false,}, + {headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '견적원화환산공급가액', field : 'EST_TOTAL_AMOUNT_KRW', + formatter: function(cell, formatterParams, onRendered){ + var value = fnc_checkNull(cell.getValue()); + if(value === '' || value === '0') return ''; + return addComma(value); + } }, {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '견적환종', field : 'CONTRACT_CURRENCY_NAME' }, {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '견적환율', field : 'EXCHANGE_RATE' }, diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp index b65a108..0766ebb 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp @@ -380,6 +380,7 @@ } var item = { + objId: $row.find(".item-objid").val(), // 기존 품목 OBJID (수정 시 유지) partObjId: $row.find(".item-part-objid").val(), partNo: $row.find(".item-part-no").val() ? $row.find(".item-part-no").val().trim() : "", partName: $row.find(".item-part-name").val() ? $row.find(".item-part-name").val().trim() : "", @@ -439,6 +440,7 @@ (function() { var itemId = 'item_' + itemCounter++; // 대소문자 모두 시도 + var savedItemObjId = "<%= item.get("OBJID") != null ? item.get("OBJID") : (item.get("objid") != null ? item.get("objid") : "") %>"; var savedPartObjId = "<%= item.get("PART_OBJID") != null ? item.get("PART_OBJID") : (item.get("part_objid") != null ? item.get("part_objid") : "") %>"; var savedPartNo = "<%= item.get("PART_NO") != null ? item.get("PART_NO") : (item.get("part_no") != null ? item.get("part_no") : "") %>"; var savedPartName = "<%= item.get("PART_NAME") != null ? item.get("PART_NAME") : (item.get("part_name") != null ? item.get("part_name") : "") %>"; @@ -455,6 +457,7 @@ html += ''; + html += ''; html += ''; html += ''; html += ''; @@ -1147,6 +1150,7 @@ html += ''; + html += ''; html += ''; html += ''; html += ''; diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp index e6f86b7..e13c313 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp @@ -222,6 +222,8 @@ textarea { // 전역 변수로 저장 (데이터 로드 시 설정됨) var g_contractObjId = "<%=objId%>"; var g_templateObjId = "<%=templateObjId%>"; +var g_exchangeRate = 1; // 환율 (기본값 1) +var g_currencyName = "KRW"; // 통화명 (기본값 원화) $(function(){ @@ -256,6 +258,7 @@ $(function(){ // 금액 자동 계산 $(document).on("change keyup", ".item-qty, .item-price", function(){ fn_calculateAmount($(this).closest("tr")); + fn_calculateTotal(); // 합계 재계산 }); // 콤마 자동 추가 (동적 요소 포함) @@ -284,6 +287,9 @@ $(function(){ // 데이터 로드 if("<%=objId%>" !== "" && "<%=objId%>" !== "-1") { fn_loadData(); + } else { + // 초기 로드 시 합계 계산 + fn_calculateTotal(); } }); @@ -298,6 +304,32 @@ function fn_calculateAmount(row) { } } +// 합계 계산 (금액 컬럼의 총합) +function fn_calculateTotal() { + var total = 0; + + // 품목 행만 순회 (계 행, 원화환산 행, 비고 행 제외) + $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(){ + var amount = $(this).find(".item-amount").val() || "0"; + // 콤마와 ₩ 기호 제거 후 숫자로 변환 + amount = amount.replace(/,/g, "").replace(/₩/g, ""); + var numAmount = parseInt(amount) || 0; + total += numAmount; + }); + + // 합계 행에 표시 + $("#totalAmount").text(addComma(total)); + + // 원화환산 금액 계산 및 표시 + fn_calculateTotalKRW(total); +} + +// 원화환산 공급가액 계산 +function fn_calculateTotalKRW(total) { + var totalKRW = total * g_exchangeRate; + $("#totalAmountKRW").text(addComma(Math.round(totalKRW))); +} + // 콤마 추가 function addComma(num) { var regexp = /\B(?=(\d{3})+(?!\d))/g; @@ -306,10 +338,9 @@ function addComma(num) { // 행 추가 함수 function fn_addItemRow() { - // 비고 행 제외하고 마지막 품목 행 찾기 - var $lastRow = $("#itemsTableBody tr").not(":last"); - var lastRowIndex = $lastRow.length; - var nextNo = lastRowIndex + 1; + // 계 행, 원화환산 행, 비고 행 제외하고 품목 행 개수 계산 + var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row"); + var nextNo = itemRows.length + 1; // 새 행 생성 var newRow = '' + @@ -323,8 +354,11 @@ function fn_addItemRow() { '' + ''; - // 비고 행 바로 위에 추가 (이벤트는 이미 document에 바인딩되어 있으므로 별도 바인딩 불필요) - $("#itemsTableBody tr:last").before(newRow); + // 계 행 바로 위에 추가 + $(".total-row").before(newRow); + + // 합계 재계산 + fn_calculateTotal(); } // 데이터 로드 @@ -338,6 +372,10 @@ function fn_loadData() { dataType: "json", success: function(data) { if(data && data.estimate) { + // 환율 정보 저장 + g_exchangeRate = parseFloat(data.estimate.EXCHANGE_RATE || "1"); + g_currencyName = data.estimate.CONTRACT_CURRENCY_NAME || "KRW"; + // 데이터 바인딩 $("#executor").val(data.estimate.EXECUTOR || ""); $("#recipient").val(data.estimate.RECIPIENT || ""); @@ -362,7 +400,33 @@ function fn_loadData() { itemsHtml += ''; itemsHtml += ''; } + + // 계 행 추가 + itemsHtml += ''; + itemsHtml += '계'; + itemsHtml += '0'; + itemsHtml += ''; + itemsHtml += ''; + + // 원화환산 공급가액 행 추가 + itemsHtml += ''; + itemsHtml += '원화환산 공급가액 (KRW)'; + itemsHtml += '0'; + itemsHtml += ''; + itemsHtml += ''; + + // 비고 행 추가 + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += '
<비고>
'; + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += ''; + $("#itemsTableBody").html(itemsHtml); + + // 합계 계산 + fn_calculateTotal(); } // 비고 로드 @@ -397,6 +461,10 @@ function fn_loadTemplateData(templateObjId){ g_contractObjId = contractObjId; } + // 환율 정보 저장 + g_exchangeRate = parseFloat(template.EXCHANGE_RATE || template.exchange_rate || template.exchangeRate || "1"); + g_currencyName = template.CONTRACT_CURRENCY_NAME || template.contract_currency_name || template.contractCurrencyName || "KRW"; + // 대문자/소문자 모두 지원 var executor = template.EXECUTOR || template.executor || ""; var recipient = template.RECIPIENT || template.recipient || ""; @@ -446,6 +514,31 @@ function fn_loadTemplateData(templateObjId){ row.append(''); $("#itemsTableBody").append(row); }); + + // 계 행 추가 + var totalRow = $(""); + totalRow.append('계'); + totalRow.append('0'); + totalRow.append(''); + $("#itemsTableBody").append(totalRow); + + // 원화환산 공급가액 행 추가 + var totalKRWRow = $(""); + totalKRWRow.append('원화환산 공급가액 (KRW)'); + totalKRWRow.append('0'); + totalKRWRow.append(''); + $("#itemsTableBody").append(totalKRWRow); + + // 비고 행 추가 + var remarksRow = $(""); + remarksRow.append('' + + '
<비고>
' + + '' + + ''); + $("#itemsTableBody").append(remarksRow); + + // 합계 계산 + fn_calculateTotal(); } } else { console.error("데이터 로드 실패:", data); @@ -462,7 +555,8 @@ function fn_loadTemplateData(templateObjId){ // 저장 function fn_save() { var items = []; - $("#itemsTableBody tr").each(function(idx) { + // 계 행, 원화환산 행, 비고 행 제외하고 품목 행만 저장 + $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(idx) { var row = $(this); var unitPrice = row.find(".item-price").val() || ""; var amount = row.find(".item-amount").val() || ""; @@ -488,6 +582,10 @@ function fn_save() { return; } + // 합계 계산 (콤마 제거한 순수 숫자) + var totalAmount = $("#totalAmount").text().replace(/,/g, ""); + var totalAmountKRW = $("#totalAmountKRW").text().replace(/,/g, ""); + var formData = { objId: contractObjId, template_type: "1", @@ -496,6 +594,8 @@ function fn_save() { estimate_no: $("#estimate_no").val(), contact_person: $("#contact_person").val(), greeting_text: $("#greeting_text").val(), + total_amount: totalAmount, // 합계 + total_amount_krw: totalAmountKRW, // 원화환산 공급가액 note1: $("#note1").val(), note2: $("#note2").val(), note3: $("#note3").val(), @@ -637,7 +737,20 @@ function fn_save() { - + + + 계 + 0 + + + + + 원화환산 공급가액 (KRW) + 0 + + + +
<비고>
diff --git a/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp b/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp index d40aa65..9e0c037 100644 --- a/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp @@ -57,7 +57,7 @@ $(document).ready(function(){ return false; } else { var contractObjId = fnc_checkNull(selectedData[0].OBJID); - var popup_width = 1000; + var popup_width = 1400; var popup_height = 450; var params = "?actionType=regist&contractObjId="+contractObjId; var url = "/contractMgmt/orderRegistFormPopup.do"+params; @@ -100,36 +100,55 @@ var columns = [ }, {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '주문유형', field : 'CATEGORY_NAME' }, {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '제품구분', field : 'PRODUCT_NAME' }, - {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '국내/해외', field : 'AREA_NAME' }, + {headerHozAlign : 'center', hozAlign : 'center', width : '85', title : '국내/해외', field : 'AREA_NAME' }, {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '접수일', field : 'RECEIPT_DATE' }, {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' }, {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' }, - {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' }, - {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품명', field : 'PART_NAME' }, - {headerHozAlign : 'center', hozAlign : 'center', width : '150', title : 'S/N', field : 'SERIAL_NO', + {headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' }, + //품번, 품명 추가 시 사용 +// {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' }, +// {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품명', field : 'PART_NAME' }, + + //S/N 추가 시 사용 +// {headerHozAlign : 'center', hozAlign : 'center', width : '150', title : 'S/N', field : 'SERIAL_NO', +// formatter: function(cell, formatterParams, onRendered){ +// var value = fnc_checkNull(cell.getValue()); +// if(value === '') return ''; +// +// // 쉼표로 구분된 S/N 개수 계산 +// var serialNumbers = value.split(',').map(function(s){ return s.trim(); }).filter(function(s){ return s !== ''; }); +// var count = serialNumbers.length; +// +// if(count === 0) return ''; +// if(count === 1) return '' + serialNumbers[0] + ''; +// +// // 2개 이상이면 "첫번째 외 N개" 형식 +// var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개'; +// return '' + displayText + ''; +// }, +// cellClick:function(e, cell){ +// var serialNo = fnc_checkNull(cell.getData().SERIAL_NO); +// fn_showSerialNoPopup(serialNo); +// } +// }, + {headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '요청납기', field : 'EARLIEST_DUE_DATE', formatter: function(cell, formatterParams, onRendered){ - var value = fnc_checkNull(cell.getValue()); - if(value === '') return ''; + var dueDate = fnc_checkNull(cell.getValue()); + var otherCount = fnc_checkNull(cell.getData().OTHER_DUE_DATE_COUNT); - // 쉼표로 구분된 S/N 개수 계산 - var serialNumbers = value.split(',').map(function(s){ return s.trim(); }).filter(function(s){ return s !== ''; }); - var count = serialNumbers.length; + if(dueDate === '') return ''; - if(count === 0) return ''; - if(count === 1) return '' + serialNumbers[0] + ''; + // 다른 납기가 있으면 "날짜 외 N건" 형식으로 표시 + if(otherCount && parseInt(otherCount) > 0){ + return dueDate + ' 외 ' + otherCount + '건'; + } - // 2개 이상이면 "첫번째 외 N개" 형식 - var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개'; - return '' + displayText + ''; - }, - cellClick:function(e, cell){ - var serialNo = fnc_checkNull(cell.getData().SERIAL_NO); - fn_showSerialNoPopup(serialNo); + return dueDate; } - }, - {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '수량', field : 'QUANTITY' }, - {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '요청납기', field : 'DUE_DATE' }, - {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객요청사항', field : 'CUSTOMER_REQUEST' }, + }, +// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '수량', field : 'QUANTITY' }, +// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '요청납기', field : 'DUE_DATE' }, +// {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객요청사항', field : 'CUSTOMER_REQUEST' }, {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '수주상태', field : 'CONTRACT_RESULT_NAME' }, {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '발주번호', field : 'PO_NO' }, @@ -142,21 +161,23 @@ var columns = [ } }, - {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '단가', field : 'ORDER_UNIT_PRICE', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, - }, - {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '공급가액', field : 'ORDER_SUPPLY_PRICE', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, - }, - {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '부가세', field : 'ORDER_VAT', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, - }, - {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '총액', field : 'ORDER_TOTAL_AMOUNT', - formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, - }, - - {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '환종', field : 'CONTRACT_CURRENCY_NAME' }, - {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '환율', field : 'EXCHANGE_RATE' }, +// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '단가', field : 'ORDER_UNIT_PRICE', +// formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, +// }, + {headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '수주공급가액', field : 'ORDER_SUPPLY_PRICE_SUM', + formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, + }, + {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '수주부가세', field : 'ORDER_VAT_SUM', + formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, + }, + {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '수주총액', field : 'ORDER_TOTAL_AMOUNT_SUM', + formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, + }, + {headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '수주원화총액', field : 'ORDER_TOTAL_AMOUNT_KRW', + formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,}, + }, + {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '견적환종', field : 'CONTRACT_CURRENCY_NAME' }, + {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '견적환율', field : 'EXCHANGE_RATE' }, ]; //var grid; @@ -241,7 +262,7 @@ function fn_FileRegist(objId, docType, docTypeName){ //영업활동등록 상세 function fn_projectConceptDetail(objId){ - var popup_width = 1000; + var popup_width = 1400; var popup_height = 560; var url = "/contractMgmt/estimateRegistFormPopup.do?objId="+objId; diff --git a/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp index b60c8a5..0903ac2 100644 --- a/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/orderRegistFormPopup.jsp @@ -17,12 +17,48 @@ .fileListscrollTbody td { font-size: 11px !important; } + + /* 품목 테이블 스타일 */ + #itemListTable { + width: 100%; + border-collapse: collapse; + } + + #itemListTable th, + #itemListTable td { + border: 1px solid #ddd; + padding: 8px; + text-align: center; + } + + #itemListTable th { + background-color: #f5f5f5; + font-weight: bold; + } + + #itemListTable input[type="text"], + #itemListTable input[type="number"] { + width: 100%; + box-sizing: border-box; + } @@ -175,6 +323,7 @@ +
@@ -184,8 +333,9 @@
+
- 수주 정보입력 + 수주 기본정보
@@ -216,53 +366,25 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + @@ -271,6 +393,58 @@
- -
- - - -
- - - -
+
+ + +
+ 품목정보 (견적요청에서 자동 로드) +
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No품번품명S/N수주수량 *수주단가 *수주공급가액수주부가세수주총액-
+ 견적요청에 등록된 품목이 자동으로 표시됩니다. +
+
+
diff --git a/database/add_order_columns_to_contract_item.sql b/database/add_order_columns_to_contract_item.sql new file mode 100644 index 0000000..c66d935 --- /dev/null +++ b/database/add_order_columns_to_contract_item.sql @@ -0,0 +1,29 @@ +-- CONTRACT_ITEM 테이블에 수주 관련 컬럼 추가 + +-- 수주 수량 +ALTER TABLE CONTRACT_ITEM +ADD COLUMN IF NOT EXISTS ORDER_QUANTITY VARCHAR(50); + +-- 수주 단가 +ALTER TABLE CONTRACT_ITEM +ADD COLUMN IF NOT EXISTS ORDER_UNIT_PRICE VARCHAR(50); + +-- 수주 공급가액 +ALTER TABLE CONTRACT_ITEM +ADD COLUMN IF NOT EXISTS ORDER_SUPPLY_PRICE VARCHAR(50); + +-- 수주 부가세 +ALTER TABLE CONTRACT_ITEM +ADD COLUMN IF NOT EXISTS ORDER_VAT VARCHAR(50); + +-- 수주 총액 +ALTER TABLE CONTRACT_ITEM +ADD COLUMN IF NOT EXISTS ORDER_TOTAL_AMOUNT VARCHAR(50); + +-- 컬럼 코멘트 추가 +COMMENT ON COLUMN CONTRACT_ITEM.ORDER_QUANTITY IS '수주 수량'; +COMMENT ON COLUMN CONTRACT_ITEM.ORDER_UNIT_PRICE IS '수주 단가'; +COMMENT ON COLUMN CONTRACT_ITEM.ORDER_SUPPLY_PRICE IS '수주 공급가액'; +COMMENT ON COLUMN CONTRACT_ITEM.ORDER_VAT IS '수주 부가세 (10%)'; +COMMENT ON COLUMN CONTRACT_ITEM.ORDER_TOTAL_AMOUNT IS '수주 총액 (공급가액 + 부가세)'; + diff --git a/database/add_total_amount_column.sql b/database/add_total_amount_column.sql new file mode 100644 index 0000000..95837f9 --- /dev/null +++ b/database/add_total_amount_column.sql @@ -0,0 +1,14 @@ +-- 견적서 템플릿 테이블에 합계 관련 컬럼 추가 + +-- ESTIMATE_TEMPLATE 테이블에 TOTAL_AMOUNT 컬럼 추가 +ALTER TABLE ESTIMATE_TEMPLATE +ADD COLUMN IF NOT EXISTS TOTAL_AMOUNT VARCHAR(50); + +-- ESTIMATE_TEMPLATE 테이블에 TOTAL_AMOUNT_KRW 컬럼 추가 (원화환산공급가액) +ALTER TABLE ESTIMATE_TEMPLATE +ADD COLUMN IF NOT EXISTS TOTAL_AMOUNT_KRW VARCHAR(50); + +-- 컬럼 코멘트 추가 +COMMENT ON COLUMN ESTIMATE_TEMPLATE.TOTAL_AMOUNT IS '견적서 총 합계 금액 (품목 금액의 합, 콤마 포맷팅 포함)'; +COMMENT ON COLUMN ESTIMATE_TEMPLATE.TOTAL_AMOUNT_KRW IS '원화환산 공급가액 (합계 × 환율, 콤마 포맷팅 포함)'; + diff --git a/src/com/pms/salesmgmt/controller/ContractMgmtController.java b/src/com/pms/salesmgmt/controller/ContractMgmtController.java index 8f34e36..6983be0 100644 --- a/src/com/pms/salesmgmt/controller/ContractMgmtController.java +++ b/src/com/pms/salesmgmt/controller/ContractMgmtController.java @@ -2000,6 +2000,9 @@ public class ContractMgmtController { PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN); paramMap.put("userId", person.getUserId()); + // 합계 정보 로그 (디버깅용) + System.out.println("견적서 저장 - 합계: " + paramMap.get("total_amount") + ", 원화환산: " + paramMap.get("total_amount_krw")); + contractMgmtService.saveEstimateTemplate(request, paramMap); resultMap.put("result", "success"); @@ -2168,4 +2171,29 @@ public class ContractMgmtController { } return "/ajax/ajaxResult"; } + + /** + * 계약 품목 조회 (AJAX) + * @param request + * @param paramMap - contractObjId + * @return + */ + @ResponseBody + @RequestMapping(value="/contractMgmt/getContractItems.do", method=RequestMethod.POST) + public Map getContractItems(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + + try { + List items = contractMgmtService.getContractItems(paramMap); + resultMap.put("result", "success"); + resultMap.put("items", items); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("result", "error"); + resultMap.put("message", e.getMessage()); + } + + return resultMap; + } } diff --git a/src/com/pms/salesmgmt/mapper/contractMgmt.xml b/src/com/pms/salesmgmt/mapper/contractMgmt.xml index 45fd03a..7e796db 100644 --- a/src/com/pms/salesmgmt/mapper/contractMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/contractMgmt.xml @@ -521,6 +521,19 @@ ,A.APPROVAL_OBJID ,A.ROUTE_OBJID ,(SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID order by regdate desc limit 1) AS EST_OBJID + -- 최근 차수 견적서 합계 정보 + ,(SELECT TOTAL_AMOUNT FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT + ,(SELECT TOTAL_AMOUNT_KRW FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT_KRW + -- 수주 합계 정보 (CONTRACT_MGMT 테이블에 저장된 값 사용) + ,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM + ,T.ORDER_VAT AS ORDER_VAT_SUM + ,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM + ,CASE + WHEN T.ORDER_TOTAL_AMOUNT IS NOT NULL AND T.ORDER_TOTAL_AMOUNT != '' + AND T.EXCHANGE_RATE IS NOT NULL AND T.EXCHANGE_RATE != '' + THEN CAST(T.ORDER_TOTAL_AMOUNT AS NUMERIC) * CAST(T.EXCHANGE_RATE AS NUMERIC) + ELSE 0 + END AS ORDER_TOTAL_AMOUNT_KRW -- 품목 정보 요약 ,( WITH item_info AS ( @@ -3820,62 +3833,72 @@ ORDER BY ASM.SUPPLY_NAME @@ -3944,6 +3967,8 @@ ORDER BY ASM.SUPPLY_NAME NOTE2, NOTE3, NOTE4, + TOTAL_AMOUNT, + TOTAL_AMOUNT_KRW, WRITER, REGDATE, CHG_USER_ID, @@ -3964,6 +3989,8 @@ ORDER BY ASM.SUPPLY_NAME #{note2}, #{note3}, #{note4}, + #{total_amount}, + #{total_amount_krw}, #{writer}, NOW(), #{chg_user_id}, @@ -3987,6 +4014,8 @@ ORDER BY ASM.SUPPLY_NAME NOTE2 = #{note2}, NOTE3 = #{note3}, NOTE4 = #{note4}, + TOTAL_AMOUNT = #{total_amount}, + TOTAL_AMOUNT_KRW = #{total_amount_krw}, CHG_USER_ID = #{chg_user_id}, CHGDATE = NOW() WHERE @@ -4051,33 +4080,38 @@ ORDER BY ASM.SUPPLY_NAME @@ -4240,14 +4274,70 @@ ORDER BY ASM.SUPPLY_NAME CONTRACT_RESULT = #{contract_result}, PO_NO = #{po_no}, ORDER_DATE = #{order_date}, - QUANTITY = #{quantity}, - ORDER_UNIT_PRICE = #{unit_price}, - ORDER_SUPPLY_PRICE = #{supply_price}, - ORDER_VAT = #{vat}, - ORDER_TOTAL_AMOUNT = #{total_amount}, CONTRACT_CURRENCY = #{contract_currency}, - EXCHANGE_RATE = #{exchange_rate} - WHERE OBJID = #{contractObjId} + EXCHANGE_RATE = #{exchange_rate}, + ORDER_SUPPLY_PRICE = #{order_supply_price}, + ORDER_VAT = #{order_vat}, + ORDER_TOTAL_AMOUNT = #{order_total_amount} + WHERE OBJID = #{objId} + + + + + + + + UPDATE CONTRACT_ITEM + SET + ORDER_QUANTITY = #{orderQuantity}, + ORDER_UNIT_PRICE = #{orderUnitPrice}, + ORDER_SUPPLY_PRICE = #{orderSupplyPrice}, + ORDER_VAT = #{orderVat}, + ORDER_TOTAL_AMOUNT = #{orderTotalAmount} + WHERE OBJID = #{contractItemObjId} + + + INSERT INTO CONTRACT_ITEM ( + OBJID, + CONTRACT_OBJID, + SEQ, + PART_OBJID, + PART_NO, + PART_NAME, + QUANTITY, + DUE_DATE, + CUSTOMER_REQUEST, + RETURN_REASON, + REGDATE, + WRITER, + STATUS + ) VALUES ( + #{objId}, + #{contractObjId}, + #{seq}, + #{partObjId}, + #{partNo}, + #{partName}, + #{quantity}, + #{dueDate}, + #{customerRequest}, + #{returnReason}, + NOW(), + #{writer}, + 'ACTIVE' + ) + ON CONFLICT (OBJID) DO UPDATE + SET + SEQ = #{seq}, + PART_OBJID = #{partObjId}, + PART_NO = #{partNo}, + PART_NAME = #{partName}, + QUANTITY = #{quantity}, + DUE_DATE = #{dueDate}, + CUSTOMER_REQUEST = #{customerRequest}, + RETURN_REASON = #{returnReason}, + CHGDATE = NOW(), + CHG_USER_ID = #{writer}, + STATUS = 'ACTIVE' + + + + + INSERT INTO CONTRACT_ITEM_SERIAL ( + OBJID, + ITEM_OBJID, + SEQ, + SERIAL_NO, + REGDATE, + WRITER, + STATUS + ) VALUES ( + #{objId}, + #{itemObjId}, + #{seq}, + #{serialNo}, + NOW(), + #{writer}, + 'ACTIVE' + ) + ON CONFLICT (ITEM_OBJID, SERIAL_NO) DO UPDATE + SET + SEQ = #{seq}, + STATUS = 'ACTIVE' + + + + + UPDATE CONTRACT_ITEM + SET STATUS = 'INACTIVE', + CHGDATE = NOW(), + CHG_USER_ID = #{userId} + WHERE CONTRACT_OBJID = #{contractObjId} + AND STATUS = 'ACTIVE' + + AND OBJID NOT IN + + #{objId} + + + + + UPDATE CONTRACT_ITEM SET STATUS = 'INACTIVE', diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java index ce8bdc4..323cb51 100644 --- a/src/com/pms/salesmgmt/service/ContractMgmtService.java +++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java @@ -12,14 +12,19 @@ package com.pms.salesmgmt.service; import java.io.File; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.SqlSession; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -1387,14 +1392,20 @@ public class ContractMgmtService { PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN); String userId = person.getUserId(); - String objId = CommonUtils.checkNull(paramMap.get("objId")); // CONTRACT_OBJID - String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId")); // 기존 템플릿 수정 시 - String templateType = CommonUtils.checkNull(paramMap.get("template_type")); - String itemsJson = CommonUtils.checkNull(paramMap.get("items")); - String categoriesJson = CommonUtils.checkNull(paramMap.get("categories")); - - paramMap.put("writer", userId); - paramMap.put("chg_user_id", userId); + String objId = CommonUtils.checkNull(paramMap.get("objId")); // CONTRACT_OBJID + String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId")); // 기존 템플릿 수정 시 + String templateType = CommonUtils.checkNull(paramMap.get("template_type")); + String itemsJson = CommonUtils.checkNull(paramMap.get("items")); + String categoriesJson = CommonUtils.checkNull(paramMap.get("categories")); + + // 합계 정보 (일반 견적서용) + String totalAmount = CommonUtils.checkNull(paramMap.get("total_amount")); + String totalAmountKrw = CommonUtils.checkNull(paramMap.get("total_amount_krw")); + + paramMap.put("writer", userId); + paramMap.put("chg_user_id", userId); + paramMap.put("total_amount", totalAmount); + paramMap.put("total_amount_krw", totalAmountKrw); // 기존 템플릿 수정인지 신규 작성인지 확인 // 중요: templateObjId가 명시적으로 있을 때만 수정, 없으면 항상 신규 작성 @@ -1790,6 +1801,32 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate, return resultMap != null ? resultMap : new HashMap(); } + /** + * 계약 품목 조회 + * @param paramMap - contractObjId + * @return + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public List getContractItems(Map paramMap){ + SqlSession sqlSession = null; + List items = new ArrayList(); + + try{ + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + items = sqlSession.selectList("contractMgmt.getContractItems", paramMap); + + // 대문자 변환 + items = CommonUtils.keyChangeUpperList(items); + + }catch(Exception e){ + e.printStackTrace(); + }finally{ + if(sqlSession != null) sqlSession.close(); + } + + return items; + } + @SuppressWarnings({ "rawtypes", "unchecked" }) public Map saveOrderInfo(HttpServletRequest request, Map paramMap){ Map resultMap = new HashMap(); @@ -1800,9 +1837,61 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate, PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN); paramMap.put("writer", person.getUserId()); - String contract_objid= CommonUtils.checkNull(paramMap.get("objId")); - paramMap.put("objId", contract_objid); - int cnt = sqlSession.update("contractMgmt.updateOrderInfo", paramMap); + String contract_objid= CommonUtils.checkNull(paramMap.get("contractObjId")); + paramMap.put("objId", contract_objid); + + // 품목별 수주 정보 업데이트 및 합계 계산 + String itemsJson = CommonUtils.checkNull(paramMap.get("items_json")); + long totalSupplyPrice = 0; + long totalVat = 0; + long totalAmount = 0; + + if(!"".equals(itemsJson)){ + try { + // JSON 파싱 + JSONParser parser = new JSONParser(); + JSONArray jsonArray = (JSONArray) parser.parse(itemsJson); + + // 각 품목별로 업데이트 및 합계 계산 + for(int i = 0; i < jsonArray.size(); i++) { + JSONObject item = (JSONObject) jsonArray.get(i); + Map itemMap = new HashMap(); + + String orderSupplyPrice = item.get("orderSupplyPrice") != null ? item.get("orderSupplyPrice").toString().replace(",", "") : "0"; + String orderVat = item.get("orderVat") != null ? item.get("orderVat").toString().replace(",", "") : "0"; + String orderTotalAmount = item.get("orderTotalAmount") != null ? item.get("orderTotalAmount").toString().replace(",", "") : "0"; + + itemMap.put("contractItemObjId", item.get("contractItemObjId") != null ? item.get("contractItemObjId").toString() : ""); + itemMap.put("orderQuantity", item.get("orderQuantity") != null ? item.get("orderQuantity").toString() : ""); + itemMap.put("orderUnitPrice", item.get("orderUnitPrice") != null ? item.get("orderUnitPrice").toString() : ""); + itemMap.put("orderSupplyPrice", orderSupplyPrice); + itemMap.put("orderVat", orderVat); + itemMap.put("orderTotalAmount", orderTotalAmount); + + sqlSession.update("contractMgmt.updateContractItemOrderInfo", itemMap); + + // 합계 계산 + try { + totalSupplyPrice += Long.parseLong(orderSupplyPrice); + totalVat += Long.parseLong(orderVat); + totalAmount += Long.parseLong(orderTotalAmount); + } catch (NumberFormatException e) { + // 숫자 변환 실패 시 무시 + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("품목 정보 저장 중 오류가 발생했습니다."); + } + } + + // 합계를 paramMap에 추가 + paramMap.put("order_supply_price", String.valueOf(totalSupplyPrice)); + paramMap.put("order_vat", String.valueOf(totalVat)); + paramMap.put("order_total_amount", String.valueOf(totalAmount)); + + // 기본 수주 정보 및 합계 업데이트 + int cnt = sqlSession.update("contractMgmt.updateOrderInfo", paramMap); //영업 수주 완료시 자동 프로젝트 등록 로직 String result_cd= CommonUtils.checkNull(paramMap.get("contract_result")); @@ -1890,7 +1979,7 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate, } /** - * 품목 저장 (여러 건) + * 품목 저장 (여러 건) - UPSERT 방식 * @param contractObjId 견적 OBJID * @param itemList 품목 목록 * @param userId 사용자 ID @@ -1901,94 +1990,109 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate, Map resultMap = new HashMap(); try { - System.out.println("=== saveContractItems 시작 ==="); + System.out.println("=== saveContractItems 시작 (UPSERT 방식) ==="); System.out.println("contractObjId: " + contractObjId); System.out.println("itemList size: " + (itemList != null ? itemList.size() : 0)); System.out.println("userId: " + userId); - // 기존 품목 삭제 (상태 변경) - Map deleteParam = new HashMap(); - deleteParam.put("contractObjId", contractObjId); - deleteParam.put("userId", userId); + // 프론트에서 전달된 품목 OBJID 목록 수집 + Set currentItemObjIds = new HashSet(); - sqlSession.update("contractMgmt.deleteContractItemSerials", deleteParam); - sqlSession.update("contractMgmt.deleteContractItems", deleteParam); - System.out.println("기존 품목 삭제 완료"); - - // 새 품목 저장 + // 품목 UPSERT if (itemList != null && !itemList.isEmpty()) { for (int i = 0; i < itemList.size(); i++) { Map item = itemList.get(i); System.out.println("품목 " + (i+1) + " 처리 중: " + item); - // 품목 OBJID 생성 (CommonUtils 사용) - String itemObjId = CommonUtils.createObjId(); - - // 품목 저장 - Map itemParam = new HashMap(); - itemParam.put("objId", itemObjId); - itemParam.put("contractObjId", contractObjId); - itemParam.put("seq", i + 1); - itemParam.put("partObjId", item.get("partObjId")); - itemParam.put("partNo", item.get("partNo")); - itemParam.put("partName", item.get("partName")); - - // quantity를 Integer로 변환 - Object quantityObj = item.get("quantity"); - Integer quantity = null; - if(quantityObj != null) { - if(quantityObj instanceof Integer) { - quantity = (Integer) quantityObj; - } else if(quantityObj instanceof Double) { - quantity = ((Double) quantityObj).intValue(); + // 기존 품목 OBJID가 있으면 사용, 없으면 새로 생성 + String itemObjId = CommonUtils.checkNull(item.get("objId")); + if (itemObjId.isEmpty()) { + itemObjId = CommonUtils.createObjId(); + System.out.println("새 품목 OBJID 생성: " + itemObjId); } else { - // String인 경우 콤마 제거 후 변환 - String quantityStr = quantityObj.toString().replace(",", "").trim(); - if(!quantityStr.isEmpty()) { - quantity = Integer.parseInt(quantityStr); + System.out.println("기존 품목 OBJID 사용: " + itemObjId); + } + currentItemObjIds.add(itemObjId); + + // 품목 저장 + Map itemParam = new HashMap(); + itemParam.put("objId", itemObjId); + itemParam.put("contractObjId", contractObjId); + itemParam.put("seq", i + 1); + itemParam.put("partObjId", item.get("partObjId")); + itemParam.put("partNo", item.get("partNo")); + itemParam.put("partName", item.get("partName")); + + // quantity를 Integer로 변환 + Object quantityObj = item.get("quantity"); + Integer quantity = null; + if(quantityObj != null) { + if(quantityObj instanceof Integer) { + quantity = (Integer) quantityObj; + } else if(quantityObj instanceof Double) { + quantity = ((Double) quantityObj).intValue(); + } else { + // String인 경우 콤마 제거 후 변환 + String quantityStr = quantityObj.toString().replace(",", "").trim(); + if(!quantityStr.isEmpty()) { + quantity = Integer.parseInt(quantityStr); + } + } + } + itemParam.put("quantity", quantity); + + itemParam.put("dueDate", item.get("dueDate")); + itemParam.put("customerRequest", item.get("customerRequest")); + itemParam.put("returnReason", item.get("returnReason")); + itemParam.put("writer", userId); + + System.out.println("품목 UPSERT 시도 - OBJID: " + itemObjId); + int result = sqlSession.insert("contractMgmt.upsertContractItem", itemParam); + System.out.println("품목 UPSERT 결과: " + result); + + // 기존 S/N 전체 비활성화 (새로 저장할 것이므로) + Map deleteSnParam = new HashMap(); + deleteSnParam.put("itemObjId", itemObjId); + sqlSession.update("contractMgmt.deleteItemSerials", deleteSnParam); + + // S/N 저장 + @SuppressWarnings("unchecked") + List> snList = (List>) item.get("snList"); + if (snList != null && !snList.isEmpty()) { + for (int j = 0; j < snList.size(); j++) { + Map sn = snList.get(j); + String serialNo = (String) sn.get("value"); + + // S/N 값이 비어있으면 건너뛰기 + if (serialNo == null || serialNo.trim().isEmpty()) { + System.out.println("S/N 값이 비어있어서 건너뜀"); + continue; + } + + // S/N OBJID는 항상 새로 생성 (기존 것을 비활성화했으므로) + String snObjId = CommonUtils.createObjId(); + + Map snParam = new HashMap(); + snParam.put("objId", snObjId); + snParam.put("itemObjId", itemObjId); + snParam.put("seq", j + 1); + snParam.put("serialNo", serialNo); + snParam.put("writer", userId); + + sqlSession.insert("contractMgmt.upsertContractItemSerial", snParam); } } } - itemParam.put("quantity", quantity); - - itemParam.put("dueDate", item.get("dueDate")); - itemParam.put("customerRequest", item.get("customerRequest")); - itemParam.put("returnReason", item.get("returnReason")); - itemParam.put("writer", userId); - - System.out.println("품목 INSERT 시도 - OBJID: " + itemObjId); - int result = sqlSession.insert("contractMgmt.insertContractItem", itemParam); - System.out.println("품목 INSERT 결과: " + result); - - // S/N 저장 - @SuppressWarnings("unchecked") - List> snList = (List>) item.get("snList"); - if (snList != null && !snList.isEmpty()) { - for (int j = 0; j < snList.size(); j++) { - Map sn = snList.get(j); - String serialNo = (String) sn.get("value"); - - // S/N 값이 비어있으면 건너뛰기 - if (serialNo == null || serialNo.trim().isEmpty()) { - System.out.println("S/N 값이 비어있어서 건너뜀"); - continue; - } - - // S/N OBJID 생성 (CommonUtils 사용) - String snObjId = CommonUtils.createObjId(); - - Map snParam = new HashMap(); - snParam.put("objId", snObjId); - snParam.put("itemObjId", itemObjId); - snParam.put("seq", j + 1); - snParam.put("serialNo", serialNo); - snParam.put("writer", userId); - - sqlSession.insert("contractMgmt.insertContractItemSerial", snParam); - } - } - } + } + + // 프론트에서 전달되지 않은 기존 품목들은 INACTIVE 처리 + if (!currentItemObjIds.isEmpty()) { + Map inactiveParam = new HashMap(); + inactiveParam.put("contractObjId", contractObjId); + inactiveParam.put("currentItemObjIds", new ArrayList(currentItemObjIds)); + inactiveParam.put("userId", userId); + sqlSession.update("contractMgmt.inactivateRemovedItems", inactiveParam); } sqlSession.commit();