Browse Source

2026-02-04

报价【计算】优化
master
fengyuan_yang 4 weeks ago
parent
commit
925803beda
  1. 102
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

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

@ -202,6 +202,8 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
@Override
public Map<String, Object> queryQuoteDetailCost(QuoteDetail detail) {
log.info("========== [COST_CALC] START - QuoteDetailId: {} ==========", detail.getId());
QuoteDetail quoteDetail = getById(detail.getId());
Map<String, Object> map = new HashMap<>();
// 1工具成本
@ -234,9 +236,13 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
.orderByDesc(QuoteDetailBomTree::getLevel)
.orderByDesc(QuoteDetailBomTree::getId)
.list();
log.info("[COST_CALC] Total BomTree nodes: {}, Root node exists: {}", list.size(), one != null);
// 判断 报价BomTree是否存在
if (Objects.nonNull(one)) {
log.info("[COST_CALC] Root node - PartNo: {}, BomType: {}, TreeId: {}", one.getPartNo(), one.getBomType(), one.getId());
// 查询 工艺
QuoteDetailRouting queryRouting = new QuoteDetailRouting();
queryRouting.setQuoteDetailId(quoteDetail.getId());
@ -244,6 +250,8 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
queryRouting.setTreeId(one.getId());
List<QuoteDetailRouting> routingList = quoteDetailRoutingService.queryQuoteDetailRouting(queryRouting);
Map<Long,List<QuoteDetailRouting>> routingMap = routingList.stream().collect(Collectors.groupingBy(QuoteDetailRouting::getTreeId));
log.info("[COST_CALC] Total routing records loaded: {}", routingList.size());
// 查询 BOM
QuoteDetailBom quoteDetailBom = new QuoteDetailBom();
quoteDetailBom.setQuoteDetailId(quoteDetail.getId());
@ -251,30 +259,46 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
quoteDetailBom.setTreeId(one.getId());
List<QuoteDetailBom> bomList = quoteDetailBomService.queryQuoteDetailBom(quoteDetailBom);
Map<Long,List<QuoteDetailBom>> bomMap = bomList.stream().collect(Collectors.groupingBy(QuoteDetailBom::getTreeId));
log.info("[COST_CALC] Total BOM component records loaded: {}", bomList.size());
long treeId = 0;
int nodeIndex = 0;
// 第一层BOM
for (QuoteDetailBomTree bomTree : list) {
nodeIndex++;
log.info("[COST_CALC] ===== Processing node {}/{}: PartNo={}, Level={}, TreeId={}, ParentId={}, BomType={} =====",
nodeIndex, list.size(), bomTree.getPartNo(), bomTree.getLevel(), bomTree.getId(), bomTree.getParentId(), bomTree.getBomType());
if (Long.valueOf(0L).equals(bomTree.getParentId())){
treeId = bomTree.getId();
log.info("[COST_CALC] This is ROOT node (ParentId=0)");
}
// 查询物料类型只有Manufactured或Manufactured Recipe类型的物料才计算Routing成本
String partType = quoteDetailBomTreeService.queryPartType(bomTree.getSite(), bomTree.getPartNo());
boolean isManufacturedPart = "Manufactured".equals(partType) || "Manufactured Recipe".equals(partType);
log.info("[COST_CALC] PartType for {}: {}, isManufacturedPart: {}", bomTree.getPartNo(), partType, isManufacturedPart);
// 计算具体人工成本
List<QuoteDetailRouting> routings = Optional.ofNullable(routingMap.get(bomTree.getId())).orElse(new ArrayList<>());
log.info("[COST_CALC] Found {} routing records for this node", routings.size());
BigDecimal bomLabourCost = BigDecimal.ZERO;
BigDecimal bomMachineCost = BigDecimal.ZERO;
// 获得 BomTree的 人工和制造成本仅Manufactured或Manufactured Recipe类型的物料
if (isManufacturedPart) {
for (QuoteDetailRouting routing : routings) {
log.info("[COST_CALC] Routing: OperationId={}, LaborCost={}, MachCost={}",
routing.getOperationId(), routing.getTotalLaborCost(), routing.getTotalMachCost());
bomLabourCost = bomLabourCost.add(routing.getTotalLaborCost());
bomMachineCost = bomMachineCost.add(routing.getTotalMachCost());
}
log.info("[COST_CALC] Base routing cost for node {}: LabourCost={}, MachineCost={}",
bomTree.getPartNo(), bomLabourCost, bomMachineCost);
} else {
log.info("[COST_CALC] Skipping routing cost for non-manufactured part (PartType={})", partType);
}
// BomTree的物料成本
List<QuoteDetailBom> boms = Optional.ofNullable(bomMap.get(bomTree.getId())).orElse(new ArrayList<>());
BigDecimal bomQuotePrice = BigDecimal.ZERO;
@ -286,21 +310,40 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
BigDecimal estimatedMaterialCost = quoteDetailBomTreeService.queryEstimatedMaterialCost(
bomTree.getSite(), bomTree.getPartNo());
bomQuotePrice = bomQuotePrice.add(estimatedMaterialCost);
log.info("Semi-finished product {} is Purchase type, add estimated material cost {}, accumulated cost is {}",
log.info("[COST_CALC] Semi-finished product {} is Purchase type, add estimated material cost {}, accumulated cost is {}",
bomTree.getPartNo(), estimatedMaterialCost, bomQuotePrice);
}
log.info("当前BOM:{}", bomTree.getPartNo());
// 第二层BOM 目的是遍历所有的 BOM中 Part用量
long parentId = bomTree.getParentId();
String partNo = bomTree.getPartNo();
// BUG FIX: Track the current node's TreeId during upward convolution
// Previously used bomTree.getId() which is always the starting node's ID
// Now we track currentTreeId which changes as we move up the tree
Long currentTreeId = bomTree.getId();
log.info("[COST_CALC] Starting upward convolution from node {} (Level={}, TreeId={})",
bomTree.getPartNo(), bomTree.getLevel(), currentTreeId);
int convolutionStep = 0;
for (QuoteDetailBomTree tree : list) {
if (tree.getLevel() > bomTree.getLevel() || tree.getId() != parentId) {
continue;
}
convolutionStep++;
log.info("[COST_CALC] Convolution step {}: Current PartNo={}, CurrentTreeId={}, Looking for parent TreeId={}",
convolutionStep, partNo, currentTreeId, parentId);
// 获取 层级比自己高的Bom 来获得BOM信息 用量和损耗
// List<QuoteDetailBom> parentBomList = quoteDetailBomService.lambdaQuery().eq(QuoteDetailBom::getTreeId, parentId).eq(QuoteDetailBom::getComponentPart, partNo).list();
String finalPartNo = partNo;
List<QuoteDetailBom> parentBomList = bomMap.get(parentId).stream().filter(bom->bom.getComponentPart().equals(finalPartNo)).collect(Collectors.toList());
List<QuoteDetailBom> parentBomListRaw = bomMap.get(parentId);
if (parentBomListRaw == null) {
log.warn("[COST_CALC] WARNING: No BOM list found for parentId={}", parentId);
continue;
}
List<QuoteDetailBom> parentBomList = parentBomListRaw.stream().filter(bom->bom.getComponentPart().equals(finalPartNo)).collect(Collectors.toList());
log.info("[COST_CALC] Found {} matching BOM records for component {} in parent {}",
parentBomList.size(), finalPartNo, parentId);
// 赋值新的成本
for (QuoteDetailBom bom : parentBomList) {
bom.setQuotePrice(bomQuotePrice);
@ -320,21 +363,52 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
BigDecimal qtyPerAssembly = BigDecimal.ONE;
for (QuoteDetailBom bom : parentBomList) {
if (bomTree.getId().equals(bom.getBomId())) {
BigDecimal yield = BigDecimal.ONE.subtract(bom.getShrinkageFactor().divide( BigDecimal.valueOf(100),16,RoundingMode.HALF_UP));
BigDecimal decimal = bom.getQtyPerAssembly().divide(yield, 16, RoundingMode.HALF_UP);
// BUG FIX: Use currentTreeId instead of bomTree.getId()
// currentTreeId tracks the node we're currently moving up from
if (currentTreeId.equals(bom.getBomId())) {
BigDecimal shrinkageFactor = bom.getShrinkageFactor();
BigDecimal qtyPerAsm = bom.getQtyPerAssembly();
BigDecimal yield = BigDecimal.ONE.subtract(shrinkageFactor.divide(BigDecimal.valueOf(100),16,RoundingMode.HALF_UP));
BigDecimal decimal = qtyPerAsm.divide(yield, 16, RoundingMode.HALF_UP);
log.info("[COST_CALC] BomId={} matches CurrentTreeId={}: QtyPerAssembly={}, ShrinkageFactor={}%, Yield={}, AdjustedQty={}",
bom.getBomId(), currentTreeId, qtyPerAsm, shrinkageFactor, yield, decimal);
qtyPerAssembly = qtyPerAssembly.multiply(decimal);
} else {
log.debug("[COST_CALC] BomId={} does not match CurrentTreeId={}, skipping", bom.getBomId(), currentTreeId);
}
}
// Check if no matching BomId was found
if (qtyPerAssembly.equals(BigDecimal.ONE) && !parentBomList.isEmpty()) {
log.warn("[COST_CALC] WARNING: No BomId matched CurrentTreeId={}. Available BomIds: {}",
currentTreeId,
parentBomList.stream().map(b -> String.valueOf(b.getBomId())).collect(Collectors.joining(",")));
}
log.info("[COST_CALC] Before convolution: LabourCost={}, MachineCost={}", bomLabourCost, bomMachineCost);
log.info("[COST_CALC] Multiplier (qtyPerAssembly): {}", qtyPerAssembly);
// 计算 损耗和用量得到的差异
bomLabourCost = bomLabourCost.multiply(qtyPerAssembly);
bomMachineCost = bomMachineCost.multiply(qtyPerAssembly);
log.info("[COST_CALC] After convolution: LabourCost={}, MachineCost={}", bomLabourCost, bomMachineCost);
// 拿到上层数据
// BUG FIX: Update currentTreeId to the parent node's TreeId for the next iteration
currentTreeId = tree.getId();
parentId = tree.getParentId();
partNo = tree.getPartNo();
log.debug("[COST_CALC] Moving up: nextTreeId={}, nextParentId={}, nextPartNo={}", currentTreeId, parentId, partNo);
}
log.info("[COST_CALC] Final cost for node {}: LabourCost={}, MachineCost={}", bomTree.getPartNo(), bomLabourCost, bomMachineCost);
log.info("[COST_CALC] Accumulating to total - Before: LabourCost={}, MachineCost={}", labourCost, machineCost);
labourCost = labourCost.add(bomLabourCost);
machineCost = machineCost.add(bomMachineCost);
log.info("[COST_CALC] Accumulating to total - After: LabourCost={}, MachineCost={}", labourCost, machineCost);
}
// 计算最终成本
List<QuoteDetailBom> boms = bomMap.get(treeId);
@ -376,6 +450,20 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
map.put("manufactureCost", manufactureCost);
map.put("testCost", testCost);
map.put("elseCost", elseCost);
log.info("========== [COST_CALC] FINAL RESULT ==========");
log.info("[COST_CALC] QuoteDetailId: {}", detail.getId());
log.info("[COST_CALC] LabourCost (Total): {}", labourCost);
log.info("[COST_CALC] MachineCost (Total): {}", machineCost);
log.info("[COST_CALC] UnitQuotePrice (Material): {}", unitQuotePrice);
log.info("[COST_CALC] ActualQuotePrice: {}", actualQuotePrice);
log.info("[COST_CALC] ToolCost: {}", toolCost);
log.info("[COST_CALC] PackCost: {}", packCost);
log.info("[COST_CALC] ShippingCost: {}", shippingCost);
log.info("[COST_CALC] TestCost: {}", testCost);
log.info("[COST_CALC] ElseCost: {}", elseCost);
log.info("========== [COST_CALC] END ==========");
return map;
}

Loading…
Cancel
Save