/** * 数据迁移任务:为现有项目添加阶段截止时间 * * 使用方法: * Parse.Cloud.startJob('migrateProjectPhaseDeadlines', { * dryRun: true, // 是否干跑(只计算不保存) * batchSize: 100 // 批量处理大小 * }) */ Parse.Cloud.job("migrateProjectPhaseDeadlines", async (request) => { const { params, message } = request; const { dryRun = false, batchSize = 100 } = params || {}; message(`开始迁移项目阶段截止时间数据...`); message(`干跑模式: ${dryRun ? '是' : '否'}`); message(`批处理大小: ${batchSize}`); // 阶段默认工期(天数) const DEFAULT_DURATIONS = { modeling: 1, // 建模默认1天(临时改为1天便于查看效果) softDecor: 1, // 软装默认1天(临时改为1天便于查看效果) rendering: 1, // 渲染默认1天(临时改为1天便于查看效果) postProcessing: 1 // 后期默认1天(临时改为1天便于查看效果) }; try { // 查询所有未删除且没有阶段截止时间的项目 const projectQuery = new Parse.Query("Project"); projectQuery.notEqualTo("isDeleted", true); projectQuery.limit(10000); // 限制最大数量 const totalCount = await projectQuery.count({ useMasterKey: true }); message(`找到${totalCount}个项目待检查`); let processedCount = 0; let updatedCount = 0; let skippedCount = 0; let errorCount = 0; // 分批处理 for (let skip = 0; skip < totalCount; skip += batchSize) { projectQuery.skip(skip); projectQuery.limit(batchSize); const projects = await projectQuery.find({ useMasterKey: true }); message(`处理批次: ${skip}-${skip + projects.length}/${totalCount}`); for (const project of projects) { try { processedCount++; const data = project.get("data") || {}; // 如果已经有phaseDeadlines,跳过 if (data.phaseDeadlines) { skippedCount++; continue; } // 获取项目截止时间 const deadline = project.get("deadline"); // 如果没有截止时间,也跳过 if (!deadline) { message(` 项目 ${project.id} (${project.get("title")}) 没有截止时间,跳过`); skippedCount++; continue; } // 根据项目deadline推算各阶段截止时间 const deadlineTime = deadline.getTime(); // 计算各阶段截止时间(从后往前推) const postProcessingDeadline = new Date(deadlineTime); // 后期:项目截止日 const renderingDeadline = new Date(deadlineTime - DEFAULT_DURATIONS.postProcessing * 24 * 60 * 60 * 1000); // 渲染:提前3天 const softDecorDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering) * 24 * 60 * 60 * 1000); // 软装:提前9天 const modelingDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering + DEFAULT_DURATIONS.softDecor) * 24 * 60 * 60 * 1000); // 建模:提前13天 // 构建phaseDeadlines对象 const phaseDeadlines = { modeling: { deadline: modelingDeadline, estimatedDays: DEFAULT_DURATIONS.modeling, status: "not_started", priority: "medium" }, softDecor: { deadline: softDecorDeadline, estimatedDays: DEFAULT_DURATIONS.softDecor, status: "not_started", priority: "medium" }, rendering: { deadline: renderingDeadline, estimatedDays: DEFAULT_DURATIONS.rendering, status: "not_started", priority: "medium" }, postProcessing: { deadline: postProcessingDeadline, estimatedDays: DEFAULT_DURATIONS.postProcessing, status: "not_started", priority: "medium" } }; // 如果不是干跑模式,保存数据 if (!dryRun) { data.phaseDeadlines = phaseDeadlines; project.set("data", data); await project.save(null, { useMasterKey: true }); } updatedCount++; // 每10个项目输出一次详细日志 if (updatedCount % 10 === 0) { message(` ✅ 已更新 ${updatedCount} 个项目`); } } catch (error) { errorCount++; message(` ❌ 处理项目 ${project.id} 失败: ${error.message}`); } } } // 输出最终统计 message(''); message('='.repeat(50)); message('迁移完成!统计信息:'); message(` 总处理数: ${processedCount}`); message(` 已更新: ${updatedCount}`); message(` 已跳过: ${skippedCount}`); message(` 失败数: ${errorCount}`); message(` 干跑模式: ${dryRun ? '是(未实际保存)' : '否(已保存)'}`); message('='.repeat(50)); } catch (error) { message(`❌ 迁移失败: ${error.message}`); throw error; } }); /** * 测试单个项目的阶段截止时间生成 * * 使用方法: * Parse.Cloud.run('testProjectPhaseDeadlines', { projectId: 'xxx' }) */ Parse.Cloud.define("testProjectPhaseDeadlines", async (request) => { const { projectId } = request.params; if (!projectId) { throw new Error("缺少projectId参数"); } const projectQuery = new Parse.Query("Project"); const project = await projectQuery.get(projectId, { useMasterKey: true }); const deadline = project.get("deadline"); if (!deadline) { throw new Error("项目没有截止时间"); } const data = project.get("data") || {}; // 默认工期 const DEFAULT_DURATIONS = { modeling: 1, softDecor: 1, rendering: 1, postProcessing: 1 }; // 计算阶段截止时间 const deadlineTime = deadline.getTime(); const postProcessingDeadline = new Date(deadlineTime); const renderingDeadline = new Date(deadlineTime - DEFAULT_DURATIONS.postProcessing * 24 * 60 * 60 * 1000); const softDecorDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering) * 24 * 60 * 60 * 1000); const modelingDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering + DEFAULT_DURATIONS.softDecor) * 24 * 60 * 60 * 1000); return { projectId: project.id, projectTitle: project.get("title"), projectDeadline: deadline, phaseDeadlines: { modeling: { deadline: modelingDeadline, estimatedDays: DEFAULT_DURATIONS.modeling, daysFromNow: Math.ceil((modelingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000)) }, softDecor: { deadline: softDecorDeadline, estimatedDays: DEFAULT_DURATIONS.softDecor, daysFromNow: Math.ceil((softDecorDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000)) }, rendering: { deadline: renderingDeadline, estimatedDays: DEFAULT_DURATIONS.rendering, daysFromNow: Math.ceil((renderingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000)) }, postProcessing: { deadline: postProcessingDeadline, estimatedDays: DEFAULT_DURATIONS.postProcessing, daysFromNow: Math.ceil((postProcessingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000)) } }, currentData: data }; });