feat(workspace): 添加个人日报派生逻辑
This commit is contained in:
123
apps/web/lib/workspace-daily-report.test.ts
Normal file
123
apps/web/lib/workspace-daily-report.test.ts
Normal file
@@ -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, '处理历史任务');
|
||||||
|
});
|
||||||
63
apps/web/lib/workspace-daily-report.ts
Normal file
63
apps/web/lib/workspace-daily-report.ts
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user