견적요청등록화면 수정

This commit is contained in:
2025-10-18 22:51:59 +09:00
parent df30332a49
commit f47b091dca
6 changed files with 1749 additions and 108 deletions

View File

@@ -48,7 +48,7 @@ $(document).ready(function(){
//영업활동 등록 팝업
$(".btnRegist").click(function(){
var popup_width = 1000;
var popup_width = 1400;
var popup_height = 560;
var params = "?actionType=regist"
var url = "/contractMgmt/estimateRegistFormPopup.do"+params;
@@ -140,7 +140,8 @@ $(document).ready(function(){
return false;
}else{
if(confirm("결재상신 하시겠습니까?")){
var objId = fnc_checkNull(selectedData[0].OBJID);
//var objId = fnc_checkNull(selectedData[0].OBJID);
var objId = fnc_checkNull(selectedData[0].EST_OBJID);
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");
}
@@ -194,6 +195,7 @@ $(document).ready(function(){
});
var columns = [
{title:'EST_OBJID' ,field:'EST_OBJID' ,visible:false},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
@@ -203,36 +205,52 @@ 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 : '88', 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 '';
// {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
// {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;
// 쉼표로 구분된 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>';
// 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);
// 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 : 'center', width : '120', 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);
if(dueDate === '') return '';
// 다른 납기가 있으면 "날짜 외 N건" 형식으로 표시
if(otherCount && parseInt(otherCount) > 0){
return dueDate + ' 외 ' + otherCount + '건';
}
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 : 'left', width : '120', title : '반납사유', field : 'RETURN_REASON_SUMMARY' },
// {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){
@@ -269,10 +287,15 @@ var columns = [
// }
}
},
{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' },
// {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 : '150', title : '견적원화환산공급가액', field : 'EXC_EST_SUPPLY_PRICE',
formatter:"money", formatterParams:{thousand:",", symbolAfter:"p", precision:false,},
},
{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:[
@@ -422,7 +445,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;
@@ -764,9 +787,9 @@ function openProjectFormPopUp(objId){
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch" name="btnSearch">
<input type="button" value="삭제" class="plm_btns" id="btnDelete">
<input type="button" value="견적요청등록" class="plm_btns btnRegist">
<input type="button" value="결재상신" class="plm_btns" id="btnApproval">
<input type="button" value="견적작성" class="plm_btns" id="btnEstimate">
<input type="button" value="견적요청등록" class="plm_btns btnRegist">
<input type="button" value="견적작성" class="plm_btns" id="btnEstimate">
<input type="button" value="결재상신" class="plm_btns" id="btnApproval">
<input type="button" value="메일발송" class="plm_btns" id="btnMail">
<!-- <input type="button" value="Excel Download" class="plm_btns" id="btnExcel">
<input type="button" value="고객등록" class="plm_btns supplyMng create"> -->

View File

@@ -0,0 +1,230 @@
-- 견적 품목 테이블 생성 스크립트
-- 작성일: 2025-10-17
-- 설명: 견적 요청 시 여러 품목을 등록할 수 있도록 품목 정보를 별도 테이블로 관리
-- ====================================
-- 1. 견적 품목 테이블 (CONTRACT_ITEM)
-- ====================================
-- 견적(CONTRACT_MGMT)에 속한 품목 정보를 저장
CREATE TABLE IF NOT EXISTS CONTRACT_ITEM (
OBJID VARCHAR(50) PRIMARY KEY, -- 품목 고유 ID
CONTRACT_OBJID VARCHAR(50) NOT NULL, -- 견적 OBJID (FK)
SEQ INTEGER NOT NULL, -- 품목 순번
PART_NO VARCHAR(100) NOT NULL, -- 품번
PART_NAME VARCHAR(200) NOT NULL, -- 품명
QUANTITY INTEGER NOT NULL DEFAULT 1, -- 수량
DUE_DATE VARCHAR(10), -- 요청납기 (YYYY-MM-DD)
CUSTOMER_REQUEST TEXT, -- 고객요청사항
REGDATE TIMESTAMP NOT NULL DEFAULT NOW(), -- 등록일시
WRITER VARCHAR(50), -- 등록자
CHGDATE TIMESTAMP, -- 수정일시
CHG_USER_ID VARCHAR(50), -- 수정자
STATUS VARCHAR(20) DEFAULT 'ACTIVE', -- 상태 (ACTIVE/INACTIVE)
-- 제약조건
CONSTRAINT FK_CONTRACT_ITEM_CONTRACT FOREIGN KEY (CONTRACT_OBJID)
REFERENCES CONTRACT_MGMT(OBJID) ON DELETE CASCADE,
CONSTRAINT CHK_CONTRACT_ITEM_QUANTITY CHECK (QUANTITY > 0)
);
-- 인덱스 생성
CREATE INDEX IDX_CONTRACT_ITEM_CONTRACT ON CONTRACT_ITEM(CONTRACT_OBJID);
CREATE INDEX IDX_CONTRACT_ITEM_SEQ ON CONTRACT_ITEM(CONTRACT_OBJID, SEQ);
-- 테이블 코멘트
COMMENT ON TABLE CONTRACT_ITEM IS '견적 품목 정보';
COMMENT ON COLUMN CONTRACT_ITEM.OBJID IS '품목 고유 ID';
COMMENT ON COLUMN CONTRACT_ITEM.CONTRACT_OBJID IS '견적 OBJID (FK)';
COMMENT ON COLUMN CONTRACT_ITEM.SEQ IS '품목 순번';
COMMENT ON COLUMN CONTRACT_ITEM.PART_NO IS '품번';
COMMENT ON COLUMN CONTRACT_ITEM.PART_NAME IS '품명';
COMMENT ON COLUMN CONTRACT_ITEM.QUANTITY IS '수량';
COMMENT ON COLUMN CONTRACT_ITEM.DUE_DATE IS '요청납기 (YYYY-MM-DD)';
COMMENT ON COLUMN CONTRACT_ITEM.CUSTOMER_REQUEST IS '고객요청사항';
COMMENT ON COLUMN CONTRACT_ITEM.REGDATE IS '등록일시';
COMMENT ON COLUMN CONTRACT_ITEM.WRITER IS '등록자';
COMMENT ON COLUMN CONTRACT_ITEM.CHGDATE IS '수정일시';
COMMENT ON COLUMN CONTRACT_ITEM.CHG_USER_ID IS '수정자';
COMMENT ON COLUMN CONTRACT_ITEM.STATUS IS '상태 (ACTIVE/INACTIVE)';
-- ====================================
-- 2. 품목별 S/N 테이블 (CONTRACT_ITEM_SERIAL)
-- ====================================
-- 각 품목에 속한 S/N 정보를 저장
CREATE TABLE IF NOT EXISTS CONTRACT_ITEM_SERIAL (
OBJID VARCHAR(50) PRIMARY KEY, -- S/N 고유 ID
ITEM_OBJID VARCHAR(50) NOT NULL, -- 품목 OBJID (FK)
SEQ INTEGER NOT NULL, -- S/N 순번
SERIAL_NO VARCHAR(200) NOT NULL, -- S/N
REGDATE TIMESTAMP NOT NULL DEFAULT NOW(), -- 등록일시
WRITER VARCHAR(50), -- 등록자
STATUS VARCHAR(20) DEFAULT 'ACTIVE', -- 상태 (ACTIVE/INACTIVE)
-- 제약조건
CONSTRAINT FK_CONTRACT_ITEM_SERIAL_ITEM FOREIGN KEY (ITEM_OBJID)
REFERENCES CONTRACT_ITEM(OBJID) ON DELETE CASCADE,
CONSTRAINT UQ_CONTRACT_ITEM_SERIAL UNIQUE (ITEM_OBJID, SERIAL_NO)
);
-- 인덱스 생성
CREATE INDEX IDX_CONTRACT_ITEM_SERIAL_ITEM ON CONTRACT_ITEM_SERIAL(ITEM_OBJID);
CREATE INDEX IDX_CONTRACT_ITEM_SERIAL_SEQ ON CONTRACT_ITEM_SERIAL(ITEM_OBJID, SEQ);
-- 테이블 코멘트
COMMENT ON TABLE CONTRACT_ITEM_SERIAL IS '품목별 S/N 정보';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.OBJID IS 'S/N 고유 ID';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.ITEM_OBJID IS '품목 OBJID (FK)';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.SEQ IS 'S/N 순번';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.SERIAL_NO IS 'S/N';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.REGDATE IS '등록일시';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.WRITER IS '등록자';
COMMENT ON COLUMN CONTRACT_ITEM_SERIAL.STATUS IS '상태 (ACTIVE/INACTIVE)';
-- ====================================
-- 3. 시퀀스 생성 (OBJID 자동 생성용)
-- ====================================
-- 품목 OBJID 시퀀스
CREATE SEQUENCE IF NOT EXISTS SEQ_CONTRACT_ITEM
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
-- S/N OBJID 시퀀스
CREATE SEQUENCE IF NOT EXISTS SEQ_CONTRACT_ITEM_SERIAL
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
-- ====================================
-- 4. 샘플 데이터 조회 쿼리
-- ====================================
/*
-- 견적별 품목 목록 조회
SELECT
CI.OBJID,
CI.SEQ,
CI.PART_NO,
CI.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SEQ) AS SERIAL_NOS,
COUNT(CIS.OBJID) AS SERIAL_COUNT
FROM
CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND CIS.STATUS = 'ACTIVE'
WHERE
CI.CONTRACT_OBJID = 'CONTRACT_OBJID_HERE'
AND CI.STATUS = 'ACTIVE'
GROUP BY
CI.OBJID, CI.SEQ, CI.PART_NO, CI.PART_NAME, CI.QUANTITY, CI.DUE_DATE, CI.CUSTOMER_REQUEST
ORDER BY
CI.SEQ;
-- 품목별 S/N 목록 조회
SELECT
OBJID,
SEQ,
SERIAL_NO
FROM
CONTRACT_ITEM_SERIAL
WHERE
ITEM_OBJID = 'ITEM_OBJID_HERE'
AND STATUS = 'ACTIVE'
ORDER BY
SEQ;
*/
-- ====================================
-- 5. 기존 데이터 마이그레이션 (선택사항)
-- ====================================
-- 기존 CONTRACT_MGMT 테이블의 PART_NO, PART_NAME, SERIAL_NO, QUANTITY 데이터를
-- 새로운 CONTRACT_ITEM 테이블로 마이그레이션
/*
-- 기존 품목 데이터를 CONTRACT_ITEM으로 복사
INSERT INTO CONTRACT_ITEM (
OBJID,
CONTRACT_OBJID,
SEQ,
PART_NO,
PART_NAME,
QUANTITY,
DUE_DATE,
CUSTOMER_REQUEST,
REGDATE,
WRITER,
STATUS
)
SELECT
'ITEM_' || CM.OBJID AS OBJID,
CM.OBJID AS CONTRACT_OBJID,
1 AS SEQ,
CM.PART_NO,
CM.PART_NAME,
COALESCE(CM.QUANTITY, 1) AS QUANTITY,
CM.DUE_DATE,
CM.CUSTOMER_REQUEST,
CM.REGDATE,
CM.WRITER,
'ACTIVE' AS STATUS
FROM
CONTRACT_MGMT CM
WHERE
CM.PART_NO IS NOT NULL
AND CM.PART_NO != ''
AND NOT EXISTS (
SELECT 1 FROM CONTRACT_ITEM CI WHERE CI.CONTRACT_OBJID = CM.OBJID
);
-- 기존 S/N 데이터를 CONTRACT_ITEM_SERIAL로 복사 (쉼표로 구분된 경우)
-- PostgreSQL의 string_to_array 함수 사용
INSERT INTO CONTRACT_ITEM_SERIAL (
OBJID,
ITEM_OBJID,
SEQ,
SERIAL_NO,
REGDATE,
WRITER,
STATUS
)
SELECT
'SN_' || CI.OBJID || '_' || ROW_NUMBER() OVER (PARTITION BY CI.OBJID ORDER BY sn_value) AS OBJID,
CI.OBJID AS ITEM_OBJID,
ROW_NUMBER() OVER (PARTITION BY CI.OBJID ORDER BY sn_value) AS SEQ,
TRIM(sn_value) AS SERIAL_NO,
CI.REGDATE,
CI.WRITER,
'ACTIVE' AS STATUS
FROM
CONTRACT_ITEM CI
CROSS JOIN LATERAL UNNEST(string_to_array(
(SELECT SERIAL_NO FROM CONTRACT_MGMT WHERE OBJID = CI.CONTRACT_OBJID),
','
)) AS sn_value
WHERE
TRIM(sn_value) != ''
AND NOT EXISTS (
SELECT 1 FROM CONTRACT_ITEM_SERIAL CIS
WHERE CIS.ITEM_OBJID = CI.OBJID
AND CIS.SERIAL_NO = TRIM(sn_value)
);
*/
-- ====================================
-- 6. 권한 설정 (필요시)
-- ====================================
-- GRANT SELECT, INSERT, UPDATE, DELETE ON CONTRACT_ITEM TO plm_user;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON CONTRACT_ITEM_SERIAL TO plm_user;
-- GRANT USAGE, SELECT ON SEQUENCE SEQ_CONTRACT_ITEM TO plm_user;
-- GRANT USAGE, SELECT ON SEQUENCE SEQ_CONTRACT_ITEM_SERIAL TO plm_user;

View File

@@ -665,7 +665,57 @@ public class ContractMgmtController {
@RequestMapping("/contractMgmt/saveContractMgmtInfo.do")
public String saveContractMgmtInfo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
try {
request.setAttribute("RESULT", CommonUtils.getJsonMap(contractMgmtService.saveContractMgmtInfo(request, paramMap)) );
// 기본 견적 정보 저장
Map<String, Object> result = contractMgmtService.saveContractMgmtInfo(request, paramMap);
// 품목 데이터 처리
if(result.get("result") != null && (Boolean)result.get("result")) {
// 수정 시에는 paramMap의 objId 사용, 신규 시에는 result의 objId 사용
String contractObjId = (String) result.get("objId");
if(contractObjId == null || contractObjId.isEmpty()) {
contractObjId = (String) paramMap.get("objId");
}
String itemsJsonStr = (String) paramMap.get("items_json");
System.out.println("=== Controller: 품목 저장 시작 ===");
System.out.println("contractObjId: " + contractObjId);
System.out.println("itemsJsonStr: " + (itemsJsonStr != null ? itemsJsonStr.substring(0, Math.min(100, itemsJsonStr.length())) : "null"));
if(contractObjId != null && itemsJsonStr != null && !itemsJsonStr.isEmpty()) {
// JSON 파싱
try {
com.google.gson.Gson gson = new com.google.gson.Gson();
java.lang.reflect.Type listType = new com.google.gson.reflect.TypeToken<List<Map<String, Object>>>(){}.getType();
List<Map<String, Object>> itemList = gson.fromJson(itemsJsonStr, listType);
System.out.println("JSON 파싱 완료. itemList size: " + (itemList != null ? itemList.size() : 0));
// 사용자 ID 가져오기
HttpSession session = request.getSession();
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
String userId = person.getUserId();
System.out.println("userId: " + userId);
// 품목 저장
Map<String, Object> itemResult = contractMgmtService.saveContractItems(contractObjId, itemList, userId);
System.out.println("품목 저장 결과: " + itemResult);
if(itemResult.get("result") == null || !(Boolean)itemResult.get("result")) {
result.put("msg", result.get("msg") + " (단, 품목 저장 실패: " + itemResult.get("msg") + ")");
}
} catch(Exception je) {
je.printStackTrace();
System.out.println("품목 저장 중 예외 발생: " + je.getMessage());
result.put("msg", result.get("msg") + " (단, 품목 저장 중 오류 발생: " + je.getMessage() + ")");
}
} else {
System.out.println("품목 저장 건너뜀 - contractObjId: " + contractObjId + ", itemsJsonStr isEmpty: " + (itemsJsonStr == null || itemsJsonStr.isEmpty()));
}
}
request.setAttribute("RESULT", CommonUtils.getJsonMap(result));
} catch (Exception e) {
e.printStackTrace();
@@ -1649,7 +1699,9 @@ public class ContractMgmtController {
String mechanical_type ="";//기계형식
String overhaul_order ="";//오버홀차수
Map info = null;
Map info = null;
List<Map<String, Object>> itemList = new ArrayList<Map<String, Object>>();
if(paramMap.get("objId")!=null){
paramMap.put("objId",objId);
info = CommonUtils.keyChangeUpperMap(contractMgmtService.getContractMgmtInfo(paramMap));
@@ -1675,6 +1727,9 @@ public class ContractMgmtController {
target_project_no = CommonUtils.nullToEmpty((String)info.get("TARGET_PROJECT_NO"));
mechanical_type = CommonUtils.nullToEmpty((String)info.get("MECHANICAL_TYPE"));
overhaul_order = CommonUtils.nullToEmpty((String)info.get("OVERHAUL_ORDER"));
// 품목 목록 조회
itemList = contractMgmtService.getContractItemList(objId);
}
if("".equals(objId)) objId = CommonUtils.createObjId();
@@ -1741,10 +1796,13 @@ public class ContractMgmtController {
code_map.put("mechanical_type", commonService.bizMakeOptionList("", mechanical_type,"common.getMechanicalTypeList"));
// 품번 목록은 AJAX로 검색하므로 제거
request.setAttribute("code_map",code_map);
request.setAttribute("info", info);
request.setAttribute("objId", objId);
request.setAttribute("actionType", actionType);
request.setAttribute("itemList", itemList); // 품목 목록 추가
}catch(Exception e){
e.printStackTrace();
@@ -1753,6 +1811,27 @@ public class ContractMgmtController {
return "/contractMgmt/estimateRegistFormPopup";
}
/**
* 품번 검색 (AJAX용)
*/
@RequestMapping(value = "/contractMgmt/searchPartList.do", produces = "application/json; charset=UTF-8")
@ResponseBody
public List<Map<String, Object>> searchPartList(@RequestParam Map<String, Object> paramMap) {
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
try {
resultList = contractMgmtService.searchPartList(paramMap);
System.out.println("품번 검색 결과 개수: " + resultList.size());
if(resultList.size() > 0) {
System.out.println("첫번째 결과: " + resultList.get(0));
}
} catch (Exception e) {
e.printStackTrace();
}
return resultList;
}
@RequestMapping("/contractMgmt/saveEstimateInfo.do")
public String saveEstimateInfo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
try {

View File

@@ -525,6 +525,53 @@
,A.APPR_STATUS
,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
-- 품목 정보 요약
,(
WITH item_info AS (
SELECT
COALESCE(PM.PART_NAME, CI.PART_NAME) AS PART_NAME,
COUNT(*) OVER() AS total_count,
ROW_NUMBER() OVER(ORDER BY CI.SEQ) AS rn
FROM CONTRACT_ITEM CI
LEFT JOIN PART_MNG PM ON CI.PART_OBJID = PM.OBJID
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
)
SELECT
CASE
WHEN total_count = 1 THEN PART_NAME
WHEN total_count > 1 THEN PART_NAME || ' 외 ' || (total_count - 1) || '건'
ELSE ''
END
FROM item_info
WHERE rn = 1
) AS ITEM_SUMMARY
,(
SELECT MIN(DUE_DATE)
FROM CONTRACT_ITEM
WHERE CONTRACT_OBJID = T.OBJID
AND STATUS = 'ACTIVE'
AND DUE_DATE IS NOT NULL
AND DUE_DATE != ''
) AS EARLIEST_DUE_DATE
,(
SELECT COUNT(*) - 1
FROM CONTRACT_ITEM
WHERE CONTRACT_OBJID = T.OBJID
AND STATUS = 'ACTIVE'
AND DUE_DATE IS NOT NULL
AND DUE_DATE != ''
) AS OTHER_DUE_DATE_COUNT
,(
SELECT RETURN_REASON
FROM CONTRACT_ITEM
WHERE CONTRACT_OBJID = T.OBJID
AND STATUS = 'ACTIVE'
AND RETURN_REASON IS NOT NULL
AND RETURN_REASON != ''
LIMIT 1
) AS RETURN_REASON_SUMMARY
FROM
CONTRACT_MGMT AS T
LEFT OUTER JOIN
@@ -564,7 +611,7 @@
A.OBJID = B.APPROVAL_OBJID
AND TARGET_TYPE IN ('CONTRACT_ESTIMATE')
) A
ON T.OBJID::numeric = A.TARGET_OBJID
ON (SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID order by regdate desc limit 1)::numeric = A.TARGET_OBJID
)
</sql>
@@ -4190,4 +4237,202 @@ ORDER BY ASM.SUPPLY_NAME
EXCHANGE_RATE = #{exchange_rate}
WHERE OBJID = #{contractObjId}
</update>
<!-- ====================================
품목 관리 쿼리
==================================== -->
<!-- 품번 목록 조회 (is_last = 1) -->
<select id="getPartList" parameterType="map" resultType="map">
SELECT
OBJID,
PART_NO,
PART_NAME,
SPEC
FROM
PART_MNG
WHERE
IS_LAST = '1'
AND STATUS = 'release'
ORDER BY
PART_NO
</select>
<!-- 품번 검색 (AJAX용 - 검색어 기반 또는 OBJID 기반) -->
<select id="searchPartList" parameterType="map" resultType="map">
SELECT
OBJID,
PART_NO,
PART_NAME,
SPEC
FROM
PART_MNG
WHERE
IS_LAST = '1'
AND STATUS = 'release'
<if test="partObjId != null and partObjId != ''">
AND OBJID = #{partObjId}
</if>
<if test="searchTerm != null and searchTerm != ''">
AND (
UPPER(PART_NO) LIKE '%' || UPPER(#{searchTerm}) || '%'
OR UPPER(PART_NAME) LIKE '%' || UPPER(#{searchTerm}) || '%'
)
</if>
ORDER BY
PART_NO
LIMIT 100
</select>
<!-- 품목 저장 -->
<insert id="insertContractItem" 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'
)
</insert>
<!-- 품목별 S/N 저장 -->
<insert id="insertContractItemSerial" parameterType="map">
INSERT INTO CONTRACT_ITEM_SERIAL (
OBJID,
ITEM_OBJID,
SEQ,
SERIAL_NO,
REGDATE,
WRITER,
STATUS
) VALUES (
#{objId},
#{itemObjId},
#{seq},
#{serialNo},
NOW(),
#{writer},
'ACTIVE'
)
</insert>
<!-- 견적의 품목 목록 조회 -->
<select id="getContractItemList" parameterType="map" resultType="map">
SELECT
CI.OBJID,
CI.CONTRACT_OBJID,
CI.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
CI.RETURN_REASON,
CI.REGDATE,
CI.WRITER,
STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SEQ) AS SERIAL_NOS,
COUNT(CIS.OBJID) AS SERIAL_COUNT
FROM
CONTRACT_ITEM CI
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.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
CI.REGDATE,
CI.WRITER
ORDER BY
CI.SEQ
</select>
<!-- 품목의 S/N 목록 조회 -->
<select id="getContractItemSerialList" parameterType="map" resultType="map">
SELECT
OBJID,
ITEM_OBJID,
SEQ,
SERIAL_NO,
REGDATE,
WRITER
FROM
CONTRACT_ITEM_SERIAL
WHERE
ITEM_OBJID = #{itemObjId}
AND STATUS = 'ACTIVE'
ORDER BY
SEQ
</select>
<!-- 견적의 품목 전체 삭제 (상태 변경) -->
<update id="deleteContractItems" parameterType="map">
UPDATE CONTRACT_ITEM
SET STATUS = 'INACTIVE',
CHGDATE = NOW(),
CHG_USER_ID = #{userId}
WHERE CONTRACT_OBJID = #{contractObjId}
AND STATUS = 'ACTIVE'
</update>
<!-- 품목의 S/N 전체 삭제 (상태 변경) -->
<update id="deleteContractItemSerials" parameterType="map">
UPDATE CONTRACT_ITEM_SERIAL
SET STATUS = 'INACTIVE'
WHERE ITEM_OBJID IN (
SELECT OBJID
FROM CONTRACT_ITEM
WHERE CONTRACT_OBJID = #{contractObjId}
AND STATUS = 'ACTIVE'
)
</update>
<!-- 특정 품목 삭제 (상태 변경) -->
<update id="deleteContractItem" parameterType="map">
UPDATE CONTRACT_ITEM
SET STATUS = 'INACTIVE',
CHGDATE = NOW(),
CHG_USER_ID = #{userId}
WHERE OBJID = #{itemObjId}
</update>
<!-- 특정 품목의 S/N 전체 삭제 -->
<update id="deleteItemSerials" parameterType="map">
UPDATE CONTRACT_ITEM_SERIAL
SET STATUS = 'INACTIVE'
WHERE ITEM_OBJID = #{itemObjId}
</update>
</mapper>

View File

@@ -1888,4 +1888,201 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
return resultMap;
}
/**
* 품목 저장 (여러 건)
* @param contractObjId 견적 OBJID
* @param itemList 품목 목록
* @param userId 사용자 ID
* @return 성공 여부
*/
public Map<String, Object> saveContractItems(String contractObjId, List<Map<String, Object>> itemList, String userId) {
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
System.out.println("=== saveContractItems 시작 ===");
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);
sqlSession.update("contractMgmt.deleteContractItemSerials", deleteParam);
sqlSession.update("contractMgmt.deleteContractItems", deleteParam);
System.out.println("기존 품목 삭제 완료");
// 새 품목 저장
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();
} 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("품목 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);
}
}
}
}
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "품목이 저장되었습니다.");
} catch (Exception e) {
sqlSession.rollback();
resultMap.put("result", false);
resultMap.put("msg", "품목 저장 중 오류가 발생했습니다.");
e.printStackTrace();
} finally {
sqlSession.close();
}
return resultMap;
}
/**
* 품번 목록 조회 (is_last = 1)
* @return 품번 목록
*/
public List<Map<String, Object>> getPartList() {
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
List<Map<String, Object>> partList = new ArrayList<Map<String, Object>>();
try {
partList = sqlSession.selectList("contractMgmt.getPartList");
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return partList;
}
/**
* 품번 검색 (검색어 기반)
*/
public List<Map<String, Object>> searchPartList(Map<String, Object> paramMap) {
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
List<Map<String, Object>> partList = new ArrayList<Map<String, Object>>();
try {
partList = sqlSession.selectList("contractMgmt.searchPartList", paramMap);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return partList;
}
/**
* 품목 목록 조회
* @param contractObjId 견적 OBJID
* @return 품목 목록
*/
public List<Map<String, Object>> getContractItemList(String contractObjId) {
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
List<Map<String, Object>> itemList = new ArrayList<Map<String, Object>>();
try {
Map<String, Object> param = new HashMap<String, Object>();
param.put("contractObjId", contractObjId);
itemList = sqlSession.selectList("contractMgmt.getContractItemList", param);
// 각 품목의 S/N 목록 조회
for (Map<String, Object> item : itemList) {
String itemObjId = (String) item.get("OBJID");
if(itemObjId == null) {
itemObjId = (String) item.get("objid");
}
System.out.println("품목 OBJID: " + itemObjId);
Map<String, Object> snParam = new HashMap<String, Object>();
snParam.put("itemObjId", itemObjId);
List<Map<String, Object>> snList = sqlSession.selectList("contractMgmt.getContractItemSerialList", snParam);
System.out.println("조회된 S/N 개수: " + (snList != null ? snList.size() : 0));
item.put("SN_LIST", snList);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return itemList;
}
}