DATA_REPAIR_STAGE_ROLLBACK.md 17 KB

项目阶段数据修复方案

问题描述

部分项目的 currentStage 已经推进到后续阶段,但实际上没有完成前面阶段的必填工作。需要将这些项目回退到正确的阶段。


各阶段完成条件分析

阶段1:订单分配 → 确认需求

必填条件

  1. project.title 存在且不为空
  2. project.projectType 存在(家装/工装)
  3. project.demoday 存在(小图日期)
  4. project.data.quotation.total > 0(报价总额)
  5. ✅ 已分配设计师(ProjectTeam表有记录) data.approvalStatus === 'approved'(组长已审批)

验证逻辑

async function isOrderStageCompleted(project: any): Promise<boolean> {
  // 1. 检查基本信息
  if (!project.get('title')?.trim()) return false;
  if (!project.get('projectType')) return false;
  if (!project.get('demoday')) return false;
  
  // 2. 检查报价
  const data = project.get('data') || {};
  if (!data.quotation || data.quotation.total <= 0) return false;
  
  // 3. 检查设计师分配或审批状态
  const query = new Parse.Query('ProjectTeam');
  query.equalTo('project', project.toPointer());
  query.notEqualTo('isDeleted', true);
  const teams = await query.find();
  
  const hasDesigner = teams.length > 0;
  const isApproved = data.approvalStatus === 'approved';
  
  return hasDesigner || isApproved;
}

阶段2:确认需求 → 交付执行

必填条件

  1. ✅ 所有空间的需求数据已保存(project.data.requirementsAnalysis 存在)
  2. ✅ 或者至少有一个空间的需求已确认

验证逻辑

async function isRequirementsStageCompleted(project: any): Promise<boolean> {
  const data = project.get('data') || {};
  
  // 检查是否有需求分析数据
  if (data.requirementsAnalysis && Object.keys(data.requirementsAnalysis).length > 0) {
    return true;
  }
  
  // 检查是否有空间需求数据
  if (data.spaceRequirements && Object.keys(data.spaceRequirements).length > 0) {
    return true;
  }
  
  return false;
}

阶段3:交付执行 → 售后归档

必填条件

  1. ✅ 所有交付子阶段都已审批通过
  2. data.deliveryStages 中所有阶段的 approvalStatus === 'approved'

验证逻辑

async function isDeliveryStageCompleted(project: any): Promise<boolean> {
  const data = project.get('data') || {};
  const deliveryStages = data.deliveryStages || {};
  
  // 检查所有交付阶段
  const requiredStages = ['modeling', 'softDecor', 'rendering', 'postProcess'];
  
  for (const stage of requiredStages) {
    const stageData = deliveryStages[stage];
    if (!stageData || stageData.approvalStatus !== 'approved') {
      return false;
    }
  }
  
  return true;
}

数据修复脚本

完整修复方法

文件:创建新文件 repair-project-stages.ts

import { FmodeParse } from 'fmode-ng/core';

const Parse = FmodeParse.with('nova');

/**
 * 修复项目阶段数据
 * 将没有完成阶段工作的项目回退到正确的阶段
 */
export async function repairProjectStages(): Promise<void> {
  console.log('🔧 开始修复项目阶段数据...');
  
  try {
    // 查询所有项目
    const query = new Parse.Query('Project');
    query.notEqualTo('isDeleted', true);
    query.limit(1000);
    const projects = await query.find();
    
    console.log(`📊 共找到 ${projects.length} 个项目`);
    
    let fixedCount = 0;
    const fixedProjects: Array<{
      id: string;
      title: string;
      oldStage: string;
      newStage: string;
      reason: string;
    }> = [];
    
    for (const project of projects) {
      const currentStage = project.get('currentStage');
      const correctStage = await getCorrectStage(project);
      
      if (currentStage !== correctStage) {
        console.log(`\n🔄 项目需要修复: ${project.get('title')}`);
        console.log(`   当前阶段: ${currentStage}`);
        console.log(`   应该阶段: ${correctStage}`);
        
        // 记录修复信息
        const fixInfo = {
          id: project.id,
          title: project.get('title'),
          oldStage: currentStage,
          newStage: correctStage,
          reason: await getFixReason(project, correctStage)
        };
        
        // 回退到正确的阶段
        project.set('currentStage', correctStage);
        
        // 清除不合理的审批状态
        const data = project.get('data') || {};
        if (correctStage === '订单分配' && data.approvalStatus === 'approved') {
          // 如果回退到订单分配,但没有完成工作,清除approved状态
          const isCompleted = await isOrderStageCompleted(project);
          if (!isCompleted) {
            data.approvalStatus = null;
            project.set('data', data);
          }
        }
        
        await project.save();
        
        fixedProjects.push(fixInfo);
        fixedCount++;
        
        console.log(`   ✅ 已修复`);
      }
    }
    
    console.log(`\n✅ 修复完成!共修复 ${fixedCount} 个项目`);
    
    if (fixedProjects.length > 0) {
      console.log('\n📋 修复详情:');
      console.table(fixedProjects);
    }
    
    return;
  } catch (error) {
    console.error('❌ 修复失败:', error);
    throw error;
  }
}

