feat(平台): 补齐服务端持久化和AI拆解契约

This commit is contained in:
Script Generator
2026-06-25 15:21:32 +08:00
parent 5723356d08
commit 5adc7759ad
73 changed files with 5599 additions and 368 deletions

View 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);
}