Files
ftb-project-management/apps/web/lib/dev-task.ts
Script Generator c86e87f95b fix: 实际耗时改为精确时间戳计算(精确到0.5h)
之前用日期+工作日×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>
2026-06-12 16:11:51 +08:00

101 lines
3.0 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();
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')}`;
}