| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.service.pro.mps.MesProMpsService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | |
| | | @Resource |
| | | private MesProMpsMapper mpsMapper; |
| | | @Resource |
| | | private MesProMpsService mpsService; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | |
| | | @Override |
| | | public MesProAiMaterialPredictRespVO predictMaterial(MesProAiMaterialPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherMaterialData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherMaterialDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherMaterialData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_MATERIAL, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 物料预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 物料预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseMaterialResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 物料预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 物料预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiMaterialPredictRespVO fallback = new MesProAiMaterialPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherMaterialDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息预测"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | |
| | | // 查询产品在库物料库存(通用,非工单特定 BOM) |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | List<Map<String, Object>> materials = new ArrayList<>(); |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("物料ID", stock.getItemId()); |
| | | item.put("物料名称", resolveItemName(stock.getItemId())); |
| | | item.put("库存可用量", stock.getQuantity()); |
| | | materials.add(item); |
| | | } |
| | | data.put("当前库存物料", materials); |
| | | } else { |
| | | data.put("提示", "暂无库存数据,请参考产品编码评估物料需求"); |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiMaterialPredictRespVO parseMaterialResponse(String aiResponse) { |
| | | MesProAiMaterialPredictRespVO respVO = new MesProAiMaterialPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiDurationPredictRespVO predictDuration(MesProAiDurationPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDurationData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherDurationDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherDurationData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DURATION, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 时长预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 时长预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseDurationResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 时长预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 时长预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiDurationPredictRespVO fallback = new MesProAiDurationPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return totalEfficiency; |
| | | } |
| | | |
| | | private Map<String, Object> gatherDurationDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息估算"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | data.put("提示", "MPS 未关联工单工序,请基于产品编码和经验估算生产时长"); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiDurationPredictRespVO parseDurationResponse(String aiResponse) { |
| | | MesProAiDurationPredictRespVO respVO = new MesProAiDurationPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiRiskPredictRespVO predictRisk(MesProAiRiskPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherRiskData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherRiskDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherRiskData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_RISK, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 风险预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 风险预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseRiskResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 风险预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 风险预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiRiskPredictRespVO fallback = new MesProAiRiskPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherRiskDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,风险评估基于产品信息"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | data.put("下发日期", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "未下发"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | |
| | | // 产品库存情况 |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | List<Map<String, Object>> stockList = new ArrayList<>(); |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("物料ID", stock.getItemId()); |
| | | item.put("物料名称", resolveItemName(stock.getItemId())); |
| | | item.put("可用量", stock.getQuantity()); |
| | | stockList.add(item); |
| | | } |
| | | data.put("库存物料", stockList); |
| | | } |
| | | |
| | | data.put("提示", "MPS 未下发无工单数据,请结合产品特性和库存自行判断风险"); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiRiskPredictRespVO parseRiskResponse(String aiResponse) { |
| | | MesProAiRiskPredictRespVO respVO = new MesProAiRiskPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiDeliveryPredictRespVO predictDelivery(MesProAiDeliveryPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDeliveryData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherDeliveryDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherDeliveryData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DELIVERY, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 交付预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 交付预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseDeliveryResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 交付预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 交付预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiDeliveryPredictRespVO fallback = new MesProAiDeliveryPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherDeliveryDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息评估"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("MPS 状态", mps.getStatus()); |
| | | data.put("销售订单号", mps.getSaleOrderNo()); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | data.put("下发日期", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "未下发"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | fillClientInfo(data, mps.getClientId()); |
| | | |
| | | // 进度计算 |
| | | BigDecimal progressPercent = BigDecimal.ZERO; |
| | | if (mps.getQuantity() != null && mps.getQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && mps.getQuantityProduced() != null) { |
| | | progressPercent = mps.getQuantityProduced() |
| | | .divide(mps.getQuantity(), 4, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)); |
| | | } |
| | | data.put("生产进度(%)", progressPercent.setScale(1, RoundingMode.HALF_UP)); |
| | | |
| | | // 剩余天数 |
| | | int remainingDays = 0; |
| | | if (mps.getRequestDate() != null) { |
| | | remainingDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), mps.getRequestDate().toLocalDate()); |
| | | } |
| | | data.put("距离需求日期剩余天数", Math.max(0, remainingDays)); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiDeliveryPredictRespVO parseDeliveryResponse(String aiResponse) { |
| | | MesProAiDeliveryPredictRespVO respVO = new MesProAiDeliveryPredictRespVO(); |
| | | try { |