repair-project-stages.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /**
  2. * 项目阶段数据修复脚本
  3. *
  4. * 功能:将没有完成阶段工作的项目回退到正确的阶段
  5. *
  6. * 使用方法:
  7. * 1. 在浏览器控制台粘贴此脚本
  8. * 2. 调用 repairProjectStages() 预览需要修复的项目
  9. * 3. 调用 repairProjectStages(false) 执行实际修复
  10. */
  11. import { FmodeParse } from 'fmode-ng/core';
  12. const Parse = FmodeParse.with('nova');
  13. /**
  14. * 修复项目阶段数据
  15. * @param dryRun 是否为预览模式(默认true,只预览不修复)
  16. */
  17. export async function repairProjectStages(dryRun: boolean = true): Promise<void> {
  18. console.log('\n' + '='.repeat(80));
  19. console.log(dryRun ? '🔍 预览模式:查看需要修复的项目' : '🔧 执行模式:开始修复项目阶段数据');
  20. console.log('='.repeat(80) + '\n');
  21. try {
  22. // 查询所有未删除的项目
  23. const query = new Parse.Query('Project');
  24. query.notEqualTo('isDeleted', true);
  25. query.limit(1000);
  26. const projects = await query.find();
  27. console.log(`📊 共找到 ${projects.length} 个项目\n`);
  28. let needFixCount = 0;
  29. const fixedProjects: Array<{
  30. id: string;
  31. title: string;
  32. oldStage: string;
  33. newStage: string;
  34. reason: string;
  35. }> = [];
  36. for (const project of projects) {
  37. const currentStage = project.get('currentStage') || '订单分配';
  38. const correctStage = await getCorrectStage(project);
  39. // 如果当前阶段与正确阶段不一致
  40. if (currentStage !== correctStage) {
  41. const title = project.get('title') || '未命名项目';
  42. const reason = await getFixReason(project, correctStage);
  43. console.log(`\n${'─'.repeat(60)}`);
  44. console.log(`📋 项目: ${title}`);
  45. console.log(` ID: ${project.id}`);
  46. console.log(` 当前阶段: ${currentStage}`);
  47. console.log(` 应该阶段: ${correctStage}`);
  48. console.log(` 回退原因: ${reason}`);
  49. if (!dryRun) {
  50. // 执行修复
  51. project.set('currentStage', correctStage);
  52. // 清除不合理的审批状态
  53. const data = project.get('data') || {};
  54. if (correctStage === '订单分配' && data.approvalStatus === 'approved') {
  55. const isCompleted = await isOrderStageCompleted(project);
  56. if (!isCompleted) {
  57. data.approvalStatus = null;
  58. project.set('data', data);
  59. console.log(` 🧹 已清除错误的审批状态`);
  60. }
  61. }
  62. await project.save();
  63. console.log(` ✅ 已回退到"${correctStage}"阶段`);
  64. } else {
  65. console.log(` 🔍 [预览] 需要回退到"${correctStage}"阶段`);
  66. }
  67. fixedProjects.push({
  68. id: project.id || 'unknown',
  69. title,
  70. oldStage: currentStage,
  71. newStage: correctStage,
  72. reason
  73. });
  74. needFixCount++;
  75. }
  76. }
  77. console.log(`\n${'='.repeat(80)}`);
  78. if (needFixCount === 0) {
  79. console.log('✅ 所有项目的阶段状态都是正确的,无需修复!');
  80. } else {
  81. if (dryRun) {
  82. console.log(`🔍 预览完成!发现 ${needFixCount} 个项目需要修复`);
  83. console.log(`\n💡 提示:调用 repairProjectStages(false) 执行实际修复`);
  84. } else {
  85. console.log(`✅ 修复完成!共回退 ${needFixCount} 个项目到正确阶段`);
  86. }
  87. console.log('\n📋 修复详情:');
  88. console.table(fixedProjects);
  89. }
  90. console.log('='.repeat(80) + '\n');
  91. } catch (error) {
  92. console.error('❌ 操作失败:', error);
  93. throw error;
  94. }
  95. }
  96. /**
  97. * 获取项目应该处于的正确阶段
  98. */
  99. async function getCorrectStage(project: any): Promise<string> {
  100. // 检查订单分配阶段是否完成
  101. const orderCompleted = await isOrderStageCompleted(project);
  102. if (!orderCompleted) {
  103. return '订单分配';
  104. }
  105. // 检查确认需求阶段是否完成
  106. const requirementsCompleted = await isRequirementsStageCompleted(project);
  107. if (!requirementsCompleted) {
  108. return '确认需求';
  109. }
  110. // 检查交付执行阶段是否完成
  111. const deliveryCompleted = await isDeliveryStageCompleted(project);
  112. if (!deliveryCompleted) {
  113. return '交付执行';
  114. }
  115. // 所有阶段都完成了,应该在售后归档
  116. return '售后归档';
  117. }
  118. /**
  119. * 获取修复原因说明
  120. */
  121. async function getFixReason(project: any, correctStage: string): Promise<string> {
  122. const reasons: string[] = [];
  123. if (correctStage === '订单分配') {
  124. if (!project.get('title')?.trim()) reasons.push('缺少项目名称');
  125. if (!project.get('projectType')) reasons.push('缺少项目类型');
  126. if (!project.get('demoday')) reasons.push('缺少小图日期');
  127. const data = project.get('data') || {};
  128. if (!data.quotation || data.quotation.total <= 0) reasons.push('缺少报价数据');
  129. const query = new Parse.Query('ProjectTeam');
  130. query.equalTo('project', project.toPointer());
  131. query.notEqualTo('isDeleted', true);
  132. const teams = await query.find();
  133. if (teams.length === 0 && data.approvalStatus !== 'approved') {
  134. reasons.push('未分配设计师且未审批');
  135. }
  136. }
  137. if (correctStage === '确认需求') {
  138. const data = project.get('data') || {};
  139. if (!data.requirementsAnalysis || Object.keys(data.requirementsAnalysis).length === 0) {
  140. if (!data.spaceRequirements || Object.keys(data.spaceRequirements).length === 0) {
  141. reasons.push('缺少需求分析数据');
  142. }
  143. }
  144. }
  145. if (correctStage === '交付执行') {
  146. const data = project.get('data') || {};
  147. const deliveryStages = data.deliveryStages || {};
  148. const requiredStages = [
  149. { id: 'modeling', name: '建模' },
  150. { id: 'softDecor', name: '软装' },
  151. { id: 'rendering', name: '渲染' },
  152. { id: 'postProcess', name: '后期' }
  153. ];
  154. for (const stage of requiredStages) {
  155. if (!deliveryStages[stage.id] || deliveryStages[stage.id].approvalStatus !== 'approved') {
  156. reasons.push(`${stage.name}阶段未审批`);
  157. }
  158. }
  159. }
  160. return reasons.length > 0 ? reasons.join(', ') : '未知原因';
  161. }
  162. /**
  163. * 检查订单分配阶段是否完成
  164. */
  165. async function isOrderStageCompleted(project: any): Promise<boolean> {
  166. // 1. 检查基本信息
  167. if (!project.get('title')?.trim()) return false;
  168. if (!project.get('projectType')) return false;
  169. if (!project.get('demoday')) return false;
  170. // 2. 检查报价
  171. const data = project.get('data') || {};
  172. if (!data.quotation || data.quotation.total <= 0) return false;
  173. // 3. 检查设计师分配或审批状态
  174. const query = new Parse.Query('ProjectTeam');
  175. query.equalTo('project', project.toPointer());
  176. query.notEqualTo('isDeleted', true);
  177. const teams = await query.find();
  178. const hasDesigner = teams.length > 0;
  179. const isApproved = data.approvalStatus === 'approved';
  180. // 至少满足一个条件:已分配设计师 或 已审批通过
  181. return hasDesigner || isApproved;
  182. }
  183. /**
  184. * 检查确认需求阶段是否完成
  185. */
  186. async function isRequirementsStageCompleted(project: any): Promise<boolean> {
  187. const data = project.get('data') || {};
  188. // 检查是否有需求分析数据
  189. if (data.requirementsAnalysis && Object.keys(data.requirementsAnalysis).length > 0) {
  190. return true;
  191. }
  192. // 检查是否有空间需求数据
  193. if (data.spaceRequirements && Object.keys(data.spaceRequirements).length > 0) {
  194. return true;
  195. }
  196. // 如果没有任何需求数据,视为未完成
  197. return false;
  198. }
  199. /**
  200. * 检查交付执行阶段是否完成
  201. */
  202. async function isDeliveryStageCompleted(project: any): Promise<boolean> {
  203. const data = project.get('data') || {};
  204. const deliveryStages = data.deliveryStages || {};
  205. // 检查所有必需的交付阶段
  206. const requiredStages = ['modeling', 'softDecor', 'rendering', 'postProcess'];
  207. for (const stageId of requiredStages) {
  208. const stageData = deliveryStages[stageId];
  209. // 如果某个阶段不存在或未审批通过,则交付执行未完成
  210. if (!stageData || stageData.approvalStatus !== 'approved') {
  211. return false;
  212. }
  213. }
  214. // 所有阶段都已审批通过
  215. return true;
  216. }
  217. /**
  218. * 导出项目数据(用于备份)
  219. */
  220. export async function exportProjectsForBackup(): Promise<void> {
  221. console.log('📦 开始导出项目数据...');
  222. const query = new Parse.Query('Project');
  223. query.notEqualTo('isDeleted', true);
  224. query.limit(1000);
  225. const projects = await query.find();
  226. const backup = projects.map(p => ({
  227. id: p.id,
  228. title: p.get('title'),
  229. currentStage: p.get('currentStage'),
  230. projectType: p.get('projectType'),
  231. demoday: p.get('demoday'),
  232. data: p.get('data')
  233. }));
  234. console.log('✅ 导出完成,共', projects.length, '个项目');
  235. console.log('📋 备份数据:', JSON.stringify(backup, null, 2));
  236. // 下载为JSON文件
  237. const dataStr = JSON.stringify(backup, null, 2);
  238. const dataBlob = new Blob([dataStr], { type: 'application/json' });
  239. const url = URL.createObjectURL(dataBlob);
  240. const link = document.createElement('a');
  241. link.href = url;
  242. link.download = `projects-backup-${new Date().toISOString()}.json`;
  243. link.click();
  244. console.log('💾 备份文件已下载');
  245. }
  246. // 导出全局方法(用于浏览器控制台)
  247. if (typeof window !== 'undefined') {
  248. (window as any).repairProjectStages = repairProjectStages;
  249. (window as any).exportProjectsForBackup = exportProjectsForBackup;
  250. console.log('✅ 数据修复工具已加载');
  251. console.log('💡 使用方法:');
  252. console.log(' 1. repairProjectStages() - 预览需要修复的项目');
  253. console.log(' 2. repairProjectStages(false) - 执行实际修复');
  254. console.log(' 3. exportProjectsForBackup() - 导出备份数据');
  255. }