gaoluyang
6 天以前 7bd10d34e8a8dc2437ec8551eb511e37634d7306
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -3,14 +3,17 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseRequestApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseRequestCreateReqDTO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO;
@@ -77,6 +80,11 @@
     */
    private static final String MPS_CODE_RULE = "MPS";
    /**
     * MPS 审批流程定义 Key(BPM 流程模型中配置)
     */
    private static final String MPS_APPROVE_PROCESS_DEFINITION_KEY = "mes-pro-mps-audit";
    @Resource
    private MesProMpsMapper mpsMapper;
    @Resource
@@ -105,6 +113,8 @@
    private ErpPurchaseOrderApi purchaseOrderApi;
    @Resource
    private ErpPurchaseRequestApi purchaseRequestApi;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -194,11 +204,8 @@
        // 校验编码唯一
        validateMpsCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
        // 更新;审核中的计划被修改后需要重新提交审核
        // 更新(仅草稿可编辑)
        MesProMpsDO updateObj = BeanUtils.toBean(updateReqVO, MesProMpsDO.class);
        if (MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
            updateObj.setStatus(MesProMpsStatusEnum.DRAFT.getStatus());
        }
        mpsMapper.updateById(updateObj);
    }
@@ -221,35 +228,71 @@
    @Transactional(rollbackFor = Exception.class)
    public void submitMps(Long id) {
        MesProMpsDO mps = validateMpsExists(id);
        if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())
                && !MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
        // 只有草稿可以提交审核;审核中的计划需等待协同办公审批结果
        if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
            throw exception(PRO_MPS_NOT_DRAFT);
        }
        // 全局免审批:不做 BPM 流程,直接按审批通过处理
        if (processInstanceApi.isGlobalAutoApprove()) {
            mpsMapper.updateById(new MesProMpsDO()
                    .setId(id)
                    .setStatus(MesProMpsStatusEnum.AUDITING.getStatus())
                    .setProcessInstanceId(""));
            handleBpmProcessResult(id, BpmProcessInstanceStatusEnum.APPROVE.getStatus(), "全局免审批自动通过", null);
            return;
        }
        // 启动 BPM 流程,审批在协同办公完成,结果由监听器回写
        String processInstanceId;
        try {
            processInstanceId = processInstanceApi.createProcessInstance(SecurityFrameworkUtils.getLoginUserId(),
                    new BpmProcessInstanceCreateReqDTO()
                            .setProcessDefinitionKey(MPS_APPROVE_PROCESS_DEFINITION_KEY)
                            .setBusinessKey(String.valueOf(id)));
        } catch (ServiceException ex) {
            // 流程未部署时 BPM 只抛「流程定义不存在」,补上可执行的提示
            if (StrUtil.contains(ex.getMessage(), "流程定义不存在")) {
                log.warn("[submitMps] 生产计划审批流程({}) 未部署,mpsId={}", MPS_APPROVE_PROCESS_DEFINITION_KEY, id);
                throw exception(PRO_MPS_PROCESS_NOT_DEPLOYED);
            }
            throw ex;
        }
        mpsMapper.updateById(new MesProMpsDO()
                .setId(id)
                .setStatus(MesProMpsStatusEnum.AUDITING.getStatus()));
                .setStatus(MesProMpsStatusEnum.AUDITING.getStatus())
                .setProcessInstanceId(processInstanceId));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditMps(MesProMpsAuditReqVO reqVO) {
        MesProMpsDO mps = validateMpsExists(reqVO.getId());
    public void handleBpmProcessResult(Long id, Integer bpmResult, String reason, Long auditUserId) {
        // 1. 柔性校验:计划存在且处于审核中
        MesProMpsDO mps = mpsMapper.selectById(id);
        if (mps == null) {
            log.warn("[handleBpmProcessResult] 生产计划({}) 不存在", id);
            return;
        }
        if (!MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
            throw exception(PRO_MPS_NOT_AUDITING);
            log.warn("[handleBpmProcessResult] 生产计划({}) 不处于审核中状态,当前状态:{}", id, mps.getStatus());
            return;
        }
        boolean approved = Boolean.TRUE.equals(reqVO.getApproved());
        if (!approved && StrUtil.isBlank(reqVO.getAuditRemark())) {
            throw exception(PRO_MPS_AUDIT_REMARK_REQUIRED);
        // 2. 根据审批结果更新状态
        if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            mpsMapper.updateById(new MesProMpsDO()
                    .setId(id)
                    .setStatus(MesProMpsStatusEnum.APPROVED.getStatus())
                    .setAuditUserId(auditUserId)
                    .setAuditTime(LocalDateTime.now())
                    .setAuditRemark(StrUtil.blankToDefault(reason, "审核通过")));
        } else if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult)
                || BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            mpsMapper.updateById(new MesProMpsDO()
                    .setId(id)
                    .setStatus(MesProMpsStatusEnum.DRAFT.getStatus())
                    .setAuditUserId(auditUserId)
                    .setAuditTime(LocalDateTime.now())
                    .setAuditRemark(StrUtil.blankToDefault(reason, "审批驳回")));
        }
        mpsMapper.updateById(new MesProMpsDO()
                .setId(reqVO.getId())
                .setStatus(approved ? MesProMpsStatusEnum.APPROVED.getStatus()
                        : MesProMpsStatusEnum.DRAFT.getStatus())
                .setAuditUserId(SecurityFrameworkUtils.getLoginUserId())
                .setAuditTime(LocalDateTime.now())
                .setAuditRemark(approved
                        ? StrUtil.blankToDefault(reqVO.getAuditRemark(), "审核通过")
                        : reqVO.getAuditRemark()));
    }
    @Override
