- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
387 lines
11 KiB
TypeScript
387 lines
11 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
||
import { getServerSession } from 'next-auth';
|
||
import { authOptions } from '@/lib/auth';
|
||
import { prisma } from '@/lib/prisma';
|
||
import { isSystemAdmin } from '@/lib/department-permission';
|
||
|
||
// GET /api/departments/[id] - 获取单个部门详情
|
||
export async function GET(
|
||
request: NextRequest,
|
||
{ params }: { params: { id: string } }
|
||
) {
|
||
try {
|
||
const department = await prisma.department.findUnique({
|
||
where: { id: params.id },
|
||
include: {
|
||
parent: {
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
code: true,
|
||
level: true,
|
||
},
|
||
},
|
||
children: {
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
code: true,
|
||
level: true,
|
||
status: true,
|
||
},
|
||
orderBy: [
|
||
{ sortOrder: 'asc' },
|
||
{ name: 'asc' },
|
||
],
|
||
},
|
||
manager: {
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
realName: true,
|
||
},
|
||
},
|
||
users: {
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
realName: true,
|
||
role: true,
|
||
status: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!department) {
|
||
return NextResponse.json({ error: '部门不存在' }, { status: 404 });
|
||
}
|
||
|
||
return NextResponse.json(department);
|
||
} catch (error) {
|
||
console.error('Error fetching department:', error);
|
||
return NextResponse.json({ error: '获取部门详情失败' }, { status: 500 });
|
||
}
|
||
}
|
||
|
||
// PUT /api/departments/[id] - 更新部门
|
||
export async function PUT(
|
||
request: NextRequest,
|
||
{ params }: { params: { id: string } }
|
||
) {
|
||
try {
|
||
const session = await getServerSession(authOptions);
|
||
// 使用兼容性检查:ADMIN 和 SYSTEM_ADMIN 都可以管理部门
|
||
if (!session || !session.user || !isSystemAdmin((session.user as any).role)) {
|
||
return NextResponse.json({ error: '无权限' }, { status: 403 });
|
||
}
|
||
|
||
const body = await request.json();
|
||
const {
|
||
name,
|
||
parentId: rawParentId,
|
||
code,
|
||
level,
|
||
managerId,
|
||
status,
|
||
description,
|
||
sortOrder,
|
||
} = body;
|
||
|
||
// 规范化parentId:空字符串转换为null
|
||
const parentId = rawParentId === '' || rawParentId === undefined ? null : rawParentId;
|
||
|
||
// 检查部门是否存在
|
||
const existing = await prisma.department.findUnique({
|
||
where: { id: params.id },
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
parentId: true,
|
||
level: true,
|
||
managerId: true,
|
||
children: {
|
||
select: { id: true },
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!existing) {
|
||
return NextResponse.json({ error: '部门不存在' }, { status: 404 });
|
||
}
|
||
|
||
// 构建更新数据
|
||
const updateData: any = {};
|
||
|
||
if (name !== undefined) {
|
||
if (!name || name.trim() === '') {
|
||
return NextResponse.json({ error: '部门名称不能为空' }, { status: 400 });
|
||
}
|
||
|
||
// 检查新名称是否与其他部门冲突
|
||
const nameConflict = await prisma.department.findFirst({
|
||
where: {
|
||
name,
|
||
id: { not: params.id },
|
||
},
|
||
});
|
||
|
||
if (nameConflict) {
|
||
return NextResponse.json({ error: '部门名称已存在' }, { status: 400 });
|
||
}
|
||
|
||
updateData.name = name;
|
||
}
|
||
|
||
// 处理父部门变更
|
||
if (parentId !== undefined) {
|
||
if (parentId === null) {
|
||
// 移除父部门,设置为一级部门
|
||
updateData.parentId = null;
|
||
updateData.level = 1;
|
||
} else {
|
||
// 验证父部门
|
||
if (parentId === params.id) {
|
||
return NextResponse.json({ error: '不能将自己设为父部门' }, { status: 400 });
|
||
}
|
||
|
||
// 检查是否会造成循环引用(不能将自己的子部门设为父部门)
|
||
const isDescendant = await checkIsDescendant(params.id, parentId);
|
||
if (isDescendant) {
|
||
return NextResponse.json({ error: '不能将子部门设为父部门' }, { status: 400 });
|
||
}
|
||
|
||
const parent = await prisma.department.findUnique({
|
||
where: { id: parentId },
|
||
select: { level: true, status: true },
|
||
});
|
||
|
||
if (!parent) {
|
||
return NextResponse.json({ error: '父部门不存在' }, { status: 400 });
|
||
}
|
||
|
||
if (parent.status !== 'active') {
|
||
return NextResponse.json({ error: '父部门未启用' }, { status: 400 });
|
||
}
|
||
|
||
const newLevel = (parent.level ?? 1) + 1;
|
||
if (newLevel > 3) {
|
||
return NextResponse.json({ error: '部门层级不能超过3级' }, { status: 400 });
|
||
}
|
||
|
||
updateData.parentId = parentId;
|
||
updateData.level = newLevel;
|
||
}
|
||
}
|
||
|
||
// 其他可选字段
|
||
if (code !== undefined) updateData.code = code;
|
||
// 注意:level字段应该由parentId自动计算,不应该手动设置
|
||
// 如果传入了level,忽略它(因为我们已经根据parentId计算了level)
|
||
if (managerId !== undefined) {
|
||
updateData.managerId = managerId;
|
||
|
||
// 处理部门管理员角色变更
|
||
// 1. 如果之前有管理员,且管理员发生变化(包括设置为null),取消其管理员角色(如果不再是任何部门的管理员)
|
||
if (existing.managerId && existing.managerId !== managerId) {
|
||
const oldManager = await prisma.user.findUnique({
|
||
where: { id: existing.managerId },
|
||
select: { id: true },
|
||
});
|
||
|
||
if (oldManager) {
|
||
// 检查旧管理员是否还是其他部门的管理员
|
||
const stillManagerOf = await prisma.department.findFirst({
|
||
where: {
|
||
managerId: existing.managerId,
|
||
id: { not: params.id },
|
||
},
|
||
});
|
||
|
||
// 如果不是其他部门的管理员,且角色是DEPT_ADMIN,则降级为USER
|
||
if (!stillManagerOf) {
|
||
await prisma.user.update({
|
||
where: { id: existing.managerId },
|
||
data: { role: 'USER' },
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 如果设置了新管理员,将其角色设置为DEPT_ADMIN
|
||
if (managerId) {
|
||
await prisma.user.update({
|
||
where: { id: managerId },
|
||
data: { role: 'DEPT_ADMIN' },
|
||
});
|
||
}
|
||
}
|
||
if (status !== undefined) {
|
||
if (!['active', 'disabled', 'archived'].includes(status)) {
|
||
return NextResponse.json({ error: '无效的状态值' }, { status: 400 });
|
||
}
|
||
updateData.status = status;
|
||
|
||
// 如果禁用部门,检查是否有子部门
|
||
if (status === 'disabled' && existing.children.length > 0) {
|
||
return NextResponse.json(
|
||
{ error: '该部门有子部门,不能禁用。请先禁用或移动所有子部门' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
}
|
||
if (description !== undefined) updateData.description = description;
|
||
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
|
||
|
||
const updated = await prisma.department.update({
|
||
where: { id: params.id },
|
||
data: updateData,
|
||
include: {
|
||
parent: {
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
code: true,
|
||
level: true,
|
||
},
|
||
},
|
||
manager: {
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
realName: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
return NextResponse.json(updated);
|
||
} catch (error: any) {
|
||
console.error('Error updating department:', error);
|
||
console.error('Error details:', {
|
||
message: error.message,
|
||
code: error.code,
|
||
meta: error.meta,
|
||
stack: error.stack,
|
||
});
|
||
|
||
// 处理唯一约束错误
|
||
if (error.code === 'P2002') {
|
||
if (error.meta?.target?.includes('code')) {
|
||
return NextResponse.json({ error: '部门编码已存在' }, { status: 400 });
|
||
}
|
||
if (error.meta?.target?.includes('name')) {
|
||
return NextResponse.json({ error: '部门名称已存在' }, { status: 400 });
|
||
}
|
||
}
|
||
|
||
// 处理外键约束错误
|
||
if (error.code === 'P2003') {
|
||
return NextResponse.json({ error: '关联数据不存在或无效' }, { status: 400 });
|
||
}
|
||
|
||
// 返回更详细的错误信息(开发环境)
|
||
const errorMessage = process.env.NODE_ENV === 'development'
|
||
? `更新部门失败: ${error.message || '未知错误'}`
|
||
: '更新部门失败';
|
||
|
||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||
}
|
||
}
|
||
|
||
// DELETE /api/departments/[id] - 删除部门
|
||
export async function DELETE(
|
||
request: NextRequest,
|
||
{ params }: { params: { id: string } }
|
||
) {
|
||
try {
|
||
const session = await getServerSession(authOptions);
|
||
// 使用兼容性检查:ADMIN 和 SYSTEM_ADMIN 都可以管理部门
|
||
if (!session || !session.user || !isSystemAdmin((session.user as any).role)) {
|
||
return NextResponse.json({ error: '无权限' }, { status: 403 });
|
||
}
|
||
|
||
// 检查部门是否存在
|
||
const department = await prisma.department.findUnique({
|
||
where: { id: params.id },
|
||
include: {
|
||
children: {
|
||
select: { id: true, name: true },
|
||
},
|
||
users: {
|
||
select: { id: true },
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!department) {
|
||
return NextResponse.json({ error: '部门不存在' }, { status: 404 });
|
||
}
|
||
|
||
// 检查是否有子部门
|
||
if (department.children.length > 0) {
|
||
return NextResponse.json(
|
||
{ error: '该部门有子部门,不能删除。请先删除或移动所有子部门' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 检查是否有用户
|
||
if (department.users.length > 0) {
|
||
return NextResponse.json(
|
||
{ error: '该部门有用户,不能删除。请先将用户移动到其他部门' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
await prisma.department.delete({
|
||
where: { id: params.id },
|
||
});
|
||
|
||
return NextResponse.json({ message: '删除成功' });
|
||
} catch (error) {
|
||
console.error('Error deleting department:', error);
|
||
return NextResponse.json({ error: '删除失败' }, { status: 500 });
|
||
}
|
||
}
|
||
|
||
// 辅助函数:检查 targetId 是否是 sourceId 的后代
|
||
async function checkIsDescendant(sourceId: string, targetId: string): Promise<boolean> {
|
||
// 如果相同,直接返回false(自己不是自己的后代)
|
||
if (sourceId === targetId) return false;
|
||
|
||
// 使用迭代方式递归查询所有子部门
|
||
const allDescendantIds = new Set<string>();
|
||
const queue: string[] = [sourceId];
|
||
const visited = new Set<string>();
|
||
|
||
while (queue.length > 0) {
|
||
const currentId = queue.shift()!;
|
||
|
||
// 避免循环引用
|
||
if (visited.has(currentId)) continue;
|
||
visited.add(currentId);
|
||
|
||
// 查询当前部门的所有直接子部门
|
||
const children = await prisma.department.findMany({
|
||
where: { parentId: currentId },
|
||
select: { id: true },
|
||
});
|
||
|
||
for (const child of children) {
|
||
allDescendantIds.add(child.id);
|
||
// 如果找到目标ID,立即返回true
|
||
if (child.id === targetId) {
|
||
return true;
|
||
}
|
||
queue.push(child.id);
|
||
}
|
||
}
|
||
|
||
return allDescendantIds.has(targetId);
|
||
}
|
||
|