云
2026-10-29 0a2206a53d5889b6095f4819c263be858d86ca91
feat(mes): 增加盘号管理和库存搜索功能

- 新增 MesQcNcrService 注入支持
- 添加 reelNo 盘号字段到 MesWmMaterialStockAdjustReqVO
- 在 MesWmMaterialStockPageReqVO 中增加 mdmItemId、itemName 和 vendorName 搜索条件
- 实现库存列表按物料名称和供应商名称模糊查询功能
- 完善批次盘号匹配校验逻辑
- 优化销售退货批次验证流程
- 添加盘号相关的单元测试用例
- 修复 SQL 查询中的二进制比较语法问题
已添加5个文件
已修改8个文件
255 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockAdjustReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockPageReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesDetailMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockReelTest.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockSearchTest.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesOrderWritebackTest.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/SalesBatchQueryParserTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/ReturnSalesManualBatchReelTest.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockAdjustReqVO.java
@@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -43,6 +44,10 @@
    @Schema(description = "批次号(新建批次时传入,系统自动创建批次记录)", example = "BATCH001")
    private String batchCode;
    @Schema(description = "盘号(新建批次时手工填写)", example = "PHCS")
    @Size(max = 128, message = "盘号不能超过128个字符")
    private String reelNo;
    @Schema(description = "生产日期(新建批次时传入)", example = "2025-01-01")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@@ -72,4 +77,4 @@
    @Schema(description = "原因", example = "盘点调整")
    private String reason;
}
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockPageReqVO.java
@@ -30,6 +30,15 @@
    @Schema(description = "物料编号", example = "1")
    private Long itemId;
    @Schema(description = "MDM ç‰©æ–™ç¼–号")
    private Long mdmItemId;
    @Schema(description = "物料名称或编码(模糊查询)")
    private String itemName;
    @Schema(description = "供应商名称(模糊查询)")
    private String vendorName;
    @Schema(description = "批次号", example = "B20260101")
    private String batchCode;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
@@ -26,6 +27,7 @@
        LambdaQueryWrapperX<MesWmMaterialStockDO> wrapper = new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .inIfPresent(MesWmMaterialStockDO::getItemTypeId, itemTypeIds)
                .inIfPresent(MesWmMaterialStockDO::getItemId, itemIds)
                .eqIfPresent(MesWmMaterialStockDO::getItemId, reqVO.getMdmItemId())
                .likeIfPresent(MesWmMaterialStockDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesWmMaterialStockDO::getBatchId, reqVO.getBatchId())
                .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId())
@@ -33,6 +35,14 @@
                .eqIfPresent(MesWmMaterialStockDO::getAreaId, reqVO.getAreaId())
                .eqIfPresent(MesWmMaterialStockDO::getVendorId, reqVO.getVendorId())
                .eqIfPresent(MesWmMaterialStockDO::getFrozen, reqVO.getFrozen());
        if (StrUtil.isNotBlank(reqVO.getItemName())) {
            String keyword = "%" + reqVO.getItemName().trim() + "%";
            wrapper.apply("item_id IN (SELECT id FROM mdm_item WHERE (name LIKE {0} OR code LIKE {0}) AND deleted = 0)", keyword);
        }
        if (StrUtil.isNotBlank(reqVO.getVendorName())) {
            wrapper.apply("vendor_id IN (SELECT id FROM srm_supplier WHERE name LIKE {0} AND deleted = 0)",
                    "%" + reqVO.getVendorName().trim() + "%");
        }
        wrapper.ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)
                .orderByAsc(MesWmMaterialStockDO::getReceiptTime);
        // è™šæ‹Ÿä»“过滤(Service å±‚已将 virtualFilter è§£æžä¸º virtualWarehouseId)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesDetailMapper.java
@@ -24,7 +24,7 @@
            + "JOIN mes_wm_batch b ON b.id = d.batch_id AND b.deleted = 0 AND b.item_id = d.item_id "
            + "WHERE d.deleted = 0 AND s.sale_order_id = #{orderId} AND s.status = #{finishedStatus} "
            + "AND d.item_id = #{itemId} AND l.item_id = #{itemId} AND i.product_id = #{itemId} "
            + "AND BINARY i.reel_no = BINARY b.reel_no "
            + "AND CAST(i.reel_no AS BINARY) = CAST(b.reel_no AS BINARY) "
            + "AND d.batch_id = #{batchId}")
    Long countFinishedSaleBatch(@Param("orderId") Long orderId, @Param("itemId") Long itemId,
                                @Param("batchId") Long batchId, @Param("finishedStatus") Integer finishedStatus);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -132,6 +132,8 @@
            MesMdItemDO item = mdItemService.getItem(pageReqVO.getItemId());
            if (item != null && item.getMdmItemId() != null) {
                itemIds = SetUtils.asSet(item.getMdmItemId());
            } else {
                return PageResult.empty();
            }
        }
        // 1.3 è§£æž virtualFilter:转换为虚拟仓 warehouseId
