zhanghl
2025-05-22 f274ea473d1ba857c8ac213fc43524b4e773cf71
aps-modules/aps-core/src/main/java/com/aps/core/service/impl/ApsGasPipingRouteStatServiceImpl.java
@@ -6,9 +6,7 @@
import com.aps.common.core.utils.uuid.IdUtils;
import com.aps.common.security.utils.SecurityUtils;
import com.aps.core.domain.*;
import com.aps.core.mapper.ApsGasPipelineCapacityPlanMapper;
import com.aps.core.mapper.ApsGasPipingPlanMapper;
import com.aps.core.mapper.ApsGasPipingRouteStatMapper;
import com.aps.core.mapper.*;
import com.aps.core.service.IApsGasMaterialUsageService;
import com.aps.core.service.IApsGasPipingRouteStatService;
import com.aps.core.service.IApsStandardProcessService;
@@ -1009,4 +1007,601 @@
        log.info("批量插入数据完成,batchNum:"+batchNum);
    }
    /**
     * 根据手工气体预测数据和手工气体工单数据生成统计数据
     *
     * @return 结果
     */
    @Override
    @Transactional
    public int generateGasPipingRouteStatData() {
        int count = 0;
        // 清空之前的数据
        apsGasPipingRouteStatMapper.deleteAll();
        // 生成批次号
        String batchNumber = IdUtils.fastSimpleUUID();
        List<ApsGasPipingRouteStat> statList = new ArrayList<>();
        // 处理手工气体预测数据 - 使用联合查询获取所有相关数据
        List<Map<String, Object>> predictionDataList = apsGasPipingRouteStatMapper.selectPredictionRouteData();
        // 按物料代码分组,便于处理同一物料的不同工序
        Map<String, List<Map<String, Object>>> predictionGroups = predictionDataList.stream()
                .collect(Collectors.groupingBy(data -> data.get("material_code").toString()));
        // 处理每个物料的工艺路线
        for (String materialCode : predictionGroups.keySet()) {
            List<Map<String, Object>> materialData = predictionGroups.get(materialCode);
            // 按工序号排序
            materialData.sort((a, b) -> {
                String numA = a.get("process_number").toString();
                String numB = b.get("process_number").toString();
                return numA.compareTo(numB);
            });
            // 获取所有工序的信息并创建统计记录
            List<ApsGasPipingRouteStat> processList = new ArrayList<>();
            for (Map<String, Object> data : materialData) {
                ApsGasPipingRouteStat stat = new ApsGasPipingRouteStat();
                stat.setId(IdUtils.fastSimpleUUID());
                stat.setWorkOrderNo(""); // 预测数据没有工单号
                stat.setItemNumber(data.get("material_code").toString());
                stat.setProcessName(data.get("process_name").toString());
                stat.setRoadProcessNumber(new BigDecimal(data.get("process_number").toString()));
                // 生产数量和标准工时
                BigDecimal quantity = new BigDecimal(data.get("predict_quantity").toString()).setScale(4, RoundingMode.HALF_UP);
                BigDecimal standardTime = new BigDecimal(data.get("standard_time").toString()).setScale(4, RoundingMode.HALF_UP);
                stat.setProductionQuantity(quantity);
                stat.setStandardTime(standardTime);
                stat.setProcessTotalTime(standardTime.multiply(quantity).setScale(4, RoundingMode.HALF_UP));
                // 工厂、专业和车间
                stat.setPlant(data.get("factory").toString());
                stat.setMajor(data.get("domain") != null ? data.get("domain").toString() : "");
                stat.setWorkshop(data.get("workshop") != null ? data.get("workshop").toString() : "");
                // 批次号和创建信息
                stat.setBatchNumber(batchNumber);
                stat.setCreateBy(SecurityUtils.getUsername());
                stat.setCreateTime(truncateToSeconds(DateUtils.getNowDate()));
                processList.add(stat);
            }
            // 计算计划完工日和计划开工日
            if (!processList.isEmpty()) {
                int lastIndex = processList.size() - 1;
                // 最后一道工序的计划完工日期为预测日期
                ApsGasPipingRouteStat lastProcess = processList.get(lastIndex);
                Date predictDate = (Date) materialData.get(lastIndex).get("predict_date");
                lastProcess.setProcessPlanEndDay(truncateToSeconds(predictDate));
                // 标记是否出现过时间冲突的情况
                boolean hasTimeConflict = false;
                // 计算每个工序的计划开工日和完工日 - 从最后一道工序开始倒推
                for (int i = lastIndex; i >= 0; i--) {
                    ApsGasPipingRouteStat current = processList.get(i);
                    // 如果当前是最后一道工序,计划开工日 = 计划完工日 - 工序总工时
                    if (i == lastIndex) {
                        hasTimeConflict = calculateProcessPlanStartDay(current, hasTimeConflict);
                    } else {
                        // 非最后一道工序,计划完工日为下一道工序的计划开工日
                        ApsGasPipingRouteStat next = processList.get(i + 1);
                        current.setProcessPlanEndDay(next.getProcessPlanStartDay());
                        // 如果已经出现时间冲突,后续工序的计划开工日和计划完工日都设为当前时间
                        if (hasTimeConflict) {
                            current.setProcessPlanStartDay(truncateToSeconds(new Date()));
                        } else {
                            // 否则正常计算,并检查是否出现时间冲突
                            hasTimeConflict = calculateProcessPlanStartDay(current, hasTimeConflict);
                        }
                    }
                    // 设置年、月、日
                    setDateComponents(current);
                }
                statList.addAll(processList);
            }
        }
        // 处理手工气体工单数据 - 使用联合查询获取所有相关数据
        List<Map<String, Object>> moDataList = apsGasPipingRouteStatMapper.selectMoRouteData();
        // 按工单号分组,便于处理同一工单的不同工序
        Map<String, List<Map<String, Object>>> moGroups = moDataList.stream()
                .collect(Collectors.groupingBy(data -> data.get("work_order_no").toString()));
        // 处理每个工单的工艺路线
        for (String workOrderNo : moGroups.keySet()) {
            List<Map<String, Object>> workOrderData = moGroups.get(workOrderNo);
            // 按工序号排序
            workOrderData.sort((a, b) -> {
                String numA = a.get("process_number").toString();
                String numB = b.get("process_number").toString();
                return numA.compareTo(numB);
            });
            // 获取所有工序的信息并创建统计记录
            List<ApsGasPipingRouteStat> processList = new ArrayList<>();
            for (Map<String, Object> data : workOrderData) {
                ApsGasPipingRouteStat stat = new ApsGasPipingRouteStat();
                stat.setId(IdUtils.fastSimpleUUID());
                stat.setWorkOrderNo(data.get("work_order_no").toString());
                stat.setItemNumber(data.get("material_code").toString());
                stat.setProcessName(data.get("process_name").toString());
                stat.setRoadProcessNumber(new BigDecimal(data.get("process_number").toString()));
                // 生产数量和标准工时
                BigDecimal quantity = new BigDecimal(data.get("quantity").toString()).setScale(4, RoundingMode.HALF_UP);
                BigDecimal standardTime = new BigDecimal(data.get("standard_time").toString()).setScale(4, RoundingMode.HALF_UP);
                stat.setProductionQuantity(quantity);
                stat.setStandardTime(standardTime);
                stat.setProcessTotalTime(standardTime.multiply(quantity).setScale(4, RoundingMode.HALF_UP));
                // 工厂、专业和车间
                stat.setPlant(data.get("factory").toString());
                stat.setMajor(data.get("domain") != null ? data.get("domain").toString() : "");
                stat.setWorkshop(data.get("workshop") != null ? data.get("workshop").toString() : "");
                // 批次号和创建信息
                stat.setBatchNumber(batchNumber);
                stat.setCreateBy(SecurityUtils.getUsername());
                stat.setCreateTime(truncateToSeconds(DateUtils.getNowDate()));
                processList.add(stat);
            }
            // 计算计划完工日和计划开工日
            if (!processList.isEmpty()) {
                int lastIndex = processList.size() - 1;
                // 最后一道工序的计划完工日期为工单计划完工日期
                ApsGasPipingRouteStat lastProcess = processList.get(lastIndex);
                Date planEnd = (Date) workOrderData.get(0).get("plan_end");
                lastProcess.setProcessPlanEndDay(truncateToSeconds(planEnd));
                // 标记是否出现过时间冲突的情况
                boolean hasTimeConflict = false;
                // 计算每个工序的计划开工日和完工日 - 从最后一道工序开始倒推
                for (int i = lastIndex; i >= 0; i--) {
                    ApsGasPipingRouteStat current = processList.get(i);
                    // 如果当前是最后一道工序,计划开工日 = 计划完工日 - 工序总工时
                    if (i == lastIndex) {
                        hasTimeConflict = calculateProcessPlanStartDay(current, hasTimeConflict);
                    } else {
                        // 非最后一道工序,计划完工日为下一道工序的计划开工日
                        ApsGasPipingRouteStat next = processList.get(i + 1);
                        current.setProcessPlanEndDay(next.getProcessPlanStartDay());
                        // 如果已经出现时间冲突,后续工序的计划开工日和计划完工日都设为当前时间
                        if (hasTimeConflict) {
                            current.setProcessPlanStartDay(truncateToSeconds(new Date()));
                        } else {
                            // 否则正常计算,并检查是否出现时间冲突
                            hasTimeConflict = calculateProcessPlanStartDay(current, hasTimeConflict);
                        }
                    }
                    // 设置年、月、日
                    setDateComponents(current);
                }
                statList.addAll(processList);
            }
        }
        // 批量插入数据
        if (!statList.isEmpty()) {
            for (int i = 0; i < statList.size(); i += 500) {
                int endIndex = Math.min(i + 500, statList.size());
                List<ApsGasPipingRouteStat> batch = statList.subList(i, endIndex);
                count += apsGasPipingRouteStatMapper.insertApsGasPipingRouteStatBatch(batch);
            }
        }
        return count;
    }
    /**
     * 计算工序计划开工日,并判断是否发生时间冲突
     *
     * @param stat 当前工序统计对象
     * @param hasTimeConflict 之前是否已经发生时间冲突
     * @return 是否发生时间冲突
     */
    private boolean calculateProcessPlanStartDay(ApsGasPipingRouteStat stat, boolean hasTimeConflict) {
        Date planEndDay = stat.getProcessPlanEndDay();
        if (planEndDay == null) {
            return hasTimeConflict;
        }
        // 计算工序总工时对应的秒数
        long processTotalTimeSeconds = stat.getProcessTotalTime()
                .multiply(BigDecimal.valueOf(60 * 60))
                .longValue();
        // 获取当前时间并精确到秒级别
        Date now = truncateToSeconds(new Date());
        // 计算开工日期并精确到秒级别
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(planEndDay);
        // 分批减去秒数,避免int溢出
        long seconds = processTotalTimeSeconds;
        while (seconds > 0) {
            int step = (seconds > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) seconds;
            calendar.add(Calendar.SECOND, -step);
            seconds -= step;
        }
        Date calculatedStartDay = truncateToSeconds(calendar.getTime());
        // 如果计划完工日期小于等于当前时间,则计划开工日也设为计划完工日(当前时间)
        if (planEndDay.compareTo(now) <= 0) {
            stat.setProcessPlanStartDay(truncateToSeconds(planEndDay));
            return true; // 发生时间冲突
        }
        // 如果计算出的计划开工日小于等于当前时间,则设为当前时间
        else if (calculatedStartDay.compareTo(now) <= 0) {
            stat.setProcessPlanStartDay(now);
            return true; // 首次出现时间冲突
        }
        // 否则正常设置计划开工日
        else {
            stat.setProcessPlanStartDay(calculatedStartDay);
            return hasTimeConflict; // 保持原有冲突状态
        }
    }
    /**
     * 将日期精确到秒级别,去除毫秒信息
     *
     * @param date 原始日期
     * @return 精确到秒的日期
     */
    private Date truncateToSeconds(Date date) {
        if (date == null) {
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }
    /**
     * 设置日期相关组件(年、月、日)
     */
    private void setDateComponents(ApsGasPipingRouteStat stat) {
        if (stat.getProcessPlanStartDay() != null) {
            SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
            SimpleDateFormat monthFormat = new SimpleDateFormat("MM");
            SimpleDateFormat dayFormat = new SimpleDateFormat("dd");
            stat.setPlanStartYear(yearFormat.format(stat.getProcessPlanStartDay()));
            stat.setPlanStartMonth(monthFormat.format(stat.getProcessPlanStartDay()));
            stat.setPlanStartDay(dayFormat.format(stat.getProcessPlanStartDay()));
        }
    }
    /**
     * 聚合气体管路产能负载统计数据
     * 在Service层完成聚合处理,支持多维度分组
     *
     * @param params 聚合参数
     * @return 聚合结果
     */
    @Override
    public Map<String, Object> aggregateGasPipingRouteStat(Map<String, Object> params) {
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> plantTable = new ArrayList<>();
        // 获取时间颗粒度参数
        String timeGranularity = params.containsKey("timeGranularity") ?
                (String) params.get("timeGranularity") : "day"; // 默认为"日"粒度
        // 确定分组维度 - 工厂、专业、车间
        boolean groupByPlant = params.containsKey("groupByPlant") && Boolean.TRUE.equals(params.get("groupByPlant"));
        boolean groupByMajor = params.containsKey("groupByMajor") && Boolean.TRUE.equals(params.get("groupByMajor"));
        boolean groupByWorkshop = params.containsKey("groupByWorkshop") && Boolean.TRUE.equals(params.get("groupByWorkshop"));
        // 构建查询条件
        Map<String, Object> queryParams = new HashMap<>();
        // 设置查询条件 - 分类条件
        if (params.containsKey("plant")) {
            Object plantParam = params.get("plant");
            if (plantParam instanceof List) {
                queryParams.put("plants", plantParam);
            } else if (plantParam instanceof String) {
                // 处理可能的逗号分隔字符串
                String plantStr = (String) plantParam;
                if (plantStr.contains(",")) {
                    List<String> plantList = Arrays.asList(plantStr.split(","));
                    queryParams.put("plants", plantList);
                } else {
                    queryParams.put("plant", plantStr);
                }
            }
        }
        if (params.containsKey("major")) {
            Object majorParam = params.get("major");
            if (majorParam instanceof List) {
                queryParams.put("majors", majorParam);
            } else if (majorParam instanceof String) {
                // 处理可能的逗号分隔字符串
                String majorStr = (String) majorParam;
                if (majorStr.contains(",")) {
                    List<String> majorList = Arrays.asList(majorStr.split(","));
                    queryParams.put("majors", majorList);
                } else {
                    queryParams.put("major", majorStr);
                }
            }
        }
        if (params.containsKey("workshop")) {
            Object workshopParam = params.get("workshop");
            if (workshopParam instanceof List) {
                queryParams.put("workshops", workshopParam);
            } else if (workshopParam instanceof String) {
                // 处理可能的逗号分隔字符串
                String workshopStr = (String) workshopParam;
                if (workshopStr.contains(",")) {
                    List<String> workshopList = Arrays.asList(workshopStr.split(","));
                    queryParams.put("workshops", workshopList);
                } else {
                    queryParams.put("workshop", workshopStr);
                }
            }
        }
        // 确定时间范围
        List<String> timePoints = new ArrayList<>();
        if ("day".equalsIgnoreCase(timeGranularity)) {
            // "日"粒度:传入一个月份,聚合该月每一天的数据
            if (!params.containsKey("yearMonth")) {
                result.put("plantTable", plantTable);
                result.put("message", "日粒度聚合需要指定yearMonth参数");
                return result;
            }
            String yearMonth = (String) params.get("yearMonth");
            // 生成指定月份中的每一天
            YearMonth ym = YearMonth.parse(yearMonth);
            int daysInMonth = ym.lengthOfMonth();
            for (int day = 1; day <= daysInMonth; day++) {
                String dayStr = String.format("%02d", day);
                timePoints.add(yearMonth + "-" + dayStr);
            }
            // 设置查询参数
            String[] ymParts = yearMonth.split("-");
            queryParams.put("yearStart", ymParts[0]);
            queryParams.put("monthStart", ymParts[1]);
            queryParams.put("yearEnd", ymParts[0]);
            queryParams.put("monthEnd", ymParts[1]);
        } else if ("month".equalsIgnoreCase(timeGranularity)) {
            // "月"粒度:传入一个时间区间,聚合该区间内每个月的数据
            if (!params.containsKey("startDate") || !params.containsKey("endDate")) {
                result.put("plantTable", plantTable);
                result.put("message", "月粒度聚合需要指定startDate和endDate参数");
                return result;
            }
            String startDate = (String) params.get("startDate");
            String endDate = (String) params.get("endDate");
            // 解析开始和结束年月
            YearMonth start = YearMonth.parse(startDate);
            YearMonth end = YearMonth.parse(endDate);
            // 生成区间内的每个月
            timePoints = getYearMonthsInRange(start, end);
            // 设置查询参数
            String[] startParts = startDate.split("-");
            String[] endParts = endDate.split("-");
            queryParams.put("yearStart", startParts[0]);
            queryParams.put("monthStart", startParts[1]);
            queryParams.put("yearEnd", endParts[0]);
            queryParams.put("monthEnd", endParts[1]);
        }
        if (timePoints.isEmpty()) {
            result.put("plantTable", plantTable);
            result.put("message", "未能生成有效的时间点列表");
            return result;
        }
        // 查询原始数据(不依赖数据库聚合)
        List<Map<String, Object>> rawData = apsGasPipingRouteStatMapper.selectRawStatData(queryParams);
        if (rawData.isEmpty()) {
            result.put("plantTable", plantTable);
            result.put("timePoints", timePoints);
            return result;
        }
        // 在Service层完成聚合
        // 使用组合key来实现多维度分组(工序名 + 可选的工厂/专业/车间)
        Map<String, Map<String, Object>> groupInfoMap = new HashMap<>();
        Map<String, Map<String, BigDecimal>> groupTimeDataMap = new HashMap<>();
        // 遍历原始数据,按多维度分组进行聚合
        for (Map<String, Object> data : rawData) {
            String processName = getStringValue(data, "processName");
            if (processName == null) {
                log.warn("跳过处理:工序名称为null");
                continue;
            }
            // 处理开工日期
            Date processPlanStartDay = (Date) data.get("processPlanStartDay");
            if (processPlanStartDay == null) {
                log.warn("跳过处理:计划开工日为null, processName={}", processName);
                continue;
            }
            // 构建分组键 - 基于工序名和可选的其他维度
            String plant = getStringValue(data, "plant");
            String major = getStringValue(data, "major");
            String workshop = getStringValue(data, "workshop");
            StringBuilder groupKeyBuilder = new StringBuilder(processName);
            // 根据用户选择的分组维度添加到分组键
            if (groupByPlant && plant != null) {
                groupKeyBuilder.append("_PLANT_").append(plant);
            }
            if (groupByMajor && major != null) {
                groupKeyBuilder.append("_MAJOR_").append(major);
            }
            if (groupByWorkshop && workshop != null) {
                groupKeyBuilder.append("_WORKSHOP_").append(workshop);
            }
            String groupKey = groupKeyBuilder.toString();
            // 记录分组的基本信息(只记录一次)
            if (!groupInfoMap.containsKey(groupKey)) {
                Map<String, Object> groupInfo = new HashMap<>();
                groupInfo.put("processName", processName);
                groupInfo.put("plant", plant);
                groupInfo.put("major", major);
                groupInfo.put("workshop", workshop);
                groupInfoMap.put(groupKey, groupInfo);
            }
            // 计算时间点Key
            String timeKey;
            LocalDate planStartLocalDate = processPlanStartDay.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            if ("day".equalsIgnoreCase(timeGranularity)) {
                // 日粒度: 2025-05-25
                timeKey = planStartLocalDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
            } else {
                // 月粒度: 2025-05
                timeKey = planStartLocalDate.getYear() + "-" + String.format("%02d", planStartLocalDate.getMonthValue());
            }
            // 获取该分组的时间点映射,如果不存在则创建
            if (!groupTimeDataMap.containsKey(groupKey)) {
                groupTimeDataMap.put(groupKey, new HashMap<>());
            }
            Map<String, BigDecimal> timeMap = groupTimeDataMap.get(groupKey);
            // 累加该分组在该时间点的工时数据
            BigDecimal processTotalTime = getBigDecimalValue(data, "processTotalTime");
            if (processTotalTime != null) {
                BigDecimal currentTotal = timeMap.getOrDefault(timeKey, BigDecimal.ZERO);
                timeMap.put(timeKey, currentTotal.add(processTotalTime));
            }
        }
        // 构建最终返回的数据结构
        for (String groupKey : groupInfoMap.keySet()) {
            Map<String, Object> processEntry = new HashMap<>();
            Map<String, Object> processDetail = new HashMap<>();
            // 获取该分组的基本信息
            Map<String, Object> groupInfo = groupInfoMap.get(groupKey);
            String processName = (String) groupInfo.get("processName");
            // 添加工序基本信息
            if (groupByPlant) {
                processDetail.put("plant", groupInfo.get("plant"));
            }
            if (groupByMajor) {
                processDetail.put("major", groupInfo.get("major"));
            }
            if (groupByWorkshop) {
                processDetail.put("workshop", groupInfo.get("workshop"));
            }
            // 添加时间数据
            List<Map<String, Object>> timeDataList = new ArrayList<>();
            Map<String, BigDecimal> timeMap = groupTimeDataMap.getOrDefault(groupKey, new HashMap<>());
            for (String timePoint : timePoints) {
                Map<String, Object> pointData = new HashMap<>();
                pointData.put("planDay", timePoint);
                pointData.put("designTimes", 0); // 设计工时暂时给0
                // 获取该时间点的需求工时,如果不存在则设为0
                BigDecimal requireTimes = timeMap.getOrDefault(timePoint, BigDecimal.ZERO);
                pointData.put("requireTimes", requireTimes);
                // 计算产能负荷(暂时为0)
                pointData.put("capacityLoad", 0);
                timeDataList.add(pointData);
            }
            processDetail.put("timeData", timeDataList);
            processEntry.put(processName, processDetail);
            plantTable.add(processEntry);
        }
        result.put("plantTable", plantTable);
        result.put("timePoints", timePoints);
        return result;
    }
    /**
     * 从Map中安全获取String值
     */
    private String getStringValue(Map<String, Object> data, String key) {
        Object value = data.get(key);
        if (value == null) {
            return null;
        }
        return value.toString();
    }
    /**
     * 从Map中安全获取BigDecimal值
     */
    private BigDecimal getBigDecimalValue(Map<String, Object> data, String key) {
        Object value = data.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        } else if (value instanceof Number) {
            return new BigDecimal(((Number) value).toString());
        } else if (value instanceof String) {
            try {
                return new BigDecimal((String) value);
            } catch (NumberFormatException e) {
                log.warn("无法将值转换为BigDecimal: {}", value);
                return null;
            }
        }
        return null;
    }
}