fix: 使用锁定库存快照保存出库明细

This commit is contained in:
zhouz
2026-07-22 16:05:37 +08:00
parent 4e276c5b05
commit 390862b2dd
3 changed files with 26 additions and 16 deletions

View File

@@ -54,7 +54,7 @@ public class IostorInvServiceImpl implements IostorInvService {
@Override
@Transactional(rollbackFor = Exception.class)
public String createOutbound(IostorInvCreateReqVO reqVO) {
validateAndLockInventory(reqVO);
Map<Long, AvailableInventoryRespVO> inventorySnapshot = validateAndLockInventory(reqVO);
String ruleCode = "IO_CODE";
CodeGenerateReqDTO codeGenerateReqDTO = new CodeGenerateReqDTO();
@@ -95,24 +95,25 @@ public class IostorInvServiceImpl implements IostorInvService {
detailDO.setIostorinvdtlId(IdUtil.fastSimpleUUID());
detailDO.setIostorinvId(iostorInv.getIostorinvId());
detailDO.setSeqNo(index + 1);
detailDO.setMaterialCode(detail.getMaterialCode());
detailDO.setMaterialId(detail.getMaterialId());
detailDO.setPcsn(detail.getPcsn());
AvailableInventoryRespVO inventory = inventorySnapshot.get(detail.getGroupId());
detailDO.setMaterialCode(inventory == null ? detail.getMaterialCode() : inventory.getMaterialCode());
detailDO.setMaterialId(inventory == null ? detail.getMaterialId() : inventory.getMaterialId());
detailDO.setPcsn(inventory == null ? detail.getPcsn() : inventory.getPcsn());
detailDO.setPlanQty(detail.getPlanQty());
detailDO.setUnassignQty(detail.getPlanQty());
detailDO.setAssignQty(BigDecimal.ZERO);
detailDO.setQtyUnitId(detail.getQtyUnitId());
detailDO.setQtyUnitName(detail.getQtyUnitName());
detailDO.setSourceBillCode(detail.getSourceBillCode());
detailDO.setSourceBillType(detail.getSourceBillType());
detailDO.setSourceBilldtlId(detail.getSourceBilldtlId());
detailDO.setQtyUnitId(inventory == null ? detail.getQtyUnitId() : inventory.getQtyUnitId());
detailDO.setQtyUnitName(inventory == null ? detail.getQtyUnitName() : inventory.getQtyUnitName());
detailDO.setSourceBillCode(inventory == null ? detail.getSourceBillCode() : inventory.getExtCode());
detailDO.setSourceBillType(inventory == null ? detail.getSourceBillType() : inventory.getExtType());
detailDO.setSourceBilldtlId(inventory == null ? detail.getSourceBilldtlId() : inventory.getExtDtlCode());
detailDO.setRemark(detail.getRemark());
iostorinvDtlMapper.insert(detailDO);
}
return iostorInv.getIostorinvId();
}
private void validateAndLockInventory(IostorInvCreateReqVO reqVO) {
private Map<Long, AvailableInventoryRespVO> validateAndLockInventory(IostorInvCreateReqVO reqVO) {
List<IostorInvCreateReqVO.Detail> inventoryDetails = new ArrayList<>();
Set<Long> requestedGroupIds = new HashSet<>();
Set<String> vehicleCodes = new LinkedHashSet<>();
@@ -132,7 +133,7 @@ public class IostorInvServiceImpl implements IostorInvService {
vehicleCodes.add(detail.getVehicleCode());
}
if (inventoryDetails.isEmpty()) {
return;
return Collections.emptyMap();
}
List<AvailableInventoryRespVO> lockedRows =
@@ -158,6 +159,7 @@ public class IostorInvServiceImpl implements IostorInvService {
}
}
// 建单仅验证库存快照;库存占用和冻结数量由后续分配流程处理。
return lockedByGroupId;
}
@Override

View File

@@ -63,7 +63,7 @@ class IostorInvServiceLocalSpringTest {
}
@Test
void shouldCreateOutboundHeaderAndDetailsInOneTransaction() {
void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() {
String id = iostorInvService.createOutbound(buildRequest());
assertNotNull(id);
@@ -101,10 +101,10 @@ class IostorInvServiceLocalSpringTest {
assertEquals(id, details.get(1).headerId());
assertEquals(new DetailRow(details.get(0).detailId(), id, 1, "MAT-01", "MID-01", "PCSN-01",
new BigDecimal("12.500"), BigDecimal.ZERO.setScale(3), new BigDecimal("12.500"),
"KG", "千克", "SRC-01", "ORDER", "SRC-DTL-01", "明细一"), details.get(0));
"DB-KG", "数据库千克", "DB-SRC-01", "DB-TYPE", "DB-DTL-01", "明细一"), details.get(0));
assertEquals(new DetailRow(details.get(1).detailId(), id, 2, "MAT-02", "MID-02", "PCSN-02",
new BigDecimal("7.250"), BigDecimal.ZERO.setScale(3), new BigDecimal("7.250"),
"KG", "千克", "SRC-02", "ORDER", "SRC-DTL-02", "明细二"), details.get(1));
"DB-KG", "数据库千克", "DB-SRC-02", "DB-TYPE", "DB-DTL-02", "明细二"), details.get(1));
}
@Test
@@ -200,6 +200,14 @@ class IostorInvServiceLocalSpringTest {
assertInventoryRejected(buildRequest());
}
@Test
void shouldRejectInventoryChangedToPartiallyFrozenBelowRequestedQuantity() {
jdbcTemplate.update("UPDATE wms_group_plate SET qty = 10, frozen_qty = 6 WHERE group_id = 101");
IostorInvCreateReqVO request = buildRequest();
request.getDetails().get(0).setPlanQty(new BigDecimal("5"));
assertInventoryRejected(request);
}
private void assertInventoryRejected(IostorInvCreateReqVO request) {
ServiceException ex = assertThrows(ServiceException.class, () -> iostorInvService.createOutbound(request));
assertEquals("出库库存已变化,请刷新后重新选择完整箱库存", ex.getMessage());

View File

@@ -32,6 +32,6 @@ CREATE TABLE wms_group_plate (group_id BIGINT PRIMARY KEY, vehicle_code VARCHAR(
INSERT INTO base_materialbase VALUES ('MID-01','物料一',FALSE), ('MID-02','物料二',FALSE);
INSERT INTO wms_structattr VALUES ('S1','BOX-01','STOR-01','S01','一号仓',FALSE), ('S2','BOX-02','STOR-01','S01','一号仓',FALSE), ('S3','BOX-X','STOR-02','S02','二号仓',FALSE);
INSERT INTO wms_group_plate VALUES
(101,'BOX-01','可用','MID-01','MAT-01','PCSN-01',12.500,NULL,'KG','千克',NULL,NULL,NULL,FALSE),
(102,'BOX-02','可用','MID-02','MAT-02','PCSN-02',8.000,0.750,'KG','千克',NULL,NULL,NULL,FALSE),
(101,'BOX-01','可用','MID-01','MAT-01','PCSN-01',12.500,NULL,'DB-KG','数据库千克','DB-SRC-01','DB-TYPE','DB-DTL-01',FALSE),
(102,'BOX-02','可用','MID-02','MAT-02','PCSN-02',8.000,0.750,'DB-KG','数据库千克','DB-SRC-02','DB-TYPE','DB-DTL-02',FALSE),
(103,'BOX-X','可用','MID-01','MAT-01','PCSN-X',5.000,0,'KG','千克',NULL,NULL,NULL,FALSE);