feat(版本): 完善研发计划与预警已读
关键改动: - 增加版本表单、发布校验和调研方向进度规则 - 扩展小宝预警已读状态、风险签名和今日证据 - 补充组长权限、加班查看范围、活动记录与相关测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user