origin/apps/server/src/models/base/base.tree.service.ts

417 lines
13 KiB
TypeScript
Raw Normal View History

2025-01-06 08:45:23 +08:00
import { Prisma, PrismaClient } from '@nice/common';
2025-02-06 16:32:52 +08:00
import { BaseService } from './base.service';
import {
DataArgs,
DelegateArgs,
DelegateFuncs,
DelegateReturnTypes,
UpdateOrderArgs,
} from './base.type';
2024-12-30 08:26:40 +08:00
/**
* BaseTreeService provides a generic CRUD interface for a tree prisma model.
* It enables common data operations such as find, create, update, and delete.
*
* @template D - Type for the model delegate, defining available operations.
* @template A - Arguments for the model delegate's operations.
* @template R - Return types for the model delegate's operations.
*/
export class BaseTreeService<
2025-02-06 16:32:52 +08:00
D extends DelegateFuncs,
A extends DelegateArgs<D> = DelegateArgs<D>,
R extends DelegateReturnTypes<D> = DelegateReturnTypes<D>,
2024-12-30 08:26:40 +08:00
> extends BaseService<D, A, R> {
2025-02-06 16:32:52 +08:00
constructor(
protected prisma: PrismaClient,
protected objectType: string,
protected ancestryType: string = objectType + 'Ancestry',
protected enableOrder: boolean = false,
) {
super(prisma, objectType, enableOrder);
}
async getNextOrder(
transaction: any,
parentId: string | null,
parentOrder?: number,
): Promise<number> {
// 查找同层级最后一个节点的 order
const lastOrder = await transaction[this.objectType].findFirst({
where: {
parentId: parentId ?? null,
},
select: { order: true },
orderBy: { order: 'desc' },
} as any);
// 如果有父节点
if (parentId) {
// 获取父节点的 order如果未提供
const parentNodeOrder =
parentOrder ??
(
await transaction[this.objectType].findUnique({
where: { id: parentId },
2024-12-30 08:26:40 +08:00
select: { order: true },
2025-02-06 16:32:52 +08:00
})
)?.order ??
0;
// 如果存在最后一个同层级节点,确保新节点 order 大于最后一个节点
// 否则,新节点 order 设置为父节点 order + 1
return lastOrder
? Math.max(
lastOrder.order + this.ORDER_INTERVAL,
parentNodeOrder + this.ORDER_INTERVAL,
)
: parentNodeOrder + this.ORDER_INTERVAL;
2024-12-30 08:26:40 +08:00
}
2025-02-06 16:32:52 +08:00
// 对于根节点,直接使用最后一个节点的 order + 1
return lastOrder?.order ? lastOrder?.order + this.ORDER_INTERVAL : 1;
}
async create(args: A['create'], params?: any) {
const anyArgs = args as any;
// 如果传入了外部事务,直接使用该事务执行所有操作
// 如果没有外部事务,则创建新事务
const executor = async (transaction: any) => {
if (this.enableOrder) {
anyArgs.data.order = await this.getNextOrder(
transaction,
anyArgs?.data.parentId ?? null,
);
}
const result: any = await super.create(anyArgs, { tx: transaction });
if (anyArgs.data.parentId) {
await transaction[this.objectType].update({
where: { id: anyArgs.data.parentId },
data: { hasChildren: true },
2024-12-30 08:26:40 +08:00
});
2025-02-06 16:32:52 +08:00
}
const newAncestries = anyArgs.data.parentId
? [
...(
await transaction[this.ancestryType].findMany({
where: { descendantId: anyArgs.data.parentId },
select: { ancestorId: true, relDepth: true },
})
).map(({ ancestorId, relDepth }) => ({
ancestorId,
descendantId: result.id,
relDepth: relDepth + 1,
})),
{
ancestorId: result.parentId,
descendantId: result.id,
relDepth: 1,
2024-12-30 08:26:40 +08:00
},
2025-02-06 16:32:52 +08:00
]
: [{ ancestorId: null, descendantId: result.id, relDepth: 1 }];
await transaction[this.ancestryType].createMany({ data: newAncestries });
return result;
};
// 根据是否有外部事务决定执行方式
if (params?.tx) {
return executor(params.tx) as Promise<R['create']>;
} else {
return this.prisma.$transaction(executor) as Promise<R['create']>;
2024-12-30 08:26:40 +08:00
}
2025-02-06 16:32:52 +08:00
}
/**
* parentId更改时管理DeptAncestry关系
* @param data -
* @returns
*/
async update(args: A['update'], params?: any) {
const anyArgs = args as any;
return this.prisma.$transaction(async (transaction) => {
const current = await transaction[this.objectType].findUnique({
where: { id: anyArgs.where.id },
});
if (!current) throw new Error('object not found');
const result: any = await super.update(anyArgs, { tx: transaction });
if (anyArgs.data.parentId !== current.parentId) {
await transaction[this.ancestryType].deleteMany({
where: { descendantId: result.id },
});
// 更新原父级的 hasChildren 状态
if (current.parentId) {
const childrenCount = await transaction[this.objectType].count({
where: { parentId: current.parentId, deletedAt: null },
});
if (childrenCount === 0) {
await transaction[this.objectType].update({
where: { id: current.parentId },
data: { hasChildren: false },
});
}
2024-12-30 08:26:40 +08:00
}
2025-02-06 16:32:52 +08:00
if (anyArgs.data.parentId) {
await transaction[this.objectType].update({
where: { id: anyArgs.data.parentId },
data: { hasChildren: true },
});
const parentAncestries = await transaction[
this.ancestryType
].findMany({
where: { descendantId: anyArgs.data.parentId },
});
const newAncestries = parentAncestries.map(
({ ancestorId, relDepth }) => ({
ancestorId,
descendantId: result.id,
relDepth: relDepth + 1,
}),
);
newAncestries.push({
ancestorId: anyArgs.data.parentId,
descendantId: result.id,
relDepth: 1,
});
await transaction[this.ancestryType].createMany({
data: newAncestries,
});
} else {
await transaction[this.ancestryType].create({
data: { ancestorId: null, descendantId: result.id, relDepth: 0 },
});
2024-12-30 08:26:40 +08:00
}
2025-02-06 16:32:52 +08:00
}
return result;
}) as Promise<R['update']>;
}
/**
* Soft deletes records by setting `isDeleted` to true for the given IDs.
* @param ids - An array of IDs of the records to soft delete.
* @param data - Additional data to update on soft delete. (Optional)
* @returns {Promise<R['update'][]>} - A promise resolving to an array of updated records.
* @example
* const softDeletedUsers = await service.softDeleteByIds(['user_id1', 'user_id2'], { reason: 'Bulk deletion' });
*/
async softDeleteByIds(
ids: string[],
data: Partial<DataArgs<A['update']>> = {}, // Default to empty object
): Promise<R['update'][]> {
return this.prisma.$transaction(async (tx) => {
// 首先找出所有需要软删除的记录的父级ID
const parentIds = await tx[this.objectType].findMany({
where: {
id: { in: ids },
parentId: { not: null },
},
select: { parentId: true },
});
const uniqueParentIds = [...new Set(parentIds.map((p) => p.parentId))];
// 执行软删除
const result = await super.softDeleteByIds(ids, data);
// 删除相关的祖先关系
await tx[this.ancestryType].deleteMany({
where: {
OR: [{ ancestorId: { in: ids } }, { descendantId: { in: ids } }],
},
});
// 更新父级的 hasChildren 状态
if (uniqueParentIds.length > 0) {
for (const parentId of uniqueParentIds) {
const remainingChildrenCount = await tx[this.objectType].count({
where: {
parentId: parentId,
deletedAt: null,
},
});
if (remainingChildrenCount === 0) {
await tx[this.objectType].update({
where: { id: parentId },
data: { hasChildren: false },
2024-12-30 08:26:40 +08:00
});
2025-02-06 16:32:52 +08:00
}
}
}
return result;
}) as Promise<R['update'][]>;
}
getAncestors(ids: string[]) {
if (!ids || ids.length === 0) return [];
const validIds = ids.filter((id) => id != null);
const hasNull = ids.includes(null);
return this.prisma[this.ancestryType].findMany({
where: {
OR: [
{ ancestorId: { in: validIds } },
{ ancestorId: hasNull ? null : undefined },
],
},
});
}
getDescendants(ids: string[]) {
if (!ids || ids.length === 0) return [];
const validIds = ids.filter((id) => id != null);
const hasNull = ids.includes(null);
return this.prisma[this.ancestryType].findMany({
where: {
OR: [
{ ancestorId: { in: validIds } },
{ ancestorId: hasNull ? null : undefined },
],
},
});
}
async getDescendantIds(
ids: string | string[],
includeOriginalIds: boolean = false,
): Promise<string[]> {
// 将单个 ID 转换为数组
const idArray = Array.isArray(ids) ? ids : [ids];
const res = await this.getDescendants(idArray);
const descendantSet = new Set(res?.map((item) => item.descendantId) || []);
if (includeOriginalIds) {
idArray.forEach((id) => descendantSet.add(id));
2024-12-30 08:26:40 +08:00
}
2025-02-06 16:32:52 +08:00
return Array.from(descendantSet).filter(Boolean) as string[];
}
2024-12-30 08:26:40 +08:00
2025-02-06 16:32:52 +08:00
async getAncestorIds(
ids: string | string[],
includeOriginalIds: boolean = false,
): Promise<string[]> {
// 将单个 ID 转换为数组
const idArray = Array.isArray(ids) ? ids : [ids];
2024-12-30 08:26:40 +08:00
2025-02-06 16:32:52 +08:00
const res = await this.getDescendants(idArray);
const ancestorSet = new Set<string>();
2024-12-30 08:26:40 +08:00
2025-02-06 16:32:52 +08:00
// 按深度排序并添加祖先ID
res
?.sort((a, b) => b.relDepth - a.relDepth)
?.forEach((item) => ancestorSet.add(item.ancestorId));
2024-12-30 08:26:40 +08:00
2025-02-06 16:32:52 +08:00
// 根据参数决定是否添加原始ID
if (includeOriginalIds) {
idArray.forEach((id) => ancestorSet.add(id));
}
2024-12-30 08:26:40 +08:00
2025-02-06 16:32:52 +08:00
return Array.from(ancestorSet).filter(Boolean) as string[];
}
async updateOrder(args: UpdateOrderArgs) {
const { id, overId } = args;
return this.prisma.$transaction(async (transaction) => {
// 查找当前节点和目标节点
const currentObject = await transaction[this.objectType].findUnique({
where: { id },
select: { id: true, parentId: true, order: true },
});
const targetObject = await transaction[this.objectType].findUnique({
where: { id: overId },
select: { id: true, parentId: true, order: true },
});
// 验证节点
if (!currentObject || !targetObject) {
throw new Error('Invalid object or target object');
}
// 查找父节点
const parentObject = currentObject.parentId
? await transaction[this.objectType].findUnique({
where: { id: currentObject.parentId },
select: { id: true, order: true },
})
: null;
// 确保在同一父节点下移动
if (currentObject.parentId !== targetObject.parentId) {
throw new Error('Cannot move between different parent nodes');
}
// 查找同层级的所有节点,按 order 排序
const siblingNodes = await transaction[this.objectType].findMany({
where: {
parentId: targetObject.parentId,
},
select: { id: true, order: true },
orderBy: { order: 'asc' },
});
// 找到目标节点和当前节点在兄弟节点中的索引
const targetIndex = siblingNodes.findIndex(
(node) => node.id === targetObject.id,
);
const currentIndex = siblingNodes.findIndex(
(node) => node.id === currentObject.id,
);
// 移除当前节点
siblingNodes.splice(currentIndex, 1);
// 在目标位置插入当前节点
const insertIndex =
currentIndex > targetIndex ? targetIndex + 1 : targetIndex;
siblingNodes.splice(insertIndex, 0, currentObject);
// 重新分配 order
const newOrders = this.redistributeOrder(
siblingNodes,
parentObject?.order || 0,
);
// 批量更新节点的 order
const updatePromises = newOrders.map((nodeOrder, index) =>
transaction[this.objectType].update({
where: { id: siblingNodes[index].id },
data: { order: nodeOrder },
}),
);
await Promise.all(updatePromises);
// 返回更新后的当前节点
return transaction[this.objectType].findUnique({
where: { id: currentObject.id },
});
});
}
// 重新分配 order 的方法
private redistributeOrder(
nodes: Array<{ id: string; order: number }>,
parentOrder: number,
): number[] {
const MIN_CHILD_ORDER = parentOrder + this.ORDER_INTERVAL; // 子节点 order 必须大于父节点
const newOrders: number[] = [];
nodes.forEach((_, index) => {
// 使用等差数列分配 order确保大于父节点
const nodeOrder = MIN_CHILD_ORDER + (index + 1) * this.ORDER_INTERVAL;
newOrders.push(nodeOrder);
});
return newOrders;
}
}