# DepCodeToCategoryServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.cons.sva.SvaConstant;
    import cloud.wconnect.workperformance.entity.sva.SvaProductivityRatioSettings;
    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.DepCodeToCategoryMapper;
    import cloud.wconnect.workperformance.pojo.bo.sva.DateVersionBO;
    import cloud.wconnect.workperformance.pojo.dto.sva.category.DepartmentDTO;
    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.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.checkerframework.checker.nullness.qual.Nullable;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.*;
    import java.util.stream.Collectors;

    import static java.util.stream.Collectors.toMap;

    /**
     * <p>
     * 服务实现类：dep code to category
     * </p>
     *
     * @author zc
     * @since 2023-02-23
     */
    @Service
    @DS("work_performance")
    public class DepCodeToCategoryServiceImpl extends ServiceImpl<DepCodeToCategoryMapper, DepCodeToCategory> implements IDepCodeToCategoryService {
        @Resource
        private IAttendanceEmployeeService attendanceEmployeeService;
        @Lazy
        @Resource
        private IDepartmentCategoryService departmentCategoryService;
        @Resource
        private IDepCategoryRecommendHourService depCategoryRecommendHourService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 在指定 property 下匹配指定 Department Code 对应的 Category id
         *
         * <p>匹配过程中，产生新的 Department Code 与 Category 关系，都进行保存或更新</p>
         *
         * @param propertyId       指定的 property id
         * @param code2Description Map: Department Code -> Department Description
         * @return Department Code 匹配到 Category 的 Map
         * @author zc
         * @date 2023/2/24
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<String, Integer> generateCategoryId4DepCodes(int propertyId, HashMap<String, String> code2Description) {
            if (code2Description.isEmpty()) {
                return Collections.emptyMap();
            }

            // 查找当前版本的 department category
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();
            Integer version = departmentCategoryService.getVersion(propertyId, DateTimeUtils.dateNowTimestamp(timezone));

            // 资产没有配置过推荐工时，创建一个当前的版本，并返回数据
            if (Objects.isNull(version)) {
                // 创建 Category，及 Department Code 与 Category 关系
                version = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
                List<DepCodeToCategory> newVersionDepCodeToCategoryList = createNewVersion(propertyId, version, code2Description);

                // 创建一个推荐工时版本
                depCategoryRecommendHourService.createNewVersion(propertyId, DateTimeUtils.dateNowTimestamp(timezone), newVersionDepCodeToCategoryList);

                return newVersionDepCodeToCategoryList.stream().collect(Collectors.toMap(DepCodeToCategory::getDepartmentCode, DepCodeToCategory::getDepCategoryId));
            }

            // 查找 department category 及 对应的 department code
            List<DepartmentCategory> departmentCategoryList = departmentCategoryService.getByVersion(propertyId, version);
            List<DepCodeToCategory> existDepCodeToCategoryList = list(propertyId, departmentCategoryList.stream().map(DepartmentCategory::getId).toList());

            // 处理当前版本下的 Department Code
            handleDepartmentCode(propertyDTO, version, code2Description, existDepCodeToCategoryList);

            // 返回的 category id 与 department code 对应关系
            return existDepCodeToCategoryList.stream().collect(Collectors.toMap(DepCodeToCategory::getDepartmentCode, DepCodeToCategory::getDepCategoryId));
        }

        /**
         * 处理当前版本下的 Department Code
         *
         * @param propertyDTO                资产
         * @param version                    当前版本
         * @param code2Description           Map: Department Code -> Department Description
         * @param existDepCodeToCategoryList 当前版本下已存在的 Department Code 与 Category 关系
         * @author yyt
         * @date 2025/3/31
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleDepartmentCode(PropertyDTO propertyDTO, Integer version, HashMap<String, String> code2Description, List<DepCodeToCategory> existDepCodeToCategoryList) {
            Integer propertyId = propertyDTO.getId();
            int size = code2Description.size();
            long now = DateTimeUtils.nowTimestamp();
            List<DepartmentCategory> updateNameDepartmentCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepartmentCategory> newDepartmentCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepCodeToCategory> updateNameDepCodeToCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepCodeToCategory> newDepCodeToCategoryList = Lists.newArrayListWithExpectedSize(size);

            Map<String, DepCodeToCategory> existDepCodeToCategoryByDepartmentCode = existDepCodeToCategoryList.stream().collect(toMap(DepCodeToCategory::getDepartmentCode, v -> v));

            // 过滤出报告中有，而 db 中不存在的 department code，进行新增
            code2Description.forEach((code, description) -> {
                DepCodeToCategory existDpCodeToCategory = existDepCodeToCategoryByDepartmentCode.get(code);
                if (Objects.isNull(existDpCodeToCategory)) {
                    DepartmentCategory departmentCategory = new DepartmentCategory(propertyId, description, version,
                            SvaConstant.TYPE_STATIC, BaseConstant.ZERO, now, BaseConstant.ZERO, now);
                    departmentCategory.setDepartmentCode(code);
                    newDepartmentCategoryList.add(departmentCategory);

                    newDepCodeToCategoryList.add(new DepCodeToCategory(propertyId, version, code, description, BaseConstant.ZERO, now));
                } else {
                    // 存在的，则重置 department description
                    if (!existDpCodeToCategory.getDepartmentDesc().equals(description)) {
                        DepartmentCategory updateNameDepartmentCategory = new DepartmentCategory();
                        updateNameDepartmentCategory.setId(existDpCodeToCategory.getDepCategoryId());
                        updateNameDepartmentCategory.setName(description);
                        updateNameDepartmentCategoryList.add(updateNameDepartmentCategory);

                        DepCodeToCategory updateNameDepCodeToCategory = new DepCodeToCategory();
                        updateNameDepCodeToCategory.setId(existDpCodeToCategory.getId());
                        updateNameDepCodeToCategory.setPropertyId(existDpCodeToCategory.getPropertyId());
                        updateNameDepCodeToCategory.setDepartmentCode(existDpCodeToCategory.getDepartmentCode());
                        updateNameDepCodeToCategory.setDepartmentDesc(description);
                        updateNameDepCodeToCategory.setDepCategoryId(existDpCodeToCategory.getDepCategoryId());
                        updateNameDepCodeToCategoryList.add(updateNameDepCodeToCategory);
                    }
                }
            });

            if (CollectionUtils.isNotEmpty(updateNameDepartmentCategoryList)) {
                departmentCategoryService.updateBatchById(updateNameDepartmentCategoryList);
            }
            if (CollectionUtils.isNotEmpty(updateNameDepCodeToCategoryList)) {
                updateBatchById(updateNameDepCodeToCategoryList);

                // 重置员工中绑定的部门名称
                attendanceEmployeeService.updateDepartmentNameBatch(updateNameDepCodeToCategoryList);
            }

            if (CollectionUtils.isNotEmpty(newDepartmentCategoryList)) {
                departmentCategoryService.saveBatch(newDepartmentCategoryList);

                Map<String, Integer> categoryIdByDepartmentCode = newDepartmentCategoryList.stream().collect(toMap(DepartmentCategory::getDepartmentCode, DepartmentCategory::getId));
                newDepCodeToCategoryList.forEach(item -> item.setDepCategoryId(categoryIdByDepartmentCode.get(item.getDepartmentCode())));
                saveBatch(newDepCodeToCategoryList);

                existDepCodeToCategoryList.addAll(newDepCodeToCategoryList);
            }
        }

        /**
         * 在指定 property 下匹配指定 Department Code 对应的 Category id
         *
         * <p>匹配过程中，产生新的 Department Code 与 Category 关系，都进行保存或更新</p>
         *
         * @param propertyId           指定的 property id
         * @param departmentDescByDate 每天(时间戳)的 Department Code 对应 Department Description
         * @return 每天的 Department Code 匹配到 Category 的 Map
         * @author yyt
         * @date 2025/3/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<Long, Map<String, Integer>> generateCategoryId4DepCodes(int propertyId, Map<Long, Map<String, String>> departmentDescByDate) {
            if (MapUtils.isEmpty(departmentDescByDate)) {
                return Collections.emptyMap();
            }
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();

            // 查询适配日期范围下的 各个版本的 Category
            List<Long> dates = new ArrayList<>(departmentDescByDate.keySet());
            Collections.sort(dates);
            long startDate = dates.get(0);
            long endDate = dates.get(dates.size() - 1);
            List<DepCategoryRecommendHour> depCategoryRecommendHours = depCategoryRecommendHourService.listByDateRangeWithNewOne(propertyId, startDate, endDate);

            // 资产没有配置过推荐工时，创建一个当前的版本，并返回数据
            if (CollectionUtils.isEmpty(depCategoryRecommendHours)) {
                // 创建 Category，及 Department Code 与 Category 关系
                Map<String, String> departmentDescByCode = Maps.newHashMapWithExpectedSize(departmentDescByDate.size());
                departmentDescByDate.forEach((date, departmentDescByCodes) -> departmentDescByCode.putAll(departmentDescByCodes));
                int version = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(startDate, timezone), BaseConstant.DATE_FORMAT_YYYYMMDD));
                List<DepCodeToCategory> newVersionDepCodeToCategoryList = createNewVersion(propertyId, version, departmentDescByCode);

                // 创建一个推荐工时版本
                depCategoryRecommendHourService.createNewVersion(propertyId, startDate, newVersionDepCodeToCategoryList);
                Map<Long, Map<String, Integer>> result = Maps.newHashMapWithExpectedSize(dates.size());

                // 返回 department code 对应的 category id
                Map<String, Integer> categoryIdByDepartmentCoded = newVersionDepCodeToCategoryList.stream()
                        .collect(toMap(DepCodeToCategory::getDepartmentCode, DepCodeToCategory::getDepCategoryId));
                for (Long date : dates) {
                    Map<String, String> departmentDescByDepartmentCode = departmentDescByDate.get(date);
                    Map<String, Integer> categoryIdByDepartmentCode = Maps.newHashMapWithExpectedSize(departmentDescByDepartmentCode.size());
                    departmentDescByDepartmentCode.forEach((code, desc) -> {
                        categoryIdByDepartmentCode.put(code, categoryIdByDepartmentCoded.get(code));
                    });
                    result.put(date, categoryIdByDepartmentCode);
                }

                return result;
            }

            // 查询推荐工时 对应的版本
            List<Long> recommendHourStartDates = depCategoryRecommendHours.stream().map(DepCategoryRecommendHour::getStartingDate).collect(Collectors.toList());
            Collections.sort(recommendHourStartDates);
            int minStartDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(recommendHourStartDates.get(0), timezone),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            int maxStartDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(recommendHourStartDates.get(recommendHourStartDates.size() - 1), timezone),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<Integer> versions = departmentCategoryService.listByDateRangeWithNewOne(propertyId, minStartDate, maxStartDate);
            List<DepCodeToCategory> allVersionDepCodeToCategoryList = list(propertyId, departmentCategoryService.listByVersions(propertyId, versions)
                    .stream().map(DepartmentCategory::getId).toList());

            int size = departmentDescByDate.size();
            // 算出每天对应的哪个版本
            List<DateVersionBO> dateVersionBOList = Lists.newArrayListWithExpectedSize(size);
            for (Long date : departmentDescByDate.keySet()) {
                dateVersionBOList.add(new DateVersionBO(date, getVersionByDate(propertyDTO, date, depCategoryRecommendHours, versions)));
            }

            // 处理每个版本对应的 Department code
            handleDepartmentCodeByVersion(propertyDTO, dateVersionBOList, departmentDescByDate, allVersionDepCodeToCategoryList);

            // 返回每天对应的 category id 与 department code 对应关系
            Map<Integer, Map<String, Integer>> categoryIdByVersion = allVersionDepCodeToCategoryList.stream()
                    .collect(Collectors.groupingBy(DepCodeToCategory::getVersion,
                            toMap(DepCodeToCategory::getDepartmentCode, DepCodeToCategory::getDepCategoryId)));
            Map<Long, Map<String, Integer>> result = Maps.newHashMapWithExpectedSize(size);
            for (DateVersionBO dateVersionBO : dateVersionBOList) {
                Map<String, String> departmentDescByDepartmentCode = departmentDescByDate.get(dateVersionBO.getDate());
                Map<String, Integer> categoryIdByCode = categoryIdByVersion.get(dateVersionBO.getVersion());

                Map<String, Integer> categoryIdByDepartmentCode = Maps.newHashMapWithExpectedSize(departmentDescByDepartmentCode.size());
                departmentDescByDepartmentCode.forEach((code, desc) -> categoryIdByDepartmentCode.put(code, categoryIdByCode.get(code)));

                result.put(dateVersionBO.getDate(), categoryIdByDepartmentCode);
            }
            return result;
        }

        /**
         * 处理每个版本对应的 Department code
         *
         * @param propertyDTO                     资产
         * @param dateVersionBOList               多个日期对应的版本
         * @param departmentDescByDate            Department Description 对应的 Department Code
         * @param allVersionDepCodeToCategoryList 多个日期版本下的 Department Code 与 Category 的关联关系
         * @author yyt
         * @date 2025/3/31
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleDepartmentCodeByVersion(PropertyDTO propertyDTO, List<DateVersionBO> dateVersionBOList,
                                                  Map<Long, Map<String, String>> departmentDescByDate,
                                                  List<DepCodeToCategory> allVersionDepCodeToCategoryList) {
            Integer propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();

            long now = DateTimeUtils.nowTimestamp();
            int size = departmentDescByDate.size();
            List<DepartmentCategory> updateNameDepartmentCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepartmentCategory> newDepartmentCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepCodeToCategory> updateNameDepCodeToCategoryList = Lists.newArrayListWithExpectedSize(size);
            List<DepCodeToCategory> newDepCodeToCategoryList = Lists.newArrayListWithExpectedSize(size);
            // 保存每个版本对应下的 Department code
            Map<Integer, List<Long>> datesByVersion = dateVersionBOList.stream().collect(Collectors.groupingBy(DateVersionBO::getVersion,
                    Collectors.mapping(DateVersionBO::getDate, Collectors.toList())));

            // 导入考勤对应的版本的中 有当天正在用版本，则需要重置员工绑定 的部门名称
            Integer currentDateVersion = departmentCategoryService.getVersion(propertyId, DateTimeUtils.dateNowTimestamp(timezone));
            List<DepCodeToCategory> updateEmployeeDepartmentNameDepCodeToCategoryList = Lists.newArrayListWithExpectedSize(size);

            Map<Integer, Map<String, DepCodeToCategory>> existDepCodeToCategoryByVersion = allVersionDepCodeToCategoryList.stream()
                    .collect(Collectors.groupingBy(DepCodeToCategory::getVersion, toMap(DepCodeToCategory::getDepartmentCode, v -> v)));

            datesByVersion.forEach((version, datesForVersion) -> {
                Map<String, String> departmentDescByDepartmentCode = Maps.newHashMap();
                for (Long date : datesForVersion) {
                    departmentDescByDepartmentCode.putAll(departmentDescByDate.get(date));
                }
                Map<String, DepCodeToCategory> existDepCodeToCategoryByDepartmentCode = existDepCodeToCategoryByVersion.getOrDefault(version, Collections.emptyMap());

                departmentDescByDepartmentCode.forEach((code, description) -> {
                    DepCodeToCategory existDpCodeToCategory = existDepCodeToCategoryByDepartmentCode.get(code);
                    if (Objects.isNull(existDpCodeToCategory)) {
                        DepartmentCategory departmentCategory = new DepartmentCategory(propertyId, description, version,
                                SvaConstant.TYPE_STATIC, BaseConstant.ZERO, now, BaseConstant.ZERO, now);
                        departmentCategory.setDepartmentCode(code);
                        newDepartmentCategoryList.add(departmentCategory);

                        newDepCodeToCategoryList.add(new DepCodeToCategory(propertyId, version, code, description, BaseConstant.ZERO, now));
                    } else {
                        // 存在的，则重置 department description
                        if (!existDpCodeToCategory.getDepartmentDesc().equals(description)) {
                            DepartmentCategory updateNameDepartmentCategory = new DepartmentCategory();
                            updateNameDepartmentCategory.setId(existDpCodeToCategory.getDepCategoryId());
                            updateNameDepartmentCategory.setName(description);
                            updateNameDepartmentCategoryList.add(updateNameDepartmentCategory);

                            DepCodeToCategory updateNameDepCodeToCategory = new DepCodeToCategory();
                            updateNameDepCodeToCategory.setId(existDpCodeToCategory.getId());
                            updateNameDepCodeToCategory.setPropertyId(existDpCodeToCategory.getPropertyId());
                            updateNameDepCodeToCategory.setDepartmentCode(existDpCodeToCategory.getDepartmentCode());
                            updateNameDepCodeToCategory.setDepartmentDesc(description);
                            updateNameDepCodeToCategory.setDepCategoryId(existDpCodeToCategory.getDepCategoryId());
                            updateNameDepCodeToCategoryList.add(updateNameDepCodeToCategory);

                            if (Objects.equals(version, currentDateVersion)) {
                                updateEmployeeDepartmentNameDepCodeToCategoryList.add(updateNameDepCodeToCategory);
                            }
                        }
                    }
                });
            });

            if (CollectionUtils.isNotEmpty(updateNameDepartmentCategoryList)) {
                departmentCategoryService.updateBatchById(updateNameDepartmentCategoryList);
            }
            if (CollectionUtils.isNotEmpty(updateNameDepCodeToCategoryList)) {
                updateBatchById(updateNameDepCodeToCategoryList);
            }

            // 重置员工中绑定的部门名称
            if (CollectionUtils.isNotEmpty(updateEmployeeDepartmentNameDepCodeToCategoryList)) {
                attendanceEmployeeService.updateDepartmentNameBatch(updateNameDepCodeToCategoryList);
            }

            if (CollectionUtils.isNotEmpty(newDepartmentCategoryList)) {
                departmentCategoryService.saveBatch(newDepartmentCategoryList);

                Map<String, Integer> categoryIdByDepartmentCode = newDepartmentCategoryList.stream().collect(toMap(DepartmentCategory::getDepartmentCode, DepartmentCategory::getId));
                newDepCodeToCategoryList.forEach(item -> item.setDepCategoryId(categoryIdByDepartmentCode.get(item.getDepartmentCode())));
                saveBatch(newDepCodeToCategoryList);

                allVersionDepCodeToCategoryList.addAll(newDepCodeToCategoryList);
            }
        }

        /**
         * 创建新版本的 Category 及 Department Code 与 Category 关系
         *
         * @param propertyId           资产 id
         * @param newVersion           新版本（yyyyMMdd）
         * @param departmentDescByCode Map: Department Code -> Department Description
         * @return Department Code 与 Category 关系
         * @author yyt
         * @date 2025/3/31
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<DepCodeToCategory> createNewVersion(int propertyId, int newVersion, Map<String, String> departmentDescByCode) {
            List<DepCodeToCategory> depCodeToCategoryList = new ArrayList<>();
            List<DepartmentCategory> departmentCategoryList = new ArrayList<>();
            long now = DateTimeUtils.nowTimestamp();
            departmentDescByCode.forEach((code, description) -> {
                DepCodeToCategory depCodeToCategory = new DepCodeToCategory(propertyId, newVersion, code, description, BaseConstant.ZERO, now);
                depCodeToCategoryList.add(depCodeToCategory);
                DepartmentCategory departmentCategory = new DepartmentCategory(propertyId, description, newVersion, SvaConstant.TYPE_STATIC, BaseConstant.ZERO, now, BaseConstant.ZERO, now);
                departmentCategory.setDepartmentCode(code);
                departmentCategoryList.add(departmentCategory);
            });
            departmentCategoryService.saveBatch(departmentCategoryList);

            Map<String, Integer> categoryIdByDepartmentCode = departmentCategoryList.stream().collect(toMap(DepartmentCategory::getDepartmentCode, DepartmentCategory::getId));
            depCodeToCategoryList.forEach(item -> item.setDepCategoryId(categoryIdByDepartmentCode.get(item.getDepartmentCode())));
            saveBatch(depCodeToCategoryList);

            return depCodeToCategoryList;
        }

        /**
         * 在多个版本中查找指定日期适用的版本
         *
         * @param propertyDTO               资产
         * @param date                      指定日期
         * @param depCategoryRecommendHours 多个版本工时
         * @param versions                  多个版本
         * @return 版本
         * @author yyt
         * @date 2025/3/27
         **/
        @Override
        public Integer getVersionByDate(PropertyDTO propertyDTO, Long date, List<DepCategoryRecommendHour> depCategoryRecommendHours, List<Integer> versions) {
            Optional<DepCategoryRecommendHour> optionalDepCategoryRecommendHour = depCategoryRecommendHours.stream()
                    .filter(item -> item.getStartingDate() <= date)
                    .max(Comparator.comparing(DepCategoryRecommendHour::getStartingDate));
            DepCategoryRecommendHour depCategoryRecommendHour = optionalDepCategoryRecommendHour.get();
            int startingDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(depCategoryRecommendHour.getStartingDate(), propertyDTO.getTimezone()),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            // 再找推荐工时适配的版本
            List<Integer> effectiveVersions = versions.stream().filter(item -> item >= startingDate).collect(Collectors.toList());
            Collections.sort(effectiveVersions);
            return effectiveVersions.get(0);
        }

        /**
         * 检查指定 版本下 部门 code 是否重复
         *
         * @param propertyId     资产 id
         * @param version        版本（yyyyMMdd）
         * @param departmentCode 部门 code
         * @param id             排除自身
         * @return true：重复，false：不重复
         * @author yyt
         * @date 2025/3/24
         **/
        @Override
        public Boolean checkRepeat(Integer propertyId, Integer version, String departmentCode, Integer id) {
            DepCodeToCategory codeToCategory = getOne(new QueryWrapper<DepCodeToCategory>().lambda().eq(DepCodeToCategory::getPropertyId, propertyId)
                    .eq(DepCodeToCategory::getVersion, version)
                    .eq(DepCodeToCategory::getDepartmentCode, departmentCode)
                    .ne(Objects.nonNull(id), DepCodeToCategory::getId, id));
            return Objects.nonNull(codeToCategory);
        }

        /**
         * 删除指定 category id 的部门分类关联关系
         *
         * @param id category id
         * @author yyt
         * @date 2025/3/25
         **/
        @Override
        public void removeByCategoryId(int id) {
            remove(new QueryWrapper<DepCodeToCategory>().lambda().eq(DepCodeToCategory::getDepCategoryId, id));
        }

        /**
         * 查询指定的部门分类关联关系
         *
         * @param propertyId  资产 id
         * @param categoryIds 部门分类 id
         * @return 部门分类关联关系（id，version，department code，department desc，depCategoryId）
         * @author yyt
         * @date 2025/3/25
         **/
        @Override
        public List<DepCodeToCategory> list(int propertyId, List<Integer> categoryIds) {
            return list(new QueryWrapper<DepCodeToCategory>().lambda()
                    .eq(DepCodeToCategory::getPropertyId, propertyId)
                    .in(DepCodeToCategory::getDepCategoryId, categoryIds)
                    .select(DepCodeToCategory::getId, DepCodeToCategory::getVersion, DepCodeToCategory::getDepartmentCode, DepCodeToCategory::getDepartmentDesc,
                            DepCodeToCategory::getDepCategoryId));
        }

        /**
         * 查询指定版本的ADP部门
         *
         * @param propertyId 资产id
         * @param version    版本
         * @return java.util.List<cloud.wconnect.workperformance.entity.sva.setting.DepCodeToCategory>
         * @author lichen
         * @date 2026/3/6
         */
        @Override
        public List<DepCodeToCategory> listByVersion(Integer propertyId, int version) {
            return list(new QueryWrapper<DepCodeToCategory>().lambda()
                    .eq(DepCodeToCategory::getPropertyId, propertyId)
                    .eq(DepCodeToCategory::getVersion, version)
                    .orderByAsc(DepCodeToCategory::getId));
        }

        /**
         * 合并去重部门
         *
         * @param depCodeToCategorys           ADP部门
         * @param svaProductivityRatioSettings 年 productivity ratio settings
         * @return DepartmentDTO 集合
         * @author lichen
         * @date 2026/3/5
         */
        @Override
        public List<DepartmentDTO> distinctMergeDepartment(List<DepCodeToCategory> depCodeToCategorys, List<SvaProductivityRatioSettings> svaProductivityRatioSettings) {
            if (CollectionUtils.isEmpty(depCodeToCategorys) && CollectionUtils.isEmpty(svaProductivityRatioSettings)) {
                return Collections.emptyList();
            }
            if (CollectionUtils.isEmpty(svaProductivityRatioSettings)) {
                return depCodeToCategorys.stream()
                        .map(item -> new DepartmentDTO(item.getDepartmentCode(), item.getDepartmentDesc(), ""))
                        .collect(Collectors.toList());
            }

            Map<Integer, List<SvaProductivityRatioSettings>> year2ProductivityRatioSettings = svaProductivityRatioSettings.stream()
                    .collect(Collectors.groupingBy(SvaProductivityRatioSettings::getYear));

            // ADP 部门映射 (departmentCode -> departmentDesc)
            Map<String, String> depCode2DescByADP = depCodeToCategorys.stream()
                    .collect(Collectors.toMap(
                            DepCodeToCategory::getDepartmentCode,
                            DepCodeToCategory::getDepartmentDesc,
                            (existing, replacement) -> replacement
                    ));

            // 获取所有年份（降序排序，年份大的优先级高）
            List<Integer> sortedYears = year2ProductivityRatioSettings.keySet().stream()
                    .sorted(Comparator.reverseOrder())
                    .toList();

            // 收集所有部门的最新信息（来自 productivity ratio settings）
            // key: departmentCode, value: DepartmentDTO
            ArrayList<DepartmentDTO> depDtoFromSettings = Lists.newArrayListWithCapacity(svaProductivityRatioSettings.size());
            ArrayList<String> depCodeFromSettings = Lists.newArrayListWithCapacity(svaProductivityRatioSettings.size());

            // 按年份降序遍历，确保使用最新的部门信息
            for (Integer year : sortedYears) {
                List<SvaProductivityRatioSettings> settingsList = year2ProductivityRatioSettings.get(year);
                if (settingsList == null || settingsList.isEmpty()) {
                    continue;
                }

                for (SvaProductivityRatioSettings setting : settingsList) {
                    String depCode = setting.getDepartmentCode();

                    // 只保留第一次出现的部门（即年份最大的）
                    if (!depCodeFromSettings.contains(depCode)) {
                        String depDesc = setting.getDepartmentDesc();
                        // 如果 ADP 中也存在该部门，优先使用 ADP 的名称
                        if (depCode2DescByADP.containsKey(depCode)) {
                            depDesc = depCode2DescByADP.get(depCode);
                        }

                        DepartmentDTO dto = new DepartmentDTO(depCode, depDesc, setting.getNote());
                        depDtoFromSettings.add(dto);
                        depCodeFromSettings.add(depCode);
                    }
                }
            }

            // 最终结果列表
            List<DepartmentDTO> result = new ArrayList<>();

            // 第一部分：Settings 中存在的部门
            // 分为两类：ADP 也存在的在前，ADP 不存在的在后
            List<DepartmentDTO> settingsDeptsWithADP = new ArrayList<>();
            List<DepartmentDTO> settingsDeptsWithoutADP = new ArrayList<>();

            for (DepartmentDTO dto : depDtoFromSettings) {
                if (depCode2DescByADP.containsKey(dto.getDepartmentCode())) {
                    settingsDeptsWithADP.add(dto);
                } else {
                    settingsDeptsWithoutADP.add(dto);
                }
            }

            // 先添加 ADP 也存在的部门
            result.addAll(settingsDeptsWithADP);

            // 第二部分：ADP 中存在但 Settings 中不存在的部门
            for (DepCodeToCategory adpDept : depCodeToCategorys) {
                String depCode = adpDept.getDepartmentCode();
                if (!depCodeFromSettings.contains(depCode)) {
                    result.add(new DepartmentDTO(depCode, adpDept.getDepartmentDesc(), ""));
                }
            }

            // 再添加仅 Settings 存在的部门
            result.addAll(settingsDeptsWithoutADP);

            return result;
        }

    }