Commit f7b06a095267da7562c47c29a7dbbf84a0d85a27

Authored by YunaiV
1 parent 7098655a

fix(mes): 完善生产工单模块,优化 BOM 展开、工单状态校验及仓库删除保护

1. BomController:新增物料需求接口 /item-list-by-work-order-id,
   采用逐层 IN 查询展开 BOM 到叶子物料(替代递归),使用 convertMultiMap 统一风格;
   补充 BomRespVO 的 itemOrProduct 字段及 itemType 拼接
2. WorkOrderController:新增 confirmWorkOrder 确认接口,
   调整 confirm/finish/cancel 端点顺序;parentMap 改用 getWorkOrderMap
3. WorkOrderService/ServiceImpl:新增 getWorkOrderList 空判断、getWorkOrderMap、
   confirmWorkOrder 状态校验,finish/cancel 增加"已确认"状态前置校验,
   新增 PRO_WORK_ORDER_NOT_CONFIRMED 错误码
4. ProductBomMapper/Service:新增 selectByItemIds 批量查询方法
5. 仓库/库区/库位删除:补充库存记录占用校验(WM_WAREHOUSE_HAS_MATERIAL_STOCK 等)
6. 移除工单 batchCode 字段(DO/SaveReqVO/RespVO)
Showing 13 changed files with 228 additions and 45 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkorderBomController.java
@@ -5,13 +5,18 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -5,13 +5,18 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO;
8 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomPageReqVO; 9 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomPageReqVO;
9 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomRespVO; 10 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomRespVO;
10 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO; 11 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; 12 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
12 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; 15 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
13 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; 16 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
14 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; 17 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  18 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
  19 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService;
15 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; 20 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
16 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService; 21 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
17 import io.swagger.v3.oas.annotations.Operation; 22 import io.swagger.v3.oas.annotations.Operation;
@@ -23,11 +28,10 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -23,11 +28,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
23 import org.springframework.validation.annotation.Validated; 28 import org.springframework.validation.annotation.Validated;
24 import org.springframework.web.bind.annotation.*; 29 import org.springframework.web.bind.annotation.*;
25 30
26 -import java.util.Collections;  
27 -import java.util.List;  
28 -import java.util.Map;  
29 - 31 +import java.math.BigDecimal;
  32 +import java.util.*;
