liu
2026-10-23 962f78c8383e7c884ddb08c951c15e5ba6eb4cb8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package cn.iocoder.yudao.module.mes.service.wm.stocktaking.task;
 
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.result.MesWmStockTakingTaskResultPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
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.MesWmStockTakingTaskResultMapper;
import jakarta.annotation.Resource;
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.Objects;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_TAKING_TASK_LINE_NOT_MATCH;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_TAKING_TASK_RESULT_NOT_EXISTS;
 
/**
 * MES 盘点结果 Service 实现类
 *
 * @author 超级管理员
 */
@Service
@Validated
public class MesWmStockTakingTaskResultServiceImpl implements MesWmStockTakingTaskResultService {
 
    @Resource
    private MesWmStockTakingTaskResultMapper stockTakingTaskResultMapper;
 
    @Resource
    private MesWmStockTakingTaskLineService stockTakingTaskLineService;
    @Resource
    private MesWmStockTakingTaskService stockTakingTaskService;
 
    @Override
    public PageResult<MesWmStockTakingTaskResultDO> getStockTakingTaskResultPage(
            MesWmStockTakingTaskResultPageReqVO pageReqVO) {
        return stockTakingTaskResultMapper.selectPage(pageReqVO);
    }
 
    @Override
    public MesWmStockTakingTaskResultDO getStockTakingTaskResult(Long id) {
        return stockTakingTaskResultMapper.selectById(id);
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createStockTakingTaskResult(MesWmStockTakingTaskResultSaveReqVO createReqVO) {
        // 1. 校验任务处于盘点中状态,且结果审批已提交、未审核时禁止写入
        stockTakingTaskService.validateStockTakingTaskExistsAndApproving(createReqVO.getTaskId());
        stockTakingTaskService.validateResultApprovalNotPending(createReqVO.getTaskId());
 
        // 2. 确定 lineId 和账面数量
        Long lineId = createReqVO.getLineId();
        MesWmStockTakingTaskLineDO existingLine = null;
        if (lineId == null) {
            // 2.1 基于 taskId + itemId + areaId 查询已存在的 line
            existingLine = stockTakingTaskLineService.getStockTakingTaskLine(
                    createReqVO.getTaskId(), createReqVO.getItemId(), createReqVO.getAreaId());
            if (existingLine != null) {
                lineId = existingLine.getId();
            } else {
                // 2.2 创建新的 line(注意:当盘点结果没有对应的盘点清单行时,会自动创建一个新的盘点任务行)
                lineId = stockTakingTaskLineService.createStockTakingTaskLine(createReqVO);
            }
        } else {
            // 2.3 如果传递了 lineId,从 line 中获取 quantity
            existingLine = stockTakingTaskLineService.validateStockTakingTaskLineExists(lineId);
            // 2.4 防止串单:盘点行必须属于请求指定的盘点任务
            if (ObjUtil.notEqual(existingLine.getTaskId(), createReqVO.getTaskId())) {
                throw exception(WM_STOCK_TAKING_TASK_LINE_NOT_MATCH);
            }
        }
        // 2.5 防止重复:同一盘点任务行只允许存在一条盘点结果(无论实盘数量是否为 0)
        Long finalLineId = lineId;
        boolean resultExists = stockTakingTaskResultMapper.selectListByTaskId(createReqVO.getTaskId()).stream()
                .anyMatch(result -> Objects.equals(result.getLineId(), finalLineId));
        if (resultExists) {
            throw exception(WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN);
        }
        BigDecimal quantity = existingLine != null ? existingLine.getQuantity() : BigDecimal.ZERO; // 账面数量
 
        // 3. 插入 result 记录
        MesWmStockTakingTaskResultDO result = BeanUtils.toBean(createReqVO, MesWmStockTakingTaskResultDO.class)
                .setLineId(lineId).setQuantity(ObjUtil.defaultIfNull(quantity, createReqVO.getTakingQuantity()));
        stockTakingTaskResultMapper.insert(result);
 
        // 4. 更新 line 的盘点数量和状态
        stockTakingTaskLineService.updateStockTakingTaskLineTakingQuantity(lineId, createReqVO.getTakingQuantity());
        return result.getId();
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStockTakingTaskResult(MesWmStockTakingTaskResultSaveReqVO updateReqVO) {
        updateReqVO.setLineId(null); // lineId 不能修改,避免误操作
        // 1.1 校验记录存在
        MesWmStockTakingTaskResultDO oldResult = validateStockTakingTaskResultExists(updateReqVO.getId());
        // 1.2 校验任务处于盘点中状态,且结果审批已提交、未审核时禁止修改
        stockTakingTaskService.validateStockTakingTaskExistsAndApproving(oldResult.getTaskId());
        stockTakingTaskService.validateResultApprovalNotPending(oldResult.getTaskId());
 
        // 2. 更新 result 记录
        MesWmStockTakingTaskResultDO result = BeanUtils.toBean(updateReqVO, MesWmStockTakingTaskResultDO.class);
        stockTakingTaskResultMapper.updateById(result);
 
        // 3. 关联更新 line 的盘点数量(覆盖式更新)
        stockTakingTaskLineService.updateStockTakingTaskLineTakingQuantity(
                oldResult.getLineId(), updateReqVO.getTakingQuantity());
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteStockTakingTaskResult(Long id) {
        // 1.1 校验 result 是否存在
        MesWmStockTakingTaskResultDO result = validateStockTakingTaskResultExists(id);
        // 1.2 校验 task 是否处于 APPROVING 状态,且结果审批已提交、未审核时禁止删除
        stockTakingTaskService.validateStockTakingTaskExistsAndApproving(result.getTaskId());
        stockTakingTaskService.validateResultApprovalNotPending(result.getTaskId());
 
        // 2. 删除 result 记录
        stockTakingTaskResultMapper.deleteById(id);
 
        // 3. 按剩余盘点结果重算对应 line 的实盘数量(无剩余结果时置 0),避免覆盖成负数
        BigDecimal remainingTakingQuantity = stockTakingTaskResultMapper.selectListByTaskId(result.getTaskId()).stream()
                .filter(item -> Objects.equals(item.getLineId(), result.getLineId()))
                .map(MesWmStockTakingTaskResultDO::getTakingQuantity)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        stockTakingTaskLineService.updateStockTakingTaskLineTakingQuantity(
                result.getLineId(), remainingTakingQuantity);
    }
 
    @Override
    public void deleteStockTakingTaskResultByTaskId(Long taskId) {
        stockTakingTaskResultMapper.deleteByTaskId(taskId);
    }
 
    @Override
    public List<MesWmStockTakingTaskResultDO> getStockTakingTaskResultListByTaskId(Long taskId) {
        return stockTakingTaskResultMapper.selectListByTaskId(taskId);
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void upsertResultForImport(MesWmStockTakingTaskLineDO line, BigDecimal takingQuantity) {
        // 1. 查询该任务下是否已存在该行的盘点结果
        MesWmStockTakingTaskResultDO existing = stockTakingTaskResultMapper.selectListByTaskId(line.getTaskId()).stream()
                .filter(result -> Objects.equals(result.getLineId(), line.getId()))
                .findFirst().orElse(null);
        if (existing != null) {
            // 2. 已存在:覆盖实盘数量(重复导入以最后一次为准)
            existing.setTakingQuantity(takingQuantity);
            stockTakingTaskResultMapper.updateById(existing);
            return;
        }
        // 3. 不存在:按盘点行维度新建,账面数量取行账面数量,保证核销差额口径一致
        MesWmStockTakingTaskResultDO result = MesWmStockTakingTaskResultDO.builder()
                .taskId(line.getTaskId()).lineId(line.getId()).materialStockId(line.getMaterialStockId())
                .itemId(line.getItemId()).batchId(line.getBatchId()).batchCode(line.getBatchCode())
                .warehouseId(line.getWarehouseId()).locationId(line.getLocationId()).areaId(line.getAreaId())
                .quantity(line.getQuantity()).takingQuantity(takingQuantity)
                .build();
        stockTakingTaskResultMapper.insert(result);
    }
 
    /**
     * 校验盘点结果是否存在
     *
     * @param id 盘点结果编号
     * @return 盘点结果
     */
    private MesWmStockTakingTaskResultDO validateStockTakingTaskResultExists(Long id) {
        MesWmStockTakingTaskResultDO result = stockTakingTaskResultMapper.selectById(id);
        if (result == null) {
            throw exception(WM_STOCK_TAKING_TASK_RESULT_NOT_EXISTS);
        }
        return result;
    }
 
}