@@ -459,11 +461,16 @@
    private MesWmBatchDO resolveBatch(MesWmMaterialStockAdjustReqVO reqVO, MesMdmItemDO item) {
        // å³ä½¿ç‰©æ–™æœªå¯ç”¨æ™®é€šæ‰¹æ¬¡ç®¡ç†ï¼Œä¹Ÿè¦ä¿ç•™å¹¶æ ¡éªŒç›˜å·è¿½æº¯æ‰¹æ¬¡ã€‚
        if (reqVO.getBatchId() != null) {
            return batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId());
            MesWmBatchDO existing = batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId());
            if (StrUtil.isNotBlank(reqVO.getReelNo())
                    && !Objects.equals(StrUtil.trim(reqVO.getReelNo()), StrUtil.trim(existing.getReelNo()))) {
                throw exception(WM_BATCH_REEL_MISMATCH);
            }
            return existing;
        }
        // 1. æœªæŒ‡å®šè¿½æº¯æ‰¹æ¬¡ä¸”物料未启用批次管理,返回 null
        if (Boolean.FALSE.equals(item.getIsBatchManaged())) {
        if (Boolean.FALSE.equals(item.getIsBatchManaged()) && StrUtil.isBlank(reqVO.getReelNo())) {
            return null;
        }
@@ -471,6 +478,7 @@
        MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO()
                .setItemId(reqVO.getItemId())
                .setBatchCode(reqVO.getBatchCode())
                .setReelNo(StrUtil.trimToNull(reqVO.getReelNo()))
                .setVendorId(reqVO.getVendorId())
                .setProduceDate(reqVO.getProduceDate() != null ? reqVO.getProduceDate().atStartOfDay() : null)
                .setExpireDate(reqVO.getExpireDate() != null ? reqVO.getExpireDate().atStartOfDay() : null)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java
@@ -113,6 +113,12 @@
                    MesWmProductSalesStatusEnum.FINISHED.getStatus()) == 0) {
                throw exception(WM_RETURN_SALES_BATCH_NOT_SHIPPED);
            }
            // æ‰‹å·¥å…¥åº“批次可能没有客户;仅在订单盘号及已完成出库都已验证后允许退货。
            if (batch != null && (batch.getClientId() != null || reelNumbers.isEmpty())) {
                batchService.validateBatchExists(batch.getId(), reqVO.getItemId(), returnSales.getClientId(), null);
            }
        } else if (batch != null) {
            batchService.validateBatchExists(batch.getId(), reqVO.getItemId(), returnSales.getClientId(), null);
        }
    }
