product-space.service.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. import { Injectable } from '@angular/core';
  2. import { FmodeParse } from 'fmode-ng/parse';
  3. const Parse = FmodeParse.with('nova');
  4. export interface Project {
  5. id: string;
  6. name: string; // productName
  7. type: string; // productType
  8. area?: number; // space.area
  9. priority: number; // space.priority
  10. status: string; // Product.status
  11. complexity: string; // space.complexity
  12. metadata?: any; // space metadata
  13. estimatedBudget?: number; // quotation.price
  14. estimatedDuration?: number; // space metadata
  15. order: number;
  16. projectId: string;
  17. designerId?: string; // profile pointer
  18. quotation?: any; // Product.quotation
  19. requirements?: any; // Product.requirements
  20. reviews?: any; // Product.reviews
  21. }
  22. export interface ProductProgress {
  23. productId: string;
  24. stage: string;
  25. progress: number;
  26. status: string;
  27. timeline?: any[];
  28. blockers?: string[];
  29. estimatedCompletion?: Date;
  30. actualCompletion?: Date;
  31. }
  32. export interface ProductRequirement {
  33. productId: string;
  34. productName: string;
  35. productType: string;
  36. colorRequirement: any;
  37. spaceStructureRequirement: any;
  38. materialRequirement: any;
  39. lightingRequirement: any;
  40. specificRequirements: any;
  41. referenceImages?: string[];
  42. referenceFiles?: any[];
  43. }
  44. @Injectable({
  45. providedIn: 'root'
  46. })
  47. export class ProductSpaceService {
  48. constructor() {}
  49. /**
  50. * 创建产品空间(即创建Product)
  51. */
  52. async createProductSpace(projectId: string, spaceData: Partial<Project>): Promise<Project> {
  53. try {
  54. const Product = Parse.Object.extend('Product');
  55. const product = new Product();
  56. // 获取项目
  57. const projectQuery = new Parse.Query('Project');
  58. const project = await projectQuery.get(projectId);
  59. // 设置产品字段
  60. product.set('project', project);
  61. product.set('productName', spaceData.name || '');
  62. product.set('productType', spaceData.type || 'other');
  63. product.set('status', spaceData.status || 'not_started');
  64. // 设置空间信息
  65. product.set('space', {
  66. spaceName: spaceData.name || '',
  67. area: spaceData.area || 0,
  68. dimensions: { length: 0, width: 0, height: 0 },
  69. features: [],
  70. constraints: [],
  71. priority: spaceData.priority || 5,
  72. complexity: spaceData.complexity || 'medium'
  73. });
  74. // 设置报价信息
  75. if (spaceData.estimatedBudget) {
  76. product.set('quotation', {
  77. price: spaceData.estimatedBudget,
  78. currency: 'CNY',
  79. breakdown: {},
  80. status: 'draft',
  81. validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
  82. });
  83. }
  84. // 设置需求信息
  85. product.set('requirements', {
  86. colorRequirement: spaceData.requirements?.colorRequirement || {},
  87. spaceStructureRequirement: spaceData.requirements?.spaceStructureRequirement || {},
  88. materialRequirement: spaceData.requirements?.materialRequirement || {},
  89. lightingRequirement: spaceData.requirements?.lightingRequirement || {},
  90. specificRequirements: spaceData.requirements?.specificRequirements || [],
  91. constraints: {}
  92. });
  93. // 设置设计师
  94. if (spaceData.designerId) {
  95. const designerQuery = new Parse.Query('Profile');
  96. const designer = await designerQuery.get(spaceData.designerId);
  97. product.set('profile', designer);
  98. }
  99. const savedProduct = await product.save();
  100. return this.parseProductData(savedProduct);
  101. } catch (error) {
  102. console.error('创建产品空间失败:', error);
  103. throw error;
  104. }
  105. }
  106. /**
  107. * 获取项目产品空间列表
  108. */
  109. async getProjectProductSpaces(projectId: string): Promise<Project[]> {
  110. try {
  111. const query = new Parse.Query('Product');
  112. query.equalTo('project', {
  113. __type: 'Pointer',
  114. className: 'Project',
  115. objectId: projectId
  116. });
  117. query.include('profile');
  118. query.ascending('createdAt');
  119. const results = await query.find();
  120. const mapped = results.map(product => this.parseProductData(product));
  121. // 去重:按名称(忽略大小写与首尾空格)保留第一个
  122. const seen = new Set<string>();
  123. const unique: Project[] = [];
  124. for (const p of mapped) {
  125. const key = (p.name || '').trim().toLowerCase();
  126. if (!seen.has(key)) {
  127. seen.add(key);
  128. unique.push(p);
  129. }
  130. }
  131. return unique;
  132. } catch (error) {
  133. console.error('获取项目产品空间失败:', error);
  134. return [];
  135. }
  136. }
  137. /**
  138. * 更新产品空间信息
  139. */
  140. async updateProductSpace(productId: string, updateData: Partial<Project>): Promise<Project> {
  141. try {
  142. const query = new Parse.Query('Product');
  143. const product = await query.get(productId);
  144. // 更新产品基本信息
  145. if (updateData.name !== undefined) product.set('productName', updateData.name);
  146. if (updateData.type !== undefined) product.set('productType', updateData.type);
  147. if (updateData.status !== undefined) product.set('status', updateData.status);
  148. // 更新空间信息
  149. if (updateData.area !== undefined || updateData.priority !== undefined || updateData.complexity !== undefined) {
  150. const space = product.get('space') || {};
  151. if (updateData.area !== undefined) space.area = updateData.area;
  152. if (updateData.priority !== undefined) space.priority = updateData.priority;
  153. if (updateData.complexity !== undefined) space.complexity = updateData.complexity;
  154. product.set('space', space);
  155. }
  156. // 更新报价信息
  157. if (updateData.estimatedBudget !== undefined) {
  158. const quotation = product.get('quotation') || {};
  159. quotation.price = updateData.estimatedBudget;
  160. product.set('quotation', quotation);
  161. }
  162. // 更新需求信息
  163. if (updateData.requirements !== undefined) {
  164. const requirements = product.get('requirements') || {};
  165. Object.assign(requirements, updateData.requirements);
  166. product.set('requirements', requirements);
  167. }
  168. // 更新设计师
  169. if (updateData.designerId !== undefined) {
  170. if (updateData.designerId) {
  171. const designerQuery = new Parse.Query('Profile');
  172. const designer = await designerQuery.get(updateData.designerId);
  173. product.set('profile', designer);
  174. } else {
  175. product.unset('profile');
  176. }
  177. }
  178. const savedProduct = await product.save();
  179. return this.parseProductData(savedProduct);
  180. } catch (error) {
  181. console.error('更新产品空间失败:', error);
  182. throw error;
  183. }
  184. }
  185. /**
  186. * 删除产品空间
  187. */
  188. async deleteProductSpace(productId: string): Promise<void> {
  189. try {
  190. const query = new Parse.Query('Product');
  191. const product = await query.get(productId);
  192. await product.destroy();
  193. } catch (error) {
  194. console.error('删除产品空间失败:', error);
  195. throw error;
  196. }
  197. }
  198. /**
  199. * 更新产品进度
  200. */
  201. async updateProductProgress(progressData: ProductProgress): Promise<void> {
  202. try {
  203. // 在Product的data字段中存储进度信息
  204. const query = new Parse.Query('Product');
  205. const product = await query.get(progressData.productId);
  206. const data = product.get('data') || {};
  207. if (!data.progress) data.progress = [];
  208. // 查找现有进度记录
  209. let progressRecord = data.progress.find((p: any) => p.stage === progressData.stage);
  210. if (!progressRecord) {
  211. // 创建新进度记录
  212. progressRecord = {
  213. stage: progressData.stage,
  214. progress: 0,
  215. status: 'not_started',
  216. timeline: [],
  217. blockers: []
  218. };
  219. data.progress.push(progressRecord);
  220. }
  221. // 更新进度数据
  222. progressRecord.progress = progressData.progress;
  223. progressRecord.status = progressData.status;
  224. progressRecord.timeline = progressData.timeline || [];
  225. progressRecord.blockers = progressData.blockers || [];
  226. progressRecord.estimatedCompletion = progressData.estimatedCompletion;
  227. progressRecord.actualCompletion = progressData.actualCompletion;
  228. product.set('data', data);
  229. await product.save();
  230. } catch (error) {
  231. console.error('更新产品进度失败:', error);
  232. throw error;
  233. }
  234. }
  235. /**
  236. * 获取产品进度
  237. */
  238. async getProductProgress(productId: string, stage?: string): Promise<ProductProgress[]> {
  239. try {
  240. const query = new Parse.Query('Product');
  241. const product = await query.get(productId);
  242. const data = product.get('data') || {};
  243. const progressData = data.progress || [];
  244. let result = progressData;
  245. if (stage) {
  246. result = progressData.filter((p: any) => p.stage === stage);
  247. }
  248. return result.map((p: any) => ({
  249. productId: productId,
  250. stage: p.stage,
  251. progress: p.progress,
  252. status: p.status,
  253. timeline: p.timeline,
  254. blockers: p.blockers,
  255. estimatedCompletion: p.estimatedCompletion,
  256. actualCompletion: p.actualCompletion
  257. }));
  258. } catch (error) {
  259. console.error('获取产品进度失败:', error);
  260. return [];
  261. }
  262. }
  263. /**
  264. * 创建产品依赖关系
  265. */
  266. async createProductDependency(
  267. projectId: string,
  268. fromProductId: string,
  269. toProductId: string,
  270. dependencyType: string,
  271. description: string
  272. ): Promise<void> {
  273. try {
  274. // 在项目的data字段中存储产品依赖关系
  275. const projectQuery = new Parse.Query('Project');
  276. const project = await projectQuery.get(projectId);
  277. const data = project.get('data') || {};
  278. if (!data.productDependencies) data.productDependencies = [];
  279. const dependency = {
  280. id: `dep_${Date.now()}`,
  281. fromProductId: fromProductId,
  282. toProductId: toProductId,
  283. type: dependencyType,
  284. description: description,
  285. status: 'pending',
  286. confidence: 0.8,
  287. createdAt: new Date()
  288. };
  289. data.productDependencies.push(dependency);
  290. project.set('data', data);
  291. await project.save();
  292. } catch (error) {
  293. console.error('创建产品依赖失败:', error);
  294. throw error;
  295. }
  296. }
  297. /**
  298. * 获取产品依赖关系
  299. */
  300. async getProductDependencies(projectId: string): Promise<any[]> {
  301. try {
  302. const projectQuery = new Parse.Query('Project');
  303. const project = await projectQuery.get(projectId);
  304. const data = project.get('data') || {};
  305. return data.productDependencies || [];
  306. } catch (error) {
  307. console.error('获取产品依赖失败:', error);
  308. return [];
  309. }
  310. }
  311. /**
  312. * 保存产品需求
  313. */
  314. async saveProductRequirements(
  315. _projectId: string,
  316. productId: string,
  317. requirements: ProductRequirement
  318. ): Promise<void> {
  319. try {
  320. const query = new Parse.Query('Product');
  321. const product = await query.get(productId);
  322. // 更新产品的需求信息
  323. product.set('requirements', {
  324. colorRequirement: requirements.colorRequirement,
  325. spaceStructureRequirement: requirements.spaceStructureRequirement,
  326. materialRequirement: requirements.materialRequirement,
  327. lightingRequirement: requirements.lightingRequirement,
  328. specificRequirements: requirements.specificRequirements,
  329. constraints: {}
  330. });
  331. await product.save();
  332. } catch (error) {
  333. console.error('保存产品需求失败:', error);
  334. throw error;
  335. }
  336. }
  337. /**
  338. * 获取产品需求
  339. */
  340. async getProductRequirements(projectId: string, productId?: string): Promise<ProductRequirement[]> {
  341. try {
  342. let query = new Parse.Query('Product');
  343. query.equalTo('project', {
  344. __type: 'Pointer',
  345. className: 'Project',
  346. objectId: projectId
  347. });
  348. if (productId) {
  349. query.equalTo('objectId', productId);
  350. }
  351. const results = await query.find();
  352. return results.map(product => ({
  353. productId: product.id || '',
  354. productName: product.get('productName'),
  355. productType: product.get('productType'),
  356. colorRequirement: product.get('requirements')?.colorRequirement || {},
  357. spaceStructureRequirement: product.get('requirements')?.spaceStructureRequirement || {},
  358. materialRequirement: product.get('requirements')?.materialRequirement || {},
  359. lightingRequirement: product.get('requirements')?.lightingRequirement || {},
  360. specificRequirements: product.get('requirements')?.specificRequirements || [],
  361. referenceImages: product.get('requirements')?.referenceImages || [],
  362. referenceFiles: product.get('requirements')?.referenceFiles || []
  363. }));
  364. } catch (error) {
  365. console.error('获取产品需求失败:', error);
  366. return [];
  367. }
  368. }
  369. /**
  370. * 计算产品完成进度
  371. */
  372. calculateProductProgress(_productId: string, _allStages: string[]): number {
  373. // 这里需要从缓存或数据库中获取各阶段进度
  374. // 暂时返回模拟数据
  375. return Math.floor(Math.random() * 100);
  376. }
  377. /**
  378. * 获取产品类型图标
  379. */
  380. getProductIcon(productType: string): string {
  381. const iconMap: Record<string, string> = {
  382. 'living_room': 'living-room',
  383. 'bedroom': 'bedroom',
  384. 'kitchen': 'kitchen',
  385. 'bathroom': 'bathroom',
  386. 'dining_room': 'dining-room',
  387. 'study': 'study',
  388. 'balcony': 'balcony',
  389. 'corridor': 'corridor',
  390. 'storage': 'storage',
  391. 'entrance': 'entrance',
  392. 'other': 'room'
  393. };
  394. return iconMap[productType] || 'room';
  395. }
  396. /**
  397. * 获取产品类型名称
  398. */
  399. getProductTypeName(productType: string): string {
  400. const nameMap: Record<string, string> = {
  401. 'living_room': '客厅',
  402. 'bedroom': '卧室',
  403. 'kitchen': '厨房',
  404. 'bathroom': '卫生间',
  405. 'dining_room': '餐厅',
  406. 'study': '书房',
  407. 'balcony': '阳台',
  408. 'corridor': '走廊',
  409. 'storage': '储物间',
  410. 'entrance': '玄关',
  411. 'other': '其他'
  412. };
  413. return nameMap[productType] || '其他';
  414. }
  415. /**
  416. * 解析产品数据
  417. */
  418. private parseProductData(product: any): Project {
  419. const space = product.get('space') || {};
  420. const quotation = product.get('quotation') || {};
  421. const profile = product.get('profile');
  422. return {
  423. id: product.id,
  424. name: product.get('productName'),
  425. type: product.get('productType'),
  426. area: space.area,
  427. priority: space.priority || 5,
  428. status: product.get('status'),
  429. complexity: space.complexity || 'medium',
  430. metadata: space.metadata || {},
  431. estimatedBudget: quotation.price,
  432. estimatedDuration: space.estimatedDuration,
  433. order: 0, // 使用创建时间排序
  434. projectId: product.get('project')?.objectId,
  435. designerId: profile?.id,
  436. quotation: quotation,
  437. requirements: product.get('requirements'),
  438. reviews: product.get('reviews')
  439. };
  440. }
  441. /**
  442. * 统一空间数据管理 - 核心方法
  443. * 将所有空间数据统一存储到Project.data.unifiedSpaces中
  444. */
  445. async saveUnifiedSpaceData(projectId: string, spaces: any[]): Promise<void> {
  446. try {
  447. console.log(`🔄 [统一空间管理] 开始保存统一空间数据,项目ID: ${projectId}, 空间数: ${spaces.length}`);
  448. const projectQuery = new Parse.Query('Project');
  449. const project = await projectQuery.get(projectId);
  450. const data = project.get('data') || {};
  451. // 统一空间数据结构
  452. data.unifiedSpaces = spaces.map((space, index) => ({
  453. id: space.id || `space_${Date.now()}_${index}`,
  454. name: space.name || `空间${index + 1}`,
  455. type: space.type || 'other',
  456. area: space.area || 0,
  457. priority: space.priority || 5,
  458. status: space.status || 'not_started',
  459. complexity: space.complexity || 'medium',
  460. estimatedBudget: space.estimatedBudget || 0,
  461. order: space.order !== undefined ? space.order : index,
  462. // 报价信息
  463. quotation: {
  464. price: space.estimatedBudget || 0,
  465. processes: space.processes || {},
  466. subtotal: space.subtotal || 0
  467. },
  468. // 需求信息
  469. requirements: space.requirements || {},
  470. // 设计师分配
  471. designerId: space.designerId || null,
  472. // 进度信息
  473. progress: space.progress || [],
  474. // 创建和更新时间
  475. createdAt: space.createdAt || new Date().toISOString(),
  476. updatedAt: new Date().toISOString()
  477. }));
  478. // 同时保存到quotation.spaces以保持向后兼容
  479. if (!data.quotation) data.quotation = {};
  480. data.quotation.spaces = data.unifiedSpaces.map(space => ({
  481. name: space.name,
  482. spaceId: space.id,
  483. processes: space.quotation.processes,
  484. subtotal: space.quotation.subtotal
  485. }));
  486. project.set('data', data);
  487. await project.save();
  488. // 同步到Product表
  489. await this.syncUnifiedSpacesToProducts(projectId, data.unifiedSpaces);
  490. console.log(`✅ [统一空间管理] 统一空间数据保存完成`);
  491. } catch (error) {
  492. console.error('❌ [统一空间管理] 保存统一空间数据失败:', error);
  493. throw error;
  494. }
  495. }
  496. /**
  497. * 获取统一空间数据
  498. */
  499. async getUnifiedSpaceData(projectId: string): Promise<any[]> {
  500. try {
  501. const projectQuery = new Parse.Query('Project');
  502. const project = await projectQuery.get(projectId);
  503. const data = project.get('data') || {};
  504. // 优先返回统一空间数据
  505. if (data.unifiedSpaces && data.unifiedSpaces.length > 0) {
  506. console.log(`✅ [统一空间管理] 从unifiedSpaces获取到 ${data.unifiedSpaces.length} 个空间`);
  507. return data.unifiedSpaces;
  508. }
  509. // 如果没有统一数据,尝试从Product表迁移
  510. const productSpaces = await this.getProjectProductSpaces(projectId);
  511. if (productSpaces.length > 0) {
  512. console.log(`🔄 [统一空间管理] 从Product表迁移 ${productSpaces.length} 个空间到统一存储`);
  513. const unifiedSpaces = productSpaces.map((space, index) => ({
  514. id: space.id,
  515. name: space.name,
  516. type: space.type,
  517. area: space.area,
  518. priority: space.priority,
  519. status: space.status,
  520. complexity: space.complexity,
  521. estimatedBudget: space.estimatedBudget,
  522. order: index,
  523. quotation: {
  524. price: space.estimatedBudget || 0,
  525. processes: {},
  526. subtotal: space.estimatedBudget || 0
  527. },
  528. requirements: space.requirements || {},
  529. designerId: space.designerId,
  530. progress: [],
  531. createdAt: new Date().toISOString(),
  532. updatedAt: new Date().toISOString()
  533. }));
  534. // 保存迁移后的数据
  535. await this.saveUnifiedSpaceData(projectId, unifiedSpaces);
  536. return unifiedSpaces;
  537. }
  538. // 如果都没有,返回空数组
  539. console.log(`⚠️ [统一空间管理] 项目 ${projectId} 没有找到任何空间数据`);
  540. return [];
  541. } catch (error) {
  542. console.error('❌ [统一空间管理] 获取统一空间数据失败:', error);
  543. return [];
  544. }
  545. }
  546. /**
  547. * 将统一空间数据同步到Product表
  548. */
  549. private async syncUnifiedSpacesToProducts(projectId: string, unifiedSpaces: any[]): Promise<void> {
  550. try {
  551. console.log(`🔄 [统一空间管理] 开始同步统一空间到Product表`);
  552. // 获取现有的Product列表
  553. const existingProducts = await this.getProjectProductSpaces(projectId);
  554. const existingById = new Map(existingProducts.map(p => [p.id, p]));
  555. for (const space of unifiedSpaces) {
  556. const existingProduct = existingById.get(space.id);
  557. if (existingProduct) {
  558. // 更新现有Product
  559. await this.updateProductSpace(space.id, {
  560. name: space.name,
  561. type: space.type,
  562. area: space.area,
  563. priority: space.priority,
  564. status: space.status,
  565. complexity: space.complexity,
  566. estimatedBudget: space.quotation.price,
  567. requirements: space.requirements,
  568. designerId: space.designerId
  569. });
  570. } else {
  571. // 创建新Product
  572. await this.createProductSpace(projectId, {
  573. name: space.name,
  574. type: space.type,
  575. area: space.area,
  576. priority: space.priority,
  577. status: space.status,
  578. complexity: space.complexity,
  579. estimatedBudget: space.quotation.price,
  580. requirements: space.requirements,
  581. designerId: space.designerId,
  582. order: space.order
  583. });
  584. }
  585. }
  586. console.log(`✅ [统一空间管理] 统一空间同步到Product表完成`);
  587. } catch (error) {
  588. console.error('❌ [统一空间管理] 同步统一空间到Product表失败:', error);
  589. }
  590. }
  591. /**
  592. * 创建初始空间(只创建1-2个默认空间)
  593. */
  594. async createInitialSpaces(projectId: string, projectType: '家装' | '工装' = '家装'): Promise<any[]> {
  595. try {
  596. console.log(`🏠 [初始空间] 为项目 ${projectId} 创建初始空间,类型: ${projectType}`);
  597. let initialSpaces: any[] = [];
  598. if (projectType === '家装') {
  599. // 家装项目:只创建客厅和主卧两个空间
  600. initialSpaces = [
  601. {
  602. id: `space_${Date.now()}_1`,
  603. name: '客厅',
  604. type: 'living_room',
  605. area: 0,
  606. priority: 5,
  607. status: 'not_started',
  608. complexity: 'medium',
  609. estimatedBudget: 300,
  610. order: 0,
  611. quotation: {
  612. price: 300,
  613. processes: {
  614. modeling: { enabled: true, price: 100, unit: '张', quantity: 1 },
  615. softDecor: { enabled: true, price: 100, unit: '张', quantity: 1 },
  616. rendering: { enabled: true, price: 100, unit: '张', quantity: 1 },
  617. postProcess: { enabled: false, price: 0, unit: '张', quantity: 1 }
  618. },
  619. subtotal: 300
  620. },
  621. requirements: {},
  622. designerId: null,
  623. progress: [],
  624. createdAt: new Date().toISOString(),
  625. updatedAt: new Date().toISOString()
  626. },
  627. {
  628. id: `space_${Date.now()}_2`,
  629. name: '主卧',
  630. type: 'bedroom',
  631. area: 0,
  632. priority: 5,
  633. status: 'not_started',
  634. complexity: 'medium',
  635. estimatedBudget: 300,
  636. order: 1,
  637. quotation: {
  638. price: 300,
  639. processes: {
  640. modeling: { enabled: true, price: 100, unit: '张', quantity: 1 },
  641. softDecor: { enabled: true, price: 100, unit: '张', quantity: 1 },
  642. rendering: { enabled: true, price: 100, unit: '张', quantity: 1 },
  643. postProcess: { enabled: false, price: 0, unit: '张', quantity: 1 }
  644. },
  645. subtotal: 300
  646. },
  647. requirements: {},
  648. designerId: null,
  649. progress: [],
  650. createdAt: new Date().toISOString(),
  651. updatedAt: new Date().toISOString()
  652. }
  653. ];
  654. } else {
  655. // 工装项目:只创建一个主要空间
  656. initialSpaces = [
  657. {
  658. id: `space_${Date.now()}_1`,
  659. name: '主要空间',
  660. type: 'other',
  661. area: 0,
  662. priority: 5,
  663. status: 'not_started',
  664. complexity: 'medium',
  665. estimatedBudget: 500,
  666. order: 0,
  667. quotation: {
  668. price: 500,
  669. processes: {
  670. modeling: { enabled: true, price: 150, unit: '张', quantity: 1 },
  671. softDecor: { enabled: true, price: 150, unit: '张', quantity: 1 },
  672. rendering: { enabled: true, price: 150, unit: '张', quantity: 1 },
  673. postProcess: { enabled: true, price: 50, unit: '张', quantity: 1 }
  674. },
  675. subtotal: 500
  676. },
  677. requirements: {},
  678. designerId: null,
  679. progress: [],
  680. createdAt: new Date().toISOString(),
  681. updatedAt: new Date().toISOString()
  682. }
  683. ];
  684. }
  685. // 保存到统一存储
  686. await this.saveUnifiedSpaceData(projectId, initialSpaces);
  687. console.log(`✅ [初始空间] 已创建 ${initialSpaces.length} 个初始空间`);
  688. return initialSpaces;
  689. } catch (error) {
  690. console.error('❌ [初始空间] 创建初始空间失败:', error);
  691. throw error;
  692. }
  693. }
  694. /**
  695. * 数据修复:强制同步当前项目的空间数据到统一存储
  696. */
  697. async forceRepairSpaceData(projectId: string): Promise<void> {
  698. try {
  699. console.log(`🔧 [数据修复] 开始修复项目 ${projectId} 的空间数据...`);
  700. // 1. 获取项目数据
  701. const projectQuery = new Parse.Query('Project');
  702. const project = await projectQuery.get(projectId);
  703. const data = project.get('data') || {};
  704. // 2. 获取Product表中的空间数据
  705. const productSpaces = await this.getProjectProductSpaces(projectId);
  706. console.log(`🔧 [数据修复] Product表中有 ${productSpaces.length} 个空间`);
  707. // 3. 获取报价数据中的空间
  708. const quotationSpaces = Array.isArray(data.quotation?.spaces) ? data.quotation.spaces : [];
  709. console.log(`🔧 [数据修复] 报价数据中有 ${quotationSpaces.length} 个空间`);
  710. // 4. 以Product表为准,构建统一空间数据
  711. const unifiedSpaces = productSpaces.map((space, index) => {
  712. // 尝试从报价数据中找到对应的空间信息
  713. const quotationSpace = quotationSpaces.find((q: any) =>
  714. q.spaceId === space.id ||
  715. (q.name && space.name && q.name.trim().toLowerCase() === space.name.trim().toLowerCase())
  716. );
  717. return {
  718. id: space.id,
  719. name: space.name,
  720. type: space.type,
  721. area: space.area || 0,
  722. priority: space.priority || 5,
  723. status: space.status || 'not_started',
  724. complexity: space.complexity || 'medium',
  725. estimatedBudget: space.estimatedBudget || 0,
  726. order: index,
  727. quotation: {
  728. price: space.estimatedBudget || 0,
  729. processes: quotationSpace?.processes || {},
  730. subtotal: quotationSpace?.subtotal || space.estimatedBudget || 0
  731. },
  732. requirements: space.requirements || {},
  733. designerId: space.designerId || null,
  734. progress: [],
  735. createdAt: new Date().toISOString(),
  736. updatedAt: new Date().toISOString()
  737. };
  738. });
  739. // 5. 保存到统一存储
  740. await this.saveUnifiedSpaceData(projectId, unifiedSpaces);
  741. console.log(`✅ [数据修复] 项目 ${projectId} 空间数据修复完成,共 ${unifiedSpaces.length} 个空间`);
  742. console.log(`🔧 [数据修复] 修复的空间:`, unifiedSpaces.map(s => s.name));
  743. } catch (error) {
  744. console.error('❌ [数据修复] 修复空间数据失败:', error);
  745. throw error;
  746. }
  747. }
  748. /**
  749. * 调试方法:检查项目的空间数据存储情况
  750. */
  751. async debugProjectSpaceData(projectId: string): Promise<void> {
  752. try {
  753. console.log(`🔍 [调试] 检查项目 ${projectId} 的空间数据存储情况...`);
  754. // 1. 检查Project.data.unifiedSpaces
  755. const projectQuery = new Parse.Query('Project');
  756. const project = await projectQuery.get(projectId);
  757. const data = project.get('data') || {};
  758. console.log(`📊 [调试] Project.data.unifiedSpaces:`, data.unifiedSpaces?.length || 0, '个空间');
  759. if (data.unifiedSpaces && data.unifiedSpaces.length > 0) {
  760. console.log(` - 统一空间列表:`, data.unifiedSpaces.map((s: any) => s.name));
  761. }
  762. // 2. 检查Project.data.quotation.spaces
  763. console.log(`📊 [调试] Project.data.quotation.spaces:`, data.quotation?.spaces?.length || 0, '个空间');
  764. if (data.quotation?.spaces && data.quotation.spaces.length > 0) {
  765. console.log(` - 报价空间列表:`, data.quotation.spaces.map((s: any) => s.name));
  766. }
  767. // 3. 检查Product表
  768. const productSpaces = await this.getProjectProductSpaces(projectId);
  769. console.log(`📊 [调试] Product表:`, productSpaces.length, '个空间');
  770. if (productSpaces.length > 0) {
  771. console.log(` - Product空间列表:`, productSpaces.map(s => s.name));
  772. }
  773. // 4. 数据一致性检查
  774. const unifiedCount = data.unifiedSpaces?.length || 0;
  775. const quotationCount = data.quotation?.spaces?.length || 0;
  776. const productCount = productSpaces.length;
  777. console.log(`🔍 [调试] 数据一致性检查:`);
  778. console.log(` - 统一存储: ${unifiedCount} 个`);
  779. console.log(` - 报价数据: ${quotationCount} 个`);
  780. console.log(` - Product表: ${productCount} 个`);
  781. if (unifiedCount === quotationCount && quotationCount === productCount) {
  782. console.log(`✅ [调试] 数据一致性检查通过`);
  783. } else {
  784. console.log(`❌ [调试] 数据不一致,需要修复`);
  785. }
  786. } catch (error) {
  787. console.error('❌ [调试] 检查项目空间数据失败:', error);
  788. }
  789. }
  790. }