云
4 天以前 f02d0df2f1dfc7a8b775cf50d45375e57628a5ef
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
package cn.iocoder.yudao.module.mes.service.qc.ncr;
 
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO;
import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
 
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
 
/** 关闭返工/返修 NCR 前,核实操作者明确指定的整批复检证据。 */
@Component
public class MesQcNcrReinspectionValidator {
 
    @Resource private MesProWorkOrderMapper workOrderMapper;
    @Resource private MesQcIpqcMapper ipqcMapper;
    @Resource private MesMdItemMapper itemMapper;
    @Resource private MesWmProductProduceLineMapper produceLineMapper;
    @Resource private MesWmProductProduceMapper produceMapper;
    @Resource private MesWmTransactionMapper transactionMapper;
    @Resource private MesQcNcrMapper ncrMapper;
 
    public void validate(MesQcNcrDO ncr, Long reinspectionIpqcId) {
        if (reinspectionIpqcId == null) {
            throw new ServiceException(400, "返工/返修关闭前请选择关联产出的整批复检单");
        }
        // 当前订单仅持有来源编码;存在多个匹配项时不能猜测关联。
        List<MesProWorkOrderDO> orders = workOrderMapper.selectList(new LambdaQueryWrapperX<MesProWorkOrderDO>()
                .eq(MesProWorkOrderDO::getOrderSourceType, MesProWorkOrderSourceTypeEnum.STORE.getType())
                .eq(MesProWorkOrderDO::getOrderSourceCode, ncr.getCode()));
        if (orders.size() != 1) {
            throw new ServiceException(400, "无法唯一确定此不合格单对应的返工/返修生产订单,请先核对来源编码");
        }
        MesProWorkOrderDO order = orders.get(0);
        if (!Objects.equals(order.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus())
                || order.getFinishDate() == null) {
            throw new ServiceException(400, "返工/返修生产订单尚未完成或缺少完成时间,不能关闭");
        }
        MesMdItemDO product = itemMapper.selectById(order.getProductId());
        if (product == null || product.getMdmItemId() == null
                || !Objects.equals(product.getMdmItemId(), ncr.getItemId())) {
            throw new ServiceException(400, "返工订单产品与不合格物料的映射不明确,请先核对产品主数据");
        }
        MesQcIpqcDO qc = ipqcMapper.selectById(reinspectionIpqcId);
        // 出货返工同样走报工产出复检:返工工单完成后,需绑定其报工产出行的过程检验单
        if (!Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType())
                && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType())
                && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType())) {
            validateLegacy(qc, order, ncr);
            return;
        }
        if (qc == null || !Objects.equals(qc.getWorkOrderId(), order.getId())
                || !Objects.equals(qc.getItemId(), order.getProductId())
                || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK)
                || qc.getSourceDocId() == null || qc.getSourceLineId() == null) {
            throw new ServiceException(400, "请选择关联返工订单报工产出行的过程检验单作为复检证据");
        }
        if (ncr.getHandleDate() == null || qc.getCreateTime() == null || qc.getCreateTime().isBefore(ncr.getHandleDate())
                || qc.getInspectDate() == null || qc.getInspectDate().isBefore(ncr.getHandleDate())) {
            throw new ServiceException(400, "复检单须在返工处置后创建并检验,请核对检验日期");
        }
        BigDecimal quantity = ncr.getUnqualifiedQuantity();
        if (!Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus())
                || quantity == null || quantity.signum() <= 0
                || qc.getCheckQuantity() == null || qc.getCheckQuantity().compareTo(quantity) != 0
                || qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().signum() < 0
                || qc.getUnqualifiedQuantity() == null || qc.getUnqualifiedQuantity().signum() < 0
                || qc.getQualifiedQuantity().add(qc.getUnqualifiedQuantity()).compareTo(quantity) != 0
                || !Objects.equals(qc.getCheckResult(), qc.getUnqualifiedQuantity().signum() == 0 ? 1 : 2)) {
            throw new ServiceException(400, "复检必须已完成,且合格与不合格数量之和须等于不合格单数量");
        }
        MesWmProductProduceLineDO line = produceLineMapper.selectById(qc.getSourceLineId());
        MesWmProductProduceDO produce = line == null ? null : produceMapper.selectById(line.getProduceId());
        if (line == null || produce == null || !Objects.equals(line.getFeedbackId(), qc.getSourceDocId())
                || !Objects.equals(produce.getWorkOrderId(), order.getId())
                || !Objects.equals(line.getItemId(), qc.getItemId())
                || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())
                || line.getQuantity() == null || line.getQuantity().compareTo(qc.getQualifiedQuantity()) != 0) {
            throw new ServiceException(400, "复检产出行与返工订单或合格数量不一致");
        }
        List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>()
                .eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_PRODUCT_PRODUCE)
                .eq(MesWmTransactionDO::getBizId, produce.getId())
                .eq(MesWmTransactionDO::getBizLineId, line.getId())
                .eq(MesWmTransactionDO::getWorkOrderId, order.getId())
                .eq(MesWmTransactionDO::getItemId, qc.getItemId())
                .eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.IN.getType()));
        BigDecimal stocked = flows.stream().map(MesWmTransactionDO::getQuantity)
                .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (stocked.compareTo(qc.getQualifiedQuantity()) != 0) {
            throw new ServiceException(400, "返工复检合格产出的入库流水数量不匹配,暂不能关闭");
        }
        if (qc.getUnqualifiedQuantity().signum() > 0) {
            List<MesQcNcrDO> children = ncrMapper.selectList(new LambdaQueryWrapperX<MesQcNcrDO>()
                    .eq(MesQcNcrDO::getQcType, MesQcTypeEnum.IPQC.getType())
                    .eq(MesQcNcrDO::getQcId, qc.getId()));
            if (children.size() != 1 || !Objects.equals(children.get(0).getItemId(), ncr.getItemId())
                    || children.get(0).getUnqualifiedQuantity() == null
                    || children.get(0).getUnqualifiedQuantity().compareTo(qc.getUnqualifiedQuantity()) != 0) {
                throw new ServiceException(400, "复检不合格数量须全部移交至唯一的新不合格处理单,请核对物料和数量");
            }
        }
    }
 
    /** 其他检验来源保持原有关闭规则。 */
    private void validateLegacy(MesQcIpqcDO qc, MesProWorkOrderDO order, MesQcNcrDO ncr) {
        if (qc == null || !Objects.equals(qc.getWorkOrderId(), order.getId())
                || !Objects.equals(qc.getItemId(), order.getProductId())
                || qc.getSourceDocType() != null || qc.getSourceDocId() != null || qc.getSourceLineId() != null) {
            throw new ServiceException(400, "请选择该返工订单产品的独立过程检验单作为复检证据");
        }
        if (qc.getCreateTime() == null || qc.getCreateTime().isBefore(order.getFinishDate())
                || qc.getInspectDate() == null || qc.getInspectDate().isBefore(order.getFinishDate())) {
            throw new ServiceException(400, "复检单须在返工订单完成后创建并检验,请核对检验日期");
        }
        BigDecimal quantity = ncr.getUnqualifiedQuantity();
        if (!Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus())
                || !Objects.equals(qc.getCheckResult(), 1)
                || quantity == null || quantity.signum() <= 0
                || qc.getCheckQuantity() == null || qc.getCheckQuantity().compareTo(quantity) != 0
                || qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().compareTo(quantity) != 0
                || qc.getUnqualifiedQuantity() == null || qc.getUnqualifiedQuantity().signum() != 0) {
            throw new ServiceException(400, "复检必须已完成且整批合格,检测数量和合格数量须等于不合格单数量");
        }
    }
}