feat(asset): 新增盘点任务盘盈功能

- 在 AmsInventoryTask 中添加盘盈明细列表属性及对应的 getter/setter 方法
- 在 AmsInventoryTaskController 中增加新增和删除盘盈明细的 API 接口
- 在 AmsInventoryTaskMapper 中新增盘盈明细相关的数据库操作方法
- 在 AmsInventoryTaskMapper.xml 中添加盘盈明细的 resultMap 和 SQL 查询语句
- 在 AmsInventoryTaskServiceImpl 中实现盘盈明细的业务逻辑,包括新增、删除和确认功能
- 更新数据库映射文件以支持统计各类关联数据的引用数量
- 修改盘点任务结果提交逻辑以处理盘盈明细数据
- 在测试类中添加相关单元测试配置
main
yangk 3 days ago
parent 972ea69120
commit 7317920a76

@ -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()
{
}
}

@ -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()
{
}

@ -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()
{
}
}

@ -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()
{
}
}

@ -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)

@ -93,6 +93,9 @@ public class AmsInventoryTask extends BaseEntity
/** 盘点任务明细信息 */
private List<AmsInventoryTaskItem> amsInventoryTaskItemList;
/** 盘点任务盘盈明细信息 */
private List<AmsInventoryTaskSurplus> amsInventoryTaskSurplusList;
public void setTaskId(Long taskId)
{
this.taskId = taskId;
@ -283,6 +286,16 @@ public class AmsInventoryTask extends BaseEntity
this.amsInventoryTaskItemList = amsInventoryTaskItemList;
}
public List<AmsInventoryTaskSurplus> getAmsInventoryTaskSurplusList()
{
return amsInventoryTaskSurplusList;
}
public void setAmsInventoryTaskSurplusList(List<AmsInventoryTaskSurplus> 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();
}
}

@ -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();
}
}

