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