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

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

@ -24,7 +24,7 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context);
/**
* IN_STOCK IN_USE使使
* IN_STOCK IN_USE使使
*
* @param assetId ID
* @param useDeptId ID
@ -48,7 +48,7 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context);
/**
* IN_STOCK BORROWED
* IN_STOCK BORROWED使
* <p>使</p>
*
* @param assetId ID
@ -70,19 +70,17 @@ public interface IAssetStatusTransitionService
AssetTransitionContext context);
/**
* IN_STOCK/IN_USE
* <p>使使</p>
* IN_STOCK IN_STOCK
* <p>退</p>
*
* @param assetId ID
* @param warehouseId ID
* @param locationId ID
* @param useDeptId 使IDnull
* @param useUserId 使IDnull
* @param context
* @return
*/
public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId,
Long useDeptId, Long useUserId, AssetTransitionContext context);
AssetTransitionContext context);
/**
* 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,
context, AssetLifecycleBusinessType.RECEIVE, "确认资产领用",
asset -> fillUseOwnership(asset, useDeptId, useUserId));
asset -> {
clearWarehouseLocation(asset);
fillUseOwnership(asset, useDeptId, useUserId);
});
}
@Override
@ -108,7 +111,11 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
{
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.BORROWED,
context, AssetLifecycleBusinessType.BORROW, "确认资产借出",
this::validateInStockOwnership);
asset -> {
validateInStockOwnership(asset);
clearWarehouseLocation(asset);
clearUseOwnership(asset);
});
}
@Override
@ -125,21 +132,13 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
@Override
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, AssetStatus.IN_USE),
AmsAsset::getAssetStatus, context, AssetLifecycleBusinessType.TRANSFER, "确认资产调拨",
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK),
asset -> AssetStatus.IN_STOCK, context, AssetLifecycleBusinessType.TRANSFER, "确认资产调拨",
asset -> {
fillWarehouseLocation(asset, warehouseId, locationId);
if (StringUtils.equals(AssetStatus.IN_USE, asset.getAssetStatus()))
{
fillUseOwnership(asset, useDeptId, useUserId);
}
else
{
clearUseOwnership(asset);
}
clearUseOwnership(asset);
});
}
@ -148,7 +147,7 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
{
return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE),
asset -> AssetStatus.REPAIRING, context, AssetLifecycleBusinessType.REPAIR, "开始资产维修",
asset -> { });
this::validateOwnershipForRepair);
}
@Override
@ -168,12 +167,12 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
asset -> {
if (StringUtils.equals(AssetStatus.IN_STOCK, restoreStatus))
{
// 恢复在库需清除使用归属
validateWarehouseLocation(asset);
clearUseOwnership(asset);
}
else
{
// 恢复在用需确保使用归属完整(维修期间不允许修改归属)
validateEmptyWarehouseLocation(asset);
validateInUseOwnership(asset);
}
});
@ -353,6 +352,39 @@ public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionS
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)
{
@ -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,
String defaultSummary, Date operateTime)

@ -92,7 +92,7 @@ class AssetStatusTransitionServiceImplTest
verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
}
/** 领用流转:写入使用部门和使用人、状态变为在用 */
/** 领用流转:清空仓位,写入使用部门和使用人、状态变为在用 */
@Test
void confirmReceiveShouldSetUseOwnershipAndWriteLifecycle()
{
@ -107,6 +107,8 @@ class AssetStatusTransitionServiceImplTest
assertEquals("生产部", result.getAfterAsset().getUseDeptName());
assertEquals(4L, result.getAfterAsset().getUseUserId());
assertEquals("张三", result.getAfterAsset().getUseUserName());
assertNull(result.getAfterAsset().getWarehouseId());
assertNull(result.getAfterAsset().getLocationId());
assertLifecycle(AssetLifecycleBusinessType.RECEIVE, AssetStatus.IN_STOCK, AssetStatus.IN_USE);
}
@ -156,6 +158,8 @@ class AssetStatusTransitionServiceImplTest
AssetTransitionResult result = service.confirmBorrow(1L, buildContext());
assertEquals(AssetStatus.BORROWED, result.getAfterAsset().getAssetStatus());
assertNull(result.getAfterAsset().getWarehouseId());
assertNull(result.getAfterAsset().getLocationId());
assertLifecycle(AssetLifecycleBusinessType.BORROW, AssetStatus.IN_STOCK, AssetStatus.BORROWED);
}
@ -173,25 +177,22 @@ class AssetStatusTransitionServiceImplTest
assertLifecycle(AssetLifecycleBusinessType.BORROW_RETURN, AssetStatus.BORROWED, AssetStatus.IN_STOCK);
}
/** 在用资产调拨:保持在用状态并替换使用归属 */
/** 在用资产不能直接调拨,必须先退库。 */
@Test
void confirmTransferShouldKeepInUseStatusAndReplaceOwnership()
void confirmTransferShouldRejectInUseAsset()
{
AmsAsset asset = buildAsset(AssetStatus.IN_USE);
asset.setUseDeptId(7L);
asset.setUseDeptName("旧部门");
asset.setUseUserId(8L);
asset.setUseUserName("旧使用人");
stubLockedAsset(asset);
stubWarehouseLocation(2L, 20L);
stubDeptUser(3L, 4L);
stubLockedAssetWithoutUpdate(asset);
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());
assertEquals(3L, result.getAfterAsset().getUseDeptId());
assertEquals(4L, result.getAfterAsset().getUseUserId());
assertLifecycle(AssetLifecycleBusinessType.TRANSFER, AssetStatus.IN_USE, AssetStatus.IN_USE);
assertTrue(exception.getMessage().contains("不允许"));
verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
}
/** 在库资产调拨:保持在库状态并清除使用归属 */
@ -206,7 +207,7 @@ class AssetStatusTransitionServiceImplTest
stubLockedAsset(asset);
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());
assertNull(result.getAfterAsset().getUseDeptId());
@ -223,6 +224,7 @@ class AssetStatusTransitionServiceImplTest
asset.setUseDeptName("生产部");
asset.setUseUserId(4L);
asset.setUseUserName("张三");
clearWarehouseLocation(asset);
stubLockedAsset(asset);
AssetTransitionResult result = service.startRepair(1L, buildContext());
@ -237,10 +239,6 @@ class AssetStatusTransitionServiceImplTest
void finishRepairWithoutBeforeStatusShouldRestoreStock()
{
AmsAsset asset = buildAsset(AssetStatus.REPAIRING);
asset.setUseDeptId(3L);
asset.setUseDeptName("生产部");
asset.setUseUserId(4L);
asset.setUseUserName("张三");
stubLockedAsset(asset);
AssetTransitionResult result = service.finishRepair(1L, null, buildContext());
@ -260,6 +258,7 @@ class AssetStatusTransitionServiceImplTest
asset.setUseDeptName("生产部");
asset.setUseUserId(4L);
asset.setUseUserName("张三");
clearWarehouseLocation(asset);
stubLockedAsset(asset);
AssetTransitionResult result = service.finishRepair(1L, AssetStatus.IN_USE, buildContext());
@ -301,7 +300,7 @@ class AssetStatusTransitionServiceImplTest
stubLockedAssetWithoutUpdate(buildAsset(AssetStatus.BORROWED));
ServiceException exception = assertThrows(ServiceException.class,
() -> service.confirmTransfer(1L, 2L, 20L, null, null, buildContext()));
() -> service.confirmTransfer(1L, 2L, 20L, buildContext()));
assertTrue(exception.getMessage().contains("不允许"));
verify(amsAssetMapper, never()).updateAssetForTransition(any(AmsAsset.class));
@ -477,6 +476,16 @@ class AssetStatusTransitionServiceImplTest
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)
{

Loading…
Cancel
Save