30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
32 36
33 @Tag(name = "管理后台 - MES 生产工单 BOM") 37 @Tag(name = "管理后台 - MES 生产工单 BOM")
@@ -45,6 +49,12 @@ public class MesProWorkOrderBomController { @@ -45,6 +49,12 @@ public class MesProWorkOrderBomController {
45 @Resource 49 @Resource
46 private MesMdUnitMeasureService unitMeasureService; 50 private MesMdUnitMeasureService unitMeasureService;
47 51
  52 + @Resource
  53 + private MesMdItemTypeService itemTypeService;
  54 +
  55 + @Resource
  56 + private MesMdProductBomService productBomService;
  57 +
48 @PostMapping("/create") 58 @PostMapping("/create")
49 @Operation(summary = "创建工单 BOM") 59 @Operation(summary = "创建工单 BOM")
50 @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')") 60 @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')")
@@ -89,6 +99,44 @@ public class MesProWorkOrderBomController { @@ -89,6 +99,44 @@ public class MesProWorkOrderBomController {
89 return success(new PageResult<>(buildWorkOrderBomRespVOList(pageResult.getList()), pageResult.getTotal())); 99 return success(new PageResult<>(buildWorkOrderBomRespVOList(pageResult.getList()), pageResult.getTotal()));
90 } 100 }
91 101
  102 + @GetMapping("/item-list-by-work-order-id")
  103 + @Operation(summary = "获得工单物料需求列表")
  104 + @Parameter(name = "workOrderId", description = "工单编号", required = true)
  105 + @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
  106 + public CommonResult<List<MesProWorkOrderItemRespVO>> getWorkOrderBomItemListByWorkOrderId(
  107 + @RequestParam("workOrderId") Long workOrderId) {
  108 + // 1. 获取工单 BOM 行
  109 + List<MesProWorkOrderBomDO> bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(workOrderId);
  110 + if (CollUtil.isEmpty(bomList)) {
  111 + return success(Collections.emptyList());
  112 + }
  113 +
  114 + // 2. 逐层展开,得到叶子物料 itemId → quantity
  115 + Map<Long, BigDecimal> leafItems = buildWorkOrderItems(bomList);
  116 + if (leafItems.isEmpty()) {
  117 + return success(Collections.emptyList());
  118 + }
  119 +
  120 + // 3. 拼接 VO 字段
  121 + Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(leafItems.keySet());
  122 + Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
  123 + convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
  124 + List<MesProWorkOrderItemRespVO> result = new ArrayList<>(leafItems.size());
  125 + for (Map.Entry<Long, BigDecimal> entry : leafItems.entrySet()) {
  126 + MesMdItemDO item = itemMap.get(entry.getKey());
  127 + if (item == null) {
  128 + continue;
  129 + }
  130 + MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
  131 + .setItemId(item.getId()).setQuantity(entry.getValue())
  132 + .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpec(item.getSpecification());
  133 + MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
  134 + unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
  135 + result.add(vo);
  136 + }
  137 + return success(result);
  138 + }
  139 +
92 // ==================== 拼接 VO ==================== 140 // ==================== 拼接 VO ====================
93 141
94 private List<MesProWorkOrderBomRespVO> buildWorkOrderBomRespVOList(List<MesProWorkOrderBomDO> list) { 142 private List<MesProWorkOrderBomRespVO> buildWorkOrderBomRespVOList(List<MesProWorkOrderBomDO> list) {
@@ -100,14 +148,62 @@ public class MesProWorkOrderBomController { @@ -100,14 +148,62 @@ public class MesProWorkOrderBomController {
100 convertSet(list, MesProWorkOrderBomDO::getItemId)); 148 convertSet(list, MesProWorkOrderBomDO::getItemId));
101 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( 149 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
102 convertSet(list, MesProWorkOrderBomDO::getUnitMeasureId)); 150 convertSet(list, MesProWorkOrderBomDO::getUnitMeasureId));
  151 + Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
  152 + convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
103 // 2. 拼接 VO 153 // 2. 拼接 VO
104 return BeanUtils.toBean(list, MesProWorkOrderBomRespVO.class, vo -> { 154 return BeanUtils.toBean(list, MesProWorkOrderBomRespVO.class, vo -> {
105 - MapUtils.findAndThen(itemMap, vo.getItemId(), item ->  
106 - vo.setItemName(item.getName()).setItemCode(item.getCode())  
107 - .setItemSpec(item.getSpecification())); 155 + MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
  156 + vo.setItemName(item.getName()).setItemCode(item.getCode())
  157 + .setItemSpec(item.getSpecification());
  158 + MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(),
  159 + itemType -> vo.setItemOrProduct(itemType.getItemOrProduct()));
  160 + });
108 MapUtils.findAndThen(unitMeasureMap, vo.getUnitMeasureId(), 161 MapUtils.findAndThen(unitMeasureMap, vo.getUnitMeasureId(),
109 unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName())); 162 unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
110 }); 163 });
111 } 164 }
112 165
  166 + // ==================== 物料需求逐层展开 ====================
  167 +
  168 + /**
  169 + * 逐层 IN 查询展开工单 BOM 到叶子物料,合并同物料的需求数量
  170 + *
  171 + * @param bomList 工单 BOM 列表
  172 + * @return 叶子物料 Map:itemId → 合并后的需求数量
  173 + */
  174 + private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderBomDO> bomList) {
  175 + // 1. 初始化:收集第一层物料 ID 和对应数量
  176 + Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
  177 + for (MesProWorkOrderBomDO bom : bomList) {
  178 + currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
  179 + }
  180 +
  181 + // 2. 逐层 IN 查询展开 BOM
  182 + Map<Long, BigDecimal> leafItems = new LinkedHashMap<>();
  183 + for (int i = 0; i < Byte.MAX_VALUE && CollUtil.isNotEmpty(currentLayer); i++) {
  184 + // 2.1 批量查询当前层所有物料的子 BOM
  185 + List<MesMdProductBomDO> subBomList = productBomService.getProductBomListByItemIds(currentLayer.keySet());
  186 + // 2.2 按 itemId 分组
  187 + Map<Long, List<MesMdProductBomDO>> subBomMap = convertMultiMap(subBomList, MesMdProductBomDO::getItemId);
  188 + // 2.3 区分叶子节点和非叶子节点
  189 + Map<Long, BigDecimal> nextLayer = new LinkedHashMap<>();
  190 + for (Map.Entry<Long, BigDecimal> entry : currentLayer.entrySet()) {
  191 + Long itemId = entry.getKey();
  192 + BigDecimal quantity = entry.getValue();
  193 + List<MesMdProductBomDO> children = subBomMap.get(itemId);
  194 + // 情况一:叶子节点,累加到结果
  195 + if (CollUtil.isEmpty(children)) {
  196 + leafItems.merge(itemId, quantity, BigDecimal::add);
  197 + continue;
  198 + }
  199 + // 情况二:非叶子,子物料进入下一层
  200 + for (MesMdProductBomDO child : children) {
  201 + nextLayer.merge(child.getBomItemId(), quantity.multiply(child.getQuantity()), BigDecimal::add);
  202 + }
  203 + }
  204 + currentLayer = nextLayer;
  205 + }
  206 + return leafItems;
  207 + }
  208 +
