Compare commits

...

2 Commits

Author SHA1 Message Date
ca25e0de56 feat: 품번/품명 Select2 AJAX 검색 기능 추가
- common.js에 initPartSelect2Ajax 함수 추가 (기존 select2 영향 없음)
- estimateList_new.jsp: 품번/품명 AJAX 검색 적용
- orderMgmtList.jsp: 품번/품명 AJAX 검색 적용
- 디버깅 로그 추가로 문제 해결 용이
2025-10-20 15:55:15 +09:00
889231ae27 feat: 견적서 합계 및 수주 관리 기능 추가
- 견적서(estimateTemplate1.jsp)에 합계 및 원화환산 공급가액 표시 추가
- ESTIMATE_TEMPLATE 테이블에 TOTAL_AMOUNT, TOTAL_AMOUNT_KRW 컬럼 추가
- 견적관리 목록에 최신 차수 견적 합계 표시
- 수주등록 화면(orderRegistFormPopup.jsp) 개선: 품목별 수주 정보 입력
- CONTRACT_ITEM 테이블에 수주 관련 컬럼 추가 (ORDER_QUANTITY, ORDER_UNIT_PRICE 등)
- CONTRACT_ITEM UPSERT 방식으로 변경하여 품목 OBJID 유지 (수주 정보 연결 유지)
- 수주관리 목록에 수주 합계 정보 표시 (공급가액, 부가세, 총액, 원화총액)
- 품목 수정 시 기존 OBJID 유지로 데이터 무결성 확보
2025-10-19 21:58:41 +09:00
15 changed files with 1918 additions and 562 deletions

View File

