diff --git a/src/main/java/com/xujie/sys/modules/part/mapper/BomSearch3Mapper.java b/src/main/java/com/xujie/sys/modules/part/mapper/BomSearch3Mapper.java
index 665ffb21..8c2a5857 100644
--- a/src/main/java/com/xujie/sys/modules/part/mapper/BomSearch3Mapper.java
+++ b/src/main/java/com/xujie/sys/modules/part/mapper/BomSearch3Mapper.java
@@ -93,4 +93,8 @@ public interface BomSearch3Mapper {
void saveRoutingHeaderFromTemp(@Param("bomId") Long bomId, @Param("createBy") String createBy);
void saveRoutingDetailFromTemp(@Param("bomId") Long bomId, @Param("createBy") String createBy);
+
+ void updateBomDetailStatusToReleased(@Param("bomId") Long bomId, @Param("createBy") String createBy);
+
+ void updateRoutingDetailStatusToReleased(@Param("bomId") Long bomId, @Param("createBy") String createBy);
}
diff --git a/src/main/java/com/xujie/sys/modules/part/service/impl/BomSearch3ServiceImpl.java b/src/main/java/com/xujie/sys/modules/part/service/impl/BomSearch3ServiceImpl.java
index 6775fc6a..1e968692 100644
--- a/src/main/java/com/xujie/sys/modules/part/service/impl/BomSearch3ServiceImpl.java
+++ b/src/main/java/com/xujie/sys/modules/part/service/impl/BomSearch3ServiceImpl.java
@@ -611,6 +611,10 @@ public class BomSearch3ServiceImpl implements BomSearch3Service {
// 2. 新增BOM detail(明细表)
log.info("步骤2: 保存BOM detail");
bomSearch3Mapper.saveBomDetailFromTemp(data.getBomId(), data.getCreateBy());
+
+ // 2.1 更新 Tentative 状态为 Released
+ log.info("步骤2.1: 更新BOM detail状态为Released");
+ bomSearch3Mapper.updateBomDetailStatusToReleased(data.getBomId(), data.getCreateBy());
// 3. 新增Routing header(主表)⚠️ 调整顺序:必须先保存Routing
log.info("步骤3: 保存Routing header");
@@ -619,6 +623,10 @@ public class BomSearch3ServiceImpl implements BomSearch3Service {
// 4. 新增Routing detail(明细表)
log.info("步骤4: 保存Routing detail");
bomSearch3Mapper.saveRoutingDetailFromTemp(data.getBomId(), data.getCreateBy());
+
+ // 4.1 更新 Tentative 状态为 Released
+ log.info("步骤4.1: 更新Routing detail状态为Released");
+ bomSearch3Mapper.updateRoutingDetailStatusToReleased(data.getBomId(), data.getCreateBy());
// 5. 新增Routing component(工艺路线)⚠️ 关键步骤:生成新的operation_id
log.info("步骤5: 保存Routing component");
@@ -631,7 +639,7 @@ public class BomSearch3ServiceImpl implements BomSearch3Service {
// 7. 新增BOM节点树到快速创建BOM表
log.info("步骤7: 保存BOM节点树");
// data.getPartNo() = 原BOM的物料编码, bomTreeHeader.getPartNo() = 新BOM的物料编码
- saveQuicklyBomInfoFromTemp(data.getSite(), data.getPartNo(), bomTreeHeader.getPartNo(), data.getCreateBy());
+ saveQuicklyBomInfoFromTemp(data.getBomId(), data.getSite(), data.getPartNo(), bomTreeHeader.getPartNo(), data.getCreateBy());
log.info("=== 完整BOM数据保存完成 ===");
@@ -669,65 +677,111 @@ public class BomSearch3ServiceImpl implements BomSearch3Service {
}
/**
- * 复制原BOM的节点树到新的mainPart下
+ * 从临时表保存BOM节点树到快速创建表(支持多级节点变更)
+ *
+ *
关键修复逻辑:
+ *
+ * - ❌ 错误方式:从原BOM复制数据,只修改根节点物料
+ * - ✅ 正确方式:从临时表读取所有节点(包含所有层级的变更)
+ * - ✅ 使用 bomId 精确查询当前BOM的临时数据
+ * - ✅ 按层级排序,确保父子关系正确
+ *
+ *
+ * @param bomId BOM ID(必须,用于精确查询临时表数据)
* @param site 站点
- * @param originalPartNo 原BOM物料编码(被复制的)
- * @param newPartNo 新BOM物料编码(复制目标)
+ * @param originalPartNo 原BOM物料编码
+ * @param newPartNo 新BOM物料编码(根节点)
* @param createBy 创建人
*/
- private void saveQuicklyBomInfoFromTemp(String site, String originalPartNo, String newPartNo, String createBy) {
- log.info("=== 开始复制BOM节点树到快速创建表 ===");
- log.info("site: {}, 原BOM物料: {}, 新BOM物料: {}, 创建人: {}", site, originalPartNo, newPartNo, createBy);
+ private void saveQuicklyBomInfoFromTemp(Long bomId, String site, String originalPartNo, String newPartNo, String createBy) {
+ log.info("=== 开始从临时表保存BOM节点树到快速创建表 ===");
+ log.info("bomId: {}, site: {}, 原BOM物料: {}, 新BOM根节点物料: {}, 创建人: {}", bomId, site, originalPartNo, newPartNo, createBy);
+
+ // 1. ⚠️ 关键修复:从临时表查询所有节点(包含所有层级的变更后part_no)
+ List tempNodes = copyTempBomTreeMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(CopyTempBomTree::getBomId, bomId)
+ .eq(CopyTempBomTree::getUsername, createBy)
+ .orderByAsc(CopyTempBomTree::getLevel)
+ .orderByAsc(CopyTempBomTree::getId)
+ );
+
+ if (tempNodes.isEmpty()) {
+ log.warn("临时表中没有BOM节点树数据,不执行保存。bomId: {}, createBy: {}", bomId, createBy);
+ return;
+ }
+
+ log.info("从临时表找到变更后的节点树,共{}个节点", tempNodes.size());
- // 查询原BOM的节点树结构
+ // 2. 查询原BOM的节点树模板信息(仅用于获取template_no、node_id等元数据)
List originalNodes = bomSearch3Mapper.queryOriginalBomNodeTree(site, originalPartNo);
- if (originalNodes.isEmpty()) {
- log.warn("原BOM没有节点树数据,不执行复制。originalPartNo: {}", originalPartNo);
- log.warn("这可能是因为原BOM不是通过快速创建BOM功能创建的");
- return;
+ // 3. 构建原节点的 (level -> originalNode) 映射,用于匹配模板信息
+ Map levelToOriginalNodeMap = new HashMap<>();
+ for (int i = 0; i < originalNodes.size(); i++) {
+ levelToOriginalNodeMap.put(i, originalNodes.get(i));
}
- log.info("找到原BOM节点树,共{}个节点", originalNodes.size());
-
- // 复制节点树,修改mainPart和part_no(根节点)
+ // 4. ✅ 正确逻辑:使用临时表的所有数据(变更后的part_no)+ 原模板的结构信息
List newNodes = new ArrayList<>();
- for (PlmQuicklyBomInfoEntity originalNode : originalNodes) {
+
+ for (int i = 0; i < tempNodes.size(); i++) {
+ CopyTempBomTree tempNode = tempNodes.get(i);
PlmQuicklyBomInfoEntity newNode = new PlmQuicklyBomInfoEntity();
- // 复制所有字段(保留原节点结构)
- newNode.setSite(originalNode.getSite());
- newNode.setTemplateNo(originalNode.getTemplateNo());
- newNode.setNodeId(originalNode.getNodeId());
- newNode.setNodeName(originalNode.getNodeName());
- newNode.setId(originalNode.getId());
- newNode.setpId(originalNode.getpId());
- newNode.setEngChgLevel(originalNode.getEngChgLevel());
- newNode.setRoutingRevision(originalNode.getRoutingRevision());
- newNode.setCreateBy(createBy);
-
- // 修改mainPart为新物料编码
+ // ✅ 使用临时表的数据(包含变更后的part_no)
+ newNode.setPartNo(tempNode.getPartNo());
+ newNode.setSite(tempNode.getSite());
+ newNode.setEngChgLevel(String.valueOf(tempNode.getEngChgLevel()));
+ newNode.setRoutingRevision(String.valueOf(tempNode.getEngChgLevel()));
newNode.setMainPart(newPartNo);
+ newNode.setCreateBy(createBy);
- // 如果是根节点(p_id = '0'),修改part_no为新物料编码
- if ("0".equals(originalNode.getpId())) {
- newNode.setPartNo(newPartNo);
- log.info("复制根节点: id={}, 原partNo={} → 新partNo={}",
- originalNode.getId(), originalNode.getPartNo(), newPartNo);
+ // 尝试从原节点获取模板信息
+ PlmQuicklyBomInfoEntity originalNode = levelToOriginalNodeMap.get(i);
+ if (originalNode != null) {
+ // 使用原模板的树形结构信息
+ newNode.setTemplateNo(originalNode.getTemplateNo());
+ newNode.setNodeId(originalNode.getNodeId());
+ newNode.setNodeName(originalNode.getNodeName());
+ newNode.setId(originalNode.getId());
+ newNode.setpId(originalNode.getpId());
+
+ log.info("节点[{}]: 层级={}, part_no={} (原={}, 已变更={}), 模板id={}",
+ i, tempNode.getLevel(), tempNode.getPartNo(),
+ originalNode.getPartNo(), tempNode.getChangeFlag(), originalNode.getId());
} else {
- // 子节点保持原来的part_no
- newNode.setPartNo(originalNode.getPartNo());
- log.debug("复制子节点: id={}, partNo={}", originalNode.getId(), originalNode.getPartNo());
+ // 没有原模板,使用默认值
+ newNode.setNodeId(String.valueOf(tempNode.getLevel()));
+ newNode.setNodeName("Node-Level" + tempNode.getLevel());
+ newNode.setId(String.valueOf(tempNode.getLevel()));
+ newNode.setpId(tempNode.getParentId() == 0 ? "0" : String.valueOf(tempNode.getLevel() - 1));
+
+ log.info("节点[{}]: 层级={}, part_no={} (新增节点, 无原模板), 变更标识={}",
+ i, tempNode.getLevel(), tempNode.getPartNo(), tempNode.getChangeFlag());
}
newNodes.add(newNode);
}
- // 批量插入
+ // 5. 批量插入
if (!newNodes.isEmpty()) {
bomSearch3Mapper.batchInsertQuicklyBomInfo(newNodes);
- log.info("=== BOM节点树复制完成!共复制{}个节点 ===", newNodes.size());
- log.info("新BOM物料编码: {}, 可在quicklyCreateBom页面查看", newPartNo);
+ log.info("=== BOM节点树保存完成!共保存{}个节点 ===", newNodes.size());
+ log.info("新BOM根节点物料编码: {}, 可在quicklyCreateBom页面查看", newPartNo);
+
+ // 打印变更摘要
+ int changeCount = 0;
+ for (int i = 0; i < newNodes.size(); i++) {
+ PlmQuicklyBomInfoEntity newNode = newNodes.get(i);
+ PlmQuicklyBomInfoEntity originalNode = levelToOriginalNodeMap.get(i);
+ if (originalNode != null && !newNode.getPartNo().equals(originalNode.getPartNo())) {
+ log.info(" ✓ 节点[{}] 物料已变更: {} → {}",
+ i, originalNode.getPartNo(), newNode.getPartNo());
+ changeCount++;
+ }
+ }
+ log.info("✅ 变更统计: 共{}个节点发生物料变更", changeCount);
}
}
diff --git a/src/main/resources/mapper/part/BomSearch3Mapper.xml b/src/main/resources/mapper/part/BomSearch3Mapper.xml
index cec589d5..f60ef5fb 100644
--- a/src/main/resources/mapper/part/BomSearch3Mapper.xml
+++ b/src/main/resources/mapper/part/BomSearch3Mapper.xml
@@ -722,12 +722,12 @@
insert into plm_bom_detail
(site, bu_no, part_no, eng_chg_level, bom_type, alternative_no, alternative_description, status, min_lot_qty, default_flag, note_text, create_date, create_by)
select distinct
- site,
- bu_no,
- part_no,
- eng_chg_level,
- bom_type,
- alternative_no,
+ t.site,
+ t.bu_no,
+ t.part_no,
+ t.eng_chg_level,
+ t.bom_type,
+ t.alternative_no,
'*' as alternative_description,
'Released' as status,
1 as min_lot_qty,
@@ -735,9 +735,37 @@
null as note_text,
getDate() as create_date,
#{createBy} as create_by
- from plm_copy_temp_bom_tree
- where bom_id = #{bomId} and username = #{createBy} and change_flag = 'Y' and level = 0
+ from plm_copy_temp_bom_tree t
+ where t.bom_id = #{bomId} and t.username = #{createBy} and t.change_flag = 'Y'
+ and not exists (
+ select 1 from plm_bom_detail d
+ where d.site = t.site
+ and d.bu_no = t.bu_no
+ and d.part_no = t.part_no
+ and d.eng_chg_level = t.eng_chg_level
+ and d.bom_type = t.bom_type
+ and d.alternative_no = t.alternative_no
+ )
+
+
+
+ update plm_bom_detail
+ set status = 'Released'
+ where status = 'Tentative'
+ and exists (
+ select 1 from plm_copy_temp_bom_tree t
+ where t.bom_id = #{bomId}
+ and t.username = #{createBy}
+ and t.change_flag = 'Y'
+ and t.site = plm_bom_detail.site
+ and t.bu_no = plm_bom_detail.bu_no
+ and t.part_no = plm_bom_detail.part_no
+ and t.eng_chg_level = plm_bom_detail.eng_chg_level
+ and t.bom_type = plm_bom_detail.bom_type
+ and t.alternative_no = plm_bom_detail.alternative_no
+ )
+