# ClcSchedulerController.java

    package cloud.wconnect.frontoffice.controller.clc;

    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.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.frontoffice.entity.clc.ClcSchedulerJob;
    import cloud.wconnect.frontoffice.pojo.dto.clc.ClcSchedulerDTO;
    import cloud.wconnect.frontoffice.service.clc.IClcSchedulerService;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import jakarta.validation.Valid;
    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;

    /**
     * @author: yyt
     * 2024-03-13
     */
    @RestController
    @RequestMapping("/clc/scheduler")
    public class ClcSchedulerController {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private IClcSchedulerService clcSchedulerService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询定时任务
         *
         * @param property 资产标识
         * @param category  类型（1：arrival 报告缺失，2：missing 状态通知，3：通知酒店处理 clc 平台顾客退房，4：guest in house 报告缺失）
         * @return 定时任务
         * @author yyt
         * @date 2024/3/13
         */
        @ApiId(1076)
        @GetMapping("/{category:\\d+}")
        public List<ClcSchedulerDTO> get(String property, @PathVariable int category) {
            Integer propertyId = sessionContext.getPropertyId(property);
            // 查询非删除中的定时任务
            List<ClcSchedulerJob> schedulers = clcSchedulerService.list(propertyId, category);
            return beanMapper.mapList(schedulers, ClcSchedulerDTO.class);
        }

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

            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            Integer propertyId = propertyDTO.getId();

            Integer category = schedulerDTO.getCategory();

            if (category == ClcSchedulerDTO.MISSING_ARRIVAL_REPORT) {
                // 检测报告缺失的 dateRange默认为：-1，阈值
                JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                        schedulerDTO.getParameters(), JobParameterDTO.class);
                jobParameterDTO.setDateRange("-1");
                jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
                schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
            } else if (category == ClcSchedulerDTO.MISSING_NOTIFY ||
                    category == ClcSchedulerDTO.NOTIFY_HANDLE_CHECK_OUT) {
                schedulerDTO.setParameters("{}");
            }

            // 校验 scheduler 不能重复
            checkSchedulerRepeat(propertyId, category, schedulerDTO.getScheduler(), id);

            if (id.equals("0")) {
                if (category == ClcSchedulerDTO.MISSING_NOTIFY ||
                        category == ClcSchedulerDTO.NOTIFY_HANDLE_CHECK_OUT) {
                    // 未删除状态下，missing 状态通知 / 通知酒店处理 clc 平台顾客退房，最多只能创建 5个
                    clcSchedulerService.checkCount(propertyId, category, 5);
                } else {
                    // 限制 同一个 category 只能存在一条未删除中的数据
                    clcSchedulerService.checkCount(propertyId, category, 1);
                }

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

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

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

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

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

        /**
         * 校验 scheduler 不能重复
         *
         * @param propertyId      资产 id
         * @param category        分类
         * @param updateScheduler 修改后的 scheduler
         * @param id              排除自身
         * @author yyt
         * @date 2024/3/19
         **/
        private void checkSchedulerRepeat(Integer propertyId, Integer category, String updateScheduler, String id) {
            List<ClcSchedulerJob> existSchedulerJobs = clcSchedulerService.listExistJob(propertyId, category, id);
            if (existSchedulerJobs.isEmpty()) {
                return;
            }

            CronRuleUtil.checkSchedulerRepeat(objectMapper, existSchedulerJobs.stream().map(ClcSchedulerJob::getScheduler).toList(), updateScheduler);
        }

        /**
         * 删除定时任务
         *
         * @param property     资产标识
         * @param id           定时任务 id
         * @return {}
         * @author yyt
         * @date 2024/3/13
         **/
        @ApiId(1078)
        @DeleteMapping("/{id}")
        public Map<String, Object> delete(String property, @PathVariable String id) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            //校验调度任务是否存在
            ClcSchedulerJob scheduler = clcSchedulerService.checkExist(id);

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

            if (scheduler.getStatus() == SchedulerCons.STATUS_DELETING) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE_DELETING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "deleting clcScheduler do not support deletion; clcEmailConfigSchedulerId:" + id);
            }
            if (scheduler.getCategory() == ClcSchedulerDTO.MISSING_ARRIVAL_REPORT ||
                    scheduler.getCategory() == ClcSchedulerDTO.MISSING_GUEST_IN_HOUSE_REPORT) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_DELETE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "clcScheduler that report missing types do not support deletion; clcEmailConfigSchedulerId:" + id);
            }

            clcSchedulerService.delete(propertyDTO, scheduler);

            return Collections.emptyMap();
        }

        /**
         * 校验定时规则
         *
         * @param schedulerDTO 定时
         * @author yyt
         * @date 2024/3/13
         **/
        private void checkScheduler(ClcSchedulerDTO schedulerDTO) {
            switch (schedulerDTO.getCategory()) {
                case ClcSchedulerDTO.MISSING_ARRIVAL_REPORT -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());

                    //检查 parameters json 格式是否合法
                    JobParameterUtils.checkParameters(objectMapper, schedulerDTO.getParameters(), i18nMessageUtils);
                }
                case ClcSchedulerDTO.MISSING_NOTIFY -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天 / 周执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY, SchedulerCons.EVERY_WEEK),
                            schedulerDTO.getScheduler());
                }
                case ClcSchedulerDTO.NOTIFY_HANDLE_CHECK_OUT -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天 / 周/ 月 执行
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper,
                            List.of(SchedulerCons.EVERY_DAY, SchedulerCons.EVERY_WEEK, SchedulerCons.EVERY_MONTH),
                            schedulerDTO.getScheduler());
                }
                case ClcSchedulerDTO.MISSING_GUEST_IN_HOUSE_REPORT -> {
                    // 检查 scheduler json 格式是否合法，目前支持配置按天执行；
                    CronRuleUtil.validateCronRuleForScheduler(objectMapper, List.of(SchedulerCons.EVERY_DAY), schedulerDTO.getScheduler());
                }
            }
        }

        /**
         * 校验数据归属
         *
         * @param clcSchedulerJob clc 定时任务
         * @param propertyId      资产 id
         * @author yyt
         * @date 2024/3/13
         */
        private void checkBelong(ClcSchedulerJob clcSchedulerJob, Integer propertyId) {
            if (!clcSchedulerJob.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 clcScheduler;propertyId:" + propertyId + ",clcSchedulerPropertyId:" +
                        clcSchedulerJob.getPropertyId() + ",clcSchedulerId:" + clcSchedulerJob.getId());
            }
        }

    }