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
package cn.iocoder.yudao.module.mes.service.wm.unitconvert;
 
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
 
import java.math.BigDecimal;
import java.util.List;
 
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
/**
 * MES 物料主辅单位换算 Service 实现类
 *
 * @author 超级管理员
 */
@Service
@Validated
public class MesWmItemUnitConvertServiceImpl implements MesWmItemUnitConvertService {
 
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
 
    @Override
    public BigDecimal resolveRateToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId) {
        MdmItemRespDTO item = mdmItemApi.validateItemExists(itemId).getCheckedData();
        // 1. 未指定录入单位,或录入单位就是物料主单位 → 比率 1
        if (unitMeasureId == null || ObjUtil.equal(item.getUnitMeasureId(), unitMeasureId)) {
            return BigDecimal.ONE;
        }
        // 2. 指定了型号 → 以型号上配置的单位与比率为准
        if (itemModelId != null) {
            MdmItemModelRespDTO model = mdmItemModelApi.validateItemModelExistsAndEnable(itemModelId, itemId)
                    .getCheckedData();
            if (ObjUtil.notEqual(model.getUnitMeasureId(), unitMeasureId)) {
                throw new ServiceException(WM_ITEM_UNIT_CONVERT_MODEL_UNIT_MISMATCH.getCode(), String.format(
                        "所选型号【%s】的计量单位是【%s】,与本次录入单位【%s】不一致,请重新选择型号或改回型号对应的计量单位",
                        model.getModel(), unitName(model.getUnitMeasureId()), unitName(unitMeasureId)));
            }
            return validateRate(model);
        }
        // 3. 未指定型号 → 先匹配物料主数据上的辅单位 1 / 辅单位 2
        if (ObjUtil.equal(item.getUnitMeasureId2(), unitMeasureId) && item.getUnitMeasureRate1() != null) {
            return item.getUnitMeasureRate1();
        }
        if (ObjUtil.equal(item.getUnitMeasureId3(), unitMeasureId) && item.getUnitMeasureRate2() != null) {
            return item.getUnitMeasureRate2();
        }
        // 4. 再从物料型号中找该单位:比率唯一可直接使用,比率有多个取值则必须指定型号
        List<MdmItemModelRespDTO> models = mdmItemModelApi.getItemModelListByItemId(itemId).getCheckedData().stream()
                .filter(model -> ObjUtil.equal(model.getUnitMeasureId(), unitMeasureId))
                .filter(model -> CommonStatusEnum.ENABLE.getStatus().equals(model.getStatus()))
                .toList();
        List<BigDecimal> distinctRates = models.stream()
                .map(MdmItemModelRespDTO::getUnitMeasureRate)
                .filter(java.util.Objects::nonNull)
                .distinct()
                .toList();
        if (distinctRates.size() > 1) {
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_MODEL_REQUIRED.getCode(), String.format(
                    "物料【%s-%s】下使用计量单位【%s】的型号有多个且换算比率不同,请先选择型号后再录入数量",
                    item.getCode(), item.getName(), unitName(unitMeasureId)));
        }
        if (distinctRates.size() == 1) {
            return validateRate(distinctRates.get(0), models.get(0).getModel());
        }
        // 5. 主数据与型号均未配置该单位的换算比率
        throw new ServiceException(WM_ITEM_UNIT_CONVERT_NOT_CONFIGURED.getCode(), String.format(
                "物料【%s-%s】没有配置计量单位【%s】到主单位【%s】的换算比率,无法换算,"
                        + "请先到【基础数据-物料主数据-型号】为该物料新增型号并填写换算比率",
                item.getCode(), item.getName(), unitName(unitMeasureId), unitName(item.getUnitMeasureId())));
    }
 
    @Override
    public BigDecimal convertToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId, BigDecimal enterQuantity) {
        BigDecimal rate = resolveRateToPrimaryUnit(itemId, itemModelId, unitMeasureId);
        // 1. 录入单位即主单位 → 数量原样返回
        if (BigDecimal.ONE.compareTo(rate) == 0) {
            return enterQuantity;
        }
        // 2. 按非主单位录入时,录入数量必填
        if (enterQuantity == null) {
            MdmItemRespDTO item = mdmItemApi.validateItemExists(itemId).getCheckedData();
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_QUANTITY_REQUIRED.getCode(), String.format(
                    "计量单位【%s】不是物料【%s-%s】的主单位,按该单位录入时录入数量不能为空,请填写录入数量或改用主单位【%s】",
                    unitName(unitMeasureId), item.getCode(), item.getName(), unitName(item.getUnitMeasureId())));
        }
        // 3. 辅单位数量 × 换算比率 = 主单位数量
        return enterQuantity.multiply(rate);
    }
 
    private BigDecimal validateRate(MdmItemModelRespDTO model) {
        return validateRate(model.getUnitMeasureRate(), model.getModel());
    }
 
    private BigDecimal validateRate(BigDecimal rate, String modelName) {
        if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_RATE_INVALID.getCode(), String.format(
                    "型号【%s】的换算比率为【%s】不合理,1 个型号单位必须对应大于 0 的主单位数量,"
                            + "请到【基础数据-物料主数据-型号】修正该型号的换算比率",
                    modelName, rate));
        }
        return rate;
    }
 
    private String unitName(Long unitMeasureId) {
        if (unitMeasureId == null) {
            return "未指定";
        }
        MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasure(unitMeasureId).getCheckedData();
        return unit != null ? unit.getName() : "ID=" + unitMeasureId;
    }
 
}