9 小时以前 4803b22ae0f0f5b4e2ae1627a7d1eea38684f02d
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -6,8 +6,10 @@
import cn.hutool.core.util.ObjectUtil;
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.pro.feedback.vo.MesProFeedbackPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.*;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
@@ -26,13 +28,18 @@
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
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.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -67,6 +74,12 @@
    private MesWmProductProduceService productProduceService;
    @Resource
    private MesWmProductProduceLineService produceLineService;
    @Resource
    private MesProMpsApi mpsApi;
    @Resource
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Override
    public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
@@ -187,9 +200,14 @@
            }
        }
        // 4. 关键工序:生成产出单,并根据是否需要检验决定入库方式
        // 5. 先更新报工状态为已完成(确保 checkAndFinishMps 检查时当前报工已标记完成)
        feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
                .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus())
                .setUncheckQuantity(BigDecimal.ZERO));
        // 6. 关键工序:生成产出单,并根据是否需要检验决定入库方式
        if (keyFlag) {
            // 4.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库
            // 6.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库
            if (checkFlag) {
                // 完成时回调见:MesQcIpqcServiceImpl#finishIpqc → splitPendingAndFinishProduce + completeFeedbackFromIpqc
                productProduceService.generateProductProduce(feedback, true);
@@ -197,22 +215,20 @@
                        .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()));
                return false;
            }
            // 4.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库,并更新任务/工单的已生产数量
            // 6.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库,并更新任务/工单的已生产数量
            MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false);
            productProduceService.finishProductProduce(produce.getId());
            updateTaskAndWorkOrderByFeedback(feedback);
        } else {
            // 4.3 非关键工序:只更新任务的已生产数量(工单不更新,工单只看关键工序产出)
            // 6.3 非关键工序:只更新任务的已生产数量(工单不更新,工单只看关键工序产出)
            taskService.updateProducedQuantity(feedback.getTaskId(),
                    feedback.getFeedbackQuantity(),
                    ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO),
                    ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO));
            // 6.4 非关键工序也需要检查 MPS 完成状态(所有工序报工完成才算完成)
            checkAndPublishProductionFinished(feedback.getWorkOrderId());
        }
        // 5. 非关键工序 / 关键非质检工序:直接完成(清零 uncheckQuantity 防止 !key+check 留脏数据)
        feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
                .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus())
                .setUncheckQuantity(BigDecimal.ZERO));
        return true; // 已完成
    }
@@ -247,6 +263,34 @@
        // 3. 更新工单的已生产数量
        workOrderService.updateProducedQuantity(feedback.getWorkOrderId(),
                feedback.getFeedbackQuantity());
        // 4. 更新 MPS 的已生产数量(如果是销售订单来源)
        MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(feedback.getWorkOrderId());
        if (mps != null) {
            mpsApi.updateMpsProducedQuantity(mps.getId(), feedback.getFeedbackQuantity());
        }
        // 5. 检查并发布生产完成事件
        checkAndPublishProductionFinished(feedback.getWorkOrderId());
    }
    /**
     * 检查并发布生产完成事件
     */
    private void checkAndPublishProductionFinished(Long workOrderId) {
        // 1. 获取工单关联的 MPS
        MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(workOrderId);
        if (mps == null || mps.getSaleOrderId() == null) {
            return; // 非销售订单来源的工单,跳过
        }
        // 2. 检查并自动完成 MPS(如果满足条件)
        mpsApi.checkAndFinishMps(mps.getId());
        // 3. 检查销售订单所有生产是否完成
        if (mpsApi.checkSaleOrderProductionFinished(mps.getSaleOrderId())) {
            eventPublisher.publishEvent(new ProductionFinishedEvent(mps.getSaleOrderId(), java.time.LocalDateTime.now()));
        }
    }
    // ==================== 校验方法 ====================
