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
package cn.iocoder.yudao.module.mes.service.wm.returnsales;
 
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.detail.MesWmReturnSalesDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesDetailMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
 
import java.math.BigDecimal;
import java.util.List;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
/**
 * MES 销售退货明细 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
public class MesWmReturnSalesDetailServiceImpl implements MesWmReturnSalesDetailService {
 
    @Resource
    private MesWmReturnSalesDetailMapper returnSalesDetailMapper;
 
    @Resource
    @Lazy
    private MesWmReturnSalesLineService returnSalesLineService;
    @Resource
    @Lazy
    private MesWmReturnSalesService returnSalesService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmBatchService batchService;
    @Resource
    @Lazy
    private MesWmMaterialStockService materialStockService;
 
    @Override
    public Long createReturnSalesDetail(MesWmReturnSalesDetailSaveReqVO createReqVO) {
        // 校验保存数据
        validateReturnSalesDetailSaveData(createReqVO);
 
        // 插入
        MesWmReturnSalesDetailDO detail = BeanUtils.toBean(createReqVO, MesWmReturnSalesDetailDO.class);
        returnSalesDetailMapper.insert(detail);
        return detail.getId();
    }
 
    @Override
    public void updateReturnSalesDetail(MesWmReturnSalesDetailSaveReqVO updateReqVO) {
        // 校验存在
        validateReturnSalesDetailExists(updateReqVO.getId());
        // 校验保存数据
        validateReturnSalesDetailSaveData(updateReqVO);
 
        // 更新
        MesWmReturnSalesDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnSalesDetailDO.class);
        returnSalesDetailMapper.updateById(updateObj);
    }
 
    @Override
    public void deleteReturnSalesDetail(Long id) {
        // 校验存在
        MesWmReturnSalesDetailDO detail = validateReturnSalesDetailExists(id);
        // 校验退货单为待上架状态才允许删除明细
        validateReturnSalesStatusApproved(detail.getReturnId());
 
        // 删除
        returnSalesDetailMapper.deleteById(id);
    }
 
    @Override
    public MesWmReturnSalesDetailDO getReturnSalesDetail(Long id) {
        return returnSalesDetailMapper.selectById(id);
    }
 
    @Override
    public List<MesWmReturnSalesDetailDO> getReturnSalesDetailListByLineId(Long lineId) {
        return returnSalesDetailMapper.selectListByLineId(lineId);
    }
 
    @Override
    public List<MesWmReturnSalesDetailDO> getReturnSalesDetailListByReturnId(Long returnId) {
        return returnSalesDetailMapper.selectListByReturnId(returnId);
    }
 
    @Override
    public void deleteReturnSalesDetailByReturnId(Long returnId) {
        returnSalesDetailMapper.deleteByReturnId(returnId);
    }
 
    @Override
    public void deleteReturnSalesDetailByLineId(Long lineId) {
        returnSalesDetailMapper.deleteByLineId(lineId);
    }
 
    private MesWmReturnSalesDetailDO validateReturnSalesDetailExists(Long id) {
        MesWmReturnSalesDetailDO detail = returnSalesDetailMapper.selectById(id);
        if (detail == null) {
            throw exception(WM_RETURN_SALES_DETAIL_NOT_EXISTS);
        }
        return detail;
    }
 
    /**
     * 校验退货单为待上架状态(明细操作仅允许在待上架状态)
     */
    private void validateReturnSalesStatusApproved(Long returnId) {
        MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExists(returnId);
        if (ObjUtil.notEqual(MesWmReturnSalesStatusEnum.APPROVED.getStatus(), returnSales.getStatus())) {
            throw exception(WM_RETURN_SALES_STATUS_NOT_APPROVED);
        }
    }
 
    /**
     * 校验保存时的关联数据
     */
    private void validateReturnSalesDetailSaveData(MesWmReturnSalesDetailSaveReqVO reqVO) {
        // 校验父数据存在
        MesWmReturnSalesLineDO line = returnSalesLineService.validateReturnSalesLineExists(reqVO.getLineId());
        // 校验退货单为待上架状态
        validateReturnSalesStatusApproved(line.getReturnId());
        // 校验仓库、库区、库位的关联关系
        warehouseAreaService.validateWarehouseAreaExists(
                reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
        // 校验库位物料/批次混放规则
        materialStockService.checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), reqVO.getBatchId());
        // 校验明细总数量不超过行数量(排除自身)
        validateDetailQuantityNotExceed(reqVO.getLineId(), reqVO.getQuantity(), reqVO.getId(), line);
 
        // 根据 batchCode 解析 batchId(必须在混放规则校验之前,因为它依赖 batchId)
        fillBatchId(reqVO);
    }
 
    /**
     * 校验明细总数量不超过行数量
     *
     * @param lineId          行 ID
     * @param newQuantity     本次新增/修改的数量
     * @param excludeDetailId 排除的明细 ID(更新时排除自身,新增时传 null)
     * @param line            退货单行
     */
    private void validateDetailQuantityNotExceed(Long lineId, BigDecimal newQuantity,
                                                  Long excludeDetailId, MesWmReturnSalesLineDO line) {
        // 计算已有明细总数量(排除自身)
        List<MesWmReturnSalesDetailDO> details = returnSalesDetailMapper.selectListByLineId(lineId);
        BigDecimal existingTotal = CollectionUtils.getSumValue(details,
                detail -> excludeDetailId != null && excludeDetailId.equals(detail.getId())
                        ? BigDecimal.ZERO : detail.getQuantity(),
                BigDecimal::add, BigDecimal.ZERO);
        // 校验:已有 + 本次 <= 行数量
        if (existingTotal.add(newQuantity).compareTo(line.getQuantity()) > 0) {
            throw exception(WM_RETURN_SALES_DETAIL_QUANTITY_EXCEED);
        }
    }
 
    /**
     * 根据 batchCode 解析并回填 batchId
     */
    private void fillBatchId(MesWmReturnSalesDetailSaveReqVO reqVO) {
        if (StrUtil.isBlank(reqVO.getBatchCode())) {
            return;
        }
        MesWmBatchDO batch = batchService.getBatchByCode(reqVO.getBatchCode());
        if (batch != null) {
            reqVO.setBatchId(batch.getId());
        }
    }
 
}