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

368 lines
12 KiB
TypeScript
Raw Normal View History

2024-09-10 10:31:24 +08:00
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 IDnull
* @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
});
}
}