refactor(asset): 优化资产状态流转服务中的仓位管理逻辑

- 领用和借出流程中增加清空仓位操作
- 借出流程中增加清空使用归属操作
- 调拨功能限制为仅在库资产可操作,移除对在用资产的支持
- 维修流程中增加归属验证逻辑
- 添加仓位信息完整性校验方法
- 更新单元测试以验证新的仓位清空逻辑
main
yangk 2 weeks ago
parent 25635eb538
commit 790ba7ab39

@ -24,7 +24,7 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context); AssetTransitionContext context);
/** /**
* IN_STOCK IN_USE使使 * IN_STOCK IN_USE使使
* *
* @param assetId ID * @param assetId ID
* @param useDeptId ID * @param useDeptId ID
@ -48,7 +48,7 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context); AssetTransitionContext context);
/** /**
* IN_STOCK BORROWED * IN_STOCK BORROWED使
* <p>使</p> * <p>使</p>
* *
* @param assetId ID * @param assetId ID
@ -70,19 +70,17 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context); AssetTransitionContext context);
/** /**
* IN_STOCK/IN_USE * IN_STOCK IN_STOCK
* <p>使使</p> * <p>退</p>
* *
* @param assetId ID * @param assetId ID
* @param warehouseId ID * @param warehouseId ID
* @param locationId ID * @param locationId ID
* @param useDeptId 使IDnull
* @param useUserId 使IDnull
* @param context * @param context
* @return * @return
*/ */
public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId, public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId,
Long useDeptId, Long useUserId, AssetTransitionContext context); AssetTransitionContext context);
/** /**
* IN_STOCK/IN_USE REPAIRING * IN_STOCK/IN_USE REPAIRING

@ -88,7 +88,10 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
{ {
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.IN_USE, return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.IN_USE,
context, AssetLifecycleBusinessType.RECEIVE, "确认资产领用", context, AssetLifecycleBusinessType.RECEIVE, "确认资产领用",
asset -> fillUseOwnership(asset, useDeptId, useUserId)); asset -> {
clearWarehouseLocation(asset);
fillUseOwnership(asset, useDeptId, useUserId);
});
} }
@Override @Override
@ -108,7 +111,11 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
{ {
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.BORROWED, return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.BORROWED,
context, AssetLifecycleBusinessType.BORROW, "确认资产借出", context, AssetLifecycleBusinessType.BORROW, "确认资产借出",
this::validateInStockOwnership); asset -> {
validateInStockOwnership(asset);
clearWarehouseLocation(asset);
clearUseOwnership(asset);
});
} }
@Override @Override
@ -125,21 +132,13 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
@Override @Override
public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId, public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId,
Long useDeptId, Long useUserId, AssetTransitionContext context) AssetTransitionContext context)
{ {
// 调拨保持原状态不变;在用资产调拨需替换使用归属,在库资产调拨则清除使用归属 return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK),
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE), asset -> AssetStatus.IN_STOCK, context, AssetLifecycleBusinessType.TRANSFER, "确认资产调拨",
AmsAsset::getAssetStatus, context, AssetLifecycleBusinessType.TRANSFER, "确认资产调拨",
asset -> { asset -> {
fillWarehouseLocation(asset, warehouseId, locationId); fillWarehouseLocation(asset, warehouseId, locationId);
if (StringUtils.equals(AssetStatus.IN_USE, asset.getAssetStatus())) clearUseOwnership(asset);
{
fillUseOwnership(asset, useDeptId, useUserId);
}
else
{
clearUseOwnership(asset);
}
}); });
} }
@ -148,7 +147,7 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
{ {
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE), return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE),
asset -> AssetStatus.REPAIRING, context, AssetLifecycleBusinessType.REPAIR, "开始资产维修", asset -> AssetStatus.REPAIRING, context, AssetLifecycleBusinessType.REPAIR, "开始资产维修",
asset -> { }); this::validateOwnershipForRepair);
} }
@Override @Override
@ -168,12 +167,12 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
asset -> { asset -> {
if (StringUtils.equals(AssetStatus.IN_STOCK, restoreStatus)) if (StringUtils.equals(AssetStatus.IN_STOCK, restoreStatus))
{ {
// 恢复在库需清除使用归属 validateWarehouseLocation(asset);
clearUseOwnership(asset); clearUseOwnership(asset);
} }
else else
{ {
// 恢复在用需确保使用归属完整(维修期间不允许修改归属) validateEmptyWarehouseLocation(asset);
validateInUseOwnership(asset); validateInUseOwnership(asset);
} }
}); });
@ -353,6 +352,39 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
asset.setUseUserName(null); asset.setUseUserName(null);
} }
/** 清空当前仓位;资产领用或借出后,实物已经离开仓库。 */
private void clearWarehouseLocation(AmsAsset asset)
{
asset.setWarehouseId(null);
asset.setWarehouseCode(null);
asset.setWarehouseName(null);
asset.setLocationId(null);
asset.setLocationCode(null);
asset.setLocationName(null);
}
/** 恢复在库状态时必须保留完整仓位。 */
private void validateWarehouseLocation(AmsAsset asset)
{
if (StringUtils.isNull(asset.getWarehouseId()) || StringUtils.isNull(asset.getLocationId())
|| StringUtils.isEmpty(asset.getWarehouseCode()) || StringUtils.isEmpty(asset.getWarehouseName())
|| StringUtils.isEmpty(asset.getLocationCode()) || StringUtils.isEmpty(asset.getLocationName()))
{
throw new ServiceException("维修前为在库状态,但资产缺少完整仓库或位置信息");
}
}
/** 恢复在用状态时仓位必须为空,避免同时存在仓位和使用归属。 */
private void validateEmptyWarehouseLocation(AmsAsset asset)
{
if (StringUtils.isNotNull(asset.getWarehouseId()) || StringUtils.isNotNull(asset.getLocationId())
|| StringUtils.isNotEmpty(asset.getWarehouseCode()) || StringUtils.isNotEmpty(asset.getWarehouseName())
|| StringUtils.isNotEmpty(asset.getLocationCode()) || StringUtils.isNotEmpty(asset.getLocationName()))
{
throw new ServiceException("维修前为在用状态,但资产仍存在仓库或位置信息");
}
}
/** 借出前校验:在库资产不应有残留的使用归属数据,否则说明台账数据不一致 */ /** 借出前校验:在库资产不应有残留的使用归属数据,否则说明台账数据不一致 */
private void validateInStockOwnership(AmsAsset asset) private void validateInStockOwnership(AmsAsset asset)
{ {
@ -373,6 +405,21 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
} }
} }
/** 开始维修前校验当前状态对应的归属模式完整。 */
private void validateOwnershipForRepair(AmsAsset asset)
{
if (StringUtils.equals(AssetStatus.IN_STOCK, asset.getAssetStatus()))
{
validateWarehouseLocation(asset);
validateInStockOwnership(asset);
}
else
{
validateEmptyWarehouseLocation(asset);
validateInUseOwnership(asset);
}
}
/** 构建履历日志对象,变更摘要未传入时使用默认描述 */ /** 构建履历日志对象,变更摘要未传入时使用默认描述 */
private AmsAssetLifecycleLog buildLifecycleLog(AssetTransitionContext context, String businessType, private AmsAssetLifecycleLog buildLifecycleLog(AssetTransitionContext context, String businessType,
String defaultSummary, Date operateTime) String defaultSummary, Date operateTime)

