# DepartmentCategoryServiceImpl.java

    package cloud.wconnect.workperformance.service.sva.setting.impl;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    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.sva.SvaErrorI18nConstant;
    import cloud.wconnect.workperformance.cons.sva.SvaI18nConstant;
    import cloud.wconnect.workperformance.entity.sva.setting.DepCategoryRecommendHour;
    import cloud.wconnect.workperformance.entity.sva.setting.DepCodeToCategory;
    import cloud.wconnect.workperformance.entity.sva.setting.DepartmentCategory;
    import cloud.wconnect.workperformance.mapper.sva.setting.DepartmentCategoryMapper;
    import cloud.wconnect.workperformance.pojo.dto.sva.category.CategoryDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.category.DepartmentCategoryDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.category.DepartmentCodeUsableDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.category.DepartmentInfoDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.recommendhours.RecommendHoursDTO;
    import cloud.wconnect.workperformance.pojo.query.sva.setting.DepartmentQuery;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.ISvaActualScheduleRecordService;
    import cloud.wconnect.workperformance.service.sva.employees.IAttendanceEmployeeService;
    import cloud.wconnect.workperformance.service.sva.setting.IDepCategoryRecommendHourService;
    import cloud.wconnect.workperformance.service.sva.setting.IDepCodeToCategoryService;
    import cloud.wconnect.workperformance.service.sva.setting.IDepartmentCategoryService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

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

    /**
     * <p>
     * 部门分类 服务实现类
     * </p>
     *
     * @author zc
     * @since 2023-02-22
     */
    @Service
    @DS("work_performance")
    public class DepartmentCategoryServiceImpl extends ServiceImpl<DepartmentCategoryMapper, DepartmentCategory> implements IDepartmentCategoryService {
        @Resource
        private IDepCodeToCategoryService depCodeToCategoryService;
        @Resource
        private IDepCategoryRecommendHourService depCategoryRecommendHourService;
        @Resource
        private IAttendanceEmployeeService attendanceEmployeeService;
        @Resource
        private ISvaActualScheduleRecordService actualScheduleRecordService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 根据 property id + 查询条件 获取 Category 数据
         *
         * @param propertyId property id
         * @param query      查询条件
         * @return Category 数据
         * @author zc
         * @date 2023/2/22
         */
        @Override
        public List<DepartmentCategoryDTO> list(final int propertyId, DepartmentQuery query) {
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            Integer version = BaseConstant.ZERO;
            if (Objects.nonNull(query.getTargetDate())) {
                // 获取指定日期适用的版本
                version = getVersion(propertyId, DateTimeUtils.localDateToLong(DateTimeUtils.strToLocalDate(query.getTargetDate(), BaseConstant.DATE_FORMAT_YYYYMMDD), timezone));
            }

            List<DepartmentCategory> departmentCategoryList = getByVersion(propertyId, version);
            if (CollectionUtils.isEmpty(departmentCategoryList)) {
                return Collections.emptyList();
            }

            List<Integer> categoryIds = departmentCategoryList.stream().map(DepartmentCategory::getId).toList();
            // 查询 category 对应的部门 code
            Map<Integer, List<DepCodeToCategory>> depCodeToCategoryListByDepCategoryId = depCodeToCategoryService.list(propertyId, categoryIds)
                    .stream().collect(Collectors.groupingBy(DepCodeToCategory::getDepCategoryId));

            return departmentCategoryList.stream().map(item -> {
                DepartmentCategoryDTO departmentCategoryDTO = new DepartmentCategoryDTO();
                departmentCategoryDTO.setCategoryId(item.getId());
                departmentCategoryDTO.setPropertyId(propertyId);
                departmentCategoryDTO.setCategoryName(item.getName());
                departmentCategoryDTO.setCategoryType(item.getType());

                departmentCategoryDTO.setDepartmentCodes(depCodeToCategoryListByDepCategoryId.getOrDefault(item.getId(), Collections.emptyList())
                        .stream().map(depCodeToCategory -> new DepartmentInfoDTO(depCodeToCategory.getId(), depCodeToCategory.getDepartmentCode()))
                        .collect(Collectors.toList()));

                return departmentCategoryDTO;
            }).collect(Collectors.toList());
        }

        /**
         * 获取指定版本的数据
         *
         * @param propertyId 资产 id
         * @param version    指定版本
         * @return DepartmentCategory
         * @author yyt
         * @date 2025/3/26
         **/
        @Override
        public List<DepartmentCategory> getByVersion(int propertyId, Integer version) {
            LambdaQueryWrapper<DepartmentCategory> queryWrapper = new QueryWrapper<DepartmentCategory>().lambda()
                    .eq(DepartmentCategory::getPropertyId, propertyId)
                    .eq(DepartmentCategory::getVersion, version)
                    .select(DepartmentCategory::getId, DepartmentCategory::getName, DepartmentCategory::getType);
            return list(queryWrapper);
        }

        /**
         * 新增 department category
         *
         * @param categoryDTO department category
         * @param userId      操作人
         * @return department category id
         * @author zc
         * @date 2023/3/13
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public int insert(CategoryDTO categoryDTO, int userId) {
            Integer propertyId = categoryDTO.getPropertyId();
            int version = Objects.isNull(categoryDTO.getVersion()) ? BaseConstant.ZERO : Integer.parseInt(categoryDTO.getVersion());
            String departmentCode = categoryDTO.getDepartmentCode();
            String categoryName = categoryDTO.getCategoryName();
            long now = DateTimeUtils.nowTimestamp();

            DepartmentCategory category = new DepartmentCategory(propertyId, categoryName, version, categoryDTO.getCategoryType(), userId, now, userId, now);
            save(category);

            Integer categoryId = category.getId();

            DepCodeToCategory depCodeToCategory = new DepCodeToCategory(propertyId, version, departmentCode, categoryName, categoryId, now);
            depCodeToCategory.setUpdatedBy(userId);
            depCodeToCategory.setUpdatedTime(now);
            depCodeToCategoryService.save(depCodeToCategory);

            return categoryId;
        }

        /**
         * 编辑 department category
         *
         * @param updateBeforeDepCodeToCategory 修改之前的 DepCodeToCategory
         * @param categoryDTO                   department category
         * @param userId                        操作人
         * @author zc
         * @date 2023/3/13
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(DepCodeToCategory updateBeforeDepCodeToCategory, CategoryDTO categoryDTO, int userId) {
            String departmentCode = categoryDTO.getDepartmentCode();
            String categoryName = categoryDTO.getCategoryName();
            long now = DateTimeUtils.nowTimestamp();

            DepCodeToCategory updateDepCodeToCategory = new DepCodeToCategory();
            updateDepCodeToCategory.setId(categoryDTO.getDepartmentId());
            updateDepCodeToCategory.setDepartmentCode(departmentCode);
            updateDepCodeToCategory.setDepartmentDesc(categoryName);
            updateDepCodeToCategory.setUpdatedBy(userId);
            updateDepCodeToCategory.setUpdatedTime(now);
            depCodeToCategoryService.updateById(updateDepCodeToCategory);

            DepartmentCategory category = new DepartmentCategory();
            category.setId(categoryDTO.getCategoryId());
            category.setName(categoryDTO.getCategoryName());
            category.setType(categoryDTO.getCategoryType());
            category.setLastUpdatedBy(userId);
            category.setLastUpdatedTime(now);
            updateById(category);
        }

        /**
         * 根据部门分类名称获取部门分类数据
         *
         * @param propertyId   资产 id
         * @param categoryName 部门分类名称
         * @return DepartmentCategory
         * @author zc
         * @date 2025/1/13
         */
        @Override
        public DepartmentCategory getByName(int propertyId, String categoryName) {
            return getOne(new QueryWrapper<DepartmentCategory>().lambda()
                    .eq(DepartmentCategory::getPropertyId, propertyId)
                    .eq(DepartmentCategory::getName, categoryName));
        }

        /**
         * 校验部门分类是否存在并返回部门分类数据
         *
         * @param id 部门分类 id
         * @return DepartmentCategory
         * @author zc
         * @date 2023/6/28
         */
        @Override
        public DepartmentCategory checkExist(int id) {
            DepartmentCategory departmentCategory = getById(id);
            if (Objects.isNull(departmentCategory)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The departmentCategory is not found; id:" + id);
            }
            return departmentCategory;
        }

        /**
         * 校验数据归属
         *
         * @param propertyId         资产 id
         * @param departmentCategory 部门分类数据
         * @author zc
         * @date 2023/6/21
         */
        @Override
        public void checkBelong(int propertyId, DepartmentCategory departmentCategory) {
            if (!departmentCategory.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The departmentCategory does not belong to this property; propertyId:{0}; departmentCategoryId:{1}",
                                propertyId, departmentCategory.getId()));
            }
        }

        /**
         * 获取指定日期对应的 版本
         *
         * @param propertyId 资产
         * @param targetDate 指定日期（时间戳）
         * @return 版本
         * @author yyt
         * @date 2025/3/24
         **/
        @Override
        public Integer getVersion(Integer propertyId, long targetDate) {
            RecommendHoursDTO current = depCategoryRecommendHourService.getCurrent(propertyId, targetDate);
            if (Objects.isNull(current)) {
                return null;
            }

            // 查询 部门分类中 大于等于 推荐工时的开始日期 最近的数据，来确定版本
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();
            int startingDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.toZonedDateTime(current.getStartingDate(),
                    timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            DepartmentCategory departmentCategory = getOne(new QueryWrapper<DepartmentCategory>().lambda()
                    .eq(DepartmentCategory::getPropertyId, propertyId)
                    .ge(DepartmentCategory::getVersion, startingDate)
                    .orderByAsc(DepartmentCategory::getVersion)
                    .last("LIMIT 1"));
            return departmentCategory.getVersion();
        }

        /**
         * 校验 department code 是否可用
         *
         * @param departmentCategory departmentCategory
         * @param departmentCode     department code
         * @return department code 是否可用
         * @author yyt
         * @date 2025/3/25
         **/
        @Override
        public DepartmentCodeUsableDTO getUsable(DepartmentCategory departmentCategory, String departmentCode) {
            Integer propertyId = departmentCategory.getPropertyId();
            Integer categoryId = departmentCategory.getId();
            // 编辑部门分类时，若 department code 没有被当前版本下的 考勤 / 员工 占用，则可用；
            if (actualScheduleRecordService.checkOccupied(propertyId, categoryId)) {
                return new DepartmentCodeUsableDTO(null, true);
            }
            if (attendanceEmployeeService.checkOccupied(propertyId, categoryId)) {
                return new DepartmentCodeUsableDTO(null, true);
            }

            // 编辑部门分类时，若 department code 没有被其他部门分类占用，则可用；
            return new DepartmentCodeUsableDTO(depCodeToCategoryService.checkRepeat(propertyId,
                    departmentCategory.getVersion(), departmentCode, categoryId), null);
        }

        /**
         * 删除 department category
         *
         * @param id 部门分类 id
         * @author yyt
         * @date 2025/3/25
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void removeDepartmentCategory(int id) {
            // 删除部门分类，删除部门分类关联关系
            removeById(id);
            depCodeToCategoryService.removeByCategoryId(id);
        }

        /**
         * 新增版本对应的 部门分类，部门分类关联关系
         *
         * @param propertyId   资产 id
         * @param newVersion   新版本
         * @param basicVersion 基础版本
         * @param userId       操作人
         * @author yyt
         * @date 2025/3/25
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<Integer, Integer> saveNewVersion(int propertyId, Integer newVersion, Integer basicVersion, int userId) {
            Map<Integer, Integer> newVersionCategoryIdByLastVersionCategoryId;

            // 获取基础版本对应的 department category
            List<DepartmentCategory> targetVersionDepartmentCategory = getByVersion(propertyId, basicVersion);
            if (basicVersion == BaseConstant.ZERO) {
                // 资产是第一次操作推荐工时，无需新建一个版本，把当前所有数据设置一个版本标识即可
                update(new UpdateWrapper<DepartmentCategory>().lambda()
                        .set(DepartmentCategory::getVersion, newVersion)
                        .eq(DepartmentCategory::getPropertyId, propertyId)
                        .eq(DepartmentCategory::getVersion, BaseConstant.ZERO));

                depCodeToCategoryService.update(new UpdateWrapper<DepCodeToCategory>().lambda()
                        .set(DepCodeToCategory::getVersion, newVersion)
                        .eq(DepCodeToCategory::getPropertyId, propertyId)
                        .eq(DepCodeToCategory::getVersion, BaseConstant.ZERO));

                newVersionCategoryIdByLastVersionCategoryId = targetVersionDepartmentCategory.stream().collect(Collectors.toMap(DepartmentCategory::getId, DepartmentCategory::getId));
            } else {
                // 基础版本的 department category 转至新版本
                long now = DateTimeUtils.nowTimestamp();
                List<DepartmentCategory> newDepartmentCategoryList = targetVersionDepartmentCategory.stream().map(item -> {
                    DepartmentCategory departmentCategory = new DepartmentCategory(propertyId, item.getName(), newVersion, item.getType(), userId, now, userId, now);
                    departmentCategory.setLastVersionCategoryId(item.getId());
                    return departmentCategory;
                }).collect(Collectors.toList());
                saveBatch(newDepartmentCategoryList);

                // 基础版本的 department code 转至新版本
                newVersionCategoryIdByLastVersionCategoryId = newDepartmentCategoryList.stream()
                        .collect(Collectors.toMap(DepartmentCategory::getLastVersionCategoryId, DepartmentCategory::getId));
                List<Integer> targetVersionCategoryIds = targetVersionDepartmentCategory.stream().map(DepartmentCategory::getId).collect(Collectors.toList());
                List<DepCodeToCategory> lastVersionDepCodeToCategoryList = depCodeToCategoryService.list(propertyId, targetVersionCategoryIds);
                Map<Integer, Integer> finalNewVersionCategoryIdByLastVersionCategoryId = newVersionCategoryIdByLastVersionCategoryId;
                List<DepCodeToCategory> newDepCodeToCategoryList = lastVersionDepCodeToCategoryList.stream().map(item -> {
                    DepCodeToCategory depCodeToCategory = new DepCodeToCategory(propertyId, newVersion, item.getDepartmentCode(), item.getDepartmentDesc(),
                            finalNewVersionCategoryIdByLastVersionCategoryId.get(item.getDepCategoryId()), now);
                    depCodeToCategory.setUpdatedBy(userId);
                    depCodeToCategory.setUpdatedTime(now);
                    return depCodeToCategory;
                }).collect(Collectors.toList());
                depCodeToCategoryService.saveBatch(newDepCodeToCategoryList);
            }

            return newVersionCategoryIdByLastVersionCategoryId;
        }

        /**
         * 获取版本日期在选择日期范围内和选择日期之后的最近的一条版本
         *
         * @param propertyId 资产 id
         * @param startDate  开始时间（yyyyMMdd）
         * @param endDate    结束时间（yyyyMMdd）
         * @return 多个版本日期
         * @author yyt
         * @date 2025/3/27
         */
        @Override
        public List<Integer> listByDateRangeWithNewOne(int propertyId, int startDate, int endDate) {
            return baseMapper.listByDate(propertyId, startDate, endDate);
        }

        /**
         * 获取多个版本的数据
         *
         * @param propertyId 资产 id
         * @param versions   版本
         * @return 多个版本的数据
         * @author yyt
         * @date 2025/3/27
         **/
        @Override
        public List<DepartmentCategory> listByVersions(int propertyId, List<Integer> versions) {
            return list(new QueryWrapper<DepartmentCategory>().lambda()
                    .eq(DepartmentCategory::getPropertyId, propertyId)
                    .in(DepartmentCategory::getVersion, versions));
        }

        /**
         * 检查更新的版本是否可以操作
         *
         * @param propertyId    资产 id
         * @param updateVersion 更新的版本
         * @author yyt
         * @date 2025/3/31
         **/
        @Override
        public void checkUpdateVersion(Integer propertyId, Integer updateVersion) {
            // 当前正在使用及历史版本的数据，不支持任何操作。
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(propertyDTO.getTimezone()).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            if (updateVersion <= currentDate) {
                throw new BadRequestException(SvaErrorI18nConstant.DEPARTMENT_VERSION_CURRENT_HISTORY_CANNOT_OPERATION,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The current and historical versions of the data do not support any operation.; version: " + updateVersion);
            }
        }

        /**
         * 若当前版本的 Category 被考勤数据占用，则不能删除
         *
         * @param recommendHour 推荐工时信息
         * @author yyt
         * @date 2025/3/31
         **/
        @Override
        public void checkOccupied(DepCategoryRecommendHour recommendHour) {
            Integer propertyId = recommendHour.getPropertyId();
            Integer version = getVersion(propertyId, recommendHour.getStartingDate());
            List<DepartmentCategory> departmentCategories = getByVersion(propertyId, version);
            if (CollectionUtils.isEmpty(departmentCategories)) {
                return;
            }
            List<Integer> departmentCategoryIdList = departmentCategories.stream().map(DepartmentCategory::getId).collect(Collectors.toList());
            if (actualScheduleRecordService.checkOccupied(propertyId, departmentCategoryIdList)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_IS_IN_USE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(SvaI18nConstant.DEPARTMENT),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The current version of the department is occupied by attendance data; version: " + version);
            }
        }

    }