liusheng
4 天以前 05f8fb61b57f601b39a89284a9a1af737e3c9c1d
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -21,6 +21,7 @@
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ScheduleSubtaskBuilder;
import com.smartor.config.PhoneUtils;
import com.smartor.domain.DTO.MydTrendReqDTO;
import com.smartor.domain.DTO.QuestionCountKsOrBqDTO;
import com.smartor.domain.DTO.ServiceSubtaskDetailDTO;
import com.smartor.domain.*;
@@ -785,6 +786,7 @@
        serviceSubtask.setUpdateTime(DateUtils.getNowDate());
        if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 6)
            serviceSubtask.setFinishtime(new Date());
        return serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
    }
@@ -5136,13 +5138,13 @@
    }
    @Override
    public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
    public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
        //先清理一下重复数据
        List<Long> repeatIds = serviceSubtaskDetailMapper.selectRepeatDetailIds();
        if (!CollectionUtils.isEmpty(repeatIds)) {
            serviceSubtaskDetailMapper.deleteRepeatDetailByIds(repeatIds);
        }
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOption(taskIds, startOutHospTime, endOutHospTime, scoreStart, scoreEnd, scriptContent, valueType);
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOption(taskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType);
        // 处理 targetvalue:按 & 分割成多条记录
        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
