Ver código fonte

feat:zongguanliyuanduan1

徐福静0235668 3 dias atrás
pai
commit
3999b3a72b

+ 105 - 58
src/app/pages/admin/dashboard/dashboard.ts

@@ -217,10 +217,13 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
   // 加载项目统计数据
   private async loadProjectStats(): Promise<void> {
     try {
+      const companyId = this.company?.id || 'cDL6R1hgSi';
+      console.log(`🔍 [项目统计] 使用公司ID: ${companyId}`);
+      
       const companyPointer = {
         __type: 'Pointer',
         className: 'Company',
-        objectId: this.company?.id || 'cDL6R1hgSi'
+        objectId: companyId
       };
 
       // 总项目数
@@ -229,24 +232,7 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
       totalProjectQuery.notEqualTo('isDeleted', true);
       const totalProjects = await totalProjectQuery.count();
       this.stats.totalProjects.set(totalProjects);
-
-      // 进行中项目数
-      // 参考客服仪表板:排除待分配、已完成、已取消状态
-      const activeProjectQuery = new Parse.Query('Project');
-      activeProjectQuery.equalTo('company', companyPointer);
-      activeProjectQuery.notEqualTo('isDeleted', true);
-      
-      // 排除状态
-      activeProjectQuery.notEqualTo('status', '待分配');
-      activeProjectQuery.notEqualTo('status', '已完成');
-      activeProjectQuery.notEqualTo('status', '已取消');
-      
-      // 排除订单分配阶段的项目
-      activeProjectQuery.notEqualTo('currentStage', '订单分配');
-      activeProjectQuery.notEqualTo('stage', '订单分配');
-      
-      const activeProjects = await activeProjectQuery.count();
-      this.stats.activeProjects.set(activeProjects);
+      console.log(`📊 [项目统计] 总项目数: ${totalProjects}`);
 
       // 已完成项目数
       // 状态为"已完成"或者阶段为"售后归档"
@@ -254,17 +240,37 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
       completedQuery1.equalTo('company', companyPointer);
       completedQuery1.notEqualTo('isDeleted', true);
       completedQuery1.equalTo('status', '已完成');
+      const completedByStatus = await completedQuery1.count();
+      console.log(`  - 状态为'已完成'的项目: ${completedByStatus}`);
       
       const completedQuery2 = new Parse.Query('Project');
       completedQuery2.equalTo('company', companyPointer);
       completedQuery2.notEqualTo('isDeleted', true);
       completedQuery2.equalTo('currentStage', '售后归档');
+      const completedByStage = await completedQuery2.count();
+      console.log(`  - 阶段为'售后归档'的项目: ${completedByStage}`);
       
       const completedProjectQuery = Parse.Query.or(completedQuery1, completedQuery2);
       const completedProjects = await completedProjectQuery.count();
       this.stats.completedProjects.set(completedProjects);
+      console.log(`📊 [项目统计] 已完成项目数: ${completedProjects}`);
 
-      console.log(`✅ 项目统计: 总计${totalProjects}, 进行中${activeProjects}, 已完成${completedProjects}`);
+      // 进行中项目数
+      // 🔥 修复:进行中 = 总项目数 - 已完成项目数 - 已取消项目数
+      const cancelledQuery = new Parse.Query('Project');
+      cancelledQuery.equalTo('company', companyPointer);
+      cancelledQuery.notEqualTo('isDeleted', true);
+      cancelledQuery.equalTo('status', '已取消');
+      
+      const cancelledProjects = await cancelledQuery.count();
+      console.log(`  - 已取消项目数: ${cancelledProjects}`);
+      
+      // 进行中 = 总数 - 已完成 - 已取消
+      const activeProjects = totalProjects - completedProjects - cancelledProjects;
+      this.stats.activeProjects.set(Math.max(0, activeProjects));
+      console.log(`📊 [项目统计] 进行中项目数: ${activeProjects} (${totalProjects} - ${completedProjects} - ${cancelledProjects})`);
+
+      console.log(`✅ 项目统计完成: 总计${totalProjects}, 进行中${activeProjects}, 已完成${completedProjects}`);
     } catch (error) {
       console.error('❌ 项目统计加载失败:', error);
       throw error;
@@ -343,69 +349,110 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
   // 加载收入统计数据
   private async loadRevenueStats(): Promise<void> {
     try {
+      const companyId = this.company?.id || 'cDL6R1hgSi';
+      console.log(`🔍 [收入统计] 使用公司ID: ${companyId}`);
+      
       const companyPointer = {
         __type: 'Pointer',
         className: 'Company',
-        objectId: this.company?.id || 'cDL6R1hgSi'
+        objectId: companyId
       };
 
-      // 🔥 修复:从ProjectPayment表获取已收到的尾款(真实收入)
-      // 而不是从Project.data.pricing获取报价金额
+      // 🔥 修复:从售后归档阶段的项目中获取已支付的尾款
       let totalRevenue = 0;
       
       try {
-        // 查询所有ProjectPayment记录(已收到的支付)
-        const paymentQuery = new Parse.Query('ProjectPayment');
-        paymentQuery.equalTo('company', companyPointer);
-        paymentQuery.notEqualTo('isDeleted', true);
-        paymentQuery.limit(1000);
+        // 1️⃣ 查询所有在"售后归档"阶段的项目
+        const projectQuery = new Parse.Query('Project');
+        projectQuery.equalTo('company', companyPointer);
+        projectQuery.notEqualTo('isDeleted', true);
+        projectQuery.equalTo('currentStage', '售后归档');
+        projectQuery.limit(1000);
         
-        const payments = await paymentQuery.find();
+        const aftercareProjects = await projectQuery.find();
+        console.log(`📊 [收入统计] 找到 ${aftercareProjects.length} 个售后归档项目`);
         
-        console.log(`📊 找到 ${payments.length} 条支付记录`);
-        
-        // 统计所有已收到的支付金额
-        payments.forEach(payment => {
-          const amount = payment.get('amount') || 0;
-          if (amount > 0) {
-            totalRevenue += amount;
-            const projectId = payment.get('project')?.id || '未知项目';
-            console.log(`  支付记录 ${projectId}: ¥${amount.toLocaleString()}`);
+        // 2️⃣ 从每个项目的data字段中获取已支付的尾款
+        let projectsWithPayment = 0;
+        for (const project of aftercareProjects) {
+          const data = project.get('data') || {};
+          const projectTitle = project.get('title') || '未命名项目';
+          
+          // 检查多个可能的尾款字段
+          const finalPayment = 
+            data.finalPaymentReceived ||           // 已收到的尾款
+            data.finalPayment ||                   // 尾款
+            data.paidFinalPayment ||               // 已支付尾款
+            data.receivedFinalPayment ||           // 已收尾款
+            0;
+          
+          if (finalPayment > 0) {
+            totalRevenue += finalPayment;
+            projectsWithPayment++;
+            console.log(`  ✅ 项目 "${projectTitle}": ¥${finalPayment.toLocaleString()}`);
+          } else {
+            console.log(`  ⚠️ 项目 "${projectTitle}": 没有尾款数据`);
           }
-        });
+        }
         
-        console.log(`✅ 收入统计: 已收到尾款 ¥${totalRevenue.toLocaleString()} (来自${payments.length}条支付记录)`);
-      } catch (paymentError) {
-        console.warn('⚠️ ProjectPayment查询失败,尝试从ProjectFile(payment_voucher)获取:', paymentError);
+        console.log(`✅ 收入统计: 已收到尾款 ¥${totalRevenue.toLocaleString()} (${projectsWithPayment}/${aftercareProjects.length}个项目有支付)`);
+      } catch (projectError) {
+        console.warn('⚠️ 项目查询失败,尝试从ProjectPayment表获取:', projectError);
         
-        // 降级方案:从ProjectFile表的payment_voucher类型获取
+        // 降级方案1:从ProjectPayment表获取
         try {
-          const fileQuery = new Parse.Query('ProjectFile');
-          fileQuery.equalTo('company', companyPointer);
-          fileQuery.equalTo('fileType', 'payment_voucher');
-          fileQuery.notEqualTo('isDeleted', true);
-          fileQuery.limit(1000);
+          const paymentQuery = new Parse.Query('ProjectPayment');
+          paymentQuery.equalTo('company', companyPointer);
+          paymentQuery.notEqualTo('isDeleted', true);
+          paymentQuery.limit(1000);
           
-          const voucherFiles = await fileQuery.find();
+          const payments = await paymentQuery.find();
+          console.log(`📊 [收入统计] 找到 ${payments.length} 条支付记录`);
           
-          voucherFiles.forEach(file => {
-            const data = file.get('data') || {};
-            const aiAnalysis = data.aiAnalysis || {};
-            const amount = aiAnalysis.amount || 0;
-            
+          payments.forEach(payment => {
+            const amount = payment.get('amount') || 0;
             if (amount > 0) {
               totalRevenue += amount;
+              console.log(`  ✅ 支付记录: ¥${amount.toLocaleString()}`);
             }
           });
           
-          console.log(`✅ 收入统计(降级): 已收到尾款 ¥${totalRevenue.toLocaleString()} (来自${voucherFiles.length}张凭证)`);
-        } catch (fallbackError) {
-          console.error('❌ 降级方案也失败:', fallbackError);
-          // 继续使用totalRevenue = 0
+          console.log(`✅ 收入统计(降级1): 已收到尾款 ¥${totalRevenue.toLocaleString()} (来自${payments.length}条支付记录)`);
+        } catch (paymentError) {
+          console.warn('⚠️ ProjectPayment查询失败,尝试从ProjectFile(payment_voucher)获取:', paymentError);
+          
+          // 降级方案2:从ProjectFile表的payment_voucher类型获取
+          try {
+            const fileQuery = new Parse.Query('ProjectFile');
+            fileQuery.equalTo('company', companyPointer);
+            fileQuery.equalTo('fileType', 'payment_voucher');
+            fileQuery.notEqualTo('isDeleted', true);
+            fileQuery.limit(1000);
+            
+            const voucherFiles = await fileQuery.find();
+            console.log(`📊 [收入统计] 找到 ${voucherFiles.length} 张支付凭证`);
+            
+            voucherFiles.forEach(file => {
+              const data = file.get('data') || {};
+              const aiAnalysis = data.aiAnalysis || {};
+              const amount = aiAnalysis.amount || 0;
+              
+              if (amount > 0) {
+                totalRevenue += amount;
+                console.log(`  ✅ 凭证: ¥${amount.toLocaleString()}`);
+              }
+            });
+            
+            console.log(`✅ 收入统计(降级2): 已收到尾款 ¥${totalRevenue.toLocaleString()} (来自${voucherFiles.length}张凭证)`);
+          } catch (fallbackError) {
+            console.error('❌ 降级方案也失败:', fallbackError);
+            // 继续使用totalRevenue
+          }
         }
       }
 
       this.stats.totalRevenue.set(totalRevenue);
+      console.log(`💰 [收入统计] 最终总收入: ¥${totalRevenue.toLocaleString()}`);
     } catch (error) {
       console.error('❌ 收入统计加载失败:', error);
       throw error;

+ 27 - 1
src/app/pages/admin/groupchats/groupchats.ts

@@ -80,13 +80,37 @@ export class GroupChats implements OnInit {
     try {
       const groups = await this.groupChatService.findGroupChats();
       console.log("groups",groups)
+      
+      // 🔥 使用Map去重,避免同一项目关联多个群组时重复显示
+      const projectTitleMap = new Map<string, string>();
+      
       const groupList: GroupChat[] = groups.map(g => {
         const json = this.groupChatService.toJSON(g);
+        const projectObj = g.get("project");
+        
+        // 🔥 获取project的完整信息(已通过include加载)
+        let projectTitle = '';
+        if (projectObj) {
+          // 如果project是完整对象,直接获取title
+          if (typeof projectObj.get === 'function') {
+            projectTitle = projectObj.get('title') || '';
+          } else if (projectObj.title) {
+            projectTitle = projectObj.title;
+          }
+          
+          // 🔥 使用projectId作为key进行去重
+          const projectId = projectObj.id || projectObj.objectId;
+          if (projectId && !projectTitleMap.has(projectId)) {
+            projectTitleMap.set(projectId, projectTitle);
+            console.log(`📌 项目 "${projectTitle}" (${projectId}) 已记录`);
+          }
+        }
+        
         return {
           id: json.objectId,
           chat_id: json.chat_id || '',
           name: json.name || '未命名群组',
-          project: g.get("project"),
+          project: projectObj,
           projectId: json.projectId,
           memberCount: json.member_list?.length || 0,
           isDisabled: json.isDisabled || false,
@@ -96,6 +120,8 @@ export class GroupChats implements OnInit {
 
       this.groupChats.set(groupList);
       this.total.set(groupList.length);
+      
+      console.log(`✅ 加载群组完成: 共${groupList.length}个群组, ${projectTitleMap.size}个不同项目`);
     } catch (error) {
       console.error('加载群组列表失败:', error);
     } finally {

+ 22 - 6
src/app/pages/admin/project-management/project-management.ts

@@ -197,9 +197,25 @@ export class ProjectManagement implements OnInit {
         limit: 100
       });
 
-      const projectList: Project[] = projects.map(p => {
+      // 🔥 异步加载所有项目的完整信息(包括ProjectTeam中的负责人)
+      const projectList: Project[] = await Promise.all(projects.map(async (p) => {
         const json = this.projectService.toJSON(p);
         
+        // 🔥 如果没有负责人,尝试从ProjectTeam表获取
+        let assigneeName = json.assigneeName;
+        let assigneeId = json.assigneeId;
+        let assigneeRole = json.assigneeRole;
+        
+        if (!assigneeName || assigneeName === '未分配') {
+          const primaryDesigner = await this.projectService.getPrimaryDesignerFromProjectTeam(json.objectId);
+          if (primaryDesigner) {
+            assigneeName = primaryDesigner.name;
+            assigneeId = primaryDesigner.id;
+            assigneeRole = primaryDesigner.role;
+            console.log(`👤 [项目管理] 项目 "${json.title}" 负责人(从ProjectTeam): ${assigneeName}`);
+          }
+        }
+        
         // 获取原始阶段
         const rawStage = json.currentStage || json.stage || '订单分配';
         
@@ -211,8 +227,8 @@ export class ProjectManagement implements OnInit {
         
         console.log(`📊 [项目管理] "${json.title}":`, {
           客户: json.customerName,
-          负责人: json.assigneeName,
-          角色: json.assigneeRole,
+          负责人: assigneeName,
+          角色: assigneeRole,
           原始阶段: rawStage,
           规范化阶段: normalizedStage,
           状态: `${json.status} → ${autoStatus}`
@@ -224,14 +240,14 @@ export class ProjectManagement implements OnInit {
           customer: json.customerName || '未知客户',
           customerId: json.customerId,
           status: autoStatus, // 使用根据阶段自动判断的状态
-          assignee: json.assigneeName || '未分配',
-          assigneeId: json.assigneeId,
+          assignee: assigneeName || '未分配',
+          assigneeId: assigneeId,
           createdAt: json.createdAt?.iso || json.createdAt,
           updatedAt: json.updatedAt?.iso || json.updatedAt,
           deadline: json.deadline,
           currentStage: normalizedStage // 使用规范化后的阶段名称
         };
-      });
+      }));
 
       this.projects.set(projectList);
       this.applyFilters();

+ 54 - 4
src/app/pages/admin/services/project.service.ts

@@ -230,6 +230,38 @@ export class ProjectService {
     return validProjects.length;
   }
 
+  /**
+   * 从ProjectTeam表获取项目的第一个标记为星号的设计师(异步版本)
+   */
+  async getPrimaryDesignerFromProjectTeam(projectId: string): Promise<any | null> {
+    try {
+      const query = new Parse.Query('ProjectTeam');
+      const projectPtr = new Parse.Object('Project');
+      projectPtr.id = projectId;
+      query.equalTo('project', projectPtr);
+      query.notEqualTo('isDeleted', true);
+      query.include('profile');
+      query.ascending('createdAt'); // 按创建时间排序,获取第一个
+      query.limit(1);
+      
+      const teams = await query.find();
+      if (teams.length > 0) {
+        const profile = teams[0].get('profile');
+        if (profile) {
+          return {
+            id: profile.id,
+            name: profile.get('name') || '未知设计师',
+            role: '设计师'
+          };
+        }
+      }
+      return null;
+    } catch (error) {
+      console.warn(`⚠️ [ProjectService] 获取ProjectTeam失败:`, error);
+      return null;
+    }
+  }
+
   /**
    * 转换为JSON
    */
@@ -241,13 +273,31 @@ export class ProjectService {
     let customerId = '';
     
     if (json.contact && typeof json.contact === 'object') {
-      customerName = json.contact.name || '';
+      // 🔥 ContactInfo表中的name字段可能为空,需要检查多个字段
+      customerName = 
+        json.contact.name ||                              // 直接name字段
+        json.contact.data?.name ||                        // data.name字段
+        json.contact.data?.external_contact?.name ||      // data.external_contact.name字段
+        json.contact.data?.realname ||                    // data.realname字段
+        json.contact.data?.nickname ||                    // data.nickname字段
+        json.contact.realname ||                          // 直接realname字段
+        json.contact.nickname ||                          // 直接nickname字段
+        '';
       customerId = json.contact.objectId;
-      console.log(`📋 [ProjectService] 项目 "${json.title}" 客户(contact): ${customerName}`);
+      console.log(`📋 [ProjectService] 项目 "${json.title}" 客户(contact): ${customerName || '(名称为空)'}`);
     } else if (json.customer && typeof json.customer === 'object') {
-      customerName = json.customer.name || '';
+      // 同样处理customer字段
+      customerName = 
+        json.customer.name ||
+        json.customer.data?.name ||
+        json.customer.data?.external_contact?.name ||
+        json.customer.data?.realname ||
+        json.customer.data?.nickname ||
+        json.customer.realname ||
+        json.customer.nickname ||
+        '';
       customerId = json.customer.objectId;
-      console.log(`📋 [ProjectService] 项目 "${json.title}" 客户(customer): ${customerName}`);
+      console.log(`📋 [ProjectService] 项目 "${json.title}" 客户(customer): ${customerName || '(名称为空)'}`);
     } else {
       console.warn(`⚠️ [ProjectService] 项目 "${json.title}" 没有关联客户信息(contact或customer都为空)`);
     }