From f47b091dca1e2de761097750e7426e9bcdef9d64 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Sat, 18 Oct 2025 22:51:59 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B2=AC=EC=A0=81=EC=9A=94=EC=B2=AD=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=ED=99=94=EB=A9=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/contractMgmt/estimateList_new.jsp | 89 +- .../contractMgmt/estimateRegistFormPopup.jsp | 1011 +++++++++++++++-- database/contract_item_tables.sql | 230 ++++ .../controller/ContractMgmtController.java | 83 +- src/com/pms/salesmgmt/mapper/contractMgmt.xml | 247 +++- .../service/ContractMgmtService.java | 197 ++++ 6 files changed, 1749 insertions(+), 108 deletions(-) create mode 100644 database/contract_item_tables.sql diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp index dfdf6c6..b83babe 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp @@ -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 '' + serialNumbers[0] + ''; +// if(count === 0) return ''; +// if(count === 1) return '' + serialNumbers[0] + ''; // 2개 이상이면 "첫번째 외 N개" 형식 - var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개'; - return '' + displayText + ''; - }, - cellClick:function(e, cell){ - var serialNo = fnc_checkNull(cell.getData().SERIAL_NO); - fn_showSerialNoPopup(serialNo); +// var displayText = serialNumbers[0] + ' 외 ' + (count - 1) + '개'; +// return '' + displayText + ''; +// }, +// cellClick:function(e, cell){ +// var serialNo = fnc_checkNull(cell.getData().SERIAL_NO); +// fn_showSerialNoPopup(serialNo); +// } +// }, +// {headerHozAlign : 'center', hozAlign : 'center', width : '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){
- - - + + + diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp index 21a8a16..b65a108 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp @@ -38,6 +38,12 @@ } @@ -914,8 +1763,9 @@
+
- 견적요청 정보입력 + 견적요청 기본정보
@@ -929,13 +1779,13 @@ - + - + - - - + - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
@@ -951,8 +1801,6 @@ ${code_map.product_cd}
- - + +
- - - -
- - - -
- -
- -
+
+ + + + + +
+ 품목정보 + +
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No품번 *품명 *S/N견적수량 *요청납기고객요청사항반납사유삭제
+ 품목 추가 버튼을 클릭하여 품목을 등록하세요. +
+
+
+ + + + + + +
+ <%-- diff --git a/database/contract_item_tables.sql b/database/contract_item_tables.sql new file mode 100644 index 0000000..e9e3bdd --- /dev/null +++ b/database/contract_item_tables.sql @@ -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; + diff --git a/src/com/pms/salesmgmt/controller/ContractMgmtController.java b/src/com/pms/salesmgmt/controller/ContractMgmtController.java index 8729c14..8f34e36 100644 --- a/src/com/pms/salesmgmt/controller/ContractMgmtController.java +++ b/src/com/pms/salesmgmt/controller/ContractMgmtController.java @@ -665,7 +665,57 @@ public class ContractMgmtController { @RequestMapping("/contractMgmt/saveContractMgmtInfo.do") public String saveContractMgmtInfo(HttpServletRequest request, @RequestParam Map paramMap){ try { - request.setAttribute("RESULT", CommonUtils.getJsonMap(contractMgmtService.saveContractMgmtInfo(request, paramMap)) ); + // 기본 견적 정보 저장 + Map 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>>(){}.getType(); + List> 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 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> itemList = new ArrayList>(); + 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> searchPartList(@RequestParam Map paramMap) { + List> resultList = new ArrayList>(); + + 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 paramMap){ try { diff --git a/src/com/pms/salesmgmt/mapper/contractMgmt.xml b/src/com/pms/salesmgmt/mapper/contractMgmt.xml index 08e3dfb..540349e 100644 --- a/src/com/pms/salesmgmt/mapper/contractMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/contractMgmt.xml @@ -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 ) @@ -4190,4 +4237,202 @@ ORDER BY ASM.SUPPLY_NAME EXCHANGE_RATE = #{exchange_rate} WHERE OBJID = #{contractObjId} + + + + + + + + + + + + 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 INTO CONTRACT_ITEM_SERIAL ( + OBJID, + ITEM_OBJID, + SEQ, + SERIAL_NO, + REGDATE, + WRITER, + STATUS + ) VALUES ( + #{objId}, + #{itemObjId}, + #{seq}, + #{serialNo}, + NOW(), + #{writer}, + 'ACTIVE' + ) + + + + + + + + + + + UPDATE CONTRACT_ITEM + SET STATUS = 'INACTIVE', + CHGDATE = NOW(), + CHG_USER_ID = #{userId} + WHERE CONTRACT_OBJID = #{contractObjId} + AND STATUS = 'ACTIVE' + + + + + UPDATE CONTRACT_ITEM_SERIAL + SET STATUS = 'INACTIVE' + WHERE ITEM_OBJID IN ( + SELECT OBJID + FROM CONTRACT_ITEM + WHERE CONTRACT_OBJID = #{contractObjId} + AND STATUS = 'ACTIVE' + ) + + + + + UPDATE CONTRACT_ITEM + SET STATUS = 'INACTIVE', + CHGDATE = NOW(), + CHG_USER_ID = #{userId} + WHERE OBJID = #{itemObjId} + + + + + UPDATE CONTRACT_ITEM_SERIAL + SET STATUS = 'INACTIVE' + WHERE ITEM_OBJID = #{itemObjId} + + \ No newline at end of file diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java index 9c7063c..ce8bdc4 100644 --- a/src/com/pms/salesmgmt/service/ContractMgmtService.java +++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java @@ -1888,4 +1888,201 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate, return resultMap; } + + /** + * 품목 저장 (여러 건) + * @param contractObjId 견적 OBJID + * @param itemList 품목 목록 + * @param userId 사용자 ID + * @return 성공 여부 + */ + public Map saveContractItems(String contractObjId, List> itemList, String userId) { + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(); + Map resultMap = new HashMap(); + + 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 deleteParam = new HashMap(); + 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 item = itemList.get(i); + + System.out.println("품목 " + (i+1) + " 처리 중: " + item); + + // 품목 OBJID 생성 (CommonUtils 사용) + String itemObjId = CommonUtils.createObjId(); + + // 품목 저장 + Map itemParam = new HashMap(); + itemParam.put("objId", itemObjId); + itemParam.put("contractObjId", contractObjId); + itemParam.put("seq", i + 1); + itemParam.put("partObjId", item.get("partObjId")); + itemParam.put("partNo", item.get("partNo")); + itemParam.put("partName", item.get("partName")); + + // quantity를 Integer로 변환 + Object quantityObj = item.get("quantity"); + Integer quantity = null; + if(quantityObj != null) { + if(quantityObj instanceof Integer) { + quantity = (Integer) quantityObj; + } else if(quantityObj instanceof Double) { + quantity = ((Double) quantityObj).intValue(); + } 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> snList = (List>) item.get("snList"); + if (snList != null && !snList.isEmpty()) { + for (int j = 0; j < snList.size(); j++) { + Map sn = snList.get(j); + String serialNo = (String) sn.get("value"); + + // S/N 값이 비어있으면 건너뛰기 + if (serialNo == null || serialNo.trim().isEmpty()) { + System.out.println("S/N 값이 비어있어서 건너뜀"); + continue; + } + + // S/N OBJID 생성 (CommonUtils 사용) + String snObjId = CommonUtils.createObjId(); + + Map snParam = new HashMap(); + snParam.put("objId", snObjId); + snParam.put("itemObjId", itemObjId); + snParam.put("seq", j + 1); + snParam.put("serialNo", serialNo); + snParam.put("writer", userId); + + sqlSession.insert("contractMgmt.insertContractItemSerial", snParam); + } + } + } + } + + 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> getPartList() { + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(); + List> partList = new ArrayList>(); + + try { + partList = sqlSession.selectList("contractMgmt.getPartList"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + sqlSession.close(); + } + + return partList; + } + + /** + * 품번 검색 (검색어 기반) + */ + public List> searchPartList(Map paramMap) { + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(); + List> partList = new ArrayList>(); + + try { + partList = sqlSession.selectList("contractMgmt.searchPartList", paramMap); + } catch (Exception e) { + e.printStackTrace(); + } finally { + sqlSession.close(); + } + + return partList; + } + + /** + * 품목 목록 조회 + * @param contractObjId 견적 OBJID + * @return 품목 목록 + */ + public List> getContractItemList(String contractObjId) { + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(); + List> itemList = new ArrayList>(); + + try { + Map param = new HashMap(); + param.put("contractObjId", contractObjId); + + itemList = sqlSession.selectList("contractMgmt.getContractItemList", param); + + // 각 품목의 S/N 목록 조회 + for (Map item : itemList) { + String itemObjId = (String) item.get("OBJID"); + if(itemObjId == null) { + itemObjId = (String) item.get("objid"); + } + + System.out.println("품목 OBJID: " + itemObjId); + + Map snParam = new HashMap(); + snParam.put("itemObjId", itemObjId); + + List> 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; + } }