关键改动: - 增加版本表单、发布校验和调研方向进度规则 - 扩展小宝预警已读状态、风险签名和今日证据 - 补充组长权限、加班查看范围、活动记录与相关测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { getRequirementCoverageSummary } from './version-plan';
|
|
import type { ProductPlanKind, ProductPlanReviewFailureType, ProductPlanReviewResult, VersionPlan } from './version-plan';
|
|
|
|
export interface PlanResultPayload {
|
|
type?: VersionPlan['type'];
|
|
productPlanKind?: ProductPlanKind;
|
|
resultType?: 'link' | 'file';
|
|
resultTitle?: string;
|
|
resultUrl?: string;
|
|
resultFileName?: string;
|
|
resultFileData?: string;
|
|
prototypeReviewConfirmed?: boolean;
|
|
reviewResult?: ProductPlanReviewResult;
|
|
reviewFailureTypes?: ProductPlanReviewFailureType[];
|
|
reviewFailureReason?: 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 {
|
|
if (plan.productPlanKind === 'review') {
|
|
if (plan.reviewResult === 'passed') return true;
|
|
if (plan.reviewResult === 'failed') {
|
|
return Boolean(plan.reviewFailureTypes?.length && plan.reviewFailureReason?.trim());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (plan.productPlanKind === 'design') {
|
|
return Boolean(
|
|
plan.resultType === 'link'
|
|
&& plan.resultTitle?.trim()
|
|
&& plan.resultUrl?.trim()
|
|
&& plan.prototypeReviewConfirmed,
|
|
);
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
function getProductPlanKind(plan: VersionPlan): ProductPlanKind | undefined {
|
|
if (plan.type !== 'product') return undefined;
|
|
return plan.productPlanKind ?? 'design';
|
|
}
|
|
|
|
export function getPlanCompletionState(plan: VersionPlan): PlanCompletionState {
|
|
const tasks = plan.tasks ?? [];
|
|
const checklistTotal = tasks.length;
|
|
const checklistCompleted = tasks.filter((task) => task.status === 'completed').length;
|
|
|
|
const requirementSummary = getRequirementCoverageSummary(plan);
|
|
const requirementTotal = requirementSummary.total;
|
|
const requirementCompleted = requirementSummary.completed;
|
|
|
|
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 productPlanKind = getProductPlanKind(plan);
|
|
const resultPlan = productPlanKind ? { ...plan, productPlanKind } : plan;
|
|
const hasResult = hasPlanResult(resultPlan);
|
|
if (canSubmitResult && !hasResult) {
|
|
if (productPlanKind === 'design') {
|
|
if (plan.resultType === 'file') missingReasons.push('产品设计方案只支持原型链接');
|
|
if (plan.resultType === 'link' && plan.resultUrl?.trim() && !plan.prototypeReviewConfirmed) {
|
|
missingReasons.push('请先确认方案评审已通过');
|
|
} else {
|
|
missingReasons.push('尚未提交原型链接');
|
|
}
|
|
} else if (productPlanKind === 'review') {
|
|
if (!plan.reviewResult) missingReasons.push('请选择评审结论');
|
|
if (plan.reviewResult === 'failed' && !plan.reviewFailureTypes?.length) missingReasons.push('请选择评审不通过类型');
|
|
if (plan.reviewResult === 'failed' && !plan.reviewFailureReason?.trim()) missingReasons.push('请填写评审不通过原因');
|
|
} else {
|
|
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);
|
|
}
|