# GmWeeklyReportController.java

    package cloud.wconnect.workperformance.controller.gmwr;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.workperformance.cons.gmwr.GmwrErrorI18nConstant;
    import cloud.wconnect.workperformance.cons.gmwr.GmwrHotelKpiCategoryEnum;
    import cloud.wconnect.workperformance.cons.gmwr.GmwrStatusEnum;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiCons;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrHotelKpi;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrWeeks;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.GmwrImportReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.GmwrWeeksDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.comment.GmwrCommentActionPlansDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.comment.GmwrCommentSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.hotelkpi.GmwrHotelKpiItemDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.hotelkpi.note.GmwrHotelKpiNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.hotelkpi.note.GmwrHotelKpiNoteMetricsDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.hotelkpi.note.GmwrHotelKpiSyncDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.GmwrMonthEndDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.GmwrMonthEndNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.GmwrMonthEndSaveBudgetDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.GmwrMonthEndSaveOthersDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthlyotbpace.GmwrMonthlyOtbPaceDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthlyotbpace.note.GmwrMonthlyOtbPaceNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthlyotbpace.note.GmwrMonthlyOtbPaceNoteDateDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthlyotbpace.save.GmwrMonthlyOtbPaceSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthlyotbpace.save.GmwrMonthlyOtbPaceValueSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrSelloutEfficiencyNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrSelloutEfficiencyPackageDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.strdata.GmwrStrDataDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.strdata.note.GmwrStrDataNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.strdata.note.GmwrStrDataNoteDateDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.strdata.save.GmwrStrDataSaveDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.internal.metrics.HotelKpiMetricsConfigInternalDTO;
    import cloud.wconnect.workperformance.pojo.query.gmwr.GmwrWeeksQuery;
    import cloud.wconnect.workperformance.service.gmwr.*;
    import cloud.wconnect.workperformance.service.gmwr.str.GmwrStrWeekParseService;
    import cloud.wconnect.workperformance.service.hotelkpi.internal.IHotelKpiInternalService;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

    import java.time.LocalDate;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.IntStream;

    /**
     * Controller: GM Weekly Report
     *
     * @author zc
     * @date 2024/10/15
     */
    @Validated
    @RestController
    @RequestMapping("/gm-weekly-report")
    public class GmWeeklyReportController {
        @Resource
        private IGmwrWeeksService weeksService;
        @Resource
        private IGmwrSendNotifyService sendNotification;
        @Resource
        private IGmwrMonthEndService monthEndService;
        @Resource
        private IGmwrMonthEndNoteService monthEndNoteService;
        @Resource
        private IGmwrMonthlyOtbPaceService monthlyOtbPaceService;
        @Resource
        private IGmwrMonthlyOtbPaceNoteService monthlyOtbPaceNoteService;
        @Resource
        private IGmwrStrDataService strDataService;
        @Resource
        private IGmwrStrDataNoteService strDataNoteService;
        @Resource
        private IGmwrHotelKpiService hotelKpiService;
        @Resource
        private IGmwrHotelKpiNoteService hotelKpiNoteService;
        @Resource
        private IHotelKpiInternalService hotelKpiInternalService;
        @Resource
        private IGmwrCommentsActionPlansService commentsActionPlansService;
        @Resource
        private IGmwrSelloutEfficiencyService selloutEfficiencyService;
        @Resource
        private IGmwrSelloutEfficiencyNoteService selloutEfficiencyNoteService;
        @Resource
        private GmwrStrWeekParseService strWeekParseService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 周报数据列表
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return GmwrWeeksDTO 集合
         * @author zc
         * @date 2024/10/15
         */
        @ApiId(1273)
        @GetMapping
        public ListPagedDTO<GmwrWeeksDTO> list(@RequestParam String property, @Valid GmwrWeeksQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            return weeksService.list(propertyId, query);
        }

        /**
         * 获取周报数据
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrMonthEndDTO
         * @author zc
         * @date 2024/10/16
         */
        @ApiId(1296)
        @GetMapping("/{id:\\d+}")
        public GmwrWeeksDTO get(@RequestParam String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, id);
            return beanMapper.map(gmwrWeeks, GmwrWeeksDTO.class, dto -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                dto.setIndexOfMonth(DateTimeUtils.getWeekIndexOfMonth(localDate));
            });
        }

        /**
         * 变更状态
         *
         * @param property 资产标识
         * @param id       周报ID
         * @return empty map
         * @author zc
         * @date 2024/10/15
         */
        @ApiId(1274)
        @PutMapping("/{id:\\d+}/status")
        public Map<Object, Object> changeStatus(@RequestParam String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, id);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            int changeStatus = getChangeStatus(gmwrWeeks);
            // 变更状态
            weeksService.changeStatus(gmwrWeeks, sessionContext.getLoginUserId(), changeStatus);
            gmwrWeeks.setStatus(changeStatus);
            // 发送通知处理周报数据
            sendNotification.sendNotificationForComplete(propertyDTO, gmwrWeeks);
            return Collections.emptyMap();
        }

        /**
         * 获取状态变更值
         *
         * @param gmwrWeeks 周报数据
         * @return 状态
         * @author zc
         * @date 2024/10/21
         */
        private int getChangeStatus(GmwrWeeks gmwrWeeks) {
            if (gmwrWeeks.getStatus() == GmwrStatusEnum.DRAFT.getStatus()) {
                return GmwrStatusEnum.COMPLETE.getStatus();
            }
            throw new BadRequestException(GmwrErrorI18nConstant.ALREADY_COMPLETED,
                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                    "The operation can only be performed when the status is equal to 1:draft");
        }

        /**
         * 获取 Month End
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrMonthEndDTO
         * @author zc
         * @date 2024/10/15
         */
        @ApiId(1275)
        @GetMapping("/{id:\\d+}/month-end")
        public GmwrMonthEndDTO getMonthEnd(@RequestParam String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, id);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            return monthEndService.get(propertyDTO, gmwrWeeks);
        }

        /**
         * 保存 Month End Note
         *
         * @param property    资产标识
         * @param id          报告 id
         * @param noteDTOList 备注集合
         * @return empty map
         * @author zc
         * @date 2024/11/2
         */
        @ApiId(1300)
        @PostMapping("/{id:\\d+}/month-end/note")
        public Map<Object, Object> saveMonthEndNote(@RequestParam String property, @PathVariable int id,
                                                    @RequestBody List<@Valid GmwrMonthEndNoteDTO> noteDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(noteDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("noteList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The noteDTOList cannot be empty");
            }
            // 校验数据分类不能重复
            if (noteDTOList.stream().map(GmwrMonthEndNoteDTO::getCategory).distinct().count() != noteDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The category cannot be repeated");
            }

            // 保存备注
            monthEndNoteService.save(propertyId, id, noteDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 保存 Month End Budget
         *
         * @param property      资产标识
         * @param id            周报 id
         * @param budgetDTOList 数据集合
         * @return empty map
         * @author zc
         * @date 2024/10/15
         */
        @ApiId(1277)
        @PostMapping("/{id:\\d+}/month-end/budget")
        public Map<Object, Object> saveMonthEndBudget(@RequestParam String property, @PathVariable int id,
                                                      @RequestBody List<@Valid GmwrMonthEndSaveBudgetDTO> budgetDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(budgetDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("budgetList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The budgetDTOList cannot be empty");
            }
            // 校验数据分类不能重复
            if (budgetDTOList.stream().map(GmwrMonthEndSaveBudgetDTO::getCategory).distinct().count() != budgetDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The category cannot be repeated");
            }

            // 保存数据
            monthEndService.saveBudget(propertyId, id, budgetDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 保存 Month End Current Forecast Others
         *
         * @param property  资产标识
         * @param id        周报 id
         * @param othersDTO Others 数据
         * @return empty map
         * @author zc
         * @date 2025/4/25
         */
        @ApiId(1432)
        @PostMapping("/{id:\\d+}/month-end/current-forecast-others")
        public Map<Object, Object> saveMonthEndCurrentForecastOthers(@RequestParam String property, @PathVariable int id,
                                                                     @RequestBody @Valid GmwrMonthEndSaveOthersDTO othersDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 保存数据
            monthEndService.saveCurrentForecastOthers(propertyId, id, othersDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取 Monthly OTB Pace
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrMonthlyOtbPaceListDTO 集合
         * @author zc
         * @date 2024/10/16
         */
        @ApiId(1278)
        @GetMapping("/{id:\\d+}/monthly-otb-pace")
        public List<GmwrMonthlyOtbPaceDTO> getMonthlyOtbPace(@RequestParam String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, id);

            return monthlyOtbPaceService.get(propertyId, gmwrWeeks);
        }

        /**
         * 保存 Monthly OTB Pace
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param saveDTOList 保存的 Monthly OTB Pace 数据集合
         * @return empty map
         * @author zc
         * @date 2025/5/16
         */
        @ApiId(1518)
        @PostMapping("/{id:\\d+}/monthly-otb-pace")
        public Map<Object, Object> saveMonthlyOtbPace(@RequestParam String property, @PathVariable int id,
                                                      @RequestBody List<@Valid GmwrMonthlyOtbPaceSaveDTO> saveDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            GmwrWeeks gmwrWeeks = draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(saveDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("monthlyOtbPaceList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The saveMonthlyOtbPaceList cannot be empty");
            }
            // 校验日期是否重复
            if (saveDTOList.stream().map(GmwrMonthlyOtbPaceSaveDTO::getMonth).distinct().count() != saveDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.MONTH),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The month cannot be repeated");
            }
            // 校验分类是否重复
            saveDTOList.forEach(otbPaceDTO -> {
                if (otbPaceDTO.getList().stream().map(GmwrMonthlyOtbPaceValueSaveDTO::getCategory)
                        .distinct().count() != otbPaceDTO.getList().size()) {
                    throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The category cannot be repeated");
                }
            });

            LocalDate localDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 近三个月日期
            List<Long> targetDateList = IntStream.range(0, 3).mapToObj(i ->
                    Long.parseLong(DateTimeUtils.localDateToStr(localDate.plusMonths(i),
                            BaseConstant.DATE_FORMAT_YYYYMM))).toList();
            // 校验日期是否符合周报日期的近三个月
            for (GmwrMonthlyOtbPaceSaveDTO otbPaceDTO : saveDTOList) {
                if (!targetDateList.contains(otbPaceDTO.getMonth())) {
                    throw new BadRequestException(GmwrErrorI18nConstant.MUST_NEAR_THREE_MONTHS,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The saved data month must be nearly 3 months");
                }
            }
            // 保存数据
            monthlyOtbPaceService.save(propertyId, gmwrWeeks, saveDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 保存 Monthly OTB Pace Note
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param noteDTOList 备注数据
         * @return empty map
         * @author zc
         * @date 2024/11/2
         */
        @ApiId(1301)
        @PostMapping("/{id:\\d+}/monthly-otb-pace/note")
        public Map<Object, Object> saveMonthlyOtbPaceNote(@RequestParam String property, @PathVariable int id,
                                                          @RequestBody List<@Valid GmwrMonthlyOtbPaceNoteDateDTO> noteDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            GmwrWeeks gmwrWeeks = draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(noteDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("noteList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The noteDTOList cannot be empty");
            }
            // 校验日期是否重复
            if (noteDTOList.stream().map(GmwrMonthlyOtbPaceNoteDateDTO::getMonth).distinct().count() != noteDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.MONTH),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The month cannot be repeated");
            }
            // 校验分类是否重复
            noteDTOList.forEach(noteDTO -> {
                if (noteDTO.getList().stream().map(GmwrMonthlyOtbPaceNoteDTO::getCategory)
                        .distinct().count() != noteDTO.getList().size()) {
                    throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The category cannot be repeated");
                }
            });

            LocalDate localDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 近三个月日期
            List<Long> targetDateList = IntStream.range(0, 3).mapToObj(i ->
                    Long.parseLong(DateTimeUtils.localDateToStr(localDate.plusMonths(i),
                            BaseConstant.DATE_FORMAT_YYYYMM))).toList();
            // 校验日期是否符合周报日期的近三个月
            for (GmwrMonthlyOtbPaceNoteDateDTO noteDTO : noteDTOList) {
                if (!targetDateList.contains(noteDTO.getMonth())) {
                    throw new BadRequestException(GmwrErrorI18nConstant.MUST_NEAR_THREE_MONTHS,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The saved data month must be nearly 3 months");
                }
            }
            // 保存备注
            monthlyOtbPaceNoteService.save(propertyId, id, noteDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取 STR Data
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrStrDataDTO
         * @author zc
         * @date 2024/10/18
         */
        @ApiId(1279)
        @GetMapping("/{id:\\d+}/str-data")
        public GmwrStrDataDTO getStrData(@RequestParam String property, @PathVariable int id) {
            int tenantId = sessionContext.getTenantSymbolBO().getId();
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            checkExistAndBelong(propertyId, id);
            return strDataService.get(tenantId, propertyId, id);
        }

        /**
         * 保存 STR Data
         *
         * @param property 资产标识
         * @param id       周报 id
         * @param saveDTO  保存数据
         * @return empty map
         * @author zc
         * @date 2025/1/21
         */
        @ApiId(1331)
        @PostMapping("/{id:\\d+}/str-data")
        public Map<Objects, Objects> saveStrData(@RequestParam String property, @PathVariable int id,
                                                 @RequestBody @Valid GmwrStrDataSaveDTO saveDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            GmwrWeeks gmwrWeeks = draftStatusPreCheck(propertyId, id);
            strDataService.save(propertyId, gmwrWeeks, sessionContext.getLoginUserId(), saveDTO);
            return Collections.emptyMap();
        }

        /**
         * 保存 STR Data Note
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param noteDTOList 备注数据
         * @return empty map
         * @author zc
         * @date 2024/11/5
         */
        @ApiId(1302)
        @PostMapping("/{id:\\d+}/str-data/note")
        public Map<Object, Object> saveStrDataNote(@RequestParam String property, @PathVariable int id,
                                                   @RequestBody List<@Valid GmwrStrDataNoteDateDTO> noteDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(noteDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("noteList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The noteDTOList cannot be empty");
            }
            // 校验日期分类是否重复
            if (noteDTOList.stream().map(GmwrStrDataNoteDateDTO::getCategory).distinct().count() != noteDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The date category cannot be repeated");
            }
            // 校验分类是否重复
            noteDTOList.forEach(noteDTO -> {
                if (noteDTO.getList().stream().map(GmwrStrDataNoteDTO::getCategory).distinct().count() != noteDTO.getList().size()) {
                    throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The category cannot be repeated");
                }
            });
            strDataNoteService.save(propertyId, id, noteDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 导入 Str 报告
         *
         * @param property  资产
         * @param id        周报 id
         * @param importDTO 导入报告 DTO
         * @return empty map
         * @author zc
         * @date 2024/11/6
         */
        @ApiId(1299)
        @PostMapping("/{id:\\d+}/str-data/report")
        public Map<Object, Object> importStrReport(@RequestParam String property, @PathVariable int id,
                                                   @Valid GmwrImportReportDTO importDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            // Draft 状态下操作的前置检查
            GmwrWeeks gmwrWeeks = draftStatusPreCheck(propertyId, id);

            // 校验文件是否合法
            strWeekParseService.checkFile(importDTO.getFile());
            // 保存数据为处理中
            int strDataId = strDataService.saveInProgress(propertyId, gmwrWeeks);
            // 处理数据
            strWeekParseService.handleData(propertyDTO, importDTO.getFile(), strDataId, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 获取 Hotel KPI
         *
         * @param property 资产标识
         * @param id       周报 id
         * @param category Hotel KPI 分类
         * @return GmwrHotelKpiItemDTO 集合
         * @author zc
         * @date 2024/12/30
         */
        @ApiId(1282)
        @GetMapping("/{id:\\d+}/hotel-kpi")
        public List<GmwrHotelKpiItemDTO> listHotelKpi(@RequestParam String property, @PathVariable int id, int category) {
            String categoryTitle = GmwrHotelKpiCategoryEnum.getTitle(category);
            if (categoryTitle.isEmpty()) {
                return Collections.emptyList();
            }
            int tenantId = sessionContext.getTenantSymbolBO().getId();
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            checkExistAndBelong(propertyId, id);

            return hotelKpiService.list(tenantId, id, categoryTitle);
        }

        /**
         * 保存 Hotel KPI 备注
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param saveNoteDTO 备注数据
         * @return empty map
         * @author zc
         * @date 2024/12/30
         */
        @ApiId(1303)
        @PostMapping("/{id:\\d+}/hotel-kpi/note")
        public Map<Object, Object> saveHotelKpiNote(@RequestParam String property, @PathVariable int id,
                                                    @RequestBody @Valid GmwrHotelKpiNoteDTO saveNoteDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 没有数据时，不能保存备注
            GmwrHotelKpi hotelKpi = hotelKpiService.getByReportId(id);
            if (Objects.isNull(hotelKpi)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The hotelKpi is not found. reportId:" + id);
            }
            // 校验分类是否重复
            List<Integer> saveMetricsTypeList = saveNoteDTO.getList().stream()
                    .map(GmwrHotelKpiNoteMetricsDTO::getCategory).distinct().toList();
            if (saveMetricsTypeList.size() != saveNoteDTO.getList().size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The category cannot be repeated");
            }
            // 校验分类是否存在
            List<Integer> metricsTypeList = hotelKpiInternalService.listAllMetrics(propertyId, HotelKpiCons.V1).stream()
                    .map(HotelKpiMetricsConfigInternalDTO::getMetricsType).toList();
            if (saveMetricsTypeList.stream().anyMatch(metricsType -> !metricsTypeList.contains(metricsType))) {
                throw new BadRequestException(GmwrErrorI18nConstant.HOTEL_KPI_METRICS_TYPE_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The metrics inconsistent with valid data");
            }
            hotelKpiNoteService.save(propertyId, id, saveNoteDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 同步 Hotel KPI
         *
         * @param property 资产标识
         * @param id       周报 id
         * @param syncDTO  同步内容
         * @return empty map
         * @author zc
         * @date 2025/4/25
         */
        @ApiId(1433)
        @PutMapping("/{id:\\d+}/hotel-kpi/synchronized")
        public Map<Object, Object> syncHotelKpi(@RequestParam String property, @PathVariable int id, @RequestBody @Valid GmwrHotelKpiSyncDTO syncDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            GmwrWeeks gmwrWeeks = draftStatusPreCheck(propertyId, id);
            hotelKpiService.sync(propertyId, gmwrWeeks, syncDTO);
            return Collections.emptyMap();
        }

        /**
         * 获取 Sellout Efficiency
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrSelloutEfficiencyDTO 集合
         * @author zc
         * @date 2024/10/21
         */
        @ApiId(1285)
        @GetMapping("/{id:\\d+}/sellout-efficiency")
        public GmwrSelloutEfficiencyPackageDTO getSelloutEfficiency(@RequestParam String property, @PathVariable int id) {
            int tenantId = sessionContext.getTenantSymbolBO().getId();
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, id);
            return selloutEfficiencyService.getDetail(tenantId, propertyId, gmwrWeeks);
        }

        /**
         * 保存 Sellout Efficiency 备注
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param noteDTOList 备注内容
         * @return empty map
         * @author zc
         * @date 2024/10/21
         */
        @ApiId(1304)
        @PostMapping("/{id:\\d+}/sellout-efficiency/note")
        public Map<Object, Object> saveSelloutEfficiencyNote(@RequestParam String property, @PathVariable int id,
                                                             @Valid @RequestBody List<GmwrSelloutEfficiencyNoteDTO> noteDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(noteDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("noteList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The noteDTOList cannot be empty");
            }
            // 校验数据分类不能重复
            if (noteDTOList.stream().map(GmwrSelloutEfficiencyNoteDTO::getCategory).distinct().count() != noteDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The category cannot be repeated");
            }
            // 保存备注
            selloutEfficiencyNoteService.save(propertyId, id, noteDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取 Comment Action Plans
         *
         * @param property 资产标识
         * @param id       周报 id
         * @return GmwrCommentActionPlansDTO 集合
         * @author zc
         * @date 2024/10/19
         */
        @ApiId(1288)
        @GetMapping("/{id:\\d+}/comments-action-plans")
        public List<GmwrCommentActionPlansDTO> getCommentActionPlans(@RequestParam String property, @PathVariable int id) {
            int tenantId = sessionContext.getTenantSymbolBO().getId();
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在和归属
            checkExistAndBelong(propertyId, id);
            return commentsActionPlansService.list(tenantId, propertyId, id);
        }

        /**
         * 保存 Comment Action Plans
         *
         * @param property    资产标识
         * @param id          周报 id
         * @param saveDTOList comment DTO
         * @return empty map
         * @author zc
         * @date 2024/10/19
         */
        @ApiId(1289)
        @PostMapping("/{id:\\d+}/comments-action-plans")
        public Map<Object, Object> saveCommentActionPlans(@RequestParam String property, @PathVariable int id,
                                                          @RequestBody List<@Valid GmwrCommentSaveDTO> saveDTOList) {
            int propertyId = sessionContext.getPropertyId(property);
            // Draft 状态下操作的前置检查
            draftStatusPreCheck(propertyId, id);

            // 校验数据不能为空
            if (CollectionUtils.isEmpty(saveDTOList)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("noteList"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The noteDTOList cannot be empty");
            }
            // 校验数据分类不能重复
            if (saveDTOList.stream().map(GmwrCommentSaveDTO::getCategory).distinct().count() != saveDTOList.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.CATEGORY),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The category cannot be repeated");
            }
            // 保存备注
            commentsActionPlansService.save(propertyId, id, saveDTOList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * Draft 状态下操作的前置检查
         *
         * @param propertyId 资产 id
         * @param reportId   周报 id
         * @author zc
         * @date 2025/1/15
         */
        private GmwrWeeks draftStatusPreCheck(int propertyId, int reportId) {
            GmwrWeeks gmwrWeeks = checkExistAndBelong(propertyId, reportId);
            // 校验数据状态
            if (gmwrWeeks.getStatus() != GmwrStatusEnum.DRAFT.getStatus()) {
                throw new BadRequestException(GmwrErrorI18nConstant.ONLY_DRAFT_STAGE_CAN_OPERATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The operation can only be performed when the status is equal to 1:draft");
            }
            return gmwrWeeks;
        }

        /**
         * 校验周报是否存在和归属
         *
         * @param propertyId 资产 id
         * @param reportId   周报 id
         * @return GmwrWeeks
         * @author zc
         * @date 2024/11/2
         */
        private GmwrWeeks checkExistAndBelong(int propertyId, int reportId) {
            // 校验是否存在
            GmwrWeeks gmwrWeeks = weeksService.checkExist(reportId);

            // 校验归属
            weeksService.checkBelong(propertyId, gmwrWeeks);

            return gmwrWeeks;
        }
    }