77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
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);
|
|
}
|