diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAssetStatusTransitionService.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAssetStatusTransitionService.java index 7b0b5f7..918d8b0 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAssetStatusTransitionService.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAssetStatusTransitionService.java @@ -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(清空仓位和使用归属) *
借出前校验在库资产不能残留使用归属,防止数据不一致。
* * @param assetId 资产ID @@ -70,19 +70,17 @@ public interface IAssetStatusTransitionService AssetTransitionContext context); /** - * 确认资产调拨:IN_STOCK/IN_USE → 保持原状态(更新仓位和归属) - *在用资产调拨时替换使用归属,在库资产调拨时清除使用归属。
+ * 确认资产调拨:IN_STOCK → IN_STOCK(仅更新仓库和位置) + *在用资产必须先退库,不能直接调拨。
* * @param assetId 资产ID * @param warehouseId 调拨目标仓库ID * @param locationId 调拨目标位置ID - * @param useDeptId 新使用部门ID(在用资产必填,在库资产可为null) - * @param useUserId 新使用人ID(在用资产必填,在库资产可为null) * @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 diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImpl.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImpl.java index 3cafde5..a2e1145 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImpl.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImpl.java @@ -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) diff --git a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImplTest.java b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImplTest.java index 31576f3..eb23d9a 100644 --- a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImplTest.java +++ b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AssetStatusTransitionServiceImplTest.java @@ -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) {