# 项目阶段数据修复方案 ## 问题描述 部分项目的 `currentStage` 已经推进到后续阶段,但实际上没有完成前面阶段的必填工作。需要将这些项目回退到正确的阶段。 --- ## 各阶段完成条件分析 ### 阶段1:订单分配 → 确认需求 **必填条件**: 1. ✅ `project.title` 存在且不为空 2. ✅ `project.projectType` 存在(家装/工装) 3. ✅ `project.demoday` 存在(小图日期) 4. ✅ `project.data.quotation.total > 0`(报价总额) 5. ✅ 已分配设计师(ProjectTeam表有记录)**或** `data.approvalStatus === 'approved'`(组长已审批) **验证逻辑**: ```typescript async function isOrderStageCompleted(project: any): Promise { // 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. ✅ 或者至少有一个空间的需求已确认 **验证逻辑**: ```typescript async function isRequirementsStageCompleted(project: any): Promise { 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'` **验证逻辑**: ```typescript async function isDeliveryStageCompleted(project: any): Promise { 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` ```typescript import { FmodeParse } from 'fmode-ng/core'; const Parse = FmodeParse.with('nova'); /** * 修复项目阶段数据 * 将没有完成阶段工作的项目回退到正确的阶段 */ export async function repairProjectStages(): Promise { 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 { // 检查订单分配阶段是否完成 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 { 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 { // 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 { 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 { 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. 复制粘贴以下代码: ```javascript // 数据修复脚本(浏览器版本) (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` ```html
``` **文件**:`src/app/pages/admin/dashboard/dashboard.ts` ```typescript 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`: "交付执行" ← ✅ 回退 **原因**:交付子阶段未全部审批通过 --- ## 安全检查 ### 修复前备份 ```javascript // 导出项目数据(修复前) 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` 参数: ```javascript 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` 反映真实完成状态 - ✅ 用户无法通过导航栏跳过未完成的阶段 - ✅ 项目流程更加规范和可控