# OperatingStatisticsServiceImpl.java

    package cloud.wconnect.workperformance.service.sva.operatingstatistics.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.m3.DailyOperatingStatisticsIndexEnum;
    import cloud.wconnect.commonlib.exception.ListenerException;
    import cloud.wconnect.commonlib.pojo.bo.m3.DailyOperatingStatisticsIndexBO;
    import cloud.wconnect.commonlib.pojo.bo.m3.DailyOperatingStatisticsIndexValueBO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.RedissonLockUtil;
    import cloud.wconnect.workperformance.cons.operatingstatisticsdaily.DataUpdateResult;
    import cloud.wconnect.workperformance.cons.operatingstatisticsdaily.OperatingStatisticsDatasourceEnum;
    import cloud.wconnect.workperformance.cons.sva.SvaConstant;
    import cloud.wconnect.workperformance.entity.sva.operatingstatistics.OperatingStatistics;
    import cloud.wconnect.workperformance.mapper.sva.operatingstatistics.OperatingStatisticsMapper;
    import cloud.wconnect.workperformance.pojo.bo.sva.operatingstatistics.OperatingStatisticsListBO;
    import cloud.wconnect.workperformance.pojo.dto.sva.operatingstatistics.OperatingStatisticsDatasourceDTO;
    import cloud.wconnect.workperformance.service.sva.operatingstatistics.IOperatingStatisticsService;
    import cloud.wconnect.workperformance.service.sva.operatingstatistics.operatingstatistics.IOperatingStatisticsParseService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    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.Maps;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Resource;
    import org.springframework.util.ReflectionUtils;

    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类：运营收入分析
     * </p>
     *
     * @author zc
     * @since 2023-03-17
     */
    @Service
    @DS("work_performance")
    public class OperatingStatisticsServiceImpl extends ServiceImpl<OperatingStatisticsMapper, OperatingStatistics> implements IOperatingStatisticsService {
        @Resource
        private ApplicationContext applicationContext;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private RedissonClient redissonClient;

        /**
         * 获取 Operating Statistics 集合
         *
         * @param propertyId property id
         * @param fromDate   开始日期
         * @param toDate     结束日期
         * @return Operating Statistics 集合
         * @author zc
         * @date 2023/3/17
         */
        @Override
        public List<OperatingStatistics> list(int propertyId, long fromDate, long toDate) {
            return list(new QueryWrapper<OperatingStatistics>().lambda()
                    .eq(OperatingStatistics::getPropertyId, propertyId)
                    .between(OperatingStatistics::getTheDate, fromDate, toDate));
        }

        /**
         * 解析报告
         *
         * @param propertyList         资产集合
         * @param in                   报告文件
         * @param source               数据源
         * @param propertyId2DateRange 报告数据日期范围(yyyyMMdd)
         * @return OperatingStatisticsListBO 集合
         * @author zc
         * @date 2024/8/19
         */
        @Override
        public List<OperatingStatisticsListBO> parseReport(List<PropertyDTO> propertyList, InputStream in,
                                                           String source, Map<Integer, List<Long>> propertyId2DateRange) {
            Collection<IOperatingStatisticsParseService> parserList = applicationContext.getBeansOfType(IOperatingStatisticsParseService.class).values();
            for (IOperatingStatisticsParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyList, in, propertyId2DateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理报告数据
         *
         * @param propertyList         资产
         * @param boList               报告数据 BO
         * @param operatorId           操作人 id
         * @param propertyId2DateRange 待处理数据的日期区间
         * @param datasource           数据源 1:PDF 2:CSV 4:Other
         * @return java.util.Map<java.lang.Integer, java.lang.Integer>
         * @author lichen
         * @date 2025/11/21
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<Integer, Integer> handleOperatingStatistics(List<PropertyDTO> propertyList, List<OperatingStatisticsListBO> boList, int operatorId,
                                                               Map<Integer, List<Long>> propertyId2DateRange, Integer datasource) {
            ArrayList<String> keys = Lists.newArrayListWithCapacity(boList.size());

            HashMap<Integer, List<LocalDate>> propertyId2LocalDates = Maps.newHashMapWithExpectedSize(propertyId2DateRange.size());

            propertyId2DateRange.forEach((propertyId, dateRange) -> {
                LocalDate startLocalDate = DateTimeUtils.strToLocalDate(dateRange.get(0).toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                LocalDate endLocalDate = DateTimeUtils.strToLocalDate(dateRange.get(1).toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                // 获取开始日期到结束日期的每一天
                List<LocalDate> dates = startLocalDate.datesUntil(endLocalDate.plusDays(1)).toList();
                propertyId2LocalDates.put(propertyId, dates);

                // 生成锁key
                List<String> keysByPropertyIds = dates.stream().map(localDate -> {
                    String date = DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
                    return MessageFormat.format(SvaConstant.HANDLE_DAILY_OPERATING_STATISTICS_KEY, propertyId, date);
                }).toList();
                keys.addAll(keysByPropertyIds);
            });
            //锁集合
            RLock[] locks = keys.stream().map(redissonClient::getLock).toList().toArray(new RLock[keys.size()]);

            //获取锁
            RLock multiLock = redissonClient.getMultiLock(locks);

            if (RedissonLockUtil.getLock(multiLock, 3, 2)) {
                try {
                    // 处理数据
                    return doHandleOperatingStatistics(propertyList, boList, operatorId, propertyId2LocalDates, datasource);
                } finally {
                    if (multiLock.isHeldByCurrentThread()) {
                        multiLock.unlock();  // 释放锁
                    }
                }
            } else {
                throw new RuntimeException("handleOperatingStatistics error, The number of execution failures reached the upper limit");
            }
        }

        /**
         * 处理报告数据
         *
         * @param propertyList          资产
         * @param boList                报告数据 BO
         * @param operatorId            操作人 id
         * @param propertyId2LocalDates 待处理数据的日期集合
         * @param datasource            数据源 1:PDF 2:CSV 4:Other
         * @return java.util.Map<java.lang.Integer, java.lang.Integer>
         * @author lichen
         * @date 2025/11/21
         */
        private Map<Integer, Integer> doHandleOperatingStatistics(List<PropertyDTO> propertyList, List<OperatingStatisticsListBO> boList, int operatorId,
                                                                  HashMap<Integer, List<LocalDate>> propertyId2LocalDates, Integer datasource) {
            Set<LocalDate> localDateList = propertyId2LocalDates.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
            // 不同的资产时区不一样，为防止丢失数据，将最小和最大日期减去1天和加1天
            LocalDate minLocalDate = Collections.min(localDateList).minusDays(1);
            LocalDate maxLocalDate = Collections.max(localDateList).plusDays(1);
            long minDate = DateTimeUtils.localDateToLong(minLocalDate, ZoneId.systemDefault());
            long maxDate = DateTimeUtils.localDateToLong(maxLocalDate, ZoneId.systemDefault());
            // 获取已存在的数据
            List<OperatingStatistics> existList = listByDateRange(propertyList.stream().map(PropertyDTO::getId).toList(),
                    minDate, maxDate);
            // 已存在的数据按照资产id和日期进行分组
            Map<Integer, Map<Long, OperatingStatistics>> propertyId2TheDate2ExistData = existList.stream().collect(
                    Collectors.groupingBy(OperatingStatistics::getPropertyId, Collectors.toMap(OperatingStatistics::getTheDate, Function.identity())));

            ArrayList<OperatingStatistics> batchInsertList = Lists.newArrayListWithCapacity(boList.size());
            ArrayList<OperatingStatistics> batchUpdateWithNullList = Lists.newArrayListWithCapacity(boList.size());
            // 资产对应的数据更新结果
            HashMap<Integer, Integer> propertyId2DataUpdateResult = Maps.newHashMapWithExpectedSize(propertyList.size());
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            // 按照资产标识和日期进行分组
            Map<String, Map<String, OperatingStatisticsListBO>> propertyAbbr2TheDay2BO = boList.stream().collect(
                    Collectors.groupingBy(OperatingStatisticsListBO::getProperty, Collectors.toMap(OperatingStatisticsListBO::getTheDay, Function.identity())));

            Map<Integer, PropertyDTO> propertyId2DTO = propertyList.stream().collect(Collectors.toMap(PropertyDTO::getId, Function.identity()));

            propertyId2LocalDates.forEach((propertyId, localDates) -> {
                PropertyDTO propertyDTO = propertyId2DTO.get(propertyId);
                String abbr = propertyDTO.getAbbr();

                // 指定资产待处理的数据
                Map<String, OperatingStatisticsListBO> boListByProperty = propertyAbbr2TheDay2BO.getOrDefault(abbr, Collections.emptyMap());
                // 指定资产存在的数据
                Map<Long, OperatingStatistics> existDataByPropertyId = propertyId2TheDate2ExistData.getOrDefault(propertyId, Collections.emptyMap());

                // 未更新的数量
                int nonUpdateCount = 0;
                // 无需处理的日期数量
                int noProcessDateCount = 0;
                // 总的日期数
                int dateCount = localDates.size();

                for (LocalDate localDate : localDates) {
                    // 待处理的数据
                    String theDay = DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                    OperatingStatisticsListBO operatingStatisticsBO = boListByProperty.get(theDay);

                    // 存在的数据
                    long theDate = DateTimeUtils.localDateToLong(localDate, propertyDTO.getTimezone());
                    OperatingStatistics existOperatingStatistics = existDataByPropertyId.get(theDate);

                    if (Objects.isNull(operatingStatisticsBO)) {
                        //情况1：待处理数据为空 且 存在数据不为空：更新数据源非M3 Api的指标值为null

                        if (Objects.nonNull(existOperatingStatistics)) {
                            List<OperatingStatisticsDatasourceDTO> originDatasourceDTOS = JsonUtils.convertStringToList(objectMapper,
                                    existOperatingStatistics.getDatasource(), OperatingStatisticsDatasourceDTO.class);
                            // 筛选出M3 API数据源的指标
                            List<OperatingStatisticsDatasourceDTO> m3ApiDatasourceDTOS = originDatasourceDTOS.stream()
                                    .filter(item -> OperatingStatisticsDatasourceEnum.M3_API.getValue() == item.getDatasource())
                                    .toList();
                            List<String> m3ApiIndexNameList = m3ApiDatasourceDTOS.stream().map(OperatingStatisticsDatasourceDTO::getIndexName).toList();
                            // 筛选出非M3 API的指标集合
                            List<String> nonM3ApiIndexNameList = Arrays.stream(DailyOperatingStatisticsIndexEnum.values())
                                    .map(DailyOperatingStatisticsIndexEnum::getIndexName)
                                    .filter(indexName -> !m3ApiIndexNameList.contains(indexName)).toList();
                            if (CollectionUtils.isNotEmpty(nonM3ApiIndexNameList)) { // 存在非M3 API的指标，则更新
                                existOperatingStatistics.setLastUpdatedBy(operatorId);
                                existOperatingStatistics.setLastUpdatedTime(nowTimestamp);

                                // 非M3 Api数据源的指标值设置为null
                                setIndexValueIsNull(existOperatingStatistics, nonM3ApiIndexNameList);

                                // 设置非M3 API的指标的数据源为当前数据源
                                List<OperatingStatisticsDatasourceDTO> nonM3ApiDatasourceDTOS = nonM3ApiIndexNameList.stream()
                                        .map(indexName -> new OperatingStatisticsDatasourceDTO(indexName, datasource)).toList();

                                // 收集数据源
                                ArrayList<OperatingStatisticsDatasourceDTO> allDatasourceDTOS = Lists.newArrayList(nonM3ApiDatasourceDTOS);
                                if (CollectionUtils.isNotEmpty(m3ApiDatasourceDTOS)) {
                                    allDatasourceDTOS.addAll(m3ApiDatasourceDTOS);
                                }
                                existOperatingStatistics.setDatasource(JsonUtils.convertObjListToString(objectMapper, allDatasourceDTOS));

                                batchUpdateWithNullList.add(existOperatingStatistics);
                            } else {
                                //原数据的数据源都是M3 API时，不能更新数据。未更新的数量加1
                                nonUpdateCount++;
                            }
                        } else {
                            // 待处理数据为空 且 存在数据为空：无需处理
                            noProcessDateCount++;
                        }
                    } else {
                        if (Objects.isNull(existOperatingStatistics)) {
                            //情况2：待处理数据不为空 且 存在数据为空：插入数据

                            OperatingStatistics insertData = beanMapper.map(operatingStatisticsBO, OperatingStatistics.class);
                            insertData.setPropertyId(propertyDTO.getId());
                            insertData.setTheDate(theDate);
                            insertData.setCreateBy(operatorId);
                            insertData.setCreatedTime(nowTimestamp);
                            // 设置数据源
                            List<OperatingStatisticsDatasourceDTO> datasourceDTOS = Arrays.stream(DailyOperatingStatisticsIndexEnum.values()).map(item -> {
                                String indexName = item.getIndexName();
                                return new OperatingStatisticsDatasourceDTO(indexName, datasource);
                            }).toList();
                            insertData.setDatasource(JsonUtils.convertObjListToString(objectMapper, datasourceDTOS));

                            batchInsertList.add(insertData);
                        } else {
                            //情况3：待处理数据不为空 且 存在数据不为空：更新非M3 Api数据源的指标

                            List<OperatingStatisticsDatasourceDTO> originDatasourceDTOS = JsonUtils.convertStringToList(objectMapper,
                                    existOperatingStatistics.getDatasource(), OperatingStatisticsDatasourceDTO.class);
                            // 筛选出M3 API数据源的指标
                            List<OperatingStatisticsDatasourceDTO> m3ApiDatasourceDTOS = originDatasourceDTOS.stream()
                                    .filter(item -> OperatingStatisticsDatasourceEnum.M3_API.getValue() == item.getDatasource())
                                    .toList();
                            List<String> m3ApiIndexNameList = m3ApiDatasourceDTOS.stream().map(OperatingStatisticsDatasourceDTO::getIndexName).toList();
                            // 筛选出非M3 API的指标集合
                            List<String> nonM3ApiIndexNameList = Arrays.stream(DailyOperatingStatisticsIndexEnum.values())
                                    .map(DailyOperatingStatisticsIndexEnum::getIndexName)
                                    .filter(indexName -> !m3ApiIndexNameList.contains(indexName)).toList();

                            if (CollectionUtils.isNotEmpty(nonM3ApiIndexNameList)) { // 存在非M3 API的指标，则更新
                                existOperatingStatistics.setLastUpdatedBy(operatorId);
                                existOperatingStatistics.setLastUpdatedTime(nowTimestamp);

                                // 非M3 Api的指标设置新的值
                                setIndexValueByBO(existOperatingStatistics, operatingStatisticsBO, nonM3ApiIndexNameList);

                                // 设置非M3 API的指标的数据源
                                List<OperatingStatisticsDatasourceDTO> nonM3ApiDatasourceDTOS = nonM3ApiIndexNameList.stream()
                                        .map(indexName -> new OperatingStatisticsDatasourceDTO(indexName, datasource)).toList();

                                // 收集数据源
                                ArrayList<OperatingStatisticsDatasourceDTO> allDatasourceDTOS = Lists.newArrayList(nonM3ApiDatasourceDTOS);
                                if (CollectionUtils.isNotEmpty(m3ApiDatasourceDTOS)) {
                                    allDatasourceDTOS.addAll(m3ApiDatasourceDTOS);
                                }
                                existOperatingStatistics.setDatasource(JsonUtils.convertObjListToString(objectMapper, allDatasourceDTOS));

                                batchUpdateWithNullList.add(existOperatingStatistics);
                            } else {
                                // 数据无需更新
                                nonUpdateCount++;
                            }
                        }
                    }
                }

                // 需要处理的日期数
                int needProcessDateCount = dateCount - noProcessDateCount;

                int dataUpdateResult;
                if (nonUpdateCount == needProcessDateCount) {
                    // 未更新的数量等于需要处理的数量，表示全部未更新
                    dataUpdateResult = DataUpdateResult.NOT_UPDATED.getValue();
                } else if (nonUpdateCount == 0) {
                    // 未更新的数量等于0，表示全部更新
                    dataUpdateResult = DataUpdateResult.ALL_UPDATED.getValue();
                } else {
                    // 部分更新
                    dataUpdateResult = DataUpdateResult.PARTIAL_UPDATED.getValue();
                }
                propertyId2DataUpdateResult.put(propertyDTO.getId(), dataUpdateResult);
            });

            // 批量新增
            saveBatch(batchInsertList);

            // 批量更新,支持指标值设置为null
            if (CollectionUtils.isNotEmpty(batchUpdateWithNullList)) {
                baseMapper.batchUpdateWithNull(batchUpdateWithNullList);
            }

            return propertyId2DataUpdateResult;
        }

        /**
         * 根据BO设置指标值
         *
         * @param operatingStatistics 待更新的数据
         * @param bo                  新的指标值
         * @param indexNames          待更新的指标集合
         * @author lichen
         * @date 2025/11/25
         */
        private void setIndexValueByBO(OperatingStatistics operatingStatistics, OperatingStatisticsListBO bo, List<String> indexNames) {
            indexNames.forEach(indexName -> {
                if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOMS_RENTED.getIndexName())) {
                    operatingStatistics.setRoomsRented(bo.getRoomsRented());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OCC_PERCENTAGE.getIndexName())) {
                    operatingStatistics.setOccPercent(bo.getOccPercent());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ADR.getIndexName())) {
                    operatingStatistics.setAdr(bo.getAdr());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.REV_PAR.getIndexName())) {
                    operatingStatistics.setRevPar(bo.getRevPar());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOM_REV.getIndexName())) {
                    operatingStatistics.setRoomRev(bo.getRoomRev());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.FOOD_REV.getIndexName())) {
                    operatingStatistics.setFoodRev(bo.getFoodRev());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.BEVERAGE_REV.getIndexName())) {
                    operatingStatistics.setBevRev(bo.getBevRev());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OTHER_REV.getIndexName())) {
                    operatingStatistics.setOtherRev(bo.getOtherRev());
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.TOTAL_REV.getIndexName())) {
                    operatingStatistics.setTotalRev(bo.getTotalRev());
                }
            });
        }

        /**
         * 指标值设置为null
         *
         * @param operatingStatistics 待处理数据
         * @param indexNames          设置为null的指标集合
         * @author lichen
         * @date 2025/11/22
         */
        private void setIndexValueIsNull(OperatingStatistics operatingStatistics, List<String> indexNames) {
            if (CollectionUtils.isEmpty(indexNames)) return;

            indexNames.forEach(indexName -> {
                if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOMS_RENTED.getIndexName())) {
                    operatingStatistics.setRoomsRented(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OCC_PERCENTAGE.getIndexName())) {
                    operatingStatistics.setOccPercent(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ADR.getIndexName())) {
                    operatingStatistics.setAdr(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.REV_PAR.getIndexName())) {
                    operatingStatistics.setRevPar(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOM_REV.getIndexName())) {
                    operatingStatistics.setRoomRev(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.FOOD_REV.getIndexName())) {
                    operatingStatistics.setFoodRev(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.BEVERAGE_REV.getIndexName())) {
                    operatingStatistics.setBevRev(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OTHER_REV.getIndexName())) {
                    operatingStatistics.setOtherRev(null);
                } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.TOTAL_REV.getIndexName())) {
                    operatingStatistics.setTotalRev(null);
                }
            });
        }

        /**
         * 更新资产日经营指标
         *
         * @param propertyDTO 资产
         * @param indexBO     日经营指标
         * @param operatorId  操作人id
         * @author lichen
         * @date 2025/10/15
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateOperatingStatisticsIndexByM3Api(PropertyDTO propertyDTO, DailyOperatingStatisticsIndexBO indexBO, int operatorId) {
            List<DailyOperatingStatisticsIndexValueBO> indexValues = indexBO.getIndexValues();
            if (CollectionUtils.isEmpty(indexValues)) return;

            //获取日期区间
            List<Integer> dateList = indexValues.stream().map(DailyOperatingStatisticsIndexValueBO::getDate).sorted().toList();
            Integer startDate = Collections.min(dateList);
            Integer endDate = Collections.max(dateList);
            Long startDateLong = DateTimeUtils.strToLocalDate(startDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD)
                    .atStartOfDay(ZoneId.of(propertyDTO.getTimezone())).toEpochSecond();
            Long endDateLong = DateTimeUtils.strToLocalDate(endDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD)
                    .atStartOfDay(ZoneId.of(propertyDTO.getTimezone())).toEpochSecond();

            //查询指定资产、指定日期的OperatingStatistics数据
            List<OperatingStatistics> existOperatingStatistics = list(new LambdaQueryWrapper<OperatingStatistics>()
                    .eq(OperatingStatistics::getPropertyId, propertyDTO.getId())
                    .between(OperatingStatistics::getTheDate, startDateLong, endDateLong));
            Map<Long, OperatingStatistics> theDate2ExistData = existOperatingStatistics.stream().collect(Collectors.toMap(OperatingStatistics::getTheDate, Function.identity()));

            ArrayList<OperatingStatistics> insertList = Lists.newArrayListWithCapacity(indexValues.size());
            ArrayList<OperatingStatistics> updateList = Lists.newArrayListWithCapacity(indexValues.size());

            long nowTimestamp = DateTimeUtils.nowTimestamp();

            for (DailyOperatingStatisticsIndexValueBO indexValue : indexValues) {
                Integer date = indexValue.getDate();
                long theDate = DateTimeUtils.strToLocalDate(date.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD)
                        .atStartOfDay(ZoneId.of(propertyDTO.getTimezone())).toEpochSecond();

                OperatingStatistics originData = theDate2ExistData.get(theDate);
                if (Objects.isNull(originData)) {
                    originData = new OperatingStatistics();
                    originData.setPropertyId(propertyDTO.getId()).setTheDate(theDate)
                            .setCreateBy(operatorId).setCreatedTime(nowTimestamp);
                    //设置指标值
                    this.setIndexValue(indexBO.getIndexName(), indexValue.getIndexValue(), originData);
                    //设置数据源
                    String datasource = JsonUtils.convertObjListToString(objectMapper, Collections.singletonList(
                            new OperatingStatisticsDatasourceDTO(indexBO.getIndexName(), OperatingStatisticsDatasourceEnum.M3_API.getValue())));
                    originData.setDatasource(datasource);

                    insertList.add(originData);
                } else {
                    //设置指标值
                    this.setIndexValue(indexBO.getIndexName(), indexValue.getIndexValue(), originData);
                    originData.setLastUpdatedBy(operatorId).setLastUpdatedTime(nowTimestamp);

                    String datasource = originData.getDatasource();
                    if (StringUtils.isBlank(datasource)) {
                        //原数据源为空时，构建当前指标的数据源
                        datasource = JsonUtils.convertObjListToString(objectMapper, Collections.singletonList(
                                new OperatingStatisticsDatasourceDTO(indexBO.getIndexName(), OperatingStatisticsDatasourceEnum.M3_API.getValue())));
                    } else {
                        //获取非当前指标的数据源集合
                        List<OperatingStatisticsDatasourceDTO> otherDatasourceList = JsonUtils.convertStringToList(objectMapper, datasource, OperatingStatisticsDatasourceDTO.class)
                                .stream().filter(item -> !item.getIndexName().equals(indexBO.getIndexName())).toList();

                        ArrayList<OperatingStatisticsDatasourceDTO> operatingStatisticsDatasourceDTOS = new ArrayList<>(otherDatasourceList);
                        operatingStatisticsDatasourceDTOS.add(new OperatingStatisticsDatasourceDTO(indexBO.getIndexName(), OperatingStatisticsDatasourceEnum.M3_API.getValue()));
                        datasource = JsonUtils.convertObjListToString(objectMapper, operatingStatisticsDatasourceDTOS);
                    }
                    originData.setDatasource(datasource);

                    updateList.add(originData);
                }
            }

            //新增
            saveBatch(insertList);
            //更新
            saveOrUpdateBatch(updateList);
        }

        /**
         * 获取资产指定时间段经营数据
         *
         * @param propertyIds 资产id集合
         * @param startDate   开始时间
         * @param endDate     结束时间
         * @return java.util.List<cloud.wconnect.workperformance.entity.sva.operatingstatistics.OperatingStatistics>
         * @author lichen
         * @date 2025/10/15
         */
        @Override
        public List<OperatingStatistics> listByDateRange(List<Integer> propertyIds, long startDate, long endDate) {
            if (CollectionUtils.isEmpty(propertyIds)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<OperatingStatistics>()
                    .in(OperatingStatistics::getPropertyId, propertyIds)
                    .between(OperatingStatistics::getTheDate, startDate, endDate));
        }

        /**
         * 设置指标值
         *
         * @param indexName           指标名称
         * @param indexValue          指标值
         * @param operatingStatistics 日运营数据
         * @return void
         * @author lichen
         * @date 2025/10/15
         */
        private void setIndexValue(String indexName, BigDecimal indexValue, OperatingStatistics operatingStatistics) {
            //获取指标名称对应的字段名称
            String fieldName = "";
            if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOMS_RENTED.getIndexName())) {
                fieldName = "roomsRented";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OCC_PERCENTAGE.getIndexName())) {
                fieldName = "occPercent";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ADR.getIndexName())) {
                fieldName = "adr";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.REV_PAR.getIndexName())) {
                fieldName = "revPar";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.ROOM_REV.getIndexName())) {
                fieldName = "roomRev";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.FOOD_REV.getIndexName())) {
                fieldName = "foodRev";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.BEVERAGE_REV.getIndexName())) {
                fieldName = "bevRev";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.OTHER_REV.getIndexName())) {
                fieldName = "otherRev";
            } else if (indexName.equals(DailyOperatingStatisticsIndexEnum.TOTAL_REV.getIndexName())) {
                fieldName = "totalRev";
            }
            //获取字段对象
            Field field = ReflectionUtils.findField(OperatingStatistics.class, fieldName);
            //设置可访问
            field.setAccessible(true);

            //设置字段值
            if (field.getType().isAssignableFrom(Integer.class)) {
                ReflectionUtils.setField(field, operatingStatistics, indexValue.intValue());
            } else {
                ReflectionUtils.setField(field, operatingStatistics, indexValue);
            }
        }

        /**
         * 删除指定日期、资产范围的 operating 数据
         *
         * @param propertyDTO 资产
         * @param startDate   开始日期(时间戳)
         * @param endDate     结束日期(时间戳)
         * @author zc
         * @date 2024/3/2
         */
        private void deleteByDateRange(PropertyDTO propertyDTO, long startDate, long endDate) {
            remove(new QueryWrapper<OperatingStatistics>().lambda()
                    .eq(OperatingStatistics::getPropertyId, propertyDTO.getId())
                    .between(OperatingStatistics::getTheDate, startDate, endDate));
        }
    }