Commit acda3c95de8dcddb447759e99edd670b9bda543f

Authored by YunaiV
1 parent 3d1b66d4

fix(wms): 优化库存批量锁定顺序

- 批量 FOR UPDATE 查询前复制并排序库存 id,保持查询输入顺序稳定
- 补充多条已存在库存变更单测,验证库存余额更新和流水顺序
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryMapper.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
14 import org.apache.ibatis.annotations.Mapper; 14 import org.apache.ibatis.annotations.Mapper;
15 15
16 import java.math.BigDecimal; 16 import java.math.BigDecimal;
  17 +import java.util.ArrayList;
17 import java.util.Collection; 18 import java.util.Collection;
18 import java.util.Collections; 19 import java.util.Collections;
19 import java.util.List; 20 import java.util.List;
@@ -99,8 +100,10 @@ public interface WmsInventoryMapper extends BaseMapperX<WmsInventoryDO> { @@ -99,8 +100,10 @@ public interface WmsInventoryMapper extends BaseMapperX<WmsInventoryDO> {
99 if (CollUtil.isEmpty(ids)) { 100 if (CollUtil.isEmpty(ids)) {
100 return Collections.emptyList(); 101 return Collections.emptyList();
101 } 102 }
  103 + List<Long> sortedIds = new ArrayList<>(ids);
  104 + Collections.sort(sortedIds);
102 return selectList(new LambdaQueryWrapperX<WmsInventoryDO>() 105 return selectList(new LambdaQueryWrapperX<WmsInventoryDO>()
103 - .in(WmsInventoryDO::getId, ids) 106 + .in(WmsInventoryDO::getId, sortedIds)
104 .orderByAsc(WmsInventoryDO::getId) 107 .orderByAsc(WmsInventoryDO::getId)
105 .last("FOR UPDATE")); 108 .last("FOR UPDATE"));
106 } 109 }
yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java
@@ -241,6 +241,46 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -241,6 +241,46 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
241 } 241 }
242 242
243 @Test 243 @Test
  244 + public void testChangeInventory_multipleExistingInventoryKeys() {
  245 + // mock 数据
  246 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  247 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  248 + inventoryMapper.insert(createInventory(sku.getId(), 200L, "7.00"));
  249 + inventoryMapper.insert(createInventory(sku.getId(), 100L, "5.00"));
  250 + WmsInventoryChangeReqDTO reqDTO = createChangeReq(sku.getId(), 100L, "2.00");
  251 + List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>(reqDTO.getItems());
  252 + items.add(new WmsInventoryChangeReqDTO.Item()
  253 + .setSkuId(sku.getId())
  254 + .setWarehouseId(200L)
  255 + .setQuantity(new BigDecimal("3.00"))
  256 + .setPrice(new BigDecimal("100.00"))
  257 + .setTotalPrice(new BigDecimal("300.00"))
  258 + .setRemark("测试入库 2"));
  259 + reqDTO.setItems(items);
  260 +
  261 + // 调用
  262 + inventoryService.changeInventory(reqDTO);
  263 +
  264 + // 断言:多条已存在库存余额分别更新
  265 + WmsInventoryDO inventory1 = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L);
  266 + assertNotNull(inventory1);
  267 + assertEquals(0, new BigDecimal("7.00").compareTo(inventory1.getQuantity()));
  268 + WmsInventoryDO inventory2 = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 200L);
  269 + assertNotNull(inventory2);
  270 + assertEquals(0, new BigDecimal("10.00").compareTo(inventory2.getQuantity()));
  271 + // 断言:库存流水保持入参明细顺序
  272 + List<WmsInventoryHistoryDO> histories = inventoryHistoryMapper.selectList();
  273 + histories.sort(Comparator.comparing(WmsInventoryHistoryDO::getId));
  274 + assertEquals(2, histories.size());
  275 + assertEquals(100L, histories.get(0).getWarehouseId());
  276 + assertEquals(0, new BigDecimal("5.00").compareTo(histories.get(0).getBeforeQuantity()));
  277 + assertEquals(0, new BigDecimal("7.00").compareTo(histories.get(0).getAfterQuantity()));
  278 + assertEquals(200L, histories.get(1).getWarehouseId());
  279 + assertEquals(0, new BigDecimal("7.00").compareTo(histories.get(1).getBeforeQuantity()));
  280 + assertEquals(0, new BigDecimal("10.00").compareTo(histories.get(1).getAfterQuantity()));
  281 + }
  282 +
  283 + @Test
244 public void testChangeInventory_concurrentCreateSameInventoryOnlyOneBalance() throws Exception { 284 public void testChangeInventory_concurrentCreateSameInventoryOnlyOneBalance() throws Exception {
245 // mock 数据 285 // mock 数据
246 WmsItemDO item = createItem("ITEM-001", "红富士苹果"); 286 WmsItemDO item = createItem("ITEM-001", "红富士苹果");