- users/orgs/resources 新增 POST /batch-delete(事务 + 失败汇总 + 用户踢下线) - 角色数据权限自定义:GET/PUT /roles/:roleCode/orgs(roleOrg 写入路径) - assignResources 校验 resourceCodes 存在性;角色 :id 语义改 :roleCode - createTenant 补 createdBy;登录锁定双层阈值(IP+用户名 / 用户名全局) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
import { BadRequestException, Injectable } from '@nestjs/common';
|
||
import { BaseCrudService } from '../common/base-crud.service.js';
|
||
import { PrismaService } from '../prisma/prisma.service.js';
|
||
import { getRequestContext } from '../common/context/request-context.js';
|
||
import { CreateOrgDto, UpdateOrgDto } from './dto.js';
|
||
|
||
@Injectable()
|
||
export class OrgsService extends BaseCrudService {
|
||
constructor(prisma: PrismaService) {
|
||
super(prisma, 'org');
|
||
}
|
||
|
||
/** 组织树列表:按数据权限过滤(空 orgCodes=全部可见)。 */
|
||
listOrgs() {
|
||
const ctx = getRequestContext();
|
||
const where: Record<string, unknown> = {};
|
||
if (ctx && ctx.orgCodes.length > 0) {
|
||
where.code = { in: ctx.orgCodes };
|
||
}
|
||
return this.list(where, { sort: 'asc' });
|
||
}
|
||
|
||
async createOrg(dto: CreateOrgDto) {
|
||
await this.assertValidParent(dto.parentCode);
|
||
return this.create({ ...dto });
|
||
}
|
||
|
||
async updateOrg(id: string, dto: UpdateOrgDto) {
|
||
if (dto.parentCode !== undefined) {
|
||
const current = await this.findById(id);
|
||
await this.assertValidParent(dto.parentCode, current?.code);
|
||
}
|
||
return this.update(id, { ...dto });
|
||
}
|
||
|
||
/** 批量删除组织(软删 + 回收 code),收集失败项。 */
|
||
async batchDelete(ids: string[]) {
|
||
const failed: string[] = [];
|
||
for (const id of ids) {
|
||
try {
|
||
await this.softRemove(id);
|
||
} catch {
|
||
failed.push(id);
|
||
}
|
||
}
|
||
return { total: ids.length, success: ids.length - failed.length, failed };
|
||
}
|
||
|
||
/** 校验父组织存在,且不会构成环(父不能是自身或自身后代)。 */
|
||
private async assertValidParent(parentCode: string | null | undefined, selfCode?: string): Promise<void> {
|
||
if (!parentCode) return; // 根节点
|
||
const parent = await this.db().findFirst({ where: { code: parentCode } });
|
||
if (!parent) throw new BadRequestException('父组织不存在');
|
||
if (!selfCode) return;
|
||
let cur: string | null = parentCode;
|
||
const visited = new Set<string>();
|
||
while (cur && !visited.has(cur)) {
|
||
if (cur === selfCode) throw new BadRequestException('不能将组织挂到自身或其子组织下');
|
||
visited.add(cur);
|
||
const node: { parentCode: string | null } | null = await this.db().findFirst({
|
||
where: { code: cur },
|
||
select: { parentCode: true },
|
||
});
|
||
cur = node?.parentCode ?? null;
|
||
}
|
||
}
|
||
}
|