113 } 209 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkorderController.java
@@ -32,14 +32,10 @@ import org.springframework.validation.annotation.Validated; @@ -32,14 +32,10 @@ import org.springframework.validation.annotation.Validated;
32 import org.springframework.web.bind.annotation.*; 32 import org.springframework.web.bind.annotation.*;
33 33
34 import java.io.IOException; 34 import java.io.IOException;
35 -import java.util.Collections;  
36 -import java.util.List;  
37 -import java.util.Map;  
38 -import java.util.Set; 35 +import java.util.*;
39 36
40 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; 37 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
41 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 38 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;  
43 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 39 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
44 40
45 @Tag(name = "管理后台 - MES 生产工单") 41 @Tag(name = "管理后台 - MES 生产工单")
@@ -119,6 +115,15 @@ public class MesProWorkOrderController { @@ -119,6 +115,15 @@ public class MesProWorkOrderController {
119 ExcelUtils.write(response, "生产工单.xls", "数据", MesProWorkOrderRespVO.class, voList); 115 ExcelUtils.write(response, "生产工单.xls", "数据", MesProWorkOrderRespVO.class, voList);
120 } 116 }
121 117
  118 + @PutMapping("/confirm")
  119 + @Operation(summary = "确认工单")
  120 + @Parameter(name = "id", description = "编号", required = true)
  121 + @PreAuthorize("@ss.hasPermission('mes:pro-work-order:update')")
  122 + public CommonResult<Boolean> confirmWorkOrder(@RequestParam("id") Long id) {
  123 + workOrderService.confirmWorkOrder(id);
  124 + return success(true);
  125 + }
  126 +
122 @PutMapping("/finish") 127 @PutMapping("/finish")
123 @Operation(summary = "完成工单") 128 @Operation(summary = "完成工单")
124 @Parameter(name = "id", description = "编号", required = true) 129 @Parameter(name = "id", description = "编号", required = true)
@@ -152,11 +157,8 @@ public class MesProWorkOrderController { @@ -152,11 +157,8 @@ public class MesProWorkOrderController {
152 convertSet(list, MesProWorkOrderDO::getVendorId)); 157 convertSet(list, MesProWorkOrderDO::getVendorId));
153 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( 158 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
154 convertSet(list, MesProWorkOrderDO::getUnitMeasureId)); 159 convertSet(list, MesProWorkOrderDO::getUnitMeasureId));
155 - // TODO @AI:直接 workOrderService.getWorkOrderMap,内部调用 workOrderService.getWorkOrderList  
156 - Set<Long> parentIds = convertSet(list, MesProWorkOrderDO::getParentId);  
157 - Map<Long, MesProWorkOrderDO> parentMap = CollUtil.isEmpty(parentIds)  
158 - ? Collections.emptyMap()  
159 - : convertMap(workOrderService.getWorkOrderList(parentIds), MesProWorkOrderDO::getId); 160 + Map<Long, MesProWorkOrderDO> parentMap = workOrderService.getWorkOrderMap(
  161 + convertSet(list, MesProWorkOrderDO::getParentId));
