|
|
|
@ -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、工具成本 |
|
|
|
@ -235,8 +237,12 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote |
|
|
|
.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; |
|
|
|
} |
|
|
|
|
|
|
|
|