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

368 lines
12 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 { z, TermSchema, db, Staff, Term, RelationType, ObjectType, Prisma, TermDto } from '@nicestack/common';
import { RolePermsService } from '@server/rbac/roleperms.service';
import { RelationService } from '@server/relation/relation.service';
/**
* Service for managing terms and their ancestries.
*/
@Injectable()
export class TermService {
constructor(private readonly permissionService: RolePermsService, private readonly relations: RelationService) { }
/**
* 生成TermDto对象包含权限和关系信息
* @param staff 当前操作的工作人员
* @param term 当前处理的术语对象,包含其子节点
* @returns 完整的TermDto对象
*/
async genTermDto(staff: Staff, term: Term & { children: Term[] }): Promise<TermDto> {
const { children, ...others } = term as any;
const permissions = this.permissionService.getTermPerms(staff, term);
const relationTypes = [
{ type: RelationType.WATCH, object: ObjectType.DEPARTMENT, key: 'watchDeptIds', limit: undefined },
{ type: RelationType.WATCH, object: ObjectType.STAFF, key: 'watchStaffIds', limit: undefined }
] as const;
type RelationResult = {
[key in typeof relationTypes[number]['key']]: string[];
};
const promises = relationTypes.map(async ({ type, object, key, limit }) => ({
[key]: await this.relations.getEROBids(ObjectType.TERM, type, object, term.id, limit)
}));
const results = await Promise.all(promises);
const mergedResults = Object.assign({}, ...(results as Partial<RelationResult>[]));
return { ...others, ...mergedResults, permissions, hasChildren: term.children.length > 0 };
}
/**
* 获取特定父节点下新的排序值。
*
* @param parentId 父节点ID如果是根节点则为null
* @returns 下一个排序值
*/
private async getNextOrder(parentId?: string) {
let newOrder = 0;
if (parentId) {
const siblingTerms = await db.term.findMany({
where: { parentId },
orderBy: { order: 'desc' },
take: 1,
});
if (siblingTerms.length > 0) {
newOrder = siblingTerms[0].order + 1;
}
} else {
const rootTerms = await db.term.findMany({
where: { parentId: null },
orderBy: { order: 'desc' },
take: 1,
});
if (rootTerms.length > 0) {
newOrder = rootTerms[0].order + 1;
}
}
return newOrder;
}
/**
* 创建关系数据用于批量插入。
*
* @param termId 术语ID
* @param watchDeptIds 监控部门ID数组
* @param watchStaffIds 监控员工ID数组
* @returns 关系数据数组
*/
private createRelations(
termId: string,
watchDeptIds: string[],
watchStaffIds: string[]
) {
const relationsData = [
...watchDeptIds.map(bId => this.relations.buildRelation(termId, bId, ObjectType.TERM, ObjectType.DEPARTMENT, RelationType.WATCH)),
...watchStaffIds.map(bId => this.relations.buildRelation(termId, bId, ObjectType.TERM, ObjectType.STAFF, RelationType.WATCH)),
];
return relationsData;
}
/**
* 创建一个新的术语并根据需要创建祖先关系。
*
* @param data 创建新术语的数据
* @returns 新创建的术语
*/
async create(staff: Staff, data: z.infer<typeof TermSchema.create>) {
const { parentId, watchDeptIds = [], watchStaffIds = [], ...others } = data;
return await db.$transaction(async (trx) => {
const order = await this.getNextOrder(parentId);
const newTerm = await trx.term.create({
data: {
...others,
parentId,
order,
createdBy: staff.id
},
});
if (parentId) {
const parentTerm = await trx.term.findUnique({
where: { id: parentId },
include: { ancestors: true },
});
const ancestries = parentTerm.ancestors.map((ancestor) => ({
ancestorId: ancestor.ancestorId,
descendantId: newTerm.id,
relDepth: ancestor.relDepth + 1,
}));
ancestries.push({
ancestorId: parentTerm.id,
descendantId: newTerm.id,
relDepth: 1,
});
await trx.termAncestry.createMany({ data: ancestries });
}
const relations = this.createRelations(newTerm.id, watchDeptIds, watchStaffIds);
await trx.relation.createMany({ data: relations });
return newTerm;
});
}
/**
* 更新现有术语的数据并在parentId改变时管理术语祖先关系。
*
* @param data 更新术语的数据
* @returns 更新后的术语
*/
async update(data: z.infer<typeof TermSchema.update>) {
return await db.$transaction(async (prisma) => {
const currentTerm = await prisma.term.findUnique({
where: { id: data.id },
});
if (!currentTerm) throw new Error('Term not found');
console.log(data)
const updatedTerm = await prisma.term.update({
where: { id: data.id },
data,
});
if (data.parentId !== currentTerm.parentId) {
await prisma.termAncestry.deleteMany({
where: { descendantId: data.id },
});
if (data.parentId) {
const parentAncestries = await prisma.termAncestry.findMany({
where: { descendantId: data.parentId },
});
const newAncestries = parentAncestries.map(ancestry => ({
ancestorId: ancestry.ancestorId,
descendantId: data.id,
relDepth: ancestry.relDepth + 1,
}));
newAncestries.push({
ancestorId: data.parentId,
descendantId: data.id,
relDepth: 1,
});
await prisma.termAncestry.createMany({
data: newAncestries,
});
const order = await this.getNextOrder(data.parentId);
await prisma.term.update({
where: { id: data.id },
data: { order },
});
}
}
if (data.watchDeptIds || data.watchStaffIds) {
await prisma.relation.deleteMany({ where: { aId: data.id, relationType: { in: [RelationType.WATCH] } } });
const relations = this.createRelations(
data.id,
data.watchDeptIds ?? [],
data.watchStaffIds ?? []
);
await prisma.relation.createMany({ data: relations });
}
return updatedTerm;
});
}
/**
* 根据ID删除现有术语。
*
* @param data 删除术语的数据
* @returns 被删除的术语
*/
async delete(data: z.infer<typeof TermSchema.delete>) {
const { id } = data;
await db.termAncestry.deleteMany({
where: { OR: [{ ancestorId: id }, { descendantId: id }] },
});
const deletedTerm = await db.term.update({
where: { id },
data: {
deletedAt: new Date(),
},
});
return deletedTerm;
}
/**
* 批量删除术语。
*
* @param ids 要删除的术语ID数组
* @returns 已删除的术语列表
*/
async batchDelete(ids: string[]) {
await db.termAncestry.deleteMany({
where: { OR: [{ ancestorId: { in: ids } }, { descendantId: { in: ids } }] },
});
const deletedTerms = await db.term.updateMany({
where: { id: { in: ids } },
data: {
deletedAt: new Date(),
}
});
return deletedTerms;
}
/**
* 查找唯一术语并生成TermDto对象。
*
* @param staff 当前操作的工作人员
* @param id 术语ID
* @returns 包含详细信息的术语对象
*/
async findUnique(staff: Staff, id: string) {
const term = await db.term.findUnique({
where: {
id,
},
include: {
domain: true,
children: true,
},
});
return await this.genTermDto(staff, term);
}
/**
* 获取指定条件下的术语子节点。
*
* @param staff 当前操作的工作人员
* @param data 查询条件
* @returns 子节点术语列表
*/
async getChildren(staff: Staff, data: z.infer<typeof TermSchema.getChildren>) {
const { parentId, domainId, taxonomyId, cursor, limit = 10 } = data;
const extraCondition = await this.permissionService.getTermExtraConditions(staff);
let queryCondition: Prisma.TermWhereInput = { taxonomyId, parentId: parentId === undefined ? null : parentId, domainId, deletedAt: null }
const whereCondition: Prisma.TermWhereInput = {
AND: [extraCondition, queryCondition],
};
console.log(JSON.stringify(whereCondition))
const terms = await db.term.findMany({
where: whereCondition,
include: {
children: {
where: {
deletedAt: null,
},
}
},
take: limit + 1,
cursor: cursor ? { createdAt: cursor.split('_')[0], id: cursor.split('_')[1] } : undefined,
});
let nextCursor: typeof cursor | undefined = undefined;
if (terms.length > limit) {
const nextItem = terms.pop();
nextCursor = `${nextItem.createdAt.toISOString()}_${nextItem!.id}`;
}
const termDtos = await Promise.all(terms.map((item) => this.genTermDto(staff, item)));
return {
items: termDtos,
nextCursor,
};
}
/**
* 获取指定条件下的所有术语子节点。
*
* @param staff 当前操作的工作人员
* @param data 查询条件
* @returns 子节点术语列表
*/
async getAllChildren(staff: Staff, data: z.infer<typeof TermSchema.getChildren>) {
const { parentId, domainId, taxonomyId } = data;
const extraCondition = await this.permissionService.getTermExtraConditions(staff);
let queryCondition: Prisma.TermWhereInput = { taxonomyId, parentId: parentId === undefined ? null : parentId, domainId, deletedAt: null }
const whereCondition: Prisma.TermWhereInput = {
AND: [extraCondition, queryCondition],
};
console.log(JSON.stringify(whereCondition))
const terms = await db.term.findMany({
where: whereCondition,
include: {
children: {
where: {
deletedAt: null,
},
},
},
});
return await Promise.all(terms.map((item) => this.genTermDto(staff, item)));
}
/**
* 根据关键词或ID集合查找术语
* @param data 包含关键词、域ID和ID集合的对象
* @returns 匹配的术语记录列表
*/
async findMany(data: z.infer<typeof TermSchema.findMany>) {
const { keyword, taxonomyId, ids } = data;
return await db.term.findMany({
where: {
deletedAt: null,
taxonomyId,
OR: [
{ name: { contains: keyword } },
{
id: { in: ids }
}
]
},
orderBy: { order: "asc" },
take: 20
});
}
}