diff --git a/apps/web/lib/workspace-daily-report.test.ts b/apps/web/lib/workspace-daily-report.test.ts new file mode 100644 index 0000000..ed19733 --- /dev/null +++ b/apps/web/lib/workspace-daily-report.test.ts @@ -0,0 +1,123 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import type { TaskWorklog } from './task-worklog'; +import type { WorkItem } from './workspace-engine'; +import { getWorkspaceDailyReport } from './workspace-daily-report'; + +const workItems: WorkItem[] = [ + { + id: 'task-1', + type: 'devTask', + title: '实现登录接口', + status: 'in_progress', + completed: false, + productName: 'FTB', + projectName: '项目管理', + versionName: 'V1.0', + versionId: 'version-1', + raw: {} as any, + }, + { + id: 'task-2', + type: 'bug', + title: '修复菜单错位', + status: 'fixing', + completed: false, + productName: 'FTB', + projectName: '项目管理', + versionName: 'V1.0', + versionId: 'version-1', + raw: {} as any, + }, +]; + +const worklogs: TaskWorklog[] = [ + { + id: 'wl-1', + taskId: 'task-1', + userId: '张三', + date: '2026-06-26', + hours: 1.5, + workContent: '完成登录接口联调', + createdAt: '2026-06-26T02:00:00.000Z', + }, + { + id: 'wl-2', + taskId: 'task-2', + userId: '张三', + date: '2026-06-26', + hours: 0.5, + workContent: '定位菜单错位原因', + createdAt: '2026-06-26T03:00:00.000Z', + }, + { + id: 'wl-other-user', + taskId: 'task-1', + userId: '李四', + date: '2026-06-26', + hours: 8, + workContent: '其他人的日报', + createdAt: '2026-06-26T04:00:00.000Z', + }, + { + id: 'wl-other-date', + taskId: 'task-1', + userId: '张三', + date: '2026-06-25', + hours: 8, + workContent: '昨天的日报', + createdAt: '2026-06-25T04:00:00.000Z', + }, +]; + +test('getWorkspaceDailyReport filters by current user and date', () => { + const report = getWorkspaceDailyReport({ + worklogs, + workItems, + userId: '张三', + date: '2026-06-26', + }); + + assert.equal(report.totalHours, 2); + assert.equal(report.totalCount, 2); + assert.deepEqual(report.items.map((item) => item.id), ['wl-2', 'wl-1']); +}); + +test('getWorkspaceDailyReport resolves task title and workspace context', () => { + const report = getWorkspaceDailyReport({ + worklogs, + workItems, + userId: '张三', + date: '2026-06-26', + }); + + assert.equal(report.items[0].taskTitle, '修复菜单错位'); + assert.equal(report.items[0].productName, 'FTB'); + assert.equal(report.items[0].projectName, '项目管理'); + assert.equal(report.items[0].versionName, 'V1.0'); +}); + +test('getWorkspaceDailyReport keeps logs whose task is no longer visible', () => { + const report = getWorkspaceDailyReport({ + worklogs: [ + { + id: 'wl-missing', + taskId: 'deleted-task', + userId: '张三', + date: '2026-06-26', + hours: 0.5, + workContent: '处理历史任务', + createdAt: '2026-06-26T01:00:00.000Z', + }, + ], + workItems: [], + userId: '张三', + date: '2026-06-26', + }); + + assert.equal(report.totalHours, 0.5); + assert.equal(report.totalCount, 1); + assert.equal(report.items[0].taskTitle, '未知任务'); + assert.equal(report.items[0].workContent, '处理历史任务'); +}); diff --git a/apps/web/lib/workspace-daily-report.ts b/apps/web/lib/workspace-daily-report.ts new file mode 100644 index 0000000..eb6d343 --- /dev/null +++ b/apps/web/lib/workspace-daily-report.ts @@ -0,0 +1,63 @@ +import type { TaskWorklog } from './task-worklog'; +import type { WorkItem } from './workspace-engine'; + +export interface WorkspaceDailyReportItem { + id: string; + taskId: string; + taskTitle: string; + workContent: string; + hours: number; + createdAt: string; + productName?: string; + projectName?: string; + versionName?: string; +} + +export interface WorkspaceDailyReport { + date: string; + totalHours: number; + totalCount: number; + items: WorkspaceDailyReportItem[]; +} + +interface GetWorkspaceDailyReportInput { + worklogs: TaskWorklog[]; + workItems: WorkItem[]; + userId: string; + date: string; +} + +export function getWorkspaceDailyReport({ + worklogs, + workItems, + userId, + date, +}: GetWorkspaceDailyReportInput): WorkspaceDailyReport { + const workItemMap = new Map(workItems.map((item) => [item.id, item])); + + const items = worklogs + .filter((log) => log.userId === userId && log.date === date) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .map((log) => { + const item = workItemMap.get(log.taskId); + + return { + id: log.id, + taskId: log.taskId, + taskTitle: item?.title ?? '未知任务', + workContent: log.workContent, + hours: log.hours, + createdAt: log.createdAt, + productName: item?.productName, + projectName: item?.projectName, + versionName: item?.versionName, + }; + }); + + return { + date, + totalHours: items.reduce((sum, item) => sum + item.hours, 0), + totalCount: items.length, + items, + }; +}