@@ -267,9 +310,16 @@
        if (!MesProMpsStatusEnum.APPROVED.getStatus().equals(mps.getStatus())) {
            throw exception(PRO_MPS_NOT_APPROVED);
        }
        // 1. 只对缺口物料生成采购明细:缺口 = 需求 - 可用库存 - 在途采购
        // 1. 物料需求为空说明产品未维护任何 BOM,直接提示,避免误报“库存充足”
        List<MesProWorkOrderItemRespVO> materialItems = getMpsItemList(id);
        if (CollUtil.isEmpty(materialItems)) {
            MesMdItemDO product = itemService.getItem(mps.getProductId());
            throw exception(PRO_MPS_NO_MATERIAL_BOM, mps.getCode(),
                    product != null ? product.getName() : String.valueOf(mps.getProductId()));
        }
        // 2. 只对缺口物料生成采购明细:缺口 = 需求 - 可用库存 - 在途采购
        List<ErpPurchaseRequestCreateReqDTO.Item> shortageItems = new ArrayList<>();
        for (MesProWorkOrderItemRespVO item : getMpsItemList(id)) {
        for (MesProWorkOrderItemRespVO item : materialItems) {
            if (Boolean.TRUE.equals(item.getStockSufficient())) {
                continue;
            }
@@ -293,14 +343,14 @@
        if (CollUtil.isEmpty(shortageItems)) {
            throw exception(PRO_MPS_NO_MATERIAL_SHORTAGE);
        }
        // 2. 生成采购申请单(草稿,无供应商,后续由采购人员补充并按流程提交)
        // 3. 生成采购申请单(草稿,无供应商,后续由采购人员补充并按流程提交)
        ErpPurchaseRequestCreateReqDTO createReqDTO = new ErpPurchaseRequestCreateReqDTO()
                .setRequestTime(LocalDateTime.now())
                .setRequestReason("生产计划 " + mps.getCode() + " 辅料采购")
                .setRemark("由生产计划自动生成,共 " + shortageItems.size() + " 项物料库存不足")
                .setItems(shortageItems);
        Long purchaseRequestId = purchaseRequestApi.createPurchaseRequest(createReqDTO).getCheckedData();
        // 3. 回写采购申请单编号,便于从计划追溯
        // 4. 回写采购申请单编号,便于从计划追溯
        mpsMapper.updateById(new MesProMpsDO().setId(id).setPurchaseRequestId(purchaseRequestId));
        log.info("[generatePurchaseRequest] 计划({}) 生成采购申请单({}),明细 {} 项", mps.getCode(), purchaseRequestId, shortageItems.size());
        return purchaseRequestId;
@@ -638,12 +688,11 @@
    }
    /**
     * 校验 MPS 存在 + 可编辑状态(未开始、审核中)
     * 校验 MPS 存在 + 可编辑状态(仅草稿;审核中的计划需等待协同办公审批)
     */
    private MesProMpsDO validateMpsExistsAndEditable(Long id) {
        MesProMpsDO mps = validateMpsExists(id);
        if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())
                && !MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
        if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
            throw exception(PRO_MPS_NOT_DRAFT);
        }
        return mps;