diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryIdentifyMethod.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryIdentifyMethod.java new file mode 100644 index 0000000..dd1a317 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryIdentifyMethod.java @@ -0,0 +1,27 @@ +package com.ruoyi.asset.constant; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 盘盈识别方式 + * + * @author Yangk + */ +public final class InventoryIdentifyMethod +{ + /** EPC 识别 */ + public static final String EPC = "EPC"; + /** 资产编码识别 */ + public static final String ASSET_CODE = "ASSET_CODE"; + /** 手工记录线索 */ + public static final String MANUAL = "MANUAL"; + + public static boolean isValid(String value) + { + return StringUtils.equalsAny(value, EPC, ASSET_CODE, MANUAL); + } + + private InventoryIdentifyMethod() + { + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryResult.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryResult.java index ffe8a4f..a6be097 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryResult.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventoryResult.java @@ -23,6 +23,11 @@ public final class InventoryResult return StringUtils.equalsAny(value, NORMAL, SURPLUS, LOSS, LOCATION_ERROR); } + public static boolean isExpectedItemResult(String value) + { + return StringUtils.equalsAny(value, NORMAL, LOSS, LOCATION_ERROR); + } + private InventoryResult() { } diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusStatus.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusStatus.java new file mode 100644 index 0000000..54043c6 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusStatus.java @@ -0,0 +1,25 @@ +package com.ruoyi.asset.constant; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 盘盈确认状态 + * + * @author Yangk + */ +public final class InventorySurplusStatus +{ + /** 已记录 */ + public static final String RECORDED = "RECORDED"; + /** 已确认 */ + public static final String CONFIRMED = "CONFIRMED"; + + public static boolean isValid(String value) + { + return StringUtils.equalsAny(value, RECORDED, CONFIRMED); + } + + private InventorySurplusStatus() + { + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusType.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusType.java new file mode 100644 index 0000000..17f817a --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/constant/InventorySurplusType.java @@ -0,0 +1,25 @@ +package com.ruoyi.asset.constant; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 盘盈类型 + * + * @author Yangk + */ +public final class InventorySurplusType +{ + /** 账内但不在本次任务应盘范围 */ + public static final String KNOWN_OUT_OF_SCOPE = "KNOWN_OUT_OF_SCOPE"; + /** 未知实物 */ + public static final String UNKNOWN_OBJECT = "UNKNOWN_OBJECT"; + + public static boolean isValid(String value) + { + return StringUtils.equalsAny(value, KNOWN_OUT_OF_SCOPE, UNKNOWN_OBJECT); + } + + private InventorySurplusType() + { + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsInventoryTaskController.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsInventoryTaskController.java index 2114dcc..7d4241b 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsInventoryTaskController.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsInventoryTaskController.java @@ -4,6 +4,7 @@ import java.util.List; import com.ruoyi.asset.domain.AmsAssetCategory; import com.ruoyi.asset.domain.AmsAssetLocation; import com.ruoyi.asset.domain.AmsInventoryTask; +import com.ruoyi.asset.domain.AmsInventoryTaskSurplus; import com.ruoyi.asset.domain.AmsWarehouse; import com.ruoyi.asset.service.IAmsAssetCategoryService; import com.ruoyi.asset.service.IAmsAssetLocationService; @@ -154,6 +155,27 @@ public class AmsInventoryTaskController extends BaseController getSysUser().getUserName(), getLoginName())); } + /** 新增盘盈明细,真实盘盈不写入应盘快照行。 */ + @RequiresPermissions("asset:task:result") + @Log(title = "盘点任务管理盘盈明细", businessType = BusinessType.INSERT) + @PostMapping("/surplus/add") + @ResponseBody + public AjaxResult addSurplus(AmsInventoryTaskSurplus surplus) + { + return toAjax(amsInventoryTaskService.addSurplusItem(surplus, getUserId(), + getSysUser().getUserName(), getLoginName())); + } + + /** 删除盘盈明细,仅允许在结果提交前删除。 */ + @RequiresPermissions("asset:task:result") + @Log(title = "盘点任务管理盘盈明细", businessType = BusinessType.DELETE) + @PostMapping("/surplus/remove/{surplusId}") + @ResponseBody + public AjaxResult removeSurplus(@PathVariable("surplusId") Long surplusId) + { + return toAjax(amsInventoryTaskService.deleteSurplusItem(surplusId, getLoginName())); + } + /** 确认盘点结果,只写盘点履历,不自动调整资产台账。 */ @RequiresPermissions("asset:task:confirm") @Log(title = "盘点任务管理", businessType = BusinessType.UPDATE) diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTask.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTask.java index 539775f..84583ba 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTask.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTask.java @@ -93,6 +93,9 @@ public class AmsInventoryTask extends BaseEntity /** 盘点任务明细信息 */ private List amsInventoryTaskItemList; + /** 盘点任务盘盈明细信息 */ + private List amsInventoryTaskSurplusList; + public void setTaskId(Long taskId) { this.taskId = taskId; @@ -283,6 +286,16 @@ public class AmsInventoryTask extends BaseEntity this.amsInventoryTaskItemList = amsInventoryTaskItemList; } + public List getAmsInventoryTaskSurplusList() + { + return amsInventoryTaskSurplusList; + } + + public void setAmsInventoryTaskSurplusList(List amsInventoryTaskSurplusList) + { + this.amsInventoryTaskSurplusList = amsInventoryTaskSurplusList; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) @@ -310,6 +323,7 @@ public class AmsInventoryTask extends BaseEntity .append("remark", getRemark()) .append("delFlag", getDelFlag()) .append("amsInventoryTaskItemList", getAmsInventoryTaskItemList()) + .append("amsInventoryTaskSurplusList", getAmsInventoryTaskSurplusList()) .toString(); } } diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTaskSurplus.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTaskSurplus.java new file mode 100644 index 0000000..159554d --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsInventoryTaskSurplus.java @@ -0,0 +1,527 @@ +package com.ruoyi.asset.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 盘点任务盘盈明细对象 ams_inventory_task_surplus + * + * @author Yangk + */ +public class AmsInventoryTaskSurplus extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 盘盈ID */ + private Long surplusId; + + /** 盘点任务ID */ + @Excel(name = "盘点任务ID") + private Long taskId; + + /** 盘点任务号快照 */ + @Excel(name = "盘点任务号快照") + private String taskNo; + + /** 盘盈类型 */ + @Excel(name = "盘盈类型", dictType = "ams_inventory_surplus_type") + private String surplusType; + + /** 识别方式 */ + @Excel(name = "识别方式", dictType = "ams_inventory_identify_method") + private String identifyMethod; + + /** 资产ID */ + @Excel(name = "资产ID") + private Long assetId; + + /** 资产编码快照 */ + @Excel(name = "资产编码快照") + private String assetCode; + + /** 资产名称快照 */ + @Excel(name = "资产名称快照") + private String assetName; + + /** 资产类别ID快照 */ + private Long categoryId; + + /** 类别编码快照 */ + private String categoryCode; + + /** 类别名称快照 */ + @Excel(name = "类别名称快照") + private String categoryName; + + /** 规格型号快照 */ + @Excel(name = "规格型号快照") + private String specModel; + + /** 品牌快照 */ + @Excel(name = "品牌快照") + private String brand; + + /** RFID标签ID */ + private Long tagId; + + /** RFID标签编码快照 */ + @Excel(name = "RFID标签编码快照") + private String tagCode; + + /** EPC编码快照 */ + @Excel(name = "EPC编码快照") + private String epcCode; + + /** 账面资产状态 */ + @Excel(name = "账面资产状态", dictType = "ams_asset_status") + private String bookAssetStatus; + + /** 账面仓库ID */ + private Long bookWarehouseId; + + /** 账面仓库编码快照 */ + private String bookWarehouseCode; + + /** 账面仓库名称快照 */ + @Excel(name = "账面仓库名称快照") + private String bookWarehouseName; + + /** 账面位置ID */ + private Long bookLocationId; + + /** 账面位置编码快照 */ + private String bookLocationCode; + + /** 账面位置名称快照 */ + @Excel(name = "账面位置名称快照") + private String bookLocationName; + + /** 盘点仓库ID */ + private Long inventoryWarehouseId; + + /** 盘点仓库编码快照 */ + private String inventoryWarehouseCode; + + /** 盘点仓库名称快照 */ + @Excel(name = "盘点仓库名称快照") + private String inventoryWarehouseName; + + /** 盘点位置ID */ + private Long inventoryLocationId; + + /** 盘点位置编码快照 */ + private String inventoryLocationCode; + + /** 盘点位置名称快照 */ + @Excel(name = "盘点位置名称快照") + private String inventoryLocationName; + + /** 盘点人ID */ + private Long inventoryUserId; + + /** 盘点人名称快照 */ + @Excel(name = "盘点人名称快照") + private String inventoryUserName; + + /** 盘点时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Excel(name = "盘点时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date inventoryTime; + + /** 确认状态 */ + @Excel(name = "确认状态", dictType = "ams_inventory_surplus_status") + private String confirmStatus; + + /** 删除标志:0存在,1删除 */ + private String delFlag; + + public Long getSurplusId() + { + return surplusId; + } + + public void setSurplusId(Long surplusId) + { + this.surplusId = surplusId; + } + + public Long getTaskId() + { + return taskId; + } + + public void setTaskId(Long taskId) + { + this.taskId = taskId; + } + + public String getTaskNo() + { + return taskNo; + } + + public void setTaskNo(String taskNo) + { + this.taskNo = taskNo; + } + + public String getSurplusType() + { + return surplusType; + } + + public void setSurplusType(String surplusType) + { + this.surplusType = surplusType; + } + + public String getIdentifyMethod() + { + return identifyMethod; + } + + public void setIdentifyMethod(String identifyMethod) + { + this.identifyMethod = identifyMethod; + } + + public Long getAssetId() + { + return assetId; + } + + public void setAssetId(Long assetId) + { + this.assetId = assetId; + } + + public String getAssetCode() + { + return assetCode; + } + + public void setAssetCode(String assetCode) + { + this.assetCode = assetCode; + } + + public String getAssetName() + { + return assetName; + } + + public void setAssetName(String assetName) + { + this.assetName = assetName; + } + + public Long getCategoryId() + { + return categoryId; + } + + public void setCategoryId(Long categoryId) + { + this.categoryId = categoryId; + } + + public String getCategoryCode() + { + return categoryCode; + } + + public void setCategoryCode(String categoryCode) + { + this.categoryCode = categoryCode; + } + + public String getCategoryName() + { + return categoryName; + } + + public void setCategoryName(String categoryName) + { + this.categoryName = categoryName; + } + + public String getSpecModel() + { + return specModel; + } + + public void setSpecModel(String specModel) + { + this.specModel = specModel; + } + + public String getBrand() + { + return brand; + } + + public void setBrand(String brand) + { + this.brand = brand; + } + + public Long getTagId() + { + return tagId; + } + + public void setTagId(Long tagId) + { + this.tagId = tagId; + } + + public String getTagCode() + { + return tagCode; + } + + public void setTagCode(String tagCode) + { + this.tagCode = tagCode; + } + + public String getEpcCode() + { + return epcCode; + } + + public void setEpcCode(String epcCode) + { + this.epcCode = epcCode; + } + + public String getBookAssetStatus() + { + return bookAssetStatus; + } + + public void setBookAssetStatus(String bookAssetStatus) + { + this.bookAssetStatus = bookAssetStatus; + } + + public Long getBookWarehouseId() + { + return bookWarehouseId; + } + + public void setBookWarehouseId(Long bookWarehouseId) + { + this.bookWarehouseId = bookWarehouseId; + } + + public String getBookWarehouseCode() + { + return bookWarehouseCode; + } + + public void setBookWarehouseCode(String bookWarehouseCode) + { + this.bookWarehouseCode = bookWarehouseCode; + } + + public String getBookWarehouseName() + { + return bookWarehouseName; + } + + public void setBookWarehouseName(String bookWarehouseName) + { + this.bookWarehouseName = bookWarehouseName; + } + + public Long getBookLocationId() + { + return bookLocationId; + } + + public void setBookLocationId(Long bookLocationId) + { + this.bookLocationId = bookLocationId; + } + + public String getBookLocationCode() + { + return bookLocationCode; + } + + public void setBookLocationCode(String bookLocationCode) + { + this.bookLocationCode = bookLocationCode; + } + + public String getBookLocationName() + { + return bookLocationName; + } + + public void setBookLocationName(String bookLocationName) + { + this.bookLocationName = bookLocationName; + } + + public Long getInventoryWarehouseId() + { + return inventoryWarehouseId; + } + + public void setInventoryWarehouseId(Long inventoryWarehouseId) + { + this.inventoryWarehouseId = inventoryWarehouseId; + } + + public String getInventoryWarehouseCode() + { + return inventoryWarehouseCode; + } + + public void setInventoryWarehouseCode(String inventoryWarehouseCode) + { + this.inventoryWarehouseCode = inventoryWarehouseCode; + } + + public String getInventoryWarehouseName() + { + return inventoryWarehouseName; + } + + public void setInventoryWarehouseName(String inventoryWarehouseName) + { + this.inventoryWarehouseName = inventoryWarehouseName; + } + + public Long getInventoryLocationId() + { + return inventoryLocationId; + } + + public void setInventoryLocationId(Long inventoryLocationId) + { + this.inventoryLocationId = inventoryLocationId; + } + + public String getInventoryLocationCode() + { + return inventoryLocationCode; + } + + public void setInventoryLocationCode(String inventoryLocationCode) + { + this.inventoryLocationCode = inventoryLocationCode; + } + + public String getInventoryLocationName() + { + return inventoryLocationName; + } + + public void setInventoryLocationName(String inventoryLocationName) + { + this.inventoryLocationName = inventoryLocationName; + } + + public Long getInventoryUserId() + { + return inventoryUserId; + } + + public void setInventoryUserId(Long inventoryUserId) + { + this.inventoryUserId = inventoryUserId; + } + + public String getInventoryUserName() + { + return inventoryUserName; + } + + public void setInventoryUserName(String inventoryUserName) + { + this.inventoryUserName = inventoryUserName; + } + + public Date getInventoryTime() + { + return inventoryTime; + } + + public void setInventoryTime(Date inventoryTime) + { + this.inventoryTime = inventoryTime; + } + + public String getConfirmStatus() + { + return confirmStatus; + } + + public void setConfirmStatus(String confirmStatus) + { + this.confirmStatus = confirmStatus; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("surplusId", getSurplusId()) + .append("taskId", getTaskId()) + .append("taskNo", getTaskNo()) + .append("surplusType", getSurplusType()) + .append("identifyMethod", getIdentifyMethod()) + .append("assetId", getAssetId()) + .append("assetCode", getAssetCode()) + .append("assetName", getAssetName()) + .append("categoryId", getCategoryId()) + .append("categoryCode", getCategoryCode()) + .append("categoryName", getCategoryName()) + .append("specModel", getSpecModel()) + .append("brand", getBrand()) + .append("tagId", getTagId()) + .append("tagCode", getTagCode()) + .append("epcCode", getEpcCode()) + .append("bookAssetStatus", getBookAssetStatus()) + .append("bookWarehouseId", getBookWarehouseId()) + .append("bookWarehouseCode", getBookWarehouseCode()) + .append("bookWarehouseName", getBookWarehouseName()) + .append("bookLocationId", getBookLocationId()) + .append("bookLocationCode", getBookLocationCode()) + .append("bookLocationName", getBookLocationName()) + .append("inventoryWarehouseId", getInventoryWarehouseId()) + .append("inventoryWarehouseCode", getInventoryWarehouseCode()) + .append("inventoryWarehouseName", getInventoryWarehouseName()) + .append("inventoryLocationId", getInventoryLocationId()) + .append("inventoryLocationCode", getInventoryLocationCode()) + .append("inventoryLocationName", getInventoryLocationName()) + .append("inventoryUserId", getInventoryUserId()) + .append("inventoryUserName", getInventoryUserName()) + .append("inventoryTime", getInventoryTime()) + .append("confirmStatus", getConfirmStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("delFlag", getDelFlag()) + .toString(); + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsInventoryTaskMapper.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsInventoryTaskMapper.java index 9c97dc5..766648c 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsInventoryTaskMapper.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsInventoryTaskMapper.java @@ -4,6 +4,7 @@ import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.asset.domain.AmsInventoryTask; import com.ruoyi.asset.domain.AmsInventoryTaskItem; +import com.ruoyi.asset.domain.AmsInventoryTaskSurplus; /** * 盘点任务管理Mapper接口 @@ -79,6 +80,90 @@ public interface AmsInventoryTaskMapper */ public int updateAmsInventoryTaskItemResult(AmsInventoryTaskItem amsInventoryTaskItem); + /** + * 查询盘点任务盘盈明细列表 + * + * @param taskId 盘点任务ID + * @return 盘盈明细集合 + */ + public List selectAmsInventoryTaskSurplusList(Long taskId); + + /** + * 加锁查询盘点任务盘盈明细 + * + * @param surplusId 盘盈明细ID + * @return 盘盈明细 + */ + public AmsInventoryTaskSurplus selectAmsInventoryTaskSurplusBySurplusIdForUpdate(Long surplusId); + + /** + * 统计任务应盘明细中的资产数量 + * + * @param taskId 盘点任务ID + * @param assetId 资产ID + * @return 数量 + */ + public int countInventoryTaskItemByTaskIdAndAssetId(@Param("taskId") Long taskId, + @Param("assetId") Long assetId); + + /** + * 统计任务盘盈明细中的资产数量 + * + * @param taskId 盘点任务ID + * @param assetId 资产ID + * @return 数量 + */ + public int countInventoryTaskSurplusByAssetId(@Param("taskId") Long taskId, + @Param("assetId") Long assetId); + + /** + * 统计任务盘盈明细中的 EPC 数量 + * + * @param taskId 盘点任务ID + * @param epcCode EPC 编码 + * @return 数量 + */ + public int countInventoryTaskSurplusByEpcCode(@Param("taskId") Long taskId, + @Param("epcCode") String epcCode); + + /** + * 新增盘点任务盘盈明细 + * + * @param surplus 盘盈明细 + * @return 结果 + */ + public int insertAmsInventoryTaskSurplus(AmsInventoryTaskSurplus surplus); + + /** + * 确认任务下所有盘盈明细 + * + * @param taskId 盘点任务ID + * @param confirmStatus 确认状态 + * @param updateBy 更新人 + * @return 结果 + */ + public int confirmAmsInventoryTaskSurplusByTaskId(@Param("taskId") Long taskId, + @Param("confirmStatus") String confirmStatus, + @Param("updateBy") String updateBy); + + /** + * 逻辑删除盘盈明细 + * + * @param surplusId 盘盈明细ID + * @param updateBy 更新人 + * @return 结果 + */ + public int deleteAmsInventoryTaskSurplusBySurplusId(@Param("surplusId") Long surplusId, + @Param("updateBy") String updateBy); + + /** + * 按任务逻辑删除盘盈明细 + * + * @param taskId 盘点任务ID + * @return 结果 + */ + public int deleteAmsInventoryTaskSurplusByTaskId(Long taskId); + /** * 删除盘点任务管理 * diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsInventoryTaskService.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsInventoryTaskService.java index 952e89c..31ca3d4 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsInventoryTaskService.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsInventoryTaskService.java @@ -2,6 +2,7 @@ package com.ruoyi.asset.service; import java.util.List; import com.ruoyi.asset.domain.AmsInventoryTask; +import com.ruoyi.asset.domain.AmsInventoryTaskSurplus; /** * 盘点任务管理Service接口 @@ -64,6 +65,27 @@ public interface IAmsInventoryTaskService public int submitResult(AmsInventoryTask amsInventoryTask, Long operateUserId, String operateUserName, String operateLoginName); + /** + * 新增盘点任务盘盈明细 + * + * @param surplus 盘盈明细 + * @param operateUserId 操作用户ID + * @param operateUserName 操作用户名称 + * @param operateLoginName 操作登录账号 + * @return 结果 + */ + public int addSurplusItem(AmsInventoryTaskSurplus surplus, Long operateUserId, String operateUserName, + String operateLoginName); + + /** + * 删除盘点任务盘盈明细 + * + * @param surplusId 盘盈明细ID + * @param operateLoginName 操作登录账号 + * @return 结果 + */ + public int deleteSurplusItem(Long surplusId, String operateLoginName); + /** * 确认盘点结果,只写履历,不调整资产台账 * diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImpl.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImpl.java index d999175..3720cd5 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImpl.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImpl.java @@ -10,18 +10,25 @@ import java.util.Map; import java.util.Objects; import com.ruoyi.asset.constant.AssetLifecycleBusinessType; import com.ruoyi.asset.constant.AssetStatus; +import com.ruoyi.asset.constant.InventoryIdentifyMethod; import com.ruoyi.asset.constant.InventoryResult; import com.ruoyi.asset.constant.InventoryScopeType; +import com.ruoyi.asset.constant.InventorySurplusStatus; +import com.ruoyi.asset.constant.InventorySurplusType; import com.ruoyi.asset.constant.InventoryTaskStatus; +import com.ruoyi.asset.constant.RfidBindStatus; import com.ruoyi.asset.domain.AmsAsset; import com.ruoyi.asset.domain.AmsAssetCategory; import com.ruoyi.asset.domain.AmsAssetLifecycleLog; import com.ruoyi.asset.domain.AmsAssetLocation; import com.ruoyi.asset.domain.AmsInventoryTask; import com.ruoyi.asset.domain.AmsInventoryTaskItem; +import com.ruoyi.asset.domain.AmsInventoryTaskSurplus; +import com.ruoyi.asset.domain.AmsRfidTag; import com.ruoyi.asset.domain.AmsWarehouse; import com.ruoyi.asset.mapper.AmsAssetMapper; import com.ruoyi.asset.mapper.AmsInventoryTaskMapper; +import com.ruoyi.asset.mapper.AmsRfidTagMapper; import com.ruoyi.asset.service.IAmsAssetCategoryService; import com.ruoyi.asset.service.IAmsAssetLocationService; import com.ruoyi.asset.service.IAmsInventoryTaskService; @@ -64,6 +71,9 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService @Autowired private AmsAssetMapper amsAssetMapper; + @Autowired + private AmsRfidTagMapper amsRfidTagMapper; + @Autowired private ISysCodeRuleService sysCodeRuleService; @@ -245,6 +255,7 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService { throw new ServiceException("盘点明细结果保存失败"); } + validateSurplusItemsBeforeSubmit(current); current.setTaskStatus(InventoryTaskStatus.PENDING_RESULT_CONFIRM); current.setUpdateBy(StringUtils.trim(operateLoginName)); @@ -257,6 +268,63 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService return 1; } + /** + * 新增盘盈明细:盘盈是任务范围外额外盘到的实物,不能复用应盘快照行。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int addSurplusItem(AmsInventoryTaskSurplus surplus, Long operateUserId, String operateUserName, + String operateLoginName) + { + if (StringUtils.isNull(surplus) || StringUtils.isNull(surplus.getTaskId())) + { + throw new ServiceException("盘点任务ID不能为空"); + } + AmsInventoryTask task = requireTaskForUpdate(surplus.getTaskId()); + requireAnyStatus(task, new String[] {InventoryTaskStatus.ISSUED, InventoryTaskStatus.INVENTORYING}, + "仅已下发或盘点中的任务允许新增盘盈"); + validateOperator(operateUserId, operateUserName, "盘点人"); + validateLoginName(operateLoginName); + + Date now = DateUtils.getNowDate(); + AmsInventoryTaskSurplus result = buildSurplusItem(task, surplus, operateUserId, operateUserName, + operateLoginName, now); + ensureSurplusNotDuplicate(result); + if (amsInventoryTaskMapper.insertAmsInventoryTaskSurplus(result) != 1 + || StringUtils.isNull(result.getSurplusId())) + { + throw new ServiceException("盘盈明细保存失败"); + } + moveTaskToInventoryingIfIssued(task, operateLoginName, now); + return 1; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteSurplusItem(Long surplusId, String operateLoginName) + { + if (StringUtils.isNull(surplusId)) + { + throw new ServiceException("盘盈明细ID不能为空"); + } + validateLoginName(operateLoginName); + AmsInventoryTaskSurplus surplus = + amsInventoryTaskMapper.selectAmsInventoryTaskSurplusBySurplusIdForUpdate(surplusId); + if (StringUtils.isNull(surplus)) + { + throw new ServiceException("盘盈明细不存在或已删除"); + } + AmsInventoryTask task = requireTaskForUpdate(surplus.getTaskId()); + requireAnyStatus(task, new String[] {InventoryTaskStatus.ISSUED, InventoryTaskStatus.INVENTORYING}, + "仅结果提交前允许删除盘盈"); + if (amsInventoryTaskMapper.deleteAmsInventoryTaskSurplusBySurplusId(surplusId, + StringUtils.trim(operateLoginName)) != 1) + { + throw new ServiceException("盘盈明细状态已变化,删除失败"); + } + return 1; + } + /** * 确认盘点结果:逐资产写盘点履历,不更新资产台账当前状态或归属。 */ @@ -277,7 +345,7 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService Date now = DateUtils.getNowDate(); for (AmsInventoryTaskItem item : items) { - if (!InventoryResult.isValid(item.getInventoryResult())) + if (!InventoryResult.isExpectedItemResult(item.getInventoryResult())) { throw new ServiceException(StringUtils.format("资产【{}】盘点结果无效", item.getAssetCode())); } @@ -299,6 +367,34 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService lifecycleLog.setRemark(buildLifecycleRemark(item)); assetLifecycleService.recordLifecycle(asset, asset, lifecycleLog); } + List surplusItems = sortedSurplusItems(task); + for (AmsInventoryTaskSurplus surplus : surplusItems) + { + requireSurplusRecorded(surplus); + if (StringUtils.isNotNull(surplus.getAssetId())) + { + AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(surplus.getAssetId()); + if (StringUtils.isNull(asset)) + { + throw new ServiceException(StringUtils.format("盘盈资产【{}】不存在或已删除", + surplus.getAssetCode())); + } + AmsAssetLifecycleLog lifecycleLog = new AmsAssetLifecycleLog(); + lifecycleLog.setBusinessType(AssetLifecycleBusinessType.INVENTORY); + lifecycleLog.setSourceOrderId(task.getTaskId()); + lifecycleLog.setSourceOrderNo(task.getTaskNo()); + lifecycleLog.setSourceItemId(surplus.getSurplusId()); + lifecycleLog.setOperateUserId(operateUserId); + lifecycleLog.setOperateUserName(StringUtils.trim(operateUserName)); + lifecycleLog.setOperateTime(now); + lifecycleLog.setChangeSummary("确认盘盈:" + surplus.getSurplusType()); + lifecycleLog.setCreateBy(StringUtils.trim(operateLoginName)); + lifecycleLog.setRemark(buildSurplusLifecycleRemark(surplus)); + assetLifecycleService.recordLifecycle(asset, asset, lifecycleLog); + } + } + amsInventoryTaskMapper.confirmAmsInventoryTaskSurplusByTaskId(task.getTaskId(), + InventorySurplusStatus.CONFIRMED, StringUtils.trim(operateLoginName)); task.setTaskStatus(InventoryTaskStatus.INVENTORY_DONE); task.setFinishTime(now); @@ -355,6 +451,7 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService AmsInventoryTask task = requireTaskForUpdate(taskId); requireStatus(task, InventoryTaskStatus.DRAFT, "仅草稿盘点任务允许删除"); amsInventoryTaskMapper.deleteAmsInventoryTaskItemByTaskId(taskId); + amsInventoryTaskMapper.deleteAmsInventoryTaskSurplusByTaskId(taskId); if (amsInventoryTaskMapper.deleteAmsInventoryTaskByTaskId(taskId) != 1) { throw new ServiceException("盘点任务状态已变化,删除失败"); @@ -497,7 +594,7 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService Long operateUserId, String operateUserName, String operateLoginName, Date now) { String inventoryResult = StringUtils.trim(submittedItem.getInventoryResult()); - if (!InventoryResult.isValid(inventoryResult)) + if (!InventoryResult.isExpectedItemResult(inventoryResult)) { throw new ServiceException(StringUtils.format("资产【{}】盘点结果不合法", currentItem.getAssetCode())); } @@ -517,6 +614,226 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService return resultItem; } + private AmsInventoryTaskSurplus buildSurplusItem(AmsInventoryTask task, AmsInventoryTaskSurplus submittedSurplus, + Long operateUserId, String operateUserName, String operateLoginName, Date now) + { + String identifyMethod = StringUtils.trim(submittedSurplus.getIdentifyMethod()); + if (!InventoryIdentifyMethod.isValid(identifyMethod)) + { + throw new ServiceException("盘盈识别方式不合法"); + } + validateLength(submittedSurplus.getRemark(), 500, "盘盈备注"); + + AmsInventoryTaskSurplus surplus; + if (StringUtils.equals(InventoryIdentifyMethod.EPC, identifyMethod)) + { + surplus = buildEpcSurplus(task, submittedSurplus); + } + else if (StringUtils.equals(InventoryIdentifyMethod.ASSET_CODE, identifyMethod)) + { + surplus = buildAssetCodeSurplus(task, submittedSurplus); + } + else + { + surplus = buildManualSurplus(submittedSurplus); + } + + surplus.setTaskId(task.getTaskId()); + surplus.setTaskNo(task.getTaskNo()); + fillSurplusInventoryLocation(surplus, submittedSurplus); + surplus.setInventoryUserId(operateUserId); + surplus.setInventoryUserName(StringUtils.trim(operateUserName)); + surplus.setInventoryTime(now); + surplus.setConfirmStatus(InventorySurplusStatus.RECORDED); + surplus.setCreateBy(StringUtils.trim(operateLoginName)); + surplus.setCreateTime(now); + surplus.setRemark(StringUtils.trim(submittedSurplus.getRemark())); + surplus.setDelFlag(DEL_FLAG_NORMAL); + return surplus; + } + + private AmsInventoryTaskSurplus buildEpcSurplus(AmsInventoryTask task, AmsInventoryTaskSurplus submittedSurplus) + { + String epcCode = StringUtils.trim(submittedSurplus.getEpcCode()); + if (StringUtils.isEmpty(epcCode)) + { + throw new ServiceException("EPC识别盘盈时EPC不能为空"); + } + validateLength(epcCode, 128, "EPC编码"); + + AmsInventoryTaskSurplus surplus = new AmsInventoryTaskSurplus(); + surplus.setIdentifyMethod(InventoryIdentifyMethod.EPC); + surplus.setEpcCode(epcCode); + AmsRfidTag tag = amsRfidTagMapper.selectAmsRfidTagByEpcForUpdate(epcCode); + if (StringUtils.isNotNull(tag)) + { + fillTagSnapshot(surplus, tag); + if (StringUtils.equals(RfidBindStatus.BOUND, tag.getBindStatus()) && StringUtils.isNotNull(tag.getAssetId())) + { + AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(tag.getAssetId()); + if (StringUtils.isNull(asset)) + { + throw new ServiceException("EPC绑定的资产不存在或已删除"); + } + requireAssetNotInTaskItems(task.getTaskId(), asset.getAssetId(), asset.getAssetCode()); + surplus.setSurplusType(InventorySurplusType.KNOWN_OUT_OF_SCOPE); + fillAssetSnapshot(surplus, asset); + return surplus; + } + } + + surplus.setSurplusType(InventorySurplusType.UNKNOWN_OBJECT); + fillUnknownSurplusDescription(surplus, submittedSurplus); + return surplus; + } + + private AmsInventoryTaskSurplus buildAssetCodeSurplus(AmsInventoryTask task, + AmsInventoryTaskSurplus submittedSurplus) + { + String assetCode = StringUtils.trim(submittedSurplus.getAssetCode()); + if (StringUtils.isEmpty(assetCode)) + { + throw new ServiceException("资产编码识别盘盈时资产编码不能为空"); + } + validateLength(assetCode, 64, "资产编码"); + + AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetCodeForUpdate(assetCode); + if (StringUtils.isNull(asset)) + { + throw new ServiceException("资产编码不存在或已删除"); + } + requireAssetNotInTaskItems(task.getTaskId(), asset.getAssetId(), asset.getAssetCode()); + + AmsInventoryTaskSurplus surplus = new AmsInventoryTaskSurplus(); + surplus.setSurplusType(InventorySurplusType.KNOWN_OUT_OF_SCOPE); + surplus.setIdentifyMethod(InventoryIdentifyMethod.ASSET_CODE); + fillAssetSnapshot(surplus, asset); + return surplus; + } + + private AmsInventoryTaskSurplus buildManualSurplus(AmsInventoryTaskSurplus submittedSurplus) + { + if (StringUtils.isNotEmpty(StringUtils.trim(submittedSurplus.getEpcCode())) + || StringUtils.isNotEmpty(StringUtils.trim(submittedSurplus.getAssetCode()))) + { + throw new ServiceException("手工盘盈不填写EPC或资产编码,请选择对应识别方式"); + } + if (StringUtils.isEmpty(StringUtils.trim(submittedSurplus.getAssetName()))) + { + throw new ServiceException("手工盘盈资产名称不能为空"); + } + AmsInventoryTaskSurplus surplus = new AmsInventoryTaskSurplus(); + surplus.setSurplusType(InventorySurplusType.UNKNOWN_OBJECT); + surplus.setIdentifyMethod(InventoryIdentifyMethod.MANUAL); + fillUnknownSurplusDescription(surplus, submittedSurplus); + return surplus; + } + + private void fillAssetSnapshot(AmsInventoryTaskSurplus surplus, AmsAsset asset) + { + surplus.setAssetId(asset.getAssetId()); + surplus.setAssetCode(asset.getAssetCode()); + surplus.setAssetName(asset.getAssetName()); + surplus.setCategoryId(asset.getCategoryId()); + surplus.setCategoryCode(asset.getCategoryCode()); + surplus.setCategoryName(asset.getCategoryName()); + surplus.setSpecModel(asset.getSpecModel()); + surplus.setBrand(asset.getBrand()); + surplus.setTagId(asset.getTagId()); + surplus.setTagCode(asset.getTagCode()); + surplus.setEpcCode(asset.getEpcCode()); + surplus.setBookAssetStatus(asset.getAssetStatus()); + surplus.setBookWarehouseId(asset.getWarehouseId()); + surplus.setBookWarehouseCode(asset.getWarehouseCode()); + surplus.setBookWarehouseName(asset.getWarehouseName()); + surplus.setBookLocationId(asset.getLocationId()); + surplus.setBookLocationCode(asset.getLocationCode()); + surplus.setBookLocationName(asset.getLocationName()); + } + + private void fillTagSnapshot(AmsInventoryTaskSurplus surplus, AmsRfidTag tag) + { + surplus.setTagId(tag.getTagId()); + surplus.setTagCode(tag.getTagCode()); + surplus.setEpcCode(tag.getEpcCode()); + } + + private void fillUnknownSurplusDescription(AmsInventoryTaskSurplus surplus, + AmsInventoryTaskSurplus submittedSurplus) + { + validateLength(submittedSurplus.getAssetName(), 100, "盘盈资产名称"); + validateLength(submittedSurplus.getSpecModel(), 100, "盘盈规格型号"); + validateLength(submittedSurplus.getBrand(), 100, "盘盈品牌"); + surplus.setAssetName(StringUtils.trim(submittedSurplus.getAssetName())); + surplus.setSpecModel(StringUtils.trim(submittedSurplus.getSpecModel())); + surplus.setBrand(StringUtils.trim(submittedSurplus.getBrand())); + if (StringUtils.isNotNull(submittedSurplus.getCategoryId())) + { + AmsAssetCategory category = requireEnabledCategory(submittedSurplus.getCategoryId()); + surplus.setCategoryId(category.getCategoryId()); + surplus.setCategoryCode(category.getCategoryCode()); + surplus.setCategoryName(category.getCategoryName()); + } + } + + private void fillSurplusInventoryLocation(AmsInventoryTaskSurplus surplus, + AmsInventoryTaskSurplus submittedSurplus) + { + if (StringUtils.isNull(submittedSurplus.getInventoryWarehouseId()) + || StringUtils.isNull(submittedSurplus.getInventoryLocationId())) + { + throw new ServiceException("盘盈明细需要填写盘点仓库和位置"); + } + AmsWarehouse warehouse = requireEnabledWarehouse(submittedSurplus.getInventoryWarehouseId()); + AmsAssetLocation location = requireEnabledLocation(submittedSurplus.getInventoryLocationId(), warehouse); + surplus.setInventoryWarehouseId(warehouse.getWarehouseId()); + surplus.setInventoryWarehouseCode(warehouse.getWarehouseCode()); + surplus.setInventoryWarehouseName(warehouse.getWarehouseName()); + surplus.setInventoryLocationId(location.getLocationId()); + surplus.setInventoryLocationCode(location.getLocationCode()); + surplus.setInventoryLocationName(location.getLocationName()); + } + + private void ensureSurplusNotDuplicate(AmsInventoryTaskSurplus surplus) + { + if (StringUtils.isNotNull(surplus.getAssetId()) + && amsInventoryTaskMapper.countInventoryTaskSurplusByAssetId(surplus.getTaskId(), + surplus.getAssetId()) > 0) + { + throw new ServiceException(StringUtils.format("资产【{}】已登记为盘盈", surplus.getAssetCode())); + } + if (StringUtils.isNotEmpty(surplus.getEpcCode()) + && amsInventoryTaskMapper.countInventoryTaskSurplusByEpcCode(surplus.getTaskId(), + surplus.getEpcCode()) > 0) + { + throw new ServiceException(StringUtils.format("EPC【{}】已登记为盘盈", surplus.getEpcCode())); + } + } + + private void requireAssetNotInTaskItems(Long taskId, Long assetId, String assetCode) + { + if (amsInventoryTaskMapper.countInventoryTaskItemByTaskIdAndAssetId(taskId, assetId) > 0) + { + throw new ServiceException(StringUtils.format("资产【{}】已在本任务应盘明细中,请直接录入盘点结果", assetCode)); + } + } + + private void moveTaskToInventoryingIfIssued(AmsInventoryTask task, String operateLoginName, Date now) + { + if (!StringUtils.equals(InventoryTaskStatus.ISSUED, task.getTaskStatus())) + { + return; + } + task.setTaskStatus(InventoryTaskStatus.INVENTORYING); + task.setUpdateBy(StringUtils.trim(operateLoginName)); + task.setUpdateTime(now); + putExpectedStatus(task, InventoryTaskStatus.ISSUED); + if (amsInventoryTaskMapper.updateAmsInventoryTaskStatus(task) != 1) + { + throw new ServiceException("盘点任务状态已变化,盘盈登记失败"); + } + } + private void fillInventoryLocation(AmsInventoryTaskItem resultItem, AmsInventoryTaskItem currentItem, AmsInventoryTaskItem submittedItem) { @@ -598,6 +915,36 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService return sortedItems; } + private List sortedSurplusItems(AmsInventoryTask task) + { + List surplusItems = task.getAmsInventoryTaskSurplusList(); + if (surplusItems == null) + { + return new ArrayList<>(); + } + List sortedSurplusItems = new ArrayList<>(surplusItems); + sortedSurplusItems.sort(Comparator.comparing(AmsInventoryTaskSurplus::getSurplusId, + Comparator.nullsLast(Long::compareTo))); + return sortedSurplusItems; + } + + private void validateSurplusItemsBeforeSubmit(AmsInventoryTask task) + { + for (AmsInventoryTaskSurplus surplus : sortedSurplusItems(task)) + { + requireSurplusRecorded(surplus); + } + } + + private void requireSurplusRecorded(AmsInventoryTaskSurplus surplus) + { + if (!StringUtils.equals(InventorySurplusStatus.RECORDED, surplus.getConfirmStatus())) + { + throw new ServiceException(StringUtils.format("盘盈明细【{}】状态无效,不能提交或确认", + StringUtils.nvl(surplus.getAssetCode(), surplus.getEpcCode()))); + } + } + private String buildLifecycleRemark(AmsInventoryTaskItem item) { StringBuilder remark = new StringBuilder("盘点结果:").append(item.getInventoryResult()); @@ -623,6 +970,37 @@ public class AmsInventoryTaskServiceImpl implements IAmsInventoryTaskService return remark.length() > 500 ? remark.substring(0, 500) : remark.toString(); } + private String buildSurplusLifecycleRemark(AmsInventoryTaskSurplus surplus) + { + StringBuilder remark = new StringBuilder("盘盈类型:").append(surplus.getSurplusType()); + remark.append(";识别方式:").append(surplus.getIdentifyMethod()); + if (StringUtils.isNotEmpty(surplus.getAssetCode())) + { + remark.append(";资产编码:").append(surplus.getAssetCode()); + } + if (StringUtils.isNotEmpty(surplus.getEpcCode())) + { + remark.append(";EPC:").append(surplus.getEpcCode()); + } + if (StringUtils.isNotEmpty(surplus.getBookWarehouseName()) + || StringUtils.isNotEmpty(surplus.getBookLocationName())) + { + remark.append(";账面位置:") + .append(StringUtils.nvl(surplus.getBookWarehouseName(), "")) + .append("/") + .append(StringUtils.nvl(surplus.getBookLocationName(), "")); + } + remark.append(";盘点位置:") + .append(StringUtils.nvl(surplus.getInventoryWarehouseName(), "")) + .append("/") + .append(StringUtils.nvl(surplus.getInventoryLocationName(), "")); + if (StringUtils.isNotEmpty(surplus.getRemark())) + { + remark.append(";").append(surplus.getRemark()); + } + return remark.length() > 500 ? remark.substring(0, 500) : remark.toString(); + } + private void requireStatus(AmsInventoryTask task, String requiredStatus, String message) { if (!StringUtils.equals(requiredStatus, task.getTaskStatus())) diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetCategoryMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetCategoryMapper.xml index 06ed8f6..da76891 100644 --- a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetCategoryMapper.xml +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetCategoryMapper.xml @@ -44,7 +44,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetLocationMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetLocationMapper.xml index 7da3e7a..efcdad7 100644 --- a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetLocationMapper.xml +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetLocationMapper.xml @@ -55,6 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + (select count(1) from ams_inbound_order_item where location_id = #{locationId} and del_flag = '0') + (select count(1) from ams_inventory_task where location_id = #{locationId} and del_flag = '0') + (select count(1) from ams_inventory_task_item where del_flag = '0' and (book_location_id = #{locationId} or inventory_location_id = #{locationId})) + + (select count(1) from ams_inventory_task_surplus where del_flag = '0' and (book_location_id = #{locationId} or inventory_location_id = #{locationId})) + (select count(1) from ams_receive_order_item where before_location_id = #{locationId} and del_flag = '0') + (select count(1) from ams_return_order_item where after_location_id = #{locationId} and del_flag = '0') + (select count(1) from ams_transfer_order_item where del_flag = '0' and (old_location_id = #{locationId} or new_location_id = #{locationId})) diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetMapper.xml index f8e96c1..31fa4a6 100644 --- a/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetMapper.xml +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsAssetMapper.xml @@ -142,6 +142,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" union all select count(1) as ref_count from ams_inventory_task_item where asset_id = #{assetId} and del_flag = '0' union all + select count(1) as ref_count from ams_inventory_task_surplus where asset_id = #{assetId} and del_flag = '0' + union all select count(1) as ref_count from ams_receive_order_item where asset_id = #{assetId} and del_flag = '0' union all select count(1) as ref_count from ams_repair_order where asset_id = #{assetId} and del_flag = '0' diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsInventoryTaskMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsInventoryTaskMapper.xml index a9a804b..3e918c5 100644 --- a/ruoyi-asset/src/main/resources/mapper/asset/AmsInventoryTaskMapper.xml +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsInventoryTaskMapper.xml @@ -33,6 +33,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -67,6 +69,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select task_id, task_no, scope_type, warehouse_id, warehouse_code, warehouse_name, location_id, location_code, location_name, category_id, category_code, category_name, @@ -137,6 +181,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by item_id + + + + + + + + + + - @@ -85,6 +86,82 @@ + +

盘盈明细

+
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型识别方式资产编码资产名称EPC账面仓库账面位置盘点仓库盘点位置状态盘点人备注操作
+ 删除 +
+
diff --git a/ruoyi-asset/src/main/resources/templates/asset/task/view.html b/ruoyi-asset/src/main/resources/templates/asset/task/view.html index 799f37f..ea50112 100644 --- a/ruoyi-asset/src/main/resources/templates/asset/task/view.html +++ b/ruoyi-asset/src/main/resources/templates/asset/task/view.html @@ -79,7 +79,7 @@ -

盘点明细

+

应盘明细

@@ -114,6 +114,46 @@
+ +

盘盈明细

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型识别方式资产编码资产名称EPC账面仓库账面位置盘点仓库盘点位置状态盘点人盘点时间备注
+
diff --git a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImplTest.java b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImplTest.java index 7bcaf25..a72808b 100644 --- a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImplTest.java +++ b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsInventoryTaskServiceImplTest.java @@ -10,6 +10,7 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -17,18 +18,25 @@ import java.util.Collections; import java.util.List; import com.ruoyi.asset.constant.AssetLifecycleBusinessType; import com.ruoyi.asset.constant.AssetStatus; +import com.ruoyi.asset.constant.InventoryIdentifyMethod; import com.ruoyi.asset.constant.InventoryResult; import com.ruoyi.asset.constant.InventoryScopeType; +import com.ruoyi.asset.constant.InventorySurplusStatus; +import com.ruoyi.asset.constant.InventorySurplusType; import com.ruoyi.asset.constant.InventoryTaskStatus; +import com.ruoyi.asset.constant.RfidBindStatus; import com.ruoyi.asset.domain.AmsAsset; import com.ruoyi.asset.domain.AmsAssetCategory; import com.ruoyi.asset.domain.AmsAssetLifecycleLog; import com.ruoyi.asset.domain.AmsAssetLocation; import com.ruoyi.asset.domain.AmsInventoryTask; import com.ruoyi.asset.domain.AmsInventoryTaskItem; +import com.ruoyi.asset.domain.AmsInventoryTaskSurplus; +import com.ruoyi.asset.domain.AmsRfidTag; import com.ruoyi.asset.domain.AmsWarehouse; import com.ruoyi.asset.mapper.AmsAssetMapper; import com.ruoyi.asset.mapper.AmsInventoryTaskMapper; +import com.ruoyi.asset.mapper.AmsRfidTagMapper; import com.ruoyi.asset.service.IAmsAssetCategoryService; import com.ruoyi.asset.service.IAmsAssetLocationService; import com.ruoyi.asset.service.IAmsWarehouseService; @@ -51,6 +59,9 @@ class AmsInventoryTaskServiceImplTest @Mock private AmsAssetMapper amsAssetMapper; + @Mock + private AmsRfidTagMapper amsRfidTagMapper; + @Mock private ISysCodeRuleService sysCodeRuleService; @@ -154,6 +165,171 @@ class AmsInventoryTaskServiceImplTest assertEquals(InventoryTaskStatus.PENDING_RESULT_CONFIRM, current.getTaskStatus()); } + /** 应盘快照行不允许提交盘盈,真实盘盈必须进入盘盈明细。 */ + @Test + void submitResultShouldRejectSurplusOnExpectedItem() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.ISSUED); + current.setAmsInventoryTaskItemList(Collections.singletonList(buildPersistedItem())); + AmsInventoryTask submit = new AmsInventoryTask(); + submit.setTaskId(100L); + AmsInventoryTaskItem submitItem = new AmsInventoryTaskItem(); + submitItem.setItemId(500L); + submitItem.setInventoryResult(InventoryResult.SURPLUS); + submit.setAmsInventoryTaskItemList(Collections.singletonList(submitItem)); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.submitResult(submit, 9L, "管理员", "admin")); + + assertTrue(exception.getMessage().contains("不合法")); + verify(amsInventoryTaskMapper, never()).updateAmsInventoryTaskItemResult(any(AmsInventoryTaskItem.class)); + } + + /** EPC 识别到已绑定账内资产时,生成账内范围外盘盈并推进到盘点中。 */ + @Test + void addSurplusByBoundEpcShouldCreateKnownOutOfScopeItem() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.ISSUED); + AmsInventoryTaskSurplus request = buildSurplusRequest(InventoryIdentifyMethod.EPC); + request.setEpcCode("EPC-002"); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + stubWarehouse(); + stubLocation(); + when(amsRfidTagMapper.selectAmsRfidTagByEpcForUpdate("EPC-002")).thenReturn(buildBoundTag()); + when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(2L)).thenReturn(buildOutOfScopeAsset()); + when(amsInventoryTaskMapper.countInventoryTaskItemByTaskIdAndAssetId(100L, 2L)).thenReturn(0); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByAssetId(100L, 2L)).thenReturn(0); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByEpcCode(100L, "EPC-002")).thenReturn(0); + doAnswer(invocation -> { + AmsInventoryTaskSurplus surplus = invocation.getArgument(0); + surplus.setSurplusId(700L); + return 1; + }).when(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(any(AmsInventoryTaskSurplus.class)); + when(amsInventoryTaskMapper.updateAmsInventoryTaskStatus(any(AmsInventoryTask.class))).thenReturn(1); + + assertEquals(1, service.addSurplusItem(request, 9L, "管理员", "admin")); + + ArgumentCaptor surplusCaptor = + ArgumentCaptor.forClass(AmsInventoryTaskSurplus.class); + verify(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(surplusCaptor.capture()); + AmsInventoryTaskSurplus saved = surplusCaptor.getValue(); + assertEquals(InventorySurplusType.KNOWN_OUT_OF_SCOPE, saved.getSurplusType()); + assertEquals(InventoryIdentifyMethod.EPC, saved.getIdentifyMethod()); + assertEquals(2L, saved.getAssetId()); + assertEquals("EPC-002", saved.getEpcCode()); + assertEquals(InventorySurplusStatus.RECORDED, saved.getConfirmStatus()); + assertEquals(InventoryTaskStatus.INVENTORYING, current.getTaskStatus()); + } + + /** EPC 未建档或未绑定资产时,生成未知实物盘盈。 */ + @Test + void addSurplusByUnknownEpcShouldCreateUnknownItem() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.INVENTORYING); + AmsInventoryTaskSurplus request = buildSurplusRequest(InventoryIdentifyMethod.EPC); + request.setEpcCode("EPC-UNKNOWN"); + request.setAssetName("未知设备"); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + stubWarehouse(); + stubLocation(); + when(amsRfidTagMapper.selectAmsRfidTagByEpcForUpdate("EPC-UNKNOWN")).thenReturn(null); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByEpcCode(100L, "EPC-UNKNOWN")).thenReturn(0); + doAnswer(invocation -> { + AmsInventoryTaskSurplus surplus = invocation.getArgument(0); + surplus.setSurplusId(701L); + return 1; + }).when(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(any(AmsInventoryTaskSurplus.class)); + + assertEquals(1, service.addSurplusItem(request, 9L, "管理员", "admin")); + + ArgumentCaptor surplusCaptor = + ArgumentCaptor.forClass(AmsInventoryTaskSurplus.class); + verify(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(surplusCaptor.capture()); + AmsInventoryTaskSurplus saved = surplusCaptor.getValue(); + assertEquals(InventorySurplusType.UNKNOWN_OBJECT, saved.getSurplusType()); + assertEquals("未知设备", saved.getAssetName()); + assertNull(saved.getAssetId()); + } + + /** 资产编码识别必须匹配到账内资产,且该资产不能已经在本任务应盘明细中。 */ + @Test + void addSurplusByAssetCodeShouldCreateKnownOutOfScopeItem() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.INVENTORYING); + AmsInventoryTaskSurplus request = buildSurplusRequest(InventoryIdentifyMethod.ASSET_CODE); + request.setAssetCode("A002"); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + stubWarehouse(); + stubLocation(); + when(amsAssetMapper.selectAmsAssetByAssetCodeForUpdate("A002")).thenReturn(buildOutOfScopeAsset()); + when(amsInventoryTaskMapper.countInventoryTaskItemByTaskIdAndAssetId(100L, 2L)).thenReturn(0); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByAssetId(100L, 2L)).thenReturn(0); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByEpcCode(100L, "EPC-002")).thenReturn(0); + doAnswer(invocation -> { + AmsInventoryTaskSurplus surplus = invocation.getArgument(0); + surplus.setSurplusId(702L); + return 1; + }).when(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(any(AmsInventoryTaskSurplus.class)); + + assertEquals(1, service.addSurplusItem(request, 9L, "管理员", "admin")); + + ArgumentCaptor surplusCaptor = + ArgumentCaptor.forClass(AmsInventoryTaskSurplus.class); + verify(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(surplusCaptor.capture()); + AmsInventoryTaskSurplus saved = surplusCaptor.getValue(); + assertEquals(InventorySurplusType.KNOWN_OUT_OF_SCOPE, saved.getSurplusType()); + assertEquals("A002", saved.getAssetCode()); + } + + /** 手工盘盈用于记录没有可靠资产编码或 EPC 的现场实物线索。 */ + @Test + void addManualSurplusShouldCreateUnknownItem() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.INVENTORYING); + AmsInventoryTaskSurplus request = buildSurplusRequest(InventoryIdentifyMethod.MANUAL); + request.setAssetName("无标签显示器"); + request.setSpecModel("24寸"); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + stubWarehouse(); + stubLocation(); + doAnswer(invocation -> { + AmsInventoryTaskSurplus surplus = invocation.getArgument(0); + surplus.setSurplusId(703L); + return 1; + }).when(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(any(AmsInventoryTaskSurplus.class)); + + assertEquals(1, service.addSurplusItem(request, 9L, "管理员", "admin")); + + ArgumentCaptor surplusCaptor = + ArgumentCaptor.forClass(AmsInventoryTaskSurplus.class); + verify(amsInventoryTaskMapper).insertAmsInventoryTaskSurplus(surplusCaptor.capture()); + AmsInventoryTaskSurplus saved = surplusCaptor.getValue(); + assertEquals(InventoryIdentifyMethod.MANUAL, saved.getIdentifyMethod()); + assertEquals(InventorySurplusType.UNKNOWN_OBJECT, saved.getSurplusType()); + assertEquals("无标签显示器", saved.getAssetName()); + } + + /** 同一 EPC 在同一任务中不能重复登记盘盈。 */ + @Test + void addSurplusShouldRejectDuplicateEpc() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.INVENTORYING); + AmsInventoryTaskSurplus request = buildSurplusRequest(InventoryIdentifyMethod.EPC); + request.setEpcCode("EPC-UNKNOWN"); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + stubWarehouse(); + stubLocation(); + when(amsRfidTagMapper.selectAmsRfidTagByEpcForUpdate("EPC-UNKNOWN")).thenReturn(null); + when(amsInventoryTaskMapper.countInventoryTaskSurplusByEpcCode(100L, "EPC-UNKNOWN")).thenReturn(1); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.addSurplusItem(request, 9L, "管理员", "admin")); + + assertTrue(exception.getMessage().contains("已登记为盘盈")); + verify(amsInventoryTaskMapper, never()).insertAmsInventoryTaskSurplus(any(AmsInventoryTaskSurplus.class)); + } + /** 确认结果只写盘点履历,不调用资产台账状态更新。 */ @Test void confirmResultShouldWriteLifecycleWithoutUpdatingAssetLedger() @@ -181,6 +357,33 @@ class AmsInventoryTaskServiceImplTest assertNotNull(current.getFinishTime()); } + /** 确认结果时,账内盘盈写盘点履历并把盘盈明细置为已确认。 */ + @Test + void confirmResultShouldWriteLifecycleForKnownSurplus() + { + AmsInventoryTask current = buildPersistedTask(InventoryTaskStatus.PENDING_RESULT_CONFIRM); + current.setAmsInventoryTaskItemList(Collections.singletonList(buildPersistedItemWithResult())); + current.setAmsInventoryTaskSurplusList(Collections.singletonList(buildPersistedKnownSurplus())); + when(amsInventoryTaskMapper.selectAmsInventoryTaskByTaskIdForUpdate(100L)).thenReturn(current); + when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(buildAsset()); + when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(2L)).thenReturn(buildOutOfScopeAsset()); + when(amsInventoryTaskMapper.updateAmsInventoryTaskStatus(any(AmsInventoryTask.class))).thenReturn(1); + when(amsInventoryTaskMapper.confirmAmsInventoryTaskSurplusByTaskId(100L, + InventorySurplusStatus.CONFIRMED, "admin")).thenReturn(1); + + assertEquals(1, service.confirmResult(100L, 9L, "管理员", "admin")); + + ArgumentCaptor lifecycleCaptor = + ArgumentCaptor.forClass(AmsAssetLifecycleLog.class); + verify(assetLifecycleService, times(2)).recordLifecycle(any(AmsAsset.class), any(AmsAsset.class), + lifecycleCaptor.capture()); + assertTrue(lifecycleCaptor.getAllValues().stream() + .anyMatch(log -> Long.valueOf(700L).equals(log.getSourceItemId()) + && log.getRemark().contains("盘盈类型"))); + verify(amsInventoryTaskMapper).confirmAmsInventoryTaskSurplusByTaskId(100L, + InventorySurplusStatus.CONFIRMED, "admin"); + } + /** 非草稿任务不允许删除。 */ @Test void deleteShouldRejectNonDraftTask() @@ -279,6 +482,70 @@ class AmsInventoryTaskServiceImplTest return asset; } + private AmsAsset buildOutOfScopeAsset() + { + AmsAsset asset = new AmsAsset(); + asset.setAssetId(2L); + asset.setAssetCode("A002"); + asset.setAssetName("资产B"); + asset.setAssetStatus(AssetStatus.IN_STOCK); + asset.setWarehouseId(2L); + asset.setWarehouseCode("WH-002"); + asset.setWarehouseName("二号仓"); + asset.setLocationId(20L); + asset.setLocationCode("LOC-002"); + asset.setLocationName("二号仓A区"); + asset.setTagId(20L); + asset.setTagCode("TAG-002"); + asset.setEpcCode("EPC-002"); + return asset; + } + + private AmsRfidTag buildBoundTag() + { + AmsRfidTag tag = new AmsRfidTag(); + tag.setTagId(20L); + tag.setTagCode("TAG-002"); + tag.setEpcCode("EPC-002"); + tag.setBindStatus(RfidBindStatus.BOUND); + tag.setAssetId(2L); + tag.setAssetCode("A002"); + tag.setAssetName("资产B"); + return tag; + } + + private AmsInventoryTaskSurplus buildSurplusRequest(String identifyMethod) + { + AmsInventoryTaskSurplus surplus = new AmsInventoryTaskSurplus(); + surplus.setTaskId(100L); + surplus.setIdentifyMethod(identifyMethod); + surplus.setInventoryWarehouseId(1L); + surplus.setInventoryLocationId(10L); + return surplus; + } + + private AmsInventoryTaskSurplus buildPersistedKnownSurplus() + { + AmsInventoryTaskSurplus surplus = new AmsInventoryTaskSurplus(); + surplus.setSurplusId(700L); + surplus.setTaskId(100L); + surplus.setTaskNo("PD202606180001"); + surplus.setSurplusType(InventorySurplusType.KNOWN_OUT_OF_SCOPE); + surplus.setIdentifyMethod(InventoryIdentifyMethod.EPC); + surplus.setAssetId(2L); + surplus.setAssetCode("A002"); + surplus.setAssetName("资产B"); + surplus.setEpcCode("EPC-002"); + surplus.setBookWarehouseName("二号仓"); + surplus.setBookLocationName("二号仓A区"); + surplus.setInventoryWarehouseId(1L); + surplus.setInventoryWarehouseName("一号仓"); + surplus.setInventoryLocationId(10L); + surplus.setInventoryLocationName("一号仓A区"); + surplus.setConfirmStatus(InventorySurplusStatus.RECORDED); + return surplus; + } + private void stubWarehouse() { AmsWarehouse warehouse = new AmsWarehouse();