Browse Source

2026-05-19

【切换版本】优化
master
fengyuan_yang 3 months ago
parent
commit
7281e0552f
  1. 10
      src/main/java/com/spring/modules/quote/mapper/QuoteDetailBomTreeMapper.java
  2. 90
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java
  3. 43
      src/main/resources/mapper/quote/QuoteDetailBomTreeMapper.xml
  4. 5
      src/main/resources/mapper/quote/QuoteDetailRoutingMapper.xml

10
src/main/java/com/spring/modules/quote/mapper/QuoteDetailBomTreeMapper.java

@ -26,8 +26,18 @@ public interface QuoteDetailBomTreeMapper extends BaseMapper<QuoteDetailBomTree>
QuoteDetailRouting queryPartBomRouting(QuoteDetail detail);
/**
* 查询 Routing 组件行用于事务外预加载事务内直接写入报价 Routing
*/
List<QuoteDetailRouting> queryRoutingRowsForSave(QuoteDetailRouting routing);
BigDecimal getPartCost(@Param("site") String site,@Param("partNo") String partNo);
/**
* 查询加工中心成本 dbo.get_workCenter_cost 做单次缓存调用
*/
BigDecimal queryWorkCenterCost(@Param("site") String site, @Param("workCenterNo") String workCenterNo);
PartInformationEntity queryPart(@Param("site") String site,@Param("partNo") String partNo);
PartInformationEntity queryPLMPart(@Param("site") String site,@Param("partNo") String partNo);

90
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java

