Files
ftb-project-management/apps/web/lib/work-activity-factory.ts
Script Generator 8947aeaac7 feat(工作台): 细化计划进展工时证据
关键改动:

- 支持需求覆盖和调研方向开始工作记录

- 日报按计划记录开始时间和进度下次开始时间计算证据

- 更新版本编辑校验、项目展示和 workflow 说明

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-01 11:02:38 +08:00

369 lines
9.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Bug, BugStatus } from './bug';
import type { DevTask, DevTaskStatus } from './dev-task';
import type { TestCase, TestCaseStatus } from './test-case';
import { REQUIREMENT_COVERAGE_LABEL, type VersionPlan, type VersionPlanLog } from './version-plan';
import type { WorkActivityDraft, WorkActivitySourceType } from './work-activity';
const PLAN_TYPE_LABEL: Record<VersionPlan['type'], string> = {
research: '调研',
product: '产品方案',
ui: 'UI设计',
};
export function makeVersionPlanCreatedActivity(plan: VersionPlan, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'version_plan',
sourceId: plan.id,
action: 'version_plan_created',
category: 'creation',
title: plan.title,
summary: `新建${PLAN_TYPE_LABEL[plan.type]}${plan.title}`,
};
}
export function makeVersionPlanStartedActivity(plan: VersionPlan, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'version_plan',
sourceId: plan.id,
action: 'version_plan_started',
category: 'progress',
title: plan.title,
summary: `开始${PLAN_TYPE_LABEL[plan.type]}${plan.title}`,
};
}
export function makeVersionPlanCompletedActivity(plan: VersionPlan, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'version_plan',
sourceId: plan.id,
action: 'version_plan_completed',
category: 'delivery',
title: plan.title,
summary: `完成${PLAN_TYPE_LABEL[plan.type]}${plan.title}`,
};
}
export function makeVersionPlanRequirementProgressActivity(plan: VersionPlan, log: VersionPlanLog): WorkActivityDraft | undefined {
if (log.type !== 'requirement_progress') return undefined;
if (plan.type !== 'product' && plan.type !== 'ui') return undefined;
const requirementLabel = [log.requirementCode, log.requirementTitle].filter(Boolean).join(' ') || '需求';
const statusLabel = log.coverageStatus ? REQUIREMENT_COVERAGE_LABEL[log.coverageStatus] : '进度更新';
return {
actorId: log.actor,
sourceType: 'version_plan',
sourceId: plan.id,
action: 'version_plan_requirement_progress',
category: 'progress',
title: plan.title,
summary: `推进${PLAN_TYPE_LABEL[plan.type]}需求:${requirementLabel}${statusLabel}`,
occurredAt: log.createdAt,
metadata: {
requirementId: log.requirementId,
requirementCode: log.requirementCode,
requirementTitle: log.requirementTitle,
coverageStatus: log.coverageStatus,
completedContent: log.completedContent,
remainingContent: log.remainingContent,
workStartedAt: log.workStartedAt,
},
};
}
export function makeVersionPlanResearchDirectionProgressActivity(plan: VersionPlan, log: VersionPlanLog): WorkActivityDraft | undefined {
if (log.type !== 'research_direction_progress') return undefined;
if (plan.type !== 'research') return undefined;
const directionTitle = log.directionTitle || '调研方向';
const statusLabel = log.coverageStatus ? REQUIREMENT_COVERAGE_LABEL[log.coverageStatus] : '进度更新';
return {
actorId: log.actor,
sourceType: 'version_plan',
sourceId: plan.id,
action: 'version_plan_research_direction_progress',
category: 'progress',
title: plan.title,
summary: `推进调研方向:${directionTitle}${statusLabel}`,
occurredAt: log.createdAt,
metadata: {
directionTaskId: log.directionTaskId,
directionTitle: log.directionTitle,
coverageStatus: log.coverageStatus,
completedContent: log.completedContent,
remainingContent: log.remainingContent,
workStartedAt: log.workStartedAt,
},
};
}
export interface ProgressNoteActivityInput {
actorId: string;
sourceType: WorkActivitySourceType;
sourceId: string;
title: string;
note: string;
nextStartAt: string;
blocker?: string;
helperId?: string;
delayRisk?: string;
}
export function makeProgressNoteActivity(data: ProgressNoteActivityInput): WorkActivityDraft {
const note = data.note.trim();
const blocker = data.blocker?.trim() || undefined;
const helperId = data.helperId?.trim() || undefined;
const delayRisk = data.delayRisk?.trim() || undefined;
const nextStartAt = data.nextStartAt.trim();
const details = [
note,
blocker ? `阻塞:${blocker}` : '',
helperId ? `需协助:${helperId}` : '',
delayRisk ? `延期风险:${delayRisk}` : '',
].filter(Boolean);
return {
actorId: data.actorId,
sourceType: data.sourceType,
sourceId: data.sourceId,
action: 'progress_note_added',
category: blocker || delayRisk ? 'risk' : 'note',
title: data.title,
summary: `补充进展:${details.join('')}`,
metadata: {
note,
blocker,
helperId,
delayRisk,
nextStartAt: nextStartAt || undefined,
},
};
}
export function makeDevTaskCreatedActivity(task: DevTask, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'dev_task',
sourceId: task.id,
action: 'dev_task_created',
category: 'creation',
title: task.title,
summary: `新建开发任务:${task.title}`,
};
}
export function makeDevTaskStatusActivity(
task: DevTask,
fromStatus: DevTaskStatus,
toStatus: DevTaskStatus,
actorId: string,
): WorkActivityDraft | undefined {
const base = {
actorId,
sourceType: 'dev_task' as const,
sourceId: task.id,
title: task.title,
metadata: { fromStatus, toStatus },
};
if (toStatus === 'in_progress') {
return {
...base,
action: 'dev_task_started',
category: 'progress',
summary: `开始开发:${task.title}`,
};
}
if (toStatus === 'testing') {
return {
...base,
action: 'dev_task_self_testing',
category: 'progress',
summary: `进入自测:${task.title}`,
};
}
if (toStatus === 'submitted') {
return {
...base,
action: 'dev_task_submitted',
category: 'delivery',
summary: `已提测开发任务:${task.title}`,
};
}
return undefined;
}
export function makeDevTaskBlockedActivity(
task: DevTask,
actorId: string,
reason?: string,
helperId?: string,
): WorkActivityDraft {
return {
actorId,
sourceType: 'dev_task',
sourceId: task.id,
action: 'dev_task_blocked',
category: 'risk',
title: task.title,
summary: `标记阻塞:${reason?.trim() || task.title}`,
metadata: {
blocker: reason?.trim() || undefined,
helperId: helperId?.trim() || undefined,
},
};
}
export function makeDevTaskUnblockedActivity(task: DevTask, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'dev_task',
sourceId: task.id,
action: 'dev_task_unblocked',
category: 'progress',
title: task.title,
summary: `解除阻塞:${task.title}`,
};
}
export function makeBugCreatedActivity(bug: Bug, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'bug',
sourceId: bug.id,
action: 'bug_created',
category: 'creation',
title: bug.title,
summary: `新建 Bug${bug.title}`,
};
}
export function makeTestCaseCreatedActivity(testCase: TestCase, actorId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'test_case',
sourceId: testCase.id,
action: 'test_case_created',
category: 'creation',
title: testCase.title,
summary: `新建测试用例:${testCase.title}`,
};
}
export function makeTestCaseStatusActivity(
testCase: TestCase,
fromStatus: TestCaseStatus,
toStatus: TestCaseStatus,
actorId: string,
): WorkActivityDraft | undefined {
const base = {
actorId,
sourceType: 'test_case' as const,
sourceId: testCase.id,
title: testCase.title,
metadata: { fromStatus, toStatus },
};
if (toStatus === 'running') {
return {
...base,
action: 'test_case_started',
category: 'progress',
summary: `开始测试:${testCase.title}`,
};
}
if (toStatus === 'passed') {
return {
...base,
action: 'test_case_passed',
category: 'delivery',
summary: `测试通过:${testCase.title}`,
};
}
if (toStatus === 'failed') {
return {
...base,
action: 'test_case_failed',
category: 'risk',
summary: `测试不通过:${testCase.title}`,
};
}
if (toStatus === 'blocked') {
return {
...base,
action: 'test_case_blocked',
category: 'risk',
summary: `测试阻塞:${testCase.title}`,
};
}
return undefined;
}
export function makeBugStatusActivity(
bug: Bug,
fromStatus: BugStatus,
toStatus: BugStatus,
actorId: string,
): WorkActivityDraft | undefined {
const base = {
actorId,
sourceType: 'bug' as const,
sourceId: bug.id,
title: bug.title,
metadata: { fromStatus, toStatus },
};
if (toStatus === 'fixing') {
return {
...base,
action: 'bug_fixing',
category: 'progress',
summary: `开始修复 Bug${bug.title}`,
};
}
if (toStatus === 'fixed') {
return {
...base,
action: 'bug_fixed',
category: 'delivery',
summary: `已修复 Bug${bug.title}`,
};
}
if (toStatus === 'closed') {
return {
...base,
action: 'bug_closed',
category: 'delivery',
summary: `已关闭 Bug${bug.title}`,
};
}
return undefined;
}
export function makeBugTransferredActivity(bug: Bug, actorId: string, toAssigneeId: string): WorkActivityDraft {
return {
actorId,
sourceType: 'bug',
sourceId: bug.id,
action: 'bug_transferred',
category: 'progress',
title: bug.title,
summary: `转交 Bug${bug.title}${toAssigneeId}`,
metadata: { toAssigneeId },
};
}