sysmgr-nest/src/orgs/orgs.service.ts
liuyun 127707ffde feat(rbac): 批量删除 + 数据权限自定义闭环 + 若干修复
- 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>
2026-10-11 17:42:05 +08:00

68 lines
2.4 KiB
TypeScript
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 { 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;
}
}
}