Commit 24b5a79a1ddc3ea6501f19c0155b828bda518e68

Authored by YunaiV
1 parent 5c9cbe75

✨ feat(mes): 添加取消外协发料单功能

实现取消外协发料单的接口,支持在特定状态下取消发料单。新增相关的业务逻辑和错误处理,确保只有在允许的状态下才能执行取消操作。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 8 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
11 -import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueExcelVO;  
12 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssuePageReqVO; 11 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssuePageReqVO;
13 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueRespVO; 12 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueRespVO;
14 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueSaveReqVO; 13 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueSaveReqVO;
@@ -105,8 +104,8 @@ public class MesWmOutsourceIssueController { @@ -105,8 +104,8 @@ public class MesWmOutsourceIssueController {
105 HttpServletResponse response) throws IOException { 104 HttpServletResponse response) throws IOException {
106 pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); 105 pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
107 PageResult<MesWmOutsourceIssueDO> pageResult = outsourceIssueService.getOutsourceIssuePage(pageReqVO); 106 PageResult<MesWmOutsourceIssueDO> pageResult = outsourceIssueService.getOutsourceIssuePage(pageReqVO);
108 - ExcelUtils.write(response, "外协发料单.xls", "数据", MesWmOutsourceIssueExcelVO.class,  
109 - BeanUtils.toBean(pageResult.getList(), MesWmOutsourceIssueExcelVO.class)); 107 + ExcelUtils.write(response, "外协发料单.xls", "数据", MesWmOutsourceIssueRespVO.class,
  108 + buildRespVOList(pageResult.getList()));
110 } 109 }
111 110
112 @PutMapping("/submit") 111 @PutMapping("/submit")
@@ -138,6 +137,17 @@ public class MesWmOutsourceIssueController { @@ -138,6 +137,17 @@ public class MesWmOutsourceIssueController {
138 137
139 // DONE @AI:需要有 checkQuantity 类似的接口;(AI 未修复原因:需要明确业务逻辑,checkQuantity 接口需要产品经理确认具体校验规则) 138 // DONE @AI:需要有 checkQuantity 类似的接口;(AI 未修复原因:需要明确业务逻辑,checkQuantity 接口需要产品经理确认具体校验规则)
140 139
  140 + @PutMapping("/cancel")
  141 + @Operation(summary = "取消外协发料单")
  142 + @Parameter(name = "id", description = "编号", required = true)
  143 + @PreAuthorize("@ss.hasPermission('mes:wm-outsource-issue:update')")
  144 + public CommonResult<Boolean> cancelOutsourceIssue(@RequestParam("id") Long id) {
  145 + outsourceIssueService.cancelOutsourceIssue(id);
  146 + return success(true);
  147 + }
  148 +
  149 + // DONE @AI:需要有 checkQuantity 类似的接口;(AI 未修复原因:需要明确业务逻辑,checkQuantity 接口需要产品经理确认具体校验规则)
  150 +
141 // ==================== 拼接 VO ==================== 151 // ==================== 拼接 VO ====================
142 152
143 private List<MesWmOutsourceIssueRespVO> buildRespVOList(List<MesWmOutsourceIssueDO> list) { 153 private List<MesWmOutsourceIssueRespVO> buildRespVOList(List<MesWmOutsourceIssueDO> list) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/MesWmOutsourceIssueExcelVO.java deleted
1 -package cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo;  
2 -  
3 -import cn.idev.excel.annotation.ExcelIgnoreUnannotated;  
4 -import cn.idev.excel.annotation.ExcelProperty;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import lombok.Data;  
7 -  
8 -import java.time.LocalDateTime;  
9 -  
10 -// TODO @AI:删除掉,融合到 MesWmOutsourceIssueRespVO 中,保持一个 VO 就好  
11 -@Schema(description = "管理后台 - MES 外协发料单 Excel VO")  
12 -@Data  
13 -@ExcelIgnoreUnannotated  
14 -public class MesWmOutsourceIssueExcelVO {  
15 -  
16 - @ExcelProperty("发料单ID")  
17 - private Long id;  
18 -  
19 - @ExcelProperty("发料单编号")  
20 - private String code;  
21 -  
22 - @ExcelProperty("发料单名称")  
23 - private String name;  
24 -  
25 - @ExcelProperty("供应商编码")  
26 - private String vendorCode;  
27 -  
28 - @ExcelProperty("供应商名称")  
29 - private String vendorName;  
30 -  
31 - @ExcelProperty("生产工单编码")  
32 - private String workOrderCode;  
33 -  
34 - @ExcelProperty("生产工单名称")  
35 - private String workOrderName;  
36 -  
37 - @ExcelProperty("发料日期")  
38 - private LocalDateTime issueDate;  
39 -  
40 - @ExcelProperty("单据状态")  
41 - private Integer status;  
42 -  
43 - @ExcelProperty("备注")  
44 - private String remark;  
45 -  
46 - @ExcelProperty("创建时间")  
47 - private LocalDateTime createTime;  
48 -  
49 -}  
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/MesWmOutsourceIssuePageReqVO.java
@@ -29,16 +29,8 @@ public class MesWmOutsourceIssuePageReqVO extends PageParam { @@ -29,16 +29,8 @@ public class MesWmOutsourceIssuePageReqVO extends PageParam {
29 @Schema(description = "生产工单ID", example = "1") 29 @Schema(description = "生产工单ID", example = "1")
30 private Long workOrderId; 30 private Long workOrderId;
31 31
32 - // TODO @AI:这个字段,去掉筛选  
33 - @Schema(description = "生产工单编码", example = "WO202603020001")  
34 - private String workOrderCode;  
35 -  
36 @Schema(description = "发料日期") 32 @Schema(description = "发料日期")
37 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 33 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
38 private LocalDateTime[] issueDate; 34 private LocalDateTime[] issueDate;
39 35
40 - // TODO @AI:这个字段,去掉筛选  
41 - @Schema(description = "单据状态", example = "0")  
42 - private Integer status;  
43 -  
44 } 36 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -360,6 +360,7 @@ public interface ErrorCodeConstants { @@ -360,6 +360,7 @@ public interface ErrorCodeConstants {
360 ErrorCode WM_OUTSOURCE_ISSUE_QUANTITY_MISMATCH = new ErrorCode(1_040_707_004, "发料单行数量与明细数量不一致"); 360 ErrorCode WM_OUTSOURCE_ISSUE_QUANTITY_MISMATCH = new ErrorCode(1_040_707_004, "发料单行数量与明细数量不一致");
361 ErrorCode WM_OUTSOURCE_ISSUE_STATUS_NOT_APPROVING = new ErrorCode(1_040_707_005, "只有待拣货状态才允许此操作"); 361 ErrorCode WM_OUTSOURCE_ISSUE_STATUS_NOT_APPROVING = new ErrorCode(1_040_707_005, "只有待拣货状态才允许此操作");
362 ErrorCode WM_OUTSOURCE_ISSUE_STATUS_NOT_APPROVED = new ErrorCode(1_040_707_006, "只有待执行出库状态才允许此操作"); 362 ErrorCode WM_OUTSOURCE_ISSUE_STATUS_NOT_APPROVED = new ErrorCode(1_040_707_006, "只有待执行出库状态才允许此操作");
  363 + ErrorCode WM_OUTSOURCE_ISSUE_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_707_007, "已完成或已取消的发料单不允许取消");
363 ErrorCode WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_707_100, "外协发料单行不存在"); 364 ErrorCode WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_707_100, "外协发料单行不存在");
364 ErrorCode WM_OUTSOURCE_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_707_200, "外协发料单明细不存在"); 365 ErrorCode WM_OUTSOURCE_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_707_200, "外协发料单明细不存在");
365 366
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmOutsourceIssueStatusEnum.java
@@ -29,25 +29,25 @@ public enum MesWmOutsourceIssueStatusEnum implements ArrayValuable&lt;Integer&gt; { @@ -29,25 +29,25 @@ public enum MesWmOutsourceIssueStatusEnum implements ArrayValuable&lt;Integer&gt; {
29 /** 29 /**
30 * 待拣货 30 * 待拣货
31 * 31 *
32 - * 对应方法:TODO 待实现 32 + * 对应方法:{@link MesWmOutsourceIssueService#submitOutsourceIssue(Long)}
33 */ 33 */
34 APPROVING(MesOrderStatusConstants.APPROVING, "待拣货"), 34 APPROVING(MesOrderStatusConstants.APPROVING, "待拣货"),
35 /** 35 /**
36 * 待执行出库 36 * 待执行出库
37 * 37 *
38 - * 对应方法:TODO 待实现 38 + * 对应方法:{@link MesWmOutsourceIssueService#stockOutsourceIssue(Long)}
39 */ 39 */
40 APPROVED(MesOrderStatusConstants.APPROVED, "待执行出库"), 40 APPROVED(MesOrderStatusConstants.APPROVED, "待执行出库"),
41 /** 41 /**
42 * 已完成 42 * 已完成
43 * 43 *
44 - * 对应方法:{@link MesWmOutsourceIssueService#executeOutsourceIssue(Long)} 44 + * 对应方法:{@link MesWmOutsourceIssueService#finishOutsourceIssue(Long)}
45 */ 45 */
46 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"), 46 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"),
47 /** 47 /**
48 * 已取消 48 * 已取消
49 * 49 *
50 - * 对应方法:TODO 待实现 50 + * 对应方法:{@link MesWmOutsourceIssueService#cancelOutsourceIssue(Long)}
51 */ 51 */
52 CANCELLED(MesOrderStatusConstants.CANCELLED, "已取消"); 52 CANCELLED(MesOrderStatusConstants.CANCELLED, "已取消");
53 53
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueService.java
@@ -72,4 +72,11 @@ public interface MesWmOutsourceIssueService { @@ -72,4 +72,11 @@ public interface MesWmOutsourceIssueService {
72 */ 72 */
73 void finishOutsourceIssue(Long id); 73 void finishOutsourceIssue(Long id);
74 74
  75 + /**
  76 + * 取消外协发料单
  77 + *
  78 + * @param id 编号
  79 + */
  80 + void cancelOutsourceIssue(Long id);
  81 +
75 } 82 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java
@@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.mes.service.wm.outsourceissue; @@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.mes.service.wm.outsourceissue;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
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.CollectionUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
7 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssuePageReqVO; 9 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssuePageReqVO;
8 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueSaveReqVO; 10 import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueSaveReqVO;
9 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourceissue.MesWmOutsourceIssueDetailDO; 11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourceissue.MesWmOutsourceIssueDetailDO;
@@ -20,7 +22,6 @@ import org.springframework.validation.annotation.Validated; @@ -20,7 +22,6 @@ import org.springframework.validation.annotation.Validated;
20 import java.math.BigDecimal; 22 import java.math.BigDecimal;
21 import java.util.List; 23 import java.util.List;
22 import java.util.Map; 24 import java.util.Map;
23 -import java.util.stream.Collectors;  
24 25
25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; 27 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -121,7 +122,22 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic @@ -121,7 +122,22 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic
121 } 122 }
122 // 1.2 校验数量一致性(行数量 = 明细数量之和) 123 // 1.2 校验数量一致性(行数量 = 明细数量之和)
123 List<MesWmOutsourceIssueLineDO> lines = outsourceIssueLineService.getOutsourceIssueLineListByIssueId(id); 124 List<MesWmOutsourceIssueLineDO> lines = outsourceIssueLineService.getOutsourceIssueLineListByIssueId(id);
124 - validateQuantityMatch(id, lines); 125 + if (CollUtil.isNotEmpty(lines)) {
  126 + // 批量查询所有明细
  127 + List<MesWmOutsourceIssueDetailDO> allDetails = outsourceIssueDetailService.getOutsourceIssueDetailListByIssueId(id);
  128 + Map<Long, List<MesWmOutsourceIssueDetailDO>> detailMap = CollectionUtils.convertMultiMap(
  129 + allDetails, MesWmOutsourceIssueDetailDO::getLineId);
  130 + // 检查每行的明细数量
  131 + for (MesWmOutsourceIssueLineDO line : lines) {
  132 + List<MesWmOutsourceIssueDetailDO> details = detailMap.getOrDefault(line.getId(), List.of());
  133 + BigDecimal totalDetailQuantity = CollectionUtils.getSumValue(details,
  134 + MesWmOutsourceIssueDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
  135 + // 对比行数量与明细总数量,不满足直接抛出
  136 + if (line.getQuantity().compareTo(totalDetailQuantity) != 0) {
  137 + throw exception(WM_OUTSOURCE_ISSUE_QUANTITY_MISMATCH);
  138 + }
  139 + }
  140 + }
125 141
126 // 2. 更新单据状态为待执行出库 142 // 2. 更新单据状态为待执行出库
127 outsourceIssueMapper.updateById(new MesWmOutsourceIssueDO() 143 outsourceIssueMapper.updateById(new MesWmOutsourceIssueDO()
@@ -151,6 +167,23 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic @@ -151,6 +167,23 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic
151 .setId(id).setStatus(MesWmOutsourceIssueStatusEnum.FINISHED.getStatus())); 167 .setId(id).setStatus(MesWmOutsourceIssueStatusEnum.FINISHED.getStatus()));
152 } 168 }
153 169
  170 + @Override
  171 + @Transactional(rollbackFor = Exception.class)
  172 + public void cancelOutsourceIssue(Long id) {
  173 + // 校验存在
  174 + MesWmOutsourceIssueDO issue = validateOutsourceIssueExists(id);
  175 + // 已完成和已取消不允许取消
  176 + if (ObjectUtils.equalsAny(issue.getStatus(),
  177 + MesWmOutsourceIssueStatusEnum.FINISHED.getStatus(),
  178 + MesWmOutsourceIssueStatusEnum.CANCELLED.getStatus())) {
  179 + throw exception(WM_OUTSOURCE_ISSUE_CANCEL_NOT_ALLOWED);
  180 + }
  181 +
  182 + // 取消
  183 + outsourceIssueMapper.updateById(new MesWmOutsourceIssueDO()
  184 + .setId(id).setStatus(MesWmOutsourceIssueStatusEnum.CANCELLED.getStatus()));
  185 + }
  186 +
