feat(workspace): 增加工作活动日报引擎

This commit is contained in:
Script Generator
2026-06-26 17:17:35 +08:00
parent 64491789ae
commit 4278e4d565
21 changed files with 1090 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
import type { TaskWorklog } from './task-worklog';
import type { WorkActivity, WorkActivityCategory } from './work-activity';
import type { WorkItem } from './workspace-engine';
export interface WorkspaceDailyReportItem {
@@ -13,14 +14,40 @@ export interface WorkspaceDailyReportItem {
versionName?: string;
}
export interface WorkspaceDailyReportActivityItem {
id: string;
sourceId: string;
sourceType: WorkActivity['sourceType'];
action: WorkActivity['action'];
category: WorkActivityCategory;
title: string;
summary: string;
occurredAt: string;
context?: string;
metadata?: WorkActivity['metadata'];
}
export interface WorkspaceDailyReportNeedsProgressItem {
id: string;
title: string;
status: string;
type: WorkItem['type'];
context?: string;
}
export type WorkspaceDailyReportGroups = Record<WorkActivityCategory, WorkspaceDailyReportActivityItem[]>;
export interface WorkspaceDailyReport {
date: string;
totalHours: number;
totalCount: number;
items: WorkspaceDailyReportItem[];
groups: WorkspaceDailyReportGroups;
needsProgressItems: WorkspaceDailyReportNeedsProgressItem[];
}
interface GetWorkspaceDailyReportInput {
activities?: WorkActivity[];
worklogs: TaskWorklog[];
workItems: WorkItem[];
userId: string;
@@ -28,6 +55,7 @@ interface GetWorkspaceDailyReportInput {
}
export function getWorkspaceDailyReport({
activities = [],
worklogs,
workItems,
userId,
@@ -54,10 +82,85 @@ export function getWorkspaceDailyReport({
};
});
const dailyActivities = activities
.filter((activity) => activity.actorId === userId && activity.date === date)
.sort((a, b) => b.occurredAt.localeCompare(a.occurredAt));
const groups = makeEmptyGroups();
for (const activity of dailyActivities) {
const item = workItemMap.get(activity.sourceId);
groups[activity.category].push({
id: activity.id,
sourceId: activity.sourceId,
sourceType: activity.sourceType,
action: activity.action,
category: activity.category,
title: activity.title,
summary: activity.summary,
occurredAt: activity.occurredAt,
context: getContext(item),
metadata: activity.metadata,
});
}
const touchedSourceIds = new Set([
...dailyActivities.map((activity) => activity.sourceId),
...items.map((item) => item.taskId),
]);
const needsProgressItems = workItems
.filter((item) => shouldRequireProgress(item, date, touchedSourceIds))
.map((item) => ({
id: item.id,
title: item.title,
status: item.status,
type: item.type,
context: getContext(item),
}));
return {
date,
totalHours: items.reduce((sum, item) => sum + item.hours, 0),
totalCount: items.length,
totalCount: items.length + dailyActivities.length,
items,
groups,
needsProgressItems,
};
}
function makeEmptyGroups(): WorkspaceDailyReportGroups {
return {
delivery: [],
progress: [],
creation: [],
risk: [],
note: [],
};
}
function getContext(item?: WorkItem): string | undefined {
if (!item) return undefined;
return [item.productName, item.projectName, item.versionName].filter(Boolean).join(' / ');
}
function shouldRequireProgress(item: WorkItem, date: string, touchedSourceIds: Set<string>): boolean {
if (item.completed) return false;
if (touchedSourceIds.has(item.id)) return false;
if (!isInProgressStatus(item.status)) return false;
const startedAt = getStartedAt(item);
if (!startedAt) return false;
return startedAt.slice(0, 10) < date;
}
function isInProgressStatus(status: string): boolean {
return ['in_progress', 'testing', 'running', 'fixing', 'verifying'].includes(status);
}
function getStartedAt(item: WorkItem): string | undefined {
const extra = item.extra ?? {};
if (typeof extra.actualStartAt === 'string') return extra.actualStartAt;
if (typeof extra.startedAt === 'string') return extra.startedAt;
if (typeof extra.startTime === 'string') return extra.startTime;
return undefined;
}