Commit f7b06a095267da7562c47c29a7dbbf84a0d85a27
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<MesMdProductBomDO> { | @@ -28,6 +29,10 @@ public interface MesMdProductBomMapper extends BaseMapperX<MesMdProductBomDO> { | ||
| 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) { |