# AdjOutOfOfficeConfigController.java

    package cloud.wconnect.frontoffice.controller.adjustment;

    import cloud.wconnect.commonlib.annotation.ApiId;
    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.DataPermissionConfigConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.DataPermissionConfigInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjErrorCodeEnum;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjApproveConfig;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjOutOfOfficeConfig;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.OutOfOfficeDTO;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.SaveOutOfOfficeDTO;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.OutOfOfficeQuery;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjApproveConfigService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjOutOfOfficeConfigService;
    import com.google.common.collect.ImmutableMap;
    import jakarta.validation.Valid;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.*;

    import java.text.MessageFormat;
    import java.util.Collections;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    /**
     * <p>
     * out of office config 前端控制器
     * </p>
     *
     * @author yyt
     * @since 2026-03-18
     */
    @RequiredArgsConstructor
    @RestController
    @RequestMapping("/adjustment/out-of-office")
    public class AdjOutOfOfficeConfigController {

        private final IAdjOutOfOfficeConfigService adjOutOfOfficeConfigService;
        private final IAdjApproveConfigService adjApproveConfigService;
        private final UserInternalService userInternalService;
        private final DataPermissionConfigInternalService dataPermissionConfigInternalService;
        private final PropertyI18nRedisService propertyI18nRedisService;
        private final SessionContext sessionContext;
        private final I18nMessageUtils i18nMessageUtils;

        /**
         * 1703 - out of office 配置列表
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return out of office 配置列表
         * @author yyt
         * @date 2026/3/18
         **/
        @ApiId(1703)
        @GetMapping
        public ResultDTO<ListPagedDTO<OutOfOfficeDTO>> list(String property, @Valid OutOfOfficeQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            return adjOutOfOfficeConfigService.listConfigDTO(propertyId, sessionContext.getLoginUserId(), query);
        }

        /**
         * 1704 - 新增 / 修改配置
         *
         * @param property  资产标识
         * @param id  配置 id
         * @param outOfOfficeDTO  配置信息
         * @return success
         * @author yyt
         * @date 2026/3/18
         **/
        @ApiId(1704)
        @PostMapping("/{id:\\d+}")
        public ResultDTO<Void> saveOrUpdate(String property, @PathVariable int id, @RequestBody @Valid SaveOutOfOfficeDTO outOfOfficeDTO) {
            int propertyId = sessionContext.getPropertyId(property);

            // 开始日期必须大于当天，结束日期必须大于等于开始日期
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();
            Integer tenantId = propertyDTO.getTenantId();
            if (!DateTimeUtils.strToLocalDate(outOfOfficeDTO.getStartDate(), BaseConstant.DATE_FORMAT_YYYYMMDD)
                    .isAfter(DateTimeUtils.dateNow(timezone).toLocalDate())) {
                return ResultDTO.fail(AdjErrorCodeEnum.START_DATE_CANNOT_LESS_THAN_TODAY.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.START_DATE_CANNOT_LESS_THAN_TODAY.getI18nCode()));
            }
            if (DateTimeUtils.strToLocalDate(outOfOfficeDTO.getStartDate(), BaseConstant.DATE_FORMAT_YYYYMMDD)
                    .isAfter(DateTimeUtils.strToLocalDate(outOfOfficeDTO.getEndDate(), BaseConstant.DATE_FORMAT_YYYYMMDD))) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the start date cannot be greater than to end date");
            }

            // 检查登陆用户是否有审批权限
            int userId = sessionContext.getLoginUserId();
            Integer substitutePersonnelId = outOfOfficeDTO.getSubstitutePersonnelId();
            Map<Integer, Boolean> hasPermissionByUserId = dataPermissionConfigInternalService.checkContainsApi(tenantId,
                            propertyId, ImmutableMap.of(DataPermissionConfigConstant.IDENTITY_TYPE_USER, Stream.of(substitutePersonnelId, userId).collect(Collectors.toSet())),
                            DataPermissionConfigConstant.ADJ_APPROVE_API_ID)
                    .getOrDefault(DataPermissionConfigConstant.IDENTITY_TYPE_USER, Collections.emptyMap());
            if (!hasPermissionByUserId.getOrDefault(userId, false)) {
                return ResultDTO.fail(BaseErrorCodeEnum.USER_NOT_HAVE_APPROVE_PERMISSION.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(BaseErrorCodeEnum.USER_NOT_HAVE_APPROVE_PERMISSION.getI18nCode()));
            }

            // 检查补位人员不能是自己
            if (userId == substitutePersonnelId) {
                return ResultDTO.fail(AdjErrorCodeEnum.SUBSTITUTE_USER_CANNOT_BE_YOURSELF.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.SUBSTITUTE_USER_CANNOT_BE_YOURSELF.getI18nCode()));
            }
            // 检查补位人员状态
            if (!userInternalService.getOne(tenantId, substitutePersonnelId).getStatus()) {
                return ResultDTO.fail(BaseErrorCodeEnum.USER_CLOSED_NOT_SUPPORT_OPERATION.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(BaseErrorCodeEnum.USER_CLOSED_NOT_SUPPORT_OPERATION.getI18nCode()));
            }
            // 补位人员须有审批权限
            if (!hasPermissionByUserId.get(substitutePersonnelId)) {
                return ResultDTO.fail(AdjErrorCodeEnum.SUBSTITUTE_USER_NOT_HAVE_APPROVE_PERMISSION.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.SUBSTITUTE_USER_NOT_HAVE_APPROVE_PERMISSION.getI18nCode()));
            }

            // 补位人员不能是当前类型下审批树上的人员
            AdjApproveConfig approveConfig = adjApproveConfigService.getByUserId(propertyId, outOfOfficeDTO.getType(), substitutePersonnelId);
            if (Objects.nonNull(approveConfig)) {
                return ResultDTO.fail(AdjErrorCodeEnum.CANNOT_SELECT_APPROVE_CONFIG_USER_AS_SUBSTITUTE_USER.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.CANNOT_SELECT_APPROVE_CONFIG_USER_AS_SUBSTITUTE_USER.getI18nCode()));
            }

            if (id == BaseConstant.ZERO) {
                // 新增
                // 检查是否已存在有效的（未开始 / 进行中）补位配置
                if (adjOutOfOfficeConfigService.checkHasEffectiveConfig(propertyId, outOfOfficeDTO.getType(), userId)) {
                    return ResultDTO.fail(AdjErrorCodeEnum.CANNOT_ADD_OUT_OF_OFFICE_CONFIG_WHEN_EXIST_EFFECTIVE_CONFIG.getCode(),
                            i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.CANNOT_ADD_OUT_OF_OFFICE_CONFIG_WHEN_EXIST_EFFECTIVE_CONFIG.getI18nCode()));
                }

                adjOutOfOfficeConfigService.save(propertyId, outOfOfficeDTO, userId);
            } else {
                // 编辑
                // 检查配置存在
                AdjOutOfOfficeConfig config = adjOutOfOfficeConfigService.checkExist(id);
                // 检查配置资产归属
                checkPropertyBelong(config, propertyId);
                // 检查配置是否所属当前登陆用户
                checkUserBelong(config, userId);
                // 只有未生效的才可以修改
                int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(propertyI18nRedisService.get(propertyId).getTimezone()).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
                if (config.getClose() || config.getStartDate() <= currentDate) {
                    return ResultDTO.fail(AdjErrorCodeEnum.CANNOT_MODIFY_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getCode(),
                            i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.CANNOT_MODIFY_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getI18nCode()));
                }

                adjOutOfOfficeConfigService.update(id, outOfOfficeDTO);
            }

            return ResultDTO.success();
        }

        /**
         * 1705 - 关闭配置
         *
         * @param property 资产标识
         * @param id       配置 id
         * @return success
         * @author yyt
         * @date 2026/3/18
         **/
        @ApiId(1705)
        @PutMapping("/{id:\\d+}/close")
        public ResultDTO<Void> close(String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 检查配置存在
            AdjOutOfOfficeConfig config = adjOutOfOfficeConfigService.checkExist(id);
            // 检查配置资产归属
            checkPropertyBelong(config, propertyId);
            // 检查配置是否所属当前登陆用户
            checkUserBelong(config, sessionContext.getLoginUserId());
            // 只有生效中的才可以关闭
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(propertyI18nRedisService.get(propertyId).getTimezone()).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            if (config.getClose() || config.getStartDate() > currentDate) {
                return ResultDTO.fail(AdjErrorCodeEnum.CANNOT_CLOSE_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.CANNOT_CLOSE_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getI18nCode()));
            }
            // 关闭
            adjOutOfOfficeConfigService.close(id);

            return ResultDTO.success();
        }

        /**
         * 1706 - 删除配置
         *
         * @param property 资产标识
         * @param id       配置 id
         * @return success
         * @author yyt
         * @date 2026/3/18
         **/
        @ApiId(1706)
        @DeleteMapping("/{id:\\d+}")
        public ResultDTO<Void> delete(String property, @PathVariable int id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 检查配置存在
            AdjOutOfOfficeConfig config = adjOutOfOfficeConfigService.checkExist(id);
            // 检查配置资产归属
            checkPropertyBelong(config, propertyId);
            // 检查配置是否所属当前登陆用户
            checkUserBelong(config, sessionContext.getLoginUserId());
            // 只有未生效的配置支持删除
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(propertyI18nRedisService.get(propertyId).getTimezone()).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            if (config.getClose() || config.getStartDate() <= currentDate) {
                return ResultDTO.fail(AdjErrorCodeEnum.CANNOT_DELETE_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getCode(),
                        i18nMessageUtils.getAllLanguageMsg(AdjErrorCodeEnum.CANNOT_DELETE_OUT_OF_OFFICE_CONFIG_WHEN_EFFECTIVE.getI18nCode()));
            }

            adjOutOfOfficeConfigService.delete(id);

            return ResultDTO.success();
        }

        /**
         * 检查资产归属
         *
         * @param config     out of office 配置
         * @param propertyId 资产 id
         * @author yyt
         * @date 2026/3/18
         **/
        private void checkPropertyBelong(AdjOutOfOfficeConfig config, int propertyId) {
            if (config.getPropertyId() != propertyId) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("the adjOutOfOfficeConfig not belong to current property;currentProperty:{0},adjOutOfOfficeConfigProperty:{1}",
                                propertyId, config.getPropertyId()));
            }
        }

        /**
         * 检查用户归属
         *
         * @param config     out of office 配置
         * @param userId     用户 id
         * @author yyt
         * @date 2026/3/18
         **/
        private void checkUserBelong(AdjOutOfOfficeConfig config, int userId) {
            if (config.getUpdatedBy() != userId) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("the adjOutOfOfficeConfig not belong to current login user;currentLoginUser:{0},adjOutOfOfficeConfigCreatedBy:{1}",
                                userId, config.getUpdatedBy()));
            }
        }

    }