From ba66f2b219197d93e8a10ce5f83622af0b64623d Mon Sep 17 00:00:00 2001 From: hjjeong Date: Mon, 23 Feb 2026 11:49:35 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8F=99=EC=9D=BC=20=ED=92=88=EB=B2=88=20?= =?UTF-8?q?=EC=88=98=EC=A3=BC=20=EC=8B=9C=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=EC=A0=90=EC=97=90=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EC=97=A0=EB=B4=84=20=EB=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/productionplanning/mBomMgmtList.jsp | 2 +- .../ProductionPlanningController.java | 6 +- src/com/pms/mapper/productionplanning.xml | 40 ++-- .../service/ContractMgmtService.java | 50 +++++ .../service/ProductionPlanningService.java | 204 ++++++++++++++++-- 5 files changed, 252 insertions(+), 50 deletions(-) diff --git a/WebContent/WEB-INF/view/productionplanning/mBomMgmtList.jsp b/WebContent/WEB-INF/view/productionplanning/mBomMgmtList.jsp index 32634f0..3d5180c 100644 --- a/WebContent/WEB-INF/view/productionplanning/mBomMgmtList.jsp +++ b/WebContent/WEB-INF/view/productionplanning/mBomMgmtList.jsp @@ -467,7 +467,7 @@ function fn_checkAssignmentAndOpenMbom(projectObjId) { dataType: "json", async: false, success: function(templateResponse) { - if(templateResponse && templateResponse.MBOM_PART_NO) { + if(templateResponse && templateResponse.TEMPLATE_HEADER_OBJID) { console.log("동일 품번의 M-BOM 템플릿 발견 - 팝업 열기"); fn_openMBomFormPopup(projectObjId); } else { diff --git a/src/com/pms/controller/ProductionPlanningController.java b/src/com/pms/controller/ProductionPlanningController.java index 906a8cd..ef9073f 100644 --- a/src/com/pms/controller/ProductionPlanningController.java +++ b/src/com/pms/controller/ProductionPlanningController.java @@ -1163,14 +1163,14 @@ public class ProductionPlanningController extends BaseService { // 할당된 BOM도 없으면 Machine 이외 제품은 템플릿 확인 String productCode = CommonUtils.checkNull(projectInfo.get("PRODUCT_CODE")); String partNo = CommonUtils.checkNull(projectInfo.get("PART_NO")); - + if(!"0000928".equals(productCode) && !"".equals(partNo)) { System.out.println("Machine 이외 제품 - 템플릿 조회 시도"); Map templateParam = new HashMap<>(); templateParam.put("partNo", partNo); - + Map template = commonService.selectOne("productionplanning.getLatestMbomTemplateByPartNo", request, templateParam); - + if(template != null && !template.isEmpty()) { bomReportObjid = CommonUtils.checkNull(template.get("TEMPLATE_HEADER_OBJID")); bomDataType = "TEMPLATE"; diff --git a/src/com/pms/mapper/productionplanning.xml b/src/com/pms/mapper/productionplanning.xml index bca42a8..f0edca5 100644 --- a/src/com/pms/mapper/productionplanning.xml +++ b/src/com/pms/mapper/productionplanning.xml @@ -3406,31 +3406,25 @@ BPQ.SEQ - + diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java index a096230..47d2ddb 100644 --- a/src/com/pms/salesmgmt/service/ContractMgmtService.java +++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java @@ -62,6 +62,9 @@ public class ContractMgmtService { @Autowired CommonService commonService; + @Autowired + com.pms.service.ProductionPlanningService productionPlanningService; + /** *
 	 * 계약관리 목록 조회
@@ -2784,6 +2787,15 @@ private String encodeImageToBase64(String imagePath) {
 								// 프로젝트 SETUP_TASK 등록
 								cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", projectParam);
 								
+								// 동일 품번 M-BOM 자동 복사 (Machine 제외)
+								if(!isMachine) {
+									copyMbomIfSamePartNoExists(sqlSession, 
+											(String)projectParam.get("OBJID"), 
+											CommonUtils.checkNull(item.get("PART_NO")), 
+											CommonUtils.checkNull(item.get("PART_NAME")), 
+											person.getUserId());
+								}
+								
 								// project_no - unit 폴더 생성
 								Map projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam);
 								
@@ -2999,6 +3011,15 @@ private String encodeImageToBase64(String imagePath) {
 							// 프로젝트 SETUP_TASK 등록
 							cnt = sqlSession.insert("contractMgmt.insertProjectSetupTask", projectParam);
 							
+							// 동일 품번 M-BOM 자동 복사 (Machine 제외)
+							if(!isMachine) {
+								copyMbomIfSamePartNoExists(sqlSession, 
+										(String)projectParam.get("OBJID"), 
+										CommonUtils.checkNull(item.get("PART_NO")), 
+										CommonUtils.checkNull(item.get("PART_NAME")), 
+										person.getUserId());
+							}
+							
 							// project_no - unit 폴더 생성
 							Map projectInfo = (Map)sqlSession.selectOne("project.getProjectMngInfo", projectParam);
 							
@@ -3787,4 +3808,33 @@ private String encodeImageToBase64(String imagePath) {
 			if(sqlSession != null) sqlSession.close();
 		}
 	}
+	
+	/**
+	 * 동일 품번의 최신 M-BOM이 있으면 자동 복사
+	 * 프로젝트 자동생성 시 WBS TASK 등록 직후 호출
+	 */
+	private void copyMbomIfSamePartNoExists(SqlSession sqlSession, String projectObjId, 
+			String partNo, String partName, String userId) {
+		try {
+			if(partNo == null || partNo.isEmpty()) return;
+			
+			// 동일 품번의 최신 M-BOM 템플릿 조회
+			Map templateParam = new HashMap<>();
+			templateParam.put("partNo", partNo);
+			Map template = sqlSession.selectOne("productionplanning.getLatestMbomTemplateByPartNo", templateParam);
+			
+			if(template != null && !template.isEmpty()) {
+				template = CommonUtils.toUpperCaseMapKey(template);
+				String templateHeaderObjid = CommonUtils.checkNull(template.get("TEMPLATE_HEADER_OBJID"));
+				
+				System.out.println("동일 품번(" + partNo + ") M-BOM 발견 → 프로젝트(" + projectObjId + ")에 자동 복사");
+				
+				productionPlanningService.copyMbomFromTemplate(
+						sqlSession, userId, projectObjId, templateHeaderObjid, partNo, partName);
+			}
+		} catch(Exception e) {
+			System.out.println("M-BOM 자동 복사 중 오류 (프로젝트 생성에는 영향 없음): " + e.getMessage());
+			e.printStackTrace();
+		}
+	}
 }
diff --git a/src/com/pms/service/ProductionPlanningService.java b/src/com/pms/service/ProductionPlanningService.java
index ba23b79..a3ea78e 100644
--- a/src/com/pms/service/ProductionPlanningService.java
+++ b/src/com/pms/service/ProductionPlanningService.java
@@ -1105,48 +1105,53 @@ public class ProductionPlanningService {
 		
 		try {
 			sqlSession = SqlMapConfig.getInstance().getSqlSession();
-			
-			// 현재 날짜 (YYMMDD)
+			mbomNo = generateMbomNo(sqlSession, sourceBomType, baseBomPartNo);
+		} catch(Exception e) {
+			e.printStackTrace();
+		} finally {
+			if(sqlSession != null) {
+				sqlSession.close();
+			}
+		}
+		
+		return mbomNo;
+	}
+	
+	/**
+	 * M-BOM 품번 생성 (기존 트랜잭션 공유)
+	 */
+	public String generateMbomNo(SqlSession sqlSession, String sourceBomType, String baseBomPartNo) {
+		String mbomNo = "";
+		
+		try {
 			java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyMMdd");
 			String dateStr = sdf.format(new java.util.Date());
 			
 			String mbomPrefix = "";
 			
 			if("EBOM".equals(sourceBomType)) {
-				// E-BOM 기준: M-{E-BOM품번}-YYMMDD
-				// E-BOM 품번이 이미 M-으로 시작하면 중복 방지
 				String cleanPartNo = baseBomPartNo;
 				if(cleanPartNo.startsWith("M-")) {
-					cleanPartNo = cleanPartNo.substring(2); // "M-" 제거
+					cleanPartNo = cleanPartNo.substring(2);
 				}
 				mbomPrefix = "M-" + cleanPartNo + "-" + dateStr;
 			} else if("TEMPLATE".equals(sourceBomType)) {
-				// 템플릿 기준: M-{품번}-YYMMDD
 				String cleanPartNo = baseBomPartNo.trim();
 				if(cleanPartNo.startsWith("M-")) {
-					cleanPartNo = cleanPartNo.substring(2); // "M-" 제거
+					cleanPartNo = cleanPartNo.substring(2);
 				}
 				mbomPrefix = "M-" + cleanPartNo + "-" + dateStr;
 			} else if("MBOM".equals(sourceBomType)) {
-				// M-BOM 기준: 기존 M-BOM 품번에서 날짜/순번 부분 제거 후 새 날짜 추가
-				// 예: M-000AN014000-251124-01 → M-000AN014000 → M-000AN014000-251126-01
-				
 				String basePart = baseBomPartNo;
 				
-				// 잘못된 형식 정규화 (M-M-..., M-E-... 등)
-				// 예: M-M-000AN014000-251124-01-251124 → M-000AN014000-251124-01-251124
 				while(basePart.startsWith("M-M-") || basePart.startsWith("M-E-")) {
 					basePart = "M-" + basePart.substring(4);
 				}
 				
-				// 마지막의 모든 날짜-순번 패턴 제거 (중복 복사 대응)
-				// 예: M-000AN014000-251124-01-251125-01 → M-000AN014000
-				// 날짜(6자리) 또는 순번(2자리)가 나오지 않을 때까지 반복
 				while(true) {
 					int lastDashIndex = basePart.lastIndexOf("-");
 					if(lastDashIndex > 0) {
 						String suffix = basePart.substring(lastDashIndex + 1);
-						// 날짜(6자리) 또는 순번(2자리)인 경우만 제거
 						if(suffix.matches("\\d{2}") || suffix.matches("\\d{6}")) {
 							basePart = basePart.substring(0, lastDashIndex);
 						} else {
@@ -1157,28 +1162,21 @@ public class ProductionPlanningService {
 					}
 				}
 				
-				// 새 날짜 추가
 				mbomPrefix = basePart + "-" + dateStr;
 			}
 			
-			// 같은 날짜의 최대 순번 조회
 			Map seqParam = new HashMap<>();
 			seqParam.put("mbomPrefix", mbomPrefix);
 			
 			Integer maxSeq = (Integer) sqlSession.selectOne("productionplanning.getMaxMbomSeqByDate", seqParam);
 			int nextSeq = (maxSeq != null ? maxSeq : 0) + 1;
 			
-			// 최종 M-BOM 품번 생성
 			mbomNo = mbomPrefix + "-" + String.format("%02d", nextSeq);
 			
 			System.out.println("생성된 M-BOM 품번: " + mbomNo);
 			
 		} catch(Exception e) {
 			e.printStackTrace();
-		} finally {
-			if(sqlSession != null) {
-				sqlSession.close();
-			}
 		}
 		
 		return mbomNo;
@@ -1591,6 +1589,166 @@ public class ProductionPlanningService {
 		return result;
 	}
 	
+	/**
+	 * 동일 품번의 최신 M-BOM을 새 프로젝트에 자동 복사 (독립 트랜잭션)
+	 */
+	public String copyMbomFromTemplate(HttpServletRequest request, String projectObjId, 
+			String templateHeaderObjId, String partNo, String partName) {
+		SqlSession sqlSession = null;
+		String newHeaderObjid = null;
+		
+		try {
+			sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
+			HttpSession session = request.getSession();
+			PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
+			String userId = CommonUtils.checkNull(person.getUserId());
+			
+			newHeaderObjid = copyMbomFromTemplate(sqlSession, userId, projectObjId, templateHeaderObjId, partNo, partName);
+			
+			if(newHeaderObjid != null) {
+				sqlSession.commit();
+			}
+		} catch(Exception e) {
+			if(sqlSession != null) sqlSession.rollback();
+			newHeaderObjid = null;
+			e.printStackTrace();
+		} finally {
+			if(sqlSession != null) sqlSession.close();
+		}
+		
+		return newHeaderObjid;
+	}
+	
+	/**
+	 * 동일 품번의 최신 M-BOM을 새 프로젝트에 자동 복사 (기존 트랜잭션 공유)
+	 * 수주 확정 → 프로젝트 자동생성 시 ContractMgmtService에서 호출
+	 */
+	public String copyMbomFromTemplate(SqlSession sqlSession, String userId, String projectObjId, 
+			String templateHeaderObjId, String partNo, String partName) {
+		String newHeaderObjid = null;
+		
+		try {
+			// 1. 새 MBOM_HEADER 생성 (M-BOM 품번 규칙 적용)
+			newHeaderObjid = CommonUtils.createObjId();
+			String mbomNo = generateMbomNo(sqlSession, "TEMPLATE", partNo);
+			Map headerParam = new HashMap<>();
+			headerParam.put("objid", newHeaderObjid);
+			headerParam.put("mbomNo", mbomNo);
+			headerParam.put("sourceBomType", "TEMPLATE");
+			headerParam.put("sourceEbomObjid", null);
+			headerParam.put("sourceMbomObjid", templateHeaderObjId);
+			headerParam.put("projectObjId", projectObjId);
+			headerParam.put("partNo", partNo);
+			headerParam.put("partName", partName);
+			headerParam.put("sessionUserId", userId);
+			sqlSession.insert("productionplanning.insertMbomHeader", headerParam);
+			
+			// 2. 템플릿의 MBOM_DETAIL 조회
+			Map detailParam = new HashMap<>();
+			detailParam.put("mbomHeaderObjid", templateHeaderObjId);
+			List> templateDetails = sqlSession.selectList("productionplanning.getMbomDetailList", detailParam);
+			
+			if(templateDetails != null && !templateDetails.isEmpty()) {
+				// 3. CHILD_OBJID 매핑 생성 (old → new)
+				Map childObjidMap = new HashMap<>();
+				for(Map detail : templateDetails) {
+					detail = CommonUtils.toUpperCaseMapKey(detail);
+					String oldObjid = CommonUtils.checkNull(detail.get("OBJID"));
+					String oldChildObjid = CommonUtils.checkNull(detail.get("CHILD_OBJID"));
+					if(!"".equals(oldObjid)) {
+						childObjidMap.put(oldObjid, CommonUtils.createObjId());
+					}
+					if(!"".equals(oldChildObjid) && !childObjidMap.containsKey(oldChildObjid)) {
+						childObjidMap.put(oldChildObjid, CommonUtils.createObjId());
+					}
+				}
+				
+				// 4. 매핑 적용하여 MBOM_DETAIL 복사 (수주/제작 수량은 초기화)
+				for(Map detail : templateDetails) {
+					detail = CommonUtils.toUpperCaseMapKey(detail);
+					String oldObjid = CommonUtils.checkNull(detail.get("OBJID"));
+					String oldChildObjid = CommonUtils.checkNull(detail.get("CHILD_OBJID"));
+					String oldParentObjid = CommonUtils.checkNull(detail.get("PARENT_OBJID"));
+					
+					String newObjid = childObjidMap.containsKey(oldObjid) ? childObjidMap.get(oldObjid) : CommonUtils.createObjId();
+					String newChildObjid = childObjidMap.containsKey(oldChildObjid) ? childObjidMap.get(oldChildObjid) : newObjid;
+					String newParentObjid = !"".equals(oldParentObjid) 
+							? (childObjidMap.containsKey(oldParentObjid) ? childObjidMap.get(oldParentObjid) : oldParentObjid) : "";
+					
+					Map insertParam = new HashMap<>();
+					insertParam.put("objid", newObjid);
+					insertParam.put("mbomHeaderObjid", newHeaderObjid);
+					insertParam.put("parentObjid", newParentObjid);
+					insertParam.put("childObjid", newChildObjid);
+					insertParam.put("seq", detail.get("SEQ"));
+					insertParam.put("level", detail.get("LEVEL"));
+					insertParam.put("partObjid", detail.get("PART_OBJID"));
+					insertParam.put("partNo", detail.get("PART_NO"));
+					insertParam.put("partName", detail.get("PART_NAME"));
+					insertParam.put("qty", detail.get("QTY"));
+					insertParam.put("unit", detail.get("UNIT"));
+					insertParam.put("supplyType", detail.get("SUPPLY_TYPE"));
+					insertParam.put("makeOrBuy", detail.get("MAKE_OR_BUY"));
+					insertParam.put("rawMaterialPartNo", detail.get("RAW_MATERIAL_PART_NO"));
+					insertParam.put("rawMaterialSpec", detail.get("RAW_MATERIAL_SPEC"));
+					insertParam.put("rawMaterial", detail.get("RAW_MATERIAL"));
+					insertParam.put("rawMaterialSize", detail.get("RAW_MATERIAL_SIZE"));
+					insertParam.put("processingVendor", detail.get("PROCESSING_VENDOR"));
+					insertParam.put("processingDeadline", detail.get("PROCESSING_DEADLINE"));
+					insertParam.put("grindingDeadline", detail.get("GRINDING_DEADLINE"));
+					insertParam.put("requiredQty", detail.get("REQUIRED_QTY"));
+					insertParam.put("orderQty", null);
+					insertParam.put("productionQty", null);
+					insertParam.put("stockQty", null);
+					insertParam.put("shortageQty", null);
+					insertParam.put("netQty", null);
+					insertParam.put("poQty", null);
+					insertParam.put("vendor", detail.get("VENDOR"));
+					insertParam.put("unitPrice", detail.get("UNIT_PRICE"));
+					insertParam.put("processingUnitPrice", detail.get("PROCESSING_UNIT_PRICE"));
+					insertParam.put("totalPrice", null);
+					insertParam.put("currency", detail.get("CURRENCY"));
+					insertParam.put("leadTime", detail.get("LEAD_TIME"));
+					insertParam.put("minOrderQty", detail.get("MIN_ORDER_QTY"));
+					insertParam.put("proposalDate", detail.get("PROPOSAL_DATE"));
+					insertParam.put("remark", detail.get("REMARK"));
+					insertParam.put("sessionUserId", userId);
+					
+					sqlSession.insert("productionplanning.insertMbomDetail", insertParam);
+				}
+			}
+			
+			// 5. PROJECT_MGMT MBOM_STATUS 업데이트
+			Map statusParam = new HashMap<>();
+			statusParam.put("projectObjId", projectObjId);
+			statusParam.put("sessionUserId", userId);
+			sqlSession.update("productionplanning.updateProjectMbomStatus", statusParam);
+			
+			// 6. 이력 저장
+			String historyObjid = CommonUtils.createObjId();
+			Map historyParam = new HashMap<>();
+			historyParam.put("objid", historyObjid);
+			historyParam.put("mbomHeaderObjid", newHeaderObjid);
+			historyParam.put("changeType", "COPY_FROM_TEMPLATE");
+			historyParam.put("changeDescription", "동일 품번(" + partNo + ") M-BOM 자동 복사");
+			historyParam.put("beforeData", "{}");
+			historyParam.put("afterData", "{\"sourceHeaderObjid\":\"" + templateHeaderObjId + "\"}");
+			historyParam.put("sessionUserId", userId);
+			sqlSession.insert("productionplanning.insertMbomHistory", historyParam);
+			
+			System.out.println("========== M-BOM 자동 복사 완료 ==========");
+			System.out.println("프로젝트: " + projectObjId);
+			System.out.println("원본 템플릿: " + templateHeaderObjId);
+			System.out.println("새 M-BOM: " + newHeaderObjid);
+			
+		} catch(Exception e) {
+			newHeaderObjid = null;
+			e.printStackTrace();
+		}
+		
+		return newHeaderObjid;
+	}
+	
 	/**
 	 * MBOM_HEADER 테이블에 해당 OBJID가 존재하는지 확인
 	 * @param objId