176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
/**
|
||
* 任务-详情上传初始化接口
|
||
* 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, purgeDeletedMaterialsByTaskNo } from '@/lib/product-package';
|
||
import { normalizeTaskDetailSourceType } from '@/lib/task-detail-source-type';
|
||
import { getDingtalkApprovalNoError } from '@/lib/dingtalk-approval-no';
|
||
|
||
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 }
|
||
);
|
||
}
|
||
|
||
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 }
|
||
);
|
||
}
|
||
|
||
try {
|
||
const cleanupResult = await purgeDeletedMaterialsByTaskNo(taskNo);
|
||
if (cleanupResult.deletedMaterials > 0) {
|
||
console.log(
|
||
`[Init] 已清理任务号 ${taskNo} 的旧软删素材 ${cleanupResult.deletedMaterials} 条,删除文件 ${cleanupResult.deletedFiles} 个,失败 ${cleanupResult.failedFiles} 个`
|
||
);
|
||
}
|
||
} catch (cleanupError) {
|
||
console.error(`[Init] 清理旧素材失败:`, cleanupError);
|
||
// 保持旧行为:清理失败时仅记录,不阻断新建流程
|
||
}
|
||
}
|
||
|
||
// 创建素材记录(不写入taskNo,上传完成后再写入)
|
||
const material: any = await prisma.material.create({
|
||
data: {
|
||
// taskNo: 不写入,上传完成后再写入
|
||
productModel,
|
||
categoryName,
|
||
sceneName: sceneName || '',
|
||
platformName: platformName || '',
|
||
productCategoryName: productCategoryName || '',
|
||
materialType: '任务-详情',
|
||
// taskStatus: 不写入,上传完成后再写入
|
||
previewUrl: '', // 临时占位,上传完成后更新
|
||
authorName: (session.user as any).username || (session.user as any).realName || session.user.email,
|
||
userId: (session.user as any).id,
|
||
renderer: '', // 任务-详情不需要渲染器
|
||
isFullSet: false,
|
||
taskDetailSourceType,
|
||
taskDetailRequesterUserId: taskDetailRequester.id,
|
||
taskDetailRequesterName: taskDetailRequester.realName || taskDetailRequester.username,
|
||
} 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 }
|
||
);
|
||
}
|
||
}
|