之前用日期+工作日×8h 计算耗时太粗糙,现改为: - DevTask startDate/completedAt 使用完整 ISO 时间戳(含时分秒) - TestCase startedAt/completedAt 同上 - 耗时 = (endTimestamp - startTimestamp) / 3600000,精确到0.5h - 详情抽屉显示精确到分钟的时间(YYYY-MM-DD HH:mm) 例如:08:00 开始,10:30 提测,耗时 2.5h(不再是8h) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
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();
|
||
const startMs = new Date(startDate).getTime();
|
||
const endMs = new Date(end).getTime();
|
||
if (isNaN(startMs) || isNaN(endMs) || endMs < startMs) return 0;
|
||
const diffHours = (endMs - startMs) / (1000 * 60 * 60);
|
||
return Math.round(diffHours * 2) / 2; // 精确到0.5h
|
||
}
|
||
|
||
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')}`;
|
||
}
|