# SalesKpiMonthlyServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.workperformance.cons.S3BucketFunctionCons;
    import cloud.wconnect.workperformance.cons.saleskpi.SalesKpiMonthlyDataSourceCons;
    import cloud.wconnect.workperformance.entity.saleskpi.*;
    import cloud.wconnect.workperformance.mapper.saleskpi.SalesKpiMonthlyMapper;
    import cloud.wconnect.workperformance.pojo.bo.saleskpi.OperatingStatementBO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.SalesKpiAttachmentDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.config.SalesConfigMonthlyTargetDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.*;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.basic.*;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.trans.SalesKpiTransMonthlyDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.trans.SalesKpiTransMonthlyDataDTO;
    import cloud.wconnect.workperformance.service.saleskpi.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Nullable;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections4.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Workbook;
    import org.apache.poi.ss.usermodel.WorkbookFactory;
    import org.apache.poi.ss.util.CellReference;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.text.MessageFormat;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    /**
     * <p>
     * sales kpi monthly 服务实现类
     * </p>
     *
     * @author zc
     * @since 2023-09-05
     */
    @Service
    @DS("work_performance")
    public class SalesKpiMonthlyServiceImpl extends ServiceImpl<SalesKpiMonthlyMapper, SalesKpiMonthly> implements ISalesKpiMonthlyService {
        @Lazy
        @Resource
        private ISalesKpiConfigService salesKpiConfigService;
        @Resource
        private ISalesKpiMonthlyConfigService salesKpiMonthlyConfigService;
        @Lazy
        @Resource
        private ISalesKpiWconnectAdjustmentService wconnectAdjustmentService;
        @Resource
        private ISalesKpiMonthlyReportService salesKpiMonthlyReportService;
        @Resource
        private ISalesKpiRevParSourceConfigService revParSourceConfigService;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private MailInternalService mailInternalService;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private Executor executor;
        @Resource
        private ApplicationContext applicationContext;
        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;

        /**
         * 获取指定月份的 Monthly 数据
         *
         * @param propertyId 资产 id
         * @param salesIds   Sales config id
         * @param monthly    月份（yyyyMM）
         * @return SalesKpiMonthly 集合
         * @author zc
         * @date 2023/9/6
         */
        @Override
        public List<SalesKpiMonthly> listByMonthly(@Nullable Integer propertyId, @Nullable List<Integer> salesIds, long monthly) {
            return list(new QueryWrapper<SalesKpiMonthly>().lambda()
                    .eq(Objects.nonNull(propertyId), SalesKpiMonthly::getPropertyId, propertyId)
                    .in(CollectionUtils.isNotEmpty(salesIds), SalesKpiMonthly::getSalesId, salesIds)
                    .eq(SalesKpiMonthly::getMonthly, monthly));
        }

        /**
         * 生成初始数据
         *
         * @param propertyId 资产 id
         * @param monthly    月份（yyyyMM）
         * @return SalesKpiMonthly 集合
         * @author zc
         * @date 2023/9/7
         */
        @Override
        public List<SalesKpiMonthly> generateInitData(int propertyId, long monthly) {
            // 获取开启的业务员配置数据
            List<SalesKpiConfig> salesKpiConfigs = salesKpiConfigService.listConfigs(propertyId, Boolean.TRUE);
            if (CollectionUtils.isEmpty(salesKpiConfigs)) {
                // 没有业务员配置数据则直接返回空集合
                return Collections.emptyList();
            }

            // 获取已存在指定月份 Monthly 数据的 sales id
            Set<Integer> existMonthlyDataSalesIds = listByMonthly(propertyId, null, monthly).stream()
                    .map(SalesKpiMonthly::getSalesId).collect(Collectors.toSet());

            // 获取未删除的 monthly 配置数据
            List<SalesKpiMonthlyConfig> monthlyConfigs = salesKpiMonthlyConfigService.getMonthlyConfigs(propertyId,
                    null, Boolean.FALSE);
            if (CollectionUtils.isEmpty(monthlyConfigs)) {
                return Collections.emptyList();
            }

            return salesKpiConfigs.stream()
                    // 过滤掉已存在指定月份 Monthly 数据的 sales 配置数据
                    .filter(item -> !existMonthlyDataSalesIds.contains(item.getId()))
                    .map(item -> {
                        List<Integer> segmentIds = Stream.of(StringUtils.split(item.getMarketSegments(), ","))
                                .map(Integer::parseInt).toList();

                        List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = Lists.newArrayList();
                        monthlyConfigs.forEach(config -> {
                            // 过滤掉父节点数据
                            if (config.getParentId().equals(BaseConstant.ZERO)) {
                                return;
                            }

                            // 组装 Actual、Monthly value
                            SalesKpiMonthlyValueBasicDTO actualValue = new SalesKpiMonthlyValueBasicDTO();
                            SalesKpiMonthlyValueBasicDTO targetValue = new SalesKpiMonthlyValueBasicDTO();
                            packageItemValue(propertyId, config.getDataSource(), monthly, actualValue, targetValue);

                            // 组装默认配置的 Target value
                            Map<Integer, BigDecimal> itemId2TargetValue = JsonUtils.convertStringToList(objectMapper,
                                            item.getMonthlyTarget(), SalesConfigMonthlyTargetDTO.class).stream()
                                    .collect(Collectors.toMap(SalesConfigMonthlyTargetDTO::getItemId, SalesConfigMonthlyTargetDTO::getTargetValue));
                            if (itemId2TargetValue.containsKey(config.getId())) {
                                targetValue.setValue(itemId2TargetValue.get(config.getId()));
                                targetValue.setCrawlValue(itemId2TargetValue.get(config.getId()));
                            }

                            SalesKpiMonthlyBasicDTO monthlyBasic = SalesKpiMonthlyBasicDTO.builder()
                                    .itemId(config.getId())
                                    .itemTitle(config.getTitle())
                                    .description(config.getDescription())
                                    .actual(actualValue)
                                    .target(targetValue)
                                    .attachment(new SalesKpiAttachmentBasicDTO())
                                    .build();
                            if (config.getType().equals(SalesKpiMonthlyConfig.TYPE_SEGMENT)) {
                                if (!segmentIds.contains(config.getId())) {
                                    return;
                                }
                                monthlyBasic.setType(SalesKpiMonthlyConfig.TYPE_SEGMENT);
                            } else {
                                monthlyBasic.setType(SalesKpiMonthlyConfig.TYPE_GENERAL);
                            }

                            salesKpiMonthlyBasicList.add(monthlyBasic);
                        });

                        return SalesKpiMonthly.builder()
                                .propertyId(propertyId)
                                .monthly(monthly)
                                .salesId(item.getId())
                                .salesmanId(item.getSalesmanId())
                                .salesAccount(item.getSalesAccount())
                                .jsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList))
                                .build();
                    }).toList();
        }

        /**
         * 组装指标值
         *
         * @param propertyId  资产 id
         * @param dataSource  数据源标识
         * @param monthly     月（yyyyMM）
         * @param actualValue monthlyActualValue
         * @param targetValue monthlyTargetValue
         * @author zc
         * @date 2023/10/26
         */
        private void packageItemValue(int propertyId, String dataSource, long monthly, SalesKpiMonthlyValueBasicDTO actualValue,
                                      SalesKpiMonthlyValueBasicDTO targetValue) {
            switch (dataSource) {
                case SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVENUE_ADJ_LOST -> {
                    wconnectAdjustmentService.packageItemValueForRevenueLost(propertyId, monthly, actualValue);
                }
                case SalesKpiMonthlyDataSourceCons.DATA_SOURCE_HOTEL_ACHIEVEMENT_ROOMS,
                        SalesKpiMonthlyDataSourceCons.DATA_SOURCE_HOTEL_ACHIEVEMENT_FB,
                        SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVPAR -> {
                    packageItemValueForMonthlyReport(propertyId, monthly, dataSource, actualValue, targetValue);
                }
                default -> {
                }
            }
        }

        /**
         * 组装指标值 for Monthly Report
         *
         * @param propertyId  资产 id
         * @param monthly     月（yyyyMM）
         * @param dataSource  指标数据源
         * @param actualValue actual value
         * @param targetValue target value
         * @author zc
         * @date 2024/6/15
         */
        private void packageItemValueForMonthlyReport(int propertyId, long monthly, String dataSource,
                                                      SalesKpiMonthlyValueBasicDTO actualValue, SalesKpiMonthlyValueBasicDTO targetValue) {
            List<SalesKpiMonthlyReport> monthlyReportList = salesKpiMonthlyReportService.list(propertyId, monthly, monthly);
            if (CollectionUtils.isEmpty(monthlyReportList)) {
                return;
            }

            Map<String, SalesKpiMonthlyReport> dataSource2MonthlyReport = monthlyReportList.stream()
                    .collect(Collectors.toMap(SalesKpiMonthlyReport::getDataSource, Function.identity()));
            SalesKpiMonthlyReport salesKpiMonthlyReport = dataSource2MonthlyReport.getOrDefault(dataSource, new SalesKpiMonthlyReport());
            if (dataSource.equals(SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVPAR)) {
                SalesKpiRevParSourceConfig revParSourceConfig = revParSourceConfigService.getByPropertyId(propertyId);
                if (revParSourceConfig.getSource().equals(SalesKpiRevParSourceConfig.SOURCE_MONTHLY_REPORT)) {
                    actualValue.setValue(salesKpiMonthlyReport.getActual());
                    actualValue.setCrawlValue(salesKpiMonthlyReport.getActual());
                }
            } else {
                actualValue.setValue(salesKpiMonthlyReport.getActual());
                actualValue.setCrawlValue(salesKpiMonthlyReport.getActual());
            }

            targetValue.setValue(salesKpiMonthlyReport.getTarget());
            targetValue.setCrawlValue(salesKpiMonthlyReport.getTarget());
        }

        /**
         * 获取 monthly 列表数据
         *
         * @param tenantId   租户 id
         * @param propertyId 资产 id
         * @param monthly    月份（yyyyMM）
         * @return SalesKpiMonthlyDTO
         * @author zc
         * @date 2023/9/7
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<SalesKpiMonthlyDTO> list(int tenantId, int propertyId, long monthly) {
            // 获取或生成 Monthly 数据
            List<SalesKpiMonthly> monthlyList = listMonthlyList(propertyId, monthly).stream()
                    // 根据 SalesId 降序排序
                    .sorted(Comparator.comparing(SalesKpiMonthly::getSalesId).reversed()).toList();
            if (CollectionUtils.isEmpty(monthlyList)) {
                return Collections.emptyList();
            }

            // 获取 Monthly 配置数据
            List<SalesKpiMonthlyConfig> monthlyConfigList = salesKpiMonthlyConfigService
                    .getMonthlyConfigs(propertyId, null, null);
            Map<Integer, SalesKpiMonthlyConfig> monthlyConfigId2MonthlyConfig = monthlyConfigList.stream()
                    .collect(Collectors.toMap(SalesKpiMonthlyConfig::getId, Function.identity()));

            // 提取操作人名称
            Map<Integer, String> userId2Name = extractOperatorName(tenantId, monthlyList);

            // 获取业务员配置数据
            Map<Integer, SalesKpiConfig> salesId2SalesKpiConfig = salesKpiConfigService.listConfigs(propertyId, null).stream()
                    .collect(Collectors.toMap(SalesKpiConfig::getId, Function.identity()));

            return monthlyList.stream()
                    // 需要展示开启的和已经删除的业务员
                    .filter(item ->
                            Objects.isNull(salesId2SalesKpiConfig.get(item.getSalesId()))
                                    || salesId2SalesKpiConfig.get(item.getSalesId()).getStatus())
                    .map(item ->
                            SalesKpiMonthlyDTO.builder()
                                    .id(item.getId())
                                    .salesId(item.getSalesId())
                                    .salesmanId(item.getSalesmanId())
                                    .salesmanName(userId2Name.get(item.getSalesmanId()))
                                    .salesAccount(Stream.of(StringUtils.split(item.getSalesAccount(), ",")).toList())
                                    // 组装父节点指标数据
                                    .detail(packageParentItemConfigs(salesId2SalesKpiConfig, monthlyConfigList,
                                            monthlyConfigId2MonthlyConfig, item, userId2Name))
                                    .build()
                    ).toList();
        }

        /**
         * 组装父节点指标数据
         *
         * @param salesId2SalesKpiConfig        业务员配置数据
         * @param monthlyConfigList             Monthly 配置数据
         * @param monthlyConfigId2MonthlyConfig Monthly 配置数据 map
         * @param salesKpiMonthly               Monthly 数据
         * @param userId2Name                   用户名称
         * @return SalesKpiMonthlyItemDTO 集合
         * @author zc
         * @date 2023/9/8
         */
        private List<SalesKpiMonthlyItemDTO> packageParentItemConfigs(Map<Integer, SalesKpiConfig> salesId2SalesKpiConfig,
                                                                      List<SalesKpiMonthlyConfig> monthlyConfigList,
                                                                      Map<Integer, SalesKpiMonthlyConfig> monthlyConfigId2MonthlyConfig,
                                                                      SalesKpiMonthly salesKpiMonthly,
                                                                      Map<Integer, String> userId2Name) {
            // 获取需要隐藏的 itemDataSources
            SalesKpiConfig salesKpiConfig = salesId2SalesKpiConfig.get(salesKpiMonthly.getSalesId());
            List<Integer> hiddenItemIds = Objects.nonNull(salesKpiConfig) ?
                    JsonUtils.convertStringToList(objectMapper, salesKpiConfig.getHiddenItem(), Integer.class)
                    : Collections.emptyList();

            List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                    salesKpiMonthly.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
            Map<Integer, SalesKpiMonthlyBasicDTO> itemId2SalesKpiMonthlyBasic = salesKpiMonthlyBasicList.stream()
                    .collect(Collectors.toMap(SalesKpiMonthlyBasicDTO::getItemId, Function.identity()));

            return salesKpiMonthlyBasicList.stream()
                    // 1、提取配置信息中对应的 item
                    .map(monthlyItem -> monthlyConfigId2MonthlyConfig.get(monthlyItem.getItemId()))
                    // 2、提取 父 id
                    .map(SalesKpiMonthlyConfig::getParentId).distinct()
                    // 3、提取 父 item config
                    .map(monthlyConfigId2MonthlyConfig::get)
                    // 4、排序
                    .sorted(Comparator.comparing(SalesKpiMonthlyConfig::getSort))
                    // 5、渲染每一个父 item 信息
                    .map(parentItemConfig ->
                            SalesKpiMonthlyItemDTO.builder()
                                    .keyMetrics(parentItemConfig.getTitle())
                                    // 组装父节点下的子节点指标数据
                                    .items(packageChildItemConfigs(monthlyConfigList, itemId2SalesKpiMonthlyBasic,
                                            parentItemConfig, hiddenItemIds, userId2Name))
                                    .build()
                    ).toList();
        }

        /**
         * 组装父节点下的子节点指标数据
         *
         * @param monthlyConfigList           monthly 配置数据
         * @param itemId2SalesKpiMonthlyBasic monthly 指标数据
         * @param parentItemConfig            父节点指标数据
         * @param hiddenItemIds               设置隐藏的子节点指标 id
         * @param userId2Name                 用户名称
         * @return SalesKpiMonthlyItemDetailDTO
         * @author zc
         * @date 2023/9/8
         */
        private List<SalesKpiMonthlyItemDetailDTO> packageChildItemConfigs(List<SalesKpiMonthlyConfig> monthlyConfigList,
                                                                           Map<Integer, SalesKpiMonthlyBasicDTO> itemId2SalesKpiMonthlyBasic,
                                                                           SalesKpiMonthlyConfig parentItemConfig,
                                                                           List<Integer> hiddenItemIds,
                                                                           Map<Integer, String> userId2Name) {
            List<SalesKpiMonthlyConfig> childItemConfigs = monthlyConfigList.stream()
                    .filter(config -> config.getParentId().equals(parentItemConfig.getId())
                            && Objects.nonNull(itemId2SalesKpiMonthlyBasic.get(config.getId())))
                    .peek(config -> {
                        config.setTitle(itemId2SalesKpiMonthlyBasic.get(config.getId()).getItemTitle());
                        config.setDescription(itemId2SalesKpiMonthlyBasic.get(config.getId()).getDescription());
                    }).sorted(Comparator.comparing(SalesKpiMonthlyConfig::getSort)
                            .thenComparing(config -> config.getTitle().toLowerCase())).toList();

            return childItemConfigs.stream()
                    // 过滤掉 SalesKpiConfig 中设置隐藏的 item
                    .filter(childItem -> !hiddenItemIds.contains(childItem.getId()))
                    .map(childItem -> {
                        SalesKpiMonthlyBasicDTO monthlyBasic = itemId2SalesKpiMonthlyBasic.get(childItem.getId());

                        SalesKpiMonthlyItemDetailDTO monthlyItemDetail = beanMapper.map(childItem, SalesKpiMonthlyItemDetailDTO.class);
                        monthlyItemDetail.setItemId(childItem.getId());
                        monthlyItemDetail.setItemTitle(childItem.getTitle());
                        monthlyItemDetail.setActual(beanMapper.map(monthlyBasic.getActual(), SalesKpiMonthlyActualDTO.class));
                        monthlyItemDetail.getActual().setUpdateName(userId2Name.get(monthlyBasic.getActual().getUpdateUid()));
                        monthlyItemDetail.setTarget(beanMapper.map(monthlyBasic.getTarget(), SalesKpiMonthlyTargetDTO.class));
                        monthlyItemDetail.getTarget().setUpdateName(userId2Name.get(monthlyBasic.getTarget().getUpdateUid()));
                        monthlyItemDetail.getTarget().setType(JsonUtils.convertStringToObject(objectMapper, childItem.getTargetSetting(),
                                MonthlyConfigTargetSettingDTO.class).getType());
                        monthlyItemDetail.setAttachment(beanMapper.map(monthlyBasic.getAttachment(), SalesKpiAttachmentDTO.class));
                        monthlyItemDetail.getAttachment().setUpdateName(userId2Name.get(monthlyBasic.getAttachment().getUpdateUid()));
                        monthlyItemDetail.setItemConfig(JsonUtils.convertStringToObject(objectMapper, childItem.getItemConfig(),
                                MonthlyConfigItemConfigDTO.class).getType());
                        monthlyItemDetail.setType(childItem.getType());

                        return monthlyItemDetail;
                    }).toList();
        }

        /**
         * 获取或生成 Monthly 数据
         *
         * @param propertyId 资产 id
         * @param monthly    月份（yyyyMM）
         * @return SalesKpiMonthly
         * @author zc
         * @date 2023/9/7
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<SalesKpiMonthly> listMonthlyList(int propertyId, long monthly) {
            List<SalesKpiMonthly> monthlyList = listByMonthly(propertyId, null, monthly);
            if (CollectionUtils.isEmpty(monthlyList)) {
                // 生成指定月份的初始数据
                monthlyList = generateInitData(propertyId, monthly);
                if (CollectionUtils.isNotEmpty(monthlyList)) {
                    // 保存初始数据
                    saveBatch(monthlyList);
                }
            }
            return monthlyList;
        }

        /**
         * 提取用户名
         *
         * @param tenantId    租户 id
         * @param monthlyList monthly 数据
         * @return 用户名 map
         * @author zc
         * @date 2023/9/7
         */
        private Map<Integer, String> extractOperatorName(int tenantId, List<SalesKpiMonthly> monthlyList) {
            // 提取用户 id
            Set<Integer> userIds = Sets.newHashSet();
            monthlyList.forEach(item -> {
                userIds.add(item.getSalesmanId());

                JsonUtils.convertStringToList(objectMapper, item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class)
                        .forEach(monthlyItem -> userIds.addAll(monthlyItem.summaryUserIds()));
            });

            if (CollectionUtils.isEmpty(userIds)) {
                return Collections.emptyMap();
            }

            // 获取用户名称
            return userInternalService.getNameMapByIds(tenantId, Lists.newArrayList(userIds));
        }

        /**
         * 校验数据是否存在
         *
         * @param id 数据 id
         * @return SalesKpiMonthly
         * @author zc
         * @date 2023/9/8
         */
        @Override
        public SalesKpiMonthly checkExist(int id) {
            SalesKpiMonthly salesKpiMonthly = getById(id);
            if (Objects.isNull(salesKpiMonthly)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the salesKpiMonthly is not found by id: " + id);
            }
            return salesKpiMonthly;
        }

        /**
         * 校验数据归属
         *
         * @param propertyId      资产 id
         * @param salesKpiMonthly 源数据
         * @author zc
         * @date 2023/9/8
         */
        @Override
        public void checkBelong(int propertyId, SalesKpiMonthly salesKpiMonthly) {
            if (!salesKpiMonthly.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The salesKpiMonthly does not belong to this property; propertyId:{0}; salesKpiMonthlyId:{1}",
                                propertyId, salesKpiMonthly.getId()));
            }
        }

        /**
         * 保存 Monthly 数据
         *
         * @param originSalesKpiMonthly 源数据
         * @param itemDetailList        Monthly 数据
         * @param operatorId            操作人 id
         * @author zc
         * @date 2023/9/8
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(SalesKpiMonthly originSalesKpiMonthly,
                         List<MonthlyConfigItemDetailDTO> itemDetailList,
                         int operatorId) {
            Map<Integer, MonthlyConfigItemDetailDTO> itemId2ItemDetail = itemDetailList.stream()
                    .collect(Collectors.toMap(MonthlyConfigItemDetailDTO::getItemId, Function.identity()));
            //更新后的S3Keys
            List<String> updateAfterS3Keys = itemDetailList.stream()
                    .map(item -> item.getAttachment().getUrl())
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .toList();

            long nowTimestamp = DateTimeUtils.nowTimestamp();

            List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                    originSalesKpiMonthly.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
            //更新前的S3Keys
            List<String> updateBeforeS3Keys = salesKpiMonthlyBasicList.stream()
                    .map(item -> item.getAttachment().getUrl())
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .toList();

            // 组装需要更新的 Monthly 数据
            salesKpiMonthlyBasicList.forEach(item -> {
                MonthlyConfigItemDetailDTO itemDetail = itemId2ItemDetail.get(item.getItemId());
                if (Objects.isNull(itemDetail)) {
                    return;
                }
                setMonthlyValue(item.getActual(), itemDetail.getActual(), operatorId, nowTimestamp);
                setMonthlyValue(item.getTarget(), itemDetail.getTarget(), operatorId, nowTimestamp);
                setMonthlyAttachment(item.getAttachment(), itemDetail.getAttachment(), operatorId, nowTimestamp);
            });

            // 更新数据
            updateById(SalesKpiMonthly.builder()
                    .id(originSalesKpiMonthly.getId())
                    .jsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList))
                    .build());

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                delS3Keys.removeAll(updateAfterS3Keys);
                S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.SALES_KPI), delS3Keys);
            });
        }

        /**
         * 保存 item
         *
         * @param propertyId    资产 id
         * @param monthly       月份（yyyyMM）
         * @param id            指标 id
         * @param updateItemDTO 内容
         * @author zc
         * @date 2023/9/8
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveItemTitle(int propertyId, long monthly, int id,
                                  SalesKpiMonthlyUpdateItemDTO updateItemDTO) {
            // 更新指标数据
            salesKpiMonthlyConfigService.updateById(SalesKpiMonthlyConfig.builder()
                    .id(id)
                    .title(updateItemDTO.getTitle())
                    .description(updateItemDTO.getDescription())
                    .build());

            // 获取大于等于目标月的 Monthly 数据
            List<SalesKpiMonthly> monthlyList = list(new QueryWrapper<SalesKpiMonthly>().lambda()
                    .eq(SalesKpiMonthly::getPropertyId, propertyId)
                    .ge(SalesKpiMonthly::getMonthly, monthly));
            if (CollectionUtils.isEmpty(monthlyList)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The SalesKpiMonthly is not found; propertyId: " + propertyId + "; monthly:" + monthly);
            }

            // 组装要更新的 Monthly 数据
            List<SalesKpiMonthly> needUpdateMonthlyList = Lists.newArrayList();
            monthlyList.forEach(salesKpiMonthly -> {
                List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                        salesKpiMonthly.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);

                Optional<SalesKpiMonthlyBasicDTO> itemOptional = salesKpiMonthlyBasicList.stream()
                        .filter(item -> item.getItemId().equals(id)).findFirst();
                if (itemOptional.isEmpty()) {
                    return;
                }
                itemOptional.get().setItemTitle(updateItemDTO.getTitle());
                itemOptional.get().setDescription(updateItemDTO.getDescription());

                // 收集需要更新的 Monthly 数据
                needUpdateMonthlyList.add(SalesKpiMonthly.builder()
                        .id(salesKpiMonthly.getId())
                        .jsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList))
                        .build());
            });

            // 更新 Monthly 数据
            updateBatchById(needUpdateMonthlyList);
        }

        /**
         * 处理报告
         *
         * @param propertyId             资产 id
         * @param monthly                月份（yyyyMM）
         * @param operatingStatementList 报告数据
         * @author zc
         * @date 2023/9/9
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleAccountingReport(int propertyId, long monthly, List<OperatingStatementBO> operatingStatementList) {
            if (CollectionUtils.isEmpty(operatingStatementList)) {
                return;
            }

            Map<String, OperatingStatementBO> segment2OperatingStatement = operatingStatementList.stream()
                    .collect(Collectors.toMap(OperatingStatementBO::getSegment, Function.identity(), (v1, v2) -> v2));

            // 获取指定月份的 Monthly 数据
            List<SalesKpiMonthly> monthlyList = listByMonthly(propertyId, null, monthly);
            if (CollectionUtils.isEmpty(monthlyList)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The SalesKpiMonthly is not found; propertyId: " + propertyId + "; monthly:" + monthly);
            }

            // 组装需要更新的 Monthly 数据
            List<SalesKpiMonthly> needUpdateMonthlyList = Lists.newArrayList();
            monthlyList.forEach(item -> {
                List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                        item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                if (salesKpiMonthlyBasicList.stream().noneMatch(monthlyBasic ->
                        monthlyBasic.getType().equals(SalesKpiMonthlyConfig.TYPE_SEGMENT))) {
                    return;
                }

                salesKpiMonthlyBasicList.forEach(monthlyBasic -> {
                    // 过滤掉非 segment 指标数据
                    if (!monthlyBasic.getType().equals(SalesKpiMonthlyConfig.TYPE_SEGMENT)) {
                        return;
                    }
                    OperatingStatementBO operatingStatement = segment2OperatingStatement.get(monthlyBasic.getItemTitle());
                    if (Objects.isNull(operatingStatement)) {
                        return;
                    }
                    // 设置 value 值
                    setMonthlyCrawlValue(monthlyBasic.getActual(), operatingStatement.getActual());
                    setMonthlyCrawlValue(monthlyBasic.getTarget(), operatingStatement.getTarget());
                });

                needUpdateMonthlyList.add(SalesKpiMonthly.builder()
                        .id(item.getId())
                        .jsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList))
                        .build());
            });

            // 更新数据
            updateBatchById(needUpdateMonthlyList);
        }

        /**
         * 设置采集 value 值
         *
         * @param monthlyValueBasicDTO monthlyValueBasicDTO
         * @param value                actual
         * @author zc
         * @date 2024/8/27
         */
        @Override
        public void setMonthlyCrawlValue(SalesKpiMonthlyValueBasicDTO monthlyValueBasicDTO, BigDecimal value) {
            if (Objects.isNull(monthlyValueBasicDTO.getValue())
                    || Objects.isNull(monthlyValueBasicDTO.getUpdateUid())) {
                monthlyValueBasicDTO.setValue(value);
                monthlyValueBasicDTO.setRemark(null);
                monthlyValueBasicDTO.setUpdateUid(null);
                monthlyValueBasicDTO.setUpdateTime(null);
            }
            monthlyValueBasicDTO.setCrawlValue(value);
        }

        /**
         * 设置采集 reportUrl 值
         *
         * @param monthlyBasicDTO SalesKpiMonthlyBasicDTO
         * @param reportUrl       报告 url
         * @author zc
         * @date 2023/9/11
         */
        private void setMonthlyCrawlReportUrl(SalesKpiMonthlyBasicDTO monthlyBasicDTO, String reportUrl) {
            SalesKpiAttachmentBasicDTO attachment = monthlyBasicDTO.getAttachment();
            if (Objects.isNull(attachment.getUrl())
                    || Objects.isNull(attachment.getUpdateUid())) {
                attachment.setUrl(reportUrl);
                attachment.setUpdateUid(null);
                attachment.setUpdateTime(null);
            }
            attachment.setCrawlUrl(reportUrl);
        }

        /**
         * 设置编辑内容
         *
         * @param origin      原数据
         * @param current     当前数据
         * @param operatorId  操作人 id
         * @param operateTime 操作时间
         * @author zc
         * @date 2023/9/12
         */
        private void setMonthlyValue(SalesKpiMonthlyValueBasicDTO origin, SalesKpiMonthlyValueBasicDTO current,
                                     int operatorId, long operateTime) {
            // 如果 value、remark 发生变化，则需要更新操作人和操作时间
            boolean changed = compareNotEqual(origin.getValue(), current.getValue())
                    || !StringUtils.equals(origin.getRemark(), current.getRemark());
            if (changed) {
                origin.setValue(current.getValue());
                origin.setRemark(current.getRemark());
                origin.setUpdateUid(operatorId);
                origin.setUpdateTime(operateTime);
            }
        }

        /**
         * 比较数值是否不相等
         *
         * @param value1 数值 1
         * @param value2 数值 2
         * @return true: 不相等 false：相等
         * @author zc
         * @date 2024/2/8
         */
        private boolean compareNotEqual(BigDecimal value1, BigDecimal value2) {
            boolean notEqual = false;
            if (value1 != null && value2 != null) {
                if (value1.compareTo(value2) != 0) {
                    notEqual = true;
                }
            } else if (!Objects.equals(value1, value2)) {
                notEqual = true;
            }
            return notEqual;
        }

        /**
         * 设置编辑附件
         *
         * @param origin      原数据
         * @param current     当前数据
         * @param operatorId  操作人 id
         * @param operateTime 操作时间
         * @author zc
         * @date 2023/9/12
         */
        private void setMonthlyAttachment(SalesKpiAttachmentBasicDTO origin, SalesKpiAttachmentBasicDTO current,
                                          int operatorId, long operateTime) {
            if (!StringUtils.equals(Objects.isNull(origin.getUrl()) ? StringUtils.EMPTY : origin.getUrl(),
                    Objects.isNull(current.getUrl()) ? StringUtils.EMPTY : current.getUrl())) {
                origin.setUrl(current.getUrl());
                origin.setUpdateUid(operatorId);
                origin.setUpdateTime(operateTime);
            }
        }

        /**
         * 保存销售指标数据
         *
         * @param propertyId 资产 id
         * @param monthly    月份（yyyyMM）
         * @param dataSource 数据源标识
         * @param value      指标数据
         * @param reportUrl  报告 url
         * @author zc
         * @date 2023/9/11
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveSalesData(int propertyId, long monthly, String dataSource,
                                  BigDecimal value, String reportUrl) {
            // 获取或生成 Monthly 数据
            List<SalesKpiMonthly> monthlyList = listMonthlyList(propertyId, monthly);

            SalesKpiMonthlyConfig kpiMonthlyConfig = salesKpiMonthlyConfigService.getByDataSource(propertyId, dataSource);

            // 组装数据
            monthlyList.forEach(item -> {
                List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                        item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                Map<Integer, SalesKpiMonthlyBasicDTO> itemId2SalesKpiMonthlyBasic = salesKpiMonthlyBasicList.stream()
                        .collect(Collectors.toMap(SalesKpiMonthlyBasicDTO::getItemId, Function.identity()));

                SalesKpiMonthlyBasicDTO monthlyBasicDTO = itemId2SalesKpiMonthlyBasic.get(kpiMonthlyConfig.getId());
                if (Objects.nonNull(monthlyBasicDTO)) {
                    setMonthlyCrawlValue(monthlyBasicDTO.getActual(), value);
                    setMonthlyCrawlReportUrl(monthlyBasicDTO, reportUrl);
                    item.setJsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList));
                }
            });
            // 批量更新
            updateBatchById(monthlyList);
        }

        /**
         * 保存业务员指标数据
         *
         * @param propertyId      资产 id
         * @param monthly         月份（yyyyMM）
         * @param dataSource      数据源标识
         * @param itemDataDTOList 业务员指标数据
         * @author zc
         * @date 2023/9/11
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveSalesmanData(int propertyId, long monthly, String dataSource,
                                     List<SalesKpiSalesmanItemDataDTO> itemDataDTOList) {
            // 获取或生成 Monthly 数据
            List<SalesKpiMonthly> monthlyList = listMonthlyList(propertyId, monthly);

            SalesKpiMonthlyConfig kpiMonthlyConfig = salesKpiMonthlyConfigService.getByDataSource(propertyId, dataSource);

            Map<Integer, SalesKpiSalesmanItemDataDTO> salesId2ItemData = itemDataDTOList.stream()
                    .collect(Collectors.toMap(SalesKpiSalesmanItemDataDTO::getSalesId, Function.identity()));
            // 组装数据
            monthlyList.forEach(item -> {
                List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                        item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                Map<Integer, SalesKpiMonthlyBasicDTO> itemId2SalesKpiMonthlyBasic = salesKpiMonthlyBasicList.stream()
                        .collect(Collectors.toMap(SalesKpiMonthlyBasicDTO::getItemId, Function.identity()));

                SalesKpiMonthlyBasicDTO monthlyBasicDTO = itemId2SalesKpiMonthlyBasic.get(kpiMonthlyConfig.getId());
                SalesKpiSalesmanItemDataDTO itemDataDTO = salesId2ItemData.get(item.getSalesId());
                if (Objects.nonNull(monthlyBasicDTO) && Objects.nonNull(itemDataDTO)) {
                    setMonthlyCrawlValue(monthlyBasicDTO.getActual(), itemDataDTO.getValue1());
                    setMonthlyCrawlValue(monthlyBasicDTO.getTarget(), itemDataDTO.getValue2());
                    setMonthlyCrawlReportUrl(monthlyBasicDTO, itemDataDTO.getReportUrl());
                    item.setJsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList));
                }
            });
            // 批量更新
            updateBatchById(monthlyList);
        }

        /**
         * 提取报告指标数据
         *
         * @param property  资产
         * @param reportUrl 报告 url
         * @return 报告指标数据
         * @author zc
         * @date 2023/9/11
         */
        @Override
        public BigDecimal getReportItemValue(PropertyDTO property, String reportUrl) {
            // 获取国际化数字配置
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(property
                    .getInternationalization().getNumber().getCalculationRule());
            BigDecimal actual = null;
            Map<String, Object> s3Config = bucketMap.get(S3BucketFunctionCons.SALES_KPI);
            try (InputStream inputStream = S3Utils.getFile(region, s3Config, reportUrl);
                 Workbook workbook = WorkbookFactory.create(inputStream)) {
                Row row = workbook.getSheetAt(1).getRow(10);
                String actualStr = CellUtils.convertCellStr(row.getCell(CellReference.convertColStringToIndex("Q")));
                actual = StringUtils.isEmpty(actualStr) ? null : new BigDecimal(actualStr).setScale(BaseConstant.DATA_PRECISION, roundingMode);
            } catch (Exception e) {
                // 发送预警邮件通知获取数据异常
                String mailContent = MessageFormat.format("Business:{0}<br/>Content:{1}<br/>S3Key:{2}",
                        "Sales KPI Monthly internal - Save report - DataSource: " + SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVPAR,
                        "There is something wrong when handle report for Sales Kpi Monthly.Exception: " + e.getMessage(), reportUrl);
                mailInternalService.sendAlertEmail("[Warning] - Parse star report For Sales Kpi Monthly", mailContent);
            }
            return actual;
        }

        /**
         * 处理发生变化的指标数据
         *
         * @param propertyId       资产 id
         * @param userId           用户 id
         * @param transMonthlyList 发生变化的指标数据
         * @author zc
         * @date 2023/9/13
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleSalesKpiTransData(int propertyId, @Nullable Integer userId, List<SalesKpiTransMonthlyDTO> transMonthlyList) {
            if (CollectionUtils.isEmpty(transMonthlyList)) {
                return;
            }

            // 获取 Monthly config 数据
            List<SalesKpiMonthlyConfig> monthlyConfigs = salesKpiMonthlyConfigService.getMonthlyConfigs(propertyId, null, null);
            if (CollectionUtils.isEmpty(monthlyConfigs)) {
                return;
            }

            Integer salesId = null;
            if (Objects.nonNull(userId)) {
                // 根据用户 id 获取 Sales config 数据
                SalesKpiConfig salesKpiConfig = salesKpiConfigService.getBySalesmanId(propertyId, userId);
                // SalesKpiConfig 不存在的不处理
                if (Objects.isNull(salesKpiConfig)) {
                    return;
                }
                salesId = salesKpiConfig.getId();
            }

            // 提取受影响的 Monthly 集合
            List<Long> monthlyList = transMonthlyList.stream().map(SalesKpiTransMonthlyDTO::getMonthly).toList();

            // 获取对应 Monthly 的 KPI 数据
            List<SalesKpiMonthly> salesKpiMonthlyList = list(new QueryWrapper<SalesKpiMonthly>().lambda()
                    .eq(SalesKpiMonthly::getPropertyId, propertyId)
                    .eq(Objects.nonNull(salesId), SalesKpiMonthly::getSalesId, salesId)
                    .in(SalesKpiMonthly::getMonthly, monthlyList));
            // 提取数据库中暂时没有生成 KPI 数据的 Monthly
            Set<Long> existMonthlyList = salesKpiMonthlyList.stream().map(SalesKpiMonthly::getMonthly).collect(Collectors.toSet());
            // 添加初始化的 KPI 数据
            monthlyList.forEach(item -> {
                if (existMonthlyList.contains(item)) {
                    return;
                }
                salesKpiMonthlyList.addAll(generateInitData(propertyId, item));
            });

            Map<Long, Map<String, SalesKpiTransMonthlyDataDTO>> dataSource2TransDataByMonthly = transMonthlyList.stream()
                    .collect(Collectors.toMap(SalesKpiTransMonthlyDTO::getMonthly, dto -> dto.getData().stream()
                            .collect(Collectors.toMap(SalesKpiTransMonthlyDataDTO::getDataSource, Function.identity()))));

            // Monthly config 数据
            Map<Integer, SalesKpiMonthlyConfig> id2MonthlyConfig = monthlyConfigs.stream()
                    .collect(Collectors.toMap(SalesKpiMonthlyConfig::getId, Function.identity()));

            Integer finalSalesId = salesId;
            List<SalesKpiMonthly> effectiveMonthlyList = Lists.newArrayList();
            salesKpiMonthlyList.forEach(item -> {
                // 若 salesId 不为 null，则表示只更新对应 salesId 的数据
                if (Objects.nonNull(finalSalesId) && !item.getSalesId().equals(finalSalesId)) {
                    return;
                }
                Map<String, SalesKpiTransMonthlyDataDTO> dataSource2TransData = dataSource2TransDataByMonthly.get(item.getMonthly());
                List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                        item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                salesKpiMonthlyBasicList.forEach(monthlyItem -> {
                    SalesKpiMonthlyConfig salesKpiMonthlyConfig = id2MonthlyConfig.get(monthlyItem.getItemId());
                    SalesKpiTransMonthlyDataDTO transMonthlyDataDTO = dataSource2TransData.get(salesKpiMonthlyConfig.getDataSource());
                    if (Objects.isNull(transMonthlyDataDTO)) {
                        return;
                    }
                    // 设置 value 值
                    setMonthlyCrawlValue(monthlyItem.getActual(), transMonthlyDataDTO.getActual());
                    setMonthlyCrawlValue(monthlyItem.getTarget(), transMonthlyDataDTO.getTarget());
                });
                item.setJsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList));
                effectiveMonthlyList.add(item);
            });

            saveOrUpdateBatch(effectiveMonthlyList);
        }

        /**
         * 处理 Room Revenue 指标数据
         *
         * @param propertyDTO  资产
         * @param theMonthList 日期（yyyyMM）
         * @author zc
         * @date 2024/6/13
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleRoomRevenueData(PropertyDTO propertyDTO, List<Long> theMonthList) {
            List<SalesKpiMonthlyConfig> monthlyConfigList = salesKpiMonthlyConfigService.listByDataSources(propertyDTO.getId(),
                    List.of(SalesKpiMonthlyDataSourceCons.DATA_SOURCE_HOTEL_ACHIEVEMENT_ROOMS,
                            SalesKpiMonthlyDataSourceCons.DATA_SOURCE_HOTEL_ACHIEVEMENT_FB,
                            SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVPAR));
            if (CollectionUtils.isEmpty(monthlyConfigList)) {
                return;
            }

            Map<Integer, List<Long>> year2Monthly = theMonthList.stream().map(item ->
                            YearMonthUtils.getYearMonth(item.toString()))
                    .collect(Collectors.groupingBy(YearMonth::getYear,
                            Collectors.mapping(YearMonthUtils::parseYearMonthToLong, Collectors.toList())));

            // 获取 RevPar 数据源配置
            SalesKpiRevParSourceConfig revParSourceConfig = revParSourceConfigService.getByPropertyId(propertyDTO.getId());

            year2Monthly.forEach((year, monthlies) -> {
                List<SalesKpiMonthly> salesKpiMonthlyList = listByMonthliesForUpdate(propertyDTO.getId(), monthlies);
                if (CollectionUtils.isEmpty(salesKpiMonthlyList)) {
                    return;
                }

                // 取最小、最大日期
                long startMonth = Collections.min(monthlies);
                long endMonth = Collections.max(monthlies);

                // 获取 Monthly Report 数据
                Map<Long, Map<String, SalesKpiMonthlyReport>> dataSource2MonthlyReportByMonth = salesKpiMonthlyReportService.list(propertyDTO.getId(),
                        startMonth, endMonth).stream().collect(Collectors.groupingBy(SalesKpiMonthlyReport::getMonthly,
                        Collectors.toMap(SalesKpiMonthlyReport::getDataSource, Function.identity())));

                salesKpiMonthlyList.forEach(item -> {
                    List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                            item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                    salesKpiMonthlyBasicList.forEach(monthlyItem -> {
                        monthlyConfigList.forEach(monthlyConfig -> {
                            if (!monthlyItem.getItemId().equals(monthlyConfig.getId())) {
                                return;
                            }
                            SalesKpiMonthlyReport salesKpiMonthlyReport = dataSource2MonthlyReportByMonth.getOrDefault(item.getMonthly(),
                                    Collections.emptyMap()).getOrDefault(monthlyConfig.getDataSource(), new SalesKpiMonthlyReport());
                            BigDecimal actual = salesKpiMonthlyReport.getActual();
                            BigDecimal target = salesKpiMonthlyReport.getTarget();
                            if (monthlyConfig.getDataSource().equals(SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVPAR)) {
                                if (revParSourceConfig.getSource().equals(SalesKpiRevParSourceConfig.SOURCE_MONTHLY_REPORT)) {
                                    setMonthlyCrawlValue(monthlyItem.getActual(), actual);
                                }
                            } else {
                                setMonthlyCrawlValue(monthlyItem.getActual(), actual);
                            }
                            setMonthlyCrawlValue(monthlyItem.getTarget(), target);
                        });
                    });
                    item.setJsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList));
                });
                // 更新数据
                updateBatchById(salesKpiMonthlyList);
            });
        }

        /**
         * 根据日期范围获取 Monthly
         *
         * @param propertyId 资产 id
         * @param monthlies  月（yyyyMM）
         * @return SalesKpiMonthly 集合
         * @author zc
         * @date 2024/8/27
         */
        @Override
        public List<SalesKpiMonthly> listByMonthliesForUpdate(int propertyId, List<Long> monthlies) {
            return list(new QueryWrapper<SalesKpiMonthly>().lambda()
                    .eq(SalesKpiMonthly::getPropertyId, propertyId)
                    .in(SalesKpiMonthly::getMonthly, monthlies)
                    .last("for update"));
        }

    }