feat(工作台): 细化计划进展工时证据

关键改动:

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

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

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

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-07-01 11:02:38 +08:00
parent e5ce2d221e
commit 8947aeaac7
18 changed files with 1125 additions and 94 deletions

View File

@@ -47,6 +47,7 @@ export interface PlanTask {
status: PlanTaskStatus;
completedContent?: string;
remainingContent?: string;
currentWorkStartedAt?: string;
updatedAt?: string;
updatedBy?: string;
}
@@ -76,6 +77,7 @@ export interface VersionPlanRequirementCoverage {
status: RequirementCoverageStatus;
completedContent?: string;
remainingContent?: string;
currentWorkStartedAt?: string;
updatedAt: string;
updatedBy: string;
}
@@ -93,28 +95,43 @@ export interface VersionPlanLog {
coverageStatus?: RequirementCoverageStatus;
completedContent?: string;
remainingContent?: string;
workStartedAt?: string;
directionTaskId?: string;
directionTitle?: string;
aiTarget?: AgentDecomposeTarget;
aiStatus?: AiDecomposeLogStatus;
}
export interface RequirementCoverageWorkStartInput {
requirementId: string;
startedAt?: string;
updatedBy: string;
}
export interface RequirementCoverageUpdateInput {
requirementId: string;
status: RequirementCoverageStatus;
completedContent?: string;
remainingContent?: string;
workStartedAt?: string;
updatedAt?: string;
updatedBy: string;
requirementCode?: string;
requirementTitle?: string;
}
export interface ResearchDirectionWorkStartInput {
taskId: string;
startedAt?: string;
updatedBy: string;
}
export interface ResearchDirectionProgressUpdateInput {
taskId: string;
status: Extract<RequirementCoverageStatus, 'partial' | 'completed'>;
completedContent?: string;
remainingContent?: string;
workStartedAt?: string;
updatedAt?: string;
updatedBy: string;
}
@@ -250,7 +267,9 @@ export function canSaveRequirementCoverageDraft(
status: RequirementCoverageStatus,
completedContent?: string,
remainingContent?: string,
workStartedAt?: string,
): boolean {
if (!workStartedAt?.trim()) return false;
if (status === 'completed') return true;
if (status === 'partial') {
return Boolean(completedContent?.trim()) && Boolean(remainingContent?.trim());
@@ -290,11 +309,37 @@ export function getPlanLogsForPlans(plans: VersionPlan[]): VersionPlanLogView[]
.sort((a, b) => getPlanLogCreatedAtTime(b) - getPlanLogCreatedAtTime(a));
}
export function startRequirementCoverageWork(
plan: VersionPlan,
input: RequirementCoverageWorkStartInput,
): Pick<VersionPlan, 'requirementCoverage'> {
const startedAt = input.startedAt ?? new Date().toISOString();
const existing = getRequirementCoverage(plan, input.requirementId);
const nextCoverage: VersionPlanRequirementCoverage = {
requirementId: input.requirementId,
status: existing?.status ?? 'not_started',
completedContent: existing?.completedContent,
remainingContent: existing?.remainingContent,
currentWorkStartedAt: startedAt,
updatedAt: startedAt,
updatedBy: input.updatedBy,
};
return {
requirementCoverage: [
nextCoverage,
...(plan.requirementCoverage ?? []).filter((item) => item.requirementId !== input.requirementId),
],
};
}
export function updateRequirementCoverage(
plan: VersionPlan,
input: RequirementCoverageUpdateInput,
): Pick<VersionPlan, 'requirementCoverage' | 'completedRequirementIds' | 'logs'> {
const updatedAt = input.updatedAt ?? new Date().toISOString();
const existingCoverage = plan.requirementCoverage?.find((item) => item.requirementId === input.requirementId);
const workStartedAt = input.workStartedAt?.trim() || existingCoverage?.currentWorkStartedAt;
const nextCoverage: VersionPlanRequirementCoverage = {
requirementId: input.requirementId,
status: input.status,
@@ -333,10 +378,31 @@ export function updateRequirementCoverage(
coverageStatus: input.status,
completedContent: nextCoverage.completedContent,
remainingContent: nextCoverage.remainingContent,
workStartedAt,
}),
};
}
export function startResearchDirectionWork(
plan: VersionPlan,
input: ResearchDirectionWorkStartInput,
): Pick<VersionPlan, 'tasks'> {
const tasks = plan.tasks ?? [];
const startedAt = input.startedAt ?? new Date().toISOString();
const nextTasks = tasks.map((task) => {
if (task.id !== input.taskId || task.status === 'completed') return task;
return {
...task,
status: 'in_progress' as const,
currentWorkStartedAt: startedAt,
updatedAt: startedAt,
updatedBy: input.updatedBy,
};
});
return { tasks: nextTasks };
}
export function updateResearchDirectionProgress(
plan: VersionPlan,
input: ResearchDirectionProgressUpdateInput,
@@ -351,6 +417,7 @@ export function updateResearchDirectionProgress(
}
const updatedAt = input.updatedAt ?? new Date().toISOString();
const workStartedAt = input.workStartedAt?.trim() || target.currentWorkStartedAt;
const nextStatus: PlanTaskStatus = input.status === 'completed' ? 'completed' : 'in_progress';
const completedContent = input.status === 'partial' ? input.completedContent?.trim() || undefined : undefined;
const remainingContent = input.status === 'partial' ? input.remainingContent?.trim() || undefined : undefined;
@@ -360,6 +427,7 @@ export function updateResearchDirectionProgress(
status: nextStatus,
completedContent,
remainingContent,
currentWorkStartedAt: undefined,
updatedAt,
updatedBy: input.updatedBy,
}
@@ -382,6 +450,7 @@ export function updateResearchDirectionProgress(
coverageStatus: input.status,
completedContent,
remainingContent,
workStartedAt,
}),
};
}