@@ -324,6 +368,91 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProFeedbackQuickCreateRespVO quickCreateFeedback(MesProFeedbackQuickCreateReqVO reqVO) {
        // 1. 校验任务存在且非终态
        MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId());
        // 2. 校验工艺路线工序配置
        MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId(
                task.getRouteId(), task.getProcessId());
        if (routeProcess == null) {
            throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID);
        }
        // 3. 校验数量
        boolean checkFlag = Boolean.TRUE.equals(routeProcess.getCheckFlag());
        if (checkFlag) {
            if (reqVO.getFeedbackQuantity() == null
                    || reqVO.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE);
            }
        } else {
            BigDecimal qualified = ObjectUtil.defaultIfNull(reqVO.getQualifiedQuantity(), BigDecimal.ZERO);
            BigDecimal unqualified = ObjectUtil.defaultIfNull(reqVO.getUnqualifiedQuantity(), BigDecimal.ZERO);
            if (qualified.add(unqualified).compareTo(BigDecimal.ZERO) <= 0) {
                throw exception(PRO_FEEDBACK_QUALIFIED_UNQUALIFIED_REQUIRED);
            }
        }
        // 4. 构建报工DO
        MesProFeedbackDO feedback = new MesProFeedbackDO()
                .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_FEEDBACK_CODE.getCode()))
                .setType(MesProFeedbackTypeEnum.SELF.getType())
                .setWorkstationId(task.getWorkstationId())
                .setRouteId(task.getRouteId())
                .setProcessId(task.getProcessId())
                .setWorkOrderId(task.getWorkOrderId())
                .setTaskId(reqVO.getTaskId())
                .setItemId(task.getItemId())
                .setFeedbackQuantity(reqVO.getFeedbackQuantity())
                .setQualifiedQuantity(reqVO.getQualifiedQuantity())
                .setUnqualifiedQuantity(reqVO.getUnqualifiedQuantity())
                .setScheduledQuantity(task.getQuantity())
                .setFeedbackTime(LocalDateTime.now())
                .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
                .setRemark(reqVO.getRemark());
        feedbackMapper.insert(feedback);
        // 5. 返回结果
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(task.getWorkOrderId());
        return new MesProFeedbackQuickCreateRespVO()
                .setFeedbackId(feedback.getId())
                .setTaskStatus(task.getStatus())
                .setWorkOrderStatus(workOrder != null ? workOrder.getStatus() : null);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProFeedbackBatchCreateRespVO batchCreateFeedback(MesProFeedbackBatchCreateReqVO reqVO) {
        List<MesProFeedbackBatchCreateRespVO.BatchResult> results = new java.util.ArrayList<>();
        int successCount = 0;
        int failCount = 0;
        for (Long taskId : reqVO.getTaskIds()) {
            try {
                MesProFeedbackQuickCreateReqVO singleReq = new MesProFeedbackQuickCreateReqVO();
                singleReq.setTaskId(taskId);
                singleReq.setFeedbackQuantity(reqVO.getFeedbackQuantity());
                singleReq.setQualifiedQuantity(reqVO.getQualifiedQuantity());
                singleReq.setUnqualifiedQuantity(reqVO.getUnqualifiedQuantity());
                singleReq.setRemark(reqVO.getRemark());
                MesProFeedbackQuickCreateRespVO resp = quickCreateFeedback(singleReq);
                results.add(new MesProFeedbackBatchCreateRespVO.BatchResult()
                        .setTaskId(taskId)
                        .setSuccess(true)
                        .setFeedbackId(resp.getFeedbackId()));
                successCount++;
            } catch (Exception e) {
                results.add(new MesProFeedbackBatchCreateRespVO.BatchResult()
                        .setTaskId(taskId)
                        .setSuccess(false)
                        .setErrorMsg(e.getMessage()));
                failCount++;
            }
        }
        return new MesProFeedbackBatchCreateRespVO()
                .setSuccessCount(successCount)
                .setFailCount(failCount)
                .setResults(results);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProFeedbackWhenIpqcFinish(Long feedbackId, Long sourceLineId,
                                                BigDecimal qualifiedQty, BigDecimal unqualifiedQty,
                                                BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty) {