견적요청등록까지 수정한 부분

This commit is contained in:
leeheejin
2025-12-03 13:32:54 +09:00
parent aa1827ae9b
commit e895cc5ee7
7 changed files with 735 additions and 264 deletions

View File

@@ -1692,6 +1692,7 @@ SELECT
,A.CONTRACT_PRICE
,A.CONTRACT_PRICE_CURRENCY
,A.CONTRACT_CURRENCY
,CODE_NAME(A.CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME
,A.REGDATE
,A.WRITER
,A.CONTRACT_NO
@@ -5434,4 +5435,122 @@ WHERE
WHERE OBJID = #{customerObjId}::NUMERIC
</select>
<!-- 결재 필요 여부 확인 (재오더/신규수주/가격인하 체크) -->
<select id="checkApprovalRequired" parameterType="map" resultType="map">
/* contractMgmt.checkApprovalRequired */
WITH current_items AS (
SELECT
ETI.PART_OBJID,
COALESCE(PM.PART_NO, ETI.PART_NO) AS PART_NO,
CAST(COALESCE(NULLIF(ETI.SUPPLY_UNIT_PRICE, ''), '0') AS NUMERIC) AS CURRENT_PRICE
FROM ESTIMATE_TEMPLATE ET
INNER JOIN ESTIMATE_TEMPLATE_ITEM ETI ON ET.OBJID = ETI.TEMPLATE_OBJID
LEFT JOIN PART_MNG PM ON ETI.PART_OBJID = PM.OBJID
WHERE ET.CONTRACT_OBJID = #{contractObjId}::NUMERIC
AND ET.OBJID = (
SELECT OBJID FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{contractObjId}::NUMERIC
ORDER BY REGDATE DESC LIMIT 1
)
),
previous_orders AS (
SELECT
CI.PART_OBJID,
COALESCE(PM.PART_NO, CI.PART_NO) AS PART_NO,
MAX(CAST(COALESCE(NULLIF(ETI.SUPPLY_UNIT_PRICE, ''), '0') AS NUMERIC)) AS PREV_MAX_PRICE
FROM CONTRACT_MGMT CM
INNER JOIN CONTRACT_ITEM CI ON CM.OBJID = CI.CONTRACT_OBJID AND CI.STATUS = 'ACTIVE'
LEFT JOIN PART_MNG PM ON CI.PART_OBJID = PM.OBJID
LEFT JOIN ESTIMATE_TEMPLATE ET ON ET.CONTRACT_OBJID = CM.OBJID
LEFT JOIN ESTIMATE_TEMPLATE_ITEM ETI ON ET.OBJID = ETI.TEMPLATE_OBJID
AND (ETI.PART_OBJID = CI.PART_OBJID OR ETI.PART_NO = CI.PART_NO)
WHERE CM.CUSTOMER_OBJID = #{customerObjId}
AND CM.OBJID != #{contractObjId}::NUMERIC
AND CM.STATUS != 'cancel'
AND CM.CONTRACT_RESULT = '0000964'
GROUP BY CI.PART_OBJID, COALESCE(PM.PART_NO, CI.PART_NO)
)
SELECT
CASE
WHEN EXISTS (
SELECT 1 FROM current_items CI
WHERE NOT EXISTS (
SELECT 1 FROM previous_orders PO
WHERE (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
)
) THEN 'Y'
WHEN EXISTS (
SELECT 1 FROM current_items CI
INNER JOIN previous_orders PO
ON (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
WHERE CI.CURRENT_PRICE &lt; PO.PREV_MAX_PRICE
) THEN 'Y'
ELSE 'N'
END AS APPROVAL_REQUIRED,
CASE
WHEN EXISTS (
SELECT 1 FROM current_items CI
WHERE NOT EXISTS (
SELECT 1 FROM previous_orders PO
WHERE (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
)
) THEN 'NEW_ORDER'
WHEN EXISTS (
SELECT 1 FROM current_items CI
INNER JOIN previous_orders PO
ON (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
WHERE CI.CURRENT_PRICE &lt; PO.PREV_MAX_PRICE
) THEN 'PRICE_DOWN'
ELSE 'REORDER'
END AS REASON
</select>
<!-- 결재불필요 - APPROVAL 테이블 레코드 생성 -->
<insert id="insertApprovalNotRequired" parameterType="map">
/* contractMgmt.insertApprovalNotRequired */
INSERT INTO APPROVAL (
OBJID,
TARGET_TYPE,
TARGET_OBJID,
APPROVAL_TITLE,
WRITER,
REGDATE
) VALUES (
#{OBJID}::NUMERIC,
#{TARGET_TYPE},
#{TARGET_OBJID}::NUMERIC,
#{APPROVAL_TITLE},
#{WRITER},
NOW()
)
</insert>
<!-- 결재불필요 - ROUTE 테이블 레코드 생성 -->
<insert id="insertRouteNotRequired" parameterType="map">
/* contractMgmt.insertRouteNotRequired */
INSERT INTO ROUTE (
OBJID,
APPROVAL_OBJID,
TARGET_OBJID,
TARGET_TYPE,
STATUS,
ROUTE_SEQ,
WRITER,
REGDATE
) VALUES (
#{OBJID}::NUMERIC,
#{APPROVAL_OBJID}::NUMERIC,
#{TARGET_OBJID}::NUMERIC,
#{TARGET_TYPE},
#{STATUS},
#{ROUTE_SEQ}::NUMERIC,
#{WRITER},
NOW()
)
</insert>
</mapper>

View File

@@ -165,6 +165,8 @@ $(document).ready(function(){
var targetStatus = fnc_checkNull(selectedData[0].APPR_STATUS);
var status = fnc_checkNull(selectedData[0].STATUS);
var estObjId = fnc_checkNull(selectedData[0].EST_OBJID);
var contractObjId = fnc_checkNull(selectedData[0].OBJID);
var customerObjId = fnc_checkNull(selectedData[0].CUSTOMER_OBJID);
// 견적서 작성 여부 확인
if(estObjId == ""){
@@ -172,16 +174,92 @@ $(document).ready(function(){
return false;
}
if(targetStatus == "결재완료" || targetStatus == "결재중" || status == "cancel"){
if(targetStatus == "결재완료" || targetStatus == "결재중" || targetStatus == "결재불필요" || status == "cancel"){
Swal.fire("작성중/결재반려인 상태만 결재상신 가능합니다.");
return false;
}else{
if(confirm("결재상신 하시겠습니까?")){
//var objId = fnc_checkNull(selectedData[0].OBJID);
var objId = estObjId;
var title = encodeURIComponent(fnc_checkNull(selectedData[0].CONTRACT_NO));
window.open("/approval/registApproval.do?targetType=CONTRACT_ESTIMATE&targetObjId="+objId+"&approvalTitle="+title,"registApproval","width=700,height=700");
}
// 결재 필요 여부 체크 (재오더/신규수주/가격인하)
$.ajax({
url: "/contractMgmt/checkApprovalRequired.do",
type: "POST",
data: {
contractObjId: contractObjId,
customerObjId: customerObjId
},
dataType: "json",
success: function(response) {
if(response.result == "success") {
var approvalRequired = response.approvalRequired;
var reason = response.reason;
if(approvalRequired == "N") {
// 재오더 + 가격동일/인상 → 결재불필요
Swal.fire({
title: '결재 불필요',
text: '재오더(가격동일/인상)로 결재가 필요하지 않습니다. 결재불필요로 처리하시겠습니까?',
icon: 'info',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '확인',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
// 결재불필요 처리
$.ajax({
url: "/contractMgmt/setApprovalNotRequired.do",
type: "POST",
data: {
estObjId: estObjId
},
dataType: "json",
success: function(res) {
if(res.result == "success") {
Swal.fire("결재불필요로 처리되었습니다.");
fn_search(); // 목록 새로고침
} else {
Swal.fire("오류: " + res.message);
}
},
error: function() {
Swal.fire("결재불필요 처리 중 오류가 발생했습니다.");
}
});
}
});
} else {
// 신규수주 또는 가격인하 → 결재필요
var confirmMsg = "결재상신 하시겠습니까?";
if(reason == "신규수주") {
confirmMsg = "신규수주입니다. 결재상신 하시겠습니까?";
} else if(reason == "가격인하") {
confirmMsg = "가격인하 건입니다. 결재상신 하시겠습니까?";
}
if(confirm(confirmMsg)){
var objId = estObjId;
var title = encodeURIComponent(fnc_checkNull(selectedData[0].CONTRACT_NO));
window.open("/approval/registApproval.do?targetType=CONTRACT_ESTIMATE&targetObjId="+objId+"&approvalTitle="+title,"registApproval","width=700,height=700");
}
}
} else {
// API 오류 시 기존 방식으로 진행
if(confirm("결재상신 하시겠습니까?")){
var objId = estObjId;
var title = encodeURIComponent(fnc_checkNull(selectedData[0].CONTRACT_NO));
window.open("/approval/registApproval.do?targetType=CONTRACT_ESTIMATE&targetObjId="+objId+"&approvalTitle="+title,"registApproval","width=700,height=700");
}
}
},
error: function() {
// AJAX 오류 시 기존 방식으로 진행
if(confirm("결재상신 하시겠습니까?")){
var objId = estObjId;
var title = encodeURIComponent(fnc_checkNull(selectedData[0].CONTRACT_NO));
window.open("/approval/registApproval.do?targetType=CONTRACT_ESTIMATE&targetObjId="+objId+"&approvalTitle="+title,"registApproval","width=700,height=700");
}
}
});
}
}
});
@@ -200,9 +278,9 @@ $(document).ready(function(){
var objId = fnc_checkNull(selectedData[0].OBJID);
var estStatus = fnc_checkNull(selectedData[0].EST_STATUS);
// 결재완료 상태 확인
if(apprStatus !== "결재완료"){
Swal.fire("결재완료 견적서만 발송 가능합니다.");
// 결재완료 또는 결재불필요 상태 확인
if(apprStatus !== "결재완료" && apprStatus !== "결재불필요"){
Swal.fire("결재완료 또는 결재불필요 상태의 견적서만 발송 가능합니다.");
return false;
}
@@ -229,16 +307,30 @@ function addComma(num) {
var columns = [
{title:'EST_OBJID' ,field:'EST_OBJID' ,visible:false, frozen:true},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true,
// 1. 영업번호
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 70, widthGrow: 0.9, title : '영업번호', field : 'CONTRACT_NO', frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_projectConceptDetail(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '주문유형', field : 'CATEGORY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '접수일', field : 'RECEIPT_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '요청납기', field : 'EARLIEST_DUE_DATE',
// 2. 주문유형
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 60, widthGrow: 0.8, title : '주문유형', field : 'CATEGORY_NAME' },
// 3. 제품구분
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 60, widthGrow: 0.8, title : '제품구분', field : 'PRODUCT_NAME' },
// 4. 국내/해외
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 55, widthGrow: 0.7, title : '국내/해외', field : 'AREA_NAME' },
// 5. 접수일
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 75, widthGrow: 0.9, title : '접수일', field : 'RECEIPT_DATE' },
// 6. 고객사
{headerHozAlign : 'center', hozAlign : 'left', minWidth: 90, widthGrow: 1.5, title : '고객사', field : 'CUSTOMER_NAME' },
// 7. 유/무상
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 45, widthGrow: 0.6, title : '유/무상', field : 'PAID_TYPE' },
// 8. 품명 (품번으로 표시)
{headerHozAlign : 'center', hozAlign : 'left', minWidth: 90, widthGrow: 1.5, title : '품명', field : 'PART_NO' },
// 9. 요청납기
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 75, widthGrow: 0.9, title : '요청납기', field : 'EARLIEST_DUE_DATE',
formatter: function(cell, formatterParams, onRendered){
var dueDate = fnc_checkNull(cell.getValue());
var otherCount = fnc_checkNull(cell.getData().OTHER_DUE_DATE_COUNT);
@@ -253,88 +345,18 @@ var columns = [
return dueDate;
}
},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '견적수량', field : 'ESTIMATE_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
/* 수주수량 컬럼 주석처리
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주수량', field : 'ORDER_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
*/
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '제품구분', field : 'PRODUCT_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '88', title : '국내/해외', field : 'AREA_NAME' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' },
// {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 : '80', title : '수량', field : 'QUANTITY' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '120', title : '반납사유', field : 'RETURN_REASON_SUMMARY' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객요청사항', field : 'CUSTOMER_REQUEST' },
{headerHozAlign : 'center', hozAlign : 'right', width : '130', title : '공급가액', field : 'EST_TOTAL_AMOUNT',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
if(value === '' || value === '0') return '';
var rowData = cell.getRow().getData();
var currencyName = fnc_checkNull(rowData.CONTRACT_CURRENCY_NAME) || '';
// 통화 이름을 기호로 변환
var currencySymbol = '';
if(currencyName.includes('원') || currencyName === 'KRW') currencySymbol = '₩';
else if(currencyName.includes('달러') || currencyName === 'USD') currencySymbol = '$';
else if(currencyName.includes('유로') || currencyName === 'EUR') currencySymbol = '€';
else if(currencyName.includes('엔') || currencyName === 'JPY') currencySymbol = '¥';
else if(currencyName.includes('위안') || currencyName === 'CNY') currencySymbol = '¥';
// 통화 기호 제거 후 콤마 추가
var numericValue = String(value).replace(/[^0-9.]/g, '');
return currencySymbol + addComma(numericValue);
}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '150', title : '원화환산공급가액', field : 'EST_TOTAL_AMOUNT_KRW',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
if(value === '' || value === '0') return '';
// 통화 기호 제거 후 콤마 추가
var numericValue = String(value).replace(/[^0-9.]/g, '');
return '₩' + addComma(numericValue);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '견적현황', field : 'EST_STATUS',
// 10. 반납사유
{headerHozAlign : 'center', hozAlign : 'left', minWidth: 60, widthGrow: 0.8, title : '반납사유', field : 'RETURN_REASON_SUMMARY' },
// 11. 견적현황
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 50, widthGrow: 0.7, title : '견적현황', field : 'EST_STATUS',
formatter:fnc_subInfoValueFormatter,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_showEstimateList(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '결재상태', field : 'APPR_STATUS',
// 12. 결재상태
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 50, widthGrow: 0.7, title : '결재상태', field : 'APPR_STATUS',
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var APPROVAL_OBJID = fnc_checkNull(cell.getData().APPROVAL_OBJID);
@@ -342,11 +364,10 @@ var columns = [
approval_form(APPROVAL_OBJID,ROUTE_OBJID);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '메일발송', field : 'MAIL_SEND_STATUS',
// 13. 메일발송
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 50, widthGrow: 0.7, title : '메일발송', field : 'MAIL_SEND_STATUS',
formatter: function(cell, formatterParams, onRendered){
var status = fnc_checkNull(cell.getValue());
var sendDate = fnc_checkNull(cell.getData().MAIL_SEND_DATE);
if(status === 'Y'){
return '<span style="color:green;">발송완료</span>';
} else if(status === 'N'){
@@ -354,36 +375,65 @@ var columns = [
} else {
return '<span style="color:#999;">미발송</span>';
}
// if(status === 'Y'){
// return '<span style="color:green;">발송완료</span><br/><span style="font-size:11px;">' + sendDate + '</span>';
// } else if(status === 'N'){
// return '<span style="color:red;">발송실패</span><br/><span style="font-size:11px;">' + sendDate + '</span>';
// } else {
// return '<span style="color:#999;">미발송</span>';
// }
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '환종', field : 'CONTRACT_CURRENCY_NAME' },
{headerHozAlign : 'center', hozAlign : 'right', width : '80', title : '환율', field : 'EXCHANGE_RATE',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
}
},
// 14. 견적공급가액
{headerHozAlign : 'center', hozAlign : 'right', minWidth: 80, widthGrow: 1, title : '견적공급가액', field : 'EST_TOTAL_AMOUNT',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
if(value === '' || value === '0') return '';
var rowData = cell.getRow().getData();
var currencyName = fnc_checkNull(rowData.CONTRACT_CURRENCY_NAME) || '';
// 통화 이름을 기호로 변환
var currencySymbol = '';
if(currencyName.includes('원') || currencyName === 'KRW') currencySymbol = '₩';
else if(currencyName.includes('달러') || currencyName === 'USD') currencySymbol = '$';
else if(currencyName.includes('유로') || currencyName === 'EUR') currencySymbol = '€';
else if(currencyName.includes('엔') || currencyName === 'JPY') currencySymbol = '¥';
else if(currencyName.includes('위안') || currencyName === 'CNY') currencySymbol = '¥';
// 통화 기호 제거 후 콤마 추가
var numericValue = String(value).replace(/[^0-9.]/g, '');
return currencySymbol + addComma(numericValue);
}
},
// 15. 견적원화환산공급가액
{headerHozAlign : 'center', hozAlign : 'right', minWidth: 110, widthGrow: 1, title : '견적원화환산공급가액', field : 'EST_TOTAL_AMOUNT_KRW',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
if(value === '' || value === '0') return '';
// 통화 기호 제거 후 콤마 추가
var numericValue = String(value).replace(/[^0-9.]/g, '');
return '₩' + addComma(numericValue);
}
},
// 16. 견적환종
{headerHozAlign : 'center', hozAlign : 'center', minWidth: 45, widthGrow: 0.6, title : '견적환종', field : 'CONTRACT_CURRENCY_NAME' },
// 17. 견적환율
{headerHozAlign : 'center', hozAlign : 'right', minWidth: 50, widthGrow: 0.7, title : '견적환율', field : 'EXCHANGE_RATE',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
}
},
/* 아래는 주석처리된 컬럼들 - 필요시 활성화 */
/*
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '견적수량', field : 'ESTIMATE_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
{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>';
},
@@ -392,20 +442,7 @@ var columns = [
fn_showSerialNoPopup(serialNo);
}
},
{headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '견적단가', field : 'EST_PRICE' },
// {title:"영업정보(상세)", headerHozAlign:'center', //고객정보
// columns:[
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '차수', field : 'OVERHAUL_ORDER'},
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '국내/해외', field : 'AREA_NAME' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '기계형식', field : 'MECHANICAL_TYPE' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '200', title : '고객사 프로젝트명', field : 'CUSTOMER_PROJECT_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '예상납기일', field : 'DUE_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '입고지', field : 'LOCATION' },
*/
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '셋업지', field : 'SETUP' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '설비방향', field : 'FACILITY_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '설비대수', field : 'FACILITY_QTY' },
@@ -631,6 +668,8 @@ function fn_showEstimateList(contractObjId){
statusColor = 'red';
} else if(apprStatus === '작성중') {
statusColor = '#999';
} else if(apprStatus === '결재불필요') {
statusColor = '#6c757d'; // 회색
}
html += '<tr style="cursor: pointer;" onclick="fn_openEstimateByObjId(\'' + objid + '\', \'' + templateType + '\')">';
@@ -922,113 +961,106 @@ function openProjectFormPopUp(objId){
</div>
</div>
<div id="plmSearchZon">
<table>
<tr>
<td><label for="category_cd">주문유형</label></td>
<td>
<select name="category_cd" id="category_cd" style="width:150px" class="select2" autocomplete="off">
<div style="display: flex; flex-wrap: wrap; gap: 10px 15px; align-items: center;">
<!-- 주문유형 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="category_cd">주문유형</label>
<select name="category_cd" id="category_cd" style="width:130px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.category_cd}
</select>
</td>
<%--
<td><label for="product">제품구분</label></td>
<td>
</div>
<!-- 제품구분 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="product">제품구분</label>
<select name="product" id="product" style="width:130px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td>
</div>
<td><label for="area_cd">국내/해외</label></td>
<td>
<select name="area_cd" id="area_cd" style="width:130px" class="select2" autocomplete="off">
<!-- 국내/해외 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="area_cd">국내/해외</label>
<select name="area_cd" id="area_cd" style="width:100px" class="select2" autocomplete="off">
<option value="">선택</option>
<option value="0001220">국내</option>
<option value="0001221">해외</option>
</select>
</td>
--%>
<td><label for="customer_objid">고객사</label></td>
<td>
<select name="customer_objid" id="customer_objid" style="width:190px" class="select2" autocomplete="off">
</div>
<!-- 고객사 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="customer_objid">고객사</label>
<select name="customer_objid" id="customer_objid" style="width:180px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</td>
<%--
<td><label for="paid_type">유/무상</label></td>
<td>
<select name="paid_type" id="paid_type" style="width:130px" class="select2" autocomplete="off">
</div>
<!-- 유/무상 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="paid_type">유/무상</label>
<select name="paid_type" id="paid_type" style="width:100px" class="select2" autocomplete="off">
<option value="">선택</option>
<option value="paid">유상</option>
<option value="free">무상</option>
</select>
</td>
--%>
</div>
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<select name="search_partNo" id="search_partNo" class="select2-part" style="width: 100%;">
<!-- 품번 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_partNo">품번</label>
<select name="search_partNo" id="search_partNo" class="select2-part" style="width:130px;">
<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>
<select name="search_partName" id="search_partName" class="select2-part" style="width: 100%;">
</div>
<!-- 품명 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_partName">품명</label>
<select name="search_partName" id="search_partName" class="select2-part" style="width:130px;">
<option value="">품명 선택</option>
</select>
</td>
</div>
<td class="">
<label for="" class="">S/N</label>
</td>
<td>
<input type="text" name="search_serialNo" id="search_serialNo" value="${param.search_serialNo}"/>
</td>
<!-- S/N -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="search_serialNo">S/N</label>
<input type="text" name="search_serialNo" id="search_serialNo" style="width:120px;" value="${param.search_serialNo}"/>
</div>
<td><label for="">결재상태</label></td>
<td>
<select name="appr_status" id="appr_status" class="select2" autocomplete="off" style="width:130px">
<!-- 결재상태 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label for="appr_status">결재상태</label>
<select name="appr_status" id="appr_status" class="select2" autocomplete="off" style="width:100px">
<option value="">선택</option>
<%-- ${code_map.appr_status} --%>
<option value="작성중">작성중</option>
<option value="결재중">결재중</option>
<option value="반려">반려</option>
<option value="결재완료">결재완료</option>
<option value="결재불필요">결재불필요</option>
<option value="취소">취소</option>
</select>
</td>
</div>
<td class="">
<!-- 접수일 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label>접수일</label>
</td>
<td colspan="3">
<input type="text" name="receipt_start_date" id="receipt_start_date" style="width:90px;" autocomplete="off" value="${param.receipt_start_date}" class="date_icon">~
<input type="text" name="receipt_end_date" id="receipt_end_date" style="width:90px;" autocomplete="off" value="${param.receipt_end_date}" class="date_icon">
</td>
</tr>
<%--<tr>
<td class="">
</div>
<!-- 요청납기 -->
<div style="display: flex; align-items: center; gap: 5px;">
<label>요청납기</label>
</td>
<td>
<input type="text" name="due_start_date" id="due_start_date" style="width:90px;" autocomplete="off" value="${param.due_start_date}" class="date_icon">~
<input type="text" name="due_end_date" id="due_end_date" style="width:90px;" autocomplete="off" value="${param.due_end_date}" class="date_icon">
</td>
</tr>--%>
</table>
</div>
</div>
</div>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>

View File

@@ -63,11 +63,11 @@
$(".SELECT").show();
}
if("${info.CONTRACT_RESULT}" == "0000964"){//수주
$("#category_cd,#area_cd,#target_project_no,#customer_objid,#product,#contract_result,#overhaul_order").prop("disabled","disabled");
$("#mechanical_type,#facility_qty,#target_project_no_direct").prop("readonly", true);
//$("#overhaul_order,#mechanical_type,#facility_qty,#target_project_no_direct").prop("readonly", true);
}
// 수주 확정 후에도 정보 수정 가능하도록 주석처리
// if("${info.CONTRACT_RESULT}" == "0000964"){//수주
// $("#category_cd,#area_cd,#target_project_no,#customer_objid,#product,#contract_result,#overhaul_order").prop("disabled","disabled");
// $("#mechanical_type,#facility_qty,#target_project_no_direct").prop("readonly", true);
// }
if ("${actionType}" == "U") {
$("#btnSave").remove();
@@ -506,23 +506,23 @@
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_due_date[]" class="item-due-date date_icon" style="width:90%; padding:5px;" value="' + dueDate + '" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:95%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:95%;"></select>';
html += '</td>';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 고객요청사항 값 설정 (안전하게 text로 설정)
if(customerRequest) {
$("#" + itemId + " .item-customer-request").val(customerRequest);
}
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:95%;"></select>';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:95%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 고객요청사항 값 설정 (안전하게 text로 설정)
if(customerRequest) {
$("#" + itemId + " .item-customer-request").val(customerRequest);
}
// S/N 데이터 설정 - JSON 객체를 문자열로 변환하여 저장
var snJsonString = JSON.stringify(snJsonData);
@@ -1289,21 +1289,21 @@
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_due_date[]" class="item-due-date date_icon" style="width:90%; padding:5px;" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:95%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:95%;"></select>';
html += '</td>';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 품번/품명 옵션 채우기 (신규 추가)
fn_fillPartOptions(itemId, null, null, null);
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:95%;"></select>';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:95%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 품번/품명 옵션 채우기 (신규 추가)
fn_fillPartOptions(itemId, null, null, null);
// 추가된 행의 날짜 필드에 datepicker 적용
$("#" + itemId + " .date_icon").datepicker({
@@ -2015,8 +2015,8 @@
<col width="15%" /> <!-- S/N -->
<col width="7%" /> <!-- 수량 -->
<col width="10%" /> <!-- 요청납기 -->
<col width="20%" /> <!-- 고객요청사항 -->
<col width="15%" /> <!-- 반납사유 -->
<col width="15%" /> <!-- 반납사유 -->
<col width="20%" /> <!-- 고객요청사항 -->
<col width="5%" /> <!-- 삭제 -->
</colgroup>
<thead>
@@ -2027,8 +2027,8 @@
<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;">요청납기</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>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">삭제</th>
</tr>
</thead>

View File

@@ -67,16 +67,18 @@
.item-table thead th {
background-color: #f8f9fa;
padding: 10px;
padding: 6px;
text-align: center;
border: 1px solid #dee2e6;
font-weight: bold;
font-size: 11px;
}
.item-table tbody td {
padding: 8px;
padding: 5px;
border: 1px solid #dee2e6;
text-align: center;
font-size: 11px;
}
.item-table tbody td.text-left {
@@ -113,7 +115,7 @@ $(function() {
</head>
<body>
<div class="popup-container">
<!-- 영업정보 섹션 -->
<%-- 영업정보 섹션 (품목정보에서 다 표시하므로 주석처리)
<div class="section-title">영업정보</div>
<table class="info-table">
<colgroup>
@@ -122,68 +124,97 @@ $(function() {
<col width="15%">
<col width="35%">
</colgroup>
<tr>
<th>주문유형</th>
<td>${info.CATEGORY_NAME}</td>
<th>제품구분</th>
<td>${info.PRODUCT_NAME}</td>
</tr>
<tr>
<th>유/무상</th>
<td>${info.PAID_TYPE_NAME}</td>
<th>접수일</th>
<td>${info.RECEIPT_DATE}</td>
</tr>
<tr>
<th>국내/해외</th>
<td>${info.AREA_NAME}</td>
<th>고객사</th>
<td>${info.CUSTOMER_NAME}</td>
</tr>
<tr>
<th>주문유형</th>
<td>${info.CATEGORY_NAME}</td>
<th>제품구분</th>
<td>${info.PRODUCT_NAME}</td>
</tr>
<tr>
<th>국내/해외</th>
<td>${info.AREA_NAME}</td>
<th>고객사</th>
<td>${info.CUSTOMER_NAME}</td>
</tr>
<tr>
<th>유/무상</th>
<td>${info.PAID_TYPE_NAME}</td>
<th>견적환종</th>
<td>${info.CONTRACT_CURRENCY_NAME}</td>
</tr>
</table>
--%>
<!-- 품목정보 섹션 -->
<div class="section-title">품목정보</div>
<table class="item-table">
<colgroup>
<col width="3%">
<col width="7%">
<col width="7%">
<col width="6%">
<col width="10%">
<col width="5%">
<col width="15%">
<col width="25%">
<col width="15%">
<col width="6%">
<col width="10%">
<col width="15%">
<col width="15%">
<col width="8%">
<col width="8%">
<col width="7%">
<col width="8%">
</colgroup>
<thead>
<tr>
<th>No</th>
<th>주문유형</th>
<th>제품구분</th>
<th>국내/해외</th>
<th>고객사</th>
<th>유/무상</th>
<th>견적환종</th>
<th>품번</th>
<th>품명</th>
<th>S/N</th>
<th>요청납기</th>
<th>고객요청사항</th>
<th>반납사유</th>
<th>고객요청사항</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty itemList}">
<tr>
<td colspan="7" style="text-align:center; padding:30px; color:#999;">
등록된 품목이 없습니다.
</td>
<td>1</td>
<td>${info["CATEGORY_NAME"]}</td>
<td>${info["PRODUCT_NAME"]}</td>
<td>${info["AREA_NAME"]}</td>
<td>${info["CUSTOMER_NAME"]}</td>
<td>${info["PAID_TYPE_NAME"]}</td>
<td>${info["CONTRACT_CURRENCY_NAME"]}</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${itemList}" var="item" varStatus="status">
<tr>
<td>${status.count}</td>
<td>${info["CATEGORY_NAME"]}</td>
<td>${info["PRODUCT_NAME"]}</td>
<td>${info["AREA_NAME"]}</td>
<td>${info["CUSTOMER_NAME"]}</td>
<td>${info["PAID_TYPE_NAME"]}</td>
<td>${info["CONTRACT_CURRENCY_NAME"]}</td>
<td>${item.part_no}</td>
<td class="text-left">${item.part_name}</td>
<td>${item.serial_nos}</td>
<td>${item.due_date}</td>
<td class="text-left">${item.customer_request}</td>
<td>${item.return_reason}</td>
<td class="text-left">${item.customer_request}</td>
</tr>
</c:forEach>
</c:otherwise>

View File

@@ -2794,4 +2794,98 @@ public class ContractMgmtController {
return resultMap;
}
/**
* 결재 필요 여부 확인 (재오더/신규수주/가격인하 체크)
* - 재오더 + 가격동일/인상: 결재불필요
* - 신규수주 또는 가격인하: 결재필요
* @param request
* @param paramMap - contractObjId, customerObjId
* @return approvalRequired (Y/N), reason (신규수주/가격인하/재오더)
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/checkApprovalRequired.do", method=RequestMethod.POST)
public Map<String, Object> checkApprovalRequired(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
String contractObjId = CommonUtils.checkNull(paramMap.get("contractObjId"));
String customerObjId = CommonUtils.checkNull(paramMap.get("customerObjId"));
if(StringUtils.isBlank(contractObjId)) {
resultMap.put("result", "error");
resultMap.put("message", "견적 OBJID가 없습니다.");
return resultMap;
}
if(StringUtils.isBlank(customerObjId)) {
resultMap.put("result", "error");
resultMap.put("message", "고객사 OBJID가 없습니다.");
return resultMap;
}
paramMap.put("contractObjId", contractObjId);
paramMap.put("customerObjId", customerObjId);
Map<String, Object> checkResult = contractMgmtService.checkApprovalRequired(paramMap);
if(checkResult != null) {
resultMap.put("result", "success");
resultMap.put("approvalRequired", CommonUtils.checkNull(checkResult.get("APPROVAL_REQUIRED")));
resultMap.put("reason", CommonUtils.checkNull(checkResult.get("REASON")));
} else {
// 조회 결과가 없으면 신규수주로 간주 → 결재필요
resultMap.put("result", "success");
resultMap.put("approvalRequired", "Y");
resultMap.put("reason", "신규수주");
}
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* 결재불필요 처리 (재오더 + 가격동일/인상인 경우)
* 결재 프로세스 없이 바로 결재불필요 상태로 변경
* @param request
* @param paramMap - estObjId (견적서 OBJID)
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/setApprovalNotRequired.do", method=RequestMethod.POST)
public Map<String, Object> setApprovalNotRequired(HttpSession session, HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
String estObjId = CommonUtils.checkNull(paramMap.get("estObjId"));
if(StringUtils.isBlank(estObjId)) {
resultMap.put("result", "error");
resultMap.put("message", "견적서 OBJID가 없습니다.");
return resultMap;
}
paramMap.put("estObjId", estObjId);
paramMap.put("userId", userId);
contractMgmtService.setApprovalNotRequired(paramMap);
resultMap.put("result", "success");
resultMap.put("message", "결재불필요로 처리되었습니다.");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
}

View File

@@ -1692,6 +1692,7 @@ SELECT
,A.CONTRACT_PRICE
,A.CONTRACT_PRICE_CURRENCY
,A.CONTRACT_CURRENCY
,CODE_NAME(A.CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME
,A.REGDATE
,A.WRITER
,A.CONTRACT_NO
@@ -5434,4 +5435,122 @@ WHERE
WHERE OBJID = #{customerObjId}::NUMERIC
</select>
<!-- 결재 필요 여부 확인 (재오더/신규수주/가격인하 체크) -->
<select id="checkApprovalRequired" parameterType="map" resultType="map">
/* contractMgmt.checkApprovalRequired */
WITH current_items AS (
SELECT
ETI.PART_OBJID,
COALESCE(PM.PART_NO, ETI.PART_NO) AS PART_NO,
CAST(COALESCE(NULLIF(ETI.SUPPLY_UNIT_PRICE, ''), '0') AS NUMERIC) AS CURRENT_PRICE
FROM ESTIMATE_TEMPLATE ET
INNER JOIN ESTIMATE_TEMPLATE_ITEM ETI ON ET.OBJID = ETI.TEMPLATE_OBJID
LEFT JOIN PART_MNG PM ON ETI.PART_OBJID = PM.OBJID
WHERE ET.CONTRACT_OBJID = #{contractObjId}::NUMERIC
AND ET.OBJID = (
SELECT OBJID FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{contractObjId}::NUMERIC
ORDER BY REGDATE DESC LIMIT 1
)
),
previous_orders AS (
SELECT
CI.PART_OBJID,
COALESCE(PM.PART_NO, CI.PART_NO) AS PART_NO,
MAX(CAST(COALESCE(NULLIF(ETI.SUPPLY_UNIT_PRICE, ''), '0') AS NUMERIC)) AS PREV_MAX_PRICE
FROM CONTRACT_MGMT CM
INNER JOIN CONTRACT_ITEM CI ON CM.OBJID = CI.CONTRACT_OBJID AND CI.STATUS = 'ACTIVE'
LEFT JOIN PART_MNG PM ON CI.PART_OBJID = PM.OBJID
LEFT JOIN ESTIMATE_TEMPLATE ET ON ET.CONTRACT_OBJID = CM.OBJID
LEFT JOIN ESTIMATE_TEMPLATE_ITEM ETI ON ET.OBJID = ETI.TEMPLATE_OBJID
AND (ETI.PART_OBJID = CI.PART_OBJID OR ETI.PART_NO = CI.PART_NO)
WHERE CM.CUSTOMER_OBJID = #{customerObjId}
AND CM.OBJID != #{contractObjId}::NUMERIC
AND CM.STATUS != 'cancel'
AND CM.CONTRACT_RESULT = '0000964'
GROUP BY CI.PART_OBJID, COALESCE(PM.PART_NO, CI.PART_NO)
)
SELECT
CASE
WHEN EXISTS (
SELECT 1 FROM current_items CI
WHERE NOT EXISTS (
SELECT 1 FROM previous_orders PO
WHERE (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
)
) THEN 'Y'
WHEN EXISTS (
SELECT 1 FROM current_items CI
INNER JOIN previous_orders PO
ON (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
WHERE CI.CURRENT_PRICE &lt; PO.PREV_MAX_PRICE
) THEN 'Y'
ELSE 'N'
END AS APPROVAL_REQUIRED,
CASE
WHEN EXISTS (
SELECT 1 FROM current_items CI
WHERE NOT EXISTS (
SELECT 1 FROM previous_orders PO
WHERE (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
)
) THEN 'NEW_ORDER'
WHEN EXISTS (
SELECT 1 FROM current_items CI
INNER JOIN previous_orders PO
ON (PO.PART_OBJID = CI.PART_OBJID AND CI.PART_OBJID IS NOT NULL)
OR (PO.PART_NO = CI.PART_NO AND CI.PART_NO IS NOT NULL AND CI.PART_NO != '')
WHERE CI.CURRENT_PRICE &lt; PO.PREV_MAX_PRICE
) THEN 'PRICE_DOWN'
ELSE 'REORDER'
END AS REASON
</select>
<!-- 결재불필요 - APPROVAL 테이블 레코드 생성 -->
<insert id="insertApprovalNotRequired" parameterType="map">
/* contractMgmt.insertApprovalNotRequired */
INSERT INTO APPROVAL (
OBJID,
TARGET_TYPE,
TARGET_OBJID,
APPROVAL_TITLE,
WRITER,
REGDATE
) VALUES (
#{OBJID}::NUMERIC,
#{TARGET_TYPE},
#{TARGET_OBJID}::NUMERIC,
#{APPROVAL_TITLE},
#{WRITER},
NOW()
)
</insert>
<!-- 결재불필요 - ROUTE 테이블 레코드 생성 -->
<insert id="insertRouteNotRequired" parameterType="map">
/* contractMgmt.insertRouteNotRequired */
INSERT INTO ROUTE (
OBJID,
APPROVAL_OBJID,
TARGET_OBJID,
TARGET_TYPE,
STATUS,
ROUTE_SEQ,
WRITER,
REGDATE
) VALUES (
#{OBJID}::NUMERIC,
#{APPROVAL_OBJID}::NUMERIC,
#{TARGET_OBJID}::NUMERIC,
#{TARGET_TYPE},
#{STATUS},
#{ROUTE_SEQ}::NUMERIC,
#{WRITER},
NOW()
)
</insert>
</mapper>

View File

@@ -3629,4 +3629,80 @@ private String encodeImageToBase64(String imagePath) {
System.out.println("Service 최종 반환값: " + projectInfo);
return projectInfo;
}
/**
* 결재 필요 여부 확인 (재오더/신규수주/가격인하 체크)
* @param paramMap - contractObjId, customerObjId
* @return approvalRequired (Y/N), reason (신규수주/가격인하/재오더)
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map<String, Object> checkApprovalRequired(Map paramMap) {
SqlSession sqlSession = null;
Map<String, Object> result = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(true);
result = sqlSession.selectOne("contractMgmt.checkApprovalRequired", paramMap);
if(result != null) {
result = CommonUtils.keyChangeUpperMap(result);
}
} catch(Exception e) {
System.out.println("❌ checkApprovalRequired 오류: " + e.getMessage());
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return result;
}
/**
* 결재불필요 처리 (재오더 + 가격동일/인상인 경우)
* APPROVAL 테이블에 결재불필요 상태로 레코드 생성
* @param paramMap - estObjId, userId
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setApprovalNotRequired(Map paramMap) throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String estObjId = CommonUtils.checkNull(paramMap.get("estObjId"));
String userId = CommonUtils.checkNull(paramMap.get("userId"));
// APPROVAL 테이블에 결재불필요 레코드 생성
Map<String, Object> approvalParam = new HashMap<String, Object>();
approvalParam.put("OBJID", CommonUtils.createObjId());
approvalParam.put("TARGET_TYPE", "CONTRACT_ESTIMATE");
approvalParam.put("TARGET_OBJID", estObjId);
approvalParam.put("APPROVAL_TITLE", "결재불필요");
approvalParam.put("WRITER", userId);
sqlSession.insert("contractMgmt.insertApprovalNotRequired", approvalParam);
// ROUTE 테이블에 결재불필요 상태 레코드 생성
Map<String, Object> routeParam = new HashMap<String, Object>();
routeParam.put("OBJID", CommonUtils.createObjId());
routeParam.put("APPROVAL_OBJID", approvalParam.get("OBJID"));
routeParam.put("TARGET_OBJID", estObjId);
routeParam.put("TARGET_TYPE", "CONTRACT_ESTIMATE");
routeParam.put("STATUS", "notRequired"); // 결재불필요 상태
routeParam.put("ROUTE_SEQ", "1");
routeParam.put("WRITER", userId);
sqlSession.insert("contractMgmt.insertRouteNotRequired", routeParam);
sqlSession.commit();
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
throw e;
} finally {
if(sqlSession != null) sqlSession.close();
}
}
}