/**
 * 获取项目应该处于的正确阶段
 */
async function getCorrectStage(project: any): Promise<string> {
  // 检查订单分配阶段是否完成
  const orderCompleted = await isOrderStageCompleted(project);
  if (!orderCompleted) {
    return '订单分配';
  }
  
  // 检查确认需求阶段是否完成
  const requirementsCompleted = await isRequirementsStageCompleted(project);
  if (!requirementsCompleted) {
    return '确认需求';
  }
  
  // 检查交付执行阶段是否完成
  const deliveryCompleted = await isDeliveryStageCompleted(project);
  if (!deliveryCompleted) {
    return '交付执行';
  }
  
  // 所有阶段都完成了,应该在售后归档
  return '售后归档';
}

/**
 * 获取修复原因说明
 */
async function getFixReason(project: any, correctStage: string): Promise<string> {
  const reasons: string[] = [];
  
  if (correctStage === '订单分配') {
    if (!project.get('title')?.trim()) reasons.push('缺少项目名称');
    if (!project.get('projectType')) reasons.push('缺少项目类型');
    if (!project.get('demoday')) reasons.push('缺少小图日期');
    
    const data = project.get('data') || {};
    if (!data.quotation || data.quotation.total <= 0) reasons.push('缺少报价');
    
    const query = new Parse.Query('ProjectTeam');
    query.equalTo('project', project.toPointer());
    query.notEqualTo('isDeleted', true);
    const teams = await query.find();
    
    if (teams.length === 0 && data.approvalStatus !== 'approved') {
      reasons.push('未分配设计师且未审批');
    }
  }
  
  if (correctStage === '确认需求') {
    const data = project.get('data') || {};
    if (!data.requirementsAnalysis || Object.keys(data.requirementsAnalysis).length === 0) {
      reasons.push('缺少需求分析数据');
    }
  }
  
  if (correctStage === '交付执行') {
    const data = project.get('data') || {};
    const deliveryStages = data.deliveryStages || {};
    const requiredStages = ['modeling', 'softDecor', 'rendering', 'postProcess'];
    
    for (const stage of requiredStages) {
      if (!deliveryStages[stage] || deliveryStages[stage].approvalStatus !== 'approved') {
        reasons.push(`${stage}阶段未完成`);
      }
    }
  }
  
  return reasons.join(', ');
}

/**
 * 检查订单分配阶段是否完成
 */
async function isOrderStageCompleted(project: any): Promise<boolean> {
  // 1. 检查基本信息
  if (!project.get('title')?.trim()) {
    console.log('   ❌ 缺少项目名称');
    return false;
  }
  if (!project.get('projectType')) {
    console.log('   ❌ 缺少项目类型');
    return false;
  }
  if (!project.get('demoday')) {
    console.log('   ❌ 缺少小图日期');
    return false;
  }
  
  // 2. 检查报价
  const data = project.get('data') || {};
  if (!data.quotation || data.quotation.total <= 0) {
    console.log('   ❌ 缺少报价数据');
    return false;
  }
  
  // 3. 检查设计师分配或审批状态
  const query = new Parse.Query('ProjectTeam');
  query.equalTo('project', project.toPointer());
  query.notEqualTo('isDeleted', true);
  const teams = await query.find();
  
  const hasDesigner = teams.length > 0;
  const isApproved = data.approvalStatus === 'approved';
  
  if (!hasDesigner && !isApproved) {
    console.log('   ❌ 未分配设计师且未审批');
    return false;
  }
  
  console.log('   ✅ 订单分配阶段已完成');
  return true;
}