@@ -191,8 +197,7 @@
        }
        // æ ¡éªŒæ‰¹æ¬¡å­˜åœ¨ä¸”属于当前物料和客户
        if (batchId != null) {
            // ä»Žçˆ¶å•获取客户ID,校验批次归属
            return batchService.validateBatchExists(batchId, itemId, returnSales.getClientId(), null);
            return batchService.validateBatchExists(batchId, itemId);
        }
        return null;
    }
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java
@@ -11,6 +11,7 @@
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService;
import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcNcrService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
@@ -59,6 +60,8 @@
    private AdminUserApi adminUserApi;
    @MockitoBean
    private MesQcIndicatorResultService indicatorResultService;
    @MockitoBean
    private MesQcNcrService ncrService;
    @Test
    public void testFinishRqc_successWithReturnIssue() {
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockReelTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.mes.service.wm.materialstock;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class MesWmMaterialStockReelTest {
    @Test
    void manualReelCreatesTraceableBatchEvenWhenBatchManagementIsDisabled() {
        var service = new MesWmMaterialStockServiceImpl();
        var batches = mock(MesWmBatchService.class);
        ReflectionTestUtils.setField(service, "batchService", batches);
        var item = new MesMdmItemDO();
        item.setIsBatchManaged(false);
        var request = new MesWmMaterialStockAdjustReqVO();
        request.setItemId(1L);
        request.setReelNo(" PHCS ");
        MesWmBatchDO batch = MesWmBatchDO.builder().id(2L).reelNo("PHCS").build();
        when(batches.getOrGenerateBatchCode(any())).thenReturn(batch);
        assertSame(batch, ReflectionTestUtils.invokeMethod(service, "resolveBatch", request, item));
        verify(batches).getOrGenerateBatchCode(argThat((MesWmBatchGenerateReqVO v) -> "PHCS".equals(v.getReelNo())));
    }
    @Test
    void existingBatchCannotBeRelabelled() {
        var service = new MesWmMaterialStockServiceImpl();
        var batches = mock(MesWmBatchService.class);
        ReflectionTestUtils.setField(service, "batchService", batches);
        var request = new MesWmMaterialStockAdjustReqVO();
        request.setItemId(1L); request.setBatchId(2L); request.setReelNo("PHCS");
        when(batches.validateBatchExists(2L, 1L)).thenReturn(MesWmBatchDO.builder().id(2L).build());
        assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(service, "resolveBatch", request, new MesMdmItemDO()));
        verify(batches, never()).getOrGenerateBatchCode(any());
    }
}
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockSearchTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.service.wm.materialstock;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class MesWmMaterialStockSearchTest extends BaseDbUnitTest {
    @Resource private MesWmMaterialStockMapper mapper;
    @Resource private DataSource dataSource;
    @Test void filtersItemNameCodeVendorAndMdmIdBeforePagination() {
        JdbcTemplate jdbc = new JdbcTemplate(dataSource);
        jdbc.execute("ALTER TABLE mes_wm_material_stock ADD COLUMN IF NOT EXISTS frozen_quantity DECIMAL(14,2)");
        jdbc.execute("ALTER TABLE mes_wm_material_stock ADD COLUMN IF NOT EXISTS reserved_quantity DECIMAL(14,2)");
        jdbc.execute("ALTER TABLE mes_wm_material_stock ADD COLUMN IF NOT EXISTS in_transit_quantity DECIMAL(14,2)");
        jdbc.execute("CREATE TABLE IF NOT EXISTS mdm_item (id BIGINT PRIMARY KEY, name VARCHAR(100), code VARCHAR(100), deleted BOOLEAN)");
        jdbc.execute("CREATE TABLE IF NOT EXISTS srm_supplier (id BIGINT PRIMARY KEY, name VARCHAR(100), deleted BOOLEAN)");
        jdbc.update("INSERT INTO mdm_item VALUES (901, '黑枸杞', 'E2E-901', FALSE), (902, '黄芪', 'E2E-902', FALSE)");
        jdbc.update("INSERT INTO srm_supplier VALUES (801, '测试供应商', FALSE), (802, '其他供应商', FALSE)");
        jdbc.update("INSERT INTO mes_wm_material_stock (item_id, vendor_id, quantity) VALUES (901, 801, 1), (902, 802, 1)");
        var req = new MesWmMaterialStockPageReqVO();
        req.setItemName("枸杞");
        assertEquals(1L, mapper.selectPage(req, null, null, null).getTotal());
        req.setItemName("E2E-901");
        req.setVendorName("测试");
        assertEquals(1L, mapper.selectPage(req, null, null, null).getTotal());
        req.setMdmItemId(902L);
        assertEquals(0L, mapper.selectPage(req, null, null, null).getTotal());
        req.setItemName("不存在");
        req.setMdmItemId(null);
        assertEquals(0L, mapper.selectPage(req, null, null, null).getTotal());
    }
}
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesOrderWritebackTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mes.service.wm.productsales;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.*;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.List;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class MesWmProductSalesOrderWritebackTest {
    @InjectMocks private MesWmProductSalesServiceImpl service;
    @Mock private MesWmProductSalesMapper mapper;
    @Mock private MesWmProductSalesLineService lines;
    @Mock private MesWmProductSalesDetailService details;
    @Mock private MesWmStockReserveService reserves;
    @Mock private MesWmTransactionService transactions;
    @Mock private MesWmSalesReelGuard reelGuard;
    @Mock private ErpSaleOrderApi orders;
    @Test void completedSaleWritesQuantityToSelectedOrderItem() {
        when(mapper.selectById(1L)).thenReturn(MesWmProductSalesDO.builder().id(1L).code("TEST")
                .status(MesWmProductSalesStatusEnum.APPROVED.getStatus()).build());
        var line = MesWmProductSalesLineDO.builder().id(2L).salesId(1L).saleOrderItemId(3L)
                .quantity(BigDecimal.ONE).build();
        when(lines.getProductSalesLineListBySalesId(1L)).thenReturn(List.of(line));
        when(lines.getProductSalesLine(2L)).thenReturn(line);
        when(details.getProductSalesDetailListBySalesId(1L)).thenReturn(List.of(
                MesWmProductSalesDetailDO.builder().lineId(2L).batchId(5L).quantity(BigDecimal.ONE).build()));
        var item = new ErpSaleOrderItemRespDTO();
        item.setOrderId(4L);
        when(orders.getSaleOrderItem(3L)).thenReturn(CommonResult.success(item));
        service.finishProductSales(1L);
        verify(orders).updateOrderItemOutCount(3L, BigDecimal.ONE);
        verify(orders).updateOrderOutStatus(4L);
        verify(reelGuard).validate(line, 5L);
    }
}
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/SalesBatchQueryParserTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mes.service.wm.productsales;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class SalesBatchQueryParserTest {
    @Test void shipmentBatchQueryCanBeParsedByMybatisInterceptor() throws Exception {
        String sql = String.join("", MesWmProductSalesDetailMapper.class
                .getMethod("countFinishedSaleBatch", Long.class, Long.class, Long.class, Integer.class)
                .getAnnotation(Select.class).value()).replaceAll("#\\{[^}]+}", "?");
        assertDoesNotThrow(() -> CCJSqlParserUtil.parse(sql));
    }
}
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java
@@ -12,6 +12,9 @@
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
import java.math.BigDecimal;
import java.util.List;
@@ -45,6 +48,12 @@
    @MockitoBean
    private MesWmReturnSalesDetailService returnSalesDetailService;
    @MockitoBean
    private MesMdmItemService mdmItemService;
    @MockitoBean
    private ErpSaleOrderApi saleOrderApi;
    @MockitoBean
    private MesWmProductSalesDetailMapper productSalesDetailMapper;
    @Test
    public void testUpdateReturnSalesLineWhenRqcFinish_allPass() {
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/ReturnSalesManualBatchReelTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mes.service.wm.returnsales;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class ReturnSalesManualBatchReelTest {
    @Test void manualBatchRequiresMatchingReelAndCompletedShipment() {
        var service = new MesWmReturnSalesLineServiceImpl();
        var returns = mock(MesWmReturnSalesService.class);
        var items = mock(MesMdmItemService.class);
        var batches = mock(MesWmBatchService.class);
        var orders = mock(ErpSaleOrderApi.class);
        var shipments = mock(MesWmProductSalesDetailMapper.class);
        ReflectionTestUtils.setField(service, "returnSalesService", returns);
        ReflectionTestUtils.setField(service, "mdmItemService", items);
        ReflectionTestUtils.setField(service, "batchService", batches);
        ReflectionTestUtils.setField(service, "saleOrderApi", orders);
        ReflectionTestUtils.setField(service, "productSalesDetailMapper", shipments);
        var order = new MesWmReturnSalesDO().setSaleOrderId(5L).setClientId(9L).setSalesOrderCode("SO");
        when(returns.validateReturnSalesExistsAndPrepare(1L)).thenReturn(order);
        when(items.validateItemExistsAndEnable(2L)).thenReturn(new MesMdmItemDO());
        var batch = MesWmBatchDO.builder().id(3L).itemId(2L).code("B").reelNo("PHCS").build();
        when(batches.validateBatchExists(3L, 2L)).thenReturn(batch);
        var item = new ErpSaleOrderItemRespDTO().setProductId(2L).setReelNo("PHCS");
        when(orders.getSaleOrderItemList(5L)).thenReturn(CommonResult.success(List.of(item)));
        var req = new MesWmReturnSalesLineSaveReqVO().setReturnId(1L).setItemId(2L).setBatchId(3L);
        assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(service, "validateLineSaveData", req));
        when(shipments.countFinishedSaleBatch(eq(5L), eq(2L), eq(3L), anyInt())).thenReturn(1L);
        assertDoesNotThrow(() -> ReflectionTestUtils.invokeMethod(service, "validateLineSaveData", req));
        assertEquals("B", req.getBatchCode());
        batch.setReelNo("OTHER");
        assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(service, "validateLineSaveData", req));
    }
}