# ForecastServiceImpl.java

    package cloud.wconnect.workperformance.service.monthlyreport.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.monthlyreport.AreaEnum;
    import cloud.wconnect.workperformance.entity.monthlyreport.*;
    import cloud.wconnect.workperformance.mapper.monthlyreport.ForecastMapper;
    import cloud.wconnect.workperformance.mapper.monthlyreport.ReportRemarkMapper;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.forecast.ForecastDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.forecast.ForecastDetailDTO;
    import cloud.wconnect.workperformance.service.monthlyreport.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.time.YearMonth;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    /**
     * <p>
     * 月报Forecast（预测）区域表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-09-01
     */
    @Service
    @DS("monthly_report")
    public class ForecastServiceImpl extends ServiceImpl<ForecastMapper, Forecast> implements IForecastService, IHandlePropertySettingChange, IHandleAreaTypeConfigChange {

        @Lazy
        @Autowired
        private IMonthlyReportService monthlyReportService;
        @Autowired
        private IReportRemarkService reportRemarkService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IAreaTypeConfigService areaTypeConfigService;
        @Autowired
        private ReportRemarkMapper reportRemarkMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private IMonthlyReportBudgetDatabaseService budgetDatabaseService;

        /**
         * 查询Forecast区域详情
         *
         * @param monthlyReport 月报
         * @param propertyDTO   资产DTO
         * @return cloud.wconnect.workperformance.pojo.dto.monthlyreport.forecast.ForecastDetailDTO
         * @author lichen
         * @date 2023/9/2
         */
        @Override
        public ForecastDetailDTO getForecast(MonthlyReport monthlyReport, PropertyDTO propertyDTO) {
            //查询备注
            String remark = reportRemarkService.listByReportIdAndAreaId(monthlyReport.getId(), AreaEnum.FORECAST.getAreaId());

            List<ForecastDTO> forecastDTOList = list(new LambdaQueryWrapper<Forecast>()
                    .eq(Forecast::getReportId, monthlyReport.getId())
                    .eq(Forecast::getPropertyId, propertyDTO.getId())
                    .orderByAsc(Forecast::getCategoryId))
                    .stream().map(item -> beanMapper.map(item, ForecastDTO.class)).toList();

            //校验是否历史月报
            Boolean isHistoryMonthlyReport = monthlyReportService.checkIsHistoryMonthlyReport(monthlyReport, propertyDTO);
            /**
             * 历史月报：按照区域表存储的数据，返回
             * 当前正在使用或未来的数据：按照最新的区域类型配置 组装返回对象
             */
            if (isHistoryMonthlyReport) {
                return new ForecastDetailDTO(forecastDTOList, remark);
            }
            //区域类型集合
            List<AreaTypeConfig> areaTypeConfigs = areaTypeConfigService.listByAreaId(propertyDTO.getId(), Lists.newArrayList(AreaEnum.FORECAST.getAreaId()));
            //按照日期分组
            Map<Integer, List<ForecastDTO>> date2DTOList = forecastDTOList.stream().collect(Collectors.groupingBy(ForecastDTO::getDate));

            //未来两个月的日期集合
            List<String> dateStrList = getNextTwoMonths(monthlyReport, propertyDTO.getTimezone());

            /**
             * 组装两个月的数据
             */
            List<ForecastDTO> resultList = Lists.newArrayListWithCapacity(areaTypeConfigs.size() * 2);
            for (String dateStr : dateStrList) {
                List<ForecastDTO> dtoListForDateStr = date2DTOList.get(Integer.valueOf(dateStr));
                /**
                 * 区域数据表中这个月的数据不为空时
                 * 把区域数据表中不存在的分类数据填充进去
                 */
                if (CollectionUtils.isNotEmpty(dtoListForDateStr)) {
                    Map<Integer, ForecastDTO> categoryId2DTO = dtoListForDateStr.stream().collect(Collectors.toMap(ForecastDTO::getCategoryId, Function.identity()));
                    for (AreaTypeConfig areaTypeConfig : areaTypeConfigs) {
                        ForecastDTO forecastDTO = categoryId2DTO.get(areaTypeConfig.getId());
                        if (Objects.isNull(forecastDTO)) {
                            resultList.add(ForecastDTO.builder()
                                    .date(Integer.valueOf(dateStr))
                                    .categoryId(areaTypeConfig.getId())
                                    .categoryName(areaTypeConfig.getName())
                                    .contentType(areaTypeConfig.getContentType())
                                    .build());
                        } else {
                            resultList.add(forecastDTO);
                        }
                    }
                } else {//区域数据表中这个月的数据为空时，按照分类集合生成这个月的数据
                    List<ForecastDTO> dtoListByAreaTypes = areaTypeConfigs.stream().map(areaTypeConfig -> ForecastDTO.builder()
                            .date(Integer.valueOf(dateStr))
                            .categoryId(areaTypeConfig.getId())
                            .categoryName(areaTypeConfig.getName())
                            .build()).toList();
                    resultList.addAll(dtoListByAreaTypes);
                }
            }
            return new ForecastDetailDTO(resultList, remark);
        }

        /**
         * 保存Forecast区域数据
         *
         * @param monthlyReport     月报
         * @param propertyDTO       资产DTO
         * @param forecastDetailDTO 区域数据
         * @param loginUserId       登录用户id
         * @return void
         * @author lichen
         * @date 2023/9/2
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(MonthlyReport monthlyReport, PropertyDTO propertyDTO, ForecastDetailDTO forecastDetailDTO, int loginUserId) {
            Integer propertyId = propertyDTO.getId();
            Integer tenantId = propertyDTO.getTenantId();
            remove(new LambdaQueryWrapper<Forecast>()
                    .eq(Forecast::getReportId, monthlyReport.getId())
                    .eq(Forecast::getPropertyId, propertyId));

            //保存区域信息
            List<Forecast> forecasts = beanMapper.mapList(forecastDetailDTO.getList(), Forecast.class, (dto, entity) -> {
                entity.setTenantId(tenantId);
                entity.setPropertyId(propertyId);
                entity.setReportId(monthlyReport.getId());
                entity.setUpdateId(loginUserId);
                entity.setUpdateTime(DateTimeUtils.nowTimestamp());
            });
            saveBatch(forecasts);

            //保存备注
            ReportRemark reportRemark = ReportRemark.builder()
                    .tenantId(tenantId)
                    .propertyId(propertyId)
                    .reportId(monthlyReport.getId())
                    .areaId(AreaEnum.FORECAST.getAreaId())
                    .remark(forecastDetailDTO.getRemark())
                    .build();
            reportRemarkMapper.saveOrUpdateByUnique(reportRemark);
        }

        /**
         * 删除区域数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @return void
         * @author lichen
         * @date 2023/9/5
         */
        @Override
        public void delete(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports) {
            List<Long> reportIds = monthlyReports.stream().map(MonthlyReport::getId).toList();
            if (CollectionUtils.isEmpty(reportIds)) return;
            remove(new LambdaQueryWrapper<Forecast>()
                    .in(Forecast::getReportId, reportIds)
                    .eq(Forecast::getPropertyId, propertyDTO.getId()));
        }

        /**
         * 更新分类
         *
         * @param propertyDTO       资产DTO
         * @param monthlyReports    月报集合
         * @param areaTypeConfig    分类
         * @param oriAreaTypeConfig 修改前的 分类
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        public void updateCategory(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, AreaTypeConfig areaTypeConfig, AreaTypeConfig oriAreaTypeConfig) {
            List<Long> monthlyReportIds = monthlyReports.stream().map(MonthlyReport::getId).collect(Collectors.toList());

            Map<YearMonth, Map<Integer, BigDecimal>> yearMonth2Type2Budget = new HashMap<>();
            if (Objects.nonNull(areaTypeConfig.getType())) {
                List<YearMonth> yearMonths = monthlyReports.stream()
                        .flatMap(monthlyReport -> {
                            YearMonth yearMonth = YearMonth.of(monthlyReport.getYear(), monthlyReport.getMonth());
                            return Stream.of(yearMonth.plusMonths(1), yearMonth.plusMonths(2));
                        })
                        .distinct()
                        .sorted()
                        .toList();
                // 获取指定type Budget Datebase功能的budget值
                yearMonth2Type2Budget = budgetDatabaseService.listMultiMonthBudget(propertyDTO.getId(), yearMonths,
                        Collections.singletonList(areaTypeConfig.getType()));
            }
            final Map<YearMonth, Map<Integer, BigDecimal>> finalYearMonth2Type2Budget = yearMonth2Type2Budget;

            //查询需要更新分类名称的数据
            List<Forecast> forecasts = list(new LambdaQueryWrapper<Forecast>()
                    .in(Forecast::getReportId, monthlyReportIds)
                    .eq(Forecast::getPropertyId, propertyDTO.getId())
                    .eq(Forecast::getCategoryId, areaTypeConfig.getId()));
            if (CollectionUtils.isEmpty(forecasts)) return;

            long nowTimestamp = DateTimeUtils.nowTimestamp();
            forecasts.forEach(item -> {
                //设置新的分类名称 和 内容类型
                item.setCategoryName(areaTypeConfig.getName()).setContentType(areaTypeConfig.getContentType());
                // 更新人为0且type不为null时，重新初始化数据
                if (item.getUpdateId() == 0 && Objects.nonNull(areaTypeConfig.getType())) {
                    YearMonth currentYearMonth = YearMonthUtils.getYearMonth(item.getDate().toString());
                    BigDecimal budget = finalYearMonth2Type2Budget.getOrDefault(currentYearMonth, Collections.emptyMap()).get(areaTypeConfig.getType());

                    item.setBudget(budget);
                    item.setUpdateTime(nowTimestamp);
                }
            });
            //更新
            baseMapper.batchUpdateWithNull(forecasts);
        }

        /**
         * 删除区域分类id对应的数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @param categoryId     分类id
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        public void deleteAreaDataByCategoryId(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, Integer categoryId) {
            List<Long> monthlyReportIds = monthlyReports.stream().map(MonthlyReport::getId).collect(Collectors.toList());

            //删除区域分类id对应的数据
            remove(new LambdaQueryWrapper<Forecast>()
                    .in(Forecast::getReportId, monthlyReportIds)
                    .eq(Forecast::getPropertyId, propertyDTO.getId())
                    .eq(Forecast::getCategoryId, categoryId));
        }

        /**
         * 根据分类，新增区域数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @param areaTypeConfig 区域分类
         * @return void
         * @author lichen
         * @date 2023/9/8
         */
        @Override
        public void insertAreaDataByCategory(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, AreaTypeConfig areaTypeConfig) {
            Map<YearMonth, Map<Integer, BigDecimal>> yearMonth2Type2Budget = new HashMap<>();
            if (Objects.nonNull(areaTypeConfig.getType())) {
                // 获取年月集合
                List<YearMonth> yearMonths = monthlyReports.stream()
                        .flatMap(monthlyReport -> {
                            YearMonth yearMonth = YearMonth.of(monthlyReport.getYear(), monthlyReport.getMonth());
                            return Stream.of(yearMonth.plusMonths(1), yearMonth.plusMonths(2));
                        })
                        .distinct()
                        .sorted()
                        .toList();
                // 获取指定type Budget Datebase功能的budget值
                yearMonth2Type2Budget = budgetDatabaseService.listMultiMonthBudget(propertyDTO.getId(), yearMonths,
                        Collections.singletonList(areaTypeConfig.getType()));
            }

            List<Forecast> forecasts = Lists.newArrayListWithCapacity(monthlyReports.size() * 2);
            for (MonthlyReport monthlyReport : monthlyReports) {
                YearMonth currentYearMonth = YearMonth.of(monthlyReport.getYear(), monthlyReport.getMonth());
                //未来两个月的日期集合
                ArrayList<YearMonth> nextTowMonths = Lists.newArrayList(currentYearMonth.plusMonths(1), currentYearMonth.plusMonths(2));
                for (YearMonth yearMonth : nextTowMonths) {
                    long yearMonthLong = YearMonthUtils.parseYearMonthToLong(yearMonth);

                    Forecast forecast = Forecast.builder()
                            .tenantId(propertyDTO.getTenantId())
                            .propertyId(propertyDTO.getId())
                            .reportId(monthlyReport.getId())
                            .date((int) yearMonthLong)
                            .categoryId(areaTypeConfig.getId())
                            .categoryName(areaTypeConfig.getName())
                            .contentType(areaTypeConfig.getContentType())
                            .updateId(0)
                            .updateTime(DateTimeUtils.nowTimestamp())
                            .build();
                    if (Objects.nonNull(areaTypeConfig.getType())) {
                        //指定月份指定type的budget指标
                        BigDecimal budget = yearMonth2Type2Budget.getOrDefault(yearMonth, Collections.emptyMap()).getOrDefault(areaTypeConfig.getType(), null);
                        forecast.setBudget(budget);
                    }
                    forecasts.add(forecast);
                }
            }
            saveBatch(forecasts);
        }

        /**
         * 初始化区域数据
         *
         * @param propertyDTO            资产DTO
         * @param monthlyReports         月报集合
         * @param areaId2AreaTypeConfigs 区域分类集合
         * @return void
         * @author lichen
         * @date 2023/9/8
         */
        @Override
        public void init(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports,
                         Map<Integer, List<AreaTypeConfig>> areaId2AreaTypeConfigs) {
            //过滤出开通对应区域的月报
            List<MonthlyReport> openAreaMonthlyReports = monthlyReports.stream().filter(monthlyReport -> {
                List<Integer> openAreaIds = JsonUtils.convertStringToList(objectMapper, monthlyReport.getAreaIds(), Integer.class);
                return openAreaIds.contains(AreaEnum.FORECAST.getAreaId());
            }).toList();

            if (CollectionUtils.isEmpty(openAreaMonthlyReports)) return;

            //获取分类集合
            List<AreaTypeConfig> areaTypeConfigs = areaId2AreaTypeConfigs.get(AreaEnum.FORECAST.getAreaId());
            if (CollectionUtils.isEmpty(areaTypeConfigs)) return;

            // 指标类型集合
            List<Integer> typeList = areaTypeConfigs.stream().map(AreaTypeConfig::getType).filter(Objects::nonNull).toList();
            Map<YearMonth, Map<Integer, BigDecimal>> yearMonth2Type2Budget = new HashMap<>();
            if(CollectionUtils.isNotEmpty(typeList)){
                // 开通月报的月份集合
                List<YearMonth> yearMonths = openAreaMonthlyReports.stream()
                        .flatMap(monthlyReport -> {
                            YearMonth yearMonth = YearMonth.of(monthlyReport.getYear(), monthlyReport.getMonth());
                            return Stream.of(yearMonth.plusMonths(1), yearMonth.plusMonths(2));
                        })
                        .distinct()
                        .sorted()
                        .toList();
                // 获取指定type Budget Datebase功能的budget值
                yearMonth2Type2Budget = budgetDatabaseService.listMultiMonthBudget(propertyDTO.getId(), yearMonths, typeList);
            }

            List<Forecast> forecasts = Lists.newArrayListWithCapacity(areaTypeConfigs.size() * openAreaMonthlyReports.size() * 2);

            for (MonthlyReport openAreaMonthlyReport : openAreaMonthlyReports) {
                YearMonth currentYearMonth = YearMonth.of(openAreaMonthlyReport.getYear(), openAreaMonthlyReport.getMonth());
                //未来两个月的日期集合
                ArrayList<YearMonth> nextTowMonths = Lists.newArrayList(currentYearMonth.plusMonths(1), currentYearMonth.plusMonths(2));

                for (YearMonth nextMonth : nextTowMonths) {
                    long yearMonthLong = YearMonthUtils.parseYearMonthToLong(nextMonth);

                    for (AreaTypeConfig areaTypeConfig : areaTypeConfigs) {
                        Forecast forecast = Forecast.builder()
                                .tenantId(propertyDTO.getTenantId())
                                .propertyId(propertyDTO.getId())
                                .reportId(openAreaMonthlyReport.getId())
                                .date((int) yearMonthLong)
                                .categoryId(areaTypeConfig.getId())
                                .categoryName(areaTypeConfig.getName())
                                .contentType(areaTypeConfig.getContentType())
                                .updateId(0)
                                .updateTime(DateTimeUtils.nowTimestamp())
                                .build();
                        if(Objects.nonNull(areaTypeConfig.getType())){
                            Map<Integer, BigDecimal> type2Budget = yearMonth2Type2Budget.getOrDefault(nextMonth, Collections.emptyMap());
                            forecast.setBudget(type2Budget.get(areaTypeConfig.getType()));
                        }
                        forecasts.add(forecast);
                    }
                }
            }
            saveBatch(forecasts);
        }

        /**
         * 获取未来两个月的日期集合（日期格式yyyyMM）
         *
         * @param monthlyReport
         * @param timezone
         * @return java.util.List<java.lang.String>
         * @author lichen
         * @date 2024/2/28
         */
        private List<String> getNextTwoMonths(MonthlyReport monthlyReport, String timezone) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYYMM, Locale.ENGLISH);
            ZonedDateTime zonedDateTime = ZonedDateTime.of(monthlyReport.getYear(), monthlyReport.getMonth(),
                    1, 0, 0, 0, 0, ZoneId.of(timezone));
            return Lists.newArrayList(zonedDateTime.plusMonths(1).format(formatter), zonedDateTime.plusMonths(2).format(formatter));
        }

    }