ryan 9 часов назад
Родитель
Сommit
5ecdd774ea
1 измененных файлов с 141 добавлено и 56 удалено
  1. 141 56
      cloud/functions/project-load.js

+ 141 - 56
cloud/functions/project-load.js

@@ -1,12 +1,54 @@
+/**
+ * 组长看板数据云函数 - 支持时间范围筛选
+ *
+ * 请求参数:
+ * - companyId: 公司ID
+ * - baseDate: 基准日期 (可选,格式: YYYY-MM-DD,默认为当前日期)
+ * - startDate: 开始日期 (可选,覆盖 baseDate 计算)
+ * - endDate: 结束日期 (可选,覆盖 baseDate 计算)
+ *
+ * 时间范围逻辑:
+ * - 如果不传时间参数,默认查询当前进行中的项目
+ * - 如果传 baseDate,则以该日期为基准,查询该月的项目
+ * - 如果传 startDate/endDate,则使用指定的日期范围
+ */
+
 async function handler(request, response) {
-  console.log('🚀 执行高性能 SQL 统计 (完全体 - 最终修正版)...');
-  
+  console.log('🚀 执行高性能 SQL 统计 (时间范围筛选版)...');
+
   try {
+    // 获取公司ID
     let companyId = 'cDL6R1hgSi';
     if (request.company && request.company.id) companyId = request.company.id;
     else if (request.params && request.params.companyId) companyId = request.params.companyId;
     else if (request.body && request.body.companyId) companyId = request.body.companyId;
 
+    // 获取时间参数
+    let baseDate = request.body?.baseDate || request.params?.baseDate || null;
+    let startDate = request.body?.startDate || request.params?.startDate || null;
+    let endDate = request.body?.endDate || request.params?.endDate || null;
+
+    // 计算时间范围
+    const now = new Date();
+    let rangeStart, rangeEnd;
+
+    if (startDate && endDate) {
+      // 使用指定范围
+      rangeStart = new Date(startDate);
+      rangeEnd = new Date(endDate);
+    } else if (baseDate) {
+      // 基于 baseDate 计算该月的范围
+      const base = new Date(baseDate);
+      rangeStart = new Date(base.getFullYear(), base.getMonth(), 1); // 月初
+      rangeEnd = new Date(base.getFullYear(), base.getMonth() + 1, 0, 23, 59, 59); // 月末
+    } else {
+      // 默认:查询进行中的项目(不限制时间范围,或使用较宽的时间窗口)
+      rangeStart = new Date(now.getFullYear() - 1, 0, 1); // 一年前
+      rangeEnd = new Date(now.getFullYear() + 1, 11, 31); // 一年后
+    }
+
+    console.log(`📅 查询时间范围: ${rangeStart.toISOString()} ~ ${rangeEnd.toISOString()}`);
+
     // --- SQL 定义 ---
 
     const workloadSql = `
@@ -16,9 +58,9 @@ async function handler(request, response) {
         COALESCE((u."data"->'tags'->'capacity'->>'weeklyProjects')::int, 3) as "weeklyCapacity",
         COUNT(DISTINCT pt."project") as "projectCount",
         COUNT(DISTINCT CASE WHEN p."deadline" < NOW() AND p."status" != '已完成' THEN p."objectId" END) as "overdueCount",
-        SUM(CASE 
-          WHEN p."status" = '已完成' THEN 0 
-          ELSE ((CASE WHEN p."data"->>'projectType' = 'hard' THEN 2.0 ELSE 1.0 END) * (CASE WHEN p."deadline" < NOW() THEN 1.5 ELSE 1.0 END)) 
+        SUM(CASE
+          WHEN p."status" = '已完成' THEN 0
+          ELSE ((CASE WHEN p."data"->>'projectType' = 'hard' THEN 2.0 ELSE 1.0 END) * (CASE WHEN p."deadline" < NOW() THEN 1.5 ELSE 1.0 END))
         END) as "weightedLoad"
       FROM "Profile" u
       LEFT JOIN "ProjectTeam" pt ON pt."profile" = u."objectId" AND pt."isDeleted" IS NOT TRUE
@@ -28,6 +70,8 @@ async function handler(request, response) {
       ORDER BY "weightedLoad" DESC
     `;
 
+    // 项目查询 - 添加时间范围筛选
+    // 筛选逻辑:项目的 deadline 或 createdAt 在指定时间范围内
     const projectsSql = `
       SELECT
         p."objectId" as "id",
@@ -37,9 +81,7 @@ async function handler(request, response) {
         p."deadline",
         p."updatedAt",
         p."createdAt",
-        p."data"->>'urgency' as "urgency",
-        p."data"->>'projectType' as "type",
-        p."data"->'phaseDeadlines' as "phaseDeadlines",
+        p."data",
         p."date" as "projectDate",
         EXTRACT(DAY FROM (p."deadline" - NOW())) as "daysLeft",
         (
@@ -54,20 +96,40 @@ async function handler(request, response) {
           WHERE pt."project" = p."objectId" AND pt."isDeleted" IS NOT TRUE
         ) as "designerIds"
       FROM "Project" p
-      WHERE p."company" = $1 AND p."isDeleted" IS NOT TRUE AND p."status" != '已完成'
+      WHERE p."company" = $1
+        AND p."isDeleted" IS NOT TRUE
+        AND p."status" != '已完成'
+        AND (
+          -- 项目截止时间在范围内
+          (p."deadline" >= $2 AND p."deadline" <= $3)
+          -- 或者项目创建时间在范围内
+          OR (p."createdAt" >= $2 AND p."createdAt" <= $3)
+          -- 或者项目跨越整个范围(开始于范围前,结束于范围后)
+          OR (p."createdAt" < $2 AND p."deadline" > $3)
+          -- 或者没有截止时间但状态为进行中
+          OR (p."deadline" IS NULL AND p."status" = '进行中')
+        )
       ORDER BY p."updatedAt" DESC
       LIMIT 1000
     `;
 
     const spaceStatsSql = `
       WITH ActiveProjects AS (
-          SELECT p."objectId" 
-          FROM "Project" p 
-          WHERE p."company" = $1 AND p."isDeleted" IS NOT TRUE AND p."status" != '已完成'
+          SELECT p."objectId"
+          FROM "Project" p
+          WHERE p."company" = $1
+            AND p."isDeleted" IS NOT TRUE
+            AND p."status" != '已完成'
+            AND (
+              (p."deadline" >= $2 AND p."deadline" <= $3)
+              OR (p."createdAt" >= $2 AND p."createdAt" <= $3)
+              OR (p."createdAt" < $2 AND p."deadline" > $3)
+              OR (p."deadline" IS NULL AND p."status" = '进行中')
+            )
           LIMIT 1000
       ),
       ProjectSpaces AS (
-          SELECT 
+          SELECT
               p."objectId" as "spaceId",
               p."productName" as "spaceName",
               p."productType" as "spaceType",
@@ -77,35 +139,35 @@ async function handler(request, response) {
             AND (p."isDeleted" IS NULL OR p."isDeleted" = false)
       ),
       Deliverables AS (
-          SELECT 
+          SELECT
               COALESCE(d."data"->>'spaceId', d."data"->>'productId') as "spaceId",
               COUNT(*) as "fileCount",
-              SUM(CASE WHEN 
-                  d."fileType" = 'delivery_white_model' OR 
-                  d."data"->>'deliveryType' IN ('white_model', 'delivery_white_model') 
+              SUM(CASE WHEN
+                  d."fileType" = 'delivery_white_model' OR
+                  d."data"->>'deliveryType' IN ('white_model', 'delivery_white_model')
                   THEN 1 ELSE 0 END) as "whiteModelCount",
-              SUM(CASE WHEN 
-                  d."fileType" = 'delivery_soft_decor' OR 
-                  d."data"->>'deliveryType' IN ('soft_decor', 'delivery_soft_decor') 
+              SUM(CASE WHEN
+                  d."fileType" = 'delivery_soft_decor' OR
+                  d."data"->>'deliveryType' IN ('soft_decor', 'delivery_soft_decor')
                   THEN 1 ELSE 0 END) as "softDecorCount",
-              SUM(CASE WHEN 
-                  d."fileType" = 'delivery_rendering' OR 
-                  d."data"->>'deliveryType' IN ('rendering', 'delivery_rendering') 
+              SUM(CASE WHEN
+                  d."fileType" = 'delivery_rendering' OR
+                  d."data"->>'deliveryType' IN ('rendering', 'delivery_rendering')
                   THEN 1 ELSE 0 END) as "renderingCount",
-              SUM(CASE WHEN 
-                  d."fileType" = 'delivery_post_process' OR 
-                  d."data"->>'deliveryType' IN ('post_process', 'delivery_post_process') 
+              SUM(CASE WHEN
+                  d."fileType" = 'delivery_post_process' OR
+                  d."data"->>'deliveryType' IN ('post_process', 'delivery_post_process')
                   THEN 1 ELSE 0 END) as "postProcessCount"
           FROM "ProjectFile" d
           WHERE d."project" IN (SELECT "objectId" FROM ActiveProjects)
             AND (d."isDeleted" IS NULL OR d."isDeleted" = false)
             AND (
-                d."fileType" LIKE 'delivery_%' OR 
+                d."fileType" LIKE 'delivery_%' OR
                 d."data"->>'uploadStage' = 'delivery'
             )
           GROUP BY COALESCE(d."data"->>'spaceId', d."data"->>'productId')
       )
-      SELECT 
+      SELECT
           ps."projectId",
           ps."spaceId",
           ps."spaceName",
@@ -119,7 +181,7 @@ async function handler(request, response) {
       LEFT JOIN Deliverables d ON ps."spaceId" = d."spaceId"
     `;
 
-    // 关键修正:通过 Project 表关联查询,获取更多字段
+    // Issue 查询 - 添加时间范围筛选
     const issuesSql = `
       SELECT
         i."objectId" as "id",
@@ -140,20 +202,24 @@ async function handler(request, response) {
       JOIN "Project" p ON i."project" = p."objectId"
       LEFT JOIN "Profile" c ON i."creator" = c."objectId"
       LEFT JOIN "Profile" a ON i."assignee" = a."objectId"
-      WHERE p."company" = $1 
+      WHERE p."company" = $1
         AND (i."isDeleted" IS NULL OR i."isDeleted" = false)
         AND i."status" IN ('待处理', '处理中')
+        AND (
+          i."createdAt" >= $2 OR i."dueDate" >= $2 OR i."dueDate" IS NULL
+        )
       ORDER BY i."updatedAt" DESC
       LIMIT 50
     `;
 
     // --- 执行 SQL ---
+    const queryParams = [companyId, rangeStart, rangeEnd];
 
     const [workloadResult, projectsResult, spaceStatsResult, issuesResult] = await Promise.all([
       Psql.query(workloadSql, [companyId]),
-      Psql.query(projectsSql, [companyId]),
-      Psql.query(spaceStatsSql, [companyId]),
-      Psql.query(issuesSql, [companyId])
+      Psql.query(projectsSql, queryParams),
+      Psql.query(spaceStatsSql, queryParams),
+      Psql.query(issuesSql, queryParams)
     ]);
 
     // --- 格式化数据 ---
@@ -166,7 +232,7 @@ async function handler(request, response) {
       let status = 'idle';
       if (loadRate > 80) status = 'overload';
       else if (loadRate > 50) status = 'busy';
-      
+
       return {
         id: w.id,
         name: w.name,
@@ -182,6 +248,7 @@ async function handler(request, response) {
     // 2. Projects
     const spaceAssigneeMap = {};
     const projects = projectsResult.map(p => {
+      // 解析设计师分配信息
       if (p.projectDate && p.projectDate.designerAssignmentStats) {
           const stats = p.projectDate.designerAssignmentStats;
           if (stats.projectLeader && stats.projectLeader.assignedSpaces) {
@@ -207,7 +274,10 @@ async function handler(request, response) {
       const days = parseFloat(p.daysLeft);
       if (days < 0) statusStr = 'overdue';
       else if (days <= 3) statusStr = 'urgent';
-      
+
+      // 从 data 字段提取扩展属性
+      const data = p.data || {};
+
       return {
         id: p.id,
         name: p.name,
@@ -216,33 +286,35 @@ async function handler(request, response) {
         deadline: p.deadline,
         updatedAt: p.updatedAt,
         createdAt: p.createdAt,
-        urgency: p.urgency,
-        type: p.type,
-        phaseDeadlines: p.phaseDeadlines || {},
-        daysLeft: Math.ceil(days), 
+        urgency: data.urgency,
+        type: data.projectType,
+        phaseDeadlines: data.phaseDeadlines || {},
+        daysLeft: Math.ceil(days),
         isOverdue: days < 0,
         statusStr,
         designerName: p.designerName || '待分配',
-        designerIds: p.designerIds || []
+        designerIds: p.designerIds || [],
+        // 扩展字段
+        data: data
       };
     });
 
     // 3. Space Stats (完全修复聚合逻辑)
     const spaceStats = {};
-    
-    // 创建项目名称映射,确保能获取到 projectName
+
+    // 创建项目名称映射
     const projectNameMap = {};
     projects.forEach(p => {
       projectNameMap[p.id] = p.name;
     });
-    
+
     spaceStatsResult.forEach(row => {
         if (!spaceStats[row.projectId]) {
             spaceStats[row.projectId] = {
                 spaces: []
             };
         }
-        
+
         // 计算单个空间的完成度
         const hasFiles = parseInt(row.totalFiles) > 0;
         let completion = 0;
@@ -267,24 +339,24 @@ async function handler(request, response) {
             hasDeliverables: hasFiles,
             completionRate: Math.min(100, completion)
         };
-        
+
         spaceStats[row.projectId].spaces.push(spaceInfo);
     });
 
     Object.keys(spaceStats).forEach(pid => {
         const proj = spaceStats[pid];
         const totalSpaces = proj.spaces.length;
-        
+
         // 计算整体完成率
         const sumCompletion = proj.spaces.reduce((sum, s) => sum + s.completionRate, 0);
         const overallCompletionRate = totalSpaces > 0 ? Math.round(sumCompletion / totalSpaces) : 0;
-        
+
         const calcPhaseDetails = (typeKey) => {
             const spacesWithFile = proj.spaces.filter(s => s.deliverableTypes[typeKey] > 0);
             const completedCount = spacesWithFile.length;
             const rate = totalSpaces > 0 ? Math.round((completedCount / totalSpaces) * 100) : 0;
             const fileCount = proj.spaces.reduce((sum, s) => sum + s.deliverableTypes[typeKey], 0);
-            
+
             const incomplete = proj.spaces
                 .filter(s => s.deliverableTypes[typeKey] === 0)
                 .map(s => ({
@@ -308,11 +380,10 @@ async function handler(request, response) {
             rendering: calcPhaseDetails('rendering'),
             postProcessing: calcPhaseDetails('postProcess')
         };
-        
-        // 关键:确保 projectName 和 totalByType 存在
+
         spaceStats[pid] = {
             projectId: pid,
-            projectName: projectNameMap[pid] || '未命名项目', 
+            projectName: projectNameMap[pid] || '未命名项目',
             totalSpaces,
             spaces: proj.spaces,
             totalDeliverableFiles: proj.spaces.reduce((sum, s) => sum + s.totalFiles, 0),
@@ -327,7 +398,7 @@ async function handler(request, response) {
         };
     });
 
-    // 4. Issues (恢复完整字段)
+    // 4. Issues
     const zh2enStatus = (status) => {
       const map = {
         '待处理': 'open',
@@ -365,10 +436,24 @@ async function handler(request, response) {
       avgLoadRate: workload.length > 0 ? Math.round(workload.reduce((sum, w) => sum + w.loadRate, 0) / workload.length) : 0
     };
 
-    response.json({ 
-      code: 200, 
-      success: true, 
-      data: { stats, workload, projects, spaceStats, issues } 
+    // 6. 返回时间范围信息
+    const timeRange = {
+      start: rangeStart.toISOString(),
+      end: rangeEnd.toISOString(),
+      baseDate: baseDate || now.toISOString().split('T')[0]
+    };
+
+    response.json({
+      code: 200,
+      success: true,
+      data: {
+        stats,
+        workload,
+        projects,
+        spaceStats,
+        issues,
+        timeRange // 添加时间范围信息
+      }
     });
 
   } catch (error) {