# RevenueSchedulerJobController.java

    package cloud.wconnect.rateshop.controller.revenue;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.cons.SchedulerCons;
    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.revenue.DailyRevenueNotifyTypeEnum;
    import cloud.wconnect.rateshop.cons.revenue.RevenueImportCons;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSchedulerJob;
    import cloud.wconnect.rateshop.pojo.dto.revenue.RevenueSchedulerDTO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSchedulerJobService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueService;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import jakarta.validation.Valid;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;

    /**
     * <p>
     * 前端控制器
     * </p>
     *
     * @author yyt
     * @since 2024-03-16
     */
    @RestController
    @RequestMapping("/revenue/scheduler")
    public class RevenueSchedulerJobController {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private IRevenueSchedulerJobService schedulerJobService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IRevenueService revenueService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询定时任务
         *
         * @param property 资产标识
         * @param category 分类 (1:History and Forecast,2:Business Block,3:Reservation Statistics,4:Reservation Forecast,
         *                 5:Reservation Forecast Next Month,6:History Forecast Next Month,
         *                 8: Reservations Forecast Next 14 days,9:History and Forecast Next 90 days,10:Market Code,
         *                 11:Occ jump alert，12：History and Forecast this month（future），13：History and Forecast next month（future），
         *                 14：History and Forecast the month after next，
         *                 16：Reservation Forecast next 90 days，17：History and Forecast Business Block next 90 days,
         *                 19：Manager flash,20：Detailed Availability 未来14天报告缺失,21：Rate Review 通知,
         *                 22: Group Pickup 23：History and Forecast from thirty days ago to yesterday,
         *                 24: reservation forecast this month)
         * @return 定时任务
         * @author yyt
         * @date 2024/3/16
         */
        @ApiId(1095)
        @GetMapping("/{category:\\d+}")
        public List<RevenueSchedulerDTO> get(String property, @PathVariable int category) {
            int propertyId = sessionContext.getPropertyId(property);

            revenueService.checkType(propertyI18nRedisService.get(propertyId).getPmsType(), category);

            // 查询非删除中的定时任务
            List<RevenueSchedulerJob> schedulers = schedulerJobService.list(propertyId, category);
            return beanMapper.mapList(schedulers, RevenueSchedulerDTO.class);
        }

        /**
         * 保存定时任务
         *
         * @param property     资产标识
         * @param id           定时任务 id
         * @param schedulerDTO 定时任务
         * @return {}
         * @author yyt
         * @date 2024/3/16
         */
        @ApiId(1096)
        @PostMapping("/{id}")
        public Map<String, String> save(String property, @PathVariable String id, @Valid @RequestBody RevenueSchedulerDTO schedulerDTO) {
            // 校验定时规则
            checkScheduler(schedulerDTO);

            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            revenueService.checkType(propertyDTO.getPmsType(), schedulerDTO.getCategory());

            if (schedulerDTO.getCategory() == DailyRevenueNotifyTypeEnum.RATE_REVIEW_NOTIFY.getType()) {
                schedulerDTO.setParameters("{}");
            }

            if (id.equals("0")) {
                // 校验分类下最大可配置调度任务的数量
                schedulerJobService.checkMaxNum(propertyId, schedulerDTO.getCategory(),
                        Lists.newArrayList(DailyRevenueNotifyTypeEnum.OCC_JUMP_ALERT.getType(), DailyRevenueNotifyTypeEnum.RATE_REVIEW_NOTIFY.getType())
                                .contains(schedulerDTO.getCategory()) ? 5 : 1);

                id = schedulerJobService.save(propertyDTO, schedulerDTO, sessionContext.getLoginUserId());
            } else {
                //校验调度任务是否存在
                RevenueSchedulerJob scheduler = schedulerJobService.checkExist(id);

                //校验是否归属于资产
                checkBelong(scheduler, propertyId);

                if (scheduler.getStatus() == SchedulerCons.STATUS_DELETING) {
                    throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE_DELETING,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "deleting revenueScheduler do not support update; revenueSchedulerId:" + id);
                }
                if (!scheduler.getCategory().equals(schedulerDTO.getCategory())) {
                    throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_CHANGE_CATEGORY,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "revenueScheduler category do not support update; revenueSchedulerId:" + id);
                }

                id = schedulerJobService.update(propertyDTO, scheduler, schedulerDTO, sessionContext.getLoginUserId());
            }

            return ImmutableMap.of("id", id);
        }

        /**
         * 删除任务
         *
         * @param id       任务ID
         * @param property 资产标识
         * @return empty map
         * @author zc
         * @date 2024/4/24
         */
        @ApiId(1159)
        @DeleteMapping("/{id}")
        public Map<Object, Object> delete(@PathVariable String id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在
            RevenueSchedulerJob oriSchedulerJob = schedulerJobService.checkExist(id);

            revenueService.checkType(propertyI18nRedisService.get(propertyId).getPmsType(), oriSchedulerJob.getCategory());

            // 校验归属
            checkBelong(oriSchedulerJob, propertyId);

            // 校验调度分类是否支持删除操作
            if (!Lists.newArrayList(DailyRevenueNotifyTypeEnum.OCC_JUMP_ALERT.getType(), DailyRevenueNotifyTypeEnum.RATE_REVIEW_NOTIFY.getType())
                    .contains(oriSchedulerJob.getCategory())) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Cannot be delete this RevenueSchedulerJob; category must be in [11:Occ jump alert, 21: rate review notify]");
            }
            // 删除中状态的数据不可删除
            if (oriSchedulerJob.getStatus() == SchedulerCons.STATUS_DELETING) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE_DELETING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The deleting RevenueSchedulerJob cannot delete; SchedulerJobId=" + oriSchedulerJob.getId());
            }
            // 删除任务
            schedulerJobService.delete(oriSchedulerJob);
            return Collections.emptyMap();
        }

        /**
         * 校验定时规则
         *
         * @param schedulerDTO 定时
         * @author yyt
         * @date 2024/3/16
         **/
        private void checkScheduler(RevenueSchedulerDTO schedulerDTO) {
            switch (schedulerDTO.getCategory()) {
                case RevenueImportCons.HISTORY_FORECAST, RevenueImportCons.HISTORY_FORECAST_BUSINESS_BLOCK,
                        RevenueImportCons.RESERVATION_STATISTIC, RevenueImportCons.RESERVATION_FORECAST,
                        RevenueImportCons.RESERVATION_FORECAST_NEXT_MONTH, RevenueImportCons.HISTORY_FORECAST_NEXT_MONTH,
                        RevenueImportCons.RESERVATION_FORECAST_NEXT_14_DAYS,
                        RevenueImportCons.HISTORY_FORECAST_THIS_MONTH_FUTURE,
                        RevenueImportCons.HISTORY_FORECAST_NEXT_MONTH_FUTURE, RevenueImportCons.HISTORY_FORECAST_THIS_MONTH_AFTER_NEXT_FUTURE,
                        RevenueImportCons.RESERVATION_FORECAST_NEXT_90_DAYS,
                        RevenueImportCons.HISTORY_FORECAST_BUSINESS_BLOCK_NEXT_90_DAYS, RevenueImportCons.DETAILED_AVAILABILITY_NEXT_14_DAYS -> {

                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());

                    //检查 parameters json 格式是否合法
                    JobParameterUtils.checkParametersThreshold(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);
                }
                case RevenueImportCons.HISTORY_AND_FORECAST_YESTERDAY_AND_NEXT_NINETY_DAYS -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                    //检查 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));
                }
                case RevenueImportCons.MARKET_CODE, RevenueImportCons.GROUP_PICKUP_YESTERDAY, RevenueImportCons.MANAGER_FLASH -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                    //检查 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));
                }
                case RevenueImportCons.OCC_JUMP_ALERT -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                    // 检查 parameters json格式是否合法
                    if (StringUtils.isBlank(schedulerDTO.getParameters())) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_EXEC_PARAMETERS),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "parameters cannot be blank");
                    }

                    JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, schedulerDTO.getParameters(), JobParameterDTO.class);
                    // 校验日期单位
                    if (Objects.isNull(jobParameterDTO.getDateRangeUnit())) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE_UNIT),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "dateRangeUnit cannot be blank");
                    }
                    List<Integer> dateRangeUnitList = Lists.newArrayList(JobParameterDTO.DAY, JobParameterDTO.WEEK, JobParameterDTO.MONTH);
                    if (!dateRangeUnitList.contains(jobParameterDTO.getDateRangeUnit())) {
                        throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE_UNIT, CommonFieldI18nConstant.SCHEDULER_DATE_RANGE_UNIT_VALUE),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the dateRangeUnit must between in [1:day 2:week 3:month]");
                    }
                    // 校验日期表达式
                    if (StringUtils.isBlank(jobParameterDTO.getDateRange())) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "dateRange cannot be blank");
                    }
                    // 校验自定义参数
                    try {
                        JsonNode customBusinessNode = objectMapper.readTree(jobParameterDTO.getCustomBusiness());
                        if (Objects.isNull(customBusinessNode.get(SchedulerCons.HOUR))) {
                            throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                    i18nMessageUtils.getErrorParamMapNeedTranslate(SchedulerCons.HOUR),
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "hour cannot be blank");
                        }
                    } catch (JsonProcessingException e) {
                        throw new BadRequestException(CommonErrorI18nConstant.FORMAT_ERROR,
                                i18nMessageUtils.getErrorParamMapNotNeedTranslate("customBusinessNode"),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "customBusinessNode is wrong format");
                    }

                    jobParameterDTO.setDateRange("0,90");
                    jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                    schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
                }
                case RevenueImportCons.RATE_REVIEW -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                }
                case RevenueImportCons.HISTORY_AND_FORECAST_FROM_THIRTY_DAYS_AGO_TO_YESTERDAY -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                    //检查 parameters json格式是否合法
                    JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);

                    //检测报告缺失的dateRange默认为：-30,-1
                    JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                            schedulerDTO.getParameters(), JobParameterDTO.class);
                    jobParameterDTO.setDateRange("-30,-1");
                    jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                    schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
                }
                case RevenueImportCons.RESERVATION_FORECAST_THIS_MONTH -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                    // 检查 parameters json格式是否合法
                    JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);

                    // 检测报告缺失的dateRange默认为：0（当前月）
                    JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                            schedulerDTO.getParameters(), JobParameterDTO.class);
                    jobParameterDTO.setDateRange("0");
                    jobParameterDTO.setDateRangeUnit(JobParameterDTO.MONTH);
                    schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
                }
                default -> throw new BadRequestException(CommonErrorI18nConstant.DATA_INVALID,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("category"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "category is invalid");
            }
        }

        /**
         * 校验数据归属
         *
         * @param revenueSchedulerJob 定时任务
         * @param propertyId          资产 id
         * @author yyt
         * @date 2024/3/16
         */
        private void checkBelong(RevenueSchedulerJob revenueSchedulerJob, Integer propertyId) {
            if (!revenueSchedulerJob.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the revenueSchedulerJob;propertyId:" + propertyId + ",revenueSchedulerJobPropertyId:" +
                        revenueSchedulerJob.getPropertyId() + ",revenueSchedulerJobId:" + revenueSchedulerJob.getId());
            }
        }

    }