Commit dc0874b76ca47f4fbefd8f630a46c5f77f6b341d

Authored by YunaiV
1 parent 28d0cfd0

✨ feat(validation): 增加字段长度限制和必填校验

在多个请求对象中添加了字段长度限制和必填校验,以确保数据的有效性和一致性。具体修改包括:
- MesTmToolTypeSaveReqVO 和 MesTmToolSaveReqVO 中的 code 和 name 字段增加长度限制
- MesWmItemConsumeLinePageReqVO 中的 feedbackId 字段增加必填校验
- MesMdWorkstationToolService 中新增获取指定工具类型工装夹具资源数量的方法
- ErrorCodeConstants 中新增工具类型被工作站工装资源引用的错误码

这些改动旨在提升数据的完整性和系统的健壮性
Showing 15 changed files with 233 additions and 17 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/vo/MesTmToolSaveReqVO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
5 5 import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
6 7 import lombok.Data;
7 8  
8 9 import java.time.LocalDateTime;
... ... @@ -16,16 +17,20 @@ public class MesTmToolSaveReqVO {
16 17  
17 18 @Schema(description = "工具编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "T-001")
18 19 @NotEmpty(message = "工具编码不能为空")
  20 + @Size(max = 64, message = "工具编码长度不能超过 64 个字符")
19 21 private String code;
20 22  
21 23 @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "5mm 铣刀")
22 24 @NotEmpty(message = "工具名称不能为空")
  25 + @Size(max = 255, message = "工具名称长度不能超过 255 个字符")
23 26 private String name;
24 27  
25 28 @Schema(description = "品牌", example = "三菱")
  29 + @Size(max = 255, message = "品牌长度不能超过 255 个字符")
26 30 private String brand;
27 31  
28 32 @Schema(description = "型号规格", example = "M5-100")
  33 + @Size(max = 255, message = "型号规格长度不能超过 255 个字符")
29 34 private String spec;
30 35  
31 36 @Schema(description = "工具类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
... ... @@ -36,7 +41,8 @@ public class MesTmToolSaveReqVO {
36 41 @NotNull(message = "数量不能为空")
37 42 private Integer quantity;
38 43  
39   - @Schema(description = "可用数量", example = "1")
  44 + @Schema(description = "可用数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  45 + @NotNull(message = "可用数量不能为空")
40 46 private Integer availableQuantity;
41 47  
42 48 @Schema(description = "保养维护类型", example = "1")
... ... @@ -53,6 +59,7 @@ public class MesTmToolSaveReqVO {
53 59 private Integer status;
54 60  
55 61 @Schema(description = "备注", example = "备注")
  62 + @Size(max = 500, message = "备注长度不能超过 500 个字符")
56 63 private String remark;
57 64  
58   - }
  65 +}
59 66 \ No newline at end of file
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/vo/type/MesTmToolTypeSaveReqVO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
5 5 import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
6 7 import lombok.Data;
7 8  
8 9 @Schema(description = "管理后台 - MES 工具类型新增/修改 Request VO")
... ... @@ -14,10 +15,12 @@ public class MesTmToolTypeSaveReqVO {
14 15  
15 16 @Schema(description = "类型编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "TT-001")
16 17 @NotEmpty(message = "类型编码不能为空")
  18 + @Size(max = 64, message = "类型编码长度不能超过 64 个字符")
17 19 private String code;
18 20  
19 21 @Schema(description = "类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "铣刀")
20 22 @NotEmpty(message = "类型名称不能为空")
  23 + @Size(max = 255, message = "类型名称长度不能超过 255 个字符")
21 24 private String name;
22 25  
23 26 @Schema(description = "是否编码管理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
... ... @@ -31,6 +34,7 @@ public class MesTmToolTypeSaveReqVO {
31 34 private Integer maintenPeriod;
32 35  
33 36 @Schema(description = "备注", example = "备注")
  37 + @Size(max = 500, message = "备注长度不能超过 500 个字符")
34 38 private String remark;
35 39  
36   - }
  40 +}
37 41 \ No newline at end of file
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemconsume/vo/MesWmItemConsumeLinePageReqVO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.itemconsume.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
5 6 import lombok.Data;
6 7 import lombok.EqualsAndHashCode;
7 8 import lombok.ToString;
... ... @@ -13,6 +14,7 @@ import lombok.ToString;
13 14 public class MesWmItemConsumeLinePageReqVO extends PageParam {
14 15  
15 16 @Schema(description = "报工记录编号", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + @NotNull(message = "报工记录编号不能为空")
16 18 private Long feedbackId;
17 19  
18 20 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/workstation/MesMdWorkstationToolMapper.java
... ... @@ -29,4 +29,8 @@ public interface MesMdWorkstationToolMapper extends BaseMapperX<MesMdWorkstation
29 29 delete(MesMdWorkstationToolDO::getWorkstationId, workstationId);
30 30 }
31 31  
  32 + default Long selectCountByToolTypeId(Long toolTypeId) {
  33 + return selectCount(MesMdWorkstationToolDO::getToolTypeId, toolTypeId);
  34 + }
  35 +
32 36 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
... ... @@ -217,6 +217,7 @@ public interface ErrorCodeConstants {
217 217 ErrorCode TM_TOOL_TYPE_CODE_DUPLICATE = new ErrorCode(1_040_400_001, "工具类型编码已存在");
218 218 ErrorCode TM_TOOL_TYPE_NAME_DUPLICATE = new ErrorCode(1_040_400_002, "工具类型名称已存在");
219 219 ErrorCode TM_TOOL_TYPE_HAS_TOOL = new ErrorCode(1_040_400_003, "该工具类型下存在工具,无法删除");
  220 + ErrorCode TM_TOOL_TYPE_HAS_WORKSTATION_TOOL = new ErrorCode(1_040_400_004, "该工具类型已被工作站工装资源引用,无法删除");
220 221  
221 222 // ========== MES 工具管理-工具台账(1-040-401-000) ==========
222 223 ErrorCode TM_TOOL_NOT_EXISTS = new ErrorCode(1_040_401_000, "工具不存在");
... ... @@ -362,6 +363,7 @@ public interface ErrorCodeConstants {
362 363 ErrorCode QC_OQC_QUANTITY_MISMATCH = new ErrorCode(1_040_607_004, "合格品与不合格品数量之和须等于检测数量");
363 364 ErrorCode QC_OQC_NO_TEMPLATE = new ErrorCode(1_040_607_005, "当前产品未配置 OQC 检测模板");
364 365 ErrorCode QC_OQC_CHECK_RESULT_EMPTY = new ErrorCode(1_040_607_006, "完成检验单前,检测结果必须填写");
  366 +
365 367 // ========== MES 质量管理-出货检验行(1-040-607-100) ==========
366 368 ErrorCode QC_OQC_LINE_NOT_EXISTS = new ErrorCode(1_040_607_100, "出货检验行不存在");
367 369  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/workstation/MesMdWorkstationToolService.java
... ... @@ -50,4 +50,12 @@ public interface MesMdWorkstationToolService {
50 50 */
51 51 void deleteWorkstationToolByWorkstationId(Long workstationId);
52 52  
  53 + /**
  54 + * 获得指定工具类型的工装夹具资源数量
  55 + *
  56 + * @param toolTypeId 工具类型编号
  57 + * @return 数量
  58 + */
  59 + Long getWorkstationToolCountByToolTypeId(Long toolTypeId);
  60 +
53 61 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/workstation/MesMdWorkstationToolServiceImpl.java
... ... @@ -89,4 +89,9 @@ public class MesMdWorkstationToolServiceImpl implements MesMdWorkstationToolServ
89 89 workstationToolMapper.deleteByWorkstationId(workstationId);
90 90 }
91 91  
  92 + @Override
  93 + public Long getWorkstationToolCountByToolTypeId(Long toolTypeId) {
  94 + return workstationToolMapper.selectCountByToolTypeId(toolTypeId);
  95 + }
  96 +
92 97 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImpl.java
... ... @@ -82,11 +82,14 @@ public class MesQcIndicatorResultServiceImpl implements MesQcIndicatorResultServ
82 82 @Override
83 83 @Transactional(rollbackFor = Exception.class)
84 84 public void updateIndicatorResult(MesQcIndicatorResultSaveReqVO updateReqVO) {
85   - // 1. 校验存在
  85 + // 1.1 校验存在
86 86 validateIndicatorResultExists(updateReqVO.getId());
  87 + // 1.2 校验所有明细的 indicatorId 是否存在
  88 + validateIndicatorIds(updateReqVO.getItems());
87 89  
88   - // 2.1 更新主表
  90 + // 2.1 更新主表(锁定 qcId/qcType/itemId,不允许改挂到其他质检单)
89 91 MesQcIndicatorResultDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIndicatorResultDO.class);
  92 + updateObj.setQcId(null).setQcType(null).setItemId(null);
90 93 resultMapper.updateById(updateObj);
91 94 // 2.2 批量更新明细
92 95 List<MesQcIndicatorResultDetailDO> details = BeanUtils.toBean(updateReqVO.getItems(),
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
... ... @@ -283,7 +283,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
283 283 * 推导工序 ID
284 284 *
285 285 * <p>通过产品 → 工艺路线产品 → 工艺路线 → 校验工位工序是否在该路线中
286   - * <p>如果工位的工序在路线中,则返回该 processId;否则返回 null
  286 + * <p>如果工位的工序在路线中,则返回该 processId;否则返回 null(与 KTG 保持一致)
287 287 *
288 288 * @param productId 产品 ID(工单产品)
289 289 * @param workstationProcessId 工位关联的工序 ID
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
... ... @@ -221,8 +221,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService {
221 221 return null;
222 222 }
223 223 if (Objects.equals(sourceDocType, MesBizTypeConstants.WM_PRODUCT_SALES)) {
224   - MesWmProductSalesLineDO salesLine = productSalesLineService.getProductSalesLine(sourceLineId);
225   - if (salesLine != null && salesLine.getSalesId() != null) {
  224 + MesWmProductSalesLineDO salesLine = productSalesLineService.validateProductSalesLineExists(sourceLineId);
  225 + if (salesLine.getSalesId() != null) {
226 226 MesWmProductSalesDO sales = productSalesService.getProductSales(salesLine.getSalesId());
227 227 return sales != null ? sales.getCode() : null;
228 228 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolTypeServiceImpl.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type.MesTmToolTyp
7 7 import cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type.MesTmToolTypeSaveReqVO;
8 8 import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolTypeDO;
9 9 import cn.iocoder.yudao.module.mes.dal.mysql.tm.tool.MesTmToolTypeMapper;
  10 +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationToolService;
10 11 import jakarta.annotation.Resource;
11 12 import org.springframework.context.annotation.Lazy;
12 13 import org.springframework.stereotype.Service;
... ... @@ -37,6 +38,10 @@ public class MesTmToolTypeServiceImpl implements MesTmToolTypeService {
37 38 @Lazy
38 39 private MesTmToolService toolService;
39 40  
  41 + @Resource
  42 + @Lazy
  43 + private MesMdWorkstationToolService workstationToolService;
  44 +
40 45 @Override
41 46 public Long createToolType(MesTmToolTypeSaveReqVO createReqVO) {
42 47 // 校验数据
... ... @@ -75,6 +80,10 @@ public class MesTmToolTypeServiceImpl implements MesTmToolTypeService {
75 80 if (toolService.getToolCountByToolTypeId(id) > 0) {
76 81 throw exception(TM_TOOL_TYPE_HAS_TOOL);
77 82 }
  83 + // 校验是否被工作站工装资源引用
  84 + if (workstationToolService.getWorkstationToolCountByToolTypeId(id) > 0) {
  85 + throw exception(TM_TOOL_TYPE_HAS_WORKSTATION_TOOL);
  86 + }
78 87  
79 88 // 删除
80 89 toolTypeMapper.deleteById(id);
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
... ... @@ -39,6 +39,11 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
39 39 @Slf4j
40 40 public class MesWmBatchServiceImpl implements MesWmBatchService {
41 41  
  42 + /**
  43 + * 批次追溯最大递归深度,防止极端场景下性能问题
  44 + */
  45 + private static final int MAX_TRACE_DEPTH = 20;
  46 +
42 47 @Resource
43 48 private MesWmBatchMapper batchMapper;
44 49 @Resource
... ... @@ -196,11 +201,11 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
196 201  
197 202 @Override
198 203 public List<MesWmBatchDO> getForwardBatchList(String code) {
199   - return getForwardBatchList(code, new HashSet<>());
  204 + return getForwardBatchList(code, new HashSet<>(), 0);
200 205 }
201 206  
202   - private List<MesWmBatchDO> getForwardBatchList(String code, Set<String> visited) {
203   - if (code == null || !visited.add(code)) {
  207 + private List<MesWmBatchDO> getForwardBatchList(String code, Set<String> visited, int depth) {
  208 + if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) {
204 209 return new ArrayList<>();
205 210 }
206 211 List<MesWmBatchDO> list = batchMapper.selectListByForward(code);
... ... @@ -210,18 +215,18 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
210 215 // 继续递归查询下游批次
211 216 List<MesWmBatchDO> results = new ArrayList<>(list);
212 217 for (MesWmBatchDO batch : list) {
213   - results.addAll(getForwardBatchList(batch.getCode(), visited));
  218 + results.addAll(getForwardBatchList(batch.getCode(), visited, depth + 1));
214 219 }
215 220 return results;
216 221 }
217 222  
218 223 @Override
219 224 public List<MesWmBatchDO> getBackwardBatchList(String code) {
220   - return getBackwardBatchList(code, new HashSet<>());
  225 + return getBackwardBatchList(code, new HashSet<>(), 0);
221 226 }
222 227  
223   - private List<MesWmBatchDO> getBackwardBatchList(String code, Set<String> visited) {
224   - if (code == null || !visited.add(code)) {
  228 + private List<MesWmBatchDO> getBackwardBatchList(String code, Set<String> visited, int depth) {
  229 + if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) {
225 230 return new ArrayList<>();
226 231 }
227 232 List<MesWmBatchDO> list = batchMapper.selectListByBackward(code);
... ... @@ -231,7 +236,7 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
231 236 // 继续递归查询上游批次
232 237 List<MesWmBatchDO> results = new ArrayList<>(list);
233 238 for (MesWmBatchDO batch : list) {
234   - results.addAll(getBackwardBatchList(batch.getCode(), visited));
  239 + results.addAll(getBackwardBatchList(batch.getCode(), visited, depth + 1));
235 240 }
236 241 return results;
237 242 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineService.java
... ... @@ -69,6 +69,14 @@ public interface MesWmProductSalesLineService {
69 69 PageResult<MesWmProductSalesLineDO> getProductSalesLinePage(MesWmProductSalesLinePageReqVO pageReqVO);
70 70  
71 71 /**
  72 + * 校验销售出库单行存在
  73 + *
  74 + * @param id 编号
  75 + * @return 销售出库单行
  76 + */
  77 + MesWmProductSalesLineDO validateProductSalesLineExists(Long id);
  78 +
  79 + /**
72 80 * OQC 检验完成后,更新销售出库单行的 OQC 质检状态
73 81 *
74 82 * @param id 行 ID
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
... ... @@ -124,7 +124,8 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
124 124 return productSalesLineMapper.selectPage(pageReqVO);
125 125 }
126 126  
127   - private MesWmProductSalesLineDO validateProductSalesLineExists(Long id) {
  127 + @Override
  128 + public MesWmProductSalesLineDO validateProductSalesLineExists(Long id) {
128 129 MesWmProductSalesLineDO line = productSalesLineMapper.selectById(id);
129 130 if (line == null) {
130 131 throw exception(WM_PRODUCT_SALES_LINE_NOT_EXISTS);
... ...
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
... ... @@ -8,6 +8,9 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
8 8 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
9 9 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
10 10 import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
  11 +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
  12 +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
  13 +import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
11 14 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
12 15 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
13 16 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
... ... @@ -54,6 +57,12 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
54 57 private AdminUserApi adminUserApi;
55 58 @MockitoBean
56 59 private MesProFeedbackService feedbackService;
  60 + @MockitoBean
  61 + private MesProRouteProductService routeProductService;
  62 + @MockitoBean
  63 + private MesProRouteProcessService routeProcessService;
  64 + @MockitoBean
  65 + private MesProTaskService taskService;
57 66  
58 67 @Test
59 68 public void testFinishIpqc_writeBack_feedback() {
... ... @@ -211,4 +220,153 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
211 220 assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId()));
212 221 }
213 222  
  223 + // ==================== processId 推导行为测试 ====================
  224 +
  225 + /**
  226 + * 工位有工序 + 该工序在产品工艺路线中 → processId 应该被设置为工位工序
  227 + */
  228 + @Test
  229 + public void testCreateIpqc_processId_workstationHasProcess() {
  230 + // 准备参数
  231 + Long workstationId = randomLongId();
  232 + Long workOrderId = randomLongId();
  233 + Long productId = randomLongId();
  234 + Long processId = randomLongId();
  235 + Long routeId = randomLongId();
  236 + Long templateId = randomLongId();
  237 +
  238 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  239 + reqVO.setCode("IPQC-TEST-001");
  240 + reqVO.setWorkstationId(workstationId);
  241 + reqVO.setWorkOrderId(workOrderId);
  242 + reqVO.setInspectorUserId(randomLongId());
  243 +
  244 + // mock 工位返回有工序
  245 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  246 + workstation.setId(workstationId);
  247 + workstation.setProcessId(processId);
  248 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  249 + // mock 工单
  250 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  251 + workOrder.setId(workOrderId);
  252 + workOrder.setProductId(productId);
  253 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  254 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  255 + // mock 模板匹配
  256 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  257 + templateItem.setTemplateId(templateId);
  258 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  259 + // mock 工艺路线
  260 + MesProRouteProductDO routeProduct = new MesProRouteProductDO();
  261 + routeProduct.setRouteId(routeId);
  262 + when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
  263 + // mock 工序在路线中
  264 + when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
  265 + .thenReturn(new MesProRouteProcessDO());
  266 +
  267 + // 调用
  268 + Long ipqcId = ipqcService.createIpqc(reqVO);
  269 +
  270 + // 断言:processId 应该是工位工序
  271 + MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
  272 + assertEquals(processId, ipqc.getProcessId());
  273 + }
  274 +
  275 + /**
  276 + * 工位没有工序(processId=null)→ processId 应该留 null(与 KTG 对齐,不回退到任务工序)
  277 + */
  278 + @Test
  279 + public void testCreateIpqc_processId_workstationNoProcess_shouldBeNull() {
  280 + // 准备参数
  281 + Long workstationId = randomLongId();
  282 + Long workOrderId = randomLongId();
  283 + Long productId = randomLongId();
  284 + Long taskId = randomLongId();
  285 + Long templateId = randomLongId();
  286 +
  287 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  288 + reqVO.setCode("IPQC-TEST-002");
  289 + reqVO.setWorkstationId(workstationId);
  290 + reqVO.setWorkOrderId(workOrderId);
  291 + reqVO.setTaskId(taskId); // 故意传入 taskId,验证不会回退
  292 + reqVO.setInspectorUserId(randomLongId());
  293 +
  294 + // mock 工位没有工序
  295 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  296 + workstation.setId(workstationId);
  297 + workstation.setProcessId(null); // 无工序
  298 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  299 + // mock 工单
  300 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  301 + workOrder.setId(workOrderId);
  302 + workOrder.setProductId(productId);
  303 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  304 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  305 + // mock 模板匹配
  306 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  307 + templateItem.setTemplateId(templateId);
  308 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  309 +
  310 + // 调用
  311 + Long ipqcId = ipqcService.createIpqc(reqVO);
  312 +
  313 + // 断言:processId 应该是 null,即使传入了 taskId 也不应该回退
  314 + MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
  315 + assertNull(ipqc.getProcessId(),
  316 + "工位无工序时 processId 应为 null(与 KTG 对齐),不应回退到任务工序");
  317 + // 断言:不应该读取任务的 processId
  318 + verify(routeProductService, never()).getRouteProductByItemId(any());
  319 + }
  320 +
  321 + /**
  322 + * 工位有工序但该工序不在产品工艺路线中 → processId 应该留 null
  323 + */
  324 + @Test
  325 + public void testCreateIpqc_processId_processNotInRoute() {
  326 + // 准备参数
  327 + Long workstationId = randomLongId();
  328 + Long workOrderId = randomLongId();
  329 + Long productId = randomLongId();
  330 + Long processId = randomLongId();
  331 + Long routeId = randomLongId();
  332 + Long templateId = randomLongId();
  333 +
  334 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  335 + reqVO.setCode("IPQC-TEST-003");
  336 + reqVO.setWorkstationId(workstationId);
  337 + reqVO.setWorkOrderId(workOrderId);
  338 + reqVO.setInspectorUserId(randomLongId());
  339 +
  340 + // mock 工位有工序
  341 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  342 + workstation.setId(workstationId);
  343 + workstation.setProcessId(processId);
  344 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  345 + // mock 工单
  346 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  347 + workOrder.setId(workOrderId);
  348 + workOrder.setProductId(productId);
  349 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  350 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  351 + // mock 模板匹配
  352 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  353 + templateItem.setTemplateId(templateId);
  354 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  355 + // mock 工艺路线存在
  356 + MesProRouteProductDO routeProduct = new MesProRouteProductDO();
  357 + routeProduct.setRouteId(routeId);
  358 + when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
  359 + // mock 工序不在路线中
  360 + when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
  361 + .thenReturn(null);
  362 +
  363 + // 调用
  364 + Long ipqcId = ipqcService.createIpqc(reqVO);
  365 +
  366 + // 断言:processId 应该是 null
  367 + MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
  368 + assertNull(ipqc.getProcessId());
  369 + }
  370 +
214 371 }
  372 +
... ...