package cn.iocoder.yudao.module.mes.service.wm.stocktaking.task;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.ObjUtil;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskMapper;
|
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockTakingTaskStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
|
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
|
import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanService;
|
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
|
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import jakarta.annotation.Resource;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.math.BigDecimal;
|
import java.util.List;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.Map;
|
import java.util.Set;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
/**
|
* MES 盘点任务 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class MesWmStockTakingTaskServiceImpl implements MesWmStockTakingTaskService {
|
|
@Resource
|
private MesWmStockTakingTaskMapper stockTakingTaskMapper;
|
|
@Resource
|
private MesWmMaterialStockService materialStockService;
|
@Resource
|
private MesWmStockTakingPlanService stockTakingPlanService;
|
@Lazy
|
@Resource
|
private MesWmStockTakingTaskLineService stockTakingTaskLineService;
|
@Lazy
|
@Resource
|
private MesWmStockTakingTaskResultService stockTakingTaskResultService;
|
|
@Resource
|
private MesWmTransactionService wmTransactionService;
|
|
@Resource
|
private AdminUserApi adminUserApi;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createStockTakingTask(MesWmStockTakingTaskSaveReqVO createReqVO) {
|
// 1.1 校验 code 唯一
|
validateStockTakingTaskCodeUnique(null, createReqVO.getCode());
|
// 1.2 校验方案存在
|
adminUserApi.validateUser(createReqVO.getUserId());
|
// 1.3 校验方案可用(如果有 planId)
|
if (createReqVO.getPlanId() != null) {
|
stockTakingPlanService.validateStockTakingPlanEnabled(createReqVO.getPlanId());
|
}
|
|
// 2. 插入任务
|
MesWmStockTakingTaskDO task = BeanUtils.toBean(createReqVO, MesWmStockTakingTaskDO.class)
|
.setStatus(MesWmStockTakingTaskStatusEnum.PREPARE.getStatus());
|
stockTakingTaskMapper.insert(task);
|
|
// 3. 根据方案生成盘点明细行(仅当有 plan 时)
|
if (task.getPlanId() != null) {
|
stockTakingTaskLineService.generateStockTakingLines(task, true);
|
}
|
return task.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateStockTakingTask(MesWmStockTakingTaskSaveReqVO updateReqVO) {
|
// 1.1 校验任务存在且为草稿状态
|
validateStockTakingTaskExistsAndPrepare(updateReqVO.getId());
|
// 1.2 校验 code 唯一
|
validateStockTakingTaskCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
|
// 1.3 校验方案存在
|
adminUserApi.validateUser(updateReqVO.getUserId());
|
// 1.4 校验方案可用(如果有 planId)
|
if (updateReqVO.getPlanId() != null) {
|
stockTakingPlanService.validateStockTakingPlanEnabled(updateReqVO.getPlanId());
|
}
|
|
// 2. 更新任务
|
MesWmStockTakingTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesWmStockTakingTaskDO.class);
|
stockTakingTaskMapper.updateById(updateObj);
|
|
// 3. 重新生成盘点明细行(仅当有 plan 时)
|
if (updateObj.getPlanId() != null) {
|
stockTakingTaskLineService.generateStockTakingLines(updateObj, false);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteStockTakingTask(Long id) {
|
// 1. 校验任务存在且为草稿状态
|
validateStockTakingTaskExistsAndPrepare(id);
|
|
// 2. 删除任务和明细
|
stockTakingTaskResultService.deleteStockTakingTaskResultByTaskId(id);
|
stockTakingTaskLineService.deleteStockTakingTaskLineByTaskId(id);
|
stockTakingTaskMapper.deleteById(id);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void submitStockTakingTask(Long id) {
|
// 1.1 校验任务存在且为草稿状态
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExistsAndPrepare(id);
|
// 1.2 检查要盘点的内容
|
List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id);
|
if (CollUtil.isEmpty(lines)) {
|
throw exception(WM_STOCK_TAKING_TASK_NO_LINE);
|
}
|
|
// 2. 更新任务状态为审批中
|
stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
|
.setStatus(MesWmStockTakingTaskStatusEnum.APPROVING.getStatus()));
|
|
// 3. 根据冻结标识,对物资进行冻结
|
if (Boolean.TRUE.equals(task.getFrozen())) {
|
updateMaterialStockFrozen(id, lines, true);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void finishStockTakingTask(Long id) {
|
// 1. 校验任务存在且为审批中状态
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExistsAndApproving(id);
|
validateAllLinesCounted(id);
|
|
// 2. 更新任务状态为已完成
|
stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
|
.setStatus(MesWmStockTakingTaskStatusEnum.FINISHED.getStatus()));
|
|
// 3. 解冻库存
|
if (Boolean.TRUE.equals(task.getFrozen())) {
|
List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id);
|
updateMaterialStockFrozen(id, lines, false);
|
}
|
// 与完成状态、解冻共用事务;差异入账失败时整单回滚。
|
processStockTakingDifference(id);
|
}
|
|
private void validateAllLinesCounted(Long taskId) {
|
List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(taskId);
|
if (CollUtil.isEmpty(lines)) {
|
throw exception(WM_STOCK_TAKING_TASK_NO_LINE);
|
}
|
List<MesWmStockTakingTaskResultDO> results = stockTakingTaskResultService.getStockTakingTaskResultListByTaskId(taskId);
|
Map<Long, MesWmStockTakingTaskLineDO> lineMap = new HashMap<>();
|
lines.forEach(line -> lineMap.put(line.getId(), line));
|
Set<Long> counted = new HashSet<>();
|
for (MesWmStockTakingTaskResultDO result : results) {
|
MesWmStockTakingTaskLineDO line = lineMap.get(result.getLineId());
|
if (line == null || !ObjUtil.equal(result.getTaskId(), taskId)
|
|| !ObjUtil.equal(result.getMaterialStockId(), line.getMaterialStockId())
|
|| !ObjUtil.equal(result.getItemId(), line.getItemId())
|
|| !ObjUtil.equal(result.getBatchId(), line.getBatchId())
|
|| !ObjUtil.equal(result.getWarehouseId(), line.getWarehouseId())
|
|| !ObjUtil.equal(result.getLocationId(), line.getLocationId())
|
|| !ObjUtil.equal(result.getAreaId(), line.getAreaId())
|
|| !ObjUtil.equal(result.getQuantity(), line.getQuantity())
|
|| result.getTakingQuantity() == null || result.getTakingQuantity().signum() < 0
|
|| !counted.add(result.getLineId())) {
|
throw exception(WM_STOCK_TAKING_TASK_RESULT_LINE_INVALID);
|
}
|
}
|
List<Long> missing = new ArrayList<>();
|
lines.stream().map(MesWmStockTakingTaskLineDO::getId)
|
.filter(lineId -> !counted.contains(lineId)).forEach(missing::add);
|
if (!missing.isEmpty()) {
|
throw exception(WM_STOCK_TAKING_TASK_INCOMPLETE, missing);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void cancelStockTakingTask(Long id) {
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
|
if (MesWmStockTakingTaskStatusEnum.FINISHED.getStatus().equals(task.getStatus())
|
|| MesWmStockTakingTaskStatusEnum.CANCELED.getStatus().equals(task.getStatus())) {
|
throw exception(WM_STOCK_TAKING_TASK_CANNOT_CANCEL);
|
}
|
task.setStatus(MesWmStockTakingTaskStatusEnum.CANCELED.getStatus());
|
stockTakingTaskMapper.updateById(task);
|
if (Boolean.TRUE.equals(task.getFrozen())) {
|
updateMaterialStockFrozen(id, stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
|
}
|
}
|
|
@Override
|
public MesWmStockTakingTaskDO getStockTakingTask(Long id) {
|
return stockTakingTaskMapper.selectById(id);
|
}
|
|
@Override
|
public MesWmStockTakingTaskDO validateStockTakingTaskExists(Long id) {
|
MesWmStockTakingTaskDO task = stockTakingTaskMapper.selectById(id);
|
if (task == null) {
|
throw exception(WM_STOCK_TAKING_TASK_NOT_EXISTS);
|
}
|
return task;
|
}
|
|
@Override
|
public MesWmStockTakingTaskDO validateStockTakingTaskExistsAndPrepare(Long id) {
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
|
if (ObjUtil.notEqual(MesWmStockTakingTaskStatusEnum.PREPARE.getStatus(), task.getStatus())) {
|
throw exception(WM_STOCK_TAKING_TASK_NOT_PREPARE);
|
}
|
return task;
|
}
|
|
@Override
|
public MesWmStockTakingTaskDO validateStockTakingTaskExistsAndApproving(Long id) {
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
|
if (ObjUtil.notEqual(MesWmStockTakingTaskStatusEnum.APPROVING.getStatus(), task.getStatus())) {
|
throw exception(WM_STOCK_TAKING_TASK_NOT_APPROVING);
|
}
|
return task;
|
}
|
|
@Override
|
public PageResult<MesWmStockTakingTaskDO> getStockTakingTaskPage(MesWmStockTakingTaskPageReqVO pageReqVO) {
|
return stockTakingTaskMapper.selectPage(pageReqVO);
|
}
|
|
private void updateMaterialStockFrozen(Long taskId, List<MesWmStockTakingTaskLineDO> lines, boolean frozen) {
|
if (CollUtil.isEmpty(lines)) {
|
return;
|
}
|
List<Long> materialStockIds = convertList(lines,
|
MesWmStockTakingTaskLineDO::getMaterialStockId, line -> line.getMaterialStockId() != null);
|
materialStockService.setMaterialStockFreeze(materialStockIds, "STOCK_TAKING", taskId, frozen);
|
}
|
|
private void validateStockTakingTaskCodeUnique(Long id, String code) {
|
MesWmStockTakingTaskDO task = stockTakingTaskMapper.selectByCode(code);
|
if (task == null) {
|
return;
|
}
|
if (ObjUtil.notEqual(task.getId(), id)) {
|
throw exception(WM_STOCK_TAKING_TASK_CODE_DUPLICATE);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void processStockTakingDifference(Long id) {
|
// 1. 校验任务存在且已完成
|
MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
|
if (!MesWmStockTakingTaskStatusEnum.FINISHED.getStatus().equals(task.getStatus())) {
|
throw exception(WM_STOCK_TAKING_TASK_NOT_FINISHED);
|
}
|
// 原子认领;重复调用不会重复生成盘盈/盘亏流水。
|
if (stockTakingTaskMapper.claimDifferenceProcessing(id) == 0) {
|
return;
|
}
|
|
// 2. 获取盘点结果列表
|
List<MesWmStockTakingTaskResultDO> results = stockTakingTaskResultService.getStockTakingTaskResultListByTaskId(id);
|
if (CollUtil.isEmpty(results)) {
|
return; // 无盘点结果,无需处理
|
}
|
|
// 3. 遍历结果,处理差异
|
for (MesWmStockTakingTaskResultDO result : results) {
|
BigDecimal bookQuantity = result.getQuantity(); // 账面数量
|
BigDecimal takingQuantity = result.getTakingQuantity(); // 盘点数量
|
|
if (takingQuantity == null || bookQuantity == null) {
|
continue;
|
}
|
|
// 计算差异:盘点数量 - 账面数量
|
BigDecimal difference = takingQuantity.subtract(bookQuantity);
|
|
if (difference.compareTo(BigDecimal.ZERO) == 0) {
|
continue; // 无差异,跳过
|
}
|
|
// 4. 创建库存调整事务
|
MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
|
.setItemId(result.getItemId())
|
.setBatchId(result.getBatchId())
|
.setBatchCode(result.getBatchCode())
|
.setWarehouseId(result.getWarehouseId())
|
.setLocationId(result.getLocationId())
|
.setAreaId(result.getAreaId())
|
.setMaterialStockId(result.getMaterialStockId())
|
.setBizId(task.getId())
|
.setBizCode(task.getCode())
|
.setBizLineId(result.getLineId());
|
|
if (difference.compareTo(BigDecimal.ZERO) > 0) {
|
// 盘盈:库存增加
|
transaction.setType(MesWmTransactionTypeEnum.IN.getType())
|
.setQuantity(difference)
|
.setBizType(MesBizTypeConstants.WM_STOCK_TAKING);
|
} else {
|
// 盘亏:库存减少
|
transaction.setType(MesWmTransactionTypeEnum.OUT.getType())
|
.setQuantity(difference) // 盘亏为负数,符合出库事务方向
|
.setBizType(MesBizTypeConstants.WM_STOCK_TAKING);
|
}
|
|
wmTransactionService.createTransaction(transaction);
|
}
|
}
|
|
}
|