origin/apps/server/src/models/department/department.service.ts

339 lines
10 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable } from '@nestjs/common';
import {
db,
DepartmentMethodSchema,
DeptAncestry,
getUniqueItems,
ObjectType,
Prisma,
} from '@nice/common';
import { BaseTreeService } from '../base/base.tree.service';
import { z } from 'zod';
import { mapToDeptSimpleTree, getStaffsByDeptIds } from './utils';
import EventBus, { CrudOperation } from '@server/utils/event-bus';
@Injectable()
export class DepartmentService extends BaseTreeService<Prisma.DepartmentDelegate> {
constructor() {
super(db, ObjectType.DEPARTMENT, 'deptAncestry', true);
}
async getDescendantIdsInDomain(
ancestorId: string,
includeAncestor = true,
): Promise<string[]> {
// 如果没有提供部门ID返回空数组
if (!ancestorId) return [];
// 获取祖先部门信息
const ancestorDepartment = await db.department.findUnique({
where: { id: ancestorId },
});
// 如果未找到部门,返回空数组
if (!ancestorDepartment) return [];
// 查询同域下以指定部门为祖先的部门血缘关系
const departmentAncestries = await db.deptAncestry.findMany({
where: {
ancestorId: ancestorId,
descendant: {
domainId: ancestorDepartment.domainId,
},
},
});
// 提取子部门ID列表
const descendantDepartmentIds = departmentAncestries.map(
(ancestry) => ancestry.descendantId,
);
// 根据参数决定是否包含祖先部门ID
if (includeAncestor && ancestorId) {
descendantDepartmentIds.push(ancestorId);
}
return descendantDepartmentIds;
}
async getDescendantDomainIds(
ancestorDomainId: string,
includeAncestorDomain = true,
): Promise<string[]> {
if (!ancestorDomainId) return [];
// 查询所有以指定域ID为祖先的域的血缘关系
const domainAncestries = await db.deptAncestry.findMany({
where: {
ancestorId: ancestorDomainId,
descendant: {
isDomain: true,
},
},
});
// 提取子域的ID列表
const descendantDomainIds = domainAncestries.map(
(ancestry) => ancestry.descendantId,
);
// 根据参数决定是否包含祖先域ID
if (includeAncestorDomain && ancestorDomainId) {
descendantDomainIds.push(ancestorDomainId);
}
return descendantDomainIds;
}
/**
* 获取指定DOM下的对应name的单位
* @param domainId
* @param name
* @returns
*/
async findInDomain(domainId: string, name: string) {
const subDepts = await db.deptAncestry.findMany({
where: {
ancestorId: domainId,
},
include: {
descendant: true,
},
});
const dept = subDepts.find((item) => item.descendant.name === name);
return dept.descendant;
}
private async setDomainId(parentId: string) {
const parent = await this.findUnique({ where: { id: parentId } });
return parent.isDomain ? parentId : parent.domainId;
}
async create(args: Prisma.DepartmentCreateArgs) {
if (args.data.parentId) {
args.data.domainId = await this.setDomainId(args.data.parentId);
}
const result = await super.create(args);
EventBus.emit('dataChanged', {
type: this.objectType,
operation: CrudOperation.CREATED,
data: result,
});
return result;
}
async update(args: Prisma.DepartmentUpdateArgs) {
if (args.data.parentId) {
args.data.domainId = await this.setDomainId(args.data.parentId as string);
}
const result = await super.update(args);
EventBus.emit('dataChanged', {
type: this.objectType,
operation: CrudOperation.UPDATED,
data: result,
});
return result;
}
/**
* 删除现有单位并清理DeptAncestry关系。
* @param data - 用于删除现有单位的数据。
* @returns 删除的单位对象。
*/
async softDeleteByIds(ids: string[]) {
const descendantIds = await this.getDescendantIds(ids, true);
const result = await super.softDeleteByIds(descendantIds);
EventBus.emit('dataChanged', {
type: this.objectType,
operation: CrudOperation.DELETED,
data: result,
});
return result;
}
/**
* 获取指定部门及其所有子部门的员工。
* @param deptIds - 要获取员工ID的部门ID数组。
* @returns 包含所有员工ID的数组。
*/
async getStaffsInDepts(deptIds: string[]) {
const allDeptIds = await this.getDescendantIds(deptIds, true);
return await getStaffsByDeptIds(Array.from(allDeptIds));
}
async getStaffIdsInDepts(deptIds: string[]) {
const result = await this.getStaffsInDepts(deptIds);
return result.map((s) => s.id);
}
/**
* 根据部门名称列表和域ID获取多个部门的ID。
*
* @param {string[]} names - 部门名称列表
* @param {string} domainId - 域ID
* @returns {Promise<Record<string, string | null>>} - 返回一个对象键为部门名称值为部门ID或null
*/
async getDeptIdsByNames(
names: string[],
domainId: string,
): Promise<Record<string, string | null>> {
// 使用 Prisma 的 findMany 方法批量查询部门信息,优化性能
const depts = await db.department.findMany({
where: {
// 查询条件:部门名称在给定的名称列表中
name: { in: names },
// 查询条件部门在指定的域下通过ancestors关系查询
ancestors: {
some: {
ancestorId: domainId,
},
},
},
// 选择查询的字段只查询部门的id和name字段
select: {
id: true,
name: true,
},
});
// 创建一个Map对象将部门名称映射到部门ID
const deptMap = new Map(depts.map((dept) => [dept.name, dept.id]));
// 初始化结果对象,用于存储最终的结果
const result: Record<string, string | null> = {};
// 遍历传入的部门名称列表
for (const name of names) {
// 从Map中获取部门ID如果不存在则返回null
result[name] = deptMap.get(name) || null;
}
// 返回最终的结果对象
return result;
}
async getChildSimpleTree(
data: z.infer<typeof DepartmentMethodSchema.getSimpleTree>,
) {
const { domain, deptIds, rootId } = data;
// 提取非空 deptIds
const validDeptIds = deptIds?.filter((id) => id !== null) ?? [];
const hasNullDeptId = deptIds?.includes(null) ?? false;
const [childrenData, selfData] = await Promise.all([
db.deptAncestry.findMany({
where: {
...(deptIds && {
OR: [
...(validDeptIds.length
? [{ ancestorId: { in: validDeptIds } }]
: []),
...(hasNullDeptId ? [{ ancestorId: null }] : []),
],
}),
ancestorId: rootId,
relDepth: 1,
descendant: { isDomain: domain },
},
include: {
descendant: { include: { children: true, deptStaffs: true } },
},
orderBy: { descendant: { order: 'asc' } },
}),
deptIds
? db.department.findMany({
where: {
...(deptIds && {
OR: [
...(validDeptIds.length
? [{ id: { in: validDeptIds } }]
: []),
],
}),
isDomain: domain,
},
include: { children: true },
orderBy: { order: 'asc' },
})
: [],
]);
const children = childrenData
.map(({ descendant }) => descendant)
.filter(Boolean)
.map(mapToDeptSimpleTree);
const selfItems = selfData.map(mapToDeptSimpleTree);
return getUniqueItems([...children, ...selfItems], 'id');
}
/**
* 获取父级部门简单树结构的异步方法
*
* @param data - 包含部门ID、域和根ID的输入参数
* @returns 返回唯一的部门简单树结构数组
*
* 方法整体设计思路:
* 1. 并行查询父级部门ancestry和自身部门数据
* 2. 查询根节点的直接子节点
* 3. 通过自定义过滤函数筛选超出根节点层级的祖先节点
* 4. 将查询结果映射为简单树结构
* 5. 去重并返回最终结果
*/
async getParentSimpleTree(
data: z.infer<typeof DepartmentMethodSchema.getSimpleTree>,
) {
// 解构输入参数
const { deptIds, domain, rootId } = data;
// 并行查询父级部门ancestry和自身部门数据
// 使用Promise.all提高查询效率,减少等待时间
const [parentData, selfData] = await Promise.all([
// 查询指定部门的所有祖先节点,包含子节点和父节点信息
db.deptAncestry.findMany({
where: {
descendantId: { in: deptIds }, // 查询条件:descendant在给定的部门ID列表中
ancestor: { isDomain: domain }, // 限定域
},
include: {
ancestor: {
include: {
children: true, // 包含子节点信息
parent: true, // 包含父节点信息
},
},
},
orderBy: { ancestor: { order: 'asc' } }, // 按祖先节点顺序升序排序
}),
// 查询自身部门数据
db.department.findMany({
where: { id: { in: deptIds }, isDomain: domain },
include: { children: true }, // 包含子节点信息
orderBy: { order: 'asc' }, // 按顺序升序排序
}),
]);
// 查询根节点的直接子节点
const rootChildren = await db.deptAncestry.findMany({
where: {
ancestorId: rootId, // 祖先ID为根ID
descendant: { isDomain: domain }, // 限定域
},
});
/**
* 判断祖先节点是否超出根节点层级的函数
*
* @param ancestor - 祖先节点
* @returns 是否超出根节点层级
*/
const isDirectDescendantOfRoot = (ancestor: DeptAncestry): boolean => {
return (
rootChildren.findIndex(
(child) => child.descendantId === ancestor.ancestorId,
) !== -1
);
};
// 处理父级节点:过滤并映射为简单树结构
const parents = parentData
.map(({ ancestor }) => ancestor) // 提取祖先节点
.filter(
(ancestor) => ancestor && isDirectDescendantOfRoot(ancestor as any),
) // 过滤有效且超出根节点层级的节点
.map(mapToDeptSimpleTree); // 映射为简单树结构
// 处理自身节点:映射为简单树结构
const selfItems = selfData.map(mapToDeptSimpleTree);
// 合并并去重父级和自身节点,返回唯一项
return getUniqueItems([...parents, ...selfItems], 'id');
}
}