Files
ftb-project-management/apps/web/lib/dev-task.ts
Script Generator 10ffb874aa refactor(dev-task): 去掉"已完成"状态,"已提测"作为终态
DevTask 状态简化为:待开发 → 开发中 → 自测 → 已提测(终态)

- 提测即代表开发交付完成,不需要额外的"已完成"
- 测试通过/失败产生的是 Bug,不影响 DevTask 状态
- 移除 TestCaseStore 中的自动完成副作用(不再需要)
- 版本执行态推导规则同步更新

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-12 14:44:49 +08:00

107 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Priority } from './derive';
export type DevTaskStatus = 'todo' | 'in_progress' | 'testing' | 'submitted';
export interface DevTask {
id: string;
taskNo: string;
requirementId: string;
title: string;
description?: string;
categoryId: string;
assigneeId: string;
reviewerId?: string;
priority: Priority;
estimateHours: number;
actualHours: number;
startDate?: string;
dueDate?: string;
completedAt?: string;
status: DevTaskStatus;
isBlocked: boolean;
blockReason?: string;
blockedById?: string;
predecessorIds?: string[];
riskLevel?: 'low' | 'medium' | 'high';
overdueReason?: string;
createdBy: string;
createdAt: string;
updatedAt: string;
}
export const DEV_TASK_STATUS_LABEL: Record<DevTaskStatus, string> = {
todo: '待开发',
in_progress: '开发中',
testing: '自测',
submitted: '已提测',
};
export const DEV_TASK_STATUS_COLOR: Record<DevTaskStatus, string> = {
todo: 'bg-zinc-100 text-zinc-600',
in_progress: 'bg-blue-50 text-blue-600',
testing: 'bg-purple-50 text-purple-600',
submitted: 'bg-emerald-50 text-emerald-600',
};
export const STATUS_PROGRESS: Record<DevTaskStatus, number> = {
todo: 0,
in_progress: 50,
testing: 80,
submitted: 100,
};
export const ALLOWED_TRANSITIONS: Record<DevTaskStatus, DevTaskStatus[]> = {
todo: ['in_progress'],
in_progress: ['testing'],
testing: ['submitted', 'in_progress'],
submitted: [],
};
export function canTransition(from: DevTaskStatus, to: DevTaskStatus): boolean {
return ALLOWED_TRANSITIONS[from].includes(to);
}
export function calcTaskProgress(task: DevTask): number {
return STATUS_PROGRESS[task.status];
}
export function calcGroupProgress(tasks: DevTask[]): number {
if (tasks.length === 0) return 0;
const totalEstimate = tasks.reduce((sum, t) => sum + t.estimateHours, 0);
if (totalEstimate === 0) return 0;
const weighted = tasks.reduce((sum, t) => sum + t.estimateHours * STATUS_PROGRESS[t.status], 0);
return Math.round(weighted / totalEstimate);
}
export function formatHours(hours: number): string {
if (hours < 8) return `${hours}h`;
const days = Math.floor(hours / 8);
const remainder = hours % 8;
if (remainder === 0) return `${hours}h${days}人天)`;
return `${hours}h${days}人天)`;
}
export function calcActualHoursByDates(startDate?: string, completedAt?: string): number {
if (!startDate) return 0;
const end = completedAt || new Date().toISOString().slice(0, 10);
const start = new Date(startDate);
const endDate = new Date(end);
if (isNaN(start.getTime()) || isNaN(endDate.getTime()) || endDate < start) return 0;
let workDays = 0;
const current = new Date(start);
while (current <= endDate) {
const day = current.getDay();
if (day !== 0 && day !== 6) workDays++;
current.setDate(current.getDate() + 1);
}
return workDays * 8;
}
export function generateTaskNo(existingTasks: DevTask[]): string {
const maxNum = existingTasks.reduce((max, t) => {
const num = parseInt(t.taskNo.replace('DEV-', ''), 10);
return isNaN(num) ? max : Math.max(max, num);
}, 0);
return `DEV-${String(maxNum + 1).padStart(3, '0')}`;
}