From 3329dc86e4427314593f37973e6533d162e39d1f Mon Sep 17 00:00:00 2001 From: syc0123 Date: Thu, 26 Mar 2026 17:26:02 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[RAPID]=20PCC:=20PDM=20-=20=EC=A0=9C?= =?UTF-8?q?=ED=92=88=EA=B5=AC=EB=B6=84=20=ED=92=88=EB=AA=A9=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/yc/PDM[계획]-제품구분-품목이동.md | 103 +++++++++++++++++++++++++ docs/yc/PDM[맥락]-제품구분-품목이동.md | 39 ++++++++++ docs/yc/PDM[체크]-제품구분-품목이동.md | 64 +++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 docs/yc/PDM[계획]-제품구분-품목이동.md create mode 100644 docs/yc/PDM[맥락]-제품구분-품목이동.md create mode 100644 docs/yc/PDM[체크]-제품구분-품목이동.md diff --git a/docs/yc/PDM[계획]-제품구분-품목이동.md b/docs/yc/PDM[계획]-제품구분-품목이동.md new file mode 100644 index 0000000..70d7255 --- /dev/null +++ b/docs/yc/PDM[계획]-제품구분-품목이동.md @@ -0,0 +1,103 @@ +# PDM[계획] 제품구분 품목정보 이동 + +## 개요 +견적요청등록, 수주통합등록 두 팝업 화면에서 **제품구분**을 기본정보 그리드에서 품목정보 그리드로 이동하고, Machine(0000928) 제품구분 선택 시 수량만큼 행을 자동 분할하는 기능 구현. + +## 현재 동작 +- 기본정보 그리드에 제품구분 드롭다운 존재 (공통코드 0000001) +- CONTRACT_MGMT.PRODUCT 컬럼에 저장 +- 모든 품목이 동일한 제품구분을 공유 + +## 변경 후 동작 +- 기본정보에서 제품구분 제거 +- 품목정보 그리드의 No와 품번 사이에 제품구분 드롭다운 추가 +- 각 품목별로 제품구분 개별 선택 가능 +- CONTRACT_ITEM.PRODUCT 컬럼에 품목별 저장 +- **Machine(0000928) 특수 로직:** + - 품목 추가 시 제품구분이 Machine이면 견적수량 N → N개 행 생성 (각 수량=1) + - Machine이 아니면 기존과 동일 (수량 자유) + +## 시각적 예시 + +### 변경 전 +``` +[기본정보] +주문유형: [선택] | 제품구분: [Machine ▼] | 국내/해외: [선택] | 고객사: [선택] + +[품목정보] +No | 품번 | 품명 | S/N | 견적수량 | ... +``` + +### 변경 후 +``` +[기본정보] +주문유형: [선택] | 국내/해외: [선택] | 고객사: [선택] | (빈칸) + +[품목정보] +No | 제품구분 | 품번 | 품명 | S/N | 견적수량 | ... +``` + +## 아키텍처 + +```mermaid +graph TD + A[JSP 품목 추가] --> B{제품구분 선택} + B -->|Machine 0000928| C[수량 N 입력] + C --> D[N개 행 생성, 각 수량=1] + B -->|기타| E[1개 행 생성, 수량 자유] + D --> F[fn_collectItemsData] + E --> F + F --> G[items_json POST] + G --> H[Service: saveContractItems] + H --> I[Mapper: upsertContractItem + PRODUCT] + I --> J[CONTRACT_ITEM.PRODUCT 저장] +``` + +## 변경 파일 + +| 파일 | 변경 내용 | +|------|----------| +| DB: CONTRACT_ITEM | PRODUCT VARCHAR(20) 컬럼 추가 | +| contractMgmt.xml | master INSERT/UPDATE에서 PRODUCT 주석처리, item INSERT/UPSERT에 PRODUCT 추가 | +| ContractMgmtService.java | item 저장 시 product 파라미터 전달 | +| estimateRegistFormPopup.jsp | 기본정보 제품구분 제거, 품목정보에 추가, Machine 로직 | +| estimateAndOrderRegistFormPopup.jsp | 동일 변경 | + +## 코드 설계 + +### DB +```sql +ALTER TABLE CONTRACT_ITEM ADD COLUMN PRODUCT VARCHAR(20); +``` + +### Mapper XML +- `upsertContractItem`: INSERT/UPDATE에 PRODUCT 추가 +- `upsertContractItemWithOrder`: INSERT/UPDATE에 PRODUCT 추가 +- `saveContractMgmtInfo`: PRODUCT 관련 행 주석처리 +- `saveEstimateAndOrderInfo`: PRODUCT 관련 행 주석처리 +- `getContractItems`: SELECT에 PRODUCT 추가 + +### JSP (공통 패턴) +```javascript +// 품목 추가 시 제품구분 드롭다운 +html += ''; +html += ''; + +// 드롭다운 초기화 +fnc_getCodeListAppend("0000001", "PRODUCT_" + itemId, ""); + +// Machine 분할 로직 +if(productCode === '0000928') { + var qty = parseInt(quantityInput); + for(var i = 0; i < qty; i++) { fn_addItemRow(with quantity=1); } +} +``` + +## 예상 문제 +- 기존 데이터: CONTRACT_MGMT.PRODUCT에만 값이 있고 CONTRACT_ITEM.PRODUCT는 NULL → 조회 시 COALESCE 처리 필요 +- Machine 행 분할 시 S/N 등 부가 데이터 처리 → 동일 값 복사 + +## 설계 원칙 +- 기존 CONTRACT_MGMT.PRODUCT는 주석처리만 (데이터 보존) +- 품목별 제품구분 독립 관리로 확장성 확보 +- Machine 로직은 클라이언트(JSP)에서만 처리 (서버는 받은 대로 저장) diff --git a/docs/yc/PDM[맥락]-제품구분-품목이동.md b/docs/yc/PDM[맥락]-제품구분-품목이동.md new file mode 100644 index 0000000..1ab279d --- /dev/null +++ b/docs/yc/PDM[맥락]-제품구분-품목이동.md @@ -0,0 +1,39 @@ +# PDM[맥락] 제품구분 품목정보 이동 + +## 왜 하는가 +기존에는 제품구분이 기본정보(마스터) 레벨에서 관리되어 한 건의 견적/수주에 포함된 모든 품목이 동일한 제품구분을 가져야 했다. 실무에서는 한 건에 Machine과 Part가 혼재할 수 있으므로, 품목별로 제품구분을 개별 지정할 수 있도록 변경한다. + +또한 Machine(0000928) 제품의 경우 수량 10이면 개별 관리(S/N 추적 등)를 위해 10개의 독립 행으로 분할하여 각각 수량 1로 관리해야 하는 업무 요구사항이 있다. + +## 핵심 결정 + 근거 + +| 결정 | 근거 | +|------|------| +| CONTRACT_MGMT.PRODUCT 주석처리 (삭제X) | 기존 데이터 보존, 마이그레이션 리스크 최소화 | +| CONTRACT_ITEM.PRODUCT 추가 | 품목별 독립 관리를 위한 정규화 | +| Machine 분할을 클라이언트에서 처리 | 서버는 받은 그대로 저장, 로직 단순화 | +| 공통코드 0000001 그대로 사용 | 기존 코드체계 유지 | + +## 관련 파일 + +### 견적요청등록 (estimateRegistFormPopup.jsp) +- Controller: `ContractMgmtController.java:1692` (estimateRegistFormPopup) +- Service: `ContractMgmtService.java:544` (saveContractMgmtInfo) → `saveContractItems:3121` +- Mapper: `contractMgmt.xml:1367` (saveContractMgmtInfo), `:5715` (upsertContractItem) +- 저장 URL: `/contractMgmt/saveContractMgmtInfo.do` +- 마스터 테이블: CONTRACT_MGMT +- 품목 테이블: CONTRACT_ITEM + +### 수주통합등록 (estimateAndOrderRegistFormPopup.jsp) +- Controller: `ContractMgmtController.java:2775` (saveEstimateAndOrderInfo) +- Service: `ContractMgmtService.java:2664` (saveEstimateAndOrderInfo) +- Mapper: `contractMgmt.xml:5149` (saveEstimateAndOrderInfo), `:5761` (upsertContractItemWithOrder) +- 저장 URL: `/contractMgmt/saveEstimateAndOrderInfo.do` +- 마스터 테이블: CONTRACT_MGMT +- 품목 테이블: CONTRACT_ITEM + +## 기술 참고 +- 공통코드 조회: `fnc_getCodeListAppend(codeId, selectboxId, selectedVal)` — common.js +- 공통코드 0000001의 자식 코드 중 0000928이 Machine +- 기존 isMachine 판단: `ContractMgmtService.java:2811` — `"0000928".equals(product_cd)` +- 품목 UPSERT 패턴: `ON CONFLICT (OBJID) DO UPDATE` diff --git a/docs/yc/PDM[체크]-제품구분-품목이동.md b/docs/yc/PDM[체크]-제품구분-품목이동.md new file mode 100644 index 0000000..f2c5e18 --- /dev/null +++ b/docs/yc/PDM[체크]-제품구분-품목이동.md @@ -0,0 +1,64 @@ +# PDM[체크] 제품구분 품목정보 이동 + +## 공정 상태: 0% + +## 구현 체크리스트 + +### DB 변경 +- [ ] CONTRACT_ITEM 테이블에 PRODUCT VARCHAR(20) 컬럼 추가 + +### Mapper XML (contractMgmt.xml) +- [ ] saveContractMgmtInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 +- [ ] saveEstimateAndOrderInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 +- [ ] upsertContractItem: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) +- [ ] upsertContractItemWithOrder: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) +- [ ] getContractItems: SELECT에 PRODUCT 추가 +- [ ] insertContractItem: PRODUCT 추가 +- [ ] insertContractItemWithOrder: PRODUCT 추가 + +### Service (ContractMgmtService.java) +- [ ] saveContractItems(): itemParam에 product 전달 +- [ ] saveEstimateAndOrderInfo(): itemMap에 product 전달 + +### 견적요청등록 (estimateRegistFormPopup.jsp) +- [ ] 기본정보: 제품구분 label+select 제거 (주석처리) +- [ ] 기본정보: colgroup 재배치 +- [ ] 품목정보: colgroup에 제품구분 컬럼 추가 +- [ ] 품목정보: thead에 제품구분 헤더 추가 +- [ ] fn_addItemRow(): 제품구분 드롭다운 셀 추가 +- [ ] fn_addItemRow(): Machine 수량분할 로직 추가 +- [ ] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 +- [ ] fn_collectItemsData(): product 수집 +- [ ] fn_validateItems(): 제품구분 필수 검증 +- [ ] noItemRow colspan 조정 + +### 수주통합등록 (estimateAndOrderRegistFormPopup.jsp) +- [ ] 기본정보: 제품구분 label+select 제거 (주석처리) +- [ ] 기본정보: colgroup 재배치 +- [ ] 품목정보: colgroup에 제품구분 컬럼 추가 +- [ ] 품목정보: thead에 제품구분 헤더 추가 +- [ ] fn_addItemRow(): 제품구분 드롭다운 셀 추가 +- [ ] fn_addItemRow(): Machine 수량분할 로직 추가 +- [ ] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 +- [ ] fn_collectItemsData(): product 수집 (수주 데이터 포함) +- [ ] fn_validateItems(): 제품구분 필수 검증 +- [ ] noItemRow/totalRow colspan 조정 + +## 검증 체크리스트 +- [ ] 견적요청등록: 기본정보에서 제품구분 드롭다운 미표시 +- [ ] 견적요청등록: 품목정보 No 다음에 제품구분 컬럼 존재 +- [ ] 견적요청등록: 품목 추가 시 제품구분 드롭다운 작동 (0000001 목록) +- [ ] 견적요청등록: Machine + 수량10 → 10행 생성, 각 수량1 +- [ ] 견적요청등록: non-Machine → 수량 자유입력 +- [ ] 견적요청등록: 저장 후 재오픈 시 제품구분 정상 표시 +- [ ] 수주통합등록: 위 항목 모두 동일 정상 작동 +- [ ] DB: CONTRACT_ITEM.PRODUCT에 값 정상 저장 + +## 정리 +- [ ] db/checkpoints/ 덤프 파일 정리 +- [ ] 불필요한 console.log 제거 + +## 변경 이력 +| 날짜 | 변경 내용 | +|------|----------| +| 2026-03-26 | PCC 문서 초안 생성 | From 0c400f490ccd9d22b3e6ff974cdf4b66daca46d0 Mon Sep 17 00:00:00 2001 From: syc0123 Date: Fri, 27 Mar 2026 09:42:14 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[RAPID]=20PDM:=20=EC=A0=9C=ED=92=88?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20=ED=92=88=EB=AA=A9=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20-=20=EA=B2=AC=EC=A0=81/=EC=88=98=EC=A3=BC?= =?UTF-8?q?=20=ED=8C=9D=EC=97=85=20=EC=96=91=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기본정보 제품구분 드롭다운 제거 (CONTRACT_MGMT.PRODUCT 주석처리) - 품목정보 No~품번 사이에 제품구분 컬럼 추가 (공통코드 0000001) - Machine(0000928) 선택 시 수량만큼 행 분할, 각 수량=1 고정 - CONTRACT_ITEM.PRODUCT 컬럼 추가 및 UPSERT/SELECT 반영 - getContractItems/getContractItemList GROUP BY에 PRODUCT 추가 Co-Authored-By: Claude Sonnet 4.6 --- .../estimateAndOrderRegistFormPopup.jsp | 154 +++++++++++++++--- .../contractMgmt/estimateRegistFormPopup.jsp | 126 +++++++++++--- src/com/pms/salesmgmt/mapper/contractMgmt.xml | 58 ++++--- .../service/ContractMgmtService.java | 4 +- 4 files changed, 273 insertions(+), 69 deletions(-) diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp index 9e0b650..793814a 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp @@ -366,12 +366,19 @@ var orderQuantity = $row.find(".item-order-quantity").val().replace(/,/g, "").trim(); var orderUnitPrice = $row.find(".item-order-unit-price").val().replace(/,/g, "").trim(); + var product = $row.find(".item-product").val(); + if(!product || product == "") { + alert((i+1) + "번째 품목의 제품구분을 선택해주세요."); + $row.find(".item-product").focus(); + return false; + } + if(!partObjId || partObjId == "") { alert((i+1) + "번째 품목의 품번을 선택해주세요."); $row.find(".item-part-no-select").focus(); return false; } - + if(orderQuantity == "" || orderQuantity == "0") { alert((i+1) + "번째 품목의 수주수량을 입력해주세요."); $row.find(".item-order-quantity").focus(); @@ -405,6 +412,7 @@ var item = { objId: $row.find(".item-objid").val() || '', + product: $row.find(".item-product").val(), partObjId: $row.find(".item-part-objid").val(), partNo: $row.find(".item-part-no").val() ? $row.find(".item-part-no").val().trim() : "", partName: $row.find(".item-part-name").val() ? $row.find(".item-part-name").val().trim() : "", @@ -448,7 +456,14 @@ var html = ''; html += '' + (itemCounter-1) + ''; - + + // 제품구분 드롭다운 + html += ''; + html += ''; + html += ''; + // 품번 셀렉트박스 html += ''; html += ''; + html += ''; + html += ''; + html += ''; + // 품번 셀렉트박스 html += ''; html += ''; + html += ''; + html += ''; + html += ''; + html += ''; html += ''; html += ''; @@ -744,7 +841,11 @@ // 품번/품명 옵션 채우기 fn_fillPartOptions(itemId, savedPartObjId, savedPartNo, savedPartName); - + + // 제품구분 드롭다운 초기화 + fnc_getCodeListAppend("0000001", "PRODUCT_" + itemId, savedProduct); + $("#PRODUCT_" + itemId).select2({ width: '100%' }); + // datepicker 적용 $("#" + itemId + " .date_icon").datepicker({ changeMonth: true, @@ -1534,13 +1635,11 @@ - + - + - - - + @@ -1552,13 +1651,14 @@ ${code_map.category_cd} - + <%-- 제품구분: 품목정보 그리드로 이동 --%> + <%-- + --%>
- - - - - - - + + + + + + + - - + + - + + @@ -1667,14 +1769,14 @@ - - + diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp index 6f0c41b..2aaac3c 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp @@ -477,7 +477,7 @@ message = "등록"; } if (confirm(message + "하시겠습니까?")) { - $("#category_cd,#area_cd,#target_project_no,#customer_objid,#product,#contract_result,#overhaul_order").prop("disabled",""); + $("#category_cd,#area_cd,#target_project_no,#customer_objid,#contract_result,#overhaul_order").prop("disabled",""); // 저장 직전 S/N 확인 console.log("=== 저장 시작 ==="); @@ -533,9 +533,16 @@ for(var i = 0; i < itemRows.length; i++) { var $row = $(itemRows[i]); + var product = $row.find(".item-product").val(); var partObjId = $row.find(".item-part-objid").val(); // hidden 필드에서 가져오기 var quantity = $row.find(".item-quantity").val().replace(/,/g, "").trim(); - + + if(!product || product == "") { + alert((i+1) + "번째 품목의 제품구분을 선택해주세요."); + $row.find(".item-product").focus(); + return false; + } + if(!partObjId || partObjId == "") { alert((i+1) + "번째 품목의 품번을 선택해주세요."); $row.find(".item-part-no-select").focus(); @@ -575,6 +582,7 @@ var item = { objId: $row.find(".item-objid").val(), // 기존 품목 OBJID (수정 시 유지) + product: $row.find(".item-product").val(), partObjId: $row.find(".item-part-objid").val(), partNo: $row.find(".item-part-no").val() ? $row.find(".item-part-no").val().trim() : "", partName: $row.find(".item-part-name").val() ? $row.find(".item-part-name").val().trim() : "", @@ -638,6 +646,7 @@ var savedPartObjId = "<%= item.get("PART_OBJID") != null ? item.get("PART_OBJID") : (item.get("part_objid") != null ? item.get("part_objid") : "") %>"; var savedPartNo = "<%= item.get("PART_NO") != null ? item.get("PART_NO") : (item.get("part_no") != null ? item.get("part_no") : "") %>"; var savedPartName = "<%= item.get("PART_NAME") != null ? item.get("PART_NAME") : (item.get("part_name") != null ? item.get("part_name") : "") %>"; + var savedProduct = "<%= CommonUtils.checkNull(item.get("PRODUCT") != null ? item.get("PRODUCT") : (item.get("product") != null ? item.get("product") : "")) %>"; // JSON 데이터를 안전하게 전달 var snJsonData = <%= snListJson %>; @@ -645,7 +654,14 @@ var html = ''; html += ''; - + + // 제품구분 드롭다운 + html += ''; + // 품번 셀렉트박스 html += ''; html += ''; - + + // 제품구분 드롭다운 + html += ''; + // 품번 셀렉트박스 html += '
No제품구분 * 품번 * 품명 * S/N
+ 품목 추가 버튼을 클릭하여 품목을 등록하세요.
TotalTotal 0 0.00
' + (itemCounter-1) + ''; + html += ''; + html += ''; html += '
' + (itemCounter-1) + ''; + html += ''; + html += ''; html += '- + - + - - - + - + + <%-- 제품구분을 품목정보로 이동 + --%>
@@ -2126,6 +2208,7 @@ ${code_map.category_cd} - - - + + + + - - - + + + + @@ -2239,7 +2325,7 @@ - diff --git a/src/com/pms/salesmgmt/mapper/contractMgmt.xml b/src/com/pms/salesmgmt/mapper/contractMgmt.xml index 7cebd44..1ff5dc1 100644 --- a/src/com/pms/salesmgmt/mapper/contractMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/contractMgmt.xml @@ -1370,7 +1370,7 @@ OBJID ,CATEGORY_CD ,CUSTOMER_OBJID - ,PRODUCT + /* ,PRODUCT -- 제품구분을 품목(CONTRACT_ITEM)으로 이동 */ ,CUSTOMER_PROJECT_NAME ,STATUS_CD ,DUE_DATE @@ -1431,7 +1431,7 @@ #{objId} ,#{category_cd} ,#{customer_objid} - ,#{product} + ,#{customer_project_name} ,#{status_cd} ,#{due_date} @@ -1492,7 +1492,7 @@ SET CATEGORY_CD = #{category_cd} ,CUSTOMER_OBJID = #{customer_objid} - ,PRODUCT = #{product} + ,CUSTOMER_PROJECT_NAME = #{customer_project_name} ,STATUS_CD = #{status_cd} ,DUE_DATE = #{due_date} @@ -1660,7 +1660,7 @@ SET CATEGORY_CD = #{category_cd} ,CUSTOMER_OBJID = #{customer_objid} - ,PRODUCT = #{product} + ,CUSTOMER_PROJECT_NAME = #{customer_project_name} ,STATUS_CD = #{status_cd} ,DUE_DATE = #{due_date} @@ -5152,7 +5152,7 @@ WHERE OBJID, CATEGORY_CD, CUSTOMER_OBJID, - PRODUCT, + /* PRODUCT, -- 제품구분을 품목(CONTRACT_ITEM)으로 이동 */ AREA_CD, CUSTOMER_EQUIP_NAME, CUSTOMER_PROJECT_NAME, @@ -5176,7 +5176,7 @@ WHERE #{objId}, #{category_cd}, #{customer_objid}, - #{product}, + #{area_cd}, #{customer_equip_name}, #{customer_project_name}, @@ -5200,7 +5200,7 @@ WHERE SET CATEGORY_CD = #{category_cd}, CUSTOMER_OBJID = #{customer_objid}, - PRODUCT = #{product}, + AREA_CD = #{area_cd}, CUSTOMER_EQUIP_NAME = #{customer_equip_name}, CUSTOMER_PROJECT_NAME = #{customer_project_name}, @@ -5247,7 +5247,8 @@ WHERE CI.ORDER_SUPPLY_PRICE, CI.ORDER_VAT, CI.ORDER_TOTAL_AMOUNT, - CI.CANCEL_QTY + CI.CANCEL_QTY, + CI.PRODUCT FROM CONTRACT_ITEM CI LEFT JOIN PART_MNG PM ON CI.PART_OBJID = PM.OBJID @@ -5273,7 +5274,8 @@ WHERE CI.ORDER_SUPPLY_PRICE, CI.ORDER_VAT, CI.ORDER_TOTAL_AMOUNT, - CI.CANCEL_QTY + CI.CANCEL_QTY, + CI.PRODUCT ORDER BY CI.SEQ @@ -5370,7 +5372,8 @@ WHERE RETURN_REASON, REGDATE, WRITER, - STATUS + STATUS, + PRODUCT ) VALUES ( #{objId}, #{contractObjId}, @@ -5384,7 +5387,8 @@ WHERE #{returnReason}, NOW(), #{writer}, - 'ACTIVE' + 'ACTIVE', + #{product} ) @@ -5408,7 +5412,8 @@ WHERE ORDER_UNIT_PRICE, ORDER_SUPPLY_PRICE, ORDER_VAT, - ORDER_TOTAL_AMOUNT + ORDER_TOTAL_AMOUNT, + PRODUCT ) VALUES ( #{objId}, #{contractObjId}, @@ -5427,7 +5432,8 @@ WHERE #{orderUnitPrice}, #{orderSupplyPrice}, #{orderVat}, - #{orderTotalAmount} + #{orderTotalAmount}, + #{product} ) @@ -5472,9 +5478,10 @@ WHERE CI.ORDER_SUPPLY_PRICE, CI.ORDER_VAT, CI.ORDER_TOTAL_AMOUNT, + CI.PRODUCT, STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SEQ) AS SERIAL_NOS, COUNT(CIS.OBJID) AS SERIAL_COUNT - FROM + FROM CONTRACT_ITEM CI LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID @@ -5503,8 +5510,9 @@ WHERE CI.ORDER_UNIT_PRICE, CI.ORDER_SUPPLY_PRICE, CI.ORDER_VAT, - CI.ORDER_TOTAL_AMOUNT - ORDER BY + CI.ORDER_TOTAL_AMOUNT, + CI.PRODUCT + ORDER BY CI.SEQ @@ -5726,7 +5734,8 @@ WHERE RETURN_REASON, REGDATE, WRITER, - STATUS + STATUS, + PRODUCT ) VALUES ( #{objId}, #{contractObjId}, @@ -5740,7 +5749,8 @@ WHERE #{returnReason}, NOW(), #{writer}, - 'ACTIVE' + 'ACTIVE', + #{product} ) ON CONFLICT (OBJID) DO UPDATE SET @@ -5754,7 +5764,8 @@ WHERE RETURN_REASON = #{returnReason}, CHGDATE = NOW(), CHG_USER_ID = #{writer}, - STATUS = 'ACTIVE' + STATUS = 'ACTIVE', + PRODUCT = #{product} @@ -5777,7 +5788,8 @@ WHERE ORDER_UNIT_PRICE, ORDER_SUPPLY_PRICE, ORDER_VAT, - ORDER_TOTAL_AMOUNT + ORDER_TOTAL_AMOUNT, + PRODUCT ) VALUES ( #{objId}, #{contractObjId}, @@ -5796,7 +5808,8 @@ WHERE #{orderUnitPrice}, #{orderSupplyPrice}, #{orderVat}, - #{orderTotalAmount} + #{orderTotalAmount}, + #{product} ) ON CONFLICT (OBJID) DO UPDATE SET @@ -5815,7 +5828,8 @@ WHERE ORDER_UNIT_PRICE = #{orderUnitPrice}, ORDER_SUPPLY_PRICE = #{orderSupplyPrice}, ORDER_VAT = #{orderVat}, - ORDER_TOTAL_AMOUNT = #{orderTotalAmount} + ORDER_TOTAL_AMOUNT = #{orderTotalAmount}, + PRODUCT = #{product} diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java index b993aa8..5945002 100644 --- a/src/com/pms/salesmgmt/service/ContractMgmtService.java +++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java @@ -2729,7 +2729,8 @@ private String encodeImageToBase64(String imagePath) { itemMap.put("dueDate", item.get("dueDate") != null ? item.get("dueDate").toString() : ""); itemMap.put("customerRequest", item.get("customerRequest") != null ? item.get("customerRequest").toString() : ""); itemMap.put("returnReason", item.get("returnReason") != null ? item.get("returnReason").toString() : ""); - + itemMap.put("product", item.get("product") != null ? item.get("product").toString() : ""); + // 수주 정보 String orderQuantity = item.get("orderQuantity") != null ? item.get("orderQuantity").toString().replace(",", "") : "0"; String orderUnitPrice = item.get("orderUnitPrice") != null ? item.get("orderUnitPrice").toString().replace(",", "") : "0"; @@ -3178,6 +3179,7 @@ private String encodeImageToBase64(String imagePath) { itemParam.put("dueDate", item.get("dueDate")); itemParam.put("customerRequest", item.get("customerRequest")); itemParam.put("returnReason", item.get("returnReason")); + itemParam.put("product", item.get("product")); itemParam.put("writer", userId); System.out.println("품목 UPSERT 시도 - OBJID: " + itemObjId); From 0ceb78714bef6038dadf1d573cf5df7ea6f9d3be Mon Sep 17 00:00:00 2001 From: syc0123 Date: Fri, 27 Mar 2026 09:42:40 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[RAPID]=20PDM=20=EC=B2=B4=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20(100%)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- docs/yc/PDM[체크]-제품구분-품목이동.md | 80 +++++++++++++------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/yc/PDM[체크]-제품구분-품목이동.md b/docs/yc/PDM[체크]-제품구분-품목이동.md index f2c5e18..148674a 100644 --- a/docs/yc/PDM[체크]-제품구분-품목이동.md +++ b/docs/yc/PDM[체크]-제품구분-품목이동.md @@ -1,58 +1,59 @@ # PDM[체크] 제품구분 품목정보 이동 -## 공정 상태: 0% +## 공정 상태: 100% ## 구현 체크리스트 ### DB 변경 -- [ ] CONTRACT_ITEM 테이블에 PRODUCT VARCHAR(20) 컬럼 추가 +- [x] CONTRACT_ITEM 테이블에 PRODUCT VARCHAR(20) 컬럼 추가 ### Mapper XML (contractMgmt.xml) -- [ ] saveContractMgmtInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 -- [ ] saveEstimateAndOrderInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 -- [ ] upsertContractItem: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) -- [ ] upsertContractItemWithOrder: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) -- [ ] getContractItems: SELECT에 PRODUCT 추가 -- [ ] insertContractItem: PRODUCT 추가 -- [ ] insertContractItemWithOrder: PRODUCT 추가 +- [x] saveContractMgmtInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 (XML 주석으로 변경) +- [x] saveEstimateAndOrderInfo: PRODUCT 관련 INSERT/UPDATE 주석처리 (XML 주석으로 변경) +- [x] upsertContractItem: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) +- [x] upsertContractItemWithOrder: PRODUCT 추가 (INSERT + ON CONFLICT UPDATE) +- [x] getContractItems: SELECT에 PRODUCT 추가 + GROUP BY에 PRODUCT 추가 +- [x] getContractItemList: SELECT에 PRODUCT 추가 + GROUP BY에 PRODUCT 추가 +- [x] insertContractItem: PRODUCT 추가 +- [x] insertContractItemWithOrder: PRODUCT 추가 ### Service (ContractMgmtService.java) -- [ ] saveContractItems(): itemParam에 product 전달 -- [ ] saveEstimateAndOrderInfo(): itemMap에 product 전달 +- [x] saveContractItems(): itemParam에 product 전달 +- [x] saveEstimateAndOrderInfo(): itemMap에 product 전달 ### 견적요청등록 (estimateRegistFormPopup.jsp) -- [ ] 기본정보: 제품구분 label+select 제거 (주석처리) -- [ ] 기본정보: colgroup 재배치 -- [ ] 품목정보: colgroup에 제품구분 컬럼 추가 -- [ ] 품목정보: thead에 제품구분 헤더 추가 -- [ ] fn_addItemRow(): 제품구분 드롭다운 셀 추가 -- [ ] fn_addItemRow(): Machine 수량분할 로직 추가 -- [ ] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 -- [ ] fn_collectItemsData(): product 수집 -- [ ] fn_validateItems(): 제품구분 필수 검증 -- [ ] noItemRow colspan 조정 +- [x] 기본정보: 제품구분 label+select 제거 (주석처리) +- [x] 기본정보: colgroup 재배치 +- [x] 품목정보: colgroup에 제품구분 컬럼 추가 +- [x] 품목정보: thead에 제품구분 헤더 추가 +- [x] fn_addItemRow(): 제품구분 드롭다운 셀 추가 +- [x] fn_addItemRow(): Machine 수량분할 로직 추가 +- [x] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 +- [x] fn_collectItemsData(): product 수집 +- [x] fn_validateItems(): 제품구분 필수 검증 +- [x] noItemRow colspan 조정 ### 수주통합등록 (estimateAndOrderRegistFormPopup.jsp) -- [ ] 기본정보: 제품구분 label+select 제거 (주석처리) -- [ ] 기본정보: colgroup 재배치 -- [ ] 품목정보: colgroup에 제품구분 컬럼 추가 -- [ ] 품목정보: thead에 제품구분 헤더 추가 -- [ ] fn_addItemRow(): 제품구분 드롭다운 셀 추가 -- [ ] fn_addItemRow(): Machine 수량분할 로직 추가 -- [ ] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 -- [ ] fn_collectItemsData(): product 수집 (수주 데이터 포함) -- [ ] fn_validateItems(): 제품구분 필수 검증 -- [ ] noItemRow/totalRow colspan 조정 +- [x] 기본정보: 제품구분 label+select 제거 (주석처리) +- [x] 기본정보: colgroup 재배치 +- [x] 품목정보: colgroup에 제품구분 컬럼 추가 +- [x] 품목정보: thead에 제품구분 헤더 추가 +- [x] fn_addItemRow(): 제품구분 드롭다운 셀 추가 +- [x] fn_addItemRow(): Machine 수량분할 로직 추가 +- [x] fn_loadExistingItems(): 제품구분 표시 + 드롭다운 초기화 +- [x] fn_collectItemsData(): product 수집 (수주 데이터 포함) +- [x] fn_validateItems(): 제품구분 필수 검증 +- [x] noItemRow/totalRow colspan 조정 ## 검증 체크리스트 -- [ ] 견적요청등록: 기본정보에서 제품구분 드롭다운 미표시 -- [ ] 견적요청등록: 품목정보 No 다음에 제품구분 컬럼 존재 -- [ ] 견적요청등록: 품목 추가 시 제품구분 드롭다운 작동 (0000001 목록) -- [ ] 견적요청등록: Machine + 수량10 → 10행 생성, 각 수량1 -- [ ] 견적요청등록: non-Machine → 수량 자유입력 -- [ ] 견적요청등록: 저장 후 재오픈 시 제품구분 정상 표시 -- [ ] 수주통합등록: 위 항목 모두 동일 정상 작동 -- [ ] DB: CONTRACT_ITEM.PRODUCT에 값 정상 저장 +- [x] 견적요청등록: 기본정보에서 제품구분 드롭다운 미표시 +- [x] 견적요청등록: 품목정보 No 다음에 제품구분 컬럼 존재 +- [x] 견적요청등록: 품목 추가 시 제품구분 드롭다운 작동 (0000001 목록) +- [x] 견적요청등록: Machine + 수량10 → 10행 생성, 각 수량1 +- [x] 견적요청등록: non-Machine → 수량 자유입력 +- [x] 견적요청등록: 저장 후 재오픈 시 제품구분 정상 표시 +- [x] 수주통합등록: 위 항목 모두 동일 정상 작동 +- [x] DB: CONTRACT_ITEM.PRODUCT에 값 정상 저장 ## 정리 - [ ] db/checkpoints/ 덤프 파일 정리 @@ -62,3 +63,4 @@ | 날짜 | 변경 내용 | |------|----------| | 2026-03-26 | PCC 문서 초안 생성 | +| 2026-03-27 | 구현 및 검증 완료 | From 2abe151da004b8d6070e9c0d3bf488c123b4fb3c Mon Sep 17 00:00:00 2001 From: syc0123 Date: Fri, 27 Mar 2026 10:03:41 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[RAPID-micro]=20=EA=B8=B0=EB=B3=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 견적요청등록: 3열→4열 2행 (8개 필드 수용) - 수주통합등록: 2행 4열→3행 3열 (9개 필드 균등 배치) Co-Authored-By: Claude Sonnet 4.6 --- .../estimateAndOrderRegistFormPopup.jsp | 12 ++++----- .../contractMgmt/estimateRegistFormPopup.jsp | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp index 793814a..3618525 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp @@ -1675,7 +1675,7 @@ - + + + + + - - - - + -
No제품구분 * 품번 * 품명 * S/N
+ 품목 추가 버튼을 클릭하여 품목을 등록하세요.
@@ -1696,22 +1696,22 @@ ${code_map.contract_currency}
diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp index 2aaac3c..adf5cd8 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp @@ -2191,15 +2191,17 @@
- - - - - - + + + + + + + + - + - - - - + + + + + From 0914eece46d1830757f81f78fee36906fabf2f48 Mon Sep 17 00:00:00 2001 From: syc0123 Date: Fri, 27 Mar 2026 10:14:14 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[RAPID-micro]=20Machine=20=EC=88=98?= =?UTF-8?q?=EB=9F=89=EB=B6=84=ED=95=A0=20=EC=A0=9C=EA=B1=B0=20+=20?= =?UTF-8?q?=EA=B2=B0=EC=9E=AC=EC=97=AC=EB=B6=80=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=202=ED=96=89=20=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 견적요청등록/수주통합등록: fn_handleMachineExpansion 및 blur 핸들러 제거 - 견적요청등록: 기본정보 2행 빈칸을 결재여부(필요/불필요) 필드로 채움 Co-Authored-By: Claude Sonnet 4.6 --- .../estimateAndOrderRegistFormPopup.jsp | 58 ------------------ .../contractMgmt/estimateRegistFormPopup.jsp | 60 +------------------ 2 files changed, 2 insertions(+), 116 deletions(-) diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp index 3618525..2d985cf 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp @@ -583,10 +583,6 @@ fn_calculateTotal(); }); - // Machine 수량 분할: 수주수량 입력 완료 시 체크 - $("#" + itemId + " .item-order-quantity").on("blur", function() { - fn_handleMachineExpansion(itemId); - }); // 품목 정보 저장 var itemData = { @@ -645,60 +641,6 @@ itemList.push(itemData); } - // Machine(0000928) 제품구분 수량 분할 처리 - function fn_handleMachineExpansion(itemId) { - var $row = $("#" + itemId); - var productVal = $row.find(".item-product").val(); - if(productVal !== '0000928') return; - - var qtyInput = $row.find(".item-order-quantity").val().replace(/,/g, "").trim(); - var qty = parseInt(qtyInput); - if(isNaN(qty) || qty <= 1) return; - - // 현재 행의 데이터 수집 - var partObjId = $row.find(".item-part-objid").val(); - var partNo = $row.find(".item-part-no").val(); - var partName = $row.find(".item-part-name").val(); - var dueDate = $row.find(".item-due-date").val(); - var returnReason = $row.find(".item-return-reason").val(); - var customerRequest = $row.find(".item-customer-request").val(); - var orderUnitPrice = $row.find(".item-order-unit-price").val(); - - // 현재 행의 수량을 1로 변경 - $row.find(".item-order-quantity").val("1"); - fn_calculateItemAmount(itemId); - - // 나머지 qty-1개 행 추가 - for(var i = 1; i < qty; i++) { - fn_addItemRow(); - var $lastRow = $("#itemListBody .item-row:last"); - var lastItemId = $lastRow.attr("id"); - - // 제품구분 설정 - fnc_getCodeListAppend("0000001", "PRODUCT_" + lastItemId, "0000928"); - $("#PRODUCT_" + lastItemId).select2({ width: '100%' }); - - // 품번/품명 설정 - if(partObjId) { - $lastRow.find(".item-part-objid").val(partObjId); - $lastRow.find(".item-part-no").val(partNo); - $lastRow.find(".item-part-name").val(partName); - var $pnSelect = $("#PART_NO_" + lastItemId); - var $pnameSelect = $("#PART_NAME_" + lastItemId); - $pnSelect.append(new Option(partNo, partObjId, true, true)); - $pnameSelect.append(new Option(partName, partObjId, true, true)); - } - - $lastRow.find(".item-order-quantity").val("1"); - if(orderUnitPrice) $lastRow.find(".item-order-unit-price").val(orderUnitPrice); - if(dueDate) $lastRow.find(".item-due-date").val(dueDate); - if(returnReason) $lastRow.find(".item-return-reason").val(returnReason).trigger('change'); - if(customerRequest) $lastRow.find(".item-customer-request").val(customerRequest); - fn_calculateItemAmount(lastItemId); - } - fn_calculateTotal(); - } - // 기존 품목 데이터 로드 (수정 모드) function fn_loadExistingItems() { <% diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp index adf5cd8..d2d2a86 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateRegistFormPopup.jsp @@ -1543,10 +1543,6 @@ $(this).val(formatInteger($(this).val().replace(/[^0-9]/g, ""))); }); - // Machine 수량 분할: 수량 입력 완료 시 체크 - $("#" + itemId + " .item-quantity").on("blur", function() { - fn_handleMachineExpansion(itemId); - }); // 품목 정보 저장 itemList.push({ @@ -1555,53 +1551,6 @@ }); } - // Machine(0000928) 제품구분 수량 분할 처리 - function fn_handleMachineExpansion(itemId) { - var $row = $("#" + itemId); - var productVal = $row.find(".item-product").val(); - if(productVal !== '0000928') return; - - var qtyInput = $row.find(".item-quantity").val().replace(/,/g, "").trim(); - var qty = parseInt(qtyInput); - if(isNaN(qty) || qty <= 1) return; - - var partObjId = $row.find(".item-part-objid").val(); - var partNo = $row.find(".item-part-no").val(); - var partName = $row.find(".item-part-name").val(); - var dueDate = $row.find(".item-due-date").val(); - var returnReason = $row.find(".item-return-reason").val(); - var customerRequest = $row.find(".item-customer-request").val(); - - // 현재 행의 수량을 1로 변경 - $row.find(".item-quantity").val("1"); - - // 나머지 qty-1개 행 추가 - for(var i = 1; i < qty; i++) { - fn_addItemRow(); - var $lastRow = $("#itemListBody .item-row:last"); - var lastItemId = $lastRow.attr("id"); - - // 제품구분 설정 - fnc_getCodeListAppend("0000001", "PRODUCT_" + lastItemId, "0000928"); - $("#PRODUCT_" + lastItemId).select2({ width: '100%' }); - - // 품번/품명 설정 - if(partObjId) { - $lastRow.find(".item-part-objid").val(partObjId); - $lastRow.find(".item-part-no").val(partNo); - $lastRow.find(".item-part-name").val(partName); - var $pnSelect = $("#PART_NO_" + lastItemId); - var $pnameSelect = $("#PART_NAME_" + lastItemId); - $pnSelect.append(new Option(partNo, partObjId, true, true)); - $pnameSelect.append(new Option(partName, partObjId, true, true)); - } - - $lastRow.find(".item-quantity").val("1"); - if(dueDate) $lastRow.find(".item-due-date").val(dueDate); - if(returnReason) $lastRow.find(".item-return-reason").val(returnReason).trigger('change'); - if(customerRequest) $lastRow.find(".item-customer-request").val(customerRequest); - } - } // 품번/품명 셀렉트박스 옵션 채우기 (Select2 AJAX) function fn_fillPartOptions(itemId, selectedObjId, savedPartNo, savedPartName) { @@ -2260,13 +2209,8 @@ - - - - - - From c0cf4eb0b41dc953553852189a458037e5ecb3fa Mon Sep 17 00:00:00 2001 From: syc0123 Date: Fri, 27 Mar 2026 10:42:14 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[RAPID-micro]=20.gitignore=EC=97=90=20db/ch?= =?UTF-8?q?eckpoints/=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2a8c154..7a2e69c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ Thumbs.db .playwright-mcp/ .omc/ .mcp.json +db/checkpoints/
@@ -2231,10 +2233,6 @@ ${code_map.customer_cd}
@@ -2258,6 +2260,7 @@
+ -