@ -104,7 +104,8 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
public long initQuoteDetailBomTree ( QuoteDetail detail , Long parentId , Integer level ) {
public long initQuoteDetailBomTree ( QuoteDetail detail , Long parentId , Integer level ) {
/ / 阶段一 : 事务外 , 获取 IFS 连接工厂并递归收集数据 ( 含并行 IFS 成本查询 )
/ / 阶段一 : 事务外 , 获取 IFS 连接工厂并递归收集数据 ( 含并行 IFS 成本查询 )
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
BomNodeData rootNode = collectBomData ( detail , parentId , level , ifsConFactory ) ;
Map < String , BigDecimal > costCache = new ConcurrentHashMap < > ( ) ;
BomNodeData rootNode = collectBomData ( detail , parentId , level , ifsConFactory , costCache ) ;
if ( rootNode = = null ) {
if ( rootNode = = null ) {
return 0 ;
return 0 ;
}
}
@ -128,7 +129,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
*
*
* @param ifsConFactory IFS 连接工厂 , 每个并发线程调用 get ( ) 获取独立连接 , 避免共享连接线程安全问题
* @param ifsConFactory IFS 连接工厂 , 每个并发线程调用 get ( ) 获取独立连接 , 避免共享连接线程安全问题
* /
* /
private BomNodeData collectBomData ( QuoteDetail detail , Long parentId , Integer level , Supplier < Server > ifsConFactory ) {
private BomNodeData collectBomData ( QuoteDetail detail , Long parentId , Integer level , Supplier < Server > ifsConFactory , Map < String , BigDecimal > costCache ) {
/ / 确定 BOM 类型
/ / 确定 BOM 类型
if ( parentId . equals ( 0L ) ) {
if ( parentId . equals ( 0L ) ) {
detail . setBomType ( "Manufacturing" ) ;
detail . setBomType ( "Manufacturing" ) ;
@ -189,7 +190,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
if ( Objects . nonNull ( childBomCheck ) & & "Y" . equals ( component . getBomFlag ( ) ) ) {
if ( Objects . nonNull ( childBomCheck ) & & "Y" . equals ( component . getBomFlag ( ) ) ) {
log . debug ( "[BOM_PROCESS] 递归处理半成品 - PartNo: {}" , component . getComponentPart ( ) ) ;
log . debug ( "[BOM_PROCESS] 递归处理半成品 - PartNo: {}" , component . getComponentPart ( ) ) ;
/ / 递归收集子节点 , parentId 占位 - 1 , 写入时由 doSaveBomDataRecursive 覆盖
/ / 递归收集子节点 , parentId 占位 - 1 , 写入时由 doSaveBomDataRecursive 覆盖
BomNodeData childNode = collectBomData ( childDetail , - 1L , level + 1 , ifsConFactory ) ;
BomNodeData childNode = collectBomData ( childDetail , - 1L , level + 1 , ifsConFactory , costCache ) ;
if ( childNode ! = null ) {
if ( childNode ! = null ) {
nodeData . children . add ( childNode ) ;
nodeData . children . add ( childNode ) ;
/ / 半成品 : bomId 在写入阶段填充 , 先清零价格 ( IFS 并行阶段会覆盖 )
/ / 半成品 : bomId 在写入阶段填充 , 先清零价格 ( IFS 并行阶段会覆盖 )
@ -231,7 +232,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
Server conn = null ;
Server conn = null ;
try {
try {
conn = connectionPool . take ( ) ;
conn = connectionPool . take ( ) ;
getFinalPartCost ( component , conn ) ;
getFinalPartCost ( component , conn , costCache ) ;
} catch ( InterruptedException e ) {
} catch ( InterruptedException e ) {
Thread . currentThread ( ) . interrupt ( ) ;
Thread . currentThread ( ) . interrupt ( ) ;
log . warn ( "[BOM_PROCESS] 并行成本查询线程被中断 - PartNo: {}" , component . getComponentPart ( ) ) ;
log . warn ( "[BOM_PROCESS] 并行成本查询线程被中断 - PartNo: {}" , component . getComponentPart ( ) ) ;
@ -336,6 +337,9 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
@Override
@Override
public void changeQuoteDetailBomTree ( QuoteDetailBomTree tree ) {
public void changeQuoteDetailBomTree ( QuoteDetailBomTree tree ) {
long startTime = System . currentTimeMillis ( ) ;
log . info ( "========== [PERF_LOG] START changeQuoteDetailBomTree for QuoteDetailId: {} ==========" , tree . getQuoteDetailId ( ) ) ;
QuoteDetail detail = null ;
QuoteDetail detail = null ;
BomNodeData nodeData = null ;
BomNodeData nodeData = null ;
Long newParentId = 0L ;
Long newParentId = 0L ;
@ -353,10 +357,13 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
detail . setAlternativeNo ( tree . getAlternativeNo ( ) ) ;
detail . setAlternativeNo ( tree . getAlternativeNo ( ) ) ;
/ / 先在事务外完成所有 IFS 调用和数据收集 , 避免 IFS 阻塞期间持有数据库行锁
/ / 先在事务外完成所有 IFS 调用和数据收集 , 避免 IFS 阻塞期间持有数据库行锁
long collectStartTime = System . currentTimeMillis ( ) ;
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
newParentId = bomTree . getParentId ( ) ;
newParentId = bomTree . getParentId ( ) ;
Integer newLevel = Optional . ofNullable ( tree . getLevel ( ) ) . orElse ( 0 ) ;
Integer newLevel = Optional . ofNullable ( tree . getLevel ( ) ) . orElse ( 0 ) ;
nodeData = collectBomData ( detail , newParentId , newLevel , ifsConFactory ) ;
Map < String , BigDecimal > costCache = new ConcurrentHashMap < > ( ) ;
nodeData = collectBomData ( detail , newParentId , newLevel , ifsConFactory , costCache ) ;
log . info ( "[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms" , ( System . currentTimeMillis ( ) - collectStartTime ) ) ;
} else {
} else {
detail = new QuoteDetail ( ) ;
detail = new QuoteDetail ( ) ;
detail . setQuoteId ( tree . getQuoteId ( ) ) ;
detail . setQuoteId ( tree . getQuoteId ( ) ) ;
@ -371,8 +378,11 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
detail . setAlternativeNo ( tree . getAlternativeNo ( ) ) ;
detail . setAlternativeNo ( tree . getAlternativeNo ( ) ) ;
/ / 先收集数据 , 再写入
/ / 先收集数据 , 再写入
long collectStartTime = System . currentTimeMillis ( ) ;
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
Supplier < Server > ifsConFactory = resolveIfsConFactory ( ) ;
nodeData = collectBomData ( detail , 0L , 0 , ifsConFactory ) ;
Map < String , BigDecimal > costCache = new ConcurrentHashMap < > ( ) ;
nodeData = collectBomData ( detail , 0L , 0 , ifsConFactory , costCache ) ;
log . info ( "[PERF_LOG] Phase 1: collectBomData (including IFS calls) took {} ms" , ( System . currentTimeMillis ( ) - collectStartTime ) ) ;
}
}
/ / 数据已就绪 , 开启短事务执行纯 DB 写入和成本计算
/ / 数据已就绪 , 开启短事务执行纯 DB 写入和成本计算
@ -381,6 +391,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
final Long finalNewParentId = newParentId ;
final Long finalNewParentId = newParentId ;
final QuoteDetailBomTree finalBomTree = bomTree ;
final QuoteDetailBomTree finalBomTree = bomTree ;
long transactionStartTime = System . currentTimeMillis ( ) ;
transactionTemplate . execute ( status - > {
transactionTemplate . execute ( status - > {
/ / 1 . 执行 BOM 结构切换的 DB 写入
/ / 1 . 执行 BOM 结构切换的 DB 写入
if ( Objects . nonNull ( tree . getId ( ) ) ) {
if ( Objects . nonNull ( tree . getId ( ) ) ) {
@ -400,24 +411,30 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
. update ( ) ;
. update ( ) ;
} else {
} else {
if ( finalNodeData ! = null ) {
if ( finalNodeData ! = null ) {
lambdaUpdate ( ) . eq ( QuoteDetailBomTree : : getQuoteDetailId , finalDetail . getId ( ) ) . remove ( ) ;
quoteDetailBomService . lambdaUpdate ( ) . eq ( QuoteDetailBom : : getQuoteDetailId , finalDetail . getId ( ) ) . remove ( ) ;
quoteDetailRoutingService . lambdaUpdate ( ) . eq ( QuoteDetailRouting : : getQuoteDetailId , finalDetail . getId ( ) ) . remove ( ) ;
doSaveBomDataRecursive ( finalNodeData , finalDetail , 0L ) ;
doSaveBomDataRecursive ( finalNodeData , finalDetail , 0L ) ;
}
}
}
}
return null ;
return null ;
} ) ;
} ) ;
log . info ( "[PERF_LOG] Phase 2: DB Transaction (Delete old tree & Save new tree) took {} ms" , ( System . currentTimeMillis ( ) - transactionStartTime ) ) ;
/ / 2 . 重新计算成本 ( 如果开启 )
/ / 2 . 重新计算成本 ( 如果开启 )
/ / 移出事务外执行 , 避免长事务锁表导致其他用户无法查看 / 编辑条目明细
/ / 移出事务外执行 , 避免长事务锁表导致其他用户无法查看 / 编辑条目明细
if ( Boolean . TRUE . equals ( tree . getRecalculateCost ( ) ) & & finalDetail ! = null ) {
if ( Boolean . TRUE . equals ( tree . getRecalculateCost ( ) ) & & finalDetail ! = null ) {
log . info ( "[BOM_SWITCH] Recalculate cost is enabled, executing cost calculation for QuoteDetailId: {}" , finalDetail . getId ( ) ) ;
log . info ( "[BOM_SWITCH] Recalculate cost is enabled, executing cost calculation for QuoteDetailId: {}" , finalDetail . getId ( ) ) ;
long calcCostStartTime = System . currentTimeMillis ( ) ;
try {
try {
quoteDetailService . queryQuoteDetailCost ( finalDetail ) ;
quoteDetailService . queryQuoteDetailCost ( finalDetail ) ;
log . info ( "[BOM_SWITCH] Cost calculation completed successfully for QuoteDetailId: {}" , finalDetail . getId ( ) ) ;
log . info ( "[PERF_LOG] Phase 3: Cost calculation (queryQuoteDetailCost) took {} ms" , ( System . currentTimeMillis ( ) - calcCostStartTime ) ) ;
} catch ( Exception e ) {
} catch ( Exception e ) {
log . error ( "[BOM_SWITCH] Cost calculation failed for QuoteDetailId: {}, Error: {}" , finalDetail . getId ( ) , e . getMessage ( ) , e ) ;
log . error ( "[BOM_SWITCH] Cost calculation failed for QuoteDetailId: {}, Error: {}" , finalDetail . getId ( ) , e . getMessage ( ) , e ) ;
throw new RuntimeException ( "BOM切换成功,但成本重新计算失败: " + e . getMessage ( ) ) ;
throw new RuntimeException ( "BOM切换成功,但成本重新计算失败: " + e . getMessage ( ) ) ;
}
}
}
}
log . info ( "========== [PERF_LOG] END changeQuoteDetailBomTree. Total time: {} ms ==========" , ( System . currentTimeMillis ( ) - startTime ) ) ;
}
}
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
@ -694,12 +711,22 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
/ * *
/ * *
* 获取物料成本 ( 带重试机制 )
* 获取物料成本 ( 带重试机制 )
* /
* /
private void getFinalPartCost ( QuoteDetailBom component , Server ifsServer ) {
private void getFinalPartCost ( QuoteDetailBom component , Server ifsServer , Map < String , BigDecimal > costCache ) {
final int MAX_RETRY_COUNT = 2 ;
final int MAX_RETRY_COUNT = 2 ;
final long RETRY_INTERVAL_MS = 500 ;
final long RETRY_INTERVAL_MS = 500 ;
String partNo = component . getComponentPart ( ) ;
String partNo = component . getComponentPart ( ) ;
String site = component . getSite ( ) ;
String site = component . getSite ( ) ;
String cacheKey = site + "_" + partNo ;
BigDecimal cachedCost = costCache . get ( cacheKey ) ;
if ( cachedCost ! = null ) {
component . setUnitPrice ( cachedCost ) ;
component . setActualPrice ( cachedCost ) ;
component . setQuotePrice ( cachedCost ) ;
log . info ( "[COST_QUERY] 从缓存读取成功 - PartNo: {}, Site: {}, UnitCost: {}" , partNo , site , cachedCost ) ;
return ;
}
log . info ( "[COST_QUERY] 开始查询物料成本 - PartNo: {}, Site: {}" , partNo , site ) ;
log . info ( "[COST_QUERY] 开始查询物料成本 - PartNo: {}, Site: {}" , partNo , site ) ;
@ -723,6 +750,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
component . setUnitPrice ( unitCost ) ;
component . setUnitPrice ( unitCost ) ;
component . setActualPrice ( unitCost ) ;
component . setActualPrice ( unitCost ) ;
component . setQuotePrice ( unitCost ) ;
component . setQuotePrice ( unitCost ) ;
costCache . put ( cacheKey , unitCost ) ;
log . info ( "[COST_QUERY] 查询成功 - PartNo: {}, Site: {}, UnitCost: {}, 尝试次数: {}" ,
log . info ( "[COST_QUERY] 查询成功 - PartNo: {}, Site: {}, UnitCost: {}, 尝试次数: {}" ,
partNo , site , unitCost , retryCount ) ;
partNo , site , unitCost , retryCount ) ;
@ -779,6 +807,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
component . setUnitPrice ( BigDecimal . ZERO ) ;
component . setUnitPrice ( BigDecimal . ZERO ) ;
component . setActualPrice ( BigDecimal . ZERO ) ;
component . setActualPrice ( BigDecimal . ZERO ) ;
component . setQuotePrice ( BigDecimal . ZERO ) ;
component . setQuotePrice ( BigDecimal . ZERO ) ;
costCache . put ( cacheKey , BigDecimal . ZERO ) ;
}
}
}
}