@ -79,7 +79,10 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
*/
private static class BomNodeData {
QuoteDetailBomTree tree;
QuoteDetailRouting routing;
/** Routing 头信息(用于工具处理、createBy/createDate 透传) */
QuoteDetailRouting routingHeader;
/** 预加载的 Routing 行,事务内直接写入报价 Routing 表 */
List<QuoteDetailRouting> routingRows = new ArrayList<>();
/** 当前节点下的子物料列表(已填充好成本价格) */
List<QuoteDetailBom> components = new ArrayList<>();
/** 是否顶级节点(决定是否触发 handleTool) */
@ -105,7 +108,8 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
// 阶段一事务外获取 IFS 连接工厂并递归收集数据含并行 IFS 成本查询
Supplier<Server> ifsConFactory = resolveIfsConFactory();
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
BomNodeData rootNode = collectBomData(detail, parentId, level, ifsConFactory, costCache);
Map<String, BigDecimal> workCenterCostCache = new ConcurrentHashMap<>();
BomNodeData rootNode = collectBomData(detail, parentId, level, ifsConFactory, costCache, workCenterCostCache);
if (rootNode == null) {
return 0;
}
@ -129,7 +133,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
*
* @param ifsConFactory IFS 连接工厂每个并发线程调用 get() 获取独立连接避免共享连接线程安全问题
*/
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Supplier<Server> ifsConFactory, Map<String, BigDecimal> costCache) {
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Supplier<Server> ifsConFactory, Map<String, BigDecimal> costCache, Map<String, BigDecimal> workCenterCostCache) {
// 统一先尝试将临时料映射到正式料保证新建与切换版本刷新取数来源一致
PartInformationEntity mappedPart = baseMapper.queryPart(detail.getSite(), detail.getPartNo());
if (Objects.nonNull(mappedPart) && "Y".equals(mappedPart.getStatus())) {
@ -178,8 +182,17 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
BomNodeData nodeData = new BomNodeData();
nodeData.tree = bom;
nodeData.routing = routing;
nodeData.routingHeader = routing;
nodeData.isRoot = parentId.equals(0L);
if (Objects.nonNull(routing)) {
List<QuoteDetailRouting> routingRows = baseMapper.queryRoutingRowsForSave(routing);
for (QuoteDetailRouting routingRow : routingRows) {
BigDecimal workCenterCost = resolveWorkCenterCost(routingRow.getSite(), routingRow.getWorkCenterNo(), workCenterCostCache);
routingRow.setWorkCenterCost(workCenterCost);
routingRow.setManuCost(workCenterCost);
}
nodeData.routingRows = routingRows;
}
// 串行阶段构建 BOM 层级结构递归有顺序依赖不可并行
for (QuoteDetailBom component : componentParts) {
@ -192,7 +205,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) {
log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}", component.getComponentPart());
// 递归收集子节点parentId 占位 -1写入时由 doSaveBomDataRecursive 覆盖
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsConFactory, costCache);
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsConFactory, costCache, workCenterCostCache);
if (childNode != null) {
nodeData.children.add(childNode);
// 半成品bomId 在写入阶段填充先清零价格IFS 并行阶段会覆盖
@ -302,14 +315,13 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
long treeId = bom.getId();
// 写入 Routing
if (Objects.nonNull(nodeData.routing)) {
nodeData.routing.setTreeId(treeId);
quoteDetailRoutingService.saveQuoteDetailRouting(nodeData.routing);
if (Objects.nonNull(nodeData.routingHeader)) {
savePreloadedRoutingRows(nodeData, detail, treeId);
}
// 顶级节点处理工具信息
if (nodeData.isRoot) {
handleTool(detail, nodeData.routing);
handleTool(detail, nodeData.routingHeader);
}
// 递归写入子节点收集 childNode 对应的真实 treeId用于回填 bomId
@ -368,7 +380,8 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
newParentId = bomTree.getParentId();
Integer newLevel = Optional.ofNullable(tree.getLevel()).orElse(0);
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, newParentId, newLevel, ifsConFactory, costCache);
Map<String, BigDecimal> workCenterCostCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, newParentId, newLevel, ifsConFactory, costCache, workCenterCostCache);
log.info("[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms", (System.currentTimeMillis() - collectStartTime));
} else {
detail = new QuoteDetail();
@ -387,7 +400,8 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
long collectStartTime = System.currentTimeMillis();
Supplier<Server> ifsConFactory = resolveIfsConFactory();
Map<String, BigDecimal> costCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, 0L, 0, ifsConFactory, costCache);
Map<String, BigDecimal> workCenterCostCache = new ConcurrentHashMap<>();
nodeData = collectBomData(detail, 0L, 0, ifsConFactory, costCache, workCenterCostCache);
log.info("[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms", (System.currentTimeMillis() - collectStartTime));
}
@ -684,6 +698,60 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
}
}
/**
* 事务内写入预加载好的 Routing 行数据避免每个节点再次执行 insert-select 重查询
*/
private void savePreloadedRoutingRows(BomNodeData nodeData, QuoteDetail detail, long treeId) {
if (nodeData.routingRows == null || nodeData.routingRows.isEmpty()) {
return;
}
Date createDate = Objects.nonNull(nodeData.routingHeader.getCreateDate()) ? nodeData.routingHeader.getCreateDate() : new Date();
String createBy = StringUtils.hasText(nodeData.routingHeader.getCreateBy()) ? nodeData.routingHeader.getCreateBy() : detail.getCreateBy();
for (QuoteDetailRouting routingRow : nodeData.routingRows) {
routingRow.setId(null);
routingRow.setQuoteId(detail.getQuoteId());
routingRow.setQuoteDetailId(detail.getId());
routingRow.setQuoteDetailItemNo(detail.getItemNo());
routingRow.setSite(detail.getSite());
routingRow.setBuNo(detail.getBuNo());
routingRow.setQuoteNo(detail.getQuoteNo());
routingRow.setVersionNo(detail.getVersionNo());
routingRow.setCreateBy(createBy);
routingRow.setCreateDate(createDate);
routingRow.setTreeId(treeId);
routingRow.setMachCycleTime(BigDecimal.ZERO);
routingRow.setLaborCycleTime(BigDecimal.ZERO);
routingRow.setResourceNum(0);
if (Objects.isNull(routingRow.getWorkCenterCost())) {
routingRow.setWorkCenterCost(BigDecimal.ZERO);
}
if (Objects.isNull(routingRow.getManuCost())) {
routingRow.setManuCost(routingRow.getWorkCenterCost());
}
}
// SQL Server + MyBatis-Plus saveBatch 回填自增主键时可能抛出
// "The statement must be executed before any results can be obtained."
// 这里改为逐条插入规避驱动层 generated keys 兼容性问题
for (QuoteDetailRouting routingRow : nodeData.routingRows) {
quoteDetailRoutingService.save(routingRow);
}
}
/**
* 解析加工中心成本并做本次请求级缓存避免同一加工中心重复调用数据库函数
*/
private BigDecimal resolveWorkCenterCost(String site, String workCenterNo, Map<String, BigDecimal> workCenterCostCache) {
if (!StringUtils.hasText(site) || !StringUtils.hasText(workCenterNo)) {
return BigDecimal.ZERO;
}
String cacheKey = site + "_" + workCenterNo;
return workCenterCostCache.computeIfAbsent(cacheKey, key -> {
BigDecimal cost = baseMapper.queryWorkCenterCost(site, workCenterNo);
return Objects.nonNull(cost) ? cost : BigDecimal.ZERO;
});
}
private QuoteDetailBom getPurchaseComponentPart(QuoteDetail detail, QuoteDetailBomTree bom, Integer lineSequence) {
QuoteDetailBom purchase = new QuoteDetailBom();
purchase.setQuoteDetailId(detail.getId());

43
src/main/resources/mapper/quote/QuoteDetailBomTreeMapper.xml

@ -177,10 +177,53 @@
order by rh.phase_in_date desc, rh.create_date desc
</select>
<select id="queryRoutingRowsForSave" resultType="com.spring.modules.quote.entity.QuoteDetailRouting">
select rc.site,
rc.part_no,
rc.routing_revision,
rc.routing_type,
rc.alternative_no,
rc.operation_no,
rc.efficiency_factor,
rc.mach_run_factor,
rc.mach_setup_time,
rc.run_time_code,
rc.labor_run_factor,
rc.labor_setup_time,
rc.crew_size,
rc.setup_crew_size,
rc.outside_op_item,
rc.machine_no,
rc.work_center_no,
rc.labor_class_no,
rc.setup_labor_class_no,
rc.overlap,
rc.note_text,
rc.operation_id,
rc.operation_name,
cast(0 as decimal(20, 6)) as mach_cycle_time,
cast(0 as decimal(20, 6)) as labor_cycle_time,
0 as resource_num,
isnull(lc.current_level_cost, isnull(lc.level_cost, 0)) as labor_class_cost,
isnull(slc.current_level_cost, isnull(slc.level_cost, 0)) as setup_labor_class_cost
from plm_routing_component rc
left join labor_class lc on rc.labor_class_no = lc.level_id and rc.site = lc.site
left join labor_class slc on rc.setup_labor_class_no = slc.level_id and rc.site = slc.site
where rc.part_no = #{partNo}
and rc.routing_revision = #{routingRevision}
and rc.routing_type = #{routingType}
and rc.alternative_no = #{alternativeNo}
and rc.site = #{site}
</select>
<select id="getPartCost" resultType="java.math.BigDecimal">
select isnull(dbo.get_part_unitCost(#{site}, #{partNo}),0)
</select>
<select id="queryWorkCenterCost" resultType="java.math.BigDecimal">
select isnull(dbo.get_workCenter_cost(#{site}, #{workCenterNo}),0)
</select>
<select id="queryPart" resultType="com.spring.modules.part.entity.PartInformationEntity">
select top 1 site,
part_no,

5
src/main/resources/mapper/quote/QuoteDetailRoutingMapper.xml

@ -49,11 +49,12 @@
0,
isnull(lc.current_level_cost, isnull(lc.level_cost,0)) as labor_class_cost, -- 人工
isnull(slc.current_level_cost, isnull(slc.level_cost,0)) as setup_labor_class_cost, -- 调机人工
dbo.get_workCenter_cost(rc.site, rc.work_center_no), -- 加工中心成本
dbo.get_workCenter_cost(rc.site, rc.work_center_no) -- 制造费用
wc_cost.work_center_cost, -- 加工中心成本
wc_cost.work_center_cost -- 制造费用
from plm_routing_component rc
left join labor_class lc on rc.labor_class_no = lc.level_id and rc.site = lc.site
left join labor_class slc on rc.setup_labor_class_no = slc.level_id and rc.site = slc.site
cross apply (select dbo.get_workCenter_cost(rc.site, rc.work_center_no) as work_center_cost) wc_cost
where rc.part_no = #{partNo}
and rc.routing_revision = #{routingRevision}
and rc.routing_type = #{routingType}

Loading…
Cancel
Save