feat(版本): 完善研发计划与预警已读

关键改动:

- 增加版本表单、发布校验和调研方向进度规则

- 扩展小宝预警已读状态、风险签名和今日证据

- 补充组长权限、加班查看范围、活动记录与相关测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-06-30 16:48:58 +08:00
parent 04520bd8c5
commit 3d3d56697a
41 changed files with 1701 additions and 121 deletions

View File

@@ -4,7 +4,7 @@ export type PlanTaskStatus = 'pending' | 'in_progress' | 'completed';
export type ProductPlanKind = 'design' | 'review';
export type ProductPlanReviewResult = 'passed' | 'failed';
export type RequirementCoverageStatus = 'not_started' | 'partial' | 'completed';
export type VersionPlanLogType = 'requirement_progress' | 'ai_decompose' | 'system';
export type VersionPlanLogType = 'requirement_progress' | 'research_direction_progress' | 'ai_decompose' | 'system';
export type AiDecomposeLogStatus = 'started' | 'completed' | 'error';
export type ProductPlanReviewFailureType =
| 'requirement_mismatch'
@@ -45,6 +45,30 @@ export interface PlanTask {
id: string;
title: string;
status: PlanTaskStatus;
completedContent?: string;
remainingContent?: string;
updatedAt?: string;
updatedBy?: string;
}
export const RESEARCH_DIRECTION_PRESETS = [
'\u7ade\u54c1\u5206\u6790',
'\u7528\u6237\u8bbf\u8c08',
'\u6570\u636e\u8c03\u7814',
'\u6280\u672f\u53ef\u884c\u6027\u5206\u6790',
'\u5e02\u573a\u8c03\u7814',
'\u9700\u6c42\u5206\u6790',
];
export function getResearchDirectionPresetOptions(tasks: Array<Pick<PlanTask, 'title'>>): Array<{
title: string;
disabled: boolean;
}> {
const selectedTitles = new Set(tasks.map((task) => task.title.trim()).filter(Boolean));
return RESEARCH_DIRECTION_PRESETS.map((title) => ({
title,
disabled: selectedTitles.has(title),
}));
}
export interface VersionPlanRequirementCoverage {
@@ -69,6 +93,8 @@ export interface VersionPlanLog {
coverageStatus?: RequirementCoverageStatus;
completedContent?: string;
remainingContent?: string;
directionTaskId?: string;
directionTitle?: string;
aiTarget?: AgentDecomposeTarget;
aiStatus?: AiDecomposeLogStatus;
}
@@ -84,6 +110,15 @@ export interface RequirementCoverageUpdateInput {
requirementTitle?: string;
}
export interface ResearchDirectionProgressUpdateInput {
taskId: string;
status: Extract<RequirementCoverageStatus, 'partial' | 'completed'>;
completedContent?: string;
remainingContent?: string;
updatedAt?: string;
updatedBy: string;
}
export type PlanLogDraft = Omit<VersionPlanLog, 'id' | 'createdAt'> & {
id?: string;
createdAt?: string;
@@ -100,6 +135,33 @@ export const REQUIREMENT_COVERAGE_LABEL: Record<RequirementCoverageStatus, strin
completed: '完全完成',
};
export function getResearchDirectionStatus(task: Pick<PlanTask, 'status'>): RequirementCoverageStatus {
if (task.status === 'completed') return 'completed';
if (task.status === 'in_progress') return 'partial';
return 'not_started';
}
export function getResearchDirectionProgressSummary(plan: VersionPlan): {
total: number;
completed: number;
partial: number;
notStarted: number;
percent: number;
} {
const tasks = plan.tasks ?? [];
const total = tasks.length;
const completed = tasks.filter((task) => getResearchDirectionStatus(task) === 'completed').length;
const partial = tasks.filter((task) => getResearchDirectionStatus(task) === 'partial').length;
const notStarted = Math.max(total - completed - partial, 0);
return {
total,
completed,
partial,
notStarted,
percent: total === 0 ? 0 : Math.round((completed / total) * 100),
};
}
export interface VersionPlan {
id: string;
versionId: string;
@@ -275,6 +337,55 @@ export function updateRequirementCoverage(
};
}
export function updateResearchDirectionProgress(
plan: VersionPlan,
input: ResearchDirectionProgressUpdateInput,
): Pick<VersionPlan, 'tasks' | 'logs'> {
const tasks = plan.tasks ?? [];
const target = tasks.find((task) => task.id === input.taskId);
if (!target) {
return {
tasks: plan.tasks,
logs: plan.logs,
};
}
const updatedAt = input.updatedAt ?? new Date().toISOString();
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;
const nextTasks = tasks.map((task) => task.id === input.taskId
? {
...task,
status: nextStatus,
completedContent,
remainingContent,
updatedAt,
updatedBy: input.updatedBy,
}
: task);
const detail = [
completedContent ? `已完成:${completedContent}` : '',
remainingContent ? `剩余:${remainingContent}` : '',
].filter(Boolean).join('\n');
return {
tasks: nextTasks,
logs: appendPlanLog(plan, {
type: 'research_direction_progress',
createdAt: updatedAt,
actor: input.updatedBy,
title: `${target.title}更新为${REQUIREMENT_COVERAGE_LABEL[input.status]}`,
detail: detail || undefined,
directionTaskId: target.id,
directionTitle: target.title,
coverageStatus: input.status,
completedContent,
remainingContent,
}),
};
}
function getPlanCreatedAtTime(plan: VersionPlan): number {
const time = new Date(plan.createdAt).getTime();
return Number.isFinite(time) ? time : 0;