diff --git a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp index 48ee36d..8c3fea0 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomPopupLeft.jsp @@ -39,6 +39,11 @@ body { .tabulator-row.level-8 { background-color: #dce6f1 !important; } .tabulator-row.level-9 { background-color: #FFFFEB !important; } .tabulator-row.level-10 { background-color: #ffffff !important; } + +/* 편집 가능한 컬럼 헤더 스타일 */ +.editable-header { + background-color: #fff9c4 !important; +} + + + + + + + + + 등록된 품목이 없습니다. + + + + + + + +   +   +   +   +   +   +   +   +   + + + + + + + + +
+ + + + + +
참 조 문 서선택된 문서가 없습니다.
+
+ + + +
+ + + +
+ + + + + diff --git a/WebContent/WEB-INF/view/salesMng/proposalMngList.jsp b/WebContent/WEB-INF/view/salesMng/proposalMngList.jsp index aabba3c..2676618 100644 --- a/WebContent/WEB-INF/view/salesMng/proposalMngList.jsp +++ b/WebContent/WEB-INF/view/salesMng/proposalMngList.jsp @@ -200,7 +200,7 @@ function fn_search(){ // 품의서 상세 팝업 function fn_openProposalFormPopUp(objId){ var url = "/salesMng/proposalFormPopUp.do?PROPOSAL_OBJID=" + fnc_checkNull(objId); - window.open(url, "proposalFormPopUp", "width=1200,height=700,scrollbars=yes,resizable=yes"); + window.open(url, "proposalFormPopUp", "width=1200,height=900,scrollbars=yes,resizable=yes"); } function _fnc_datepick(){ diff --git a/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp b/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp index f42f843..44e8f85 100644 --- a/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp +++ b/WebContent/WEB-INF/view/salesMng/purchaseListFormPopUp.jsp @@ -721,10 +721,12 @@ function fn_save() { return; } - // 품의서작성일 자동 설정 (현재 날짜) + // 저장 전 데이터 가공 gridData.forEach(function(item) { - // PROPOSAL_DATE는 품의서 생성 시에만 자동 설정되므로 여기서는 제거 - // (기존에 저장된 값이 있으면 유지, 없으면 NULL로 유지) + // TOTAL_PRICE 계산 (PO_QTY * UNIT_PRICE) + var poQty = parseFloat(item.PO_QTY) || 0; + var unitPrice = parseFloat(item.UNIT_PRICE) || 0; + item.TOTAL_PRICE = poQty * unitPrice; // 사용여부 변환: 사용/미사용 → Y/N if(item.USE_YN === '사용') { diff --git a/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp b/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp index e6f6109..b416dbd 100644 --- a/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp +++ b/WebContent/WEB-INF/view/salesMng/salesRequestMngRegList.jsp @@ -506,12 +506,13 @@ function fn_createProposal() { success: function(response) { if(response.resultFlag === "S") { var targetParts = response.data; + var excludedParts = response.excludedParts || []; // 공급업체 미입력 품목 // 3. 대상 품목 확인 if(!targetParts || targetParts.length == 0) { Swal.fire({ title: '알림', - text: '품의서를 생성할 품목이 없습니다.\n(단가가 입력되고 품의서가 생성되지 않은 품목만 대상)', + text: '품의서를 생성할 품목이 없습니다.\n(단가와 공급업체가 모두 입력되고 품의서가 생성되지 않은 품목만 대상)', icon: 'info' }); return; @@ -519,17 +520,33 @@ function fn_createProposal() { // 4. 품의서 생성 확인 var partCount = targetParts.length; - var partList = targetParts.map(function(part) { + var partListHtml = targetParts.map(function(part) { return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); - }).join('\n'); + }).join('
'); + + // 공급업체 미입력으로 제외된 품목이 있는 경우 알림 추가 + var excludedHtml = ''; + if(excludedParts && excludedParts.length > 0) { + var excludedListHtml = excludedParts.map(function(part) { + return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); + }).join('
'); + + excludedHtml = '
' + + '

⚠️ 공급업체 미입력으로 제외된 품목 (' + excludedParts.length + '건)

' + + '
' + + excludedListHtml + + '
' + + '
'; + } Swal.fire({ title: '품의서 생성', html: '
' + '

' + partCount + '건의 품목으로 품의서를 생성합니다.

' + - '
' + - partList + + '
' + + partListHtml + '
' + + excludedHtml + '
', icon: 'question', showCancelButton: true, @@ -541,10 +558,27 @@ function fn_createProposal() { } }); } else { + // 실패 시에도 공급업체 미입력 품목 정보 표시 + var excludedParts = response.excludedParts || []; + var excludedHtml = ''; + + if(excludedParts && excludedParts.length > 0) { + var excludedListHtml = excludedParts.map(function(part) { + return '- ' + fnc_checkNull(part.PART_NO) + ' / ' + fnc_checkNull(part.PART_NAME); + }).join('
'); + + excludedHtml = '

' + + '

⚠️ 공급업체 미입력 품목 (' + excludedParts.length + '건)

' + + '
' + + excludedListHtml + + '
' + + '
'; + } + Swal.fire({ - title: '오류', - text: response.message || '품목 조회 중 오류가 발생했습니다.', - icon: 'error' + title: '알림', + html: (response.message || '품목 조회 중 오류가 발생했습니다.') + excludedHtml, + icon: 'info' }); } }, diff --git a/src/com/pms/mapper/productionplanning.xml b/src/com/pms/mapper/productionplanning.xml index b826bcf..faea338 100644 --- a/src/com/pms/mapper/productionplanning.xml +++ b/src/com/pms/mapper/productionplanning.xml @@ -3833,12 +3833,50 @@ WHERE OBJID = #{mbomHeaderObjid} - + DELETE FROM MBOM_DETAIL WHERE MBOM_HEADER_OBJID = #{mbomHeaderObjid} + + + DELETE FROM MBOM_DETAIL + WHERE OBJID = #{objid} + + + + + UPDATE MBOM_DETAIL + SET + PARENT_OBJID = NULLIF(#{parentObjid}, ''), + SEQ = NULLIF(#{seq}::TEXT, '')::INTEGER, + LEVEL = NULLIF(#{level}::TEXT, '')::INTEGER, + PART_OBJID = NULLIF(#{partObjid}, ''), + PART_NO = NULLIF(#{partNo}, ''), + PART_NAME = NULLIF(#{partName}, ''), + QTY = NULLIF(#{qty}::TEXT, '')::NUMERIC, + UNIT = NULLIF(#{unit}, ''), + SUPPLY_TYPE = NULLIF(#{supplyType}, ''), + MAKE_OR_BUY = NULLIF(#{makeOrBuy}, ''), + RAW_MATERIAL_PART_NO = NULLIF(#{rawMaterialPartNo}, ''), + RAW_MATERIAL_SPEC = NULLIF(#{rawMaterialSpec}, ''), + RAW_MATERIAL = NULLIF(#{rawMaterial}, ''), + RAW_MATERIAL_SIZE = NULLIF(#{rawMaterialSize}, ''), + PROCESSING_VENDOR = NULLIF(#{processingVendor}, ''), + PROCESSING_DEADLINE = NULLIF(#{processingDeadline}, ''), + GRINDING_DEADLINE = NULLIF(#{grindingDeadline}, ''), + REQUIRED_QTY = NULLIF(#{requiredQty}::TEXT, '')::NUMERIC, + ORDER_QTY = NULLIF(#{orderQty}::TEXT, '')::NUMERIC, + PRODUCTION_QTY = NULLIF(#{productionQty}::TEXT, '')::NUMERIC, + STOCK_QTY = NULLIF(#{stockQty}::TEXT, '')::NUMERIC, + SHORTAGE_QTY = NULLIF(#{shortageQty}::TEXT, '')::NUMERIC, + EDITER = #{sessionUserId}, + EDIT_DATE = NOW(), + REMARK = NULLIF(#{remark}, '') + WHERE OBJID = #{objid} + + INSERT INTO MBOM_HISTORY ( diff --git a/src/com/pms/mapper/purchaseOrder.xml b/src/com/pms/mapper/purchaseOrder.xml index 8ca58b5..3534bc1 100644 --- a/src/com/pms/mapper/purchaseOrder.xml +++ b/src/com/pms/mapper/purchaseOrder.xml @@ -2863,8 +2863,102 @@ WHERE OBJID = (SELECT PURCHASE_ORDER_MASTER_OBJID FROM PURCHASE_ORDER_PART POP W WHERE OBJID = #{PURCHASE_ORDER_MASTER_OBJID} - + + + + + + + + + + + + + + UPDATE SALES_REQUEST_MASTER SET + RECIPIENT_REF = #{RECIPIENT_REF}, + EXECUTOR = #{EXECUTOR}, + EXECUTION_DATE = CASE WHEN #{EXECUTION_DATE} IS NOT NULL AND #{EXECUTION_DATE} != '' THEN #{EXECUTION_DATE}::DATE ELSE NULL END, + TITLE = #{TITLE} + WHERE OBJID = #{PROPOSAL_OBJID} + + + + + UPDATE SALES_REQUEST_PART SET + DELIVERY_REQUEST_DATE = CASE WHEN #{DELIVERY_REQUEST_DATE} IS NOT NULL AND #{DELIVERY_REQUEST_DATE} != '' THEN #{DELIVERY_REQUEST_DATE} ELSE NULL END, + UNIT = #{UNIT}, + REMARK = #{REMARK} + WHERE OBJID = #{PART_OBJID} + + \ No newline at end of file diff --git a/src/com/pms/salesmgmt/controller/SalesMngController.java b/src/com/pms/salesmgmt/controller/SalesMngController.java index 9573582..43a0d7e 100644 --- a/src/com/pms/salesmgmt/controller/SalesMngController.java +++ b/src/com/pms/salesmgmt/controller/SalesMngController.java @@ -1276,12 +1276,24 @@ public class SalesMngController { public String proposalFormPopUp(HttpServletRequest request, @RequestParam Map paramMap){ Map resultMap = new HashMap(); Map code_map = new HashMap(); + ArrayList approvalList = new ArrayList(); + List partList = new ArrayList(); try { String proposalObjId = CommonUtils.checkNull(paramMap.get("PROPOSAL_OBJID")); if(!"".equals(proposalObjId)){ resultMap = commonService.selectOne("salesMng.getProposalInfo", request, paramMap); + + // 결재 정보 조회 + Map approvalParam = new HashMap(); + approvalParam.put("OBJID", proposalObjId); + approvalList = approvalService.getApprovalLine(request, approvalParam); + + // 품의서 품목 리스트 조회 + Map partParam = new HashMap(); + partParam.put("PROPOSAL_OBJID", proposalObjId); + partList = commonService.selectList("salesMng.getProposalPartList", request, partParam); } else { resultMap.put("OBJID", CommonUtils.createObjId()); resultMap.put("STATUS", "create"); @@ -1293,6 +1305,8 @@ public class SalesMngController { code_map.put("order_type", commonService.bizMakeOptionList("0001822", (String)resultMap.get("ORDER_TYPE"), "common.getCodeselect")); // 제품구분 code_map.put("product_name", commonService.bizMakeOptionList("0000016", (String)resultMap.get("PRODUCT_NAME"), "common.getCodeselect")); + // 단위 코드 목록 (UNIT_CD: 단위) + code_map.put("unit_list", commonService.bizMakeOptionList("0001399", "", "common.getCodeselect")); } catch (Exception e) { e.printStackTrace(); @@ -1300,6 +1314,8 @@ public class SalesMngController { request.setAttribute("resultMap", resultMap); request.setAttribute("code_map", code_map); + request.setAttribute("approvalList", approvalList); + request.setAttribute("partList", partList); return "/salesMng/proposalFormPopUp"; } @@ -1345,13 +1361,22 @@ public class SalesMngController { // Service의 공통 메서드 사용 Map partsInfo = salesMngService.getProposalTargetPartsWithInfo(sqlSession, salesRequestMasterObjid); List targetParts = (List)partsInfo.get("targetParts"); + List excludedParts = (List)partsInfo.get("excludedParts"); // 공급업체 미입력 품목 if(targetParts != null && !targetParts.isEmpty()) { resultMap.put("resultFlag", "S"); resultMap.put("data", targetParts); + resultMap.put("excludedParts", excludedParts); // 공급업체 미입력 품목도 함께 반환 } else { - resultMap.put("resultFlag", "F"); - resultMap.put("message", "품의서 생성 대상 품목이 없습니다.\n(단가가 입력되고 품의서가 생성되지 않은 품목만 가능)"); + // 대상 품목이 없는 경우, 공급업체 미입력 품목이 있는지 확인 + if(excludedParts != null && !excludedParts.isEmpty()) { + resultMap.put("resultFlag", "F"); + resultMap.put("message", "품의서 생성 대상 품목이 없습니다.\n(단가와 공급업체가 모두 입력된 품목만 가능)"); + resultMap.put("excludedParts", excludedParts); + } else { + resultMap.put("resultFlag", "F"); + resultMap.put("message", "품의서 생성 대상 품목이 없습니다.\n(단가가 입력되고 품의서가 생성되지 않은 품목만 가능)"); + } } } catch (Exception e) { @@ -1388,4 +1413,47 @@ public class SalesMngController { public Map createProposalFromPurchaseList(HttpServletRequest request, @RequestParam Map paramMap){ return createProposal(request, paramMap); } + + /** + * 품의서 저장 (마스터 + 품목) + * @param request + * @param paramMap + * @return + */ + @ResponseBody + @RequestMapping("/salesMng/saveProposal.do") + public Map saveProposal(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + SqlSession sqlSession = null; + + try { + sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + // 1. 마스터 정보 저장 + sqlSession.update("salesMng.updateProposalMaster", paramMap); + + // 2. 품목 정보 저장 (JSON 배열로 전달받음) + String partListJson = CommonUtils.checkNull(paramMap.get("PART_LIST")); + if(!"".equals(partListJson)) { + List> partList = JsonUtil.JsonToList(partListJson); + for(Map part : partList) { + sqlSession.update("salesMng.updateProposalPart", part); + } + } + + sqlSession.commit(); + resultMap.put("resultFlag", "S"); + resultMap.put("message", "저장되었습니다."); + + } catch (Exception e) { + if(sqlSession != null) sqlSession.rollback(); + e.printStackTrace(); + resultMap.put("resultFlag", "F"); + resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage()); + } finally { + if(sqlSession != null) sqlSession.close(); + } + + return resultMap; + } } diff --git a/src/com/pms/salesmgmt/service/SalesMngService.java b/src/com/pms/salesmgmt/service/SalesMngService.java index c4e8578..40fbcda 100644 --- a/src/com/pms/salesmgmt/service/SalesMngService.java +++ b/src/com/pms/salesmgmt/service/SalesMngService.java @@ -1927,21 +1927,26 @@ public class SalesMngService { // 2. 품의서 대상 품목 조회 (M-BOM 기반 여부에 따라 분기) List targetParts = null; + List excludedParts = null; // 공급업체 미입력으로 제외된 품목 if(!mbomHeaderObjid.isEmpty()) { // M-BOM 기반 -> MBOM_DETAIL에서 조회 Map mbomParam = new HashMap(); mbomParam.put("MBOM_HEADER_OBJID", mbomHeaderObjid); targetParts = sqlSession.selectList("salesMng.getProposalTargetPartsFromMBom", mbomParam); + excludedParts = sqlSession.selectList("salesMng.getProposalExcludedPartsFromMBom", mbomParam); } else { // 수동 작성 -> SALES_REQUEST_PART에서 조회 targetParts = sqlSession.selectList("salesMng.getProposalTargetPartsFromManual", paramMap); + excludedParts = sqlSession.selectList("salesMng.getProposalExcludedPartsFromManual", paramMap); } targetParts = CommonUtils.keyChangeUpperList(targetParts); + excludedParts = CommonUtils.keyChangeUpperList(excludedParts); // 3. 결과 반환 result.put("targetParts", targetParts); + result.put("excludedParts", excludedParts); // 공급업체 미입력 품목 result.put("mbomHeaderObjid", mbomHeaderObjid); result.put("purchaseRequestInfo", purchaseRequestInfo); diff --git a/src/com/pms/service/ProductionPlanningService.java b/src/com/pms/service/ProductionPlanningService.java index 80e679d..5b19f62 100644 --- a/src/com/pms/service/ProductionPlanningService.java +++ b/src/com/pms/service/ProductionPlanningService.java @@ -1361,107 +1361,100 @@ public class ProductionPlanningService { } } - // M-BOM 업데이트 + // M-BOM 헤더 업데이트 paramMap.put("mbomHeaderObjid", existingMbom.get("OBJID")); sqlSession.update("productionplanning.updateMbomHeader", paramMap); - // 삭제 전에 기존 데이터 백업 (PROPOSAL_DATE, VENDOR, NET_QTY, PO_QTY 등 보존) + // 기존 데이터 조회 Map queryParam = new HashMap<>(); - queryParam.put("mbomHeaderObjid", existingMbom.get("OBJID")); // camelCase로 수정 + queryParam.put("mbomHeaderObjid", existingMbom.get("OBJID")); List oldMbomDetails = sqlSession.selectList("productionplanning.getMbomDetailList", queryParam); - // OBJID를 키로 하는 맵 생성 (빠른 조회) + // 기존 OBJID Set 생성 + java.util.Set existingObjids = new java.util.HashSet<>(); Map existingDataMap = new HashMap<>(); if(oldMbomDetails != null) { for(Map detail : oldMbomDetails) { detail = CommonUtils.toUpperCaseMapKey(detail); String objid = CommonUtils.checkNull(detail.get("OBJID")); if(!objid.isEmpty()) { + existingObjids.add(objid); existingDataMap.put(objid, detail); } } } - sqlSession.delete("productionplanning.deleteMbomDetail", paramMap); + // JSP에서 넘어온 OBJID Set 생성 + java.util.Set newObjids = new java.util.HashSet<>(); - - // 새 상세 데이터 삽입 + // UPSERT 처리 if(mbomData != null && !mbomData.isEmpty()) { for(Map item : mbomData) { item.put("mbomHeaderObjid", existingMbom.get("OBJID")); item.put("sessionUserId", userId); - // SEQ가 없으면 기존 SEQ 유지 (JSP에서 전송된 seq 사용) - // 없는 경우에만 새로 부여 + // SEQ 기본값 if(item.get("seq") == null || "".equals(item.get("seq"))) { - item.put("seq", 999); // 임시값 (나중에 정렬 필요) + item.put("seq", 999); } - // 기존 항목인 경우 objid와 childObjid 유지 - // 새 항목인 경우에만 생성 String objid = CommonUtils.checkNull(item.get("objid")); String childObjid = CommonUtils.checkNull(item.get("childObjid")); + // 신규 항목: objid가 비어있을 때만 if("".equals(objid)) { objid = CommonUtils.createObjId(); item.put("objid", objid); } if("".equals(childObjid)) { - childObjid = objid; // 새 항목은 objid와 동일 + childObjid = objid; item.put("childObjid", childObjid); } - // 기존 데이터에서 보존해야 할 값들 복원 + newObjids.add(objid); + + // 기존 데이터에서 보존할 값 복원 Map existingData = existingDataMap.get(objid); if(existingData != null) { - // PROPOSAL_DATE가 JSP에서 안 넘어왔으면 기존 값 사용 if(item.get("proposalDate") == null || "".equals(item.get("proposalDate"))) { - Object proposalDate = existingData.get("PROPOSAL_DATE"); - if(proposalDate != null) { - item.put("proposalDate", proposalDate); - System.out.println("PROPOSAL_DATE 복원: " + objid + " -> " + proposalDate); - } + Object val = existingData.get("PROPOSAL_DATE"); + if(val != null) item.put("proposalDate", val); } - - // VENDOR가 JSP에서 안 넘어왔으면 기존 값 사용 if(item.get("vendor") == null || "".equals(item.get("vendor"))) { - Object vendor = existingData.get("VENDOR"); - if(vendor != null) { - item.put("vendor", vendor); - System.out.println("VENDOR 복원: " + objid + " -> " + vendor); - } + Object val = existingData.get("VENDOR"); + if(val != null) item.put("vendor", val); } - - // NET_QTY가 JSP에서 안 넘어왔으면 기존 값 사용 if(item.get("netQty") == null || "".equals(item.get("netQty"))) { - Object netQty = existingData.get("NET_QTY"); - if(netQty != null) { - item.put("netQty", netQty); - } + Object val = existingData.get("NET_QTY"); + if(val != null) item.put("netQty", val); } - - // PO_QTY가 JSP에서 안 넘어왔으면 기존 값 사용 if(item.get("poQty") == null || "".equals(item.get("poQty"))) { - Object poQty = existingData.get("PO_QTY"); - if(poQty != null) { - item.put("poQty", poQty); - } + Object val = existingData.get("PO_QTY"); + if(val != null) item.put("poQty", val); } } - // LEVEL이 없으면 기본값 1 설정 + // LEVEL 기본값 if(item.get("level") == null || "".equals(item.get("level"))) { item.put("level", 1); } - System.out.println("UPDATE M-BOM DETAIL: seq=" + item.get("seq") + - ", level=" + item.get("level") + - ", partNo=" + item.get("partNo") + - ", parentObjid=" + item.get("parentObjid") + - ", childObjid=" + childObjid); - - sqlSession.insert("productionplanning.insertMbomDetail", item); + // UPSERT: 기존 항목이면 UPDATE, 신규면 INSERT + if(existingObjids.contains(objid)) { + sqlSession.update("productionplanning.updateMbomDetail", item); + } else { + sqlSession.insert("productionplanning.insertMbomDetail", item); + } + } + } + + // DELETE: DB에 있는데 JSP에 없는 항목 삭제 + for(String existingObjid : existingObjids) { + if(!newObjids.contains(existingObjid)) { + Map deleteParam = new HashMap<>(); + deleteParam.put("objid", existingObjid); + sqlSession.delete("productionplanning.deleteMbomDetailByObjid", deleteParam); } }