chenpei ea9076df45 feat: 实现统一的下载错误处理系统并修复产品全案数据库Schema
- 创建统一的下载错误处理模块 (lib/download-errors.ts)
- 实现13种错误类型分类和中文错误消息
- 后端统一使用 createDownloadError 创建标准化错误响应
- 前端统一使用 parseDownloadError 解析错误并显示
- 修复正式系统数据库Schema,添加产品全案相关字段和表
- 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段
- 创建 material_assets, api_keys, api_call_logs 表
2026-01-05 17:52:42 +08:00

387 lines
11 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 { 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);
}