feat(平台): 补齐服务端持久化和AI拆解契约
This commit is contained in:
76
apps/web/lib/version-plan-workflow.ts
Normal file
76
apps/web/lib/version-plan-workflow.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { VersionPlan } from './version-plan';
|
||||
|
||||
export interface PlanResultPayload {
|
||||
resultType?: 'link' | 'file';
|
||||
resultTitle?: string;
|
||||
resultUrl?: string;
|
||||
resultFileName?: string;
|
||||
resultFileData?: string;
|
||||
}
|
||||
|
||||
export interface PlanCompletionState {
|
||||
checklistTotal: number;
|
||||
checklistCompleted: number;
|
||||
requirementTotal: number;
|
||||
requirementCompleted: number;
|
||||
hasResult: boolean;
|
||||
canSubmitResult: boolean;
|
||||
canComplete: boolean;
|
||||
missingReasons: string[];
|
||||
}
|
||||
|
||||
export function hasPlanResult(plan: PlanResultPayload): boolean {
|
||||
const hasTitle = Boolean(plan.resultTitle?.trim());
|
||||
if (!hasTitle || !plan.resultType) return false;
|
||||
if (plan.resultType === 'link') return Boolean(plan.resultUrl?.trim());
|
||||
return Boolean(plan.resultFileData || plan.resultFileName);
|
||||
}
|
||||
|
||||
function requiresRequirementCoverage(plan: VersionPlan): boolean {
|
||||
return plan.type === 'product' || plan.type === 'ui';
|
||||
}
|
||||
|
||||
function requiresChecklist(plan: VersionPlan): boolean {
|
||||
return plan.type === 'research';
|
||||
}
|
||||
|
||||
export function getPlanCompletionState(plan: VersionPlan): PlanCompletionState {
|
||||
const tasks = plan.tasks ?? [];
|
||||
const checklistTotal = tasks.length;
|
||||
const checklistCompleted = tasks.filter((task) => task.status === 'completed').length;
|
||||
|
||||
const linked = plan.linkedRequirementIds ?? [];
|
||||
const completed = new Set(plan.completedRequirementIds ?? []);
|
||||
const requirementTotal = linked.length;
|
||||
const requirementCompleted = linked.filter((id) => completed.has(id)).length;
|
||||
|
||||
const missingReasons: string[] = [];
|
||||
if (requiresChecklist(plan) && checklistTotal === 0) missingReasons.push('缺少子任务');
|
||||
if (requiresChecklist(plan) && checklistTotal > 0 && checklistCompleted < checklistTotal) missingReasons.push('子任务未全部完成');
|
||||
if (requiresRequirementCoverage(plan) && requirementTotal > 0 && requirementCompleted < requirementTotal) {
|
||||
missingReasons.push('关联需求未全部覆盖');
|
||||
}
|
||||
|
||||
const canSubmitResult = missingReasons.length === 0;
|
||||
const hasResult = hasPlanResult(plan);
|
||||
if (canSubmitResult && !hasResult) missingReasons.push('尚未提交成果');
|
||||
|
||||
return {
|
||||
checklistTotal,
|
||||
checklistCompleted,
|
||||
requirementTotal,
|
||||
requirementCompleted,
|
||||
hasResult,
|
||||
canSubmitResult,
|
||||
canComplete: canSubmitResult && hasResult,
|
||||
missingReasons,
|
||||
};
|
||||
}
|
||||
|
||||
export function canTogglePlanChecklist(plan: VersionPlan, now: Date = new Date()): boolean {
|
||||
return plan.status === 'in_progress' || (plan.status === 'pending' && Boolean(plan.startTime) && new Date(plan.startTime) <= now);
|
||||
}
|
||||
|
||||
export function canEditPlanRequirementCoverage(plan: VersionPlan, now: Date = new Date()): boolean {
|
||||
return canTogglePlanChecklist(plan, now);
|
||||
}
|
||||
Reference in New Issue
Block a user