9a826c16b0
1. 注册时自动创建根部门(公司名,level=0),作为组织架构顶层节点 2. 部门列表查询时兼容历史组织:若无任何部门,自动补建根部门 3. 创建部门时若未指定父部门,默认挂到根部门下 4. 前端 OrgChart: - 根部门显示"公司"标签,不可编辑/删除 - 新增部门时默认父部门为根部门 - 上级部门下拉移除"无(根部门)"选项,改为列出所有可选部门 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
/**
|
|
* 部门管理路由
|
|
* 提供部门的增删改查(树形结构)
|
|
*/
|
|
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
|