154 /** 187 /**
155 * 校验外协发料单存在 188 * 校验外协发料单存在
156 */ 189 */
@@ -186,32 +219,4 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic @@ -186,32 +219,4 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic
186 } 219 }
187 } 220 }
188 221
189 - /**  
190 - * 校验数量一致性(行数量 = 明细数量之和)  
191 - */  
192 - // TODO @AI:参考别的模块,不要抽成小方法;  
193 - private void validateQuantityMatch(Long issueId, List<MesWmOutsourceIssueLineDO> lines) {  
194 - // 获取所有明细  
195 - List<MesWmOutsourceIssueDetailDO> details = outsourceIssueDetailService.getOutsourceIssueDetailListByIssueId(issueId);  
196 -  
197 - // 按行ID分组,计算每行的明细数量之和  
198 - Map<Long, BigDecimal> detailQuantityMap = details.stream()  
199 - .collect(Collectors.groupingBy(  
200 - MesWmOutsourceIssueDetailDO::getLineId,  
201 - Collectors.reducing(BigDecimal.ZERO,  
202 - MesWmOutsourceIssueDetailDO::getQuantity,  
203 - BigDecimal::add)  
204 - ));  
205 -  
206 - // 校验每行的数量是否匹配  
207 - for (MesWmOutsourceIssueLineDO line : lines) {  
208 - BigDecimal detailQuantity = detailQuantityMap.getOrDefault(line.getId(), BigDecimal.ZERO);  
209 - if (line.getQuantity().compareTo(detailQuantity) != 0) {  
210 - log.error("[validateQuantityMatch][发料单({}) 行({}) 数量不匹配 - 行数量: {}, 明细数量: {}]",  
211 - issueId, line.getId(), line.getQuantity(), detailQuantity);  
212 - throw exception(WM_OUTSOURCE_ISSUE_QUANTITY_MISMATCH);  
213 - }  
214 - }  
215 - }  
216 -  
217 } 222 }