import type { Priority } from './derive'; export type DevTaskStatus = 'todo' | 'in_progress' | 'testing' | 'submitted' | 'done'; 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 = { todo: '待开发', in_progress: '开发中', testing: '自测', submitted: '提测', done: '已完成', }; export const DEV_TASK_STATUS_COLOR: Record = { todo: 'bg-zinc-100 text-zinc-600', in_progress: 'bg-blue-50 text-blue-600', testing: 'bg-purple-50 text-purple-600', submitted: 'bg-orange-50 text-orange-600', done: 'bg-emerald-50 text-emerald-600', }; export const STATUS_PROGRESS: Record = { todo: 0, in_progress: 50, testing: 80, submitted: 90, done: 100, }; export const ALLOWED_TRANSITIONS: Record = { todo: ['in_progress'], in_progress: ['testing'], testing: ['submitted', 'in_progress'], submitted: ['done', 'in_progress'], done: [], }; 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')}`; }