@ -92,7 +92,7 @@ class AssetStatusTransitionServiceImplTest
verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class)); verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
} }
/** 领用流转:写入使用部门和使用人、状态变为在用 */ /** 领用流转:清空仓位,写入使用部门和使用人、状态变为在用 */
@Test @Test
void confirmReceiveShouldSetUseOwnershipAndWriteLifecycle() void confirmReceiveShouldSetUseOwnershipAndWriteLifecycle()
{ {
@ -107,6 +107,8 @@ class AssetStatusTransitionServiceImplTest
assertEquals("生产部", result.getAfterAsset().getUseDeptName()); assertEquals("生产部", result.getAfterAsset().getUseDeptName());
assertEquals(4L, result.getAfterAsset().getUseUserId()); assertEquals(4L, result.getAfterAsset().getUseUserId());
assertEquals("张三", result.getAfterAsset().getUseUserName()); assertEquals("张三", result.getAfterAsset().getUseUserName());
assertNull(result.getAfterAsset().getWarehouseId());
assertNull(result.getAfterAsset().getLocationId());
assertLifecycle(AssetLifecycleBusinessType.RECEIVE, AssetStatus.IN_STOCK, AssetStatus.IN_USE); assertLifecycle(AssetLifecycleBusinessType.RECEIVE, AssetStatus.IN_STOCK, AssetStatus.IN_USE);
} }
@ -156,6 +158,8 @@ class AssetStatusTransitionServiceImplTest
AssetTransitionResult result = service.confirmBorrow(1L, buildContext()); AssetTransitionResult result = service.confirmBorrow(1L, buildContext());
assertEquals(AssetStatus.BORROWED, result.getAfterAsset().getAssetStatus()); assertEquals(AssetStatus.BORROWED, result.getAfterAsset().getAssetStatus());
assertNull(result.getAfterAsset().getWarehouseId());
assertNull(result.getAfterAsset().getLocationId());
assertLifecycle(AssetLifecycleBusinessType.BORROW, AssetStatus.IN_STOCK, AssetStatus.BORROWED); assertLifecycle(AssetLifecycleBusinessType.BORROW, AssetStatus.IN_STOCK, AssetStatus.BORROWED);
} }
@ -173,25 +177,22 @@ class AssetStatusTransitionServiceImplTest
assertLifecycle(AssetLifecycleBusinessType.BORROW_RETURN, AssetStatus.BORROWED, AssetStatus.IN_STOCK); assertLifecycle(AssetLifecycleBusinessType.BORROW_RETURN, AssetStatus.BORROWED, AssetStatus.IN_STOCK);
} }
/** 在用资产调拨:保持在用状态并替换使用归属 */ /** 在用资产不能直接调拨,必须先退库。 */
@Test @Test
void confirmTransferShouldKeepInUseStatusAndReplaceOwnership() void confirmTransferShouldRejectInUseAsset()
{ {
AmsAsset asset = buildAsset(AssetStatus.IN_USE); AmsAsset asset = buildAsset(AssetStatus.IN_USE);
asset.setUseDeptId(7L); asset.setUseDeptId(7L);
asset.setUseDeptName("旧部门"); asset.setUseDeptName("旧部门");
asset.setUseUserId(8L); asset.setUseUserId(8L);
asset.setUseUserName("旧使用人"); asset.setUseUserName("旧使用人");
stubLockedAsset(asset); stubLockedAssetWithoutUpdate(asset);
stubWarehouseLocation(2L, 20L);
stubDeptUser(3L, 4L);
AssetTransitionResult result = service.confirmTransfer(1L, 2L, 20L, 3L, 4L, buildContext()); ServiceException exception = assertThrows(ServiceException.class,
() -> service.confirmTransfer(1L, 2L, 20L, buildContext()));
assertEquals(AssetStatus.IN_USE, result.getAfterAsset().getAssetStatus()); assertTrue(exception.getMessage().contains("不允许"));
assertEquals(3L, result.getAfterAsset().getUseDeptId()); verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
assertEquals(4L, result.getAfterAsset().getUseUserId());
assertLifecycle(AssetLifecycleBusinessType.TRANSFER, AssetStatus.IN_USE, AssetStatus.IN_USE);
} }
/** 在库资产调拨:保持在库状态并清除使用归属 */ /** 在库资产调拨:保持在库状态并清除使用归属 */
@ -206,7 +207,7 @@ class AssetStatusTransitionServiceImplTest
stubLockedAsset(asset); stubLockedAsset(asset);
stubWarehouseLocation(2L, 20L); stubWarehouseLocation(2L, 20L);
AssetTransitionResult result = service.confirmTransfer(1L, 2L, 20L, null, null, buildContext()); AssetTransitionResult result = service.confirmTransfer(1L, 2L, 20L, buildContext());
assertEquals(AssetStatus.IN_STOCK, result.getAfterAsset().getAssetStatus()); assertEquals(AssetStatus.IN_STOCK, result.getAfterAsset().getAssetStatus());
assertNull(result.getAfterAsset().getUseDeptId()); assertNull(result.getAfterAsset().getUseDeptId());
@ -223,6 +224,7 @@ class AssetStatusTransitionServiceImplTest
asset.setUseDeptName("生产部"); asset.setUseDeptName("生产部");
asset.setUseUserId(4L); asset.setUseUserId(4L);
asset.setUseUserName("张三"); asset.setUseUserName("张三");
clearWarehouseLocation(asset);
stubLockedAsset(asset); stubLockedAsset(asset);
AssetTransitionResult result = service.startRepair(1L, buildContext()); AssetTransitionResult result = service.startRepair(1L, buildContext());
@ -237,10 +239,6 @@ class AssetStatusTransitionServiceImplTest
void finishRepairWithoutBeforeStatusShouldRestoreStock() void finishRepairWithoutBeforeStatusShouldRestoreStock()
{ {
AmsAsset asset = buildAsset(AssetStatus.REPAIRING); AmsAsset asset = buildAsset(AssetStatus.REPAIRING);
asset.setUseDeptId(3L);
asset.setUseDeptName("生产部");
asset.setUseUserId(4L);
asset.setUseUserName("张三");
stubLockedAsset(asset); stubLockedAsset(asset);
AssetTransitionResult result = service.finishRepair(1L, null, buildContext()); AssetTransitionResult result = service.finishRepair(1L, null, buildContext());
@ -260,6 +258,7 @@ class AssetStatusTransitionServiceImplTest
asset.setUseDeptName("生产部"); asset.setUseDeptName("生产部");
asset.setUseUserId(4L); asset.setUseUserId(4L);
asset.setUseUserName("张三"); asset.setUseUserName("张三");
clearWarehouseLocation(asset);
stubLockedAsset(asset); stubLockedAsset(asset);
AssetTransitionResult result = service.finishRepair(1L, AssetStatus.IN_USE, buildContext()); AssetTransitionResult result = service.finishRepair(1L, AssetStatus.IN_USE, buildContext());
@ -301,7 +300,7 @@ class AssetStatusTransitionServiceImplTest
stubLockedAssetWithoutUpdate(buildAsset(AssetStatus.BORROWED)); stubLockedAssetWithoutUpdate(buildAsset(AssetStatus.BORROWED));
ServiceException exception = assertThrows(ServiceException.class, ServiceException exception = assertThrows(ServiceException.class,
() -> service.confirmTransfer(1L, 2L, 20L, null, null, buildContext())); () -> service.confirmTransfer(1L, 2L, 20L, buildContext()));
assertTrue(exception.getMessage().contains("不允许")); assertTrue(exception.getMessage().contains("不允许"));
verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class)); verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
@ -477,6 +476,16 @@ class AssetStatusTransitionServiceImplTest
when(sysUserService.selectUserById(userId)).thenReturn(user); when(sysUserService.selectUserById(userId)).thenReturn(user);
} }
private void clearWarehouseLocation(AmsAsset asset)
{
asset.setWarehouseId(null);
asset.setWarehouseCode(null);
asset.setWarehouseName(null);
asset.setLocationId(null);
asset.setLocationCode(null);
asset.setLocationName(null);
}
/** 断言履历写入:验证业务类型、前后状态和来源单据信息 */ /** 断言履历写入:验证业务类型、前后状态和来源单据信息 */
private void assertLifecycle(String businessType, String beforeStatus, String afterStatus) private void assertLifecycle(String businessType, String beforeStatus, String afterStatus)
{ {

Loading…
Cancel
Save