Commit d415f7740533e5d7883506986f9f979e71d97b42

Authored by YunaiV
1 parent 80183a18

✨ feat(mes): 添加 IPQC 相关错误码和更新反馈处理逻辑

新增 IPQC 过程中的错误码,增强了对来源产出行 ID 的校验逻辑,确保在处理反馈时能够直接定位待检行。此变更提升了系统的健壮性和用户体验。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
... ... @@ -353,6 +353,8 @@ public interface ErrorCodeConstants {
353 353 ErrorCode QC_IPQC_SOURCE_DOC_TYPE_UNKNOWN = new ErrorCode(1_040_604_007, "未知的 IPQC 来源单据类型");
354 354 ErrorCode QC_IPQC_SOURCE_DOC_NO_PENDING_LINE = new ErrorCode(1_040_604_008, "来源报工单不存在待检产出行,无法创建 IPQC");
355 355 ErrorCode QC_IPQC_SOURCE_LINE_NOT_BELONG = new ErrorCode(1_040_604_009, "来源单据行不属于该报工单");
  356 + ErrorCode QC_IPQC_SOURCE_LINE_REQUIRED = new ErrorCode(1_040_604_010, "来源单据类型为报工时,来源产出行 ID 不能为空");
  357 + ErrorCode QC_IPQC_SOURCE_LINE_NOT_PENDING = new ErrorCode(1_040_604_011, "来源产出行不是待检验状态");
356 358 // ========== MES 质量管理-过程检验行(1-040-604-100) ==========
357 359 ErrorCode QC_IPQC_LINE_NOT_EXISTS = new ErrorCode(1_040_604_100, "过程检验行不存在");
358 360  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
... ... @@ -104,13 +104,15 @@ public interface MesProFeedbackService {
104 104 * 并根据检验结果回写合格/不合格/废品数量,同时更新任务/工单的已生产数量。
105 105 *
106 106 * @param feedbackId 报工记录 ID
  107 + * @param sourceLineId 来源产出行 ID(用于直接定位待检行)
107 108 * @param qualifiedQty 合格品数量
108 109 * @param unqualifiedQty 不合格品数量
109 110 * @param laborScrapQty 工废数量
110 111 * @param materialScrapQty 料废数量
111 112 * @param otherScrapQty 其他废品数量
112 113 */
113   - void updateProFeedbackWhenIpqcFinish(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
  114 + void updateProFeedbackWhenIpqcFinish(Long feedbackId, Long sourceLineId,
  115 + BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
114 116 BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty);
115 117  
116 118 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
... ... @@ -313,7 +313,8 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService {
313 313  
314 314 @Override
315 315 @Transactional(rollbackFor = Exception.class)
316   - public void updateProFeedbackWhenIpqcFinish(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
  316 + public void updateProFeedbackWhenIpqcFinish(Long feedbackId, Long sourceLineId,
  317 + BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
317 318 BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty) {
318 319 // 1. 校验报工单存在且为待检验状态
319 320 MesProFeedbackDO feedback = validateFeedbackExists(feedbackId);
... ... @@ -322,7 +323,7 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService {
322 323 }
323 324  
324 325 // 2. 拆分待检产出行(合格/不合格),生成明细,完成产出入库
325   - productProduceService.splitPendingAndFinishProduce(feedbackId, qualifiedQty, unqualifiedQty);
  326 + productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId, qualifiedQty, unqualifiedQty);
326 327  
327 328 // 3. 回写合格/不合格/废品数量,更新状态为已完成
328 329 feedbackMapper.updateById(new MesProFeedbackDO().setId(feedbackId)
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
... ... @@ -221,7 +221,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
221 221 }
222 222  
223 223 if (Objects.equals(ipqc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK)) {
224   - feedbackService.updateProFeedbackWhenIpqcFinish(ipqc.getSourceDocId(),
  224 + feedbackService.updateProFeedbackWhenIpqcFinish(ipqc.getSourceDocId(), ipqc.getSourceLineId(),
225 225 ObjectUtil.defaultIfNull(ipqc.getQualifiedQuantity(), BigDecimal.ZERO),
226 226 ObjectUtil.defaultIfNull(ipqc.getUnqualifiedQuantity(), BigDecimal.ZERO),
227 227 ObjectUtil.defaultIfNull(ipqc.getLaborScrapQuantity(), BigDecimal.ZERO),
... ... @@ -286,20 +286,19 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
286 286 }
287 287 if (Objects.equals(sourceDocType, MesBizTypeConstants.PRO_FEEDBACK)) {
288 288 MesProFeedbackDO feedback = feedbackService.validateFeedbackExists(sourceDocId);
289   - // 校验该报工存在待检产出行
290   - List<MesWmProductProduceLineDO> lines = productProduceLineService
291   - .getProductProduceLineListByFeedbackId(sourceDocId);
292   - boolean hasPending = lines.stream().anyMatch(
293   - l -> Objects.equals(l.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()));
294   - if (!hasPending) {
295   - throw exception(QC_IPQC_SOURCE_DOC_NO_PENDING_LINE);
  289 + // 校验 sourceLineId 必填
  290 + if (sourceLineId == null) {
  291 + throw exception(QC_IPQC_SOURCE_LINE_REQUIRED);
296 292 }
297   - // 校验 sourceLineId 属于该报工的产出行
298   - if (sourceLineId != null) {
299   - boolean belongs = lines.stream().anyMatch(l -> Objects.equals(l.getId(), sourceLineId));
300   - if (!belongs) {
301   - throw exception(QC_IPQC_SOURCE_LINE_NOT_BELONG);
302   - }
  293 + // 校验 sourceLineId 存在,且属于该报工的产出行
  294 + MesWmProductProduceLineDO targetLine = productProduceLineService
  295 + .validateProductProduceLineExists(sourceLineId);
  296 + if (ObjUtil.notEqual(targetLine.getFeedbackId(), sourceDocId)) {
  297 + throw exception(QC_IPQC_SOURCE_LINE_NOT_BELONG);
  298 + }
  299 + // 校验该产出行为待检验状态
  300 + if (ObjUtil.notEqual(targetLine.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus())) {
  301 + throw exception(QC_IPQC_SOURCE_LINE_NOT_PENDING);
303 302 }
304 303 return feedback.getCode();
305 304 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java
... ... @@ -19,6 +19,14 @@ public interface MesWmProductProduceLineService {
19 19 void createProductProduceLine(MesWmProductProduceLineDO line);
20 20  
21 21 /**
  22 + * 校验生产入库单行存在
  23 + *
  24 + * @param id 行 ID
  25 + * @return 行数据
  26 + */
  27 + MesWmProductProduceLineDO validateProductProduceLineExists(Long id);
  28 +
  29 + /**
22 30 * 更新生产入库单行(内部使用)
23 31 *
24 32 * @param line 行数据
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java
... ... @@ -10,6 +10,9 @@ import org.springframework.validation.annotation.Validated;
10 10  
11 11 import java.util.List;
12 12  
  13 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  14 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  15 +
13 16 /**
14 17 * MES 生产入库单行 Service 实现类
15 18 */
... ... @@ -26,6 +29,15 @@ public class MesWmProductProduceLineServiceImpl implements MesWmProductProduceLi
26 29 }
27 30  
28 31 @Override
  32 + public MesWmProductProduceLineDO validateProductProduceLineExists(Long id) {
  33 + MesWmProductProduceLineDO line = productProduceLineMapper.selectById(id);
  34 + if (line == null) {
  35 + throw exception(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS);
  36 + }
  37 + return line;
  38 + }
  39 +
  40 + @Override
29 41 public void updateProductProduceLine(MesWmProductProduceLineDO line) {
30 42 productProduceLineMapper.updateById(line);
31 43 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
... ... @@ -41,9 +41,11 @@ public interface MesWmProductProduceService {
41 41 * 待检产出({@code checkFlag=true})按检验结果拆分行,并执行入库。
42 42 *
43 43 * @param feedbackId 报工记录 ID
  44 + * @param sourceLineId 来源产出行 ID(直接定位待检行)
44 45 * @param qualifiedQty 合格品数量
45 46 * @param unqualifiedQty 不合格品数量
46 47 */
47   - void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty);
  48 + void splitPendingAndFinishProduce(Long feedbackId, Long sourceLineId,
  49 + BigDecimal qualifiedQty, BigDecimal unqualifiedQty);
48 50  
49 51 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
... ... @@ -222,7 +222,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
222 222  
223 223 @Override
224 224 @Transactional(rollbackFor = Exception.class)
225   - public void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty) {
  225 + public void splitPendingAndFinishProduce(Long feedbackId, Long sourceLineId,
  226 + BigDecimal qualifiedQty, BigDecimal unqualifiedQty) {
226 227 // 1.1 查询产出单
227 228 MesWmProductProduceDO produce = productProduceMapper.selectByFeedbackId(feedbackId);
228 229 if (produce == null) {
... ... @@ -233,13 +234,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
233 234 MesWmWarehouseLocationDO virtualLocation = locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION);
234 235 MesWmWarehouseAreaDO virtualArea = areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA);
235 236  
236   - // 2. 查找待检验行(checkFlag=true 时只有一行 PENDING)
237   - List<MesWmProductProduceLineDO> lines = productProduceLineService.getProductProduceLineListByProduceId(produce.getId());
238   - MesWmProductProduceLineDO pendingLine = CollUtil.findOne(lines,
239   - l -> ObjUtil.equal(l.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()));
240   - if (pendingLine == null) {
241   - throw exception(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS);
242   - }
  237 + // 2. 通过 sourceLineId 直接定位待检验行
  238 + MesWmProductProduceLineDO pendingLine = productProduceLineService.validateProductProduceLineExists(sourceLineId);
243 239  
244 240 // 3A. 情况一:存在不合格品数量,需要拆分行
245 241 if (unqualifiedQty != null && unqualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
... ...
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
... ... @@ -61,6 +61,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
61 61 // 准备数据:插入一条待检验状态的报工单
62 62 Long taskId = randomLongId();
63 63 Long workOrderId = randomLongId();
  64 + Long sourceLineId = randomLongId();
64 65 MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
65 66 o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus());
66 67 o.setTaskId(taskId);
... ... @@ -87,13 +88,12 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
87 88 BigDecimal laborScrapQty = BigDecimal.valueOf(5);
88 89 BigDecimal materialScrapQty = BigDecimal.valueOf(10);
89 90 BigDecimal otherScrapQty = BigDecimal.valueOf(5);
90   -
91   - feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
  91 + feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), sourceLineId,
92 92 qualifiedQty, unqualifiedQty, laborScrapQty, materialScrapQty, otherScrapQty);
93 93  
94 94 // 断言 1:调用了 splitPendingAndFinishProduce
95 95 verify(productProduceService).splitPendingAndFinishProduce(
96   - eq(feedback.getId()), eq(qualifiedQty), eq(unqualifiedQty));
  96 + eq(feedback.getId()), eq(sourceLineId), eq(qualifiedQty), eq(unqualifiedQty));
97 97  
98 98 // 断言 2:报工单状态更新为已完成
99 99 MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId());
... ... @@ -137,7 +137,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
137 137 .thenReturn(ListUtil.of(qualifiedLine));
138 138  
139 139 // 调用
140   - feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
  140 + feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
141 141 BigDecimal.valueOf(50), BigDecimal.ZERO,
142 142 BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
143 143  
... ... @@ -156,7 +156,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
156 156 // 调用不存在的 feedbackId,应该抛异常
157 157 Long feedbackId = randomLongId();
158 158 assertThrows(Exception.class, () ->
159   - feedbackService.updateProFeedbackWhenIpqcFinish(feedbackId,
  159 + feedbackService.updateProFeedbackWhenIpqcFinish(feedbackId, randomLongId(),
160 160 BigDecimal.TEN, BigDecimal.ZERO,
161 161 BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
162 162 }
... ... @@ -171,12 +171,12 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
171 171  
172 172 // 调用,应该抛异常
173 173 assertThrows(Exception.class, () ->
174   - feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
  174 + feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
175 175 BigDecimal.TEN, BigDecimal.ZERO,
176 176 BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
177 177  
178 178 // 断言:不应该调用任何产出单方法
179   - verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), any(), any());
  179 + verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), anyLong(), any(), any());
180 180 }
181 181  
182 182 @Test
... ... @@ -189,7 +189,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
189 189  
190 190 // 调用,应该抛异常
191 191 assertThrows(Exception.class, () ->
192   - feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
  192 + feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
193 193 BigDecimal.TEN, BigDecimal.ZERO,
194 194 BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
195 195 }
... ...
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
1 1 package cn.iocoder.yudao.module.mes.service.qc.ipqc;
2 2  
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
3 4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
4 5 import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
5 6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
6 8 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
7 9 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
8 11 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
9 12 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
10 13 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
11 15 import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
12 16 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
13 17 import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
  18 +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
14 19 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
15 20 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
16 21 import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
... ... @@ -19,7 +24,9 @@ import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
19 24 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
20 25 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
21 26 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
  27 +import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
22 28 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
  29 +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
23 30 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
24 31 import jakarta.annotation.Resource;
25 32 import org.junit.jupiter.api.Test;
... ... @@ -30,6 +37,7 @@ import java.math.BigDecimal;
30 37  
31 38 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
32 39 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  40 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
33 41 import static org.junit.jupiter.api.Assertions.*;
34 42 import static org.mockito.Mockito.*;
35 43  
... ... @@ -69,11 +77,16 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
69 77 private MesProRouteProcessService routeProcessService;
70 78 @MockitoBean
71 79 private MesProTaskService taskService;
  80 + @MockitoBean
  81 + private MesQcIndicatorResultService indicatorResultService;
  82 + @MockitoBean
  83 + private MesWmProductProduceLineService productProduceLineService;
72 84  
73 85 @Test
74 86 public void testFinishIpqc_writeBack_feedback() {
75 87 // 准备数据:插入一条草稿状态的 IPQC 单,来源为报工单(PRO_FEEDBACK=304)
76 88 Long sourceDocId = randomLongId();
  89 + Long sourceLineId = randomLongId();
77 90 BigDecimal qualifiedQty = BigDecimal.valueOf(80);
78 91 BigDecimal unqualifiedQty = BigDecimal.valueOf(20);
79 92 BigDecimal laborScrapQty = BigDecimal.valueOf(5);
... ... @@ -85,7 +98,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
85 98 o.setCheckResult(1); // 非空,满足 finishIpqc 的校验
86 99 o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
87 100 o.setSourceDocId(sourceDocId);
88   - o.setSourceLineId(null); // 报工场景下预留不使用
  101 + o.setSourceLineId(sourceLineId);
89 102 o.setQualifiedQuantity(qualifiedQty);
90 103 o.setUnqualifiedQuantity(unqualifiedQty);
91 104 o.setLaborScrapQuantity(laborScrapQty);
... ... @@ -101,10 +114,11 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
101 114 MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
102 115 assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
103 116  
104   - // 断言 2:调用了 feedbackService.completeFeedbackFromIpqc,传递正确参数
  117 + // 断言 2:调用了 feedbackService,传递 sourceLineId
105 118 // 注意:数量经过 DB 存取后 scale 可能变化(例如 80 → 80.00),所以用 any() 匹配
106 119 verify(feedbackService).updateProFeedbackWhenIpqcFinish(
107 120 eq(sourceDocId),
  121 + eq(sourceLineId),
108 122 any(BigDecimal.class),
109 123 any(BigDecimal.class),
110 124 any(BigDecimal.class),
... ... @@ -116,12 +130,13 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
116 130 public void testFinishIpqc_writeBack_feedback_withNullQuantities() {
117 131 // 准备数据:合格品/不合格品/废品数量为 null 的场景,应该 defaultIfNull 为 0
118 132 Long sourceDocId = randomLongId();
  133 + Long sourceLineId = randomLongId();
119 134 MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
120 135 o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
121 136 o.setCheckResult(1);
122 137 o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
123 138 o.setSourceDocId(sourceDocId);
124   - o.setSourceLineId(null);
  139 + o.setSourceLineId(sourceLineId);
125 140 o.setQualifiedQuantity(null);
126 141 o.setUnqualifiedQuantity(null);
127 142 o.setLaborScrapQuantity(null);
... ... @@ -136,6 +151,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
136 151 // 断言:所有数量参数都是 BigDecimal.ZERO(defaultIfNull 处理)
137 152 verify(feedbackService).updateProFeedbackWhenIpqcFinish(
138 153 eq(sourceDocId),
  154 + eq(sourceLineId),
139 155 eq(BigDecimal.ZERO),
140 156 eq(BigDecimal.ZERO),
141 157 eq(BigDecimal.ZERO),
... ... @@ -164,7 +180,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
164 180  
165 181 // 断言 2:不应该调用 feedbackService
166 182 verify(feedbackService, never()).updateProFeedbackWhenIpqcFinish(
167   - anyLong(), any(), any(), any(), any(), any());
  183 + anyLong(), anyLong(), any(), any(), any(), any(), any());
168 184 }
169 185  
170 186 @Test
... ... @@ -308,6 +324,13 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
308 324 workOrder.setProductId(productId);
309 325 when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
310 326 when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  327 + // mock 任务
  328 + MesProTaskDO task = new MesProTaskDO();
  329 + task.setId(taskId);
  330 + task.setWorkOrderId(workOrderId);
  331 + task.setWorkstationId(workstationId);
  332 + task.setItemId(productId);
  333 + when(taskService.validateTaskNotFinished(taskId)).thenReturn(task);
311 334 // mock 模板匹配
312 335 MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
313 336 templateItem.setTemplateId(templateId);
... ... @@ -374,5 +397,200 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
374 397 assertNull(ipqc.getProcessId());
375 398 }
376 399  
377   -}
  400 + // ==================== sourceLineId 校验测试 ====================
  401 +
  402 + /**
  403 + * sourceDocType=PRO_FEEDBACK 时 sourceLineId 为空 → 创建失败
  404 + */
  405 + @Test
  406 + public void testCreateIpqc_sourceLineId_required() {
  407 + // 准备参数
  408 + Long workstationId = randomLongId();
  409 + Long workOrderId = randomLongId();
  410 + Long productId = randomLongId();
  411 + Long feedbackId = randomLongId();
  412 + Long templateId = randomLongId();
  413 +
  414 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  415 + reqVO.setCode("IPQC-SL-001");
  416 + reqVO.setWorkstationId(workstationId);
  417 + reqVO.setWorkOrderId(workOrderId);
  418 + reqVO.setInspectorUserId(randomLongId());
  419 + reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
  420 + reqVO.setSourceDocId(feedbackId);
  421 + reqVO.setSourceLineId(null); // 不传 sourceLineId
  422 +
  423 + // mock
  424 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  425 + workstation.setId(workstationId);
  426 + workstation.setProcessId(null);
  427 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  428 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  429 + workOrder.setId(workOrderId);
  430 + workOrder.setProductId(productId);
  431 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  432 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  433 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  434 + templateItem.setTemplateId(templateId);
  435 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  436 + when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
378 437  
  438 + // 调用,断言异常
  439 + ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
  440 + assertEquals(QC_IPQC_SOURCE_LINE_REQUIRED.getCode(), ex.getCode());
  441 + }
  442 +
  443 + /**
  444 + * sourceLineId 不属于该报工的产出行 → 创建失败
  445 + */
  446 + @Test
  447 + public void testCreateIpqc_sourceLineId_notBelong() {
  448 + // 准备参数
  449 + Long workstationId = randomLongId();
  450 + Long workOrderId = randomLongId();
  451 + Long productId = randomLongId();
  452 + Long feedbackId = randomLongId();
  453 + Long sourceLineId = randomLongId();
  454 + Long templateId = randomLongId();
  455 +
  456 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  457 + reqVO.setCode("IPQC-SL-002");
  458 + reqVO.setWorkstationId(workstationId);
  459 + reqVO.setWorkOrderId(workOrderId);
  460 + reqVO.setInspectorUserId(randomLongId());
  461 + reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
  462 + reqVO.setSourceDocId(feedbackId);
  463 + reqVO.setSourceLineId(sourceLineId);
  464 +
  465 + // mock
  466 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  467 + workstation.setId(workstationId);
  468 + workstation.setProcessId(null);
  469 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  470 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  471 + workOrder.setId(workOrderId);
  472 + workOrder.setProductId(productId);
  473 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  474 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  475 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  476 + templateItem.setTemplateId(templateId);
  477 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  478 + when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
  479 + // mock:产出行存在但 feedbackId 不匹配
  480 + MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
  481 + line.setId(sourceLineId);
  482 + line.setFeedbackId(randomLongId()); // 不同的 feedbackId
  483 + line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
  484 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
  485 +
  486 + // 调用,断言异常
  487 + ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
  488 + assertEquals(QC_IPQC_SOURCE_LINE_NOT_BELONG.getCode(), ex.getCode());
  489 + }
  490 +
  491 + /**
  492 + * sourceLineId 对应的产出行不是待检验状态 → 创建失败
  493 + */
  494 + @Test
  495 + public void testCreateIpqc_sourceLineId_notPending() {
  496 + // 准备参数
  497 + Long workstationId = randomLongId();
  498 + Long workOrderId = randomLongId();
  499 + Long productId = randomLongId();
  500 + Long feedbackId = randomLongId();
  501 + Long sourceLineId = randomLongId();
  502 + Long templateId = randomLongId();
  503 +
  504 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  505 + reqVO.setCode("IPQC-SL-003");
  506 + reqVO.setWorkstationId(workstationId);
  507 + reqVO.setWorkOrderId(workOrderId);
  508 + reqVO.setInspectorUserId(randomLongId());
  509 + reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
  510 + reqVO.setSourceDocId(feedbackId);
  511 + reqVO.setSourceLineId(sourceLineId);
  512 +
  513 + // mock
  514 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  515 + workstation.setId(workstationId);
  516 + workstation.setProcessId(null);
  517 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  518 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  519 + workOrder.setId(workOrderId);
  520 + workOrder.setProductId(productId);
  521 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  522 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  523 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  524 + templateItem.setTemplateId(templateId);
  525 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  526 + when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
  527 + // mock:产出行存在且归属正确,但不是 PENDING 状态
  528 + MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
  529 + line.setId(sourceLineId);
  530 + line.setFeedbackId(feedbackId);
  531 + line.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); // 已合格,非待检
  532 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
  533 +
  534 + // 调用,断言异常
  535 + ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
  536 + assertEquals(QC_IPQC_SOURCE_LINE_NOT_PENDING.getCode(), ex.getCode());
  537 + }
  538 +
  539 + /**
  540 + * sourceLineId 校验全部通过 → 创建成功
  541 + */
  542 + @Test
  543 + public void testCreateIpqc_sourceLineId_success() {
  544 + // 准备参数
  545 + Long workstationId = randomLongId();
  546 + Long workOrderId = randomLongId();
  547 + Long productId = randomLongId();
  548 + Long feedbackId = randomLongId();
  549 + Long sourceLineId = randomLongId();
  550 + Long templateId = randomLongId();
  551 +
  552 + MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
  553 + reqVO.setCode("IPQC-SL-004");
  554 + reqVO.setWorkstationId(workstationId);
  555 + reqVO.setWorkOrderId(workOrderId);
  556 + reqVO.setInspectorUserId(randomLongId());
  557 + reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
  558 + reqVO.setSourceDocId(feedbackId);
  559 + reqVO.setSourceLineId(sourceLineId);
  560 +
  561 + // mock
  562 + MesMdWorkstationDO workstation = new MesMdWorkstationDO();
  563 + workstation.setId(workstationId);
  564 + workstation.setProcessId(null);
  565 + when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
  566 + MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
  567 + workOrder.setId(workOrderId);
  568 + workOrder.setProductId(productId);
  569 + when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
  570 + when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
  571 + MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
  572 + templateItem.setTemplateId(templateId);
  573 + when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
  574 + MesProFeedbackDO feedback = new MesProFeedbackDO();
  575 + feedback.setId(feedbackId);
  576 + feedback.setCode("FB-001");
  577 + when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(feedback);
  578 + // mock:产出行存在、归属正确、PENDING 状态
  579 + MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
  580 + line.setId(sourceLineId);
  581 + line.setFeedbackId(feedbackId);
  582 + line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
  583 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
  584 +
  585 + // 调用
  586 + Long ipqcId = ipqcService.createIpqc(reqVO);
  587 +
  588 + // 断言:创建成功,sourceDocCode 回写
  589 + MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
  590 + assertNotNull(ipqc);
  591 + assertEquals(feedbackId, ipqc.getSourceDocId());
  592 + assertEquals(sourceLineId, ipqc.getSourceLineId());
  593 + assertEquals("FB-001", ipqc.getSourceDocCode());
  594 + }
  595 +
  596 +}
... ...
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java
1 1 package cn.iocoder.yudao.module.mes.service.wm.productproduce;
2 2  
3 3 import cn.hutool.core.collection.ListUtil;
  4 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
6 7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
... ... @@ -27,8 +28,8 @@ import java.math.BigDecimal;
27 28  
28 29 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
29 30 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
30   -import static org.junit.jupiter.api.Assertions.assertEquals;
31   -import static org.junit.jupiter.api.Assertions.assertThrows;
  31 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  32 +import static org.junit.jupiter.api.Assertions.*;
32 33 import static org.mockito.Mockito.*;
33 34  
34 35 /**
... ... @@ -66,6 +67,7 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
66 67 public void testSplitPendingAndFinishProduce_withUnqualified() {
67 68 // 准备数据:插入一个 PREPARE 状态的产出单
68 69 Long feedbackId = randomLongId();
  70 + Long sourceLineId = 100L;
69 71 Long itemId = randomLongId();
70 72 Long batchId = randomLongId();
71 73 String batchCode = "BATCH-001";
... ... @@ -85,19 +87,17 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
85 87 when(locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION)).thenReturn(loc);
86 88 when(areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA)).thenReturn(area);
87 89  
88   - // mock: 返回一条 PENDING 的产出行
  90 + // mock: 通过 sourceLineId 直接定位 PENDING 产出行
89 91 MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
90   - .id(100L).produceId(produce.getId()).feedbackId(feedbackId)
  92 + .id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
91 93 .itemId(itemId).quantity(BigDecimal.valueOf(100))
92 94 .batchId(batchId).batchCode(batchCode)
93 95 .qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
94 96 .build();
95   - when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
96   - .thenReturn(ListUtil.of(pendingLine));
  97 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
  98 + .thenReturn(pendingLine);
97 99  
98   - // mock: finishProductProduce 内部会再查一次行和明细(用于创建库存事务)
99   - // 由于 finishProductProduce 在拆分行后调用,此时行已经变了,但由于 line/detail 是 mock 的,
100   - // 我们需要 mock 已拆分后的行给 finishProductProduce
  100 + // mock: finishProductProduce 内部查行和明细(用于创建库存事务)
101 101 MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
102 102 .id(100L).produceId(produce.getId()).itemId(itemId)
103 103 .quantity(BigDecimal.valueOf(80))
... ... @@ -108,10 +108,8 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
108 108 .quantity(BigDecimal.valueOf(20))
109 109 .qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus())
110 110 .build();
111   - // finishProductProduce 内部第二次调用 getProductProduceLineListByProduceId
112 111 when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
113   - .thenReturn(ListUtil.of(pendingLine)) // 第 1 次:splitPendingAndFinishProduce 查待检行
114   - .thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine)); // 第 2 次:finishProductProduce 查所有行
  112 + .thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine));
115 113  
116 114 // mock: finishProductProduce 中按行查明细
117 115 MesWmProductProduceDetailDO qualifiedDetail = MesWmProductProduceDetailDO.builder()
... ... @@ -124,7 +122,8 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
124 122 .thenReturn(ListUtil.of(unqualifiedDetail));
125 123  
126 124 // 调用
127   - productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(80), BigDecimal.valueOf(20));
  125 + productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
  126 + BigDecimal.valueOf(80), BigDecimal.valueOf(20));
128 127  
129 128 // 断言 1:不合格品行 - 新建了一行
130 129 ArgumentCaptor<MesWmProductProduceLineDO> lineCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class);
... ... @@ -156,6 +155,7 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
156 155 public void testSplitPendingAndFinishProduce_allQualified() {
157 156 // 准备数据:全部合格品场景
158 157 Long feedbackId = randomLongId();
  158 + Long sourceLineId = 200L;
159 159 Long itemId = randomLongId();
160 160  
161 161 MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
... ... @@ -173,30 +173,31 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
173 173 when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
174 174 when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
175 175  
  176 + // mock: 通过 sourceLineId 直接定位 PENDING 产出行
176 177 MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
177   - .id(200L).produceId(produce.getId()).feedbackId(feedbackId)
  178 + .id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
178 179 .itemId(itemId).quantity(BigDecimal.valueOf(50))
179 180 .qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
180 181 .build();
  182 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
  183 + .thenReturn(pendingLine);
181 184  
182   - // 第 1 次查行(拆分阶段)返回 PENDING 行;第 2 次查行(finishProductProduce)返回已更新的合格行
  185 + // mock: finishProductProduce 查行和明细
183 186 MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
184 187 .id(200L).produceId(produce.getId()).itemId(itemId)
185 188 .quantity(BigDecimal.valueOf(50))
186 189 .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
187 190 .build();
188 191 when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
189   - .thenReturn(ListUtil.of(pendingLine))
190 192 .thenReturn(ListUtil.of(qualifiedLine));
191   -
192   - // mock: finishProductProduce 中按行查明细
193 193 MesWmProductProduceDetailDO detail = MesWmProductProduceDetailDO.builder()
194 194 .lineId(200L).quantity(BigDecimal.valueOf(50)).build();
195 195 when(productProduceDetailService.getProductProduceDetailListByLineId(200L))
196 196 .thenReturn(ListUtil.of(detail));
197 197  
198 198 // 调用:不合格品数量 = 0
199   - productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(50), BigDecimal.ZERO);
  199 + productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
  200 + BigDecimal.valueOf(50), BigDecimal.ZERO);
200 201  
201 202 // 断言 1:没有新建行(不合格品数量为 0)
202 203 verify(productProduceLineService, never()).createProductProduceLine(any());
... ... @@ -218,14 +219,18 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
218 219 public void testSplitPendingAndFinishProduce_produceNotExists() {
219 220 // 调用不存在的 feedbackId,应该抛异常
220 221 Long feedbackId = randomLongId();
221   - assertThrows(Exception.class, () ->
222   - productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO));
  222 + Long sourceLineId = randomLongId();
  223 + ServiceException ex = assertThrows(ServiceException.class, () ->
  224 + productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
  225 + BigDecimal.TEN, BigDecimal.ZERO));
  226 + assertEquals(WM_PRODUCT_PRODUCE_NOT_EXISTS.getCode(), ex.getCode());
223 227 }
224 228  
225 229 @Test
226   - public void testSplitPendingAndFinishProduce_noPendingLine() {
227   - // 准备数据:产出单存在,但没有 PENDING 行
  230 + public void testSplitPendingAndFinishProduce_lineNotExists() {
  231 + // 准备数据:产出单存在,但 sourceLineId 对应的行不存在
228 232 Long feedbackId = randomLongId();
  233 + Long sourceLineId = randomLongId();
229 234 MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
230 235 o.setFeedbackId(feedbackId);
231 236 o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
... ... @@ -240,17 +245,15 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
240 245 when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
241 246 when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
242 247  
243   - // mock: 返回一条 PASS 状态的行(不是 PENDING)
244   - MesWmProductProduceLineDO passLine = MesWmProductProduceLineDO.builder()
245   - .id(300L).produceId(produce.getId())
246   - .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
247   - .build();
248   - when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
249   - .thenReturn(ListUtil.of(passLine));
  248 + // mock: validateProductProduceLineExists 抛异常
  249 + when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
  250 + .thenThrow(new ServiceException(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS));
250 251  
251   - // 调用,应该抛异常(找不到 PENDING 行)
252   - assertThrows(Exception.class, () ->
253   - productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO));
  252 + // 调用,应该抛异常
  253 + ServiceException ex = assertThrows(ServiceException.class, () ->
  254 + productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
  255 + BigDecimal.TEN, BigDecimal.ZERO));
  256 + assertEquals(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS.getCode(), ex.getCode());
254 257 }
255 258  
256 259 }
... ...