fix: 견적서 저장/조회 버그 수정 및 여러 차수 작성 기능 추가

- 견적현황 undefined 문제 해결 (EST_STATUS 필드 추가)
- 견적서 목록 팝업 대문자 키 이름 강제
- 견적서 데이터 로드 엔드포인트 추가 (getEstimateTemplateDataByObjId)
- 한 영업번호에 여러 견적서 작성 가능하도록 수정
- PostgreSQL 타입 캐스팅 에러 수정
- 품목 저장 시 서브쿼리 다중 행 에러 수정
This commit is contained in:
2025-10-16 10:27:23 +09:00
parent b0383d3479
commit 81f071cc34
6 changed files with 794 additions and 164 deletions

View File

@@ -486,20 +486,27 @@
,CODE_NAME(AREA_CD) AS AREA_NAME
,MECHANICAL_TYPE
,OVERHAUL_ORDER
,PAID_TYPE
,CASE
WHEN PAID_TYPE = 'paid' THEN '유상'
WHEN PAID_TYPE = 'free' THEN '무상'
ELSE PAID_TYPE
END AS PAID_TYPE
,RECEIPT_DATE
,PART_NO
,PART_NAME
,SERIAL_NO
,QUANTITY
,CUSTOMER_REQUEST
,EXCHANGE_RATE
,A.APPR_STATUS
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
FROM
CONTRACT_MGMT AS T
LEFT OUTER JOIN
,QUANTITY
,CUSTOMER_REQUEST
,EXCHANGE_RATE
,EST_PRICE
,EST_SUPPLY_PRICE
,(SELECT COUNT(1) FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID) AS EST_STATUS
,A.APPR_STATUS
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
FROM
CONTRACT_MGMT AS T
LEFT OUTER JOIN
(
SELECT
B.OBJID AS ROUTE_OBJID,
@@ -3678,7 +3685,11 @@ ORDER BY ASM.SUPPLY_NAME
CUSTOMER_PROJECT_NAME,
AREA_CD,
CODE_NAME(AREA_CD) AS AREA_NAME,
PAID_TYPE,
CASE
WHEN PAID_TYPE = 'paid' THEN '유상'
WHEN PAID_TYPE = 'free' THEN '무상'
ELSE PAID_TYPE
END AS PAID_TYPE,
RECEIPT_DATE,
PART_NO,
PART_NAME,
@@ -3696,7 +3707,31 @@ ORDER BY ASM.SUPPLY_NAME
FROM
CONTRACT_MGMT AS T
WHERE
OBJID = #{objId}::NUMERIC
OBJID::VARCHAR = #{objId}
</select>
<!-- 견적서 템플릿 목록 조회 (CONTRACT_OBJID 기준) -->
<select id="getEstimateTemplateList" parameterType="map" resultType="map">
SELECT
OBJID AS "OBJID",
CONTRACT_OBJID AS "CONTRACT_OBJID",
TEMPLATE_TYPE AS "TEMPLATE_TYPE",
CASE
WHEN TEMPLATE_TYPE = '1' THEN '일반 견적서'
WHEN TEMPLATE_TYPE = '2' THEN '장비 견적서'
ELSE 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"
FROM
ESTIMATE_TEMPLATE
WHERE
CONTRACT_OBJID = #{objId}
ORDER BY TEMPLATE_TYPE, REGDATE DESC
</select>
<!-- 견적서 템플릿 데이터 조회 (ESTIMATE_TEMPLATE 테이블) -->
@@ -3724,7 +3759,7 @@ ORDER BY ASM.SUPPLY_NAME
FROM
ESTIMATE_TEMPLATE
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND TEMPLATE_TYPE = #{template_type}
</if>
@@ -3732,6 +3767,56 @@ ORDER BY ASM.SUPPLY_NAME
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
FROM
ESTIMATE_TEMPLATE
WHERE
OBJID = #{templateObjId}
</select>
<!-- 견적서 템플릿 품목 조회 (TEMPLATE_OBJID 기준) -->
<select id="getEstimateTemplateItemsByTemplateObjId" parameterType="map" resultType="map">
SELECT
OBJID,
TEMPLATE_OBJID,
SEQ,
CATEGORY,
DESCRIPTION,
SPECIFICATION,
QUANTITY,
UNIT,
UNIT_PRICE,
AMOUNT,
NOTE,
REMARK
FROM
ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID = #{templateObjId}
ORDER BY SEQ
</select>
<!-- 견적서 템플릿 품목 조회 -->
<select id="getEstimateTemplateItems" parameterType="map" resultType="map">
SELECT
@@ -3753,7 +3838,7 @@ ORDER BY ASM.SUPPLY_NAME
TEMPLATE_OBJID IN (
SELECT OBJID
FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{objId}::NUMERIC
WHERE CONTRACT_OBJID = #{objId}
)
ORDER BY SEQ
</select>
@@ -3761,6 +3846,7 @@ ORDER BY ASM.SUPPLY_NAME
<!-- 견적서 템플릿 저장 -->
<insert id="insertEstimateTemplate" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR,
@@ -3780,7 +3866,8 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID,
CHGDATE
) VALUES (
#{objId}::NUMERIC,
#{template_objid},
#{objId},
#{template_type},
#{executor},
#{recipient},
@@ -3820,7 +3907,7 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
AND TEMPLATE_TYPE = #{template_type}
</update>
@@ -3828,11 +3915,7 @@ ORDER BY ASM.SUPPLY_NAME
<delete id="deleteEstimateTemplateItems" parameterType="map">
DELETE FROM ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID IN (
SELECT OBJID
FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{objId}::NUMERIC
)
TEMPLATE_OBJID = #{template_objid}
</delete>
<!-- 견적서 템플릿 품목 저장 -->
@@ -3851,17 +3934,25 @@ ORDER BY ASM.SUPPLY_NAME
REMARK
)
SELECT
(SELECT OBJID FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = #{objId}::NUMERIC AND TEMPLATE_TYPE = #{template_type}),
(json_array_elements(#{items_json}::json)->>'seq')::INTEGER,
json_array_elements(#{items_json}::json)->>'category',
json_array_elements(#{items_json}::json)->>'description',
json_array_elements(#{items_json}::json)->>'specification',
json_array_elements(#{items_json}::json)->>'quantity',
json_array_elements(#{items_json}::json)->>'unit',
(json_array_elements(#{items_json}::json)->>'unit_price')::NUMERIC,
(json_array_elements(#{items_json}::json)->>'amount')::NUMERIC,
json_array_elements(#{items_json}::json)->>'note',
json_array_elements(#{items_json}::json)->>'remark'
#{template_objid},
(item->>'seq')::INTEGER,
item->>'category',
item->>'description',
item->>'specification',
item->>'quantity',
item->>'unit',
CASE
WHEN item->>'unit_price' = '' THEN NULL
ELSE (item->>'unit_price')::NUMERIC
END,
CASE
WHEN item->>'amount' = '' THEN NULL
ELSE (item->>'amount')::NUMERIC
END,
item->>'note',
item->>'remark'
FROM json_array_elements(#{items_json}::json) AS item
WHERE COALESCE(item->>'description', '') != ''
</insert>
<!-- 견적서 템플릿 카테고리 업데이트 (장비 견적서용) -->
@@ -3872,7 +3963,7 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
AND TEMPLATE_TYPE = #{template_type}
</update>

View File

@@ -71,24 +71,25 @@ $(document).ready(function(){
// 견적서 양식 선택 팝업
Swal.fire({
title: '견적서 양식을 선택하세요',
icon: 'question',
html: '<div style="margin: 20px 0;">' +
'<button onclick="window.openTemplate1()" class="swal2-confirm swal2-styled" style="margin: 0 5px; background-color: #3085d6;">일반 견적서</button>' +
'<button onclick="window.openTemplate2()" class="swal2-confirm swal2-styled" style="margin: 0 5px; background-color: #28a745;">장비 견적서</button>' +
'</div>',
showConfirmButton: false,
showCancelButton: true,
showDenyButton: true,
confirmButtonText: '일반 견적서',
denyButtonText: '장비 견적서',
cancelButtonText: '취소',
confirmButtonColor: '#3085d6',
denyButtonColor: '#28a745',
cancelButtonColor: '#d33',
}).then((result) => {
if (result.isConfirmed) {
// 일반 견적서 (Template 1)
fn_openEstimateTemplate(objId, "1");
} else if (result.isDenied) {
// 장비 견적서 (Template 2)
fn_openEstimateTemplate(objId, "2");
}
cancelButtonColor: '#d33'
});
// 전역 함수로 등록
window.openTemplate1 = function() {
Swal.close();
fn_openEstimateTemplate(objId, "1");
};
window.openTemplate2 = function() {
Swal.close();
fn_openEstimateTemplate(objId, "2");
};
}
});
@@ -158,71 +159,122 @@ var columns = [
fn_projectConceptDetail(objid);
}
},
{title:"영업정보(상세)", headerHozAlign:'center', //고객정보
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '계약구분', field : 'CATEGORY_NAME' },
{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 : 'PRODUCT_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' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '설비타입', field : 'FACILITY_TYPE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '설비길이', field : 'FACILITY_DEPTH' },
{headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '담당자', field : 'WRITER_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '등록일', field : 'REG_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '첨부파일', field : 'CU01_CNT',
formatter:fnc_subInfoValueFormatter,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
var docType = 'contractMgmt01';
var docTypeName = 'contractMgmt01';
fn_FileRegist(objid, docType, docTypeName);
}
}
]
},
{title:"진행사항", headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '검토내용', field : 'CU03_CNT',
formatter:fnc_subInfoValueFormatter,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_projectConceptReviewDetail(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '상태', field : 'CONTRACT_RESULT_NAME' }
]
},
{title:"수주정보", headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '수주일', field : 'CONTRACT_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : 'PO계약 No', field : 'PO_NO' },
{headerHozAlign : 'center', hozAlign : 'center', width : '70', title : 'PM', field : 'PM_USER_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '통화', field : 'CONTRACT_CURRENCY_NAME' },
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주가', field : 'CONTRACT_PRICE_CURRENCY',
formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false,},
},
{headerHozAlign : 'center', hozAlign : 'left', width : '170', title : '당사프로젝트명', field : 'PROJECT_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '계약납기일', field : 'CONTRACT_DEL_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '요청납기일', field : 'REQ_DEL_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '수주회사', field : 'CONTRACT_COMPANY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '제작공장', field : 'MANUFACTURE_PLANT_NAME' }
]
},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '상태', field : 'APPR_STATUS' ,
formatter:fnc_createGridAnchorTag,
{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 : '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',
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 APPROVAL_OBJID = fnc_checkNull(cell.getData().APPROVAL_OBJID);
var ROUTE_OBJID = fnc_checkNull(cell.getData().ROUTE_OBJID);
approval_form(APPROVAL_OBJID,ROUTE_OBJID);
}
var serialNo = fnc_checkNull(cell.getData().SERIAL_NO);
fn_showSerialNoPopup(serialNo);
}
},
{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 : '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',
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_projectConceptDetail(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '견적단가', field : 'EST_PRICE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '견적공급가액', field : 'EST_SUPPLY_PRICE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '환종', field : 'CONTRACT_CURRENCY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '환율', field : 'EXCHANGE_RATE' },
// {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' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '설비타입', field : 'FACILITY_TYPE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '설비길이', field : 'FACILITY_DEPTH' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '담당자', field : 'WRITER_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '등록일', field : 'REG_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '첨부파일', field : 'CU01_CNT',
// formatter:fnc_subInfoValueFormatter,
// cellClick:function(e, cell){
// var objid = fnc_checkNull(cell.getData().OBJID);
// var docType = 'contractMgmt01';
// var docTypeName = 'contractMgmt01';
// fn_FileRegist(objid, docType, docTypeName);
// }
// }
// ]
// },
// {title:"진행사항", headerHozAlign:'center',
// columns:[
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '검토내용', field : 'CU03_CNT',
// formatter:fnc_subInfoValueFormatter,
// cellClick:function(e, cell){
// var objid = fnc_checkNull(cell.getData().OBJID);
// fn_projectConceptReviewDetail(objid);
// }
// },
// {headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '상태', field : 'CONTRACT_RESULT_NAME' }
// ]
// },
// {title:"수주정보", headerHozAlign:'center',
// columns:[
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '수주일', field : 'CONTRACT_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : 'PO계약 No', field : 'PO_NO' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '70', title : 'PM', field : 'PM_USER_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '통화', field : 'CONTRACT_CURRENCY_NAME' },
// {headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주가', field : 'CONTRACT_PRICE_CURRENCY',
// formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false,},
// },
// {headerHozAlign : 'center', hozAlign : 'left', width : '170', title : '당사프로젝트명', field : 'PROJECT_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '계약납기일', field : 'CONTRACT_DEL_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '요청납기일', field : 'REQ_DEL_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '수주회사', field : 'CONTRACT_COMPANY_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '제작공장', field : 'MANUFACTURE_PLANT_NAME' }
// ]
// },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '상태', field : 'APPR_STATUS' ,
// formatter:fnc_createGridAnchorTag,
// cellClick:function(e, cell){
// var APPROVAL_OBJID = fnc_checkNull(cell.getData().APPROVAL_OBJID);
// var ROUTE_OBJID = fnc_checkNull(cell.getData().ROUTE_OBJID);
// approval_form(APPROVAL_OBJID,ROUTE_OBJID);
// }
// },
];
//var grid;
@@ -322,7 +374,97 @@ function fn_projectConceptReviewDetail(objId){
fn_centerPopup(popup_width, popup_height, url);
}
//견적서 양식 열기
// 견적서 목록 팝업
function fn_showEstimateList(contractObjId){
$.ajax({
url: "/contractMgmt/getEstimateTemplateList.do",
type: "POST",
data: { objId: contractObjId },
dataType: "json",
success: function(data){
console.log("견적서 목록 응답:", data); // 디버깅용
if(data.result === "success" && data.list && data.list.length > 0){
var html = '<div style="max-height: 400px; overflow-y: auto;">';
html += '<table style="width: 100%; border-collapse: collapse;">';
html += '<thead><tr style="background-color: #f0f0f0;">';
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 += '<th style="border: 1px solid #ddd; padding: 8px;">작성자</th>';
html += '</tr></thead><tbody>';
data.list.forEach(function(item){
console.log("견적서 항목:", item); // 디버깅용
// 대문자/소문자 모두 지원하도록 안전하게 처리
var objid = item.OBJID || item.objid || '';
var templateType = item.TEMPLATE_TYPE || item.template_type || item.templateType || '';
var revision = item.REVISION || item.revision || '';
var templateTypeName = item.TEMPLATE_TYPE_NAME || item.template_type_name || item.templateTypeName || '';
var estimateNo = item.ESTIMATE_NO || item.estimate_no || item.estimateNo || '-';
var regdate = item.REGDATE || item.regdate || '';
var writer = item.WRITER || item.writer || '';
html += '<tr style="cursor: pointer;" onclick="fn_openEstimateByObjId(\'' + objid + '\', \'' + templateType + '\')">';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + revision + '차</td>';
html += '<td style="border: 1px solid #ddd; padding: 8px; text-align: center;">' + templateTypeName + '</td>';
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 += '</tr>';
});
html += '</tbody></table></div>';
Swal.fire({
title: '견적서 목록',
html: html,
width: '700px',
showConfirmButton: false,
showCancelButton: true,
cancelButtonText: '닫기'
});
} else {
Swal.fire({
title: '견적서가 없습니다',
text: '작성된 견적서가 없습니다.',
icon: 'info'
});
}
},
error: function(xhr, status, error){
console.error("견적서 목록 조회 오류:", xhr, status, error); // 디버깅용
Swal.fire({
title: '오류',
text: '견적서 목록 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
// OBJID로 견적서 열기
function fn_openEstimateByObjId(templateObjId, templateType){
Swal.close();
var popup_width = 900;
var popup_height = 800;
var url = "";
if(templateType === "1"){
// 일반 견적서
url = "/contractMgmt/estimateTemplate1.do?templateObjId="+templateObjId;
} else if(templateType === "2"){
// 장비 견적서
url = "/contractMgmt/estimateTemplate2.do?templateObjId="+templateObjId;
}
window.open(url, "estimateTemplate_"+templateObjId, "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
//견적서 양식 열기 (CONTRACT_OBJID로)
function fn_openEstimateTemplate(objId, templateType){
var popup_width = 900;
var popup_height = 800;
@@ -339,6 +481,36 @@ function fn_openEstimateTemplate(objId, templateType){
window.open(url, "estimateTemplate", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
// S/N 목록 팝업 표시
function fn_showSerialNoPopup(serialNoString){
if(!serialNoString || serialNoString === ''){
Swal.fire("S/N 정보가 없습니다.");
return;
}
// 쉼표로 구분된 S/N을 배열로 변환
var serialNumbers = serialNoString.split(',').map(function(sn){ return sn.trim(); });
// HTML 리스트 생성
var listHtml = '<div style="text-align: left; max-height: 400px; overflow-y: auto;">';
listHtml += '<ol style="padding-left: 20px; margin: 10px 0;">';
serialNumbers.forEach(function(sn, index){
listHtml += '<li style="padding: 5px 0; font-size: 14px;">' + sn + '</li>';
});
listHtml += '</ol>';
listHtml += '</div>';
// SweetAlert2로 팝업 표시
Swal.fire({
title: 'S/N 목록',
html: listHtml,
icon: 'info',
width: 500,
confirmButtonText: '확인',
confirmButtonColor: '#3085d6'
});
}
//코드값을 받아와서 동적으로 selectbox 생성
function optionJobGroup(code){
var val=code;

View File

@@ -7,6 +7,7 @@
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
String objId = CommonUtils.checkNull(request.getParameter("objId"));
String templateObjId = CommonUtils.checkNull(request.getParameter("templateObjId"));
%>
<!DOCTYPE html>
<html>
@@ -218,8 +219,18 @@ textarea {
}
</style>
<script type="text/javascript">
// 전역 변수로 저장 (데이터 로드 시 설정됨)
var g_contractObjId = "<%=objId%>";
var g_templateObjId = "<%=templateObjId%>";
$(function(){
// templateObjId가 있으면 기존 데이터 로드
var templateObjId = "<%=templateObjId%>";
if(templateObjId && templateObjId !== ""){
fn_loadTemplateData(templateObjId);
}
// 인쇄 버튼
$("#btnPrint").click(function(){
window.print();
@@ -360,25 +371,114 @@ function fn_loadData() {
});
}
// 기존 견적서 데이터 로드 (templateObjId 기준)
function fn_loadTemplateData(templateObjId){
$.ajax({
url: "/contractMgmt/getEstimateTemplateDataByObjId.do",
type: "POST",
data: { templateObjId: templateObjId },
dataType: "json",
success: function(data){
console.log("견적서 데이터:", data); // 디버깅용
if(data.result === "success" && data.template){
var template = data.template;
// CONTRACT_OBJID를 전역 변수에 저장 (저장 시 사용)
var contractObjId = template.CONTRACT_OBJID || template.contract_objid || template.contractObjId || "";
if(contractObjId) {
g_contractObjId = contractObjId;
}
// 대문자/소문자 모두 지원
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);
// 품목 데이터 채우기
if(data.items && data.items.length > 0){
$("#itemsTableBody").empty();
data.items.forEach(function(item, idx){
var description = item.DESCRIPTION || item.description || '';
var specification = item.SPECIFICATION || item.specification || '';
var quantity = item.QUANTITY || item.quantity || '';
var unit = item.UNIT || item.unit || '';
var unitPrice = item.UNIT_PRICE || item.unit_price || item.unitPrice || '';
var amount = item.AMOUNT || item.amount || '';
var note = item.NOTE || item.note || '';
var row = $("<tr>");
row.append('<td>' + (idx + 1) + '</td>');
row.append('<td class="text-left editable"><input type="text" class="item-desc" value="' + description + '"></td>');
row.append('<td class="text-left editable"><textarea class="item-spec">' + specification + '</textarea></td>');
row.append('<td class="editable"><input type="text" class="item-qty" value="' + quantity + '"></td>');
row.append('<td class="editable"><input type="text" class="item-unit" value="' + unit + '"></td>');
row.append('<td class="text-right editable"><input type="text" class="item-price" value="' + unitPrice + '"></td>');
row.append('<td class="text-right editable"><input type="text" class="item-amount" value="' + amount + '" readonly></td>');
row.append('<td class="editable"><input type="text" class="item-note" value="' + note + '"></td>');
$("#itemsTableBody").append(row);
});
}
} 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) {
var row = $(this);
var unitPrice = row.find(".item-price").val() || "";
var amount = row.find(".item-amount").val() || "";
items.push({
seq: idx + 1,
description: row.find(".item-desc").val(),
specification: row.find(".item-spec").val(),
quantity: row.find(".item-qty").val(),
unit: row.find(".item-unit").val(),
unit_price: row.find(".item-price").val().replace(/,/g, ""),
amount: row.find(".item-amount").val().replace(/,/g, "").replace(/₩/g, ""),
note: row.find(".item-note").val()
description: row.find(".item-desc").val() || "",
specification: row.find(".item-spec").val() || "",
quantity: row.find(".item-qty").val() || "",
unit: row.find(".item-unit").val() || "",
unit_price: unitPrice.replace(/,/g, ""),
amount: amount.replace(/,/g, "").replace(/₩/g, ""),
note: row.find(".item-note").val() || ""
});
});
// objId는 CONTRACT_OBJID를 의미함
var contractObjId = g_contractObjId;
// 유효성 검사
if(!contractObjId || contractObjId === "" || contractObjId === "-1") {
Swal.fire("견적서를 저장할 수 없습니다. 영업정보가 없습니다.");
return;
}
var formData = {
objId: "<%=objId%>",
objId: contractObjId,
template_type: "1",
executor: $("#executor").val(),
recipient: $("#recipient").val(),
@@ -392,12 +492,21 @@ function fn_save() {
items: JSON.stringify(items)
};
// templateObjId가 있을 때만 추가 (기존 견적서 수정 시에만)
if(g_templateObjId && g_templateObjId !== "" && g_templateObjId !== "-1") {
formData.templateObjId = g_templateObjId;
}
console.log("저장 데이터:", formData); // 디버깅용
console.log("신규 작성:", !formData.templateObjId); // 디버깅용
$.ajax({
url: "/contractMgmt/saveEstimate.do",
type: "POST",
data: formData,
dataType: "json",
success: function(data) {
console.log("저장 결과:", data); // 디버깅용
if(data.result === "success") {
Swal.fire({
title: "저장되었습니다.",
@@ -409,10 +518,11 @@ function fn_save() {
self.close();
});
} else {
Swal.fire("저장에 실패했습니다.");
Swal.fire("저장에 실패했습니다." + (data.message ? "\n" + data.message : ""));
}
},
error: function() {
error: function(xhr, status, error) {
console.error("저장 오류:", xhr, status, error); // 디버깅용
Swal.fire("저장 중 오류가 발생했습니다.");
}
});

View File

@@ -1774,13 +1774,20 @@ public class ContractMgmtController {
@RequestMapping("/contractMgmt/estimateTemplate1.do")
public String estimateTemplate1(HttpSession session, HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
try{
Map estimate = null;
List<Map> items = new ArrayList<Map>();
if(!"".equals(objId) && !"-1".equals(objId)){
// 기존 견적서 데이터 조회
// templateObjId가 있으면 기존 견적서 조회 (견적현황에서 클릭한 경우)
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
paramMap.put("templateObjId", templateObjId);
estimate = contractMgmtService.getEstimateTemplateByObjId(paramMap);
items = contractMgmtService.getEstimateTemplateItemsByTemplateObjId(paramMap);
}
// objId만 있으면 CONTRACT 정보로 견적서 신규 작성
else if(!"".equals(objId) && !"-1".equals(objId)){
estimate = contractMgmtService.getEstimateTemplateInfo(paramMap);
items = contractMgmtService.getEstimateTemplateItems(paramMap);
}
@@ -1805,12 +1812,20 @@ public class ContractMgmtController {
@RequestMapping("/contractMgmt/estimateTemplate2.do")
public String estimateTemplate2(HttpSession session, HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
try{
Map estimate = null;
List<Map> items = new ArrayList<Map>();
if(!"".equals(objId) && !"-1".equals(objId)){
// templateObjId가 있으면 기존 견적서 조회 (견적현황에서 클릭한 경우)
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
paramMap.put("templateObjId", templateObjId);
estimate = contractMgmtService.getEstimateTemplateByObjId(paramMap);
items = contractMgmtService.getEstimateTemplateItemsByTemplateObjId(paramMap);
}
// objId만 있으면 CONTRACT 정보로 견적서 신규 작성
else if(!"".equals(objId) && !"-1".equals(objId)){
// 기존 견적서 데이터 조회
estimate = contractMgmtService.getEstimateTemplateInfo(paramMap);
items = contractMgmtService.getEstimateTemplateItems(paramMap);
@@ -1854,6 +1869,43 @@ public class ContractMgmtController {
return resultMap;
}
/**
* 견적서 목록 조회 (AJAX)
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/getEstimateTemplateList.do", method=RequestMethod.POST)
public Map getEstimateTemplateList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
return contractMgmtService.getEstimateTemplateList(request, paramMap);
}
/**
* 견적서 템플릿 데이터 조회 (OBJID로) (AJAX)
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/getEstimateTemplateDataByObjId.do", method=RequestMethod.POST)
public Map getEstimateTemplateDataByObjId(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
// 템플릿 기본 정보 조회
Map template = contractMgmtService.getEstimateTemplateByObjId(paramMap);
// 템플릿 품목 조회
List<Map> items = contractMgmtService.getEstimateTemplateItemsByTemplateObjId(paramMap);
resultMap.put("result", "success");
resultMap.put("template", template);
resultMap.put("items", items);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* 견적서 저장 (AJAX)
* @param request

View File

@@ -486,20 +486,27 @@
,CODE_NAME(AREA_CD) AS AREA_NAME
,MECHANICAL_TYPE
,OVERHAUL_ORDER
,PAID_TYPE
,CASE
WHEN PAID_TYPE = 'paid' THEN '유상'
WHEN PAID_TYPE = 'free' THEN '무상'
ELSE PAID_TYPE
END AS PAID_TYPE
,RECEIPT_DATE
,PART_NO
,PART_NAME
,SERIAL_NO
,QUANTITY
,CUSTOMER_REQUEST
,EXCHANGE_RATE
,A.APPR_STATUS
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
FROM
CONTRACT_MGMT AS T
LEFT OUTER JOIN
,QUANTITY
,CUSTOMER_REQUEST
,EXCHANGE_RATE
,EST_PRICE
,EST_SUPPLY_PRICE
,(SELECT COUNT(1) FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID) AS EST_STATUS
,A.APPR_STATUS
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
FROM
CONTRACT_MGMT AS T
LEFT OUTER JOIN
(
SELECT
B.OBJID AS ROUTE_OBJID,
@@ -3678,7 +3685,11 @@ ORDER BY ASM.SUPPLY_NAME
CUSTOMER_PROJECT_NAME,
AREA_CD,
CODE_NAME(AREA_CD) AS AREA_NAME,
PAID_TYPE,
CASE
WHEN PAID_TYPE = 'paid' THEN '유상'
WHEN PAID_TYPE = 'free' THEN '무상'
ELSE PAID_TYPE
END AS PAID_TYPE,
RECEIPT_DATE,
PART_NO,
PART_NAME,
@@ -3696,7 +3707,31 @@ ORDER BY ASM.SUPPLY_NAME
FROM
CONTRACT_MGMT AS T
WHERE
OBJID = #{objId}::NUMERIC
OBJID::VARCHAR = #{objId}
</select>
<!-- 견적서 템플릿 목록 조회 (CONTRACT_OBJID 기준) -->
<select id="getEstimateTemplateList" parameterType="map" resultType="map">
SELECT
OBJID AS "OBJID",
CONTRACT_OBJID AS "CONTRACT_OBJID",
TEMPLATE_TYPE AS "TEMPLATE_TYPE",
CASE
WHEN TEMPLATE_TYPE = '1' THEN '일반 견적서'
WHEN TEMPLATE_TYPE = '2' THEN '장비 견적서'
ELSE 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"
FROM
ESTIMATE_TEMPLATE
WHERE
CONTRACT_OBJID = #{objId}
ORDER BY TEMPLATE_TYPE, REGDATE DESC
</select>
<!-- 견적서 템플릿 데이터 조회 (ESTIMATE_TEMPLATE 테이블) -->
@@ -3724,7 +3759,7 @@ ORDER BY ASM.SUPPLY_NAME
FROM
ESTIMATE_TEMPLATE
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND TEMPLATE_TYPE = #{template_type}
</if>
@@ -3732,6 +3767,56 @@ ORDER BY ASM.SUPPLY_NAME
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
FROM
ESTIMATE_TEMPLATE
WHERE
OBJID = #{templateObjId}
</select>
<!-- 견적서 템플릿 품목 조회 (TEMPLATE_OBJID 기준) -->
<select id="getEstimateTemplateItemsByTemplateObjId" parameterType="map" resultType="map">
SELECT
OBJID,
TEMPLATE_OBJID,
SEQ,
CATEGORY,
DESCRIPTION,
SPECIFICATION,
QUANTITY,
UNIT,
UNIT_PRICE,
AMOUNT,
NOTE,
REMARK
FROM
ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID = #{templateObjId}
ORDER BY SEQ
</select>
<!-- 견적서 템플릿 품목 조회 -->
<select id="getEstimateTemplateItems" parameterType="map" resultType="map">
SELECT
@@ -3753,7 +3838,7 @@ ORDER BY ASM.SUPPLY_NAME
TEMPLATE_OBJID IN (
SELECT OBJID
FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{objId}::NUMERIC
WHERE CONTRACT_OBJID = #{objId}
)
ORDER BY SEQ
</select>
@@ -3761,6 +3846,7 @@ ORDER BY ASM.SUPPLY_NAME
<!-- 견적서 템플릿 저장 -->
<insert id="insertEstimateTemplate" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR,
@@ -3780,7 +3866,8 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID,
CHGDATE
) VALUES (
#{objId}::NUMERIC,
#{template_objid},
#{objId},
#{template_type},
#{executor},
#{recipient},
@@ -3820,7 +3907,7 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
AND TEMPLATE_TYPE = #{template_type}
</update>
@@ -3828,11 +3915,7 @@ ORDER BY ASM.SUPPLY_NAME
<delete id="deleteEstimateTemplateItems" parameterType="map">
DELETE FROM ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID IN (
SELECT OBJID
FROM ESTIMATE_TEMPLATE
WHERE CONTRACT_OBJID = #{objId}::NUMERIC
)
TEMPLATE_OBJID = #{template_objid}
</delete>
<!-- 견적서 템플릿 품목 저장 -->
@@ -3851,17 +3934,25 @@ ORDER BY ASM.SUPPLY_NAME
REMARK
)
SELECT
(SELECT OBJID FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = #{objId}::NUMERIC AND TEMPLATE_TYPE = #{template_type}),
(json_array_elements(#{items_json}::json)->>'seq')::INTEGER,
json_array_elements(#{items_json}::json)->>'category',
json_array_elements(#{items_json}::json)->>'description',
json_array_elements(#{items_json}::json)->>'specification',
json_array_elements(#{items_json}::json)->>'quantity',
json_array_elements(#{items_json}::json)->>'unit',
(json_array_elements(#{items_json}::json)->>'unit_price')::NUMERIC,
(json_array_elements(#{items_json}::json)->>'amount')::NUMERIC,
json_array_elements(#{items_json}::json)->>'note',
json_array_elements(#{items_json}::json)->>'remark'
#{template_objid},
(item->>'seq')::INTEGER,
item->>'category',
item->>'description',
item->>'specification',
item->>'quantity',
item->>'unit',
CASE
WHEN item->>'unit_price' = '' THEN NULL
ELSE (item->>'unit_price')::NUMERIC
END,
CASE
WHEN item->>'amount' = '' THEN NULL
ELSE (item->>'amount')::NUMERIC
END,
item->>'note',
item->>'remark'
FROM json_array_elements(#{items_json}::json) AS item
WHERE COALESCE(item->>'description', '') != ''
</insert>
<!-- 견적서 템플릿 카테고리 업데이트 (장비 견적서용) -->
@@ -3872,7 +3963,7 @@ ORDER BY ASM.SUPPLY_NAME
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
CONTRACT_OBJID = #{objId}::NUMERIC
CONTRACT_OBJID = #{objId}
AND TEMPLATE_TYPE = #{template_type}
</update>

View File

@@ -1271,6 +1271,105 @@ public class ContractMgmtService {
return resultList;
}
/**
* 견적서 템플릿 목록 조회
* @param request
* @param paramMap
* @return
*/
public Map getEstimateTemplateList(HttpServletRequest request, Map paramMap){
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession();
List<Map> list = sqlSession.selectList("contractMgmt.getEstimateTemplateList", paramMap);
resultMap.put("result", "success");
resultMap.put("list", list);
}catch(Exception e){
resultMap.put("result", "error");
resultMap.put("msg", "목록 조회 중 오류가 발생했습니다.");
e.printStackTrace();
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적서 템플릿 OBJID로 조회 (견적현황 클릭 시)
* @param paramMap
* @return
*/
public Map getEstimateTemplateByObjId(Map paramMap){
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
// ESTIMATE_TEMPLATE 테이블에서 직접 조회
resultMap = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateByObjId", paramMap);
// CONTRACT_MGMT 정보도 함께 조회
if(resultMap != null && !resultMap.isEmpty()){
String contractObjId = CommonUtils.checkNull(resultMap.get("CONTRACT_OBJID"));
if(!"".equals(contractObjId)){
Map contractInfo = new HashMap();
contractInfo.put("objId", contractObjId);
Map contractData = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateInfo", contractInfo);
if(contractData != null && !contractData.isEmpty()){
// CONTRACT 정보를 resultMap에 추가 (템플릿 정보가 우선)
contractData.putAll(resultMap);
resultMap = contractData;
}
}
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적서 템플릿 품목 조회 (템플릿 OBJID로)
* @param paramMap
* @return
*/
public List<Map> getEstimateTemplateItemsByTemplateObjId(Map paramMap){
List<Map> resultList = new ArrayList<Map>();
SqlSession sqlSession = null;
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
resultList = sqlSession.selectList("contractMgmt.getEstimateTemplateItemsByTemplateObjId", paramMap);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultList;
}
/**
* 견적서 템플릿 저장
* @param request
@@ -1287,7 +1386,8 @@ public class ContractMgmtService {
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
String userId = person.getUserId();
String objId = CommonUtils.checkNull(paramMap.get("objId"));
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"));
@@ -1295,14 +1395,28 @@ public class ContractMgmtService {
paramMap.put("writer", userId);
paramMap.put("chg_user_id", userId);
// 견적서 템플릿 정보 저장/수정
Map existingTemplate = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateData", paramMap);
// 기존 템플릿 수정인지 신규 작성인지 확인
// 중요: templateObjId가 명시적으로 있을 때만 수정, 없으면 항상 신규 작성
boolean isUpdate = false;
if(existingTemplate != null && !existingTemplate.isEmpty()){
// 기존 데이터 업데이트
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
// templateObjId가 명시적으로 있으면 해당 템플릿 수정 (견적현황에서 기존 견적서 열어서 수정한 경우)
Map existingTemplate = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateByObjId", paramMap);
if(existingTemplate != null && !existingTemplate.isEmpty()){
isUpdate = true;
paramMap.put("template_objid", templateObjId);
}
}
// else: templateObjId가 없으면 항상 신규 견적서 작성 (같은 영업번호에 여러 견적서 가능)
if(isUpdate){
// 기존 견적서 수정
sqlSession.update("contractMgmt.updateEstimateTemplate", paramMap);
} else {
// 신규 데이터 삽입
// 신규 견적서 작성 - 새로운 OBJID 생성
templateObjId = CommonUtils.createObjId();
paramMap.put("template_objid", templateObjId);
paramMap.put("contract_objid", objId);
sqlSession.insert("contractMgmt.insertEstimateTemplate", paramMap);
}