160 // 2. 拼接 VO 162 // 2. 拼接 VO
161 return BeanUtils.toBean(list, MesProWorkOrderRespVO.class, vo -> { 163 return BeanUtils.toBean(list, MesProWorkOrderRespVO.class, vo -> {
162 MapUtils.findAndThen(itemMap, vo.getProductId(), item -> 164 MapUtils.findAndThen(itemMap, vo.getProductId(), item ->
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +
  8 +@Schema(description = "管理后台 - MES 工单物料需求 Response VO")
  9 +@Data
  10 +public class MesProWorkOrderItemRespVO {
  11 +
  12 + @Schema(description = "物料编号", example = "100")
  13 + private Long itemId;
  14 +
  15 + @Schema(description = "物料编码", example = "M-001")
  16 + private String itemCode;
  17 +
  18 + @Schema(description = "物料名称", example = "电阻 10K")
  19 + private String itemName;
  20 +
  21 + @Schema(description = "规格型号", example = "0603")
  22 + private String itemSpec;
  23 +
  24 + @Schema(description = "单位名称", example = "个")
  25 + private String unitMeasureName;
  26 +
  27 + @Schema(description = "需求数量", example = "1000.00")
  28 + private BigDecimal quantity;
  29 +
  30 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkorderRespVO.java
@@ -98,10 +98,6 @@ public class MesProWorkOrderRespVO { @@ -98,10 +98,6 @@ public class MesProWorkOrderRespVO {
98 @ExcelProperty("供应商名称") 98 @ExcelProperty("供应商名称")
99 private String vendorName; 99 private String vendorName;
100 100
101 - @Schema(description = "批次号", example = "BATCH-001")  
102 - @ExcelProperty("批次号")  
103 - private String batchCode;  
104 -  
105 @Schema(description = "需求日期") 101 @Schema(description = "需求日期")
106 @ExcelProperty("需求日期") 102 @ExcelProperty("需求日期")
107 private LocalDateTime requestDate; 103 private LocalDateTime requestDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkorderSaveReqVO.java
@@ -61,9 +61,6 @@ public class MesProWorkOrderSaveReqVO { @@ -61,9 +61,6 @@ public class MesProWorkOrderSaveReqVO {
61 @Schema(description = "供应商编号", example = "400") 61 @Schema(description = "供应商编号", example = "400")
62 private Long vendorId; 62 private Long vendorId;
63 63
64 - @Schema(description = "批次号", example = "BATCH-001")  
65 - private String batchCode;  
66 -  
67 @Schema(description = "需求日期", requiredMode = Schema.RequiredMode.REQUIRED) 64 @Schema(description = "需求日期", requiredMode = Schema.RequiredMode.REQUIRED)
68 @NotNull(message = "需求日期不能为空") 65 @NotNull(message = "需求日期不能为空")
69 private LocalDateTime requestDate; 66 private LocalDateTime requestDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/bom/MesProWorkorderBomRespVO.java
@@ -50,6 +50,9 @@ public class MesProWorkOrderBomRespVO { @@ -50,6 +50,9 @@ public class MesProWorkOrderBomRespVO {
50 @ExcelProperty("备注") 50 @ExcelProperty("备注")
51 private String remark; 51 private String remark;
52 52
  53 + @Schema(description = "物料产品标识", example = "PRODUCT")
  54 + private String itemOrProduct;
  55 +
53 @Schema(description = "预留字段1") 56 @Schema(description = "预留字段1")
54 private String attribute1; 57 private String attribute1;
55 58
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkorderDO.java
@@ -101,10 +101,6 @@ public class MesProWorkOrderDO extends BaseDO { @@ -101,10 +101,6 @@ public class MesProWorkOrderDO extends BaseDO {
101 */ 101 */
102 private Long vendorId; 102 private Long vendorId;
103 /** 103 /**
104 - * 批次号  
105 - */  
106 - private String batchCode;  
107 - /**  
108 * 需求日期 104 * 需求日期
109 */ 105 */
110 private LocalDateTime requestDate; 106 private LocalDateTime requestDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdProductBomMapper.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductB @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductB
7 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
  10 +import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 12
12 /** 13 /**
@@ -28,6 +29,10 @@ public interface MesMdProductBomMapper extends BaseMapperX&lt;MesMdProductBomDO&gt; { @@ -28,6 +29,10 @@ public interface MesMdProductBomMapper extends BaseMapperX&lt;MesMdProductBomDO&gt; {
28 return selectList(MesMdProductBomDO::getItemId, itemId); 29 return selectList(MesMdProductBomDO::getItemId, itemId);
29 } 30 }
30 31
  32 + default List<MesMdProductBomDO> selectByItemIds(Collection<Long> itemIds) {
  33 + return selectList(MesMdProductBomDO::getItemId, itemIds);
  34 + }
  35 +
31 default List<MesMdProductBomDO> selectAll() { 36 default List<MesMdProductBomDO> selectAll() {
32 return selectList(); 37 return selectList();
33 } 38 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -167,6 +167,7 @@ public interface ErrorCodeConstants { @@ -167,6 +167,7 @@ public interface ErrorCodeConstants {
167 ErrorCode PRO_WORK_ORDER_NOT_EXISTS = new ErrorCode(1_040_502_000, "生产工单不存在"); 167 ErrorCode PRO_WORK_ORDER_NOT_EXISTS = new ErrorCode(1_040_502_000, "生产工单不存在");
168 ErrorCode PRO_WORK_ORDER_CODE_DUPLICATE = new ErrorCode(1_040_502_001, "生产工单编码已存在"); 168 ErrorCode PRO_WORK_ORDER_CODE_DUPLICATE = new ErrorCode(1_040_502_001, "生产工单编码已存在");
169 ErrorCode PRO_WORK_ORDER_NOT_PREPARE = new ErrorCode(1_040_502_002, "只能删除草稿状态的工单"); 169 ErrorCode PRO_WORK_ORDER_NOT_PREPARE = new ErrorCode(1_040_502_002, "只能删除草稿状态的工单");
  170 + ErrorCode PRO_WORK_ORDER_NOT_CONFIRMED = new ErrorCode(1_040_502_003, "只有已确认状态的工单才能执行此操作");
170 ErrorCode PRO_WORK_ORDER_BOM_NOT_EXISTS = new ErrorCode(1_040_502_100, "生产工单BOM不存在"); 171 ErrorCode PRO_WORK_ORDER_BOM_NOT_EXISTS = new ErrorCode(1_040_502_100, "生产工单BOM不存在");
171 172
172 // ========== MES 生产管理-生产任务(1-040-503-000) ========== 173 // ========== MES 生产管理-生产任务(1-040-503-000) ==========
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductB @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductB
6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.util.Collection;
9 import java.util.List; 10 import java.util.List;
10 11
11 /** 12 /**
@@ -62,6 +63,14 @@ public interface MesMdProductBomService { @@ -62,6 +63,14 @@ public interface MesMdProductBomService {
62 List<MesMdProductBomDO> getProductBomListByItemId(Long itemId); 63 List<MesMdProductBomDO> getProductBomListByItemId(Long itemId);
63 64
64 /** 65 /**
  66 + * 根据物料产品编号批量获得产品BOM列表
  67 + *
  68 + * @param itemIds 物料产品编号数组
  69 + * @return 产品BOM列表
  70 + */
  71 + List<MesMdProductBomDO> getProductBomListByItemIds(Collection<Long> itemIds);
  72 +
  73 + /**
65 * 根据物料产品编号删除产品BOM 74 * 根据物料产品编号删除产品BOM
66 * 75 *
67 * @param itemId 物料产品编号 76 * @param itemId 物料产品编号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdProductBomServiceImpl.java
@@ -133,6 +133,11 @@ public class MesMdProductBomServiceImpl implements MesMdProductBomService { @@ -133,6 +133,11 @@ public class MesMdProductBomServiceImpl implements MesMdProductBomService {
133 } 133 }
134 134
135 @Override 135 @Override
  136 + public List<MesMdProductBomDO> getProductBomListByItemIds(Collection<Long> itemIds) {
  137 + return productBomMapper.selectByItemIds(itemIds);
  138 + }
  139 +
  140 + @Override
136 public void deleteProductBomByItemId(Long itemId) { 141 public void deleteProductBomByItemId(Long itemId) {
137 productBomMapper.deleteByItemId(itemId); 142 productBomMapper.deleteByItemId(itemId);
138 } 143 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkorderService.java
@@ -8,6 +8,9 @@ import jakarta.validation.Valid; @@ -8,6 +8,9 @@ import jakarta.validation.Valid;
8 8
9 import java.util.Collection; 9 import java.util.Collection;
10 import java.util.List; 10 import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
11 14
12 /** 15 /**
13 * MES 生产工单 Service 接口 16 * MES 生产工单 Service 接口
@@ -63,25 +66,42 @@ public interface MesProWorkOrderService { @@ -63,25 +66,42 @@ public interface MesProWorkOrderService {
63 PageResult<MesProWorkOrderDO> getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO); 66 PageResult<MesProWorkOrderDO> getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO);
64 67
65 /** 68 /**
66 - * 完成工单 69 + * 获得工单列表
  70 + *
  71 + * @param ids 编号数组
  72 + * @return 工单列表
  73 + */
  74 + List<MesProWorkOrderDO> getWorkOrderList(Collection<Long> ids);
  75 +
  76 + /**
  77 + * 获得工单 Map
  78 + *
  79 + * @param ids 编号数组
  80 + * @return 工单 Map
  81 + */
  82 + default Map<Long, MesProWorkOrderDO> getWorkOrderMap(Collection<Long> ids) {
  83 + return convertMap(getWorkOrderList(ids), MesProWorkOrderDO::getId);
  84 + }
  85 +
  86 + /**
  87 + * 确认工单(草稿 → 已确认)
67 * 88 *
68 * @param id 编号 89 * @param id 编号
69 */ 90 */
70 - void finishWorkOrder(Long id); 91 + void confirmWorkOrder(Long id);
71 92
72 /** 93 /**
73 - * 取消工单 94 + * 完成工单
74 * 95 *
75 * @param id 编号 96 * @param id 编号
76 */ 97 */
77 - void cancelWorkOrder(Long id); 98 + void finishWorkOrder(Long id);
78 99
79 /** 100 /**
80 - * 获得工单列表 101 + * 取消工单
81 * 102 *
82 - * @param ids 编号数组  
83 - * @return 工单列表 103 + * @param id 编号
84 */ 104 */
85 - List<MesProWorkOrderDO> getWorkOrderList(Collection<Long> ids); 105 + void cancelWorkOrder(Long id);
86 106
87 } 107 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkorderServiceImpl.java
@@ -25,6 +25,7 @@ import org.springframework.validation.annotation.Validated; @@ -25,6 +25,7 @@ import org.springframework.validation.annotation.Validated;
25 import java.math.BigDecimal; 25 import java.math.BigDecimal;
26 import java.time.LocalDateTime; 26 import java.time.LocalDateTime;
27 import java.util.Collection; 27 import java.util.Collection;
  28 +import java.util.Collections;
28 import java.util.List; 29 import java.util.List;
29 30
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -139,9 +140,33 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { @@ -139,9 +140,33 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
139 } 140 }
140 141
141 @Override 142 @Override
142 - public void finishWorkOrder(Long id) { 143 + public List<MesProWorkOrderDO> getWorkOrderList(Collection<Long> ids) {
  144 + if (CollUtil.isEmpty(ids)) {
  145 + return Collections.emptyList();
  146 + }
  147 + return workOrderMapper.selectByIds(ids);
  148 + }
  149 +
  150 + @Override
  151 + public void confirmWorkOrder(Long id) {
143 // 1. 校验存在 152 // 1. 校验存在
144 - validateWorkOrderExists(id); 153 + MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
  154 + // 2. 只有草稿状态才能确认
  155 + if (!ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) {
  156 + throw exception(PRO_WORK_ORDER_NOT_PREPARE);
  157 + }
  158 + // 3. 更新状态为已确认
  159 + workOrderMapper.updateById(new MesProWorkOrderDO().setId(id)
  160 + .setStatus(MesProWorkOrderStatusEnum.CONFIRMED.getStatus()));
  161 + }
  162 +
  163 + @Override
  164 + public void finishWorkOrder(Long id) {
  165 + // 1. 校验存在 + 只有已确认状态才能完成
  166 + MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
  167 + if (!ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) {
  168 + throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
  169 + }
145 170
146 // 2. 更新状态为已完成 171 // 2. 更新状态为已完成
147 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) 172 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id)
@@ -152,8 +177,11 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { @@ -152,8 +177,11 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
152 177
153 @Override 178 @Override
154 public void cancelWorkOrder(Long id) { 179 public void cancelWorkOrder(Long id) {
155 - // 1. 校验存在  
156 - validateWorkOrderExists(id); 180 + // 1. 校验存在 + 只有已确认状态才能取消
  181 + MesProWorkOrderDO workOrder = validateWorkOrderExists(id);
  182 + if (!ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) {
  183 + throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
  184 + }
157 185
158 // 2. 更新状态为已取消 186 // 2. 更新状态为已取消
159 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) 187 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id)
@@ -162,11 +190,6 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { @@ -162,11 +190,6 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
162 // TODO @芋艿:pro_task 未迁移,暂不级联更新任务状态 190 // TODO @芋艿:pro_task 未迁移,暂不级联更新任务状态
163 } 191 }
164 192
165 - @Override  
166 - public List<MesProWorkOrderDO> getWorkOrderList(Collection<Long> ids) {  
167 - return workOrderMapper.selectByIds(ids);  
168 - }  
169 -  
170 // ==================== 校验方法 ==================== 193 // ==================== 校验方法 ====================
171 194
172 private void validateWorkOrderCodeUnique(Long id, String code) { 195 private void validateWorkOrderCodeUnique(Long id, String code) {