@@ -24,8 +24,23 @@ $(document).ready(function(){
//날짜
_fnc_datepick();
// jQuery 및 select2 로드 확인
console.log('jQuery loaded:', typeof $ !== 'undefined');
console.log('jQuery.fn exists:', typeof $.fn !== 'undefined');
console.log('select2 loaded:', typeof $.fn !== 'undefined' && typeof $.fn.select2 !== 'undefined');
$('.select2').select2();
// select2가 로드되었을 때만 초기화
if(typeof $.fn !== 'undefined' && typeof $.fn.select2 === 'function') {
$('.select2').select2();
// 품번/품명 Select2 AJAX 초기화 (common.js의 새 함수 사용)
initPartSelect2Ajax("#search_partNo", "#search_partName", "#search_partObjId", {
debug: true // 디버깅 모드 활성화
});
} else {
console.error('select2 라이브러리가 로드되지 않았습니다.');
console.error('$.fn:', $.fn);
}
$("#btnSearch").click(function(){
$("#page").val("1");
@@ -178,7 +193,7 @@ $(document).ready(function(){
// 메일 발송 확인
Swal.fire({
title: '견적서 메일 발송',
text: "최종 차수의 견적서를 PDF로 발송하시겠습니까?",
text: "최종 차수의 견적서를 발송하시겠습니까?",
icon: 'question',
showCancelButton: true,
confirmButtonText: '발송',
@@ -194,6 +209,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 +310,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' },
@@ -366,6 +396,20 @@ var columns = [
//var grid;
function fn_search(){
// Select2 값을 실제 input/select에 명시적으로 설정
var partObjId = $("#search_partObjId").val();
// 디버깅: 검색 조건 확인
console.log("품번:", $("#search_partNo").val());
console.log("품명:", $("#search_partName").val());
console.log("품목 OBJID:", partObjId);
// 품목 OBJID가 있으면 hidden 필드에 확실히 설정
if(partObjId && partObjId !== '') {
// 폼 데이터에 명시적으로 추가
console.log("품목 검색 조건 설정됨:", partObjId);
}
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/contractMgmt/contractGridList.do", columns, true);
}
@@ -480,6 +524,7 @@ function fn_showEstimateList(contractObjId){
html += '<th style="border: 1px solid #ddd; padding: 8px;">견적번호</th>';
html += '<th style="border: 1px solid #ddd; padding: 8px;">작성일</th>';
html += '<th style="border: 1px solid #ddd; padding: 8px;">작성자</th>';
html += '<th style="border: 1px solid #ddd; padding: 8px;">결재상태</th>';
html += '</tr></thead><tbody>';
data.list.forEach(function(item){
@@ -493,6 +538,19 @@ function fn_showEstimateList(contractObjId){
var estimateNo = item.ESTIMATE_NO || item.estimate_no || item.estimateNo || '-';
var regdate = item.REGDATE || item.regdate || '';
var writer = item.WRITER || item.writer || '';
var apprStatus = item.APPR_STATUS || item.appr_status || item.apprStatus || '-';
// 결재상태별 색상 지정
var statusColor = '#333';
if(apprStatus === '결재완료') {
statusColor = 'green';
} else if(apprStatus === '결재중') {
statusColor = 'blue';
} else if(apprStatus === '반려') {
statusColor = 'red';
} else if(apprStatus === '작성중') {
statusColor = '#999';
}
html += '<tr style="cursor: pointer;" onclick="fn_openEstimateByObjId(\'' + objid + '\', \'' + templateType + '\')">';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + revision + '차</td>';
@@ -500,6 +558,7 @@ function fn_showEstimateList(contractObjId){
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + estimateNo + '</td>';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + regdate + '</td>';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + writer + '</td>';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center; color: ' + statusColor + '; font-weight: bold;">' + apprStatus + '</td>';
html += '</tr>';
});
@@ -840,18 +899,23 @@ function openProjectFormPopUp(objId){
</select>
</td>
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<input type="text" name="search_partNo" id="search_partNo" value="${param.search_partNo}"/>
</td>
<td class="align_r">
<label for="" class="">품명</label>
</td>
<td colspan="3">
<input type="text" name="search_partName" id="search_partName" value="${param.search_partName}"/>
</td>
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<select name="search_partNo" id="search_partNo" class="select2-part" style="width: 100%;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_partObjId" id="search_partObjId" value=""/>
</td>
<td class="align_r">
<label for="" class="">품명</label>
</td>
<td colspan="3">
<select name="search_partName" id="search_partName" class="select2-part" style="width: 100%;">
<option value="">품명 선택</option>
</select>
</td>
</tr>
<tr>

View File

@@ -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 += '<select name="item_part_no_select[]" id="PART_NO_' + itemId + '" class="item-part-no-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_objid[]" class="item-objid" value="' + savedItemObjId + '" />';
html += '<input type="hidden" name="item_part_objid[]" class="item-part-objid" value="' + savedPartObjId + '" />';
html += '<input type="hidden" name="item_part_no[]" class="item-part-no" value="' + savedPartNo + '" />';
html += '</td>';
@@ -1147,6 +1150,7 @@
html += '<select name="item_part_no_select[]" id="PART_NO_' + itemId + '" class="item-part-no-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_objid[]" class="item-objid" value="" />';
html += '<input type="hidden" name="item_part_objid[]" class="item-part-objid" />';
html += '<input type="hidden" name="item_part_no[]" class="item-part-no" />';
html += '</td>';

View File

@@ -222,6 +222,9 @@ textarea {
// 전역 변수로 저장 (데이터 로드 시 설정됨)
var g_contractObjId = "<%=objId%>";
var g_templateObjId = "<%=templateObjId%>";
var g_exchangeRate = 1; // 환율 (기본값 1)
var g_currencyName = "KRW"; // 통화명 (기본값 원화)
var g_apprStatus = ""; // 결재상태
$(function(){
@@ -256,6 +259,7 @@ $(function(){
// 금액 자동 계산
$(document).on("change keyup", ".item-qty, .item-price", function(){
fn_calculateAmount($(this).closest("tr"));
fn_calculateTotal(); // 합계 재계산
});
// 콤마 자동 추가 (동적 요소 포함)
@@ -284,9 +288,39 @@ $(function(){
// 데이터 로드
if("<%=objId%>" !== "" && "<%=objId%>" !== "-1") {
fn_loadData();
} else {
// 초기 로드 시 합계 계산
fn_calculateTotal();
}
});
// 결재상태에 따라 버튼 표시 제어
function fn_controlButtons() {
if(g_apprStatus === "결재완료") {
// 결재완료된 경우 행추가, 저장 버튼 숨김
$("#btnAddItem").hide();
$("#btnSave").hide();
// 모든 입력 필드를 읽기 전용으로 변경
$("input, textarea").attr("readonly", true);
$("input, textarea").css("background-color", "#f5f5f5");
// 삭제 버튼 숨김
$(".btn-delete-row").hide();
} else {
// 결재완료가 아닌 경우 버튼 표시
$("#btnAddItem").show();
$("#btnSave").show();
// 입력 필드 활성화
$("input, textarea").attr("readonly", false);
$("input, textarea").css("background-color", "");
// 삭제 버튼 표시
$(".btn-delete-row").show();
}
}
// 금액 계산
function fn_calculateAmount(row) {
var qty = row.find(".item-qty").val().replace(/,/g, "") || "0";
@@ -298,6 +332,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 +366,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 = '<tr>' +
@@ -323,8 +382,11 @@ function fn_addItemRow() {
'<td class="editable"><input type="text" class="item-note" value=""></td>' +
'</tr>';
// 비고 행 바로 위에 추가 (이벤트는 이미 document에 바인딩되어 있으므로 별도 바인딩 불필요)
$("#itemsTableBody tr:last").before(newRow);
// 행 바로 위에 추가
$(".total-row").before(newRow);
// 합계 재계산
fn_calculateTotal();
}
// 데이터 로드
@@ -338,12 +400,21 @@ function fn_loadData() {
dataType: "json",
success: function(data) {
if(data && data.estimate) {
// 데이터 바인딩
$("#executor").val(data.estimate.EXECUTOR || "");
$("#recipient").val(data.estimate.RECIPIENT || "");
$("#estimate_no").val(data.estimate.ESTIMATE_NO || "");
$("#contact_person").val(data.estimate.CONTACT_PERSON || "");
$("#greeting_text").val(data.estimate.GREETING_TEXT || "견적을 요청해 주셔서 대단히 감사합니다.\n하기와 같이 견적서를 제출합니다.");
// 환율 정보 저장
g_exchangeRate = parseFloat(data.estimate.EXCHANGE_RATE || "1");
g_currencyName = data.estimate.CONTRACT_CURRENCY_NAME || "KRW";
// 결재상태 저장
g_apprStatus = data.estimate.APPR_STATUS || "작성중";
// 데이터 바인딩
$("#executor").val(data.estimate.EXECUTOR || "");
$("#recipient").val(data.estimate.RECIPIENT || "");
$("#estimate_no").val(data.estimate.ESTIMATE_NO || "");
$("#contact_person").val(data.estimate.CONTACT_PERSON || "");
$("#greeting_text").val(data.estimate.GREETING_TEXT || "견적을 요청해 주셔서 대단히 감사합니다.\n하기와 같이 견적서를 제출합니다.");
$("#manager_name").val(data.estimate.MANAGER_NAME || "영업부");
$("#manager_contact").val(data.estimate.MANAGER_CONTACT || "");
// 품목 데이터 로드
if(data.items && data.items.length > 0) {
@@ -362,15 +433,47 @@ function fn_loadData() {
itemsHtml += '<td class="editable"><input type="text" class="item-note" value="' + (item.NOTE || '') + '"></td>';
itemsHtml += '</tr>';
}
$("#itemsTableBody").html(itemsHtml);
}
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0;"></td>';
itemsHtml += '</tr>';
// 원화환산 공급가액 행 추가
itemsHtml += '<tr class="total-krw-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #e8f4f8;">원화환산 공급가액 (KRW)</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #e8f4f8;"><span id="totalAmountKRW">0</span></td>';
itemsHtml += '<td style="background-color: #e8f4f8;"></td>';
itemsHtml += '</tr>';
// 비고 행 추가
itemsHtml += '<tr class="remarks-row">';
itemsHtml += '<td colspan="8" style="height: 100px; vertical-align: top; padding: 10px; text-align: left;">';
itemsHtml += '<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;비고&gt;</div>';
itemsHtml += '<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>';
itemsHtml += '</td>';
itemsHtml += '</tr>';
$("#itemsTableBody").html(itemsHtml);
// 비고 로드
$("#note1").val(data.estimate.NOTE1 || "1. 견적유효기간: 일");
$("#note2").val(data.estimate.NOTE2 || "2. 납품기간: 발주 후 1주 이내");
$("#note3").val(data.estimate.NOTE3 || "3. VAT 별도");
$("#note4").val(data.estimate.NOTE4 || "4. 결제 조건 : 기존 결제조건에 따름.");
// 비고 로드 (테이블 생성 직후)
$("#note_remarks").val(data.estimate.NOTE_REMARKS || "");
// 합계 계산
fn_calculateTotal();
}
// 하단 비고 로드
$("#note1").val(data.estimate.NOTE1 || "1. 견적유효기간: 일");
$("#note2").val(data.estimate.NOTE2 || "2. 납품기간: 발주 후 1주 이내");
$("#note3").val(data.estimate.NOTE3 || "3. VAT 별도");
$("#note4").val(data.estimate.NOTE4 || "4. 결제 조건 : 기존 결제조건에 따름.");
// 결재상태에 따라 버튼 제어
fn_controlButtons();
}
},
error: function() {
Swal.fire("데이터를 불러오는데 실패했습니다.");
@@ -397,29 +500,45 @@ 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";
// 결재상태 저장
g_apprStatus = template.APPR_STATUS || template.appr_status || template.apprStatus || "작성중";
// 대문자/소문자 모두 지원
var executor = template.EXECUTOR || template.executor || "";
var recipient = template.RECIPIENT || template.recipient || "";
var estimateNo = template.ESTIMATE_NO || template.estimate_no || template.estimateNo || "";
var contactPerson = template.CONTACT_PERSON || template.contact_person || template.contactPerson || "";
var greetingText = template.GREETING_TEXT || template.greeting_text || template.greetingText || "";
var note1 = template.NOTE1 || template.note1 || "";
var note2 = template.NOTE2 || template.note2 || "";
var note3 = template.NOTE3 || template.note3 || "";
var note4 = template.NOTE4 || template.note4 || "";
// 기본 정보 채우기
$("#executor").val(executor);
$("#recipient").val(recipient);
$("#estimate_no").val(estimateNo);
$("#contact_person").val(contactPerson);
$("#greeting_text").val(greetingText);
$("#note1").val(note1);
$("#note2").val(note2);
$("#note3").val(note3);
$("#note4").val(note4);
// 품목 데이터 채우기
var note1 = template.NOTE1 || template.note1 || "";
var note2 = template.NOTE2 || template.note2 || "";
var note3 = template.NOTE3 || template.note3 || "";
var note4 = template.NOTE4 || template.note4 || "";
var managerName = template.MANAGER_NAME || template.manager_name || template.managerName || "영업부";
var managerContact = template.MANAGER_CONTACT || template.manager_contact || template.managerContact || "";
// 기본 정보 채우기
$("#executor").val(executor);
$("#recipient").val(recipient);
$("#estimate_no").val(estimateNo);
$("#contact_person").val(contactPerson);
$("#greeting_text").val(greetingText);
$("#manager_name").val(managerName);
$("#manager_contact").val(managerContact);
// 하단 비고 로드
$("#note1").val(note1);
$("#note2").val(note2);
$("#note3").val(note3);
$("#note4").val(note4);
// 테이블 내 비고는 나중에 설정 (textarea 생성 후)
var noteRemarks = template.NOTE_REMARKS || template.note_remarks || template.noteRemarks || "";
// 품목 데이터 채우기
if(data.items && data.items.length > 0){
$("#itemsTableBody").empty();
data.items.forEach(function(item, idx){
@@ -446,23 +565,55 @@ function fn_loadTemplateData(templateObjId){
row.append('<td class="editable"><input type="text" class="item-note" value="' + note + '"></td>');
$("#itemsTableBody").append(row);
});
}
} else {
console.error("데이터 로드 실패:", data);
Swal.fire("데이터를 불러오는데 실패했습니다.");
// 계 행 추가
var totalRow = $("<tr class='total-row'>");
totalRow.append('<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>');
totalRow.append('<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>');
totalRow.append('<td style="background-color: #f0f0f0;"></td>');
$("#itemsTableBody").append(totalRow);
// 원화환산 공급가액 행 추가
var totalKRWRow = $("<tr class='total-krw-row'>");
totalKRWRow.append('<td colspan="6" style="text-align: center; font-weight: bold; background-color: #e8f4f8;">원화환산 공급가액 (KRW)</td>');
totalKRWRow.append('<td class="text-right" style="font-weight: bold; background-color: #e8f4f8;"><span id="totalAmountKRW">0</span></td>');
totalKRWRow.append('<td style="background-color: #e8f4f8;"></td>');
$("#itemsTableBody").append(totalKRWRow);
// 비고 행 추가
var remarksRow = $("<tr class='remarks-row'>");
remarksRow.append('<td colspan="8" style="height: 100px; vertical-align: top; padding: 10px; text-align: left;">' +
'<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;비고&gt;</div>' +
'<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>' +
'</td>');
$("#itemsTableBody").append(remarksRow);
// 테이블 내 비고 값 설정 (textarea 생성 직후)
$("#note_remarks").val(noteRemarks);
// 합계 계산
fn_calculateTotal();
// 결재상태에 따라 버튼 제어
fn_controlButtons();
}
},
error: function(xhr, status, error){
console.error("AJAX 오류:", xhr, status, error);
} else {
console.error("데이터 로드 실패:", data);
Swal.fire("데이터를 불러오는데 실패했습니다.");
}
});
},
error: function(xhr, status, error){
console.error("AJAX 오류:", xhr, status, error);
Swal.fire("데이터를 불러오는데 실패했습니다.");
}
});
}
// 저장
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 +639,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 +651,11 @@ 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, // 원화환산 공급가액
manager_name: $("#manager_name").val(), // 담당자
manager_contact: $("#manager_contact").val(), // 연락처
note_remarks: $("#note_remarks").val(), // 테이블 내 비고
note1: $("#note1").val(),
note2: $("#note2").val(),
note3: $("#note3").val(),
@@ -565,10 +725,10 @@ function fn_save() {
<div class="company-stamp-text">㈊알피에스<br>RPS CO., LTD<br>대표이사이동준</div>
</div>
</div>
<div style="text-align: left; font-size: 9pt; line-height: 1.8; padding: 0 5px;">
담당자 : 영업부<br>
연락처 :
</div>
<div style="text-align: left; font-size: 9pt; line-height: 1.8; padding: 0 5px;">
담당자 : <input type="text" id="manager_name" value="영업부" style="width: 80px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px;"><br>
연락처 : <input type="text" id="manager_contact" value="" style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px;">
</div>
</td>
</tr>
<tr>
@@ -637,7 +797,20 @@ function fn_save() {
<td class="text-right editable"><input type="text" class="item-amount" value="" readonly></td>
<td class="editable"><input type="text" class="item-note" value=""></td>
</tr>
<tr>
<!-- 계 행 -->
<tr class="total-row">
<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>
<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>
<td style="background-color: #f0f0f0;"></td>
</tr>
<!-- 원화환산 공급가액 행 -->
<tr class="total-krw-row">
<td colspan="6" style="text-align: center; font-weight: bold; background-color: #e8f4f8;">원화환산 공급가액 (KRW)</td>
<td class="text-right" style="font-weight: bold; background-color: #e8f4f8;"><span id="totalAmountKRW">0</span></td>
<td style="background-color: #e8f4f8;"></td>
</tr>
<!-- 비고 행 -->
<tr class="remarks-row">
<td colspan="8" style="height: 100px; vertical-align: top; padding: 10px; text-align: left;">
<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;비고&gt;</div>
<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>

View File

@@ -27,6 +27,11 @@ $(document).ready(function(){
$('.select2').select2();
// 품번/품명 Select2 AJAX 초기화 (common.js의 새 함수 사용)
initPartSelect2Ajax("#search_partNo", "#search_partName", "#search_partObjId", {
debug: false // 디버깅 모드 비활성화
});
$("#btnSearch").click(function(){
$("#page").val("1");
fn_search();
@@ -57,7 +62,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 +105,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 '<a href="javascript:void(0);">' + serialNumbers[0] + '</a>';
//
// // 2개 이상이면 "첫번째 외 N개" 형식
// var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개';
// return '<a href="javascript:void(0);">' + displayText + '</a>';
// },
// 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 '<a href="javascript:void(0);">' + serialNumbers[0] + '</a>';
// 다른 납기가 있으면 "날짜 외 N건" 형식으로 표시
if(otherCount && parseInt(otherCount) > 0){
return dueDate + ' 외 ' + otherCount + '건';
}
// 2개 이상이면 "첫번째 외 N개" 형식
var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개';
return '<a href="javascript:void(0);">' + displayText + '</a>';
},
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 +166,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 +267,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;
@@ -578,18 +604,23 @@ function openProjectFormPopUp(objId){
</select>
</td>
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<input type="text" name="search_partNo" id="search_partNo" value="${param.search_partNo}"/>
</td>
<td class="align_r">
<label for="" class="">품명</label>
</td>
<td colspan="3">
<input type="text" name="search_partName" id="search_partName" value="${param.search_partName}"/>
</td>
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<select name="search_partNo" id="search_partNo" class="select2-part" style="width: 100%;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_partObjId" id="search_partObjId" value=""/>
</td>
<td class="align_r">
<label for="" class="">품명</label>
</td>
<td colspan="3">
<select name="search_partName" id="search_partName" class="select2-part" style="width: 100%;">
<option value="">품명 선택</option>
</select>
</td>
</tr>
<tr>

View File

@@ -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;
}
</style>
<script type="text/javascript">
// 품목 관리 변수
var itemCounter = 1;
var itemList = [];
$(function() {
// 숫자 입력 필드에 콤마 자동 추가
$("input:text[numberOnly]").on("keyup", function() {
// 숫자 입력 필드에 콤마 자동 추가 및 금액 계산
$(document).on("keyup", "input:text[numberOnly]", function() {
$(this).val(addComma($(this).val().replace(/[^0-9]/g, "")));
// 수주수량 또는 수주단가가 변경되면 자동 계산
if($(this).hasClass("item-quantity") || $(this).hasClass("item-unit-price")) {
var itemId = $(this).closest("tr").attr("id");
if(itemId) {
fn_calculateItemAmount(itemId);
}
}
});
// 페이지 로드 시 기존 값에 콤마 표시
@@ -48,93 +84,223 @@
self.close();
});
// 단가/수량 변경 시 금액 자동 계산
$("#unit_price, #quantity").on("change keyup", function() {
fn_calculateAmount();
});
// 공급가액 변경 시 부가세/총액 자동 계산
$("#supply_price").on("change keyup", function() {
fn_calculateTax();
});
// 환종 변경 시 환율 자동 로드
$("#currency").change(function() {
var selectedCurrency = $(this).val();
if(selectedCurrency && selectedCurrency !== '') {
fn_loadExchangeRate(selectedCurrency);
} else {
$("#exchange_rate").val('');
}
});
// 기존 품목 데이터 로드 (CONTRACT_ITEM에서)
fn_loadContractItems();
});
// 콤마 추가 함수
function addComma(data) {
if(!data) return '';
return data.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// 콤마 제거 함수
function removeComma(data) {
if(!data) return '';
return data.toString().replace(/,/g, "");
}
// 금액 자동 계산 (단가 × 수량 = 공급가액)
function fn_calculateAmount() {
var unitPrice = removeComma($("#unit_price").val()) || 0;
var quantity = removeComma($("#quantity").val()) || 0;
var supplyPrice = parseInt(unitPrice) * parseInt(quantity);
$("#supply_price").val(addComma(supplyPrice));
// 공급가액 변경 시 부가세/총액도 재계산
fn_calculateTax();
// 품목 행 추가 (사용 안함 - CONTRACT_ITEM에서만 로드)
function fn_addItemRow() {
alert("품목은 견적요청에서 등록된 항목만 표시됩니다.");
return;
}
// 부가세/총액 자동 계산 (공급가액 × 10% = 부가세, 공급가액 + 부가세 = 총액)
function fn_calculateTax() {
var supplyPrice = parseInt(removeComma($("#supply_price").val())) || 0;
// 품목 행 삭제
function fn_removeItemRow(itemId) {
if(confirm("이 품목을 삭제하시겠습니까?")) {
$("#" + itemId).remove();
// itemList에서 제거
for(var i = 0; i < itemList.length; i++) {
if(itemList[i].id == itemId) {
itemList.splice(i, 1);
break;
}
}
// 번호 재정렬
fn_reorderItems();
// 품목이 없으면 메시지 표시
if($("#itemListBody tr.item-row").length == 0) {
$("#itemListBody").html('<tr id="noItemRow"><td colspan="9" style="text-align:center; padding:30px; color:#999;">품목 추가 버튼을 클릭하여 품목을 등록하세요.</td></tr>');
}
}
}
// 품목 번호 재정렬
function fn_reorderItems() {
$("#itemListBody tr.item-row").each(function(index) {
$(this).find("td:first").text(index + 1);
});
}
// 품목별 금액 계산 (수량 × 단가 = 공급가액, 공급가액 × 10% = 부가세, 공급가액 + 부가세 = 총액)
function fn_calculateItemAmount(itemId) {
var quantity = parseInt(removeComma($("#" + itemId + " .item-quantity").val())) || 0;
var unitPrice = parseInt(removeComma($("#" + itemId + " .item-unit-price").val())) || 0;
var vat = Math.round(supplyPrice * 0.1); // 부가세 10%
var supplyPrice = quantity * unitPrice;
var vat = Math.round(supplyPrice * 0.1);
var totalAmount = supplyPrice + vat;
$("#vat").val(addComma(vat));
$("#total_amount").val(addComma(totalAmount));
$("#" + itemId + " .item-supply-price").val(addComma(supplyPrice));
$("#" + itemId + " .item-vat").val(addComma(vat));
$("#" + itemId + " .item-total-amount").val(addComma(totalAmount));
}
// 기존 품목 데이터 로드 (CONTRACT_ITEM 테이블에서)
function fn_loadContractItems() {
var contractObjId = $("#contractObjId").val();
if(!contractObjId || contractObjId === '') {
return;
}
$.ajax({
url: "/contractMgmt/getContractItems.do",
type: "POST",
data: { contractObjId: contractObjId },
dataType: "json",
success: function(data) {
if(data && data.items && data.items.length > 0) {
$("#noItemRow").remove();
for(var i = 0; i < data.items.length; i++) {
var item = data.items[i];
var itemId = "item_" + itemCounter++;
// S/N 처리 (쉼표로 구분된 값)
var serialNo = item.SERIAL_NO || '';
var serialNoDisplay = serialNo;
if(serialNo) {
var snArray = serialNo.split(',').map(function(s){ return s.trim(); }).filter(function(s){ return s !== ''; });
if(snArray.length > 1) {
serialNoDisplay = snArray[0] + ' 외 ' + (snArray.length - 1) + '개';
}
}
var html = '<tr id="' + itemId + '" class="item-row">';
html += '<td>' + (i + 1) + '</td>';
html += '<td><input type="text" class="item-part-no" value="' + (item.PART_NO || '') + '" readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-part-name" value="' + (item.PART_NAME || '') + '" readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-serial-no" value="' + serialNoDisplay + '" readonly style="background:#f5f5f5;" title="' + serialNo + '" /></td>';
html += '<td><input type="text" class="item-quantity" value="' + (item.ORDER_QUANTITY || item.QUANTITY || '') + '" numberOnly required /></td>';
html += '<td><input type="text" class="item-unit-price" value="' + (item.ORDER_UNIT_PRICE || '') + '" numberOnly required /></td>';
html += '<td><input type="text" class="item-supply-price" value="' + (item.ORDER_SUPPLY_PRICE || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-vat" value="' + (item.ORDER_VAT || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-total-amount" value="' + (item.ORDER_TOTAL_AMOUNT || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
html += '<td style="text-align:center;">-</td>'; // 삭제 불가
html += '<input type="hidden" class="item-objid" value="' + (item.OBJID || '') + '" />';
html += '<input type="hidden" class="item-contract-item-objid" value="' + (item.OBJID || '') + '" />'; // CONTRACT_ITEM의 OBJID
html += '<input type="hidden" class="item-part-objid" value="' + (item.PART_OBJID || '') + '" />';
html += '<input type="hidden" class="item-serial-no-full" value="' + serialNo + '" />'; // 전체 S/N
html += '</tr>';
$("#itemListBody").append(html);
// 콤마 추가
$("#" + itemId + " .item-quantity").val(addComma($("#" + itemId + " .item-quantity").val()));
$("#" + itemId + " .item-unit-price").val(addComma($("#" + itemId + " .item-unit-price").val()));
$("#" + itemId + " .item-supply-price").val(addComma($("#" + itemId + " .item-supply-price").val()));
$("#" + itemId + " .item-vat").val(addComma($("#" + itemId + " .item-vat").val()));
$("#" + itemId + " .item-total-amount").val(addComma($("#" + itemId + " .item-total-amount").val()));
// 이벤트 바인딩 (클로저 문제 해결)
(function(id) {
$("#" + id + " .item-quantity, #" + id + " .item-unit-price").on("change keyup", function() {
fn_calculateItemAmount(id);
});
})(itemId);
// S/N 클릭 시 전체 S/N 보기
$("#" + itemId + " .item-serial-no").on("click", function() {
var fullSn = $(this).attr("title");
if(fullSn && fullSn !== '') {
var snArray = fullSn.split(',').map(function(s){ return s.trim(); });
var snList = '<ol style="text-align:left; padding-left:20px;">';
for(var j = 0; j < snArray.length; j++) {
snList += '<li>' + snArray[j] + '</li>';
}
snList += '</ol>';
Swal.fire({
title: 'S/N 목록',
html: snList,
width: 500,
confirmButtonText: '확인'
});
}
});
// 품목 정보 저장
itemList.push({
id: itemId
});
}
}
},
error: function() {
console.log("품목 데이터 로드 실패");
}
});
}
// 저장 함수
function fn_save() {
if (fnc_valitate("form1")) {
var message = "수정";
if ("${actionType}" == 'regist') {
message = "등록";
}
if (confirm(message + "하시겠습니까?")) {
// 콤마 제거
$("#unit_price").val(removeComma($("#unit_price").val()));
$("#supply_price").val(removeComma($("#supply_price").val()));
$("#vat").val(removeComma($("#vat").val()));
$("#total_amount").val(removeComma($("#total_amount").val()));
$("#quantity").val(removeComma($("#quantity").val()));
$.ajax({
url : "/contractMgmt/saveOrderInfo.do",
type : "POST",
data : $("#form1").serialize(),
dataType : "json",
success : function(data) {
alert(data.msg);
if(opener && opener.fn_search) {
opener.fn_search();
}
self.close();
},
error : function(jqxhr, status, error) {
alert("저장 중 오류가 발생했습니다.");
// 품목 검증
if($("#itemListBody tr.item-row").length == 0) {
alert("품목을 하나 이상 추가해주세요.");
return false;
}
// 기본정보 검증
if (!fnc_valitate("form1")) {
return false;
}
// 품목 데이터 수집
var items = [];
$("#itemListBody tr.item-row").each(function() {
var $row = $(this);
items.push({
contractItemObjId: $row.find(".item-contract-item-objid").val(), // CONTRACT_ITEM의 OBJID
partObjId: $row.find(".item-part-objid").val(),
partNo: $row.find(".item-part-no").val(),
partName: $row.find(".item-part-name").val(),
serialNo: $row.find(".item-serial-no-full").val(),
orderQuantity: removeComma($row.find(".item-quantity").val()),
orderUnitPrice: removeComma($row.find(".item-unit-price").val()),
orderSupplyPrice: removeComma($row.find(".item-supply-price").val()),
orderVat: removeComma($row.find(".item-vat").val()),
orderTotalAmount: removeComma($row.find(".item-total-amount").val())
});
});
var message = "수정";
if ("${actionType}" == 'regist') {
message = "등록";
}
if (confirm(message + "하시겠습니까?")) {
// 품목 데이터를 JSON으로 변환
$("#items_json").val(JSON.stringify(items));
$.ajax({
url : "/contractMgmt/saveOrderInfo.do",
type : "POST",
data : $("#form1").serialize(),
dataType : "json",
success : function(data) {
alert(data.msg);
if(opener && opener.fn_search) {
opener.fn_search();
}
});
}
self.close();
},
error : function(jqxhr, status, error) {
alert("저장 중 오류가 발생했습니다.");
}
});
}
}
@@ -150,24 +316,6 @@
}
}
// 환율 자동 로드
function fn_loadExchangeRate(currencyCode) {
$.ajax({
url: "/common/getExchangeRate.do",
type: "POST",
data: { currencyCode: currencyCode },
dataType: "json",
success: function(data) {
if(data && data.rate) {
$("#exchange_rate").val(data.rate);
}
},
error: function() {
console.log("환율 조회 실패");
}
});
}
</script>
</head>
<body>
@@ -175,6 +323,7 @@
<input type="hidden" name="contractObjId" id="contractObjId" value="${contractObjId}">
<input type="hidden" name="objId" id="objId" value="${objId}">
<input type="hidden" name="actionType" id="actionType" value="${actionType}">
<input type="hidden" name="items_json" id="items_json" value="">
<section class="business_popup_min_width">
<div class="plm_menu_name">
@@ -184,8 +333,9 @@
</div>
<div id="EntirePopupFormWrap">
<!-- 기본정보 영역 -->
<div class="form_popup_title">
<span>수주 정보입력</span>
<span>수주 기본정보</span>
</div>
<table class="">
<colgroup>
@@ -216,53 +366,25 @@
</td>
</tr>
<!-- 두번째 행: 발주일, 수량 -->
<!-- 두번째 행: 발주일, 견적환종 -->
<tr>
<td class="input_title"><label for="">발주일 <span style="color:red;">*</span></label></td>
<td>
<input type="text" class="date_icon" name="order_date" id="order_date" required reqTitle="발주일" value="${info.ORDER_DATE}">
</td>
<td class="input_title"><label for="">수량 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="quantity" id="quantity" required reqTitle="수량" value="${info.QUANTITY}" numberOnly />
</td>
</tr>
<!-- 세번째 행: 단가, 공급가액 -->
<tr>
<td class="input_title"><label for="">단가 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="unit_price" id="unit_price" required reqTitle="단가" value="${info.ORDER_UNIT_PRICE}" numberOnly />
</td>
<td class="input_title"><label for="">공급가액 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="supply_price" id="supply_price" required reqTitle="공급가액" value="${info.ORDER_SUPPLY_PRICE}" numberOnly />
</td>
</tr>
<!-- 네번째 행: 부가세, 총액 -->
<tr>
<td class="input_title"><label for="">부가세 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="vat" id="vat" required reqTitle="부가세" value="${info.ORDER_VAT}" numberOnly />
</td>
<td class="input_title"><label for="">총액 <span style="color:red;">*</span></label></td>
<td>
<input type="text" name="total_amount" id="total_amount" required reqTitle="총액" value="${info.ORDER_TOTAL_AMOUNT}" numberOnly />
</td>
</tr>
<!-- 다섯번째 행: 환종, 환율 -->
<tr>
<td class="input_title"><label for="">환종</label></td>
<td class="input_title"><label for="">견적환종</label></td>
<td>
<select name="contract_currency" id="contract_currency" reqTitle="환종" type="select" class="select2">
<option value="">선택</option>
${code_map.contract_currency}
</select>
</td>
<td class="input_title"><label for="">환율</label></td>
<td>
</tr>
<!-- 세번째 행: 견적환율 -->
<tr>
<td class="input_title"><label for="">견적환율</label></td>
<td colspan="3">
<input type="text" name="exchange_rate" id="exchange_rate" reqTitle="환율" value="${info.EXCHANGE_RATE}" numberOnly />
</td>
</tr>
@@ -271,6 +393,58 @@
</td>
</tr>
</table>
<!-- 품목정보 영역 -->
<div class="form_popup_title" style="margin-top:15px;">
<span>품목정보 (견적요청에서 자동 로드)</span>
</div>
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
<tr>
<td>
<div style="max-height:300px; overflow-y:auto;">
<table class="pmsPopuptable" id="itemListTable">
<colgroup>
<col width="4%" /> <!-- 번호 -->
<col width="10%" /> <!-- 품번 -->
<col width="13%" /> <!-- 품명 -->
<col width="12%" /> <!-- S/N -->
<col width="8%" /> <!-- 수주수량 -->
<col width="11%" /> <!-- 수주단가 -->
<col width="12%" /> <!-- 수주공급가액 -->
<col width="11%" /> <!-- 수주부가세 -->
<col width="12%" /> <!-- 수주총액 -->
<col width="7%" /> <!-- 삭제 -->
</colgroup>
<thead>
<tr style="background:#f5f5f5;">
<th style="text-align:center; padding:8px; border:1px solid #ddd;">No</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">품번</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">품명</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">S/N</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">수주수량 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">수주단가 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">수주공급가액</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">수주부가세</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">수주총액</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">-</th>
</tr>
</thead>
<tbody id="itemListBody">
<!-- 품목 행이 동적으로 추가됩니다 -->
<tr id="noItemRow">
<td colspan="10" style="text-align:center; padding:30px; color:#999;">
견적요청에 등록된 품목이 자동으로 표시됩니다.
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</table>
</div>

View File

@@ -3255,4 +3255,182 @@ function fnc_tabulCallbackFnc(objid, docType, columnField, fileCnt){
});
}
//tabulator용 Function 종료
//tabulator용 Function 종료
/**
* 품번/품명 Select2 AJAX 검색 함수 (견적 목록 전용)
* @param {string} partNoSelectId - 품번 셀렉트 박스 ID (예: "#search_partNo")
* @param {string} partNameSelectId - 품명 셀렉트 박스 ID (예: "#search_partName")
* @param {string} partObjIdInputId - 품목 OBJID hidden 필드 ID (예: "#search_partObjId")
* @param {object} options - 추가 옵션 (placeholder, minimumInputLength 등)
*/
function initPartSelect2Ajax(partNoSelectId, partNameSelectId, partObjIdInputId, options) {
options = options || {};
var partNoPlaceholder = options.partNoPlaceholder || "품번 입력하여 검색...";
var partNamePlaceholder = options.partNamePlaceholder || "품명 입력하여 검색...";
var minimumInputLength = options.minimumInputLength || 1;
var searchUrl = options.searchUrl || '/contractMgmt/searchPartList.do';
var debug = options.debug || false;
// 품번 Select2 AJAX 설정
$(partNoSelectId).select2({
placeholder: partNoPlaceholder,
allowClear: true,
width: '100%',
minimumInputLength: minimumInputLength,
language: {
inputTooShort: function() {
return "최소 " + minimumInputLength + "글자 이상 입력하세요";
},
searching: function() {
return "검색 중...";
},
noResults: function() {
return "검색 결과가 없습니다";
}
},
ajax: {
url: searchUrl,
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) {
return {
searchTerm: params.term
};
},
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return {
id: partNo,
text: partNo,
objId: objId,
partName: partName,
partNo: partNo
};
});
return {
results: results
};
},
cache: true
}
});
// 품명 Select2 AJAX 설정
$(partNameSelectId).select2({
placeholder: partNamePlaceholder,
allowClear: true,
width: '100%',
minimumInputLength: minimumInputLength,
language: {
inputTooShort: function() {
return "최소 " + minimumInputLength + "글자 이상 입력하세요";
},
searching: function() {
return "검색 중...";
},
noResults: function() {
return "검색 결과가 없습니다";
}
},
ajax: {
url: searchUrl,
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) {
return {
searchTerm: params.term
};
},
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return {
id: partName,
text: partName,
objId: objId,
partName: partName,
partNo: partNo
};
});
return {
results: results
};
},
cache: true
}
});
// 품번 변경 이벤트 핸들러
var partNoChangeHandler = function() {
var selectedData = $(this).select2('data')[0];
if(debug) console.log("품번 변경됨:", selectedData);
if(selectedData && selectedData.objId) {
if(debug) console.log("품목 OBJID 설정:", selectedData.objId);
$(partObjIdInputId).val(selectedData.objId);
// 품명 셀렉트박스도 동기화
var $partNameSelect = $(partNameSelectId);
$partNameSelect.off('change');
if($partNameSelect.find("option[value='" + selectedData.partName + "']").length === 0) {
var newOption = new Option(selectedData.partName, selectedData.partName, true, true);
$partNameSelect.append(newOption);
} else {
$partNameSelect.val(selectedData.partName);
}
$partNameSelect.trigger('change.select2');
setTimeout(function() {
$partNameSelect.on('change', partNameChangeHandler);
}, 100);
} else {
$(partObjIdInputId).val("");
}
};
// 품명 변경 이벤트 핸들러
var partNameChangeHandler = function() {
var selectedData = $(this).select2('data')[0];
if(debug) console.log("품명 변경됨:", selectedData);
if(selectedData && selectedData.objId) {
if(debug) console.log("품목 OBJID 설정:", selectedData.objId);
$(partObjIdInputId).val(selectedData.objId);
// 품번 셀렉트박스도 동기화
var $partNoSelect = $(partNoSelectId);
$partNoSelect.off('change');
if($partNoSelect.find("option[value='" + selectedData.partNo + "']").length === 0) {
var newOption = new Option(selectedData.partNo, selectedData.partNo, true, true);
$partNoSelect.append(newOption);
} else {
$partNoSelect.val(selectedData.partNo);
}
$partNoSelect.trigger('change.select2');
setTimeout(function() {
$partNoSelect.on('change', partNoChangeHandler);
}, 100);
} else {
$(partObjIdInputId).val("");
}
};
// 이벤트 연결
$(partNoSelectId).on('change', partNoChangeHandler);
$(partNameSelectId).on('change', partNameChangeHandler);
}

View File

@@ -0,0 +1,16 @@
-- ESTIMATE_TEMPLATE 테이블에 담당자 정보 및 테이블 내 비고 컬럼 추가
ALTER TABLE ESTIMATE_TEMPLATE
ADD COLUMN IF NOT EXISTS MANAGER_NAME VARCHAR(100);
ALTER TABLE ESTIMATE_TEMPLATE
ADD COLUMN IF NOT EXISTS MANAGER_CONTACT VARCHAR(100);
ALTER TABLE ESTIMATE_TEMPLATE
ADD COLUMN IF NOT EXISTS NOTE_REMARKS TEXT;
-- 컬럼 설명 추가
COMMENT ON COLUMN ESTIMATE_TEMPLATE.MANAGER_NAME IS '담당자 이름';
COMMENT ON COLUMN ESTIMATE_TEMPLATE.MANAGER_CONTACT IS '담당자 연락처';
COMMENT ON COLUMN ESTIMATE_TEMPLATE.NOTE_REMARKS IS '테이블 내 비고 (품목 하단)';

View File

@@ -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 '수주 총액 (공급가액 + 부가세)';

View File

@@ -0,0 +1,27 @@
-- PROJECT_MGMT 테이블에 품목 관련 컬럼 추가
-- 품목별 프로젝트 생성을 위한 컬럼들
ALTER TABLE PROJECT_MGMT
ADD COLUMN IF NOT EXISTS PART_OBJID VARCHAR(50);
ALTER TABLE PROJECT_MGMT
ADD COLUMN IF NOT EXISTS PART_NO VARCHAR(100);
ALTER TABLE PROJECT_MGMT
ADD COLUMN IF NOT EXISTS PART_NAME VARCHAR(200);
ALTER TABLE PROJECT_MGMT
ADD COLUMN IF NOT EXISTS QUANTITY VARCHAR(50);
-- 컬럼 설명 추가
COMMENT ON COLUMN PROJECT_MGMT.PART_OBJID IS '품목 OBJID (품목별 프로젝트 생성 시 사용)';
COMMENT ON COLUMN PROJECT_MGMT.PART_NO IS '품번';
COMMENT ON COLUMN PROJECT_MGMT.PART_NAME IS '품명';
COMMENT ON COLUMN PROJECT_MGMT.QUANTITY IS '수주수량';
-- 기존 인덱스 확인 후 추가 (성능 향상)
CREATE INDEX IF NOT EXISTS idx_project_mgmt_contract_part
ON PROJECT_MGMT(CONTRACT_OBJID, PART_OBJID);
COMMENT ON INDEX idx_project_mgmt_contract_part IS '계약-품목별 프로젝트 조회 성능 향상';

View File

@@ -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 '원화환산 공급가액 (합계 × 환율, 콤마 포맷팅 포함)';

View File

@@ -691,6 +691,7 @@ public class MailUtil {
//◆◆◆ 3. db log & send mail ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
SqlSession sqlSession = null;
boolean mailSendSuccess = false; // 메일 발송 성공 여부
Map paramMap = new HashMap();
try{
@@ -715,6 +716,7 @@ public class MailUtil {
//◆◆◆ send mail ◆◆◆
Transport.send(message);
mailSendSuccess = true; // 메일 발송 성공
if(Constants.Mail.dbLogWrite){
System.out.println("메일 발송후 paramMap >> "+paramMap);
@@ -722,6 +724,7 @@ public class MailUtil {
}
}catch(Exception sqle){
mailSendSuccess = false; // 메일 발송 실패
if(Constants.Mail.dbLogWrite){
paramMap.put("errorLog", sqle.getMessage());
System.out.println("메일 발송 오류 paramMap >> "+paramMap);
@@ -735,7 +738,7 @@ public class MailUtil {
}
}
return true;
return mailSendSuccess; // 실제 발송 성공 여부 반환
}catch(Exception e) {
e.printStackTrace();
return false;

View File

@@ -7362,16 +7362,20 @@ SELECT
,EST_USER_ID
,EST_COMP_DATE
,EST_RESULT_CD
,AREA_CD
,MECHANICAL_TYPE
,OVERHAUL_ORDER
,IS_TEMP
)
,AREA_CD
,MECHANICAL_TYPE
,OVERHAUL_ORDER
,IS_TEMP
,PART_OBJID
,PART_NO
,PART_NAME
,QUANTITY
)
(
SELECT
#{OBJID}
,#{objId}
,#{objId}
,CATEGORY_CD
,CUSTOMER_OBJID
,PRODUCT
@@ -7390,29 +7394,71 @@ SELECT
,CHG_USER_ID
,PLAN_DATE
,COMPLETE_DATE
,RESULT_CD
<choose>
<when test="overhaul_project_no != null and overhaul_project_no !='' ">
,#{overhaul_project_no}<!-- || '_' || #{overhaul_order} -->
</when>
<otherwise>
,MECHANICAL_TYPE || '-' ||
<!--
(SELECT (SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1))::integer+1 FROM PROJECT_MGMT WHERE PROJECT_NO NOT LIKE '%\_%' ESCAPE '\' ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC LIMIT 1)
-->
(SELECT CASE
WHEN PROJECT_NO ~ '[-\s][0-9]+$'
THEN REGEXP_REPLACE(PROJECT_NO, '.*[-\s]([0-9]+)$', '\1')::integer+1
ELSE NULL
END AS extracted_number
FROM PROJECT_MGMT
WHERE PROJECT_NO NOT LIKE '%\_%' ESCAPE '\'
ORDER BY extracted_number DESC NULLS LAST
LIMIT 1)
</otherwise>
</choose>
<!-- ,#{project_no} -->
<!-- ,(SELECT TITLE FROM PMS_WBS_TEMPLATE PWT WHERE PWT.OBJID = #{mechanical_type}) || '-' || (SELECT (SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1))::integer+1 FROM PROJECT_MGMT ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC LIMIT 1) -->
,RESULT_CD
<!-- 기존 PROJECT_NO 자동생성 로직 (주석처리)
<choose>
<when test="overhaul_project_no != null and overhaul_project_no !='' ">
,#{overhaul_project_no}
</when>
<otherwise>
,MECHANICAL_TYPE || '-' ||
(SELECT CASE
WHEN PROJECT_NO ~ '[-\s][0-9]+$'
THEN REGEXP_REPLACE(PROJECT_NO, '.*[-\s]([0-9]+)$', '\1')::integer+1
ELSE NULL
END AS extracted_number
FROM PROJECT_MGMT
WHERE PROJECT_NO NOT LIKE '%\_%' ESCAPE '\'
ORDER BY extracted_number DESC NULLS LAST
LIMIT 1)
</otherwise>
</choose>
-->
<!-- 신규 PROJECT_NO 생성 로직: 주문유형-제품구분-날짜-순번 형식 (예: R-AS-250302-001) -->
,(
SELECT
-- 주문유형 코드 (CATEGORY_CD를 영문 약어로 매핑)
CASE CODE_NAME(CATEGORY_CD)
WHEN '오버홀' THEN 'O'
WHEN '개조' THEN 'M'
WHEN '개발' THEN 'D'
WHEN '견적' THEN 'Q'
WHEN '수리' THEN 'R'
WHEN '판매' THEN 'S'
ELSE 'T'
END || '-' ||
-- 제품구분 코드 (PRODUCT의 CODE_NAME에서 슬래시 제거)
REPLACE(CODE_NAME(PRODUCT), '/', '') || '-' ||
-- 날짜 (YYMMDD)
TO_CHAR(CURRENT_DATE, 'YYMMDD') || '-' ||
-- 순번 (001, 002, ...)
LPAD(
COALESCE(
(
SELECT MAX(SUBSTRING(PROJECT_NO FROM '\d{3}$')::INTEGER) + 1
FROM PROJECT_MGMT
WHERE PROJECT_NO LIKE
CASE CODE_NAME(CATEGORY_CD)
WHEN '오버홀' THEN 'O'
WHEN '개조' THEN 'M'
WHEN '개발' THEN 'D'
WHEN '견적' THEN 'Q'
WHEN '수리' THEN 'R'
WHEN '판매' THEN 'S'
ELSE 'T'
END || '-' ||
REPLACE(CODE_NAME(PRODUCT), '/', '') || '-' ||
TO_CHAR(CURRENT_DATE, 'YYMMDD') || '-%'
),
1
)::TEXT,
3,
'0'
)
FROM CONTRACT_MGMT
WHERE OBJID = #{objId}
)
,PM_USER_ID
,#{contract_price}
,#{contract_price_currency}
@@ -7437,12 +7483,16 @@ SELECT
,EST_USER_ID
,EST_COMP_DATE
,EST_RESULT_CD
,AREA_CD
,MECHANICAL_TYPE
,#{overhaul_order}
,#{is_temp}
FROM CONTRACT_MGMT
WHERE OBJID=#{objId}
,AREA_CD
,MECHANICAL_TYPE
,#{overhaul_order}
,#{is_temp}
,#{part_objid}
,#{part_no}
,#{part_name}
,#{quantity}
FROM CONTRACT_MGMT
WHERE OBJID=#{objId}
)
</insert>
@@ -7549,7 +7599,12 @@ SELECT
,REQ_DEL_DATE = #{req_del_date}
,CONTRACT_COMPANY = #{contract_company}
,MANUFACTURE_PLANT = #{manufacture_plant}
,PART_OBJID = #{part_objid}
,PART_NO = #{part_no}
,PART_NAME = #{part_name}
,QUANTITY = #{quantity}
WHERE CONTRACT_OBJID = #{objId}
AND PART_OBJID = #{part_objid}
</update>
<delete id="deleteProjectMngInfo" parameterType="map">

View File

@@ -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<String, Object> paramMap){
Map resultMap = new HashMap();
try {
List<Map> 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;
}
}

View File

@@ -463,8 +463,9 @@
,TO_CHAR(REGDATE,'YYYY-MM-DD') AS REG_DATE
,WRITER
,(SELECT USER_NAME FROM USER_INFO AS O WHERE O.USER_ID = T.WRITER ) AS WRITER_NAME
,(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.OBJID AND DOC_TYPE='contractMgmt01' AND UPPER(STATUS) = 'ACTIVE') AS CU01_CNT
,(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.OBJID AND DOC_TYPE='contractMgmt02' AND UPPER(STATUS) = 'ACTIVE') AS CU02_CNT
,(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.OBJID AND DOC_TYPE='ORDER_DOC' AND UPPER(STATUS) = 'ACTIVE') AS CU01_CNT
<!-- ,(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.OBJID AND DOC_TYPE='contractMgmt01' AND UPPER(STATUS) = 'ACTIVE') AS CU01_CNT
,(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.OBJID AND DOC_TYPE='contractMgmt02' AND UPPER(STATUS) = 'ACTIVE') AS CU02_CNT -->
,(CASE WHEN (RESULT_CD is null or RESULT_CD ='') and (SPEC_RESULT_CD is null or RESULT_CD ='') and (EST_RESULT_CD is null or RESULT_CD ='') then '0'
ELSE 1
END
@@ -477,6 +478,7 @@
,CODE_NAME(CONTRACT_COMPANY) AS CONTRACT_COMPANY_NAME
,CONTRACT_DATE
,PO_NO
,ORDER_DATE
,MANUFACTURE_PLANT
,CODE_NAME(MANUFACTURE_PLANT) AS MANUFACTURE_PLANT_NAME
,CONTRACT_RESULT
@@ -521,6 +523,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 (
@@ -740,12 +755,15 @@
AND PAID_TYPE = #{paid_type}
</if>
<if test="search_partNo != null and search_partNo != ''">
AND UPPER(PART_NO) LIKE UPPER('%${search_partNo}%')
</if>
<if test="search_partName != null and search_partName != ''">
AND UPPER(PART_NAME) LIKE UPPER('%${search_partName}%')
<!-- 품번/품명 검색: PART_OBJID로 정확하게 검색 -->
<if test="search_partObjId != null and search_partObjId != ''">
AND EXISTS (
SELECT 1
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
AND CI.PART_OBJID = #{search_partObjId}
)
</if>
<if test="search_serialNo != null and search_serialNo != ''">
@@ -766,9 +784,9 @@
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{due_end_date}, 'YYYY-MM-DD')
</if>
ORDER BY REGDATE DESC
</select>
<select id="contractList_bak" parameterType="map" resultType="map">
</select>
<select id="contractList_bak" parameterType="map" resultType="map">
SELECT *
FROM (
SELECT CONTRACT_MGMT.*,ROW_NUMBER() OVER (ORDER BY CONTRACT_NO DESC) AS RNUM
@@ -3082,7 +3100,8 @@ SELECT
,#{writer}
FROM PMS_WBS_TASK_STANDARD AS T LEFT JOIN PMS_WBS_TEMPLATE AS T1
ON T.PARENT_OBJID = T1.OBJID
WHERE T1.TITLE=#{mechanical_type}
WHERE T1.OBJID = '1662715267'
<!-- WHERE T1.TITLE=#{mechanical_type} -->
<!-- WHERE T1.OBJID=#{mechanical_type} -->
<!-- WHERE T1.PRODUCT_OBJID=#{product} -->
)
@@ -3452,6 +3471,20 @@ ORDER BY ASM.SUPPLY_NAME
limit 1
</select>
<!-- 품목별 프로젝트 조회 (PART_OBJID 기준) -->
<select id="getProjectListByContractAndPartObjid" parameterType="map" resultType="map">
SELECT
PROJECT_NAME,
PART_OBJID,
PART_NO,
PART_NAME
FROM
PROJECT_MGMT
WHERE CONTRACT_OBJID = #{contractObjId}
AND PART_OBJID = #{part_objid}
LIMIT 1
</select>
<select id="overlapOrder" parameterType="map" resultType="map">
SELECT
T.*
@@ -3796,86 +3829,147 @@ ORDER BY ASM.SUPPLY_NAME
<!-- 견적서 템플릿 목록 조회 (CONTRACT_OBJID 기준) -->
<select id="getEstimateTemplateList" parameterType="map" resultType="map">
SELECT
OBJID AS "OBJID",
CONTRACT_OBJID AS "CONTRACT_OBJID",
TEMPLATE_TYPE AS "TEMPLATE_TYPE",
ET.OBJID AS "OBJID",
ET.CONTRACT_OBJID AS "CONTRACT_OBJID",
ET.TEMPLATE_TYPE AS "TEMPLATE_TYPE",
CASE
WHEN TEMPLATE_TYPE = '1' THEN '일반 견적서'
WHEN TEMPLATE_TYPE = '2' THEN '장비 견적서'
ELSE TEMPLATE_TYPE
WHEN ET.TEMPLATE_TYPE = '1' THEN '일반 견적서'
WHEN ET.TEMPLATE_TYPE = '2' THEN '장비 견적서'
ELSE ET.TEMPLATE_TYPE
END AS "TEMPLATE_TYPE_NAME",
ESTIMATE_NO AS "ESTIMATE_NO",
WRITER AS "WRITER",
TO_CHAR(REGDATE, 'YYYY-MM-DD HH24:MI') AS "REGDATE",
CHG_USER_ID AS "CHG_USER_ID",
TO_CHAR(CHGDATE, 'YYYY-MM-DD HH24:MI') AS "CHGDATE",
ROW_NUMBER() OVER (PARTITION BY TEMPLATE_TYPE ORDER BY REGDATE) AS "REVISION"
ET.ESTIMATE_NO AS "ESTIMATE_NO",
ET.WRITER AS "WRITER",
TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS "REGDATE",
ET.CHG_USER_ID AS "CHG_USER_ID",
TO_CHAR(ET.CHGDATE, 'YYYY-MM-DD HH24:MI') AS "CHGDATE",
ROW_NUMBER() OVER (PARTITION BY ET.TEMPLATE_TYPE ORDER BY ET.REGDATE) AS "REVISION",
COALESCE(
(SELECT CASE
WHEN A.STATUS = 'complete' THEN '결재완료'
WHEN A.STATUS = 'cancel' THEN '취소'
WHEN A.STATUS = 'reject' THEN '반려'
WHEN A.STATUS = 'inProcess' THEN '결재중'
ELSE '작성중'
END
FROM APPROVAL A
WHERE A.TARGET_OBJID::VARCHAR = ET.OBJID
AND A.TARGET_TYPE = 'CONTRACT_ESTIMATE'
ORDER BY A.REGDATE DESC
LIMIT 1),
'작성중'
) AS "APPR_STATUS"
FROM
ESTIMATE_TEMPLATE
ESTIMATE_TEMPLATE ET
WHERE
CONTRACT_OBJID = #{objId}
ORDER BY TEMPLATE_TYPE, REGDATE DESC
ET.CONTRACT_OBJID = #{objId}
ORDER BY ET.TEMPLATE_TYPE, ET.REGDATE DESC
</select>
<!-- 견적서 템플릿 데이터 조회 (ESTIMATE_TEMPLATE 테이블) -->
<select id="getEstimateTemplateData" parameterType="map" resultType="map">
SELECT
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR,
RECIPIENT,
ESTIMATE_NO,
CONTACT_PERSON,
GREETING_TEXT,
MODEL_NAME,
MODEL_CODE,
EXECUTOR_DATE,
NOTE1,
NOTE2,
NOTE3,
NOTE4,
WRITER,
REGDATE,
CHG_USER_ID,
CHGDATE
ET.OBJID,
ET.CONTRACT_OBJID,
ET.TEMPLATE_TYPE,
ET.EXECUTOR,
ET.RECIPIENT,
ET.ESTIMATE_NO,
ET.CONTACT_PERSON,
ET.GREETING_TEXT,
ET.MODEL_NAME,
ET.MODEL_CODE,
ET.EXECUTOR_DATE,
ET.NOTE1,
ET.NOTE2,
ET.NOTE3,
ET.NOTE4,
ET.NOTE_REMARKS,
ET.TOTAL_AMOUNT,
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.WRITER,
ET.REGDATE,
ET.CHG_USER_ID,
ET.CHGDATE,
CM.EXCHANGE_RATE,
CODE_NAME(CM.CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
COALESCE(
(SELECT CASE
WHEN A.STATUS = 'complete' THEN '결재완료'
WHEN A.STATUS = 'cancel' THEN '취소'
WHEN A.STATUS = 'reject' THEN '반려'
WHEN A.STATUS = 'inProcess' THEN '결재중'
ELSE '작성중'
END
FROM APPROVAL A
WHERE A.TARGET_OBJID::VARCHAR = ET.OBJID
AND A.TARGET_TYPE = 'CONTRACT_ESTIMATE'
ORDER BY A.REGDATE DESC
LIMIT 1),
'작성중'
) AS APPR_STATUS
FROM
ESTIMATE_TEMPLATE
ESTIMATE_TEMPLATE ET
LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID
WHERE
CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY REGDATE DESC
LIMIT 1
ET.CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND ET.TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY ET.REGDATE DESC
LIMIT 1
</select>
<!-- 견적서 템플릿 데이터 조회 (OBJID 기준) -->
<select id="getEstimateTemplateByObjId" parameterType="map" resultType="map">
SELECT
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR,
RECIPIENT,
ESTIMATE_NO,
CONTACT_PERSON,
GREETING_TEXT,
MODEL_NAME,
MODEL_CODE,
EXECUTOR_DATE,
NOTE1,
NOTE2,
NOTE3,
NOTE4,
WRITER,
TO_CHAR(REGDATE, 'YYYY-MM-DD HH24:MI') AS REGDATE,
CHG_USER_ID,
TO_CHAR(CHGDATE, 'YYYY-MM-DD HH24:MI') AS CHGDATE
ET.OBJID,
ET.CONTRACT_OBJID,
ET.TEMPLATE_TYPE,
ET.EXECUTOR,
ET.RECIPIENT,
ET.ESTIMATE_NO,
ET.CONTACT_PERSON,
ET.GREETING_TEXT,
ET.MODEL_NAME,
ET.MODEL_CODE,
ET.EXECUTOR_DATE,
ET.NOTE1,
ET.NOTE2,
ET.NOTE3,
ET.NOTE4,
ET.NOTE_REMARKS,
ET.TOTAL_AMOUNT,
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.WRITER,
TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS REGDATE,
ET.CHG_USER_ID,
TO_CHAR(ET.CHGDATE, 'YYYY-MM-DD HH24:MI') AS CHGDATE,
CM.EXCHANGE_RATE,
CODE_NAME(CM.CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
COALESCE(
(SELECT CASE
WHEN A.STATUS = 'complete' THEN '결재완료'
WHEN A.STATUS = 'cancel' THEN '취소'
WHEN A.STATUS = 'reject' THEN '반려'
WHEN A.STATUS = 'inProcess' THEN '결재중'
ELSE '작성중'
END
FROM APPROVAL A
WHERE A.TARGET_OBJID::VARCHAR = ET.OBJID
AND A.TARGET_TYPE = 'CONTRACT_ESTIMATE'
ORDER BY A.REGDATE DESC
LIMIT 1),
'작성중'
) AS APPR_STATUS
FROM
ESTIMATE_TEMPLATE
ESTIMATE_TEMPLATE ET
LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID
WHERE
OBJID = #{templateObjId}
ET.OBJID = #{templateObjId}
</select>
<!-- 견적서 템플릿 품목 조회 (TEMPLATE_OBJID 기준) -->
@@ -3940,35 +4034,45 @@ ORDER BY ASM.SUPPLY_NAME
MODEL_NAME,
MODEL_CODE,
EXECUTOR_DATE,
NOTE1,
NOTE2,
NOTE3,
NOTE4,
WRITER,
REGDATE,
CHG_USER_ID,
CHGDATE
) VALUES (
#{template_objid},
#{objId},
#{template_type},
#{executor},
#{recipient},
#{estimate_no},
#{contact_person},
#{greeting_text},
#{model_name},
#{model_code},
#{executor_date},
#{note1},
#{note2},
#{note3},
#{note4},
#{writer},
NOW(),
#{chg_user_id},
NOW()
)
NOTE1,
NOTE2,
NOTE3,
NOTE4,
NOTE_REMARKS,
TOTAL_AMOUNT,
TOTAL_AMOUNT_KRW,
MANAGER_NAME,
MANAGER_CONTACT,
WRITER,
REGDATE,
CHG_USER_ID,
CHGDATE
) VALUES (
#{template_objid},
#{objId},
#{template_type},
#{executor},
#{recipient},
#{estimate_no},
#{contact_person},
#{greeting_text},
#{model_name},
#{model_code},
#{executor_date},
#{note1},
#{note2},
#{note3},
#{note4},
#{note_remarks},
#{total_amount},
#{total_amount_krw},
#{manager_name},
#{manager_contact},
#{writer},
NOW(),
#{chg_user_id},
NOW()
)
</insert>
<!-- 견적서 템플릿 수정 -->
@@ -3983,14 +4087,19 @@ ORDER BY ASM.SUPPLY_NAME
MODEL_NAME = #{model_name},
MODEL_CODE = #{model_code},
EXECUTOR_DATE = #{executor_date},
NOTE1 = #{note1},
NOTE2 = #{note2},
NOTE3 = #{note3},
NOTE4 = #{note4},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
OBJID = #{template_objid}
NOTE1 = #{note1},
NOTE2 = #{note2},
NOTE3 = #{note3},
NOTE4 = #{note4},
NOTE_REMARKS = #{note_remarks},
TOTAL_AMOUNT = #{total_amount},
TOTAL_AMOUNT_KRW = #{total_amount_krw},
MANAGER_NAME = #{manager_name},
MANAGER_CONTACT = #{manager_contact},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
OBJID = #{template_objid}
</update>
<!-- 견적서 템플릿 품목 삭제 -->
@@ -4051,33 +4160,41 @@ ORDER BY ASM.SUPPLY_NAME
<!-- 최종 차수 견적서 조회 (메일 발송용) -->
<select id="getLatestEstimateTemplate" parameterType="map" resultType="map">
SELECT
OBJID AS "OBJID",
CONTRACT_OBJID AS "CONTRACT_OBJID",
TEMPLATE_TYPE AS "TEMPLATE_TYPE",
EXECUTOR AS "EXECUTOR",
RECIPIENT AS "RECIPIENT",
ESTIMATE_NO AS "ESTIMATE_NO",
CONTACT_PERSON AS "CONTACT_PERSON",
GREETING_TEXT AS "GREETING_TEXT",
MODEL_NAME AS "MODEL_NAME",
MODEL_CODE AS "MODEL_CODE",
EXECUTOR_DATE AS "EXECUTOR_DATE",
NOTE1 AS "NOTE1",
NOTE2 AS "NOTE2",
NOTE3 AS "NOTE3",
NOTE4 AS "NOTE4",
WRITER AS "WRITER",
TO_CHAR(REGDATE, 'YYYY-MM-DD HH24:MI') AS "REGDATE",
CHG_USER_ID AS "CHG_USER_ID",
TO_CHAR(CHGDATE, 'YYYY-MM-DD HH24:MI') AS "CHGDATE",
CATEGORIES_JSON AS "CATEGORIES_JSON"
FROM
ESTIMATE_TEMPLATE
WHERE
CONTRACT_OBJID = #{objId}
ET.OBJID AS "OBJID",
ET.CONTRACT_OBJID AS "CONTRACT_OBJID",
ET.TEMPLATE_TYPE AS "TEMPLATE_TYPE",
ET.EXECUTOR AS "EXECUTOR",
ET.RECIPIENT AS "RECIPIENT",
ET.ESTIMATE_NO AS "ESTIMATE_NO",
ET.CONTACT_PERSON AS "CONTACT_PERSON",
ET.GREETING_TEXT AS "GREETING_TEXT",
ET.MODEL_NAME AS "MODEL_NAME",
ET.MODEL_CODE AS "MODEL_CODE",
ET.EXECUTOR_DATE AS "EXECUTOR_DATE",
ET.NOTE1 AS "NOTE1",
ET.NOTE2 AS "NOTE2",
ET.NOTE3 AS "NOTE3",
ET.NOTE4 AS "NOTE4",
ET.NOTE_REMARKS AS "NOTE_REMARKS",
ET.TOTAL_AMOUNT AS "TOTAL_AMOUNT",
ET.TOTAL_AMOUNT_KRW AS "TOTAL_AMOUNT_KRW",
ET.MANAGER_NAME AS "MANAGER_NAME",
ET.MANAGER_CONTACT AS "MANAGER_CONTACT",
ET.WRITER AS "WRITER",
TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS "REGDATE",
ET.CHG_USER_ID AS "CHG_USER_ID",
TO_CHAR(ET.CHGDATE, 'YYYY-MM-DD HH24:MI') AS "CHGDATE",
ET.CATEGORIES_JSON AS "CATEGORIES_JSON",
CM.EXCHANGE_RATE AS "EXCHANGE_RATE",
CODE_NAME(CM.CONTRACT_CURRENCY) AS "CONTRACT_CURRENCY_NAME"
FROM
ESTIMATE_TEMPLATE ET
LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID
WHERE
ET.CONTRACT_OBJID = #{objId}
ORDER BY
TEMPLATE_TYPE,
REGDATE DESC
ET.TEMPLATE_TYPE,
ET.REGDATE DESC
LIMIT 1
</select>
@@ -4240,14 +4357,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>
<!-- 계약 품목 조회 -->
<select id="getContractItems" parameterType="map" resultType="map">
SELECT
CI.OBJID,
CI.CONTRACT_OBJID,
CI.PART_OBJID,
COALESCE(PM.PART_NO, CI.PART_NO) AS PART_NO,
COALESCE(PM.PART_NAME, CI.PART_NAME) AS PART_NAME,
STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SEQ) AS SERIAL_NO,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
CI.RETURN_REASON,
CI.ORDER_QUANTITY,
CI.ORDER_UNIT_PRICE,
CI.ORDER_SUPPLY_PRICE,
CI.ORDER_VAT,
CI.ORDER_TOTAL_AMOUNT
FROM
CONTRACT_ITEM CI
LEFT JOIN PART_MNG PM ON CI.PART_OBJID = PM.OBJID
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND CIS.STATUS = 'ACTIVE'
WHERE
CI.CONTRACT_OBJID = #{contractObjId}
AND CI.STATUS = 'ACTIVE'
GROUP BY
CI.OBJID,
CI.CONTRACT_OBJID,
CI.PART_OBJID,
CI.SEQ,
PM.PART_NO,
PM.PART_NAME,
CI.PART_NO,
CI.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
CI.RETURN_REASON,
CI.ORDER_QUANTITY,
CI.ORDER_UNIT_PRICE,
CI.ORDER_SUPPLY_PRICE,
CI.ORDER_VAT,
CI.ORDER_TOTAL_AMOUNT
ORDER BY CI.SEQ
</select>
<!-- 계약 품목별 수주 정보 업데이트 (단일 품목) -->
<update id="updateContractItemOrderInfo" parameterType="map">
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}
</update>
<!-- ====================================
@@ -4409,7 +4582,94 @@ ORDER BY ASM.SUPPLY_NAME
SEQ
</select>
<!-- 견적의 품목 전체 삭제 (상태 변경) -->
<!-- 품목 UPSERT (INSERT or UPDATE) -->
<insert id="upsertContractItem" parameterType="map">
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>
<!-- S/N UPSERT (INSERT or UPDATE) -->
<insert id="upsertContractItemSerial" parameterType="map">
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'
</insert>
<!-- 프론트에서 전달되지 않은 품목들 INACTIVE 처리 -->
<update id="inactivateRemovedItems" parameterType="map">
UPDATE CONTRACT_ITEM
SET STATUS = 'INACTIVE',
CHGDATE = NOW(),
CHG_USER_ID = #{userId}
WHERE CONTRACT_OBJID = #{contractObjId}
AND STATUS = 'ACTIVE'
<if test="currentItemObjIds != null and currentItemObjIds.size() > 0">
AND OBJID NOT IN
<foreach collection="currentItemObjIds" item="objId" open="(" separator="," close=")">
#{objId}
</foreach>
</if>
</update>
<!-- 견적의 품목 전체 삭제 (상태 변경) - 기존 방식, 호환성 유지 -->
<update id="deleteContractItems" parameterType="map">
UPDATE CONTRACT_ITEM
SET STATUS = 'INACTIVE',

View File

@@ -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가 명시적으로 있을 때만 수정, 없으면 항상 신규 작성
@@ -1610,18 +1621,24 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
contents.append("<head>");
contents.append("<meta charset='UTF-8'>");
contents.append("<style>");
contents.append("body { font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }");
contents.append(".estimate-container { max-width: 800px; background: white; margin: 0 auto; padding: 40px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }");
contents.append("body { font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }");
contents.append(".estimate-container { max-width: 1200px; background: white; margin: 0 auto; padding: 40px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }");
contents.append(".title { text-align: center; font-size: 24pt; font-weight: bold; letter-spacing: 10px; margin-bottom: 40px; border-bottom: 3px solid #333; padding-bottom: 20px; }");
contents.append(".info-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }");
contents.append(".info-table td { padding: 8px; border: 1px solid #000; font-size: 10pt; }");
contents.append(".info-table .label { background-color: #f0f0f0; font-weight: bold; width: 100px; text-align: center; }");
contents.append(".items-table { width: 100%; border-collapse: collapse; margin: 20px 0; }");
contents.append(".header-section { width: 100%; margin-bottom: 30px; overflow: hidden; }");
contents.append(".header-left { float: left; width: 48%; }");
contents.append(".header-right { float: right; width: 48%; }");
contents.append(".info-table { width: 100%; border-collapse: collapse; }");
contents.append(".info-table td { padding: 10px; border: 1px solid #000; font-size: 10pt; vertical-align: middle; }");
contents.append(".info-table .label { background-color: #f0f0f0; font-weight: bold; width: 120px; text-align: center; }");
contents.append(".company-info { border: 2px solid #000; padding: 20px; text-align: center; min-height: 200px; box-sizing: border-box; }");
contents.append(".company-stamp-img { max-width: 100%; height: auto; margin: 0 auto 15px; display: block; }");
contents.append(".manager-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd; text-align: left; font-size: 9pt; line-height: 1.8; }");
contents.append(".items-table { width: 100%; border-collapse: collapse; margin: 20px 0; clear: both; }");
contents.append(".items-table th, .items-table td { border: 1px solid #000; padding: 8px; font-size: 9pt; }");
contents.append(".items-table th { background-color: #e0e0e0; font-weight: bold; text-align: center; }");
contents.append(".text-right { text-align: right; }");
contents.append(".text-center { text-align: center; }");
contents.append(".note-section { margin-top: 30px; padding: 20px; background-color: #f9f9f9; border: 1px solid #ddd; }");
contents.append(".note-section { margin-top: 30px; padding: 20px; background-color: #f9f9f9; border: 1px solid #ddd; clear: both; }");
contents.append(".note-section h4 { margin-top: 0; font-size: 11pt; }");
contents.append(".note-section div { margin: 10px 0; font-size: 9pt; line-height: 1.6; }");
contents.append("</style>");
@@ -1645,14 +1662,77 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
String executorDate = CommonUtils.checkNull(estimateTemplate.get("executor_date"));
if("".equals(executorDate)) executorDate = CommonUtils.checkNull(estimateTemplate.get("EXECUTOR_DATE"));
// 기본 정보 테이블
// 담당자 정보
String managerName = CommonUtils.checkNull(estimateTemplate.get("manager_name"));
if("".equals(managerName)) managerName = CommonUtils.checkNull(estimateTemplate.get("MANAGER_NAME"));
if("".equals(managerName)) managerName = "영업부";
String managerContact = CommonUtils.checkNull(estimateTemplate.get("manager_contact"));
if("".equals(managerContact)) managerContact = CommonUtils.checkNull(estimateTemplate.get("MANAGER_CONTACT"));
// 헤더 섹션 (왼쪽: 기본정보, 오른쪽: 회사정보)
contents.append("<div class='header-section'>");
// 왼쪽: 기본 정보 테이블
contents.append("<div class='header-left'>");
contents.append("<table class='info-table'>");
contents.append("<tr><td class='label'>수신처</td><td>" + recipient + "</td></tr>");
contents.append("<tr><td class='label'>수신인</td><td>" + contactPerson + "</td></tr>");
contents.append("<tr><td class='label'>견적번호</td><td>" + estimateNo + "</td></tr>");
contents.append("<tr><td class='label'>영업번호</td><td>" + CommonUtils.checkNull(contractInfo.get("contract_no")) + "</td></tr>");
contents.append("<tr><td class='label'>발행일</td><td>" + executorDate + "</td></tr>");
contents.append("</table>");
contents.append("</div>");
// 오른쪽: 회사 정보
contents.append("<div class='header-right'>");
contents.append("<div class='company-info'>");
// 회사 도장 이미지 경로
String projectRoot = System.getProperty("user.dir");
String companyStampPath = projectRoot + "/WebContent/images/company_stamp.png";
// Docker 환경인 경우 다른 경로 시도
File stampFile = new File(companyStampPath);
if(!stampFile.exists()) {
companyStampPath = "/usr/local/tomcat/webapps/ROOT/images/company_stamp.png";
stampFile = new File(companyStampPath);
if(!stampFile.exists()) {
companyStampPath = "";
}
}
String stampBase64 = !"".equals(companyStampPath) ? encodeImageToBase64(companyStampPath) : "";
if(!"".equals(stampBase64)) {
// 이미지가 있으면 Base64로 인코딩된 이미지만 표시
contents.append("<img src='" + stampBase64 + "' alt='회사 도장' class='company-stamp-img'>");
} else {
// 이미지가 없으면 텍스트로 표시
contents.append("<div style='width:120px; height:120px; margin:0 auto 15px; border:2px solid #cc0000; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; background:linear-gradient(135deg, #fff 0%, #f5f5f5 100%);'>");
contents.append("<div style='text-align:center; font-size:11pt; font-weight:bold; color:#cc0000; line-height:1.3;'>");
contents.append("(주)알피에스<br>대표이사<br>이 종 현");
contents.append("</div>");
contents.append("</div>");
contents.append("<div style='font-size:10pt; margin-bottom:5px;'>RPS CO., LTD</div>");
contents.append("<div style='font-size:9pt; color:#666; margin-bottom:10px;'>대 표 이 사 이 종 현</div>");
contents.append("<div style='font-size:9pt; line-height:1.8;'>");
contents.append("<div>대전광역시 유성구 국제과학10로 8</div>");
contents.append("<div>TEL:(042)602-3300 FAX:(042)672-3399</div>");
contents.append("</div>");
}
// 담당자 정보는 항상 표시
contents.append("<div class='manager-info'>");
contents.append("<div><strong>담당자 :</strong> " + managerName + "</div>");
if(!"".equals(managerContact)) {
contents.append("<div><strong>연락처 :</strong> " + managerContact + "</div>");
}
contents.append("</div>");
contents.append("</div>"); // company-info 닫기
contents.append("</div>"); // header-right 닫기
contents.append("</div>"); // header-section 닫기
// 견적 품목
if(estimateItems != null && !estimateItems.isEmpty()){
@@ -1693,17 +1773,51 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
contents.append("</tr>");
}
// 합계
// 계 (총 합계)
String totalAmountStr = CommonUtils.checkNull(estimateTemplate.get("total_amount"));
if("".equals(totalAmountStr)) totalAmountStr = CommonUtils.checkNull(estimateTemplate.get("TOTAL_AMOUNT"));
// DB에 저장된 합계가 없으면 계산한 값 사용
if("".equals(totalAmountStr)){
totalAmountStr = String.format("%,d", totalAmount);
}
contents.append("<tr style='background-color:#f0f0f0; font-weight:bold;'>");
contents.append("<td colspan='6' class='text-right'>계</td>");
contents.append("<td class='text-right'>" + String.format("%,d", totalAmount) + "</td>");
contents.append("<td colspan='6' class='text-center'>계</td>");
contents.append("<td class='text-right'>" + totalAmountStr + "</td>");
contents.append("<td></td>");
contents.append("</tr>");
// 원화환산 공급가액
String totalAmountKrw = CommonUtils.checkNull(estimateTemplate.get("total_amount_krw"));
if("".equals(totalAmountKrw)) totalAmountKrw = CommonUtils.checkNull(estimateTemplate.get("TOTAL_AMOUNT_KRW"));
if(!"".equals(totalAmountKrw)){
contents.append("<tr style='background-color:#e8f4f8; font-weight:bold;'>");
contents.append("<td colspan='6' class='text-center'>원화환산 공급가액 (KRW)</td>");
contents.append("<td class='text-right'>" + totalAmountKrw + "</td>");
contents.append("<td></td>");
contents.append("</tr>");
}
// 테이블 내 비고
String noteRemarks = CommonUtils.checkNull(estimateTemplate.get("note_remarks"));
if("".equals(noteRemarks)) noteRemarks = CommonUtils.checkNull(estimateTemplate.get("NOTE_REMARKS"));
if(!"".equals(noteRemarks)){
contents.append("<tr>");
contents.append("<td colspan='8' style='height:100px; vertical-align:top; padding:10px; text-align:left;'>");
contents.append("<div style='font-weight:bold; margin-bottom:10px;'>&lt;비고&gt;</div>");
contents.append("<div style='white-space:pre-wrap;'>" + noteRemarks + "</div>");
contents.append("</td>");
contents.append("</tr>");
}
contents.append("</tbody>");
contents.append("</table>");
}
// 비고 (대소문자 모두 체크)
// 참조사항 (NOTE1~4)
String note1 = CommonUtils.checkNull(estimateTemplate.get("note1"));
if("".equals(note1)) note1 = CommonUtils.checkNull(estimateTemplate.get("NOTE1"));
@@ -1718,11 +1832,23 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
if(!"".equals(note1) || !"".equals(note2) || !"".equals(note3) || !"".equals(note4)){
contents.append("<div class='note-section'>");
contents.append("<h4>※ 비고</h4>");
if(!"".equals(note1)) contents.append("<div>1. " + note1 + "</div>");
if(!"".equals(note2)) contents.append("<div>2. " + note2 + "</div>");
if(!"".equals(note3)) contents.append("<div>3. " + note3 + "</div>");
if(!"".equals(note4)) contents.append("<div>4. " + note4 + "</div>");
contents.append("<h4>※ 참조사항</h4>");
int noteNum = 1;
if(!"".equals(note1)) {
contents.append("<div>" + noteNum + ". " + note1 + "</div>");
noteNum++;
}
if(!"".equals(note2)) {
contents.append("<div>" + noteNum + ". " + note2 + "</div>");
noteNum++;
}
if(!"".equals(note3)) {
contents.append("<div>" + noteNum + ". " + note3 + "</div>");
noteNum++;
}
if(!"".equals(note4)) {
contents.append("<div>" + noteNum + ". " + note4 + "</div>");
}
contents.append("</div>");
}
@@ -1733,6 +1859,42 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
return contents.toString();
}
/**
* 이미지 파일을 Base64로 인코딩
* @param imagePath 이미지 파일 경로
* @return Base64 인코딩된 문자열 (data:image/png;base64,...)
*/
private String encodeImageToBase64(String imagePath) {
try {
File imageFile = new File(imagePath);
if(!imageFile.exists()) {
return "";
}
java.io.FileInputStream fis = new java.io.FileInputStream(imageFile);
byte[] imageBytes = new byte[(int) imageFile.length()];
fis.read(imageBytes);
fis.close();
// Apache Commons Codec 사용 (Java 7 호환)
String base64 = org.apache.commons.codec.binary.Base64.encodeBase64String(imageBytes);
// 파일 확장자로 MIME 타입 결정
String mimeType = "image/png";
if(imagePath.toLowerCase().endsWith(".jpg") || imagePath.toLowerCase().endsWith(".jpeg")) {
mimeType = "image/jpeg";
} else if(imagePath.toLowerCase().endsWith(".gif")) {
mimeType = "image/gif";
}
return "data:" + mimeType + ";base64," + base64;
} catch(Exception e) {
System.out.println("이미지 Base64 인코딩 실패: " + e.getMessage());
e.printStackTrace();
return "";
}
}
/**
* 영업정보 조회 (수주등록용)
* @param contractObjId
@@ -1790,6 +1952,32 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
return resultMap != null ? resultMap : new HashMap<String, Object>();
}
/**
* 계약 품목 조회
* @param paramMap - contractObjId
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<Map> getContractItems(Map paramMap){
SqlSession sqlSession = null;
List<Map> items = new ArrayList<Map>();
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 +1988,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<String, Object> itemMap = new HashMap<String, Object>();
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"));
@@ -1818,51 +2058,96 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
//paramMap.put("contract_price_currency", contract_price_currency/project_cnt + "");
//paramMap.put("contract_price", contract_price/project_cnt + "");
if("0000964".equals(result_cd) || "0000968".equals(result_cd)){
resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap);
System.out.println("resultList:::"+resultList);
//resultList = sqlSession.selectOne("contractMgmt.getProjectCnt", paramMap);
if(null==resultList){
// for (int i=0; i<project_cnt; i++){
if("0000964".equals(result_cd) || "0000968".equals(result_cd)){
// 품목별로 프로젝트 생성
List<Map> contractItems = getContractItems(paramMap);
if(contractItems != null && !contractItems.isEmpty()) {
System.out.println("품목 개수: " + contractItems.size() + "개 - 품목별 프로젝트 생성 시작");
for(Map item : contractItems) {
// 품목별 프로젝트 존재 여부 확인
Map<String, Object> projectCheckParam = new HashMap<String, Object>();
projectCheckParam.put("contractObjId", contract_objid);
projectCheckParam.put("part_objid", item.get("PART_OBJID"));
resultList = sqlSession.selectOne("contractMgmt.getProjectListByContractAndPartObjid", projectCheckParam);
if(null == resultList) {
// 새 프로젝트 생성
Map<String, Object> projectParam = new HashMap<String, Object>();
projectParam.putAll(paramMap); // 기본 정보 복사
// 품목별 정보 설정
projectParam.put("OBJID", CommonUtils.createObjId());
projectParam.put("is_temp", '1');
projectParam.put("part_objid", item.get("PART_OBJID"));
projectParam.put("part_no", item.get("PART_NO"));
projectParam.put("part_name", item.get("PART_NAME"));
projectParam.put("quantity", item.get("ORDER_QUANTITY") != null ? item.get("ORDER_QUANTITY") : item.get("QUANTITY"));
projectParam.put("due_date", item.get("DUE_DATE"));
paramMap.put("OBJID", CommonUtils.createObjId());
paramMap.put("is_temp", '1');
//paramMap.put("facility_qty", '1');
if("0000170".equals(category_cd) || "0000171".equals(category_cd)){
paramMap.put("overhaul_project_no", target_project_no);
//paramMap.put("overhaul_order", overhaul_order+i);
}else{
projectParam.put("overhaul_project_no", target_project_no);
}
//프로젝트 등록
cnt = sqlSession.update("project.createProject", paramMap);
//프로젝트 TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectTask", paramMap);
//프로젝트 SETUP_TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap);
//project_no - unit 폴더 생성
//paramMap.put("OBJID", paramMap.get("OBJID"));
Map<String,Object> projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", paramMap);
paramMap.put("contract_objid", paramMap.get("contractObjId"));
paramMap.put("customer_product", paramMap.get("mechanical_type"));
List<Map<String,Object>> taskUnitList = (ArrayList)sqlSession.selectList("project.getWbsTaskListByProject", paramMap);
if(CommonUtils.isNotEmpty(taskUnitList) && !taskUnitList.isEmpty()){
String projectNo = (String)projectInfo.get("project_no");
String filepath = Constants.FILE_STORAGE+"\\PART_DATA\\";
for (Map<String, Object> map : taskUnitList) {
File file = new File(filepath+File.separator+projectNo+File.separator+CommonUtils.checkNull((String)map.get("unit_no"))+"-"+CommonUtils.checkNull((String)map.get("task_name")));
if(!file.exists()){
file.mkdirs();
System.out.println("프로젝트 생성 - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO") + ", 품명: " + item.get("PART_NAME"));
// 프로젝트 등록
cnt = sqlSession.update("project.createProject", projectParam);
// 프로젝트 TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectTask", projectParam);
// 프로젝트 SETUP_TASK 등록
cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", projectParam);
// project_no - unit 폴더 생성
Map<String,Object> projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam);
projectParam.put("contract_objid", contract_objid);
projectParam.put("customer_product", projectParam.get("mechanical_type"));
List<Map<String,Object>> taskUnitList = (ArrayList)sqlSession.selectList("project.getWbsTaskListByProject", projectParam);
if(CommonUtils.isNotEmpty(taskUnitList) && !taskUnitList.isEmpty()){
String projectNo = (String)projectInfo.get("project_no");
String filepath = Constants.FILE_STORAGE+"\\PART_DATA\\";
for (Map<String, Object> map : taskUnitList) {
File file = new File(filepath+File.separator+projectNo+File.separator+CommonUtils.checkNull((String)map.get("unit_no"))+"-"+CommonUtils.checkNull((String)map.get("task_name")));
if(!file.exists()){
file.mkdirs();
}
}
}
}
// }
} else {
// 기존 프로젝트 업데이트
Map<String, Object> updateParam = new HashMap<String, Object>();
updateParam.putAll(paramMap);
updateParam.put("part_objid", item.get("PART_OBJID"));
updateParam.put("part_no", item.get("PART_NO"));
updateParam.put("part_name", item.get("PART_NAME"));
updateParam.put("quantity", item.get("ORDER_QUANTITY") != null ? item.get("ORDER_QUANTITY") : item.get("QUANTITY"));
updateParam.put("due_date", item.get("DUE_DATE"));
System.out.println("프로젝트 업데이트 - PART_OBJID: " + item.get("PART_OBJID") + ", 품번: " + item.get("PART_NO"));
sqlSession.update("project.ModifyProjectByContract", updateParam);
}
}
} else {
System.out.println("품목이 없습니다 - 기존 방식으로 프로젝트 생성");
// 품목이 없는 경우 기존 방식대로 처리
resultList = sqlSession.selectOne("contractMgmt.getProjectListBycontractObjid", paramMap);
if(null==resultList){
paramMap.put("OBJID", CommonUtils.createObjId());
paramMap.put("is_temp", '1');
if("0000170".equals(category_cd) || "0000171".equals(category_cd)){
paramMap.put("overhaul_project_no", target_project_no);
}
cnt = sqlSession.update("project.createProject", paramMap);
cnt = sqlSession.insert("contractMgmt.insertProjectTask", paramMap);
cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", paramMap);
}else{
sqlSession.update("project.ModifyProjectByContract", paramMap);
}
}
}
// if(cnt > 0){
//계약완료 일시 메일
// if("0000964".equals(CommonUtils.checkNull(paramMap.get("contract_result")))){
@@ -1890,7 +2175,7 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
}
/**
* 품목 저장 (여러 건)
* 품목 저장 (여러 건) - UPSERT 방식
* @param contractObjId 견적 OBJID
* @param itemList 품목 목록
* @param userId 사용자 ID
@@ -1901,94 +2186,109 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
Map<String, Object> resultMap = new HashMap<String, Object>();
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<String, Object> deleteParam = new HashMap<String, Object>();
deleteParam.put("contractObjId", contractObjId);
deleteParam.put("userId", userId);
// 프론트에서 전달된 품목 OBJID 목록 수집
Set<String> currentItemObjIds = new HashSet<String>();
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<String, Object> item = itemList.get(i);
System.out.println("품목 " + (i+1) + " 처리 중: " + item);
// 품목 OBJID 생성 (CommonUtils 사용)
String itemObjId = CommonUtils.createObjId();
// 품목 저장
Map<String, Object> itemParam = new HashMap<String, Object>();
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<String, Object> itemParam = new HashMap<String, Object>();
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<String, Object> deleteSnParam = new HashMap<String, Object>();
deleteSnParam.put("itemObjId", itemObjId);
sqlSession.update("contractMgmt.deleteItemSerials", deleteSnParam);
// S/N 저장
@SuppressWarnings("unchecked")
List<Map<String, Object>> snList = (List<Map<String, Object>>) item.get("snList");
if (snList != null && !snList.isEmpty()) {
for (int j = 0; j < snList.size(); j++) {
Map<String, Object> 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<String, Object> snParam = new HashMap<String, Object>();
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<Map<String, Object>> snList = (List<Map<String, Object>>) item.get("snList");
if (snList != null && !snList.isEmpty()) {
for (int j = 0; j < snList.size(); j++) {
Map<String, Object> 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<String, Object> snParam = new HashMap<String, Object>();
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<String, Object> inactiveParam = new HashMap<String, Object>();
inactiveParam.put("contractObjId", contractObjId);
inactiveParam.put("currentItemObjIds", new ArrayList<String>(currentItemObjIds));
inactiveParam.put("userId", userId);
sqlSession.update("contractMgmt.inactivateRemovedItems", inactiveParam);
}
sqlSession.commit();