/**
 * 检查确认需求阶段是否完成
 */
async function isRequirementsStageCompleted(project: any): Promise<boolean> {
  const data = project.get('data') || {};
  
  // 检查是否有需求分析数据
  if (data.requirementsAnalysis && Object.keys(data.requirementsAnalysis).length > 0) {
    console.log('   ✅ 确认需求阶段已完成(有需求分析)');
    return true;
  }
  
  // 检查是否有空间需求数据
  if (data.spaceRequirements && Object.keys(data.spaceRequirements).length > 0) {
    console.log('   ✅ 确认需求阶段已完成(有空间需求)');
    return true;
  }
  
  console.log('   ❌ 缺少需求数据');
  return false;
}

/**
 * 检查交付执行阶段是否完成
 */
async function isDeliveryStageCompleted(project: any): Promise<boolean> {
  const data = project.get('data') || {};
  const deliveryStages = data.deliveryStages || {};
  
  // 检查所有交付阶段
  const requiredStages = ['modeling', 'softDecor', 'rendering', 'postProcess'];
  
  for (const stage of requiredStages) {
    const stageData = deliveryStages[stage];
    if (!stageData || stageData.approvalStatus !== 'approved') {
      console.log(`   ❌ ${stage}阶段未完成`);
      return false;
    }
  }
  
  console.log('   ✅ 交付执行阶段已完成');
  return true;
}

使用方法

方法1:在浏览器控制台运行

  1. 打开项目管理页面
  2. 打开浏览器控制台(F12)
  3. 复制粘贴以下代码:

    // 数据修复脚本(浏览器版本)
    (async function repairProjectStages() {
    console.log('🔧 开始修复项目阶段数据...');
      
    const Parse = window.Parse || (await import('parse')).default;
      
    // 查询所有项目
    const query = new Parse.Query('Project');
    query.notEqualTo('isDeleted', true);
    query.limit(1000);
    const projects = await query.find();
      
    console.log(`📊 共找到 ${projects.length} 个项目`);
      
    let fixedCount = 0;
      
    for (const project of projects) {
    const currentStage = project.get('currentStage');
    const data = project.get('data') || {};
        
    // 检查订单分配阶段
    if (currentStage === '确认需求' || currentStage === '交付执行' || currentStage === '售后归档') {
      // 验证订单分配是否完成
      const hasTitle = !!project.get('title')?.trim();
      const hasType = !!project.get('projectType');
      const hasDemoday = !!project.get('demoday');
      const hasQuotation = data.quotation && data.quotation.total > 0;
          
      if (!hasTitle || !hasType || !hasDemoday || !hasQuotation) {
        console.log(`\n🔄 项目需要回退: ${project.get('title')}`);
        console.log(`   原阶段: ${currentStage} → 新阶段: 订单分配`);
        console.log(`   原因: 订单分配阶段未完成`);
            
        project.set('currentStage', '订单分配');
        data.approvalStatus = null;
        project.set('data', data);
        await project.save();
            
        fixedCount++;
        console.log(`   ✅ 已回退`);
        continue;
      }
    }
        
    // 检查确认需求阶段
    if (currentStage === '交付执行' || currentStage === '售后归档') {
      const hasRequirements = (data.requirementsAnalysis && Object.keys(data.requirementsAnalysis).length > 0) ||
                             (data.spaceRequirements && Object.keys(data.spaceRequirements).length > 0);
          
      if (!hasRequirements) {
        console.log(`\n🔄 项目需要回退: ${project.get('title')}`);
        console.log(`   原阶段: ${currentStage} → 新阶段: 确认需求`);
        console.log(`   原因: 确认需求阶段未完成`);
            
        project.set('currentStage', '确认需求');
        await project.save();
            
        fixedCount++;
        console.log(`   ✅ 已回退`);
        continue;
      }
    }
        
    // 检查交付执行阶段
    if (currentStage === '售后归档') {
      const deliveryStages = data.deliveryStages || {};
      const requiredStages = ['modeling', 'softDecor', 'rendering', 'postProcess'];
      let allCompleted = true;
          
      for (const stage of requiredStages) {
        if (!deliveryStages[stage] || deliveryStages[stage].approvalStatus !== 'approved') {
          allCompleted = false;
          break;
        }
      }
          
      if (!allCompleted) {
        console.log(`\n🔄 项目需要回退: ${project.get('title')}`);
        console.log(`   原阶段: ${currentStage} → 新阶段: 交付执行`);
        console.log(`   原因: 交付执行阶段未完成`);
            
        project.set('currentStage', '交付执行');
        await project.save();
            
        fixedCount++;
        console.log(`   ✅ 已回退`);
      }
    }
    }
      
    console.log(`\n✅ 修复完成!共回退 ${fixedCount} 个项目到正确阶段`);
    })();
    

