# HotelKpiStrWeekServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.MoudleEventSubjectEnum;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.sns.http.processor.Sender;
    import cloud.wconnect.workperformance.cons.HandleReportDataStatusCons;
    import cloud.wconnect.workperformance.cons.S3BucketFunctionCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.strdata.HotelKpiStrWeekDateEnum;
    import cloud.wconnect.workperformance.cons.hotelkpi.strdata.HotelKpiStrWeekEnum;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrStrData;
    import cloud.wconnect.workperformance.entity.hotelkpi.HotelKpiStrWeek;
    import cloud.wconnect.workperformance.mapper.hotelkpi.HotelKpiStrWeekMapper;
    import cloud.wconnect.workperformance.pojo.bo.hotelkpi.event.HotelKpiStrWeekEventBO;
    import cloud.wconnect.workperformance.pojo.bo.hotelkpi.str.week.HotelKpiStrWeekBO;
    import cloud.wconnect.workperformance.pojo.bo.hotelkpi.str.week.HotelKpiStrWeekCategoryBO;
    import cloud.wconnect.workperformance.pojo.bo.hotelkpi.str.week.HotelKpiStrWeekDetailBO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.HotelKpiStrWeekDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.HotelKpiStrWeekDateDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.HotelKpiStrWeekValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.basic.HotelKpiStrWeekBasicDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.basic.HotelKpiStrWeekBasicValueDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.crawl.HotelKpiStrWeekCrawlDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.save.HotelKpiStrWeekCategorySaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.save.HotelKpiStrWeekDetailSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.save.HotelKpiStrWeekSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.strdata.week.save.HotelKpiStrWeekValueSaveDTO;
    import cloud.wconnect.workperformance.service.AmazonS3Service;
    import cloud.wconnect.workperformance.service.gmwr.internal.IGmwrInternalService;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiStrWeekService;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiWeekStartDayService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;

    import java.io.IOException;
    import java.io.InputStream;
    import java.math.RoundingMode;
    import java.time.DayOfWeek;
    import java.time.LocalDate;
    import java.time.temporal.TemporalAdjusters;
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Hotel KPI STR Data 服务实现类
     * </p>
     *
     * @author zc
     * @since 2025-08-21
     */
    @Slf4j
    @Service
    @DS("work_performance")
    public class HotelKpiStrWeekServiceImpl extends ServiceImpl<HotelKpiStrWeekMapper, HotelKpiStrWeek> implements IHotelKpiStrWeekService {

        @Resource
        private IHotelKpiWeekStartDayService weekStartDayService;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private IGmwrInternalService gmwrInternalService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private AmazonS3Service amazonS3Service;
        @Resource
        @Qualifier("moduleEventSender")
        private Sender moduleEventSender;
        @Resource
        private Executor executor;
        @Resource
        private BeanMapper beanMapper;

        /**
         * 更新 STR Data 数据状态
         *
         * @param id     Str Data id
         * @param status 状态
         * @author zc
         * @date 2025/9/4
         */
        @Override
        public void updateStatus(int id, String status) {
            updateById(new HotelKpiStrWeek().setId(id).setStatus(status));
        }

        /**
         * 保存 STR Data 数据为处理中
         *
         * @param propertyId 资产 id
         * @param date       日期(yyyyMMdd)
         * @return Str Week Id
         * @author zc
         * @date 2025/9/4
         */
        @Override
        public int saveInProgress(int propertyId, long date) {
            HotelKpiStrWeek strWeek = getByDate(propertyId, date);

            if (Objects.isNull(strWeek)) {
                // 如果数据不存在则创建新对象
                strWeek = new HotelKpiStrWeek();
                strWeek.setPropertyId(propertyId);
                strWeek.setDate(date);

                // 计算报告日期
                LocalDate localDate = DateTimeUtils.strToLocalDate(String.valueOf(date), BaseConstant.DATE_FORMAT_YYYYMMDD);
                LocalDate reportLocalDate = localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
                long reportDate = Long.parseLong(DateTimeUtils.localDateToStr(reportLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                strWeek.setDataDate(reportDate);
                strWeek.setDetail(JsonUtils.convertObjListToString(objectMapper, HotelKpiStrWeekBasicDTO.generateInitBasicDTOList()));
            }
            strWeek.setStatus(HandleReportDataStatusCons.PROCESSING);

            baseMapper.insertOrUpdateByUnique(strWeek);

            return strWeek.getId();
        }

        /**
         * 获取 STR Data
         *
         * @param tenantId   租户 id
         * @param propertyId 资产 id
         * @param date       日期(yyyyMMdd)
         * @return HotelKpiStrWeekDTO
         * @author zc
         * @date 2025/8/21
         */
        @Override
        public HotelKpiStrWeekDTO get(int tenantId, int propertyId, long date) {
            LocalDate localDate = DateTimeUtils.strToLocalDate(String.valueOf(date),
                    BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate reportStartLocalDate = localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
            long reportWeekStartDate = Long.parseLong(DateTimeUtils.localDateToStr(reportStartLocalDate,
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            long reportEndDate = Long.parseLong(DateTimeUtils.localDateToStr(reportStartLocalDate.plusDays(6),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            long reportR28DStartDate = Long.parseLong(DateTimeUtils.localDateToStr(reportStartLocalDate.minusDays(21),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));

            HotelKpiStrWeekDTO strWeekDTO = new HotelKpiStrWeekDTO();
            HotelKpiStrWeek strWeek = getByDate(propertyId, date);
            if (Objects.isNull(strWeek)) {
                strWeekDTO.getList().forEach(item -> {
                    if (HotelKpiStrWeekDateEnum.WEEK.getCategory() == item.getCategory()) {
                        item.setStartDate(reportWeekStartDate);
                        item.setEndDate(reportEndDate);
                    } else {
                        item.setStartDate(reportR28DStartDate);
                        item.setEndDate(reportEndDate);
                    }
                });
                return strWeekDTO;
            }
            List<HotelKpiStrWeekBasicDTO> basicDTOList = JsonUtils.convertStringToList(objectMapper,
                    strWeek.getDetail(), HotelKpiStrWeekBasicDTO.class);

            // 获取用户名
            List<Integer> userIds = basicDTOList.stream().map(HotelKpiStrWeekBasicDTO::extractAllUserId)
                    .flatMap(Collection::stream).distinct().toList();
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            List<HotelKpiStrWeekDateDTO> strWeekDateDTOList = basicDTOList.stream().map(basicDTO -> {
                List<HotelKpiStrWeekValueDTO> valueDTOList = basicDTO.getList().stream().map(basicValueDTO ->
                        beanMapper.map(basicValueDTO, HotelKpiStrWeekValueDTO.class, dto -> {
                            dto.setCategory(basicValueDTO.getCategory());
                            dto.setCategoryName(HotelKpiStrWeekEnum.getCategoryName(basicValueDTO.getCategory()));
                            dto.getProperty().setUpdatedName(userId2Name.get(basicValueDTO.getProperty().getUpdatedBy()));
                            dto.getPropertyChg().setUpdatedName(userId2Name.get(basicValueDTO.getPropertyChg().getUpdatedBy()));
                            dto.getCompSet().setUpdatedName(userId2Name.get(basicValueDTO.getCompSet().getUpdatedBy()));
                            dto.getCompSetChg().setUpdatedName(userId2Name.get(basicValueDTO.getCompSetChg().getUpdatedBy()));
                            dto.getIndex().setUpdatedName(userId2Name.get(basicValueDTO.getIndex().getUpdatedBy()));
                            dto.getIndexChg().setUpdatedName(userId2Name.get(basicValueDTO.getIndexChg().getUpdatedBy()));
                        })
                ).toList();

                HotelKpiStrWeekDateDTO dataDateDTO = new HotelKpiStrWeekDateDTO();
                dataDateDTO.setCategory(basicDTO.getCategory());
                dataDateDTO.setCategoryName(HotelKpiStrWeekDateEnum.getCategoryName(basicDTO.getCategory()));
                dataDateDTO.setList(valueDTOList);
                if (basicDTO.getCategory() == HotelKpiStrWeekDateEnum.WEEK.getCategory()) {
                    dataDateDTO.setStartDate(reportWeekStartDate);
                    dataDateDTO.setEndDate(reportEndDate);
                } else {
                    dataDateDTO.setStartDate(reportR28DStartDate);
                    dataDateDTO.setEndDate(reportEndDate);
                }
                return dataDateDTO;
            }).toList();
            strWeekDTO.setList(strWeekDateDTOList);
            strWeekDTO.setReportUrl(strWeek.getFileUrl());
            strWeekDTO.setStatus(strWeek.getStatus());

            return strWeekDTO;
        }

        /**
         * 保存 STR Data
         *
         * @param propertyId 资产 id
         * @param operatorId 操作人 id
         * @param saveDTO    保存数据DTO
         * @author zc
         * @date 2025/8/21
         */
        @Override
        public void save(int propertyId, int operatorId, HotelKpiStrWeekSaveDTO saveDTO) {
            HotelKpiStrWeek strWeek = getByDate(propertyId, saveDTO.getDate());
            List<HotelKpiStrWeekBasicDTO> basicDTOList = Objects.isNull(strWeek) ?
                    HotelKpiStrWeekBasicDTO.generateInitBasicDTOList() :
                    JsonUtils.convertStringToList(objectMapper, strWeek.getDetail(), HotelKpiStrWeekBasicDTO.class);

            // 更新数据
            updateByManual(basicDTOList, saveDTO.getList(), operatorId);

            if (Objects.isNull(strWeek)) {
                // 如果数据不存在则创建新对象
                strWeek = new HotelKpiStrWeek();
                strWeek.setPropertyId(propertyId);
                strWeek.setDate(saveDTO.getDate());

                // 计算数据日期
                LocalDate localDate = DateTimeUtils.strToLocalDate(saveDTO.getDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                LocalDate reportLocalDate = localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
                long reportDate = Long.parseLong(DateTimeUtils.localDateToStr(reportLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                strWeek.setDataDate(reportDate);
            }
            strWeek.setDetail(JsonUtils.convertObjListToString(objectMapper, basicDTOList));
            strWeek.setStatus(HandleReportDataStatusCons.SUCCESS);

            baseMapper.insertOrUpdateByUnique(strWeek);

            // 指定日期的 Str week 数据发生变化时，发送 SNS 事件
            TransactionUtils.afterCommitAsyncExecute(executor, () ->
                    sendModuleEvent(propertyId, saveDTO.getDate())
            );
        }

        /**
         * 更新 BasicDTO 列表数据 by System
         *
         * @param basicDTOList 基础数据列表
         * @param boList       数据
         * @author zc
         * @date 2025/8/22
         */
        private void updateBySystem(List<HotelKpiStrWeekBasicDTO> basicDTOList, List<HotelKpiStrWeekCategoryBO> boList) {
            // 构建日期分类到保存数据的映射
            Map<Integer, HotelKpiStrWeekCategoryBO> category2BO = boList.stream()
                    .collect(Collectors.toMap(HotelKpiStrWeekCategoryBO::getCategory, Function.identity()));

            basicDTOList.forEach(basicDTO -> {
                HotelKpiStrWeekCategoryBO categoryBO = category2BO.get(basicDTO.getCategory());
                if (Objects.isNull(categoryBO)) {
                    return;
                }

                // 构建值分类到保存数据的映射
                Map<Integer, HotelKpiStrWeekDetailBO> category2DetailBO = categoryBO.getList().stream()
                        .collect(Collectors.toMap(HotelKpiStrWeekDetailBO::getCategory, Function.identity()));

                // 更新每个值
                basicDTO.getList().forEach(basicValueDTO -> {
                    HotelKpiStrWeekDetailBO detailBO = category2DetailBO.get(basicValueDTO.getCategory());
                    if (Objects.isNull(detailBO)) {
                        return;
                    }
                    // 更新各个字段的值
                    basicValueDTO.getProperty().setCrawl(detailBO.getProperty());
                    basicValueDTO.getPropertyChg().setCrawl(detailBO.getPropertyChg());
                    basicValueDTO.getCompSet().setCrawl(detailBO.getCompSet());
                    basicValueDTO.getCompSetChg().setCrawl(detailBO.getCompSetChg());
                    basicValueDTO.getIndex().setCrawl(detailBO.getIndex());
                    basicValueDTO.getIndexChg().setCrawl(detailBO.getIndexChg());
                });
            });
        }

        /**
         * 更新 BasicDTO 列表数据 by Manual
         *
         * @param basicDTOList 基础数据列表
         * @param saveDTOList  保存数据
         * @param operatorId   操作人 id
         * @author zc
         * @date 2025/8/22
         */
        private void updateByManual(List<HotelKpiStrWeekBasicDTO> basicDTOList, List<HotelKpiStrWeekCategorySaveDTO> saveDTOList, int operatorId) {
            Map<Integer, HotelKpiStrWeekCategorySaveDTO> category2SaveDTO = saveDTOList.stream()
                    .collect(Collectors.toMap(HotelKpiStrWeekCategorySaveDTO::getCategory, Function.identity()));

            long nowTimestamp = DateTimeUtils.nowTimestamp();
            basicDTOList.forEach(basicDTO -> {
                HotelKpiStrWeekCategorySaveDTO dateSaveDTO = category2SaveDTO.get(basicDTO.getCategory());
                if (Objects.isNull(dateSaveDTO)) {
                    return;
                }

                // 构建值分类到保存数据的映射
                Map<Integer, HotelKpiStrWeekDetailSaveDTO> category2ValueSaveDTO = dateSaveDTO.getList().stream()
                        .collect(Collectors.toMap(HotelKpiStrWeekDetailSaveDTO::getCategory, Function.identity()));

                // 更新每个值
                basicDTO.getList().forEach(basicValueDTO -> {
                    HotelKpiStrWeekDetailSaveDTO dataValueSaveDTO = category2ValueSaveDTO.get(basicValueDTO.getCategory());
                    if (Objects.isNull(dataValueSaveDTO)) {
                        return;
                    }

                    // 更新各个字段的值
                    setManualValue(basicValueDTO.getProperty(), dataValueSaveDTO.getProperty(),
                            operatorId, nowTimestamp);
                    setManualValue(basicValueDTO.getPropertyChg(), dataValueSaveDTO.getPropertyChg(),
                            operatorId, nowTimestamp);
                    setManualValue(basicValueDTO.getCompSet(), dataValueSaveDTO.getCompSet(),
                            operatorId, nowTimestamp);
                    setManualValue(basicValueDTO.getCompSetChg(), dataValueSaveDTO.getCompSetChg(),
                            operatorId, nowTimestamp);
                    setManualValue(basicValueDTO.getIndex(), dataValueSaveDTO.getIndex(),
                            operatorId, nowTimestamp);
                    setManualValue(basicValueDTO.getIndexChg(), dataValueSaveDTO.getIndexChg(),
                            operatorId, nowTimestamp);
                });
            });
        }

        /**
         * 设置人工录入值
         *
         * @param valueDetailDTO 值详情DTO
         * @param saveDTO        数值
         * @param operatorId     操作人 id
         * @param nowTimestamp   当前时间戳
         * @author zc
         * @date 2025/8/21
         */
        private void setManualValue(HotelKpiStrWeekBasicValueDetailDTO valueDetailDTO, HotelKpiStrWeekValueSaveDTO saveDTO,
                                    int operatorId, long nowTimestamp) {
            if (saveDTO.getUseSystem()) {
                valueDetailDTO.setManual(null);
                valueDetailDTO.setUpdatedBy(0);
                valueDetailDTO.setUpdatedTime(0L);
            } else {
                if (!NumberUtil.equals(valueDetailDTO.getManual(), saveDTO.getValue())) {
                    valueDetailDTO.setManual(saveDTO.getValue());
                    valueDetailDTO.setUpdatedBy(operatorId);
                    valueDetailDTO.setUpdatedTime(nowTimestamp);
                }
            }
        }

        /**
         * 根据日期获取 STR Week
         *
         * @param propertyId 资产 id
         * @param date       日期(yyyyMMdd)
         * @return HotelKpiStrWeek
         * @author zc
         * @date 2025/8/21
         */
        private HotelKpiStrWeek getByDate(int propertyId, long date) {
            return getOne(new LambdaQueryWrapper<HotelKpiStrWeek>()
                    .eq(HotelKpiStrWeek::getPropertyId, propertyId)
                    .eq(HotelKpiStrWeek::getDate, date));
        }

        /**
         * 保存 STR 周报数据
         *
         * @param propertyDTO 资产信息
         * @param bo          数据
         * @param path        文件路径
         * @author zc
         * @date 2025/8/22
         */
        @Override
        public void saveData(PropertyDTO propertyDTO, HotelKpiStrWeekBO bo, String path) {
            // 上传文件到 S3
            uploadReport(path);

            // 获取周开始日期
            DayOfWeek dayOfWeek = weekStartDayService.getWeekStartDay(propertyDTO.getId());
            LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getDataDate().toString(),
                    BaseConstant.DATE_FORMAT_YYYYMMDD).with(TemporalAdjusters.nextOrSame(dayOfWeek));
            // 设置报告所属周期
            long date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            HotelKpiStrWeek strWeek = getByDate(propertyDTO.getId(), date);
            List<HotelKpiStrWeekBasicDTO> basicDTOList = Objects.isNull(strWeek) ?
                    HotelKpiStrWeekBasicDTO.generateInitBasicDTOList() :
                    JsonUtils.convertStringToList(objectMapper, strWeek.getDetail(), HotelKpiStrWeekBasicDTO.class);

            // 更新数据
            updateBySystem(basicDTOList, bo.getList());

            // 如果数据不存在则创建新对象
            if (Objects.isNull(strWeek)) {
                strWeek = new HotelKpiStrWeek();
                strWeek.setPropertyId(propertyDTO.getId());
                strWeek.setDate(date);
                strWeek.setDataDate(bo.getDataDate());
            }
            strWeek.setFileUrl(path);
            strWeek.setStatus(HandleReportDataStatusCons.SUCCESS);
            strWeek.setDetail(JsonUtils.convertObjListToString(objectMapper, basicDTOList));

            baseMapper.insertOrUpdateByUnique(strWeek);
        }

        /**
         * 处理模块事件
         *
         * @param propertyDTO 资产信息
         * @param date        周期日期(yyyyMMdd)
         * @author zc
         * @date 2025/8/22
         */
        @Override
        public void handleModuleEvent(PropertyDTO propertyDTO, long date) {
            GmwrStrData strData = gmwrInternalService.getStrData(propertyDTO.getId(), date);
            if (Objects.isNull(strData)) {
                return;
            }
            HotelKpiStrWeek strWeek = new HotelKpiStrWeek()
                    .setPropertyId(propertyDTO.getId())
                    .setDate(date)
                    .setDataDate(strData.getDataDate())
                    .setFileUrl(strData.getFileUrl())
                    .setDetail(strData.getDetail())
                    .setStatus(HandleReportDataStatusCons.SUCCESS);
            baseMapper.insertOrUpdateByUnique(strWeek);
        }

        /**
         * 保存 STR Week 采集数据
         *
         * @param propertyDTO   资产信息
         * @param crawlValueDTO 数据
         * @author zc
         * @date 2025/9/18
         */
        @Override
        public void saveCrawl(PropertyDTO propertyDTO, HotelKpiStrWeekCrawlDTO crawlValueDTO) {
            // 格式化数据精度
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());
            crawlValueDTO.getList().forEach(item ->
                    item.getList().forEach(detail -> {
                        detail.setProperty(Objects.isNull(detail.getProperty()) ? null :
                                detail.getProperty().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        detail.setPropertyChg(Objects.isNull(detail.getPropertyChg()) ? null :
                                detail.getPropertyChg().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        detail.setCompSet(Objects.isNull(detail.getCompSet()) ? null :
                                detail.getCompSet().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        detail.setCompSetChg(Objects.isNull(detail.getCompSetChg()) ? null :
                                detail.getCompSetChg().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        detail.setIndex(Objects.isNull(detail.getIndex()) ? null :
                                detail.getIndex().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        detail.setIndexChg(Objects.isNull(detail.getIndexChg()) ? null :
                                detail.getIndexChg().setScale(BaseConstant.DATA_PRECISION, roundingMode));
                    })
            );
            HotelKpiStrWeekBO bo = beanMapper.map(crawlValueDTO, HotelKpiStrWeekBO.class);

            // 获取周开始日期
            DayOfWeek dayOfWeek = weekStartDayService.getWeekStartDay(propertyDTO.getId());
            LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getDataDate().toString(),
                    BaseConstant.DATE_FORMAT_YYYYMMDD).with(TemporalAdjusters.nextOrSame(dayOfWeek));
            // 设置报告所属周期
            long date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            HotelKpiStrWeek strWeek = getByDate(propertyDTO.getId(), date);
            List<HotelKpiStrWeekBasicDTO> basicDTOList = Objects.isNull(strWeek) ?
                    HotelKpiStrWeekBasicDTO.generateInitBasicDTOList() :
                    JsonUtils.convertStringToList(objectMapper, strWeek.getDetail(), HotelKpiStrWeekBasicDTO.class);

            // 更新数据
            updateBySystem(basicDTOList, bo.getList());

            // 如果数据不存在则创建新对象
            if (Objects.isNull(strWeek)) {
                strWeek = new HotelKpiStrWeek();
                strWeek.setPropertyId(propertyDTO.getId());
                strWeek.setDate(date);
                strWeek.setDataDate(bo.getDataDate());
            }
            strWeek.setStatus(HandleReportDataStatusCons.SUCCESS);
            strWeek.setDetail(JsonUtils.convertObjListToString(objectMapper, basicDTOList));

            baseMapper.insertOrUpdateByUnique(strWeek);

            // 指定日期的 Str week 数据发生变化时，发送 SNS 事件
            TransactionUtils.afterCommitAsyncExecute(executor, () ->
                    sendModuleEvent(propertyDTO.getId(), date)
            );
        }

        /**
         * 上传报告，用于点击指标下载报告
         *
         * @param fileUrl S3 路径
         * @author zc
         * @date 2025/8/22
         */
        private void uploadReport(String fileUrl) {
            if (StringUtils.isBlank(fileUrl)) {
                return;
            }
            try (InputStream in = amazonS3Service.getFile(fileUrl)) {
                amazonS3Service.uploadFile(S3BucketFunctionCons.HOTEL_KPI, in, fileUrl);
            } catch (IOException e) {
                log.error("HotelKpiStrWeekServiceImpl Upload Str report error. fileUrl: {}", fileUrl, e);
            }
        }

        /**
         * 发送模块事件
         *
         * @param propertyId 资产 id
         * @param date       周期日期(yyyyMMdd)
         * @author zc
         * @date 2025/8/23
         */
        private void sendModuleEvent(int propertyId, long date) {
            HotelKpiStrWeekEventBO strWeekEventBO = new HotelKpiStrWeekEventBO();
            strWeekEventBO.setPropertyId(propertyId);
            strWeekEventBO.setDate(date);
            moduleEventSender.publish(MoudleEventSubjectEnum.HOTEL_KPI_STR_WEEK.getSubject(),
                    JsonUtils.convertObjToString(objectMapper, strWeekEventBO));
        }

    }