package cn.iocoder.yudao.module.wms.service.order.movement; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.detail.WmsMovementOrderDetailSaveReqVO; import cn.iocoder.yudao.module.wms.controller.admin.order.movement.vo.order.WmsMovementOrderSaveReqVO; import cn.iocoder.yudao.module.wms.dal.dataobject.order.movement.WmsMovementOrderDO; import cn.iocoder.yudao.module.wms.dal.dataobject.order.movement.WmsMovementOrderDetailDO; import cn.iocoder.yudao.module.wms.dal.mysql.order.movement.WmsMovementOrderDetailMapper; import cn.iocoder.yudao.module.wms.dal.mysql.order.movement.WmsMovementOrderMapper; import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum; import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.MOVEMENT_ORDER_DETAIL_REQUIRED; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.MOVEMENT_ORDER_STATUS_NOT_DELETABLE; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.MOVEMENT_ORDER_STATUS_NOT_PREPARE; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.MOVEMENT_ORDER_WAREHOUSE_SAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @Import({WmsMovementOrderServiceImpl.class, WmsMovementOrderDetailServiceImpl.class}) public class WmsMovementOrderServiceImplTest extends BaseDbUnitTest { @Resource private WmsMovementOrderServiceImpl movementOrderService; @Resource private WmsMovementOrderMapper movementOrderMapper; @Resource private WmsMovementOrderDetailMapper movementOrderDetailMapper; @MockitoBean private WmsWarehouseService warehouseService; @MockitoBean private WmsItemSkuService itemSkuService; @MockitoBean private WmsInventoryService inventoryService; @Test public void testCreateMovementOrder_calculateTotal() { // mock 数据 WmsMovementOrderSaveReqVO reqVO = createMovementOrderReqVO(null, 100L, 200L, createMovementOrderDetailReqVO(null, 3001L, "1.50", "10.00", "16.00"), createMovementOrderDetailReqVO(null, 3002L, "2.50", "20.00", "51.00")); // 调用 Long orderId = movementOrderService.createMovementOrder(reqVO); // 断言 WmsMovementOrderDO dbOrder = movementOrderMapper.selectById(orderId); assertNotNull(dbOrder); assertEquals(0, new BigDecimal("4.00").compareTo(dbOrder.getTotalQuantity())); assertEquals(0, new BigDecimal("67.00").compareTo(dbOrder.getTotalPrice())); List details = movementOrderDetailMapper.selectListByOrderId(orderId); assertEquals(2, details.size()); assertEquals(0, new BigDecimal("16.00").compareTo(details.get(0).getTotalPrice())); } @Test public void testUpdateMovementOrder_calculateTotal() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L); movementOrderMapper.insert(order); WmsMovementOrderSaveReqVO reqVO = createMovementOrderReqVO(order.getId(), 100L, 200L, createMovementOrderDetailReqVO(null, 3001L, "3.00", "30.00", "88.00")); // 调用 movementOrderService.updateMovementOrder(reqVO); // 断言 WmsMovementOrderDO dbOrder = movementOrderMapper.selectById(order.getId()); assertNotNull(dbOrder); assertEquals(0, new BigDecimal("3.00").compareTo(dbOrder.getTotalQuantity())); assertEquals(0, new BigDecimal("88.00").compareTo(dbOrder.getTotalPrice())); List details = movementOrderDetailMapper.selectListByOrderId(order.getId()); assertEquals(1, details.size()); assertEquals(0, new BigDecimal("88.00").compareTo(details.get(0).getTotalPrice())); } @Test public void testCompleteMovementOrder_success() { // mock 数据 Long sourceWarehouseId = 100L; Long targetWarehouseId = 200L; Long skuId = 300L; WmsMovementOrderDO order = createMovementOrder(sourceWarehouseId, targetWarehouseId); movementOrderMapper.insert(order); movementOrderDetailMapper.insert(createMovementOrderDetail(order.getId(), skuId, sourceWarehouseId, targetWarehouseId)); // 调用 movementOrderService.completeMovementOrder(order.getId()); // 断言:移库单 WmsMovementOrderDO dbOrder = movementOrderMapper.selectById(order.getId()); assertNotNull(dbOrder); assertEquals(WmsOrderStatusEnum.FINISHED.getStatus(), dbOrder.getStatus()); // 断言:库存变更 ArgumentCaptor captor = ArgumentCaptor.forClass(WmsInventoryChangeReqDTO.class); verify(inventoryService).changeInventory(captor.capture()); WmsInventoryChangeReqDTO inventoryReqDTO = captor.getValue(); assertEquals(order.getId(), inventoryReqDTO.getOrderId()); assertEquals(order.getNo(), inventoryReqDTO.getOrderNo()); assertEquals(WmsOrderTypeEnum.MOVEMENT.getType(), inventoryReqDTO.getOrderType()); assertEquals(2, inventoryReqDTO.getItems().size()); assertEquals(skuId, inventoryReqDTO.getItems().get(0).getSkuId()); assertEquals(sourceWarehouseId, inventoryReqDTO.getItems().get(0).getWarehouseId()); assertEquals(0, new BigDecimal("-2.00").compareTo(inventoryReqDTO.getItems().get(0).getQuantity())); assertEquals(0, new BigDecimal("-40.00").compareTo(inventoryReqDTO.getItems().get(0).getTotalPrice())); assertEquals(skuId, inventoryReqDTO.getItems().get(1).getSkuId()); assertEquals(targetWarehouseId, inventoryReqDTO.getItems().get(1).getWarehouseId()); assertEquals(0, new BigDecimal("2.00").compareTo(inventoryReqDTO.getItems().get(1).getQuantity())); assertEquals(0, new BigDecimal("40.00").compareTo(inventoryReqDTO.getItems().get(1).getTotalPrice())); } @Test public void testCompleteMovementOrder_detailRequired() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L); movementOrderMapper.insert(order); // 调用,并断言 assertServiceException(() -> movementOrderService.completeMovementOrder(order.getId()), MOVEMENT_ORDER_DETAIL_REQUIRED); verify(inventoryService, never()).changeInventory(any()); } @Test public void testCompleteMovementOrder_duplicateComplete() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L); movementOrderMapper.insert(order); movementOrderDetailMapper.insert(createMovementOrderDetail(order.getId(), 300L, 100L, 200L)); // 调用 movementOrderService.completeMovementOrder(order.getId()); // 调用,并断言:二次完成不能再次写库存 assertServiceException(() -> movementOrderService.completeMovementOrder(order.getId()), MOVEMENT_ORDER_STATUS_NOT_PREPARE); verify(inventoryService).changeInventory(any()); } @Test public void testCreateMovementOrder_sameWarehouse() { // 准备参数 WmsMovementOrderSaveReqVO reqVO = createMovementOrderReqVO(100L, 100L); // 调用,并断言 assertServiceException(() -> movementOrderService.createMovementOrder(reqVO), MOVEMENT_ORDER_WAREHOUSE_SAME); verify(inventoryService, never()).changeInventory(any()); } @Test public void testCancelMovementOrder_success() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L); movementOrderMapper.insert(order); // 调用 movementOrderService.cancelMovementOrder(order.getId()); // 断言 WmsMovementOrderDO dbOrder = movementOrderMapper.selectById(order.getId()); assertNotNull(dbOrder); assertEquals(WmsOrderStatusEnum.CANCELED.getStatus(), dbOrder.getStatus()); verify(inventoryService, never()).changeInventory(any()); } @Test public void testUpdateByIdAndStatus_statusNotMatch() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L); movementOrderMapper.insert(order); // 调用 int updateCount = movementOrderMapper.updateByIdAndStatus(order.getId(), WmsOrderStatusEnum.FINISHED.getStatus(), new WmsMovementOrderDO().setStatus(WmsOrderStatusEnum.CANCELED.getStatus())); // 断言 assertEquals(0, updateCount); assertEquals(WmsOrderStatusEnum.PREPARE.getStatus(), movementOrderMapper.selectById(order.getId()).getStatus()); } @Test public void testDeleteMovementOrder_canceled() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L).setStatus(WmsOrderStatusEnum.CANCELED.getStatus()); movementOrderMapper.insert(order); movementOrderDetailMapper.insert(createMovementOrderDetail(order.getId(), 300L, 100L, 200L)); // 调用 movementOrderService.deleteMovementOrder(order.getId()); // 断言 assertNull(movementOrderMapper.selectById(order.getId())); assertEquals(0, movementOrderDetailMapper.selectListByOrderId(order.getId()).size()); } @Test public void testDeleteMovementOrder_finished() { // mock 数据 WmsMovementOrderDO order = createMovementOrder(100L, 200L).setStatus(WmsOrderStatusEnum.FINISHED.getStatus()); movementOrderMapper.insert(order); // 调用,并断言 assertServiceException(() -> movementOrderService.deleteMovementOrder(order.getId()), MOVEMENT_ORDER_STATUS_NOT_DELETABLE); } private static WmsMovementOrderDO createMovementOrder(Long sourceWarehouseId, Long targetWarehouseId) { return new WmsMovementOrderDO() .setNo("YK202605120001") .setOrderTime(LocalDateTime.of(2026, 5, 12, 0, 0)) .setStatus(WmsOrderStatusEnum.PREPARE.getStatus()) .setSourceWarehouseId(sourceWarehouseId) .setTargetWarehouseId(targetWarehouseId) .setTotalQuantity(new BigDecimal("2.00")) .setTotalPrice(new BigDecimal("20.00")); } private static WmsMovementOrderDetailDO createMovementOrderDetail(Long orderId, Long skuId, Long sourceWarehouseId, Long targetWarehouseId) { return WmsMovementOrderDetailDO.builder() .orderId(orderId) .skuId(skuId) .sourceWarehouseId(sourceWarehouseId) .targetWarehouseId(targetWarehouseId) .quantity(new BigDecimal("2.00")) .price(new BigDecimal("20.00")) .totalPrice(new BigDecimal("40.00")) .build(); } private static WmsMovementOrderSaveReqVO createMovementOrderReqVO(Long sourceWarehouseId, Long targetWarehouseId) { return createMovementOrderReqVO(null, sourceWarehouseId, targetWarehouseId); } private static WmsMovementOrderSaveReqVO createMovementOrderReqVO(Long id, Long sourceWarehouseId, Long targetWarehouseId, WmsMovementOrderDetailSaveReqVO... details) { WmsMovementOrderSaveReqVO reqVO = new WmsMovementOrderSaveReqVO(); reqVO.setId(id); reqVO.setNo("YK202605120001"); reqVO.setOrderTime(LocalDateTime.of(2026, 5, 12, 0, 0)); reqVO.setSourceWarehouseId(sourceWarehouseId); reqVO.setTargetWarehouseId(targetWarehouseId); reqVO.setDetails(Arrays.asList(details)); return reqVO; } private static WmsMovementOrderDetailSaveReqVO createMovementOrderDetailReqVO(Long id, Long skuId, String quantity, String price) { return createMovementOrderDetailReqVO(id, skuId, quantity, price, null); } private static WmsMovementOrderDetailSaveReqVO createMovementOrderDetailReqVO(Long id, Long skuId, String quantity, String price, String totalPrice) { WmsMovementOrderDetailSaveReqVO reqVO = new WmsMovementOrderDetailSaveReqVO(); reqVO.setId(id); reqVO.setSkuId(skuId); reqVO.setQuantity(new BigDecimal(quantity)); reqVO.setPrice(new BigDecimal(price)); if (totalPrice != null) { reqVO.setTotalPrice(new BigDecimal(totalPrice)); } return reqVO; } }