# RateShopSchedulerController.java

    package cloud.wconnect.rateshop.controller.rateshop;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.SchedulerCons;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.CronRuleUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JobParameterUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.rateshop.cons.rateshop.RateShopCons;
    import cloud.wconnect.rateshop.cons.rateshop.RateShopFieldI18nConstant;
    import cloud.wconnect.rateshop.cons.rateshop.RateShopNotifyConfigTypeEnum;
    import cloud.wconnect.rateshop.entity.rateshop.RateShopScheduler;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.emailconfig.RateShopSchedulerDTO;
    import cloud.wconnect.rateshop.service.rateshop.IRateShopSchedulerService;
    import com.alibaba.fastjson.JSONObject;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    import jakarta.validation.Valid;

    import java.text.MessageFormat;
    import java.util.*;

    @RestController
    @RequestMapping("/rate-shop/scheduler")
    public class RateShopSchedulerController {

        @Autowired
        private IRateShopSchedulerService schedulerService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 保存调度任务
         *
         * @param property     资产标识
         * @param id           调度任务主键ID
         * @param schedulerDTO 调度任务DTO
         * @return java.util.Map<java.lang.String, java.lang.Integer>
         * @author lichen
         * @date 2024/3/15
         */
        @ApiId(1065)
        @PostMapping("{id}")
        public Map<String, String> save(String property, @PathVariable String id,
                                        @RequestBody @Valid RateShopSchedulerDTO schedulerDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            int loginUserId = sessionContext.getLoginUserId();
            //校验分类是否符合预期
            checkCategory(propertyDTO.getPmsType(), schedulerDTO.getCategory());

            // 检查 scheduler json格式是否合法
            CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());

            // 设置定时执行参数
            setJobParameters(schedulerDTO);

            schedulerDTO.setPropertyId(propertyDTO.getId());

            if (!id.equals("0")) {
                //校验调度任务是否存在
                RateShopScheduler oriSchedulerJob = schedulerService.checkExist(id);
                //校验是否归属于资产
                checkBelong(oriSchedulerJob, propertyDTO.getId());
                if (oriSchedulerJob.getStatus() == SchedulerCons.STATUS_DELETING) {
                    throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_UPDATE_DELETING,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "deleting SchedulerJob cannot update, SchedulerJobId=" + oriSchedulerJob.getId());
                }
                if (!oriSchedulerJob.getCategory().equals(schedulerDTO.getCategory())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("category"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "SchedulerJob category cannot update, SchedulerJobId=" + oriSchedulerJob.getId());
                }
                //修改
                schedulerDTO.setId(id);
                return Map.of("id", schedulerService.update(propertyDTO, oriSchedulerJob, schedulerDTO, loginUserId));
            } else {
                //校验同一个category下最大可配置的数量
                Integer category = schedulerDTO.getCategory();
                if (Lists.newArrayList(RateShopNotifyConfigTypeEnum.HISTORY_AND_FORECAST_REPORT_MISSED.getType(),
                                RateShopNotifyConfigTypeEnum.GROUP_PICKUP_REPORT_MISSED.getType(),
                                RateShopNotifyConfigTypeEnum.OCCUPANCY_SUMMARY_REPORT_MISSED.getType(),
                                RateShopNotifyConfigTypeEnum.MARKET_SEGMENT_SUMMARY_REPORT_MISSED.getType())
                        .contains(category)) {
                    schedulerService.checkMaxNum(propertyDTO.getId(), schedulerDTO.getCategory(), 1);
                } else if (category == RateShopNotifyConfigTypeEnum.OCC_SNAPSHOT_FOR_THE_NEXT_N_DAYS.getType()) {
                    schedulerService.checkMaxNum(propertyDTO.getId(), schedulerDTO.getCategory(), 5);
                }
                //新增
                return Map.of("id", schedulerService.createScheduler(schedulerDTO, propertyDTO, loginUserId));
            }
        }

        /**
         * 设置 job Parameters
         *
         * @param schedulerDTO schedulerDTO
         * @author yyt
         * @date 2026/3/10
         **/
        private void setJobParameters(RateShopSchedulerDTO schedulerDTO) {
            Integer category = schedulerDTO.getCategory();
            if (category.equals(RateShopNotifyConfigTypeEnum.HISTORY_AND_FORECAST_REPORT_MISSED.getType())) {
                //检查 parameters json格式是否合法
                JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);

                //检测报告缺失的dateRange默认为：-1,90
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                        schedulerDTO.getParameters(), JobParameterDTO.class);
                jobParameterDTO.setDateRange("-1,90");
                jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
            } else if (category.equals(RateShopNotifyConfigTypeEnum.GROUP_PICKUP_REPORT_MISSED.getType())) {
                //检查 parameters json格式是否合法
                JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);

                //检测报告缺失的dateRange默认为：0,90
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                        schedulerDTO.getParameters(), JobParameterDTO.class);
                jobParameterDTO.setDateRange("0,90");
                jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
            } else if (category.equals(RateShopNotifyConfigTypeEnum.OCC_SNAPSHOT_FOR_THE_NEXT_N_DAYS.getType())) {
                //检查 parameters json格式是否合法
                checkParametersForOccSnapshotForTheNextNDays(schedulerDTO.getParameters());
                //json只保留需要的参数
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, schedulerDTO.getParameters(), JobParameterDTO.class);
                jobParameterDTO.setDateRange(null);
                jobParameterDTO.setDateRangeUnit(null);
                jobParameterDTO.setThreshold(null);
                schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
            } else if (Lists.newArrayList(RateShopNotifyConfigTypeEnum.OCCUPANCY_SUMMARY_REPORT_MISSED.getType(), RateShopNotifyConfigTypeEnum.MARKET_SEGMENT_SUMMARY_REPORT_MISSED.getType())
                    .contains(category)) {
                //检查 parameters json格式是否合法
                JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);

                //检测报告缺失的dateRange默认为：-1
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                        schedulerDTO.getParameters(), JobParameterDTO.class);
                jobParameterDTO.setDateRange("-1");
                jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
            }
        }

        /**
         * 删除任务
         *
         * @param id       任务ID
         * @param property 资产标识
         * @return java.util.Map
         * @author lichen
         * @date 2024/3/15
         */
        @ApiId(1066)
        @DeleteMapping("{id}")
        public Map<String, String> delete(@PathVariable String id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            //校验是否存在
            RateShopScheduler oriSchedulerJob = schedulerService.checkExist(id);
            //校验归属
            checkBelong(oriSchedulerJob, propertyDTO.getId());
            //报告缺失的不允许删除
            if (Lists.newArrayList(RateShopNotifyConfigTypeEnum.HISTORY_AND_FORECAST_REPORT_MISSED.getType(),
                            RateShopNotifyConfigTypeEnum.GROUP_PICKUP_REPORT_MISSED.getType(),
                            RateShopNotifyConfigTypeEnum.OCCUPANCY_SUMMARY_REPORT_MISSED.getType(),
                            RateShopNotifyConfigTypeEnum.MARKET_SEGMENT_SUMMARY_REPORT_MISSED.getType())
                    .contains(oriSchedulerJob.getCategory())) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE_DELETING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "SchedulerJob category cannot delete, SchedulerJobId=" + oriSchedulerJob.getId());
            }
            //删除任务
            schedulerService.delete(oriSchedulerJob, propertyDTO);
            return Collections.emptyMap();
        }

        /**
         * 查询调度任务
         *
         * @param category 调度任务类型
         * @param property 资产标识
         * @return java.util.List<cloud.wconnect.rateshop.entity.rateshop.RateShopScheduler>
         * @author lichen
         * @date 2024/3/15
         */
        @ApiId(1064)
        @GetMapping("{category:\\d+}")
        public List<RateShopScheduler> list(@PathVariable Integer category, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //校验分类是否符合预期
            checkCategory(propertyDTO.getPmsType(), category);

            //查询调度任务
            return schedulerService.listByCategory(propertyId, category);
        }

        /**
         * 校验调度任务是否归属于资产
         *
         * @param oriSchedulerJob 调度任务
         * @param propertyId      资产ID
         * @return void
         * @author lichen
         * @date 2024/3/15
         */
        private void checkBelong(RateShopScheduler oriSchedulerJob, Integer propertyId) {
            if (!oriSchedulerJob.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("SchedulerJob not belong current property,SchedulerJobId:{0},SchedulerJobBelongPropertyId:{1},currentPropertyId:{2}",
                                oriSchedulerJob.getId(), oriSchedulerJob.getPropertyId(), propertyId));
            }
        }

        /**
         * 检查 parameters json格式是否合法
         *
         * @param parameters 参数json
         * @return void
         * @author lichen
         * @date 2024/6/18
         */
        private void checkParametersForOccSnapshotForTheNextNDays(String parameters) {
            if (StringUtils.isBlank(parameters)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("parameters"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "parameters cannot be blank");
            }
            try {
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, parameters, JobParameterDTO.class);
                // 校验自定义参数
                Object compareHourObj = JSONObject.parseObject(jobParameterDTO.getCustomBusiness()).get(SchedulerCons.HOUR);
                if (Objects.isNull(compareHourObj)) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(RateShopFieldI18nConstant.COMPARE_HOUR),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "compareHour cannot be null");
                }
                int compareHour = Integer.parseInt(compareHourObj.toString());
                if (compareHour < 0 || compareHour > 23) {
                    LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                    map.put(RateShopFieldI18nConstant.COMPARE_HOUR, true);
                    map.put("0-23", false);
                    throw new BadRequestException(CommonErrorI18nConstant.INVALID_VALUE,
                            i18nMessageUtils.getErrorParamMap(map),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "compareHour must between [0-23]");
                }
            } catch (Exception e) {
                throw new BadRequestException(CommonErrorI18nConstant.FORMAT_ERROR,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("parameters"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "compareHour invalid");
            }
        }

        /**
         * 校验定时任务分类是否符合预期
         *
         * @param pmsType  当前资产配置的PMS Type
         * @param category 定时任务分类
         * @return void
         * @author lichen
         * @date 2025/7/5
         */
        private void checkCategory(String pmsType, Integer category) {
            List<Integer> validCategorys;
            if (SystemPlatformEnum.OPERA.getDesc().equals(pmsType)) {
                validCategorys = RateShopCons.OPERA_PMS_SCHEDULER_TYPE_LIST;
            } else if(SystemPlatformEnum.SHI_JI.getDesc().equals(pmsType)) {
                validCategorys = RateShopCons.SHIJI_PMS_SCHEDULER_TYPE_LIST;
            } else {
                validCategorys = RateShopCons.PEP_PMS_SCHEDULER_TYPE_LIST;
            }
            if (!validCategorys.contains(category)) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_TYPE_NOT_SUPPORTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The scheduler category must be in:" + validCategorys);
            }
        }
    }