@@ -5187,88 +5189,135 @@
     * 根据科室或病区统计问卷选项(科室、题目查询的科室评分表)
     */
    @Override
    public List<QuestionCountKsOrBqDTO> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO vo) {
    public Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO vo) {
        // 1. 查明细
        List<QuestionCountKsOrBqDTO> sourceList = serviceSubtaskMapper.statQuestionOptionByKsOrBq(vo);
        if (CollectionUtils.isEmpty(sourceList)) {
            return Collections.emptyList();
            return Collections.emptyMap();
        }
        // 2. 判断按科室还是按病区
        boolean byDept = vo.getLeaveldeptcodes() != null;
        boolean byDistrict = !byDept && vo.getLeavehospitaldistrictcodes() != null;
        if (!byDept && !byDistrict) {
            return Collections.emptyList();
            return Collections.emptyMap();
        }
        // 3. key = 科室/病区 + 题目 + 选项
        Map<String, QuestionCountKsOrBqDTO> map = new LinkedHashMap<>();
        for (QuestionCountKsOrBqDTO dto : sourceList) {
            if (StringUtils.isEmpty(dto.getTargetvalue())) {
                continue;
            }
            // 分组名:科室 或 病区
            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
            if (StringUtils.isEmpty(groupName)) {
                continue;
            }
            // 关键:两边都用 trimAll,兼容全角空格
            String asrtext = trimAll(dto.getAsrtext());
            String questionKey = dto.getTaskid() + "\u0001" + trimAll(dto.getQeustionText());
            String asrtext = trimAll(dto.getAsrtext());            // 用户实际选中的选项
            String questionText = trimAll(dto.getQuestionText());  // 题目
            if (StringUtils.isEmpty(questionText)) {
                continue;
            }
            // 拆分 targetvalue,每个选项都要有一条记录
            for (String raw : dto.getTargetvalue().split("&")) {
                String option = trimAll(raw);      // ← 修复点:用 trimAll 而不是 trim
                String option = trimAll(raw);
                if (option.isEmpty()) {
                    continue;
                }
                String key = buildKey(groupName, option, questionKey);
                // key = 科室/病区 + 题目 + 选项
                String key = buildKey(groupName, questionText, option);
                QuestionCountKsOrBqDTO item = map.get(key);
                if (item == null) {
                    item = DtoConversionUtils.sourceToTarget(dto, QuestionCountKsOrBqDTO.class);
                    item.setTargetvalue(option);       // 输出的 targetvalue 就是当前选项
                    item.setTargetvalue(option);
                    item.setAsrtext(option);
                    item.setQuestionText(dto.getQuestionText());
                    item.setAnswerCount("0");
                    item.setSingleScore("0");
                    map.put(key, item);
                }
                // 只有当前选项 == 用户实际选中的选项,才累加
                if (option.equals(asrtext)) {
                    item.setAnswerCount(String.valueOf(parseCount(item.getAnswerCount()) + 1));
                    item.setSingleScore(String.valueOf(new BigDecimal(item.getSingleScore()).add(new BigDecimal(dto.getSingleScore()))));
                    int count = parseCount(item.getAnswerCount()) + 1;
                    item.setAnswerCount(String.valueOf(count));
                    BigDecimal totalScore = new BigDecimal(item.getSingleScore())
                            .add(new BigDecimal(dto.getSingleScore() == null ? "0" : dto.getSingleScore()));
                    item.setSingleScore(totalScore.toPlainString());
                }
            }
        }
        ArrayList<QuestionCountKsOrBqDTO> questionCountKsOrBqDTOS = new ArrayList<>(map.values());
        for (QuestionCountKsOrBqDTO questionCountKsOrBqDTO : questionCountKsOrBqDTOS) {
            String singleScore = questionCountKsOrBqDTO.getSingleScore();
            String answerCount = questionCountKsOrBqDTO.getAnswerCount();
        // 4. 计算平均分
        for (QuestionCountKsOrBqDTO dto : map.values()) {
            String answerCount = dto.getAnswerCount();
            String singleScore = dto.getSingleScore();
            BigDecimal avg;
            if (StringUtils.isEmpty(answerCount)
                    || BigDecimal.ZERO.compareTo(new BigDecimal(answerCount.trim())) == 0) {
                // 除数为 0,避免 / by zero
                avg = BigDecimal.ZERO;
            } else {
                avg = new BigDecimal(singleScore)
                avg = new BigDecimal(singleScore == null ? "0" : singleScore)
                        .divide(new BigDecimal(answerCount.trim()), 2, RoundingMode.HALF_UP);
            }
            questionCountKsOrBqDTO.setAvgScore(avg.toPlainString());
            dto.setAvgScore(avg.toPlainString());
        }
        return questionCountKsOrBqDTOS;
        // 5. 转成 Map<String, Map<String, List<QuestionCountKsOrBqDTO>>>
        //    第一层 key:科室/病区
        //    第二层 key:题目
        //    第二层 value:该题目下的所有选项列表
        Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> result = new LinkedHashMap<>();
        for (QuestionCountKsOrBqDTO dto : map.values()) {
            // 分组名
            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
            if (StringUtils.isEmpty(groupName)) {
                continue;
            }
            // 题目名
            String questionText = dto.getQuestionText();
            if (StringUtils.isEmpty(questionText)) {
                continue;
            }
            // 第一层:科室/病区
            Map<String, List<QuestionCountKsOrBqDTO>> questionMap =
                    result.computeIfAbsent(groupName, k -> new LinkedHashMap<>());
            // 第二层:题目
            List<QuestionCountKsOrBqDTO> optionList =
                    questionMap.computeIfAbsent(questionText, k -> new ArrayList<>());
            optionList.add(dto);
        }
        return result;
    }
    @Override
    public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
    public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, Date startFinishTime, Date endFinishTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
        //先清理一下重复数据
        List<Long> repeatIds = serviceSubtaskDetailMapper.selectRepeatDetailIds();
        if (!CollectionUtils.isEmpty(repeatIds)) {
            serviceSubtaskDetailMapper.deleteRepeatDetailByIds(repeatIds);
        }
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, scoreStart, scoreEnd, scriptContent, valueType);
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType);
        // 处理 targetvalue:按 & 分割成多条记录
        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
        for (ServiceSubtaskDetailRatioExport export : serviceSubtaskDetailRatioExports) {
@@ -5537,6 +5586,34 @@
        }
        return content;
    }
    /**
     * 满意度趋势查询
     *
     * @param mydTrendReqVO
     * @return
     */
    @Override
    public List<MydTrendReqDTO> mydTrend(MydTrendReqVO mydTrendReqVO) {
        List<MydTrendReqDTO> list = serviceSubtaskMapper.mydTrend(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
        if (mydTrendReqVO.getType().equals("week")) list = getWeekDate(mydTrendReqVO, list);
        if (mydTrendReqVO.getType().equals("year")) list = getYearDate(mydTrendReqVO, list);
        if (mydTrendReqVO.getType().equals("month")) list = getMonthDate(mydTrendReqVO, list);
        if (mydTrendReqVO.getType().equals("day")) list = getDayDate(mydTrendReqVO, list);
        return list;
    }
    @Override
    public List<MydTrendReqDTO> sltdMydTotalByDimension(MydTrendReqVO mydTrendReqVO) {
        List<MydTrendReqDTO> list = null;
        if (mydTrendReqVO.getDimensionType().equals("1"))
            list = serviceSubtaskMapper.sltdMydTotalByDimension(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
        else if (mydTrendReqVO.getDimensionType().equals("2"))
            list = serviceSubtaskMapper.sltdMydTotalByYQ(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
        return list;
    }
    public ServiceSubtask boxedServiceSubtask(ServiceTask serviceTask, ServiceTaskdept serviceTaskdept, PatArchive patArchive) {
        ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTask, ServiceSubtask.class);
@@ -6006,20 +6083,23 @@
    /**
     * 问题结果占比
     *
     * @param questionResultVO
     * @param sltdMydTotalVO
     * @param serviceSubtaskRes
     * @return
     */
    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(QuestionResultVO questionResultVO, List<ServiceSubtaskRes> serviceSubtaskRes) {
    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(SltdMydTotalVO sltdMydTotalVO, List<ServiceSubtaskRes> serviceSubtaskRes) {
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = null;
        if (StringUtils.isNotEmpty(questionResultVO.getServiceType()) && questionResultVO.getServiceType().equals("2")) {
        if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("2") && (sltdMydTotalVO.getServiceTypeList().contains("6") || sltdMydTotalVO.getServiceTypeList().contains("14"))) {
            //serviceType,即有随访又有满意度,暂时没法统计
            return serviceSubtaskDetailRatioExports;
        } else if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("2")) {
            //获取serviceSubtaskList中的所有taskid
            List<Long> taskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getTaskid).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
            serviceSubtaskDetailRatioExports = statQuestionOption(taskIds, questionResultVO.getStartOutHospTime(), questionResultVO.getEndOutHospTime(), questionResultVO.getQuestionContent(), questionResultVO.getStartScore(), questionResultVO.getEndScore(), questionResultVO.getValueType());
        } else if (StringUtils.isNotEmpty(questionResultVO.getServiceType()) && questionResultVO.getServiceType().equals("6")) {
            serviceSubtaskDetailRatioExports = statQuestionOption(taskIds, sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
        } else if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("6") || sltdMydTotalVO.getServiceTypeList().contains("14")) {
            //满意度统计 (通过 subtask 的 id 去统计)
            List<Long> subtaskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getId).collect(Collectors.toList());
            serviceSubtaskDetailRatioExports = statQuestionOptionBySubtaskIds(subtaskIds, questionResultVO.getStartOutHospTime(), questionResultVO.getEndOutHospTime(), questionResultVO.getQuestionContent(), questionResultVO.getStartScore(), questionResultVO.getEndScore(), questionResultVO.getValueType());
            serviceSubtaskDetailRatioExports = statQuestionOptionBySubtaskIds(subtaskIds, sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
        }
        return serviceSubtaskDetailRatioExports;
    }
@@ -6082,15 +6162,12 @@
    private static Boolean isBlankChar(char c) {
        return c == '\u3000'          // 全角空格
                || c == ' '            // 半角空格
                || c == '\t'
                || c == '\n'
                || c == '\r'
                || c == '\f'
                || Character.isWhitespace(c);
                || c == '\t' || c == '\n' || c == '\r' || c == '\f' || Character.isWhitespace(c);
    }
    private String buildKey(String groupName, String option, String questionKey) {
        return questionKey + KEY_SEP + groupName + KEY_SEP + option;
    private String buildKey(String groupName, String questionText, String option) {
        return groupName + "\u0001" + questionText + "\u0001" + option;
    }
    private static int parseCount(String s) {
@@ -6103,4 +6180,239 @@
            return 0;
        }
    }
    private List<MydTrendReqDTO> getWeekDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
        // 1. 计算总周数(和 SQL 里的公式保持一致)
        long days = (mydTrendReqVO.getEndFinishTime().getTime() - mydTrendReqVO.getStartFinishTime().getTime()) / (1000L * 60 * 60 * 24);
        int totalWeeks = (int) (days / 7) + 1;
        // 2. 已有数据:period -> totalScore
        Map<Integer, String> exist = new HashMap<>();
        for (MydTrendReqDTO row : list) {
            // period 是数字,可能是 Integer / Long / BigDecimal,统一转 int
            int p = Integer.parseInt(StringUtils.isNotEmpty(row.getPeriod()) ? row.getPeriod() : "0");
            exist.put(p, row.getTotalScore() != null ? row.getTotalScore() : "0");
            // 下面这行按你的原逻辑保留(虽然看起来有点多余)
            exist.put(p, String.valueOf(row.getTotalScore() != null ? row.getTotalScore() : 0));
        }
        // 3. 补全 1..totalWeeks
        List<MydTrendReqDTO> result = new ArrayList<>(totalWeeks);
        // 获取整体的开始时间
        long startTimeMillis = mydTrendReqVO.getStartFinishTime().getTime();
        long endTimeMillis = mydTrendReqVO.getEndFinishTime().getTime();
        long oneWeekMillis = 7L * 24 * 60 * 60 * 1000; // 一周的毫秒数
        for (int i = 1; i <= totalWeeks; i++) {
            MydTrendReqDTO row = new MydTrendReqDTO();
            row.setPeriod(String.valueOf(i));
            row.setTotalScore(exist.getOrDefault(i, "0"));
            // -------- 新增:设置每周的 startDate 和 endDate --------
            // 当前周开始时间 = 整体开始时间 + (i - 1) * 7天
            long currentStartMillis = startTimeMillis + (long) (i - 1) * oneWeekMillis;
            // 当前周结束时间 = 当前周开始时间 + 7天 - 1毫秒(保证是这一周的最后一刻)
            long currentEndMillis = currentStartMillis + oneWeekMillis - 1;
            // 如果是最后一周,结束时间不能超过整体的 endTime
            if (i == totalWeeks) {
                currentEndMillis = endTimeMillis;
            }
            row.setStartDate(new Date(currentStartMillis));
            row.setEndDate(new Date(currentEndMillis));
            // ----------------------------------------------------
            result.add(row);
        }
        return result;
    }
    private List<MydTrendReqDTO> getYearDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
        // 获取整体开始和结束时间
        Date startTime = mydTrendReqVO.getStartFinishTime();
        Date endTime = mydTrendReqVO.getEndFinishTime();
        // 1. 计算年份范围
        // 使用 Calendar 获取起始年份和结束年份
        Calendar cal = Calendar.getInstance();
        cal.setTime(startTime);
        int startYear = cal.get(Calendar.YEAR);
        cal.setTime(endTime);
        int endYear = cal.get(Calendar.YEAR);
        // 总年数 (包含首尾)
        int totalYears = endYear - startYear + 1;
        // 2. 已有数据:period -> totalScore (假设 period 存的是年份字符串,如 "2024")
        Map<String, String> exist = new HashMap<>();
        for (MydTrendReqDTO row : list) {
            String period = row.getPeriod() != null ? row.getPeriod() : "";
            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
        }
        // 3. 补全所有年份
        List<MydTrendReqDTO> result = new ArrayList<>(totalYears);
        for (int i = 0; i < totalYears; i++) {
            int currentYear = startYear + i;
            String periodKey = String.valueOf(currentYear);
            MydTrendReqDTO row = new MydTrendReqDTO();
            row.setPeriod(periodKey);
            // 如果没有该年份的数据,默认给 "0"
            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
            // -------- 设置该年份的 startDate 和 endDate --------
            Calendar yearCal = Calendar.getInstance();
            // 设置为该年的 1月1日 00:00:00
            yearCal.set(currentYear, Calendar.JANUARY, 1, 0, 0, 0);
            yearCal.set(Calendar.MILLISECOND, 0);
            row.setStartDate(yearCal.getTime());
            // 设置为该年的 12月31日 23:59:59
            yearCal.set(currentYear, Calendar.DECEMBER, 31, 23, 59, 59);
            yearCal.set(Calendar.MILLISECOND, 999);
            row.setEndDate(yearCal.getTime());
            // ----------------------------------------------------
            result.add(row);
        }
        return result;
    }
    private List<MydTrendReqDTO> getMonthDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
        // 获取整体开始和结束时间
        Date startTime = mydTrendReqVO.getStartFinishTime();
        Date endTime = mydTrendReqVO.getEndFinishTime();
        // 1. 计算月份范围
        Calendar cal = Calendar.getInstance();
        cal.setTime(startTime);
        int startYear = cal.get(Calendar.YEAR);
        int startMonth = cal.get(Calendar.MONTH); // 注意:Calendar.MONTH 从 0 开始
        cal.setTime(endTime);
        int endYear = cal.get(Calendar.YEAR);
        int endMonth = cal.get(Calendar.MONTH);
        // 总月数 = (结束年 - 开始年) * 12 + (结束月 - 开始月) + 1
        int totalMonths = (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
        // 2. 已有数据:period -> totalScore (period 存的是月份字符串,如 "2024-06")
        Map<String, String> exist = new HashMap<>();
        for (MydTrendReqDTO row : list) {
            String period = row.getPeriod() != null ? row.getPeriod() : "";
            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
        }
        // 3. 补全所有月份
        List<MydTrendReqDTO> result = new ArrayList<>(totalMonths);
        Calendar monthCal = Calendar.getInstance();
        monthCal.set(startYear, startMonth, 1, 0, 0, 0);
        monthCal.set(Calendar.MILLISECOND, 0);
        for (int i = 0; i < totalMonths; i++) {
            int currentYear = monthCal.get(Calendar.YEAR);
            int currentMonth = monthCal.get(Calendar.MONTH) + 1; // 转成 1-12 月
            // 拼接 period,格式:yyyy-MM,比如 "2024-06"
            // 如果你希望是 "2024-6",可以把 String.format 改成 currentMonth
            String periodKey = String.format("%04d-%02d", currentYear, currentMonth);
            MydTrendReqDTO row = new MydTrendReqDTO();
            row.setPeriod(periodKey);
            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
            // -------- 设置该月的 startDate 和 endDate --------
            // 当月开始时间:1号 00:00:00.000
            Calendar startCal = (Calendar) monthCal.clone();
            row.setStartDate(startCal.getTime());
            // 当月结束时间:下个月1号 00:00:00.000 减 1 毫秒
            Calendar endCal = (Calendar) monthCal.clone();
            endCal.add(Calendar.MONTH, 1);
            endCal.add(Calendar.MILLISECOND, -1);
            row.setEndDate(endCal.getTime());
            // ----------------------------------------------------
            result.add(row);
            // 进入下一个月
            monthCal.add(Calendar.MONTH, 1);
        }
        return result;
    }
    private List<MydTrendReqDTO> getDayDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
        // 获取整体开始和结束时间
        Date startTime = mydTrendReqVO.getStartFinishTime();
        Date endTime = mydTrendReqVO.getEndFinishTime();
        // 1. 计算总天数
        long oneDayMillis = 24L * 60 * 60 * 1000;
        // 注意:这里只算日期差,不考虑时分秒,否则可能差一天
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startTime);
        startCal.set(Calendar.HOUR_OF_DAY, 0);
        startCal.set(Calendar.MINUTE, 0);
        startCal.set(Calendar.SECOND, 0);
        startCal.set(Calendar.MILLISECOND, 0);
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endTime);
        endCal.set(Calendar.HOUR_OF_DAY, 0);
        endCal.set(Calendar.MINUTE, 0);
        endCal.set(Calendar.SECOND, 0);
        endCal.set(Calendar.MILLISECOND, 0);
        long days = (endCal.getTimeInMillis() - startCal.getTimeInMillis()) / oneDayMillis;
        int totalDays = (int) days + 1;
        // 2. 已有数据:period -> totalScore (period 存的是日期字符串,如 "2024-06-01")
        Map<String, String> exist = new HashMap<>();
        for (MydTrendReqDTO row : list) {
            String period = row.getPeriod() != null ? row.getPeriod() : "";
            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
        }
        // 3. 补全所有日期
        List<MydTrendReqDTO> result = new ArrayList<>(totalDays);
        Calendar dayCal = (Calendar) startCal.clone();
        for (int i = 0; i < totalDays; i++) {
            // 拼接 period,格式:yyyy-MM-dd
            String periodKey = String.format("%04d-%02d-%02d", dayCal.get(Calendar.YEAR), dayCal.get(Calendar.MONTH) + 1, dayCal.get(Calendar.DAY_OF_MONTH));
            MydTrendReqDTO row = new MydTrendReqDTO();
            row.setPeriod(periodKey);
            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
            // -------- 设置该天的 startDate 和 endDate --------
            Calendar startOfDay = (Calendar) dayCal.clone();
            row.setStartDate(startOfDay.getTime());
            Calendar endOfDay = (Calendar) dayCal.clone();
            endOfDay.set(Calendar.HOUR_OF_DAY, 23);
            endOfDay.set(Calendar.MINUTE, 59);
            endOfDay.set(Calendar.SECOND, 59);
            endOfDay.set(Calendar.MILLISECOND, 999);
            row.setEndDate(endOfDay.getTime());
            // ----------------------------------------------------
            result.add(row);
            // 进入下一天
            dayCal.add(Calendar.DAY_OF_MONTH, 1);
        }
        return result;
    }
}