方法2:创建管理后台工具

在管理员后台添加一个"数据修复"按钮:

文件src/app/pages/admin/dashboard/dashboard.html

<div class="admin-tools">
  <button class="btn btn-warning" (click)="repairProjectStages()">
    <svg class="icon">...</svg>
    修复项目阶段数据
  </button>
</div>

文件src/app/pages/admin/dashboard/dashboard.ts

async repairProjectStages() {
  const confirmed = await window?.fmode?.confirm(
    '此操作将检查所有项目的阶段状态,\n' +
    '并将未完成工作的项目回退到正确阶段。\n\n' +
    '是否继续?'
  );
  
  if (!confirmed) return;
  
  try {
    this.loading = true;
    
    // 调用修复方法
    await repairProjectStages();
    
    window?.fmode?.toast?.success('项目阶段数据修复完成!');
    
    // 刷新列表
    await this.loadProjects();
  } catch (error) {
    console.error('修复失败:', error);
    window?.fmode?.alert('修复失败:' + error.message);
  } finally {
    this.loading = false;
  }
}

预期修复结果

示例1:订单分配未完成

修复前

  • currentStage: "确认需求"
  • title: null
  • projectType: null
  • quotation.total: 0

修复后

  • currentStage: "订单分配" ← ✅ 回退
  • data.approvalStatus: null ← ✅ 清除错误状态

原因:缺少项目名称、类型和报价


示例2:确认需求未完成

修复前

  • currentStage: "交付执行"
  • data.requirementsAnalysis: {}(空对象)
  • data.spaceRequirements: undefined

修复后

  • currentStage: "确认需求" ← ✅ 回退

原因:缺少需求分析数据


示例3:交付执行未完成

修复前

  • currentStage: "售后归档"
  • data.deliveryStages.modeling.approvalStatus: "pending"
  • data.deliveryStages.softDecor.approvalStatus: null

修复后

  • currentStage: "交付执行" ← ✅ 回退

原因:交付子阶段未全部审批通过


安全检查

修复前备份

// 导出项目数据(修复前)
const query = new Parse.Query('Project');
const projects = await query.find();
const backup = projects.map(p => ({
  id: p.id,
  title: p.get('title'),
  currentStage: p.get('currentStage'),
  data: p.get('data')
}));

console.log('备份数据:', JSON.stringify(backup, null, 2));

只预览不修复(Dry Run)

修改脚本,添加 dryRun 参数:

async function repairProjectStages(dryRun = true) {
  // ... 检查逻辑 ...
  
  if (dryRun) {
    console.log(`   🔍 [预览] 需要回退到: ${correctStage}`);
    // 不保存,只输出
  } else {
    project.set('currentStage', correctStage);
    await project.save();
    console.log(`   ✅ 已回退`);
  }
}

// 先预览
await repairProjectStages(true);

// 确认后执行
await repairProjectStages(false);

总结

✅ 修复方案

  1. 检查订单分配完成条件:项目名称、类型、日期、报价、设计师
  2. 检查确认需求完成条件:需求分析数据或空间需求数据
  3. 检查交付执行完成条件:所有子阶段审批通过
  4. 回退到正确阶段:将不符合条件的项目回退

📝 使用建议

  1. 先预览再执行:使用 dryRun 模式查看哪些项目需要修复
  2. 小批量测试:先修复几个项目,验证无误后再全量执行
  3. 备份数据:修复前导出项目数据作为备份
  4. 记录日志:保存修复详情,便于审计

🎯 修复效果

  • ✅ 所有项目的 currentStage 反映真实完成状态
  • ✅ 用户无法通过导航栏跳过未完成的阶段
  • ✅ 项目流程更加规范和可控