|
|
@@ -0,0 +1,370 @@
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
+import { Department } from './department.entity';
|
|
|
+import { logger } from '@/server/utils/logger';
|
|
|
+import { z } from 'zod';
|
|
|
+
|
|
|
+// 定义查询参数Schema
|
|
|
+const DepartmentQuerySchema = z.object({
|
|
|
+ name: z.string().optional(),
|
|
|
+ status: z.coerce.number().int().min(0).max(1).optional(),
|
|
|
+ parentId: z.coerce.number().int().nullable().optional(),
|
|
|
+ page: z.coerce.number().int().positive().default(1),
|
|
|
+ pageSize: z.coerce.number().int().positive().default(10),
|
|
|
+ sortBy: z.string().default('sortOrder'),
|
|
|
+ sortOrder: z.enum(['asc', 'desc']).default('asc')
|
|
|
+});
|
|
|
+
|
|
|
+// 定义创建部门Schema
|
|
|
+const CreateDepartmentSchema = z.object({
|
|
|
+ name: z.string().min(1).max(50),
|
|
|
+ parentId: z.number().int().positive().nullable(),
|
|
|
+ sortOrder: z.number().int().nullable(),
|
|
|
+ description: z.string().nullable()
|
|
|
+});
|
|
|
+
|
|
|
+// 定义更新部门Schema
|
|
|
+const UpdateDepartmentSchema = CreateDepartmentSchema.partial();
|
|
|
+
|
|
|
+export class DepartmentService {
|
|
|
+ private repository: Repository<Department>;
|
|
|
+
|
|
|
+ constructor(dataSource: DataSource) {
|
|
|
+ this.repository = dataSource.getRepository(Department);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询部门列表,支持分页、条件筛选和排序
|
|
|
+ */
|
|
|
+ async findAll(query: z.infer<typeof DepartmentQuerySchema>) {
|
|
|
+ const validatedQuery = DepartmentQuerySchema.parse(query);
|
|
|
+ const { name, status, parentId, page, pageSize, sortBy, sortOrder } = validatedQuery;
|
|
|
+
|
|
|
+ const skip = (page - 1) * pageSize;
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ const queryBuilder = this.repository.createQueryBuilder('department')
|
|
|
+ .where('department.isDeleted = 0');
|
|
|
+
|
|
|
+ if (name) {
|
|
|
+ queryBuilder.andWhere('department.name LIKE :name', { name: `%${name}%` });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (status !== undefined) {
|
|
|
+ queryBuilder.andWhere('department.status = :status', { status });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parentId !== undefined) {
|
|
|
+ queryBuilder.andWhere('department.parentId = :parentId', { parentId });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ // 将排序方向转换为大写以匹配TypeORM要求
|
|
|
+ queryBuilder.orderBy(`department.${sortBy}`, sortOrder.toUpperCase() as 'ASC' | 'DESC');
|
|
|
+
|
|
|
+ // 分页
|
|
|
+ queryBuilder.skip(skip).take(pageSize);
|
|
|
+
|
|
|
+ const [items, total] = await queryBuilder.getManyAndCount();
|
|
|
+
|
|
|
+ logger.api(`查询部门列表: 共${total}条, 第${page}页, 每页${pageSize}条`);
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: items,
|
|
|
+ pagination: {
|
|
|
+ total,
|
|
|
+ current: page,
|
|
|
+ pageSize,
|
|
|
+ totalPages: Math.ceil(total / pageSize)
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据ID查询部门详情
|
|
|
+ */
|
|
|
+ async findById(id: number) {
|
|
|
+ const department = await this.repository.findOne({
|
|
|
+ where: { id, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!department) {
|
|
|
+ logger.error(`部门不存在: ID=${id}`);
|
|
|
+ throw new Error('部门不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ return department;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建新部门
|
|
|
+ */
|
|
|
+ async create(data: z.infer<typeof CreateDepartmentSchema>) {
|
|
|
+ const validatedData = CreateDepartmentSchema.parse(data);
|
|
|
+
|
|
|
+ // 检查父部门是否存在
|
|
|
+ if (validatedData.parentId) {
|
|
|
+ const parentDepartment = await this.repository.findOne({
|
|
|
+ where: { id: validatedData.parentId, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!parentDepartment) {
|
|
|
+ logger.error(`父部门不存在: ID=${validatedData.parentId}`);
|
|
|
+ throw new Error('父部门不存在');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查部门名称是否已存在
|
|
|
+ const existingDepartment = await this.repository.findOne({
|
|
|
+ where: { name: validatedData.name, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (existingDepartment) {
|
|
|
+ logger.error(`部门名称已存在: ${validatedData.name}`);
|
|
|
+ throw new Error('部门名称已存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ const department = this.repository.create({
|
|
|
+ ...validatedData,
|
|
|
+ status: 1, // 默认启用
|
|
|
+ isDeleted: 0
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await this.repository.save(department);
|
|
|
+ logger.api(`创建部门成功: ID=${result.id}, 名称=${result.name}`);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新部门信息
|
|
|
+ */
|
|
|
+ async update(id: number, data: z.infer<typeof UpdateDepartmentSchema>) {
|
|
|
+ const validatedData = UpdateDepartmentSchema.parse(data);
|
|
|
+
|
|
|
+ // 检查部门是否存在
|
|
|
+ const department = await this.repository.findOne({
|
|
|
+ where: { id, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!department) {
|
|
|
+ logger.error(`部门不存在: ID=${id}`);
|
|
|
+ throw new Error('部门不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果更新父部门,检查父部门是否存在
|
|
|
+ if (validatedData.parentId !== undefined) {
|
|
|
+ // 不能将自己设为父部门
|
|
|
+ if (validatedData.parentId === id) {
|
|
|
+ logger.error(`部门不能将自己设为父部门: ID=${id}`);
|
|
|
+ throw new Error('部门不能将自己设为父部门');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查父部门是否存在
|
|
|
+ if (validatedData.parentId) {
|
|
|
+ const parentDepartment = await this.repository.findOne({
|
|
|
+ where: { id: validatedData.parentId, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!parentDepartment) {
|
|
|
+ logger.error(`父部门不存在: ID=${validatedData.parentId}`);
|
|
|
+ throw new Error('父部门不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否存在循环引用
|
|
|
+ const hasCycle = await this.checkCycleReference(id, validatedData.parentId);
|
|
|
+ if (hasCycle) {
|
|
|
+ logger.error(`部门存在循环引用: ID=${id}, 父部门ID=${validatedData.parentId}`);
|
|
|
+ throw new Error('不能设置子部门为父部门');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果更新名称,检查名称是否已存在
|
|
|
+ if (validatedData.name && validatedData.name !== department.name) {
|
|
|
+ const existingDepartment = await this.repository.findOne({
|
|
|
+ where: { name: validatedData.name, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (existingDepartment) {
|
|
|
+ logger.error(`部门名称已存在: ${validatedData.name}`);
|
|
|
+ throw new Error('部门名称已存在');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新部门信息
|
|
|
+ Object.assign(department, validatedData);
|
|
|
+ const result = await this.repository.save(department);
|
|
|
+ logger.api(`更新部门成功: ID=${id}, 名称=${result.name}`);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除部门
|
|
|
+ */
|
|
|
+ async delete(id: number) {
|
|
|
+ // 检查部门是否存在
|
|
|
+ const department = await this.repository.findOne({
|
|
|
+ where: { id, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!department) {
|
|
|
+ logger.error(`部门不存在: ID=${id}`);
|
|
|
+ throw new Error('部门不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否存在子部门
|
|
|
+ const hasChildren = await this.hasChildren(id);
|
|
|
+ if (hasChildren) {
|
|
|
+ logger.error(`部门存在子部门,无法删除: ID=${id}`);
|
|
|
+ throw new Error('部门存在子部门,无法删除');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否有关联用户
|
|
|
+ const hasUsers = await this.hasUsers(id);
|
|
|
+ if (hasUsers) {
|
|
|
+ logger.error(`部门存在关联用户,无法删除: ID=${id}`);
|
|
|
+ throw new Error('部门存在关联用户,无法删除');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 软删除
|
|
|
+ department.isDeleted = 1;
|
|
|
+ await this.repository.save(department);
|
|
|
+ logger.api(`删除部门成功: ID=${id}, 名称=${department.name}`);
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新部门状态
|
|
|
+ */
|
|
|
+ async updateStatus(id: number, status: number) {
|
|
|
+ // 验证状态值
|
|
|
+ if (![0, 1].includes(status)) {
|
|
|
+ logger.error(`无效的部门状态: ${status}`);
|
|
|
+ throw new Error('无效的部门状态');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查部门是否存在
|
|
|
+ const department = await this.repository.findOne({
|
|
|
+ where: { id, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!department) {
|
|
|
+ logger.error(`部门不存在: ID=${id}`);
|
|
|
+ throw new Error('部门不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果禁用部门,检查是否存在子部门,如果有则一起禁用
|
|
|
+ if (status === 0) {
|
|
|
+ await this.disableWithChildren(id);
|
|
|
+ logger.api(`禁用部门及其子部门: ID=${id}`);
|
|
|
+ } else {
|
|
|
+ department.status = status;
|
|
|
+ await this.repository.save(department);
|
|
|
+ logger.api(`更新部门状态成功: ID=${id}, 状态=${status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取部门的子部门
|
|
|
+ */
|
|
|
+ async getChildren(parentId: number) {
|
|
|
+ const children = await this.repository.find({
|
|
|
+ where: { parentId, isDeleted: 0 },
|
|
|
+ order: { sortOrder: 'ASC' }
|
|
|
+ });
|
|
|
+
|
|
|
+ logger.api(`获取部门子部门: 父部门ID=${parentId}, 子部门数量=${children.length}`);
|
|
|
+
|
|
|
+ return children;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查部门是否存在子部门
|
|
|
+ */
|
|
|
+ private async hasChildren(id: number): Promise<boolean> {
|
|
|
+ const count = await this.repository.count({
|
|
|
+ where: { parentId: id, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ return count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查部门是否有关联用户
|
|
|
+ */
|
|
|
+ private async hasUsers(id: number): Promise<boolean> {
|
|
|
+ // 这里假设存在用户表关联查询,实际实现需要根据项目的用户实体关系调整
|
|
|
+ const queryBuilder = this.repository.createQueryBuilder('department')
|
|
|
+ .leftJoin('department.users', 'user')
|
|
|
+ .where('department.id = :id', { id })
|
|
|
+ .andWhere('department.isDeleted = 0')
|
|
|
+ .andWhere('user.isDeleted = 0');
|
|
|
+
|
|
|
+ const count = await queryBuilder.getCount();
|
|
|
+ return count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否存在循环引用
|
|
|
+ */
|
|
|
+ private async checkCycleReference(childId: number, parentId: number): Promise<boolean> {
|
|
|
+ if (!parentId) return false;
|
|
|
+
|
|
|
+ let currentParentId: number | null = parentId;
|
|
|
+ while (currentParentId) {
|
|
|
+ if (currentParentId === childId) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ const parent = await this.repository.findOne({
|
|
|
+ where: { id: currentParentId, isDeleted: 0 },
|
|
|
+ select: ['parentId']
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!parent) break;
|
|
|
+ currentParentId = parent.parentId;
|
|
|
+ if (currentParentId === null) break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 禁用部门及其所有子部门
|
|
|
+ */
|
|
|
+ private async disableWithChildren(id: number) {
|
|
|
+ // 获取所有子部门ID,包括间接子部门
|
|
|
+ const childIds = await this.getAllChildIds(id);
|
|
|
+
|
|
|
+ // 禁用当前部门和所有子部门
|
|
|
+ await this.repository.createQueryBuilder()
|
|
|
+ .update(Department)
|
|
|
+ .set({ status: 0 })
|
|
|
+ .where('id IN (:...ids)', { ids: [id, ...childIds] })
|
|
|
+ .execute();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取部门的所有子部门ID,包括间接子部门
|
|
|
+ */
|
|
|
+ private async getAllChildIds(parentId: number): Promise<number[]> {
|
|
|
+ const children = await this.repository.find({
|
|
|
+ where: { parentId, isDeleted: 0 },
|
|
|
+ select: ['id']
|
|
|
+ });
|
|
|
+
|
|
|
+ const childIds = children.map(child => child.id);
|
|
|
+ let allChildIds: number[] = [...childIds];
|
|
|
+
|
|
|
+ for (const childId of childIds) {
|
|
|
+ const grandChildIds = await this.getAllChildIds(childId);
|
|
|
+ allChildIds = [...allChildIds, ...grandChildIds];
|
|
|
+ }
|
|
|
+
|
|
|
+ return allChildIds;
|
|
|
+ }
|
|
|
+}
|