# RevenueDailySummaryServiceImpl.java

    package cloud.wconnect.rateshop.service.revenue.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.HourCons;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.UpdateLog;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.commonlib.util.UpdateLogUtil;
    import cloud.wconnect.rateshop.cons.rateshop.RateTypeEnum;
    import cloud.wconnect.rateshop.cons.revenue.DailyRevenueUpdateFieldEnum;
    import cloud.wconnect.rateshop.cons.revenue.RevenueCons;
    import cloud.wconnect.rateshop.entity.rateshop.Hotel;
    import cloud.wconnect.rateshop.entity.rateshop.Setting;
    import cloud.wconnect.rateshop.entity.revenue.*;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueDailySummaryMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.DailySummaryBO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.RateConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.filterrate.RateQuery;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.filterrate.RateValue;
    import cloud.wconnect.rateshop.pojo.dto.revenue.DailySummaryDTO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.DailySummaryMEPSDTO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.HourHotelPriceDTO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.RateNHoursManualDTO;
    import cloud.wconnect.rateshop.pojo.query.revenue.HourHotelPriceQuery;
    import cloud.wconnect.rateshop.pojo.vo.revenue.DailySummaryHotelPriceVO;
    import cloud.wconnect.rateshop.pojo.vo.revenue.DailySummaryVO;
    import cloud.wconnect.rateshop.service.rateshop.IPropertyHotelService;
    import cloud.wconnect.rateshop.service.rateshop.ISettingService;
    import cloud.wconnect.rateshop.service.revenue.*;
    import cloud.wconnect.rateshop.util.rateshop.FilterRateUtil;
    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.core.conditions.update.LambdaUpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Nullable;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.ReflectionUtils;

    import java.lang.reflect.Method;
    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Daily Sumamry 酒店运营相关信息 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2024-03-23
     */
    @Service
    @DS("rate_shop")
    public class RevenueDailySummaryServiceImpl extends ServiceImpl<RevenueDailySummaryMapper, RevenueDailySummary> implements IRevenueDailySummaryService {

        @Autowired
        private IHistoryRateNHoursManualService historyRateNHoursManualService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IRevenueDailySummaryTaService revenueDailySummaryTaService;
        @Autowired
        private IRevenueDailySummaryManualService revenueDailySummaryManualService;
        @Autowired
        private UpdateLogUtil updateLogUtil;
        @Autowired
        private IPropertyHotelService propertyHotelService;
        @Autowired
        private ISettingService settingService;
        @Autowired
        private IHistoryBARRateNHoursService historyBARRateNHoursService;
        @Autowired
        private IRevenueDailySummarySenderService revenueDailySummaryOccSenderService;
        @Autowired
        private Executor executor;
        @Autowired
        private IRevenueDailySummaryMepsService revenueDailySummaryMepsService;
        @Autowired
        private IRevenueDailySummaryManagerFlashService revenueDailySummaryManagerFlashService;
        @Autowired
        private IRevenueDailySummaryGroupPickupService revenueDailySummaryGroupPickupService;

        /**
         * 指定日期资产自身酒店当日每小时BAR房价信息
         *
         * @param targetDate          指定日期（yyyyMMdd）
         * @param propertyDTO         资产
         * @param hotel               资产设置的自身酒店
         * @param hourHotelPriceQuery 查询条件
         * @return java.util.List<cloud.wconnect.rateshop.pojo.vo.revenue.DailySummaryVO>
         * @author lichen
         * @date 2024/3/26
         */
        @Override
        public List<DailySummaryHotelPriceVO> listPropertySelfHourBARRate(Integer targetDate, PropertyDTO propertyDTO, Hotel hotel, HourHotelPriceQuery hourHotelPriceQuery) {
            Setting setting = settingService.getByPropertyId(propertyDTO.getId());
            //获取资产的房价含税标识
            Integer containTaxFlag = setting.getContainTaxFlag();
            //销售地区
            String pos = setting.getPos();
            //资产配置的房价参数
            List<RateConfigDTO> rateConfigDTOS = JsonUtils.convertStringToList(objectMapper, setting.getRateConfig(), RateConfigDTO.class);
            //资产配置的房价类型
            Set<Integer> rateTypes = rateConfigDTOS.stream().map(RateConfigDTO::getRateType).collect(Collectors.toSet());
            //获取币种ID
            int currencyId = setting.getCurrencyId();

            Map<Integer, HourHotelPriceDTO> hour2HotelPriceDTOByCollect = new HashMap<>();
            //资产配置BAR房价时，查询自动抓取的BAR房价
            if (rateTypes.contains(RateTypeEnum.BEST_AVAILABLE_RATE.getRateType())) {
                hour2HotelPriceDTOByCollect = listGrabHourHotelBARRate(targetDate, hotel, currencyId, rateConfigDTOS, hourHotelPriceQuery, pos, propertyDTO.getId(), containTaxFlag);
            }

            //查询手动维护的房价
            Map<Integer, HistoryRateNHoursManual> hour2HistoryRateNHoursManual = listManualHourHotelRate(targetDate, hotel, currencyId, propertyDTO.getId(),
                    RateTypeEnum.BEST_AVAILABLE_RATE.getRateType(), pos);

            List<DailySummaryHotelPriceVO> dailySummaryHotelPriceVOList = Lists.newArrayListWithCapacity(24);
            for (int i = 0; i <= 24; i++) {
                //指定小时下手动维护的房价
                HistoryRateNHoursManual hourRateByManual = hour2HistoryRateNHoursManual.get(i);
                if (Objects.nonNull(hourRateByManual)) {
                    //房价
                    BigDecimal price = containTaxFlag == Setting.BEFORE_TAX_RATE ? hourRateByManual.getRate() : hourRateByManual.getAfterTaxRate();
                    if (Objects.nonNull(price)) {
                        //手动维护房价的操作日志
                        UpdateLog lastUpdateLog = updateLogUtil.getLast(hourRateByManual.getUpdateLog());

                        DailySummaryHotelPriceVO dailySummaryHotelPriceVO = new DailySummaryHotelPriceVO().setHour(i)
                                .setPrice(price)
                                .setDatasourceId(0).setUpdateDate(lastUpdateLog.getTimestamp())
                                .setUpdateId(lastUpdateLog.getUid()).setPlatformId(hourRateByManual.getPlatformId());
                        dailySummaryHotelPriceVOList.add(dailySummaryHotelPriceVO);
                        continue;
                    }
                }

                //指定小时下自动导入的房价
                HourHotelPriceDTO hourHotelPriceDTO = hour2HotelPriceDTOByCollect.get(i);
                if (Objects.nonNull(hourHotelPriceDTO)) {
                    //房价
                    BigDecimal price = containTaxFlag == Setting.BEFORE_TAX_RATE ? hourHotelPriceDTO.getPrice() : hourHotelPriceDTO.getAfterTaxPrice();
                    if (Objects.nonNull(price)) {
                        DailySummaryHotelPriceVO dailySummaryHotelPriceVO = new DailySummaryHotelPriceVO().setHour(i)
                                .setPrice(price)
                                .setUpdateId(0)
                                .setUpdateDate(hourHotelPriceDTO.getQueryDate())
                                .setDatasourceId(hourHotelPriceDTO.getDatasourceId())
                                .setPlatformId(hourHotelPriceDTO.getPlatformId());
                        dailySummaryHotelPriceVOList.add(dailySummaryHotelPriceVO);
                    }
                }
            }
            return dailySummaryHotelPriceVOList;
        }

        /**
         * 查询资产自身酒店指定日期、指定币种、指定房价类型 手动维护的房价
         *
         * @param targetDate 指定日期
         * @param hotel      酒店
         * @param currencyId 币种
         * @param propertyId 资产ID
         * @param rateType   房价类型1:早鸟价 2:BAR房价
         * @param pos        销售地区
         * @return java.util.Map<java.lang.Integer, cloud.wconnect.rateshop.entity.revenue.HistoryRateNHoursManual>
         * @author lichen
         * @date 2024/5/16
         */
        private Map<Integer, HistoryRateNHoursManual> listManualHourHotelRate(Integer targetDate, Hotel hotel, int currencyId,
                                                                              Integer propertyId, int rateType, String pos) {
            //final房价来源于次日5AM，所以也需要查询出次日的数据
            LocalDate nextLocalDate = DateTimeUtils.strToLocalDate(targetDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1);
            Integer nextTargetDate = Integer.valueOf(DateTimeUtils.localDateToStr(nextLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<Integer> targetDateList = Lists.newArrayList(targetDate, nextTargetDate);

            //查询手动维护的BAR房价
            Map<Integer, List<HistoryRateNHoursManual>> targetDate2HistoryRateNHoursManual = historyRateNHoursManualService.list(targetDateList, hotel.getId(),
                            currencyId, propertyId, rateType, pos)
                    .stream().collect(Collectors.groupingBy(HistoryRateNHoursManual::getTargetDate));
            ArrayList<HistoryRateNHoursManual> historyRateNHoursManualList = Lists.newArrayListWithCapacity(24);
            //手动维护的当日房价
            historyRateNHoursManualList.addAll(targetDate2HistoryRateNHoursManual.getOrDefault(targetDate, Collections.emptyList()));
            //把次日5点的房价设置为24时(Final)
            List<HistoryRateNHoursManual> finalHistoryRateNHoursManualList = targetDate2HistoryRateNHoursManual.getOrDefault(nextTargetDate, Collections.emptyList())
                    .stream()
                    .filter(item -> item.getHour() == RevenueCons.FINAL_OF_NEXT_DAYS_HOUR)
                    .peek(item -> item.setHour(RevenueDailySummary.FINAL_HOUR))
                    .toList();
            historyRateNHoursManualList.addAll(finalHistoryRateNHoursManualList);
            return historyRateNHoursManualList.stream().collect(Collectors.toMap(HistoryRateNHoursManual::getHour, Function.identity()));
        }

        /**
         * 根据条件查询指定酒店指定日期指定币种的每小时房价
         *
         * @param targetDate          目标日期
         * @param hotel               酒店
         * @param currencyId          币种
         * @param rateConfigDTOS      房价配置参数
         * @param hourHotelPriceQuery 查询条件（包括数据源、平台）
         * @param pos                 销售地区
         * @param propertyId          资产ID
         * @param containTaxFlag      含税标识
         * @return java.util.Map<java.lang.Integer, cloud.wconnect.rateshop.pojo.dto.revenue.HourHotelPriceDTO>
         * @author lichen
         * @date 2024/5/16
         */
        private Map<Integer, HourHotelPriceDTO> listGrabHourHotelBARRate(Integer targetDate, Hotel hotel, Integer currencyId, List<RateConfigDTO> rateConfigDTOS,
                                                                         HourHotelPriceQuery hourHotelPriceQuery, String pos, Integer propertyId, Integer containTaxFlag) {
            //final房价来源于次日5AM，所以也需要查询出次日的数据
            LocalDate nextLocalDate = DateTimeUtils.strToLocalDate(targetDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1);
            Integer nextTargetDate = Integer.valueOf(DateTimeUtils.localDateToStr(nextLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<Integer> targetDateList = Lists.newArrayList(targetDate, nextTargetDate);

            //采集的房价
            ArrayList<HourHotelPriceDTO> hotelPriceDTOByCollect = Lists.newArrayListWithCapacity(24);
            //查询当日和次日采集的BAR房价
            Map<Integer, HistoryBARRateNHours> targetDate2HistoryBARRateNHours = historyBARRateNHoursService.list(targetDateList, hotel.getId(), currencyId, pos, propertyId)
                    .stream().collect(Collectors.toMap(HistoryBARRateNHours::getTargetDate, Function.identity()));

            //获取当天自动导入的房价，0-23小时
            HistoryBARRateNHours historyBARRateNHoursByCurrentDate = targetDate2HistoryBARRateNHours.get(targetDate);
            if (Objects.nonNull(historyBARRateNHoursByCurrentDate)) {
                /**
                 * 获取指定数据源和平台下的最低价
                 */
                List<HourHotelPriceDTO> lowestRateList = getLowestRateByDatasourceAndPlatform(JsonUtils.convertStringToList(objectMapper, historyBARRateNHoursByCurrentDate.getData(), HourHotelPriceDTO.class),
                        hourHotelPriceQuery.getDatasourceId(), hourHotelPriceQuery.getPlatformId(), rateConfigDTOS, RateTypeEnum.BEST_AVAILABLE_RATE.getRateType(), containTaxFlag);
                hotelPriceDTOByCollect.addAll(lowestRateList);
            }

            //获取次日自动导入的房价
            HistoryBARRateNHours historyBARRateNHoursByNextDate = targetDate2HistoryBARRateNHours.get(nextTargetDate);
            if (Objects.nonNull(historyBARRateNHoursByNextDate)) {
                //获取次日5点AM的房价
                List<HourHotelPriceDTO> nextTargetDate5HourHotelPriceDTOS = JsonUtils.convertStringToList(objectMapper, historyBARRateNHoursByNextDate.getData(), HourHotelPriceDTO.class)
                        .stream().filter(item -> item.getHour() == RevenueCons.FINAL_OF_NEXT_DAYS_HOUR)
                        .toList();
                /**
                 * 获取指定数据源和平台下的最低价
                 */
                List<HourHotelPriceDTO> lowestRateList = getLowestRateByDatasourceAndPlatform(nextTargetDate5HourHotelPriceDTOS, hourHotelPriceQuery.getDatasourceId(),
                        hourHotelPriceQuery.getPlatformId(), rateConfigDTOS, RateTypeEnum.BEST_AVAILABLE_RATE.getRateType(), containTaxFlag);
                //把次日5点AM的房价设置为24点(Final)
                lowestRateList.forEach(dto -> dto.setHour(RevenueDailySummary.FINAL_HOUR));
                hotelPriceDTOByCollect.addAll(lowestRateList);
            }

            return hotelPriceDTOByCollect.stream().collect(Collectors.toMap(HourHotelPriceDTO::getHour, Function.identity()));
        }

        /**
         * 获取指定数据源和平台下的最低价
         * 1. 数据源优先于平台
         * 2. 数据源和平台为空时，忽略
         * 3. 取最低价
         *
         * @param hourHotelPriceDTOS  每小时的房价集合
         * @param datasourceIdByQuery 查询条件：数据源ID
         * @param platformIdByQuery   查询条件：平台ID
         * @param rateConfigDTOS      房价配置参数
         * @param rateType            房价类型
         * @param containTaxFlag      含税标识
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.revenue.HourHotelPriceDTO>
         * @author lichen
         * @date 2024/5/15
         */
        private List<HourHotelPriceDTO> getLowestRateByDatasourceAndPlatform(List<HourHotelPriceDTO> hourHotelPriceDTOS, @Nullable Integer datasourceIdByQuery,
                                                                             @Nullable Integer platformIdByQuery, List<RateConfigDTO> rateConfigDTOS, int rateType,
                                                                             Integer containTaxFlag) {
            if (CollectionUtils.isEmpty(hourHotelPriceDTOS)) return Collections.emptyList();

            //房价查询条件
            RateQuery rateQuery = RateQuery.builder()
                    .datasourceId(datasourceIdByQuery)
                    .platformId(platformIdByQuery)
                    .roomIds(Collections.emptyList())
                    .build();

            List<HourHotelPriceDTO> hourHotelPriceDTOSByFilter = hourHotelPriceDTOS.stream()
                    //筛选出指定数据源 指定平台的房价。数据源和平台为空时，忽略
                    .filter(hourHotelPriceDTO -> {
                        RateValue rateValue = beanMapper.map(hourHotelPriceDTO, RateValue.class);
                        return FilterRateUtil.isMatchByQueryCondition(rateValue, rateQuery, rateConfigDTOS, rateType);
                    }).toList();

            //最低房价集合
            ArrayList<HourHotelPriceDTO> lowestPriceList = Lists.newArrayListWithCapacity(hourHotelPriceDTOSByFilter.size());

            Map<Integer, List<HourHotelPriceDTO>> uniqueKey2HourHotelPrice = hourHotelPriceDTOSByFilter.stream().collect(Collectors.groupingBy(HourHotelPriceDTO::getHour));
            for (Map.Entry<Integer, List<HourHotelPriceDTO>> hourHotelPriceEntry : uniqueKey2HourHotelPrice.entrySet()) {
                List<HourHotelPriceDTO> hourHotelPricesByUniqueKey = hourHotelPriceEntry.getValue();
                if (hourHotelPricesByUniqueKey.size() == 1) {
                    lowestPriceList.add(hourHotelPricesByUniqueKey.get(0));
                } else {
                    List<HourHotelPriceDTO> notNullHourHotelPriceList = hourHotelPricesByUniqueKey.stream().filter(item -> {
                        if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                            return Objects.nonNull(item.getPrice());
                        } else {
                            return Objects.nonNull(item.getAfterTaxPrice());
                        }
                    }).toList();
                    if (CollectionUtils.isNotEmpty(notNullHourHotelPriceList)) {
                        //判断是否Sold Out
                        boolean isSoldOut = notNullHourHotelPriceList.stream().allMatch(item -> {
                            if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                                return item.getPrice().compareTo(BigDecimal.ZERO) == 0;
                            } else {
                                return item.getAfterTaxPrice().compareTo(BigDecimal.ZERO) == 0;
                            }
                        });
                        if (isSoldOut) {
                            //取任意一条数据即可
                            lowestPriceList.add(notNullHourHotelPriceList.get(0));
                        } else {
                            //取房价最低的数据
                            Optional<HourHotelPriceDTO> optional = notNullHourHotelPriceList.stream()
                                    .filter(item -> {
                                        if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                                            return item.getPrice().compareTo(BigDecimal.ZERO) > 0;
                                        } else {
                                            return item.getAfterTaxPrice().compareTo(BigDecimal.ZERO) > 0;
                                        }
                                    })
                                    .min(Comparator.comparing(item -> {
                                        if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                                            return item.getPrice();
                                        } else {
                                            return item.getAfterTaxPrice();
                                        }
                                    }));
                            optional.ifPresent(lowestPriceList::add);
                        }
                    }
                }
            }
            return lowestPriceList;
        }

        /**
         * 查询每小时的汇总信息
         *
         * @param propertyDTO 资产DTO
         * @param targetDate  指定日期时间戳
         * @return java.util.List<cloud.wconnect.rateshop.pojo.vo.revenue.DailySummaryVO>
         * @author lichen
         * @date 2025/9/6
         */
        @Override
        public List<DailySummaryVO> listDailySummary(PropertyDTO propertyDTO, long targetDate) {
            //获取当日系統自动导入的运营信息
            Map<Integer, RevenueDailySummary> hour2AutoDailySummary = list(new LambdaQueryWrapper<RevenueDailySummary>().eq(RevenueDailySummary::getPropertyId, propertyDTO.getId())
                    .eq(RevenueDailySummary::getTheDate, targetDate)).stream().collect(Collectors.toMap(RevenueDailySummary::getTheHour, Function.identity()));

            //查询手动维护的运营信息
            Map<Integer, RevenueDailySummaryManual> hour2ManualDailySummary = revenueDailySummaryManualService.listByPropertyIdAndDate(propertyDTO.getId(), targetDate)
                    .stream().collect(Collectors.toMap(RevenueDailySummaryManual::getTheHour, Function.identity()));

            //查询Ta
            RevenueDailySummaryTa autoDailySummaryTa = revenueDailySummaryTaService.getByTheDate(propertyDTO.getId(), targetDate);
            //查询MEPS Group Total
            DailySummaryMEPSDTO meps = revenueDailySummaryGroupPickupService.getMEPS(propertyDTO.getId(), targetDate);
            //查询从Manager Flash报告自动导入的字段No show、Cancelled、ADR
            RevenueDailySummaryManagerFlash revenueDailySummaryManagerFlash = revenueDailySummaryManagerFlashService.getByDate(propertyDTO.getId(), targetDate);

            //Rooms Available、Total Room Sold、Occ、OOO Rooms 相关字段值
            ArrayList<String> fieldList = Lists.newArrayList(DailyRevenueUpdateFieldEnum.ROOM_AVAILABLE.getFieldName(),
                    DailyRevenueUpdateFieldEnum.TOTAL_ROOM_SOLD.getFieldName(), DailyRevenueUpdateFieldEnum.OCC.getFieldName(),
                    DailyRevenueUpdateFieldEnum.OOOROOMS.getFieldName());

            List<DailySummaryVO> dailySummaryVOS = Lists.newArrayListWithCapacity(25);
            for (int i = 0; i <= 24; i++) {
                DailySummaryVO dailySummaryVO = new DailySummaryVO();
                dailySummaryVO.setHour(i);
                //手动维护
                RevenueDailySummaryManual manualDailySummary = hour2ManualDailySummary.get(i);
                //自动导入
                RevenueDailySummary autoDailySummary = hour2AutoDailySummary.get(i);

                if (i == RevenueDailySummary.FINAL_HOUR) {
                    fieldList.forEach(field -> packageDailySummaryFieldValue(dailySummaryVO, manualDailySummary, autoDailySummary, field));
                    //设置Ta
                    packageTa(dailySummaryVO, manualDailySummary, autoDailySummaryTa);
                    //设置MEPS Group Total
                    packageMepsGroupTotal(dailySummaryVO, manualDailySummary, meps);
                    //设置No show、Cancelled、ADR
                    ArrayList<String> fieldListByFinal = Lists.newArrayList(DailyRevenueUpdateFieldEnum.NOSHOW.getFieldName(),
                            DailyRevenueUpdateFieldEnum.CANCELLED.getFieldName(), DailyRevenueUpdateFieldEnum.ADR.getFieldName());
                    fieldListByFinal.forEach(field -> packageDailySummaryManagerFlashFieldValue(dailySummaryVO, manualDailySummary, revenueDailySummaryManagerFlash, field));
                } else {
                    fieldList.forEach(field -> packageDailySummaryFieldValue(dailySummaryVO, manualDailySummary, autoDailySummary, field));
                }
                //设置 pick up
                setPickUp(i, dailySummaryVO, manualDailySummary, hour2AutoDailySummary);

                dailySummaryVOS.add(dailySummaryVO);
            }
            return dailySummaryVOS;
        }

        /**
         * 获取指定资产指定时间段内的Daily Summary数据
         *
         * @param propertyId 资产id
         * @param startDate  开始时间
         * @param endDate    结束时间
         * @return java.util.List<cloud.wconnect.rateshop.entity.revenue.RevenueDailySummary>
         * @author yyt
         * @date 2026/3/4
         */
        @Override
        public List<RevenueDailySummary> listDailySummary(Integer propertyId, Long startDate, Long endDate) {
            return list(new QueryWrapper<RevenueDailySummary>().lambda()
                    .eq(RevenueDailySummary::getPropertyId, propertyId)
                    .between(RevenueDailySummary::getTheDate, startDate, endDate));
        }

        /**
         * 设置 pick up
         *
         * @param currentLoopHour           当前循环小时
         * @param dailySummaryVO            页面显示内容
         * @param revenueDailySummaryManual 手动修改的数据
         * @param hour2AutoDailySummary     报告下发的当日各个时刻数据
         * @author yyt
         * @date 2024/5/23
         **/
        private void setPickUp(int currentLoopHour, DailySummaryVO dailySummaryVO,
                               RevenueDailySummaryManual revenueDailySummaryManual,
                               Map<Integer, RevenueDailySummary> hour2AutoDailySummary) {
            switch (currentLoopHour) {
                case HourCons.HOUR_6 -> {
                    // 6点的【Pick Up】= 6点 - 0点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_0);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_6);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                case HourCons.HOUR_10 -> {
                    // 10点的【Pick Up】= 10点 - 6点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_6);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_10);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                case HourCons.HOUR_15 -> {
                    // 15点的【Pick Up】= 15点 - 10点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_10);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_15);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                case HourCons.HOUR_19 -> {
                    // 19点的【Pick Up】= 19点 - 15点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_15);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_19);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                case HourCons.HOUR_23 -> {
                    // 23点的【Pick Up】= 23点 - 19点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_19);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_23);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                case HourCons.HOUR_24 -> {
                    // 24点的【Pick Up】= 24点 - 0点
                    RevenueDailySummary prev = hour2AutoDailySummary.get(HourCons.HOUR_0);
                    RevenueDailySummary next = hour2AutoDailySummary.get(HourCons.HOUR_24);
                    calculatePickUp(prev, next, revenueDailySummaryManual, dailySummaryVO);
                }
                default -> {
                }
            }
        }

        /**
         * 计算Transient 和 Group 的Pick Up
         *
         * @param prev               自动导入的前一个时刻的数据
         * @param next               自动导入的后一个时刻的数据
         * @param manualDailySummary 手动维护的Pick Up
         * @param dailySummaryVO
         * @return void
         * @author lichen
         * @date 2025/1/10
         */
        private void calculatePickUp(RevenueDailySummary prev, RevenueDailySummary next,
                                     RevenueDailySummaryManual manualDailySummary,
                                     DailySummaryVO dailySummaryVO) {
            //Transient Pick Up 计算
            // 手动修改过，优先显示，否则用报告的数据进行计算
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(manualDailySummary.getTransientPickUp())) {
                dailySummaryVO.setTransientPickUp(manualDailySummary.getTransientPickUp());
                // 获取最后一次操作日志
                UpdateLog lastUpdateLog = updateLogUtil.getLast(manualDailySummary.getTransientPickUpUpdateLog());
                if (Objects.nonNull(lastUpdateLog)) {
                    dailySummaryVO.setTransientPickUpUpdateId(lastUpdateLog.getUid());
                    dailySummaryVO.setTransientPickUpUpdateTime(lastUpdateLog.getTimestamp());
                }
            } else {
                // pick up = next - prev
                if (Objects.nonNull(prev) && Objects.nonNull(prev.getTransientRooms())
                        && Objects.nonNull(next) && Objects.nonNull(next.getTransientRooms())) {
                    Integer pickUp = next.getTransientRooms() - prev.getTransientRooms();
                    dailySummaryVO.setTransientPickUp(pickUp);
                    dailySummaryVO.setTransientPickUpUpdateId(0);
                    dailySummaryVO.setTransientPickUpUpdateTime(next.getSyncTime());
                }
            }

            //Group  Pick Up 计算
            //手动修改过，优先显示，否则用报告的数据进行计算
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(manualDailySummary.getGroupPickUp())) {
                dailySummaryVO.setGroupPickUp(manualDailySummary.getGroupPickUp());
                // 获取最后一次操作日志
                UpdateLog lastUpdateLog = updateLogUtil.getLast(manualDailySummary.getGroupPickUpUpdateLog());
                if (Objects.nonNull(lastUpdateLog)) {
                    dailySummaryVO.setGroupPickUpUpdateId(lastUpdateLog.getUid());
                    dailySummaryVO.setGroupPickUpUpdateTime(lastUpdateLog.getTimestamp());
                }
            } else {
                // pick up = next - prev
                if (Objects.nonNull(prev) && Objects.nonNull(prev.getGroupRooms())
                        && Objects.nonNull(next) && Objects.nonNull(next.getGroupRooms())) {
                    Integer pickUp = next.getGroupRooms() - prev.getGroupRooms();
                    dailySummaryVO.setGroupPickUp(pickUp);
                    dailySummaryVO.setGroupPickUpUpdateId(0);
                    dailySummaryVO.setGroupPickUpUpdateTime(next.getSyncTime());
                }
            }
        }

        /**
         * 保存或更新
         *
         * @param propertyDTO     资产DTO
         * @param dailySummaryBOS 每小时的汇总信息
         * @return void
         * @author lichen
         * @date 2024/3/26
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdate(PropertyDTO propertyDTO, List<DailySummaryBO> dailySummaryBOS) {
            List<RevenueDailySummary> revenueDailySummaries = beanMapper.mapList(dailySummaryBOS, RevenueDailySummary.class,
                    entity -> entity.setPropertyId(propertyDTO.getId()));
            baseMapper.saveOrUpdateByUnique(revenueDailySummaries);

            //发送Occ变更的消息
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                revenueDailySummaryOccSenderService.publishByAutoImport(propertyDTO.getId(), revenueDailySummaries);
            });
        }

        /**
         * 手动维护Daily Summary
         *
         * @param propertyDTO     资产
         * @param date            日期yyyy-MM-dd
         * @param hour            小时数
         * @param dailySummaryDTO 手动编辑Daily Summary DTO
         * @param userId          编辑人
         * @return void
         * @author lichen
         * @date 2024/3/28
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void manualSave(PropertyDTO propertyDTO, String date, Integer hour, DailySummaryDTO dailySummaryDTO, int userId) {
            //更新的字段集合
            List<Integer> updateFieldList = Arrays.stream(dailySummaryDTO.getUpdateFields().split(",")).map(Integer::valueOf).toList();

            //排除房价后的其他字段ID
            List<Integer> excludePriceFieldIds = updateFieldList.stream().filter(item -> item != DailyRevenueUpdateFieldEnum.PRICE.getFieldId())
                    .toList();
            if (CollectionUtils.isNotEmpty(excludePriceFieldIds)) {
                //保存Daily Summary信息
                saveManualDailySummary(propertyDTO, date, hour, dailySummaryDTO, userId, excludePriceFieldIds);
            }

            //保存指定时刻的房价
            if (updateFieldList.contains(DailyRevenueUpdateFieldEnum.PRICE.getFieldId())) {
                RateNHoursManualDTO rateNHoursManualDTO = new RateNHoursManualDTO();
                rateNHoursManualDTO.setTargetDate(Integer.parseInt(date.replace("-", "")));
                rateNHoursManualDTO.setHour(hour);
                rateNHoursManualDTO.setUpdatePrice(dailySummaryDTO.getPrice());
                rateNHoursManualDTO.setPlatformId(Objects.isNull(dailySummaryDTO.getPrice()) ? null : dailySummaryDTO.getPlatformId());
                rateNHoursManualDTO.setRateType(RateTypeEnum.BEST_AVAILABLE_RATE.getRateType());
                manualEditPropertySelfHourPrice(propertyDTO, rateNHoursManualDTO, userId);
            }

        }

        /**
         * 查询指定时刻的Daily Summary数据
         *
         * @param propertyId 资产id
         * @param theDate    指定日期，时间戳
         * @param theHour    指定小时数
         * @return cloud.wconnect.rateshop.entity.revenue.RevenueDailySummary
         * @author lichen
         * @date 2024/10/18
         */
        @Override
        public RevenueDailySummary getByPropertyIdAndDateAndHour(Integer propertyId, Long theDate, Integer theHour) {
            return getOne(new LambdaQueryWrapper<RevenueDailySummary>().eq(RevenueDailySummary::getPropertyId, propertyId)
                    .eq(RevenueDailySummary::getTheDate, theDate)
                    .eq(RevenueDailySummary::getTheHour, theHour));
        }

        /**
         * 获取合并后的Daily Summary信息（以手动维护的优先，进行合并）
         *
         * @param propertyDTO     资产
         * @param date            目标日期yyyy-MM-dd
         * @param hour            小时数
         * @param userId          编辑人
         * @param updateFieldList 更新的字段集合
         * @return cloud.wconnect.rateshop.pojo.dto.revenue.DailySummaryManualDTO
         * @author lichen
         * @date 2024/3/28
         */
        private void saveManualDailySummary(PropertyDTO propertyDTO, String date, Integer hour, DailySummaryDTO updateDailySummaryDTO,
                                            Integer userId, List<Integer> updateFieldList) {
            //转化成时间戳
            long targetDate = DateTimeUtils.dateStrToEpochSecond(date, BaseConstant.DATE_FORMAT_YYYY_MM_DD, propertyDTO.getTimezone());

            //查询手动维护的运营信息
            RevenueDailySummaryManual dailySummaryManual = revenueDailySummaryManualService.get(propertyDTO.getId(), targetDate, hour);

            if (Objects.isNull(dailySummaryManual)) {
                dailySummaryManual = new RevenueDailySummaryManual();
                dailySummaryManual.setPropertyId(propertyDTO.getId())
                        .setTheDate(targetDate).setTheHour(hour);
            }

            for (Integer fieldId : updateFieldList) {
                String fieldName = DailyRevenueUpdateFieldEnum.getFieldNameById(fieldId);

                if (StringUtils.isBlank(fieldName)) {
                    log.warn("Field Name not find, fieldId = " + fieldId);
                    continue;
                }

                //DailySummaryDTO 字段对应的Get方法
                Method getMethod = getGetMethod(DailySummaryDTO.class, fieldName);
                Object fieldValue = ReflectionUtils.invokeMethod(getMethod, updateDailySummaryDTO);

                //RevenueDailySummaryManual 字段对应的Set方法
                Method setMethod = getSetMethod(RevenueDailySummaryManual.class, fieldName);
                ReflectionUtils.invokeMethod(setMethod, dailySummaryManual, fieldValue);

                //处理更新日志
                Method getMethodByUpdateLog = getGetMethod(RevenueDailySummaryManual.class, fieldName + "UpdateLog");
                Object updateLogFieldValue = ReflectionUtils.invokeMethod(getMethodByUpdateLog, dailySummaryManual);
                String oriUpdateLog = Objects.isNull(updateLogFieldValue) ? "[]" : updateLogFieldValue.toString();
                String updateLog = updateLogUtil.append(oriUpdateLog, userId);

                //追加更新日志
                Method setMethodByUpdateLog = getSetMethod(RevenueDailySummaryManual.class, fieldName + "UpdateLog");
                ReflectionUtils.invokeMethod(setMethodByUpdateLog, dailySummaryManual, updateLog);
            }
            //更新
            revenueDailySummaryManualService.saveOrUpdate(dailySummaryManual);
            //更新Occ或RoomSold时发送MoudleEvent事件
            if (updateFieldList.contains(DailyRevenueUpdateFieldEnum.OCC.getFieldId())
                    || updateFieldList.contains(DailyRevenueUpdateFieldEnum.TOTAL_ROOM_SOLD.getFieldId())) {
                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    revenueDailySummaryOccSenderService.publishByManualSave(propertyDTO.getId(), targetDate, hour);
                });
            }
        }

        /**
         * 手动编辑资产自身酒店的小时房价
         *
         * @param propertyDTO         资产DTO
         * @param rateNHoursManualDTO 更新的房价信息
         * @param updateUserId        更新人
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2024/3/28
         */
        private void manualEditPropertySelfHourPrice(PropertyDTO propertyDTO, RateNHoursManualDTO rateNHoursManualDTO, int updateUserId) {
            /**
             * final时，更新次日的早上5点
             */
            int hour = rateNHoursManualDTO.getHour();
            Integer targetDate = rateNHoursManualDTO.getTargetDate();
            if (rateNHoursManualDTO.getHour() == RevenueDailySummary.FINAL_HOUR) {
                //次日
                LocalDate nextLocalDate = DateTimeUtils.strToLocalDate(rateNHoursManualDTO.getTargetDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1);
                targetDate = Integer.parseInt(DateTimeUtils.localDateToStr(nextLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                hour = RevenueCons.FINAL_OF_NEXT_DAYS_HOUR;
            }

            //获取资产配置的房价含税标识
            Setting setting = settingService.getByPropertyId(propertyDTO.getId());
            Integer containTaxFlag = setting.getContainTaxFlag();
            //获取资产自身酒店
            Integer hotelId = propertyHotelService.getPropertySelfHotel(propertyDTO.getId()).getHotelId();
            //转化成币种id
            int currencyId = setting.getCurrencyId();

            //查询手动维护的房价
            HistoryRateNHoursManual historyRateNHoursManual = historyRateNHoursManualService.get(targetDate, hotelId, currencyId,
                    propertyDTO.getId(), hour, rateNHoursManualDTO.getRateType(), setting.getPos());

            if (Objects.nonNull(historyRateNHoursManual)) {
                //原操作日志
                String oriUpdateLog = historyRateNHoursManual.getUpdateLog();
                historyRateNHoursManualService.update(new LambdaUpdateWrapper<HistoryRateNHoursManual>()
                        //根据含税标识，设置房价
                        .set(containTaxFlag == Setting.BEFORE_TAX_RATE, HistoryRateNHoursManual::getRate, rateNHoursManualDTO.getUpdatePrice())
                        .set(containTaxFlag == Setting.AFTER_TAX_RATE, HistoryRateNHoursManual::getAfterTaxRate, rateNHoursManualDTO.getUpdatePrice())
                        .set(HistoryRateNHoursManual::getPlatformId, rateNHoursManualDTO.getPlatformId())
                        .set(HistoryRateNHoursManual::getUpdateLog, updateLogUtil.append(oriUpdateLog, updateUserId))
                        .eq(HistoryRateNHoursManual::getId, historyRateNHoursManual.getId()));
            } else {
                HistoryRateNHoursManual insertEntity = new HistoryRateNHoursManual();
                insertEntity.setTenantId(propertyDTO.getTenantId()).setPropertyId(propertyDTO.getId())
                        .setHotelId(hotelId).setCurrencyId(currencyId).setTargetDate(targetDate).setHour(hour)
                        .setPlatformId(rateNHoursManualDTO.getPlatformId()).setRateType(rateNHoursManualDTO.getRateType())
                        .setPos(setting.getPos())
                        .setUpdateLog(updateLogUtil.get(updateUserId));
                //根据含税标识，设置房价
                if (containTaxFlag == Setting.BEFORE_TAX_RATE) {
                    insertEntity.setRate(rateNHoursManualDTO.getUpdatePrice());
                } else {
                    insertEntity.setAfterTaxRate(rateNHoursManualDTO.getUpdatePrice());
                }
                historyRateNHoursManualService.save(insertEntity);
            }
        }

        /**
         * 设置Ta
         *
         * @param dailySummaryVO     Daily Summary VO
         * @param manualDailySummary 手动维护的Daily Summary
         * @param autoDailySummaryTa 自动导入的Ta
         * @return void
         * @author lichen
         * @date 2024/3/28
         */
        private void packageTa(DailySummaryVO dailySummaryVO, RevenueDailySummaryManual manualDailySummary, RevenueDailySummaryTa autoDailySummaryTa) {
            //优先手动维护
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(manualDailySummary.getTa())) {
                dailySummaryVO.setTa(manualDailySummary.getTa());
                //获取最后一次操作日志
                UpdateLog lastUpdateLog = updateLogUtil.getLast(manualDailySummary.getTaUpdateLog());
                if (Objects.nonNull(lastUpdateLog)) {
                    dailySummaryVO.setTaUpdateId(lastUpdateLog.getUid());
                    dailySummaryVO.setTaUpdateTime(lastUpdateLog.getTimestamp());
                }
            } else {//自动导入
                if (Objects.nonNull(autoDailySummaryTa)) {
                    dailySummaryVO.setTa(autoDailySummaryTa.getRoomDay());
                    //获取最后一次操作日志
                    UpdateLog lastUpdateLog = updateLogUtil.getLast(autoDailySummaryTa.getUpdateLog());
                    if (Objects.nonNull(lastUpdateLog)) {
                        dailySummaryVO.setTaUpdateId(lastUpdateLog.getUid());
                        dailySummaryVO.setTaUpdateTime(lastUpdateLog.getTimestamp());
                    }
                }
            }
        }

        /**
         * 设置Daily Summary相关的字段值
         *
         * @param dailySummaryVO     Daily Summary VO
         * @param manualDailySummary 手动维护的Daily Summary
         * @param autoDailySummary   自动导入的Daily Summary
         * @param fieldName          设置的字段名称
         * @return void
         * @author lichen
         * @date 2025/1/9
         */
        private void packageDailySummaryFieldValue(DailySummaryVO dailySummaryVO, RevenueDailySummaryManual manualDailySummary,
                                                   RevenueDailySummary autoDailySummary, String fieldName) {
            //RevenueDailySummaryManual field 的Get方法
            Method getMethodByManualDailySummary = getGetMethod(RevenueDailySummaryManual.class, fieldName);
            //RevenueDailySummaryManual field 日志的Get方法
            Method getMethodOfLogByManualDailySummary = getGetMethod(RevenueDailySummaryManual.class, fieldName + "UpdateLog");
            //DailySummaryVO field 的Set方法
            Method setMethod = getSetMethod(DailySummaryVO.class, fieldName);
            //DailySummaryVO field 更新人的Set方法
            Method setMethodByUpdateId = getSetMethod(DailySummaryVO.class, fieldName + "UpdateId");
            Method setMethodByUpdateTime = getSetMethod(DailySummaryVO.class, fieldName + "UpdateTime");

            //优先手动维护
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(ReflectionUtils.invokeMethod(getMethodByManualDailySummary, manualDailySummary))) {
                //获取手动维护的数值
                Object fieldValueObjByManual = ReflectionUtils.invokeMethod(getMethodByManualDailySummary, manualDailySummary);

                //把field value设置到vo里
                ReflectionUtils.invokeMethod(setMethod, dailySummaryVO, fieldValueObjByManual);
                //获取最后一次操作日志
                Object logByManual = ReflectionUtils.invokeMethod(getMethodOfLogByManualDailySummary, manualDailySummary);
                if (Objects.nonNull(logByManual)) {
                    UpdateLog lastUpdateLog = updateLogUtil.getLast(logByManual.toString());
                    //设置更新人
                    ReflectionUtils.invokeMethod(setMethodByUpdateId, dailySummaryVO, lastUpdateLog.getUid());
                    //设置更新时间
                    ReflectionUtils.invokeMethod(setMethodByUpdateTime, dailySummaryVO, lastUpdateLog.getTimestamp());
                }
            } else {
                if (Objects.nonNull(autoDailySummary)) {
                    //RevenueDailySummary field的Get方法
                    Method getMethodByDailySummary = getGetMethod(RevenueDailySummary.class, fieldName);

                    //获取自动下发的属性值
                    Object fieldValueObjByAuto = ReflectionUtils.invokeMethod(getMethodByDailySummary, autoDailySummary);
                    //把field value设置到vo里
                    ReflectionUtils.invokeMethod(setMethod, dailySummaryVO, fieldValueObjByAuto);
                    //设置更新人
                    ReflectionUtils.invokeMethod(setMethodByUpdateId, dailySummaryVO, 0);
                    //设置更新时间
                    ReflectionUtils.invokeMethod(setMethodByUpdateTime, dailySummaryVO,
                            ReflectionUtils.invokeMethod(getGetMethod(RevenueDailySummary.class, "syncTime"), autoDailySummary));
                }
            }
        }

        /**
         * 设置Daily Summary ManagerFlash报告中相关的字段值
         *
         * @param dailySummaryVO                      Daily Summary VO
         * @param manualDailySummary                  手动维护的Daily Summary
         * @param autoRevenueDailySummaryManagerFlash 从Manager Flash报告自动导入的字段
         * @param fieldName                           设置的字段名称
         * @return void
         * @author lichen
         * @date 2025/1/9
         */
        private void packageDailySummaryManagerFlashFieldValue(DailySummaryVO dailySummaryVO, RevenueDailySummaryManual manualDailySummary,
                                                               RevenueDailySummaryManagerFlash autoRevenueDailySummaryManagerFlash, String fieldName) {
            //RevenueDailySummaryManual field 的Get方法
            Method getMethodByManualDailySummary = getGetMethod(RevenueDailySummaryManual.class, fieldName);
            //RevenueDailySummaryManual field 日志的Get方法
            Method getMethodOfLogByManualDailySummary = getGetMethod(RevenueDailySummaryManual.class, fieldName + "UpdateLog");
            //DailySummaryVO field 的Set方法
            Method setMethod = getSetMethod(DailySummaryVO.class, fieldName);
            //DailySummaryVO field 更新人的Set方法
            Method setMethodByUpdateId = getSetMethod(DailySummaryVO.class, fieldName + "UpdateId");
            Method setMethodByUpdateTime = getSetMethod(DailySummaryVO.class, fieldName + "UpdateTime");

            //优先手动维护
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(ReflectionUtils.invokeMethod(getMethodByManualDailySummary, manualDailySummary))) {
                Object fieldValueObjByManual = ReflectionUtils.invokeMethod(getMethodByManualDailySummary, manualDailySummary);
                //把field value设置到vo里
                ReflectionUtils.invokeMethod(setMethod, dailySummaryVO, fieldValueObjByManual);
                //获取最后一次操作日志
                Object logByManual = ReflectionUtils.invokeMethod(getMethodOfLogByManualDailySummary, manualDailySummary);
                if (Objects.nonNull(logByManual)) {
                    UpdateLog lastUpdateLog = updateLogUtil.getLast(logByManual.toString());
                    //设置更新人
                    ReflectionUtils.invokeMethod(setMethodByUpdateId, dailySummaryVO, lastUpdateLog.getUid());
                    //设置更新时间
                    ReflectionUtils.invokeMethod(setMethodByUpdateTime, dailySummaryVO, lastUpdateLog.getTimestamp());
                }
            } else {
                if (Objects.nonNull(autoRevenueDailySummaryManagerFlash)) {
                    //RevenueDailySummaryManagerFlash field的Get方法
                    Method getMethodByDailySummaryManagerFlash = getGetMethod(RevenueDailySummaryManagerFlash.class, fieldName);

                    //获取自动下发的属性值
                    Object fieldValueObjByAuto = ReflectionUtils.invokeMethod(getMethodByDailySummaryManagerFlash, autoRevenueDailySummaryManagerFlash);
                    //把field value设置到vo里
                    ReflectionUtils.invokeMethod(setMethod, dailySummaryVO, fieldValueObjByAuto);
                    //设置更新人
                    ReflectionUtils.invokeMethod(setMethodByUpdateId, dailySummaryVO, 0);
                    //设置更新时间
                    ReflectionUtils.invokeMethod(setMethodByUpdateTime, dailySummaryVO,
                            ReflectionUtils.invokeMethod(getGetMethod(RevenueDailySummaryManagerFlash.class, "syncTime"), autoRevenueDailySummaryManagerFlash));
                }
            }
        }

        /**
         * 设置MEPS Group Total
         *
         * @param dailySummaryVO              Daily Summary VO
         * @param manualDailySummary          手动维护的Daily Summary
         * @param autoRevenueDailySummaryMeps 自动导入的MEPS Group Total
         * @return void
         * @author lichen
         * @date 2025/1/10
         */
        private void packageMepsGroupTotal(DailySummaryVO dailySummaryVO, RevenueDailySummaryManual manualDailySummary, DailySummaryMEPSDTO autoRevenueDailySummaryMeps) {
            //优先手动维护
            if (Objects.nonNull(manualDailySummary) && Objects.nonNull(manualDailySummary.getMepsGroupTotal())) {
                dailySummaryVO.setMepsGroupTotal(manualDailySummary.getMepsGroupTotal());
                //获取最后一次操作日志
                UpdateLog lastUpdateLog = updateLogUtil.getLast(manualDailySummary.getMepsGroupTotalUpdateLog());
                if (Objects.nonNull(lastUpdateLog)) {
                    dailySummaryVO.setMepsGroupTotalUpdateId(lastUpdateLog.getUid());
                    dailySummaryVO.setMepsGroupTotalUpdateTime(lastUpdateLog.getTimestamp());
                }
            } else {//自动导入
                if (Objects.nonNull(autoRevenueDailySummaryMeps)) {
                    dailySummaryVO.setMepsGroupTotal(autoRevenueDailySummaryMeps.getMeps());
                    dailySummaryVO.setMepsGroupTotalUpdateId(0);
                    dailySummaryVO.setMepsGroupTotalUpdateTime(autoRevenueDailySummaryMeps.getSyncTime());
                }
            }
        }

        /**
         * 获取指定类的get方法
         *
         * @param clazz 类
         * @param field 属性名称
         * @return java.lang.reflect.Method
         * @author lichen
         * @date 2025/01/09
         */
        static Method getGetMethod(Class<?> clazz, String field) {
            String getMethodName = "get" + StringUtils.capitalize(field);
            return ReflectionUtils.findMethod(clazz, getMethodName);
        }

        /**
         * 获取指定类的set方法
         *
         * @param clazz 类
         * @param field 属性名称
         * @return java.lang.reflect.Method
         * @author lichen
         * @date 2025/01/09
         */
        static Method getSetMethod(Class<?> clazz, String field) {
            String setMethodName = "set" + StringUtils.capitalize(field);
            //field的类型
            Class<?> clazzByField = ReflectionUtils.findField(clazz, field).getType();
            return ReflectionUtils.findMethod(clazz, setMethodName, clazzByField);
        }

        /**
         * 计算OOO房间数
         *
         * @param dailySummaryBO Daily Summary BO
         * @return OOO房间数
         * @author yyt
         * @date 2024/10/18
         */
        @Override
        public Integer calculateOooRooms(DailySummaryBO dailySummaryBO) {
            // OOO 房间数 = total rooms - sold rooms - available rooms - comp rooms
            Integer totalRooms = dailySummaryBO.getTotalRooms();
            if (Objects.isNull(totalRooms)) {
                return 0;
            }
            int oooRooms = totalRooms;
            Integer roomsSold = dailySummaryBO.getTotalOcc();
            if (Objects.nonNull(roomsSold)) {
                oooRooms -= roomsSold;
            }
            Integer availableRooms = dailySummaryBO.getRoomsAvailable();
            if (Objects.nonNull(availableRooms)) {
                oooRooms -= availableRooms;
            }
            Integer compRooms = dailySummaryBO.getCompRooms();
            if (Objects.nonNull(compRooms)) {
                oooRooms -= compRooms;
            }
            return Math.max(oooRooms, 0);
        }

        /**
         * 计算 transient 房间数
         *
         * @param dailySummaryBO Daily Summary BO
         * @return transient 房间数
         * @author yyt
         * @date 2026/3/4
         */
        @Override
        public Integer calculateTransientRooms(DailySummaryBO dailySummaryBO) {
            // transient rooms =  sold rooms - group rooms
            Integer roomsSold = dailySummaryBO.getTotalOcc();
            if (Objects.isNull(roomsSold)) return 0;

            int transientRooms = roomsSold;
            Integer groupRooms = dailySummaryBO.getGroupRooms();
            if (Objects.nonNull(groupRooms)) {
                transientRooms -= groupRooms;
            }

            return Math.max(transientRooms, 0);
        }

        /**
         * 计算 total room sold
         *
         * @param dailySummaryBO Daily Summary BO
         * @return total room sold
         * @author yyt
         * @date 2026/3/4
         */
        @Override
        public Integer calculateTotalRoomsSold(DailySummaryBO dailySummaryBO) {
            // total room sold =  transient rooms + group rooms
            int totalRoomsSold = 0;
            Integer transientRooms = dailySummaryBO.getTransientRooms();
            if (Objects.nonNull(transientRooms)) {
                totalRoomsSold += transientRooms;
            }
            Integer groupRooms = dailySummaryBO.getGroupRooms();
            if (Objects.nonNull(groupRooms)) {
                totalRoomsSold += groupRooms;
            }
            return Math.max(totalRoomsSold, 0);
        }

    }