migrate-project-phase-deadlines.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * 数据迁移任务:为现有项目添加阶段截止时间
  3. *
  4. * 使用方法:
  5. * Parse.Cloud.startJob('migrateProjectPhaseDeadlines', {
  6. * dryRun: true, // 是否干跑(只计算不保存)
  7. * batchSize: 100 // 批量处理大小
  8. * })
  9. */
  10. Parse.Cloud.job("migrateProjectPhaseDeadlines", async (request) => {
  11. const { params, message } = request;
  12. const { dryRun = false, batchSize = 100 } = params || {};
  13. message(`开始迁移项目阶段截止时间数据...`);
  14. message(`干跑模式: ${dryRun ? '是' : '否'}`);
  15. message(`批处理大小: ${batchSize}`);
  16. // 阶段默认工期(天数)
  17. const DEFAULT_DURATIONS = {
  18. modeling: 1, // 建模默认1天(临时改为1天便于查看效果)
  19. softDecor: 1, // 软装默认1天(临时改为1天便于查看效果)
  20. rendering: 1, // 渲染默认1天(临时改为1天便于查看效果)
  21. postProcessing: 1 // 后期默认1天(临时改为1天便于查看效果)
  22. };
  23. try {
  24. // 查询所有未删除且没有阶段截止时间的项目
  25. const projectQuery = new Parse.Query("Project");
  26. projectQuery.notEqualTo("isDeleted", true);
  27. projectQuery.limit(10000); // 限制最大数量
  28. const totalCount = await projectQuery.count({ useMasterKey: true });
  29. message(`找到${totalCount}个项目待检查`);
  30. let processedCount = 0;
  31. let updatedCount = 0;
  32. let skippedCount = 0;
  33. let errorCount = 0;
  34. // 分批处理
  35. for (let skip = 0; skip < totalCount; skip += batchSize) {
  36. projectQuery.skip(skip);
  37. projectQuery.limit(batchSize);
  38. const projects = await projectQuery.find({ useMasterKey: true });
  39. message(`处理批次: ${skip}-${skip + projects.length}/${totalCount}`);
  40. for (const project of projects) {
  41. try {
  42. processedCount++;
  43. const data = project.get("data") || {};
  44. // 如果已经有phaseDeadlines,跳过
  45. if (data.phaseDeadlines) {
  46. skippedCount++;
  47. continue;
  48. }
  49. // 获取项目截止时间
  50. const deadline = project.get("deadline");
  51. // 如果没有截止时间,也跳过
  52. if (!deadline) {
  53. message(` 项目 ${project.id} (${project.get("title")}) 没有截止时间,跳过`);
  54. skippedCount++;
  55. continue;
  56. }
  57. // 根据项目deadline推算各阶段截止时间
  58. const deadlineTime = deadline.getTime();
  59. // 计算各阶段截止时间(从后往前推)
  60. const postProcessingDeadline = new Date(deadlineTime); // 后期:项目截止日
  61. const renderingDeadline = new Date(deadlineTime - DEFAULT_DURATIONS.postProcessing * 24 * 60 * 60 * 1000); // 渲染:提前3天
  62. const softDecorDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering) * 24 * 60 * 60 * 1000); // 软装:提前9天
  63. const modelingDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering + DEFAULT_DURATIONS.softDecor) * 24 * 60 * 60 * 1000); // 建模:提前13天
  64. // 构建phaseDeadlines对象
  65. const phaseDeadlines = {
  66. modeling: {
  67. deadline: modelingDeadline,
  68. estimatedDays: DEFAULT_DURATIONS.modeling,
  69. status: "not_started",
  70. priority: "medium"
  71. },
  72. softDecor: {
  73. deadline: softDecorDeadline,
  74. estimatedDays: DEFAULT_DURATIONS.softDecor,
  75. status: "not_started",
  76. priority: "medium"
  77. },
  78. rendering: {
  79. deadline: renderingDeadline,
  80. estimatedDays: DEFAULT_DURATIONS.rendering,
  81. status: "not_started",
  82. priority: "medium"
  83. },
  84. postProcessing: {
  85. deadline: postProcessingDeadline,
  86. estimatedDays: DEFAULT_DURATIONS.postProcessing,
  87. status: "not_started",
  88. priority: "medium"
  89. }
  90. };
  91. // 如果不是干跑模式,保存数据
  92. if (!dryRun) {
  93. data.phaseDeadlines = phaseDeadlines;
  94. project.set("data", data);
  95. await project.save(null, { useMasterKey: true });
  96. }
  97. updatedCount++;
  98. // 每10个项目输出一次详细日志
  99. if (updatedCount % 10 === 0) {
  100. message(` ✅ 已更新 ${updatedCount} 个项目`);
  101. }
  102. } catch (error) {
  103. errorCount++;
  104. message(` ❌ 处理项目 ${project.id} 失败: ${error.message}`);
  105. }
  106. }
  107. }
  108. // 输出最终统计
  109. message('');
  110. message('='.repeat(50));
  111. message('迁移完成!统计信息:');
  112. message(` 总处理数: ${processedCount}`);
  113. message(` 已更新: ${updatedCount}`);
  114. message(` 已跳过: ${skippedCount}`);
  115. message(` 失败数: ${errorCount}`);
  116. message(` 干跑模式: ${dryRun ? '是(未实际保存)' : '否(已保存)'}`);
  117. message('='.repeat(50));
  118. } catch (error) {
  119. message(`❌ 迁移失败: ${error.message}`);
  120. throw error;
  121. }
  122. });
  123. /**
  124. * 测试单个项目的阶段截止时间生成
  125. *
  126. * 使用方法:
  127. * Parse.Cloud.run('testProjectPhaseDeadlines', { projectId: 'xxx' })
  128. */
  129. Parse.Cloud.define("testProjectPhaseDeadlines", async (request) => {
  130. const { projectId } = request.params;
  131. if (!projectId) {
  132. throw new Error("缺少projectId参数");
  133. }
  134. const projectQuery = new Parse.Query("Project");
  135. const project = await projectQuery.get(projectId, { useMasterKey: true });
  136. const deadline = project.get("deadline");
  137. if (!deadline) {
  138. throw new Error("项目没有截止时间");
  139. }
  140. const data = project.get("data") || {};
  141. // 默认工期
  142. const DEFAULT_DURATIONS = {
  143. modeling: 1,
  144. softDecor: 1,
  145. rendering: 1,
  146. postProcessing: 1
  147. };
  148. // 计算阶段截止时间
  149. const deadlineTime = deadline.getTime();
  150. const postProcessingDeadline = new Date(deadlineTime);
  151. const renderingDeadline = new Date(deadlineTime - DEFAULT_DURATIONS.postProcessing * 24 * 60 * 60 * 1000);
  152. const softDecorDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering) * 24 * 60 * 60 * 1000);
  153. const modelingDeadline = new Date(deadlineTime - (DEFAULT_DURATIONS.postProcessing + DEFAULT_DURATIONS.rendering + DEFAULT_DURATIONS.softDecor) * 24 * 60 * 60 * 1000);
  154. return {
  155. projectId: project.id,
  156. projectTitle: project.get("title"),
  157. projectDeadline: deadline,
  158. phaseDeadlines: {
  159. modeling: {
  160. deadline: modelingDeadline,
  161. estimatedDays: DEFAULT_DURATIONS.modeling,
  162. daysFromNow: Math.ceil((modelingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
  163. },
  164. softDecor: {
  165. deadline: softDecorDeadline,
  166. estimatedDays: DEFAULT_DURATIONS.softDecor,
  167. daysFromNow: Math.ceil((softDecorDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
  168. },
  169. rendering: {
  170. deadline: renderingDeadline,
  171. estimatedDays: DEFAULT_DURATIONS.rendering,
  172. daysFromNow: Math.ceil((renderingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
  173. },
  174. postProcessing: {
  175. deadline: postProcessingDeadline,
  176. estimatedDays: DEFAULT_DURATIONS.postProcessing,
  177. daysFromNow: Math.ceil((postProcessingDeadline.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
  178. }
  179. },
  180. currentData: data
  181. };
  182. });