yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -15,9 +15,11 @@
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.MesProMpsPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsPreparationCheckRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsMergeItemDO;
@@ -29,14 +31,18 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.enums.dv.MesDvMachineryStatusEnum;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.pro.material.MesProMaterialRequirementService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
@@ -110,6 +116,10 @@
    private MesProWorkOrderProcessService workOrderProcessService;
    @Resource
    private MesProMaterialRequirementService materialRequirementService;
    @Resource
    private MesProProcessService processService;
    @Resource
    private MesDvMachineryService machineryService;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    @Resource
@@ -305,6 +315,120 @@
    }
    @Override
    public MesProMpsPreparationCheckRespVO getPreparationCheck(Long id) {
        MesProMpsDO mps = validateMpsExists(id);
        MesProMpsPreparationCheckRespVO result = new MesProMpsPreparationCheckRespVO();
        result.setMpsId(mps.getId());
        result.setMpsCode(mps.getCode());
        result.setProductId(mps.getProductId());
        result.setQuantity(mps.getQuantity());
        MesMdItemDO product = itemService.getItem(mps.getProductId());
        result.setProductName(product != null ? product.getName() : String.valueOf(mps.getProductId()));
        // 1. 物料检查:需求 - 可用库存 - 在途采购
        List<MesProWorkOrderItemRespVO> materialItems = getMpsItemList(id);
        int materialShortageCount = 0;
        for (MesProWorkOrderItemRespVO item : materialItems) {
            MesProMpsPreparationCheckRespVO.MaterialCheck check = new MesProMpsPreparationCheckRespVO.MaterialCheck();
            check.setItemId(item.getItemId());
            check.setItemCode(item.getItemCode());
            check.setItemName(item.getItemName());
            check.setSpecification(item.getItemSpecification());
            check.setUnitMeasureName(item.getUnitMeasureName());
            check.setRequiredQuantity(nullToZero(item.getQuantity()));
            check.setStockQuantity(nullToZero(item.getStockQuantity()));
            check.setPurchaseOrderQuantity(nullToZero(item.getPurchaseOrderQuantity()));
            BigDecimal supply = nullToZero(item.getStockQuantity()).add(nullToZero(item.getPurchaseOrderQuantity()));
            BigDecimal shortage = nullToZero(item.getQuantity()).subtract(supply);
            if (shortage.compareTo(BigDecimal.ZERO) < 0) {
                shortage = BigDecimal.ZERO;
            }
            check.setShortageQuantity(shortage);
            check.setSufficient(shortage.compareTo(BigDecimal.ZERO) <= 0);
            if (!Boolean.TRUE.equals(check.getSufficient())) {
                materialShortageCount++;
            }
            result.getMaterials().add(check);
        }
        result.setMaterialReady(materialShortageCount == 0);
        // 2. 设备检查:按产品的工艺路线逐工序查找绑定设备
        List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByProductId(mps.getProductId());
        int equipmentUnavailableCount = 0;
        if (CollUtil.isEmpty(routeProcesses)) {
            MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
            check.setProcessName("工艺路线");
            check.setAvailable(false);
            check.setReason("产品未配置工艺路线,无法确定所需设备;请先在「工艺路线」中维护该产品的工艺路线后再检查");
            result.getEquipments().add(check);
            equipmentUnavailableCount++;
        } else {
            Map<Long, MesProRouteProcessDO> processIdMap = routeProcesses.stream()
                    .filter(rp -> rp.getProcessId() != null)
                    .collect(Collectors.toMap(MesProRouteProcessDO::getProcessId, rp -> rp, (a, b) -> a));
            Map<Long, MesProProcessDO> processMap = processService.getProcessMap(processIdMap.keySet());
            // 设备与工序/工位的绑定:优先取设备上的工序 ID,其次回退工位 ID(历史数据中两者一致)
            List<MesDvMachineryDO> machineries = machineryService.getMachineryList();
            Map<Long, List<MesDvMachineryDO>> machineryMap = machineries.stream()
                    .filter(m -> m.getProcessId() != null || m.getWorkstationId() != null)
                    .collect(Collectors.groupingBy(m -> m.getProcessId() != null ? m.getProcessId() : m.getWorkstationId()));
            for (MesProRouteProcessDO rp : routeProcesses) {
                MesProProcessDO process = processMap.get(rp.getProcessId());
                String processName = process != null ? process.getName() : String.valueOf(rp.getProcessId());
                List<MesDvMachineryDO> boundMachineries = machineryMap.get(rp.getProcessId());
                if (CollUtil.isEmpty(boundMachineries)) {
                    MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
                    check.setProcessId(rp.getProcessId());
                    check.setProcessName(processName);
                    check.setAvailable(false);
                    check.setReason("该工序未绑定设备,请到「设备基础信息管理」为该工序绑定设备");
                    result.getEquipments().add(check);
                    equipmentUnavailableCount++;
                    continue;
                }
                for (MesDvMachineryDO machinery : boundMachineries) {
                    MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
                    check.setProcessId(rp.getProcessId());
                    check.setProcessName(processName);
                    check.setMachineryId(machinery.getId());
                    check.setMachineryCode(machinery.getCode());
                    check.setMachineryName(machinery.getName());
                    check.setStatus(machinery.getStatus());
                    MesDvMachineryStatusEnum statusEnum = MesDvMachineryStatusEnum.valueOfStatus(machinery.getStatus());
                    check.setStatusName(statusEnum != null ? statusEnum.getName() : "未知");
                    boolean available = statusEnum != MesDvMachineryStatusEnum.MAINTENANCE;
                    check.setAvailable(available);
                    if (available) {
                        check.setReason(statusEnum == MesDvMachineryStatusEnum.PRODUCING ? "设备占用中,排产时需确认可用时段" : null);
                    } else {
                        check.setReason("设备处于维护中,请等待维护完成或改派其他设备");
                        equipmentUnavailableCount++;
                    }
                    result.getEquipments().add(check);
                }
            }
        }
        result.setEquipmentReady(equipmentUnavailableCount == 0);
        boolean ready = Boolean.TRUE.equals(result.getMaterialReady()) && Boolean.TRUE.equals(result.getEquipmentReady());
        result.setReady(ready);
        StringBuilder summary = new StringBuilder();
        if (materialShortageCount > 0) {
            summary.append(materialShortageCount).append(" 项物料短缺");
        }
        if (equipmentUnavailableCount > 0) {
            if (summary.length() > 0) {
                summary.append(",");
            }
            summary.append(equipmentUnavailableCount).append(" 项设备不可用");
        }
        if (summary.length() == 0) {
            summary.append("物料、设备均已备齐,可以安排生产");
        }
        result.setSummary(summary.toString());
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generatePurchaseRequest(Long id) {
        MesProMpsDO mps = validateMpsExists(id);