@ -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<AmsInventoryTaskSurplus> 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);
/**
*
*

@ -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);
/**
*
*

@ -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<AmsInventoryTaskSurplus> 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<AmsInventoryTaskSurplus> sortedSurplusItems(AmsInventoryTask task)
{
List<AmsInventoryTaskSurplus> surplusItems = task.getAmsInventoryTaskSurplusList();
if (surplusItems == null)
{
return new ArrayList<>();
}
List<AmsInventoryTaskSurplus> 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()))

@ -44,7 +44,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="countCategoryReferences" parameterType="Long" resultType="int">
select count(1) from ams_asset where category_id = #{categoryId} and del_flag = '0'
select
(select count(1) from ams_asset where category_id = #{categoryId} and del_flag = '0')
+ (select count(1) from ams_inventory_task where category_id = #{categoryId} and del_flag = '0')
+ (select count(1) from ams_inventory_task_item where category_id = #{categoryId} and del_flag = '0')
+ (select count(1) from ams_inventory_task_surplus where category_id = #{categoryId} and del_flag = '0')
</select>
<insert id="insertAmsAssetCategory" parameterType="AmsAssetCategory" useGeneratedKeys="true" keyProperty="categoryId">

@ -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}))

@ -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'

@ -33,6 +33,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap id="AmsInventoryTaskItemCollectionResult" type="AmsInventoryTask" extends="AmsInventoryTaskResult">
<collection property="amsInventoryTaskItemList" ofType="AmsInventoryTaskItem"
column="task_id" select="selectAmsInventoryTaskItemList" />
<collection property="amsInventoryTaskSurplusList" ofType="AmsInventoryTaskSurplus"
column="task_id" select="selectAmsInventoryTaskSurplusList" />
</resultMap>
<resultMap type="AmsInventoryTaskItem" id="AmsInventoryTaskItemResult">
@ -67,6 +69,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="delFlag" column="del_flag" />
</resultMap>
<resultMap type="AmsInventoryTaskSurplus" id="AmsInventoryTaskSurplusResult">
<result property="surplusId" column="surplus_id" />
<result property="taskId" column="task_id" />
<result property="taskNo" column="task_no" />
<result property="surplusType" column="surplus_type" />
<result property="identifyMethod" column="identify_method" />
<result property="assetId" column="asset_id" />
<result property="assetCode" column="asset_code" />
<result property="assetName" column="asset_name" />
<result property="categoryId" column="category_id" />
<result property="categoryCode" column="category_code" />
<result property="categoryName" column="category_name" />
<result property="specModel" column="spec_model" />
<result property="brand" column="brand" />
<result property="tagId" column="tag_id" />
<result property="tagCode" column="tag_code" />
<result property="epcCode" column="epc_code" />
<result property="bookAssetStatus" column="book_asset_status" />
<result property="bookWarehouseId" column="book_warehouse_id" />
<result property="bookWarehouseCode" column="book_warehouse_code" />
<result property="bookWarehouseName" column="book_warehouse_name" />
<result property="bookLocationId" column="book_location_id" />
<result property="bookLocationCode" column="book_location_code" />
<result property="bookLocationName" column="book_location_name" />
<result property="inventoryWarehouseId" column="inventory_warehouse_id" />
<result property="inventoryWarehouseCode" column="inventory_warehouse_code" />
<result property="inventoryWarehouseName" column="inventory_warehouse_name" />
<result property="inventoryLocationId" column="inventory_location_id" />
<result property="inventoryLocationCode" column="inventory_location_code" />
<result property="inventoryLocationName" column="inventory_location_name" />
<result property="inventoryUserId" column="inventory_user_id" />
<result property="inventoryUserName" column="inventory_user_name" />
<result property="inventoryTime" column="inventory_time" />
<result property="confirmStatus" column="confirm_status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="delFlag" column="del_flag" />
</resultMap>
<sql id="selectAmsInventoryTaskVo">
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
</select>
<select id="selectAmsInventoryTaskSurplusList" resultMap="AmsInventoryTaskSurplusResult">
select surplus_id, task_id, task_no, surplus_type, identify_method, asset_id, asset_code, asset_name,
category_id, category_code, category_name, spec_model, brand, tag_id, tag_code, epc_code,
book_asset_status, book_warehouse_id, book_warehouse_code, book_warehouse_name,
book_location_id, book_location_code, book_location_name, inventory_warehouse_id,
inventory_warehouse_code, inventory_warehouse_name, inventory_location_id,
inventory_location_code, inventory_location_name, inventory_user_id, inventory_user_name,
inventory_time, confirm_status, create_by, create_time, update_by, update_time, remark, del_flag
from ams_inventory_task_surplus
where task_id = #{task_id} and del_flag = '0'
order by surplus_id
</select>
<select id="selectAmsInventoryTaskSurplusBySurplusIdForUpdate" parameterType="Long"
resultMap="AmsInventoryTaskSurplusResult">
select surplus_id, task_id, task_no, surplus_type, identify_method, asset_id, asset_code, asset_name,
category_id, category_code, category_name, spec_model, brand, tag_id, tag_code, epc_code,
book_asset_status, book_warehouse_id, book_warehouse_code, book_warehouse_name,
book_location_id, book_location_code, book_location_name, inventory_warehouse_id,
inventory_warehouse_code, inventory_warehouse_name, inventory_location_id,
inventory_location_code, inventory_location_name, inventory_user_id, inventory_user_name,
inventory_time, confirm_status, create_by, create_time, update_by, update_time, remark, del_flag
from ams_inventory_task_surplus
where surplus_id = #{surplusId} and del_flag = '0'
for update
</select>
<select id="countInventoryTaskItemByTaskIdAndAssetId" resultType="int">
select count(1)
from ams_inventory_task_item
where task_id = #{taskId}
and asset_id = #{assetId}
and del_flag = '0'
</select>
<select id="countInventoryTaskSurplusByAssetId" resultType="int">
select count(1)
from ams_inventory_task_surplus
where task_id = #{taskId}
and asset_id = #{assetId}
and del_flag = '0'
</select>
<select id="countInventoryTaskSurplusByEpcCode" resultType="int">
select count(1)
from ams_inventory_task_surplus
where task_id = #{taskId}
and epc_code = #{epcCode}
and del_flag = '0'
</select>
<!-- 盘点下发时使用真实资产台账生成明细快照,不信任前端传入明细。 -->
<select id="selectInventoryAssetSnapshotList" resultMap="AmsInventoryTaskItemResult">
select asset.asset_id, asset.asset_code, asset.asset_name, asset.category_id, asset.category_code,
@ -236,6 +331,47 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and del_flag = '0'
</update>
<insert id="insertAmsInventoryTaskSurplus" parameterType="AmsInventoryTaskSurplus"
useGeneratedKeys="true" keyProperty="surplusId">
insert into ams_inventory_task_surplus (
task_id, task_no, surplus_type, identify_method, asset_id, asset_code, asset_name,
category_id, category_code, category_name, spec_model, brand, tag_id, tag_code, epc_code,
book_asset_status, book_warehouse_id, book_warehouse_code, book_warehouse_name,
book_location_id, book_location_code, book_location_name, inventory_warehouse_id,
inventory_warehouse_code, inventory_warehouse_name, inventory_location_id,
inventory_location_code, inventory_location_name, inventory_user_id, inventory_user_name,
inventory_time, confirm_status, create_by, create_time, remark, del_flag
) values (
#{taskId}, #{taskNo}, #{surplusType}, #{identifyMethod}, #{assetId}, #{assetCode}, #{assetName},
#{categoryId}, #{categoryCode}, #{categoryName}, #{specModel}, #{brand}, #{tagId}, #{tagCode}, #{epcCode},
#{bookAssetStatus}, #{bookWarehouseId}, #{bookWarehouseCode}, #{bookWarehouseName},
#{bookLocationId}, #{bookLocationCode}, #{bookLocationName}, #{inventoryWarehouseId},
#{inventoryWarehouseCode}, #{inventoryWarehouseName}, #{inventoryLocationId},
#{inventoryLocationCode}, #{inventoryLocationName}, #{inventoryUserId}, #{inventoryUserName},
#{inventoryTime}, #{confirmStatus}, #{createBy}, #{createTime}, #{remark}, #{delFlag}
)
</insert>
<update id="confirmAmsInventoryTaskSurplusByTaskId">
update ams_inventory_task_surplus
set confirm_status = #{confirmStatus},
update_by = #{updateBy},
update_time = now()
where task_id = #{taskId}
and confirm_status = 'RECORDED'
and del_flag = '0'
</update>
<update id="deleteAmsInventoryTaskSurplusBySurplusId">
update ams_inventory_task_surplus
set del_flag = '1',
update_by = #{updateBy},
update_time = now()
where surplus_id = #{surplusId}
and del_flag = '0'
and confirm_status = 'RECORDED'
</update>
<update id="deleteAmsInventoryTaskByTaskId" parameterType="Long">
update ams_inventory_task
set del_flag = '1'
@ -268,6 +404,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where task_id = #{taskId} and del_flag = '0'
</update>
<update id="deleteAmsInventoryTaskSurplusByTaskId" parameterType="Long">
update ams_inventory_task_surplus
set del_flag = '1'
where task_id = #{taskId} and del_flag = '0'
</update>
<insert id="batchAmsInventoryTaskItem">
insert into ams_inventory_task_item (
task_id, task_no, asset_id, asset_code, asset_name, category_id, category_code,

@ -61,6 +61,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ (select count(1) from ams_inbound_order where warehouse_id = #{warehouseId} and del_flag = '0')
+ (select count(1) from ams_inventory_task where warehouse_id = #{warehouseId} and del_flag = '0')
+ (select count(1) from ams_inventory_task_item where del_flag = '0' and (book_warehouse_id = #{warehouseId} or inventory_warehouse_id = #{warehouseId}))
+ (select count(1) from ams_inventory_task_surplus where del_flag = '0' and (book_warehouse_id = #{warehouseId} or inventory_warehouse_id = #{warehouseId}))
+ (select count(1) from ams_receive_order_item where before_warehouse_id = #{warehouseId} and del_flag = '0')
+ (select count(1) from ams_return_order where receive_warehouse_id = #{warehouseId} and del_flag = '0')
+ (select count(1) from ams_return_order_item where after_warehouse_id = #{warehouseId} and del_flag = '0')

@ -28,7 +28,7 @@
</div>
</div>
<h4 class="form-header h4">明细结果</h4>
<h4 class="form-header h4">盘明细结果</h4>
<div class="col-sm-12 select-table table-striped">
<table class="table table-bordered table-hover">
<thead>
@ -56,7 +56,8 @@
<select class="form-control" th:name="|amsInventoryTaskItemList[${stat.index}].inventoryResult|"
th:with="type=${@dict.getType('ams_inventory_result')}" required>
<option value="">请选择</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
<option th:each="dict : ${type}" th:if="${dict.dictValue != 'SURPLUS'}"
th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
th:selected="${dict.dictValue == item.inventoryResult}"></option>
</select>
</td>
@ -85,6 +86,82 @@
</table>
</div>
</form>
<h4 class="form-header h4">盘盈明细</h4>
<form class="form-horizontal m" id="form-surplus-add"
th:if="${amsInventoryTask.taskStatus == 'ISSUED' or amsInventoryTask.taskStatus == 'INVENTORYING'}">
<input name="taskId" th:value="${amsInventoryTask.taskId}" type="hidden">
<div class="row">
<div class="col-sm-2">
<select class="form-control" name="identifyMethod" required>
<option value="EPC">EPC识别</option>
<option value="ASSET_CODE">资产编码</option>
<option value="MANUAL">手工记录</option>
</select>
</div>
<div class="col-sm-2"><input class="form-control" name="epcCode" maxlength="128" placeholder="EPC编码"></div>
<div class="col-sm-2"><input class="form-control" name="assetCode" maxlength="64" placeholder="资产编码"></div>
<div class="col-sm-2"><input class="form-control" name="assetName" maxlength="100" placeholder="资产名称"></div>
<div class="col-sm-2">
<select class="form-control" name="inventoryWarehouseId" required>
<option value="">盘点仓库</option>
<option th:each="warehouse : ${warehouseList}" th:value="${warehouse.warehouseId}"
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></option>
</select>
</div>
<div class="col-sm-2">
<select class="form-control" name="inventoryLocationId" required>
<option value="">盘点位置</option>
<option th:each="location : ${locationList}" th:value="${location.locationId}"
th:text="${location.locationCode + ' - ' + location.locationName}"></option>
</select>
</div>
</div>
<div class="row m-t-sm">
<div class="col-sm-10"><input class="form-control" name="remark" maxlength="500" placeholder="备注"></div>
<div class="col-sm-2"><a class="btn btn-primary btn-block" onclick="addSurplus()">新增盘盈</a></div>
</div>
</form>
<div class="col-sm-12 select-table table-striped">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>类型</th>
<th>识别方式</th>
<th>资产编码</th>
<th>资产名称</th>
<th>EPC</th>
<th>账面仓库</th>
<th>账面位置</th>
<th>盘点仓库</th>
<th>盘点位置</th>
<th>状态</th>
<th>盘点人</th>
<th>备注</th>
<th th:if="${amsInventoryTask.taskStatus == 'ISSUED' or amsInventoryTask.taskStatus == 'INVENTORYING'}">操作</th>
</tr>
</thead>
<tbody>
<tr th:each="surplus : ${amsInventoryTask.amsInventoryTaskSurplusList}">
<td th:text="${@dict.getLabel('ams_inventory_surplus_type', surplus.surplusType)}"></td>
<td th:text="${@dict.getLabel('ams_inventory_identify_method', surplus.identifyMethod)}"></td>
<td th:text="${surplus.assetCode}"></td>
<td th:text="${surplus.assetName}"></td>
<td th:text="${surplus.epcCode}"></td>
<td th:text="${surplus.bookWarehouseName}"></td>
<td th:text="${surplus.bookLocationName}"></td>
<td th:text="${surplus.inventoryWarehouseName}"></td>
<td th:text="${surplus.inventoryLocationName}"></td>
<td th:text="${@dict.getLabel('ams_inventory_surplus_status', surplus.confirmStatus)}"></td>
<td th:text="${surplus.inventoryUserName}"></td>
<td th:text="${surplus.remark}"></td>
<td th:if="${amsInventoryTask.taskStatus == 'ISSUED' or amsInventoryTask.taskStatus == 'INVENTORYING'}">
<a class="btn btn-danger btn-xs" th:onclick="|removeSurplus(${surplus.surplusId})|"><i class="fa fa-remove"></i>删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
@ -96,6 +173,24 @@
$.operate.save(prefix + "/result/submit", $("#form-task-result").serialize());
}
}
function addSurplus() {
$.operate.saveModal(prefix + "/surplus/add", $("#form-surplus-add").serialize(), function(result) {
if (result.code == web_status.SUCCESS) {
setTimeout(function() { location.reload(); }, 500);
}
});
}
function removeSurplus(surplusId) {
$.modal.confirm("确认删除该盘盈明细吗?", function() {
$.operate.saveModal(prefix + "/surplus/remove/" + surplusId, {}, function(result) {
if (result.code == web_status.SUCCESS) {
setTimeout(function() { location.reload(); }, 500);
}
});
});
}
</script>
</body>
</html>

@ -79,7 +79,7 @@
</div>
</form>
<h4 class="form-header h4">明细</h4>
<h4 class="form-header h4">盘明细</h4>
<div class="col-sm-12 select-table table-striped">
<table class="table table-bordered table-hover">
<thead>
@ -114,6 +114,46 @@
</tbody>
</table>
</div>
<h4 class="form-header h4">盘盈明细</h4>
<div class="col-sm-12 select-table table-striped">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>类型</th>
<th>识别方式</th>
<th>资产编码</th>
<th>资产名称</th>
<th>EPC</th>
<th>账面仓库</th>
<th>账面位置</th>
<th>盘点仓库</th>
<th>盘点位置</th>
<th>状态</th>
<th>盘点人</th>
<th>盘点时间</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr th:each="surplus : ${amsInventoryTask.amsInventoryTaskSurplusList}">
<td th:text="${@dict.getLabel('ams_inventory_surplus_type', surplus.surplusType)}"></td>
<td th:text="${@dict.getLabel('ams_inventory_identify_method', surplus.identifyMethod)}"></td>
<td th:text="${surplus.assetCode}"></td>
<td th:text="${surplus.assetName}"></td>
<td th:text="${surplus.epcCode}"></td>
<td th:text="${surplus.bookWarehouseName}"></td>
<td th:text="${surplus.bookLocationName}"></td>
<td th:text="${surplus.inventoryWarehouseName}"></td>
<td th:text="${surplus.inventoryLocationName}"></td>
<td th:text="${@dict.getLabel('ams_inventory_surplus_status', surplus.confirmStatus)}"></td>
<td th:text="${surplus.inventoryUserName}"></td>
<td th:text="${#dates.format(surplus.inventoryTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${surplus.remark}"></td>
</tr>
</tbody>
</table>
</div>
</div>
<th:block th:include="include :: footer" />
</body>

@ -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<AmsInventoryTaskSurplus> 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<AmsInventoryTaskSurplus> 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<AmsInventoryTaskSurplus> 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<AmsInventoryTaskSurplus> 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<AmsAssetLifecycleLog> 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();

Loading…
Cancel
Save