/** * 部门管理路由 * 提供部门的增删改查(树形结构) */ import { Router } from 'express' import { authMiddleware, AuthRequest } from '../middleware/auth' import prisma from '../lib/prisma' import { z } from 'zod' const router = Router() const createDeptSchema = z.object({ name: z.string().min(1, '部门名称必填'), parentId: z.string().nullable().optional(), sortOrder: z.number().int().default(0), description: z.string().max(200).optional(), }) const updateDeptSchema = createDeptSchema.partial() /** 获取部门树 */ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => { try { // 兼容历史组织:若无任何部门,自动创建根部门(公司名) const existing = await prisma.department.findFirst({ where: { orgId: req.user!.orgId! } }) if (!existing) { const org = await prisma.organization.findUnique({ where: { id: req.user!.orgId! } }) if (org) { await prisma.department.create({ data: { orgId: org.id, name: org.name, parentId: null, level: 0, sortOrder: 0, description: '组织根节点', createdBy: req.user!.id, }, }) } } const departments = await prisma.department.findMany({ where: { orgId: req.user!.orgId! }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], include: { _count: { select: { employees: true, positions: true } } }, }) res.json({ success: true, data: departments }) } catch (err) { next(err) } }) /** 创建部门 */ router.post('/', authMiddleware, async (req: AuthRequest, res, next) => { try { const data = createDeptSchema.parse(req.body) // 空字符串视为无父部门,统一转为 null let parentId = data.parentId && data.parentId.trim() !== '' ? data.parentId : null // 若未指定父部门,默认挂到根部门(level=0)下 if (!parentId) { const root = await prisma.department.findFirst({ where: { orgId: req.user!.orgId!, parentId: null }, orderBy: { sortOrder: 'asc' } }) if (root) parentId = root.id } let level = 0 if (parentId) { const parent = await prisma.department.findFirst({ where: { id: parentId, orgId: req.user!.orgId! } }) if (!parent) throw { code: 'NOT_FOUND', message: '父部门不存在' } level = parent.level + 1 } const dept = await prisma.department.create({ data: { name: data.name, parentId, level, sortOrder: data.sortOrder, description: data.description, orgId: req.user!.orgId!, createdBy: req.user!.id, }, }) res.json({ success: true, data: dept }) } catch (err) { next(err) } }) /** 更新部门 */ router.put('/:id', authMiddleware, async (req: AuthRequest, res, next) => { try { const { id } = req.params const data = updateDeptSchema.parse(req.body) // 空字符串视为无父部门,统一转为 null const parentId = data.parentId !== undefined && data.parentId !== null && data.parentId.trim() !== '' ? data.parentId : data.parentId === undefined ? undefined : null // 防止循环引用 if (parentId === id) throw { code: 'VALIDATION_ERROR', message: '不能将自身设为父部门' } let level: number | undefined if (parentId) { const parent = await prisma.department.findFirst({ where: { id: parentId, orgId: req.user!.orgId! } }) if (!parent) throw { code: 'NOT_FOUND', message: '父部门不存在' } level = parent.level + 1 } else if (parentId === null) { level = 0 } const dept = await prisma.department.update({ where: { id }, data: { ...(data.name !== undefined ? { name: data.name } : {}), ...(parentId !== undefined ? { parentId } : {}), ...(data.sortOrder !== undefined ? { sortOrder: data.sortOrder } : {}), ...(data.description !== undefined ? { description: data.description } : {}), ...(level !== undefined ? { level } : {}), }, }) res.json({ success: true, data: dept }) } catch (err) { next(err) } }) /** 删除部门 */ router.delete('/:id', authMiddleware, async (req: AuthRequest, res, next) => { try { const { id } = req.params // 检查是否有子部门 const children = await prisma.department.findFirst({ where: { parentId: id, orgId: req.user!.orgId! } }) if (children) throw { code: 'VALIDATION_ERROR', message: '请先删除子部门' } // 检查是否有关联员工 const employees = await prisma.employee.findFirst({ where: { departmentId: id, orgId: req.user!.orgId! } }) if (employees) throw { code: 'VALIDATION_ERROR', message: '该部门下仍有员工,无法删除' } await prisma.department.delete({ where: { id } }) res.json({ success: true }) } catch (err) { next(err) } }) export default router