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

View File

@@ -63,7 +63,7 @@ class IostorInvServiceLocalSpringTest {
} }
@Test @Test
void shouldCreateOutboundHeaderAndDetailsInOneTransaction() { void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() {
String id = iostorInvService.createOutbound(buildRequest()); String id = iostorInvService.createOutbound(buildRequest());
assertNotNull(id); assertNotNull(id);
@@ -101,10 +101,10 @@ class IostorInvServiceLocalSpringTest {
assertEquals(id, details.get(1).headerId()); assertEquals(id, details.get(1).headerId());
assertEquals(new DetailRow(details.get(0).detailId(), id, 1, "MAT-01", "MID-01", "PCSN-01", 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"), 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", 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"), 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 @Test
@@ -200,6 +200,14 @@ class IostorInvServiceLocalSpringTest {
assertInventoryRejected(buildRequest()); 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) { private void assertInventoryRejected(IostorInvCreateReqVO request) {
ServiceException ex = assertThrows(ServiceException.class, () -> iostorInvService.createOutbound(request)); ServiceException ex = assertThrows(ServiceException.class, () -> iostorInvService.createOutbound(request));
assertEquals("出库库存已变化,请刷新后重新选择完整箱库存", ex.getMessage()); 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 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_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 INSERT INTO wms_group_plate VALUES
(101,'BOX-01','可用','MID-01','MAT-01','PCSN-01',12.500,NULL,'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,'KG','千克',NULL,NULL,NULL,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); (103,'BOX-X','可用','MID-01','MAT-01','PCSN-X',5.000,0,'KG','千克',NULL,NULL,NULL,FALSE);