package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService; import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; import java.util.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @Tag(name = "管理后台 - MES 生产工单 BOM") @RestController @RequestMapping("/mes/pro/work-order-bom") @Validated public class MesProWorkOrderBomController { @Resource private MesProWorkOrderBomService workOrderBomService; @Resource private MesMdItemService itemService; @Resource private MesMdUnitMeasureService unitMeasureService; @Resource private MesMdItemTypeService itemTypeService; @Resource private MesMdProductBomService productBomService; @PostMapping("/create") @Operation(summary = "创建工单 BOM") @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')") public CommonResult createWorkOrderBom(@Valid @RequestBody MesProWorkOrderBomSaveReqVO createReqVO) { return success(workOrderBomService.createWorkOrderBom(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新工单 BOM") @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')") public CommonResult updateWorkOrderBom(@Valid @RequestBody MesProWorkOrderBomSaveReqVO updateReqVO) { workOrderBomService.updateWorkOrderBom(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除工单 BOM") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')") public CommonResult deleteWorkOrderBom(@RequestParam("id") Long id) { workOrderBomService.deleteWorkOrderBom(id); return success(true); } @GetMapping("/get") @Operation(summary = "获得工单 BOM") @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") public CommonResult getWorkOrderBom(@RequestParam("id") Long id) { MesProWorkOrderBomDO workOrderBom = workOrderBomService.getWorkOrderBom(id); if (workOrderBom == null) { return success(null); } return success(buildWorkOrderBomRespVOList(ListUtil.of(workOrderBom)).get(0)); } @GetMapping("/page") @Operation(summary = "获得工单 BOM 分页") @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") public CommonResult> getWorkOrderBomPage(@Valid MesProWorkOrderBomPageReqVO pageReqVO) { PageResult pageResult = workOrderBomService.getWorkOrderBomPage(pageReqVO); return success(new PageResult<>(buildWorkOrderBomRespVOList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/item-list-by-work-order-id") @Operation(summary = "获得工单物料需求列表") @Parameter(name = "workOrderId", description = "工单编号", required = true) @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") public CommonResult> getWorkOrderBomItemListByWorkOrderId( @RequestParam("workOrderId") Long workOrderId) { // 1. 获取工单 BOM 行 List bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(workOrderId); if (CollUtil.isEmpty(bomList)) { return success(Collections.emptyList()); } // 2. 逐层展开,得到叶子物料 itemId → quantity Map leafItems = buildWorkOrderItems(bomList); if (CollUtil.isEmpty(leafItems)) { return success(Collections.emptyList()); } // 3. 拼接 VO 字段 Map itemMap = itemService.getItemMap(leafItems.keySet()); Map unitMeasureMap = unitMeasureService.getUnitMeasureMap( convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); Map itemTypeMap = itemTypeService.getItemTypeMap( convertSet(itemMap.values(), MesMdItemDO::getItemTypeId)); List result = new ArrayList<>(leafItems.size()); for (Map.Entry entry : leafItems.entrySet()) { MesMdItemDO item = itemMap.get(entry.getKey()); if (item == null) { continue; } MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO() .setItemId(item.getId()).setQuantity(entry.getValue()) .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification()); MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(), unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName())); MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(), itemType -> vo.setItemOrProduct(itemType.getItemOrProduct())); result.add(vo); } return success(result); } // ==================== 拼接 VO ==================== private List buildWorkOrderBomRespVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } // 1. 批量获取关联数据 Map itemMap = itemService.getItemMap( convertSet(list, MesProWorkOrderBomDO::getItemId)); Map unitMeasureMap = unitMeasureService.getUnitMeasureMap( convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); Map itemTypeMap = itemTypeService.getItemTypeMap( convertSet(itemMap.values(), MesMdItemDO::getItemTypeId)); // 2. 拼接 VO return BeanUtils.toBean(list, MesProWorkOrderBomRespVO.class, vo -> { MapUtils.findAndThen(itemMap, vo.getItemId(), item -> { vo.setItemName(item.getName()).setItemCode(item.getCode()) .setItemSpecification(item.getSpecification()); MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(), itemType -> vo.setItemOrProduct(itemType.getItemOrProduct())); MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(), unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName())); }); }); } // ==================== 物料需求逐层展开 ==================== /** * 逐层 IN 查询展开工单 BOM 到叶子物料,合并同物料的需求数量 * * @param bomList 工单 BOM 列表 * @return 叶子物料 Map:itemId → 合并后的需求数量 */ private Map buildWorkOrderItems(List bomList) { // 1. 初始化:收集第一层物料 ID 和对应数量 Map currentLayer = new LinkedHashMap<>(); for (MesProWorkOrderBomDO bom : bomList) { currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add); } // 2. 逐层 IN 查询展开 BOM Map leafItems = new LinkedHashMap<>(); for (int i = 0; i < Byte.MAX_VALUE && CollUtil.isNotEmpty(currentLayer); i++) { // 2.1 批量查询当前层所有物料的子 BOM List subBomList = productBomService.getProductBomListByItemIds(currentLayer.keySet()); // 2.2 按 itemId 分组 Map> subBomMap = convertMultiMap(subBomList, MesMdProductBomDO::getItemId); // 2.3 区分叶子节点和非叶子节点 Map nextLayer = new LinkedHashMap<>(); for (Map.Entry entry : currentLayer.entrySet()) { Long itemId = entry.getKey(); BigDecimal quantity = entry.getValue(); List children = subBomMap.get(itemId); // 情况一:叶子节点,累加到结果 if (CollUtil.isEmpty(children)) { leafItems.merge(itemId, quantity, BigDecimal::add); continue; } // 情况二:非叶子,子物料进入下一层 for (MesMdProductBomDO child : children) { nextLayer.merge(child.getBomItemId(), quantity.multiply(child.getQuantity()), BigDecimal::add); } } currentLayer = nextLayer; } return leafItems; } }