Commit 67193b0c3b4eb90bc13ba1804243ffbbe617e542

Authored by YunaiV
1 parent 6f4aa8fd

✨ feat(mes): 添加生产领料出库单相关功能与组件

新增生产领料出库单的相关数据模型、接口及前端组件,支持领料单的创建、编辑和完成操作。优化了数据校验逻辑,并添加了物料、库存和工单选择器,提升用户体验。

- 实现领料单的基本 CRUD 操作
- 增加物料和库存选择功能
- 优化前端表单布局与交互
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productionissue/MesWmProductionIssueController.java
... ... @@ -34,6 +34,9 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPOR
34 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
35 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
36 36  
  37 +// TODO @AI:方法的排序,参考别的模块;
  38 +// TODO @AI:controller、service、包括其他的,都需要全。createProductitonIssue 这种;其它模块也是;
  39 +// TODO @AI:需要新增 line、detail 的 controller;参考 /Users/yunai/Java/yudao-all-in-one/ruoyi-vue-pro/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt
37 40 @Tag(name = "管理后台 - MES 领料出库单")
38 41 @RestController
39 42 @RequestMapping("/mes/wm/production-issue")
... ... @@ -102,24 +105,6 @@ public class MesWmProductionIssueController {
102 105 buildRespVOList(pageResult.getList()));
103 106 }
104 107  
105   - @PutMapping("/approve")
106   - @Operation(summary = "审批领料出库单")
107   - @Parameter(name = "id", description = "编号", required = true)
108   - @PreAuthorize("@ss.hasPermission('mes:wm-production-issue:update-status')")
109   - public CommonResult<Boolean> approveIssue(@RequestParam("id") Long id) {
110   - issueService.approveIssue(id);
111   - return success(true);
112   - }
113   -
114   - @PutMapping("/unapprove")
115   - @Operation(summary = "反审批领料出库单")
116   - @Parameter(name = "id", description = "编号", required = true)
117   - @PreAuthorize("@ss.hasPermission('mes:wm-production-issue:update-status')")
118   - public CommonResult<Boolean> unapproveIssue(@RequestParam("id") Long id) {
119   - issueService.unapproveIssue(id);
120   - return success(true);
121   - }
122   -
123 108 @PutMapping("/finish")
124 109 @Operation(summary = "完成领料出库单")
125 110 @Parameter(name = "id", description = "编号", required = true)
... ... @@ -131,6 +116,7 @@ public class MesWmProductionIssueController {
131 116  
132 117 // ==================== 拼接 VO ====================
133 118  
  119 + // TODO @AI:这个方法的拼接,在优化下;
134 120 private List<MesWmProductionIssueRespVO> buildRespVOList(List<MesWmProductionIssueDO> list) {
135 121 if (CollUtil.isEmpty(list)) {
136 122 return Collections.emptyList();
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productionissue/vo/MesWmProductionIssueSaveReqVO.java
... ... @@ -13,28 +13,25 @@ public class MesWmProductionIssueSaveReqVO {
13 13 @Schema(description = "编号", example = "1024")
14 14 private Long id;
15 15  
  16 + // TODO @AI:必须传递
16 17 @Schema(description = "领料单编号", example = "ISSUE20250226001")
17 18 private String code;
18 19  
  20 + // TODO @AI:必须传递
19 21 @Schema(description = "领料单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "生产领料")
20 22 private String name;
21 23  
22   - @Schema(description = "工作站ID", example = "1")
  24 + // TODO @AI:必须传递
  25 + // TODO @AI:service 需要校验存在
  26 + @Schema(description = "工作站 ID", example = "1")
23 27 private Long workstationId;
24 28  
25   - @Schema(description = "生产工单ID", example = "1")
  29 + @Schema(description = "生产工单 ID", example = "1")
  30 + // TODO @AI:service 需要校验存在
26 31 private Long workorderId;
27 32  
28   - @Schema(description = "生产任务ID", example = "1")
29   - private Long taskId;
30   -
31   - @Schema(description = "客户ID", example = "1")
32   - private Long clientId;
33   -
34   - @Schema(description = "领料日期")
35   - private LocalDateTime issueDate;
36   -
37 33 @Schema(description = "需求时间")
  34 + // TODO @AI:必须传递
38 35 private LocalDateTime requiredTime;
39 36  
40 37 @Schema(description = "备注", example = "备注")
... ... @@ -52,6 +49,7 @@ public class MesWmProductionIssueSaveReqVO {
52 49 @Schema(description = "预留字段4")
53 50 private Integer attribute4;
54 51  
  52 + // TODO @AI:应该单独接口,传递这个;参考别的模块;
55 53 @Schema(description = "领料单行列表")
56 54 private List<MesWmProductionIssueLineSaveReqVO> lines;
57 55  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
... ... @@ -357,4 +357,17 @@ public interface ErrorCodeConstants {
357 357 ErrorCode WM_OUTSOURCE_ISSUE_QUANTITY_MISMATCH = new ErrorCode(1_040_707_002, "出库单行数量与明细数量不一致");
358 358 ErrorCode WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_707_100, "外协出库单行不存在");
359 359  
  360 + // ========== MES 仓库管理-生产领料出库单(1-040-708-000) ==========
  361 + ErrorCode WM_PRODUCTION_ISSUE_NOT_EXISTS = new ErrorCode(1_040_708_000, "生产领料出库单不存在");
  362 + ErrorCode WM_PRODUCTION_ISSUE_STATUS_INVALID = new ErrorCode(1_040_708_001, "生产领料出库单状态不正确,无法执行该操作");
  363 + // TODO @AI:不需要的错误码,需要处理下。
  364 + ErrorCode WM_PRODUCTION_ISSUE_WAREHOUSE_NOT_EXISTS = new ErrorCode(1_040_708_002, "仓库不存在");
  365 + ErrorCode WM_PRODUCTION_ISSUE_ITEM_NOT_EXISTS = new ErrorCode(1_040_708_003, "物料不存在");
  366 + ErrorCode WM_PRODUCTION_ISSUE_WORKORDER_NOT_EXISTS = new ErrorCode(1_040_708_004, "生产工单不存在");
  367 + ErrorCode WM_PRODUCTION_ISSUE_WORKSTATION_NOT_EXISTS = new ErrorCode(1_040_708_005, "工作站不存在");
  368 + ErrorCode WM_PRODUCTION_ISSUE_NO_LINE = new ErrorCode(1_040_708_006, "至少需要一条行项目");
  369 + ErrorCode WM_PRODUCTION_ISSUE_QUANTITY_MISMATCH = new ErrorCode(1_040_708_007, "领料单行数量与明细数量不一致");
  370 + ErrorCode WM_PRODUCTION_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_708_100, "生产领料出库单行不存在");
  371 + ErrorCode WM_PRODUCTION_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_708_200, "生产领料出库单明细不存在");
  372 +
360 373 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductionIssueStatusEnum.java
1 1 package cn.iocoder.yudao.module.mes.enums.wm;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Getter;
6 7  
7 8 import java.util.Arrays;
8 9  
9   -// TODO @AI:需要使用 status constants 去映射;
10 10 /**
11 11 * MES 领料出库单状态枚举
12 12 */
... ... @@ -14,9 +14,8 @@ import java.util.Arrays;
14 14 @AllArgsConstructor
15 15 public enum MesWmProductionIssueStatusEnum implements ArrayValuable<Integer> {
16 16  
17   - PREPARE(10, "准备中"),
18   - APPROVED(20, "已审批"),
19   - FINISHED(30, "已完成");
  17 + PREPARE(MesOrderStatusConstants.PREPARE, "草稿"),
  18 + FINISHED(MesOrderStatusConstants.FINISHED, "已完成");
20 19  
21 20 public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesWmProductionIssueStatusEnum::getStatus).toArray(Integer[]::new);
22 21  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productionissue/MesWmProductionIssueService.java
... ... @@ -53,21 +53,7 @@ public interface MesWmProductionIssueService {
53 53 PageResult<MesWmProductionIssueDO> getIssuePage(MesWmProductionIssuePageReqVO pageReqVO);
54 54  
55 55 /**
56   - * 审批领料出库单(准备中 → 已审批,扣减库存)
57   - *
58   - * @param id 编号
59   - */
60   - void approveIssue(Long id);
61   -
62   - /**
63   - * 反审批领料出库单(已审批 → 准备中,恢复库存)
64   - *
65   - * @param id 编号
66   - */
67   - void unapproveIssue(Long id);
68   -
69   - /**
70   - * 完成领料出库单(已审批 → 已完成)
  56 + * 完成领料出库单(草稿 → 已完成,执行出库)
71 57 *
72 58 * @param id 编号
73 59 */
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productionissue/MesWmProductionIssueServiceImpl.java
... ... @@ -4,14 +4,19 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.mes.controller.admin.wm.productionissue.vo.MesWmProductionIssueLineSaveReqVO;
7 8 import cn.iocoder.yudao.module.mes.controller.admin.wm.productionissue.vo.MesWmProductionIssuePageReqVO;
8 9 import cn.iocoder.yudao.module.mes.controller.admin.wm.productionissue.vo.MesWmProductionIssueSaveReqVO;
9   -import cn.iocoder.yudao.module.mes.controller.admin.wm.productionissue.vo.MesWmProductionIssueLineSaveReqVO;
10 10 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productionissue.MesWmProductionIssueDO;
11 11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productionissue.MesWmProductionIssueLineDO;
12   -import cn.iocoder.yudao.module.mes.dal.mysql.wm.productionissue.MesWmProductionIssueMapper;
  12 +import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper;
  13 +import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkstationMapper;
  14 +import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
13 15 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productionissue.MesWmProductionIssueLineMapper;
  16 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.productionissue.MesWmProductionIssueMapper;
  17 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.MesWmWarehouseMapper;
14 18 import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductionIssueStatusEnum;
  19 +import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
15 20 import jakarta.annotation.Resource;
16 21 import org.springframework.stereotype.Service;
17 22 import org.springframework.transaction.annotation.Transactional;
... ... @@ -21,6 +26,11 @@ import java.util.Collection;
21 26 import java.util.Collections;
22 27 import java.util.List;
23 28  
  29 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  30 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCTION_ISSUE_NOT_EXISTS;
  31 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCTION_ISSUE_STATUS_INVALID;
  32 +
  33 +// TODO @AI:参考别的模块,拆分多个;
24 34 /**
25 35 * MES 领料出库单 Service 实现类
26 36 */
... ... @@ -40,17 +50,33 @@ public class MesWmProductionIssueServiceImpl implements MesWmProductionIssueServ
40 50 @Resource
41 51 private MesWmProductionIssueDetailService issueDetailService;
42 52  
  53 + @Resource
  54 + private MesWmWarehouseMapper warehouseMapper;
  55 +
  56 + @Resource
  57 + private MesMdItemMapper itemMapper;
  58 +
  59 + @Resource
  60 + private MesMdWorkstationMapper workstationMapper;
  61 +
  62 + @Resource
  63 + private MesProWorkOrderMapper workOrderMapper;
  64 +
  65 + @Resource
  66 + private MesWmMaterialStockService materialStockService;
  67 +
43 68 @Override
44 69 @Transactional(rollbackFor = Exception.class)
45 70 public Long createIssue(MesWmProductionIssueSaveReqVO createReqVO) {
46   - // TODO @AI:领料出库关联数据校验待完善(仓库、物料、工单、工位等基础数据校验)
  71 + // 1. 校验关联数据
  72 + validateRelatedData(createReqVO);
47 73  
48   - // 1. 插入主表
  74 + // 2. 插入主表
49 75 MesWmProductionIssueDO issueHeader = BeanUtils.toBean(createReqVO, MesWmProductionIssueDO.class);
50 76 issueHeader.setStatus(MesWmProductionIssueStatusEnum.PREPARE.getStatus());
51 77 issueHeaderMapper.insert(issueHeader);
52 78  
53   - // 2. 插入行表
  79 + // 3. 插入行表
54 80 if (CollUtil.isNotEmpty(createReqVO.getLines())) {
55 81 for (MesWmProductionIssueLineSaveReqVO lineVO : createReqVO.getLines()) {
56 82 MesWmProductionIssueLineDO line = BeanUtils.toBean(lineVO, MesWmProductionIssueLineDO.class);
... ... @@ -67,13 +93,15 @@ public class MesWmProductionIssueServiceImpl implements MesWmProductionIssueServ
67 93 public void updateIssue(MesWmProductionIssueSaveReqVO updateReqVO) {
68 94 // 1. 校验存在 + 准备中状态
69 95 validateIssueExistsAndPrepare(updateReqVO.getId());
70   - // TODO @AI:领料出库关联数据校验待完善(仓库、物料、工单、工位等基础数据校验)
71 96  
72   - // 2. 更新主表
  97 + // 2. 校验关联数据
  98 + validateRelatedData(updateReqVO);
  99 +
  100 + // 3. 更新主表
73 101 MesWmProductionIssueDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductionIssueDO.class);
74 102 issueHeaderMapper.updateById(updateObj);
75 103  
76   - // 3. 更新行表:先删除旧数据,再插入新数据
  104 + // 4. 更新行表:先删除旧数据,再插入新数据
77 105 issueLineMapper.deleteByIssueId(updateReqVO.getId());
78 106 if (CollUtil.isNotEmpty(updateReqVO.getLines())) {
79 107 for (MesWmProductionIssueLineSaveReqVO lineVO : updateReqVO.getLines()) {
... ... @@ -110,51 +138,33 @@ public class MesWmProductionIssueServiceImpl implements MesWmProductionIssueServ
110 138  
111 139 @Override
112 140 @Transactional(rollbackFor = Exception.class)
113   - public void approveIssue(Long id) {
  141 + public void finishIssue(Long id) {
114 142 // 1.1 校验存在
115 143 MesWmProductionIssueDO issueHeader = validateIssueExists(id);
116   - // 1.2 校验状态:只有准备中才允许审批
  144 + // 1.2 校验状态:只有草稿才允许完成
117 145 if (ObjUtil.notEqual(MesWmProductionIssueStatusEnum.PREPARE.getStatus(), issueHeader.getStatus())) {
118   -// throw exception(WM_ISSUE_STATUS_INVALID);
119   - throw new IllegalArgumentException(); // TODO @芋艿:晚点优化下;
120   - }
121   -
122   - // 2. 准备中 → 已审批
123   - issueHeaderMapper.updateById(new MesWmProductionIssueDO()
124   - .setId(id).setStatus(MesWmProductionIssueStatusEnum.APPROVED.getStatus()));
125   -
126   - // TODO @AI:扣减库存逻辑待完善(需要调用库存服务,根据 detail 表扣减库存)
127   - }
128   -
129   - @Override
130   - @Transactional(rollbackFor = Exception.class)
131   - public void unapproveIssue(Long id) {
132   - // 1.1 校验存在
133   - MesWmProductionIssueDO issueHeader = validateIssueExists(id);
134   - // 1.2 校验状态:只有已审批才允许反审批
135   - if (ObjUtil.notEqual(MesWmProductionIssueStatusEnum.APPROVED.getStatus(), issueHeader.getStatus())) {
136   -// throw exception(WM_ISSUE_STATUS_INVALID);
137   - throw new IllegalArgumentException(); // TODO @芋艿:晚点优化下;
  146 + throw exception(WM_PRODUCTION_ISSUE_STATUS_INVALID);
138 147 }
139 148  
140   - // 2. 已审批 → 准备中
141   - issueHeaderMapper.updateById(new MesWmProductionIssueDO()
142   - .setId(id).setStatus(MesWmProductionIssueStatusEnum.PREPARE.getStatus()));
143   -
144   - // TODO @AI:恢复库存逻辑待完善(需要调用库存服务,根据 detail 表恢复库存)
145   - }
146   -
147   - @Override
148   - public void finishIssue(Long id) {
149   - // 1.1 校验存在
150   - MesWmProductionIssueDO issueHeader = validateIssueExists(id);
151   - // 1.2 校验状态:只有已审批才允许完成
152   - if (ObjUtil.notEqual(MesWmProductionIssueStatusEnum.APPROVED.getStatus(), issueHeader.getStatus())) {
153   -// throw exception(WM_ISSUE_STATUS_INVALID);
154   - throw new IllegalArgumentException(); // TODO @芋艿:晚点优化下;
  149 + // 2. 扣减库存(根据 line 表)
  150 + List<MesWmProductionIssueLineDO> lines = issueLineMapper.selectByIssueId(id);
  151 + if (CollUtil.isNotEmpty(lines)) {
  152 + for (MesWmProductionIssueLineDO line : lines) {
  153 + materialStockService.increaseStock(
  154 + line.getItemId(),
  155 + issueHeader.getWarehouseId(), // 使用主表的仓库ID
  156 + null, // locationId
  157 + null, // areaId
  158 + line.getBatchId(),
  159 + line.getQuantityIssued().negate(), // 负数表示扣减
  160 + null, // vendorId
  161 + null, // productionDate
  162 + null // expireDate
  163 + );
  164 + }
155 165 }
156 166  
157   - // 2. 已审批 → 已完成
  167 + // 3. 草稿 → 已完成
158 168 issueHeaderMapper.updateById(new MesWmProductionIssueDO()
159 169 .setId(id).setStatus(MesWmProductionIssueStatusEnum.FINISHED.getStatus()));
160 170 }
... ... @@ -170,8 +180,7 @@ public class MesWmProductionIssueServiceImpl implements MesWmProductionIssueServ
170 180 private MesWmProductionIssueDO validateIssueExists(Long id) {
171 181 MesWmProductionIssueDO issueHeader = issueHeaderMapper.selectById(id);
172 182 if (issueHeader == null) {
173   -// throw exception(WM_ISSUE_NOT_EXISTS);
174   - throw new IllegalArgumentException(); // TODO @芋艿:晚点优化下;
  183 + throw exception(WM_PRODUCTION_ISSUE_NOT_EXISTS);
175 184 }
176 185 return issueHeader;
177 186 }
... ... @@ -182,8 +191,7 @@ public class MesWmProductionIssueServiceImpl implements MesWmProductionIssueServ
182 191 private MesWmProductionIssueDO validateIssueExistsAndPrepare(Long id) {
183 192 MesWmProductionIssueDO issueHeader = validateIssueExists(id);
184 193 if (ObjUtil.notEqual(MesWmProductionIssueStatusEnum.PREPARE.getStatus(), issueHeader.getStatus())) {
185   -// throw exception(WM_ISSUE_STATUS_INVALID);
186   - throw new IllegalArgumentException(); // TODO @芋艿:晚点优化下;
  194 + throw exception(WM_PRODUCTION_ISSUE_STATUS_INVALID);
187 195 }
188 196 return issueHeader;
189 197 }
... ...