Browse Source

2026-07-16

销售报价,partType = Purchased 的成本口径改为“直接取 IFS 成本”;
master
fengyuan_yang 6 days ago
parent
commit
baed4a1825
  1. 10
      src/main/java/com/spring/modules/quote/service/QuoteDetailBomTreeService.java
  2. 65
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java
  3. 26
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

10
src/main/java/com/spring/modules/quote/service/QuoteDetailBomTreeService.java

@ -36,6 +36,16 @@ public interface QuoteDetailBomTreeService extends IService<QuoteDetailBomTree>
* @return 预估材料成本estimated_material_cost
*/
BigDecimal queryEstimatedMaterialCost(String site, String partNo);
/**
* 根据物料编码查询 IFS 库存价值InventoryValue
* 采购类成本口径改造后Purchased/Purchased (raw) 节点优先使用该值
*
* @param site 站点
* @param partNo 物料编码
* @return IFS InventoryValue查询失败返回0
*/
BigDecimal queryInventoryValue(String site, String partNo);
/**
* 根据物料编码查询物料类型

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

@ -236,6 +236,17 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
component.setCreateDate(detail.getCreateDate());
QuoteDetail childDetail = createQuoteDetail(detail, component);
String childPartType = baseMapper.queryPartType(childDetail.getSite(), childDetail.getPartNo());
boolean treatAsPurchasedLeaf = "Purchased".equals(childPartType) || "Purchased (raw)".equals(childPartType);
if (treatAsPurchasedLeaf) {
// 按业务新口径Purchased Purchased(raw) 一样按叶子处理不再递归展开下级BOM
component.setBomFlag("N");
log.info("[BOM_PROCESS] 子物料 {} (partType={}) 按叶子处理,跳过递归建树",
component.getComponentPart(), childPartType);
nodeData.components.add(component);
continue;
}
QuoteDetailBomTree childBomCheck = isComponentBom(childDetail);
if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) {
@ -720,6 +731,60 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
}
}
@Override
public BigDecimal queryInventoryValue(String site, String partNo) {
log.info("queryInventoryValue - Request params: site={}, partNo={}", site, partNo);
String status = baseMapper.queryPartStatus(site, partNo);
log.info("queryInventoryValue - Query part status from database: status={}", status);
// 非正式料按现有口径回退数据库函数避免 IFS 无数据导致整单成本归零
if (!"Y".equals(status)) {
BigDecimal cost = baseMapper.getPartCost(site, partNo);
BigDecimal result = cost != null ? cost : BigDecimal.ZERO;
log.info("queryInventoryValue - Unofficial part (status={}), fallback to DB unit cost: {}", status, result);
return result;
}
try {
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
SysUserEntity ifsUser = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", username));
if (ifsUser == null || !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsUsername())
|| !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsPassword())) {
log.warn("queryInventoryValue - User {} has no IFS account configured, fallback to DB unit cost", username);
BigDecimal cost = baseMapper.getPartCost(site, partNo);
BigDecimal result = cost != null ? cost : BigDecimal.ZERO;
log.info("queryInventoryValue - Fallback result from DB unit cost: {}", result);
return result;
}
Server ifsCon = ifsServer.getIfsServer(ifsUser.getIfsUsername(), ifsUser.getIfsPassword());
PartInformationEntity part = new PartInformationEntity();
part.setSite(site);
part.setPartNo(partNo);
log.info("queryInventoryValue - Calling IFS getInventoryValueByPartNo with params: site={}, partNo={}", site, partNo);
Map<String, String> map = baseSearchBean.getInventoryValueByPartNo(ifsCon, part);
log.info("queryInventoryValue - IFS API response: {}", map);
if (Objects.equals(map.get("resultCode"), "200")) {
InventoryPartUnitCostSumVo unitCostSumVo = JSONObject.parseObject(map.get("obj"), InventoryPartUnitCostSumVo.class);
String inventoryValueStr = unitCostSumVo.getInventoryValue();
BigDecimal inventoryValue = org.apache.commons.lang3.StringUtils.isNotBlank(inventoryValueStr)
? new BigDecimal(inventoryValueStr) : BigDecimal.ZERO;
log.info("queryInventoryValue - Successfully retrieved InventoryValue from IFS: {}", inventoryValue);
return inventoryValue;
} else {
log.warn("queryInventoryValue - Failed to retrieve from IFS, resultCode={}, resultMsg={}",
map.get("resultCode"), map.get("resultMsg"));
return BigDecimal.ZERO;
}
} catch (Exception e) {
log.error("queryInventoryValue - Exception occurred for partNo={}: {}", partNo, e.getMessage(), e);
return BigDecimal.ZERO;
}
}
@Override
public String queryPartType(String site, String partNo) {
return baseMapper.queryPartType(site, partNo);

26
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

@ -208,7 +208,9 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
QuoteDetail quoteDetail = getById(detail.getId());
Map<String, Object> map = new HashMap<>();
// 用于缓存IFS接口获取的物料预估成本避免在循环中重复调用降低性能
// 用于缓存 IFS 接口获取的 InventoryValue采购类节点直接成本口径
Map<String, BigDecimal> inventoryValueCache = new HashMap<>();
// 兼容历史数据的兜底缓存仅在 partType 无法识别但 bomType=Purchase 时使用
Map<String, BigDecimal> estimatedCostCache = new HashMap<>();
long perfAttrStartTime = System.currentTimeMillis();
@ -337,17 +339,31 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
for (QuoteDetailBom bom : boms) {
bomQuotePrice = bomQuotePrice.add(bom.getQuoteUnitPrice());
}
// 如果半成品的BOM类型是Purchase则加上该物料的预估材料成本(estimated_material_cost)
if ("Purchase".equals(bomTree.getBomType())) {
// 新口径Purchased / Purchased(raw) 节点直接取 IFS InventoryValue不再按子树卷积+估算成本叠加
if ("Purchased".equals(partType) || "Purchased (raw)".equals(partType)) {
String cacheKey = bomTree.getSite() + "_" + bomTree.getPartNo();
bomQuotePrice = inventoryValueCache.computeIfAbsent(cacheKey, k -> {
long ifsStart = System.currentTimeMillis();
BigDecimal cost = quoteDetailBomTreeService.queryInventoryValue(bomTree.getSite(), bomTree.getPartNo());
log.info("[COST_CALC] [PERF_LOG] Fetched InventoryValue for {} from IFS/DB in {} ms",
bomTree.getPartNo(), (System.currentTimeMillis() - ifsStart));
return cost == null ? BigDecimal.ZERO : cost;
});
log.info("[COST_CALC] Part {} with partType={} uses direct InventoryValue as node material cost: {}",
bomTree.getPartNo(), partType, bomQuotePrice);
} else if ("Purchase".equals(bomTree.getBomType())) {
// 兼容历史脏数据 partType 未识别但节点 bomType Purchase 时仍维持旧兜底逻辑
String cacheKey = bomTree.getSite() + "_" + bomTree.getPartNo();
BigDecimal estimatedMaterialCost = estimatedCostCache.computeIfAbsent(cacheKey, k -> {
long ifsStart = System.currentTimeMillis();
BigDecimal cost = quoteDetailBomTreeService.queryEstimatedMaterialCost(bomTree.getSite(), bomTree.getPartNo());
log.info("[COST_CALC] [PERF_LOG] Fetched estimated material cost for {} from IFS/DB in {} ms", bomTree.getPartNo(), (System.currentTimeMillis() - ifsStart));
log.info("[COST_CALC] [PERF_LOG] Legacy fallback fetched estimated material cost for {} in {} ms",
bomTree.getPartNo(), (System.currentTimeMillis() - ifsStart));
return cost == null ? BigDecimal.ZERO : cost;
});
bomQuotePrice = bomQuotePrice.add(estimatedMaterialCost);
log.info("[COST_CALC] Semi-finished product {} is Purchase type, add estimated material cost {}, accumulated cost is {}",
log.info("[COST_CALC] Legacy fallback node {} (bomType=Purchase) adds estimated material cost {}, accumulated cost {}",
bomTree.getPartNo(), estimatedMaterialCost, bomQuotePrice);
}

Loading…
Cancel
Save