/** * 任务-详情上传初始化接口 * POST - 创建素材记录,返回上传配置 */ import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { validateTaskNo, isTaskNoExists } from '@/lib/product-package'; import { normalizeTaskDetailSourceType } from '@/lib/task-detail-source-type'; import { getDingtalkApprovalNoError } from '@/lib/dingtalk-approval-no'; import { INCOMPLETE_SHELL_CREATE_DATA } from '@/lib/material-query'; export async function POST(request: NextRequest) { try { // 验证用户登录 const session = await getServerSession(authOptions); if (!session?.user || !(session.user as any).id) { return NextResponse.json( { success: false, error: '未登录' }, { status: 401 } ); } const body = await request.json(); const { taskNo, dingtalkApprovalNo, productModel, categoryName, sceneName, platformName, productCategoryName, } = body; const rawTaskDetailSourceType = body?.taskDetailSourceType; const taskDetailSourceType = normalizeTaskDetailSourceType(rawTaskDetailSourceType); const taskDetailRequesterUserId = String(body?.taskDetailRequesterUserId || '').trim(); const dingtalkApprovalNoError = getDingtalkApprovalNoError(dingtalkApprovalNo); if (dingtalkApprovalNoError) { return NextResponse.json( { success: false, error: dingtalkApprovalNoError }, { status: 400 } ); } if (rawTaskDetailSourceType != null && String(rawTaskDetailSourceType).trim() && !taskDetailSourceType) { return NextResponse.json( { success: false, error: '出图方式只能是 实拍、渲染、AI 或留空' }, { status: 400 } ); } // 验证必填字段(任务号改为可选) if (!productModel || !categoryName) { return NextResponse.json( { success: false, error: 'SPU型号、店铺为必填项' }, { status: 400 } ); } if (!taskDetailRequesterUserId) { return NextResponse.json( { success: false, error: '请选择需求运营人员' }, { status: 400 } ); } const taskDetailRequester = await prisma.user.findFirst({ where: { id: taskDetailRequesterUserId, isActive: true, status: 'active', }, select: { id: true, username: true, realName: true, }, }); if (!taskDetailRequester) { return NextResponse.json( { success: false, error: '所选需求运营人员不存在或已停用' }, { status: 400 } ); } // 作者名取数据库中的用户名,与其它上传入口保持一致。 // 此前直接读 session.user.username / realName,但登录态从未写入这两个字段, // 实际一路兜底存成了邮箱,导致任务-详情素材的作者显示与其它类型不一致。 const uploader = await prisma.user.findUnique({ where: { id: (session.user as any).id }, select: { username: true }, }); if (!uploader) { return NextResponse.json( { success: false, error: '当前登录账号不存在' }, { status: 401 } ); } if (!taskDetailSourceType) { return NextResponse.json( { success: false, error: '任务-详情必须选择出图方式(实拍、渲染或 AI)' }, { status: 400 } ); } // 如果提供了任务号,验证格式和唯一性 if (taskNo && taskNo.trim()) { // 验证任务号格式 if (!validateTaskNo(taskNo)) { return NextResponse.json( { success: false, error: '任务号格式错误,应为 TASK + 12-18位数字,可选的多个子任务号(-2位数字,如 TASK202601141713525624-01-01)' }, { status: 400 } ); } // 检查任务号是否已存在(未删除的素材) const exists = await isTaskNoExists(taskNo); if (exists) { return NextResponse.json( { success: false, error: '任务号已存在' }, { status: 409 } ); } } // 创建素材记录(不写入taskNo,上传完成后再写入) const material: any = await prisma.material.create({ data: { // taskNo: 不写入,上传完成后再写入 productModel, categoryName, sceneName: sceneName || '', platformName: platformName || '', productCategoryName: productCategoryName || '', materialType: '任务-详情', // taskStatus: 不写入,上传完成后再写入 previewUrl: '', // 临时占位,上传完成后更新 authorName: uploader.username, userId: (session.user as any).id, renderer: '', // 任务-详情不需要渲染器 isFullSet: false, taskDetailSourceType, taskDetailRequesterUserId: taskDetailRequester.id, taskDetailRequesterName: taskDetailRequester.realName || taskDetailRequester.username, ...INCOMPLETE_SHELL_CREATE_DATA, } as any, }); // 返回素材ID和任务号(任务号暂不写入数据库,上传完成后再写入) // 任务号为可选,如果为空则不返回taskNo return NextResponse.json({ success: true, data: { materialId: material.id, taskNo: taskNo && taskNo.trim() ? taskNo : null, // 返回taskNo给前端(如果有),complete接口时需要使用 isFullSet: false, message: '初始化成功,可以开始上传文件', }, }); } catch (error) { console.error('[product-package/init] error:', error); const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; console.error('[product-package/init] error details:', { errorMessage, errorStack }); // 如果素材记录已创建但初始化失败,清理记录(当前逻辑中素材记录在验证后才创建,所以这里通常不会有残留记录) // 但为了安全起见,可以添加清理逻辑(如果需要的话) return NextResponse.json( { success: false, error: errorMessage || '服务器错误' }, { status: 500 } ); } }